15.uni-popup弹窗层制作弹出信息内容效果
<script setup>
import {
ref
} from 'vue';
const maskState = ref(true);
const infoPopup = ref(null);
//遮罩层状态
const maskChange = () => {
maskState.value = !maskState.value
}
//点击info弹窗
const clickInfo = () => {
infoPopup.value.open();
}
//点击关闭信息弹窗
const clickInfoClose = () => {
infoPopup.value.close();
}
</script>
<template>
<view class="preview">
<!-- 轮播图 -->
<swiper circular>
<swiper-item v-for="item in 5">
<image @click="maskChange" src="/common/images/preview_small.webp" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 遮罩层 -->
<view class="mask" v-if="maskState">
<!-- 返回按钮 -->
<!-- #ifndef MP-TOUTIAO -->
<view class="goBack">
<uni-icons type="back" color="#fff" size="20"></uni-icons>
</view>
<!-- #endif -->
<!-- 显示进度 动态数据-->
<view class="count">2/3</view>
<!-- 时间-->
<view class="time">
<uni-dateformat :date="new Date()" format="hh:mm"></uni-dateformat>
</view>
<!-- 日期 -->
<view class="date">
<uni-dateformat :date="new Date()" format="MM月dd日"></uni-dateformat>
</view>
<!-- 按钮 -->
<view class="footer">
<view class="box">
<uni-icons type="info" size="28"></uni-icons>
<view class="text">信息</view>
</view>
<view class="box">
<uni-icons type="star" size="28"></uni-icons>
<view class="text">99分</view>
</view>
<view class="box">
<uni-icons type="download" size="23"></uni-icons>
<view class="text">下载</view>
</view>
</view>
</view>
<uni-popup ref="infoPopup" type="bottom">
<view class="infoPopup">
<!-- 标题 退出x -->
<view class="popHeader">
<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>
<view class="content">
<view class="row">
<view class="label">壁纸ID:</view>
<text selectable class="value">123456</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">abc</text>
</view>
<view class="row">
<text class="label">评分:</text>
<view class='value roteBox'>
<uni-rate readonly touchable value="4" size="16" />
<text class="score">5分</text>
</view>
</view>
<view class="row">
<text class="label">摘要:</text>
<view class='value'>
XXXXX
</view>
</view>
<view class="row">
<text class="label">标签:</text>
<view class='value tabs'>
<view class="tab" v-for="tab in 3">
1
</view>
</view>
</view>
<view class="copyright">声明:</view>
<view class="safe-area-inset-bottom"></view>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<style lang="scss" scoped>
.preview {
width: 100%;
height: 100vh;
position: relative;
swiper {
width: 100%;
height: 100%;
image {
width: 100%;
height: 100%;
}
}
// 信息
.mask {
&>view {
//让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;
}
.count {
top: 10vh;
background: rgba(0, 0, 0, 0.3); //背景
font-size: 28rpx; //字号
border-radius: 40rpx; //圆角
padding: 8rpx 28rpx; //上下8 左右28
backdrop-filter: blur(10rpx); //模糊
}
.time {
font-size: 140rpx; //字体大小
top: calc(10vh + 80rpx); //计算函数
font-weight: 100; //字体加粗
line-height: 1em; //行高
text-shadow: 0 4rpx rgba(0, 0, 0, 0.3); //文字阴影
}
.date {
font-size: 34rpx;
top: calc(10vh + 230rpx);
text-shadow: 0 2rpx rgba(0, 0, 0, 0.3);
}
.footer {
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 0 rgba(0, 0, 0, 0.1); //阴影
backdrop-filter: blur(20rpx); //模糊
.box {
display: flex;
flex-direction: column; //文本行排列顺序、堆叠的方向
align-items: center;
justify-content: center;
padding: 2rpx 12rpx;
.text {
font-size: 26rpx;
color: $text-font-color-2;
}
}
}
}
// 标题 退出x
.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 {
// 壁纸id
.row {
display: flex;
padding: 16rpx 0;
font-size: 32rpx;
line-height: 1.7em; //行间距
// 文字(不可选中
.label {
color: $text-font-color-3;
width: 140rpx;
text-align: right;
font-size: 30rpx;
}
// 数据(可选
.value {
flex: 1;
width: 0;
}
.roteBox {
display: flex; //显示一行
align-items: center; //垂直居中
//文字分数
.score {
font-size: 26rpx;
color: $text-font-color-2;
padding-left: 10rpx;
}
}
// 标签组
.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: 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.6em;
}
}
}
}
}
</style>
展示:

16.评分弹出框uni-rate组件的属性方法
<script setup>
import {
ref
} from 'vue';
const maskState = ref(true);
const infoPopup = ref(null);
const scorePopup = ref(null);
const userScore = ref(0)
const classList = ref([]);
//遮罩层状态
const maskChange = () => {
maskState.value = !maskState.value
}
//点击info弹窗
const clickInfo = () => {
infoPopup.value.open();
}
//点击关闭信息弹窗
const clickInfoClose = () => {
infoPopup.value.close();
}
//评分弹窗
const clickScore = () => {
scorePopup.value.open();
}
//关闭评分框
const clickScoreClose = () => {
scorePopup.value.close();
}
//确认评分
const submitScore = async () => {
console.log(评分成功)
}
</script>
<template>
<view class="preview">
<!-- 轮播图 -->
<swiper circular>
<swiper-item v-for="item in 5">
<image @click="maskChange" src="/common/images/preview_small.webp" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 遮罩层 -->
<view class="mask" v-if="maskState">
<!-- 返回按钮 -->
<!-- #ifndef MP-TOUTIAO -->
<view class="goBack">
<uni-icons type="back" color="#fff" size="20"></uni-icons>
</view>
<!-- #endif -->
<!-- 显示进度 动态数据-->
<view class="count">2/3</view>
<!-- 时间-->
<view class="time">
<uni-dateformat :date="new Date()" format="hh:mm"></uni-dateformat>
</view>
<!-- 日期 -->
<view class="date">
<uni-dateformat :date="new Date()" format="MM月dd日"></uni-dateformat>
</view>
<!-- 按钮 -->
<view class="footer">
<view class="box" @click="clickInfo()">
<uni-icons type="info" size="28"></uni-icons>
<view class="text">信息</view>
</view>
<view class="box" @click="clickScore()">
<uni-icons type="star" size="28"></uni-icons>
<view class="text">99分</view>
</view>
<view class="box">
<uni-icons type="download" size="23"></uni-icons>
<view class="text">下载</view>
</view>
</view>
</view>
<uni-popup ref="infoPopup" type="bottom">
<view class="infoPopup">
<!-- 标题 退出x -->
<view class="popHeader">
<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>
<view class="content">
<view class="row">
<view class="label">壁纸ID:</view>
<text selectable class="value">123456</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">abc</text>
</view>
<view class="row">
<text class="label">评分:</text>
<view class='value roteBox'>
<uni-rate readonly touchable value="4" size="16" />
<text class="score">5分</text>
</view>
</view>
<view class="row">
<text class="label">摘要:</text>
<view class='value'>
XXXXX
</view>
</view>
<view class="row">
<text class="label">标签:</text>
<view class='value tabs'>
<view class="tab" v-for="tab in 3">
1
</view>
</view>
</view>
<view class="copyright">声明:</view>
<view class="safe-area-inset-bottom"></view>
</view>
</scroll-view>
</view>
</uni-popup>
<uni-popup ref="scorePopup" :is-mask-click="false">
<view class="scorePopup">
<view class="popHeader">
<view></view>
<view class="title">壁纸评分</view>
<view class="close" @click="clickScoreClose()">
<uni-icons type="closeempty" size="18" color="#999"></uni-icons>
</view>
</view>
<!-- 内容 -->
<view class="content">
<uni-rate v-model="userScore" allowHalf :disabled="isScore" disabled-color="#FFCA3E" />
<text class="text">{{userScore}}分</text>
</view>
<!-- 按钮 -->
<view class="footer">
<button @click="submitScore" :disabled="!userScore " type="default" size="mini" plain>确认评分</button>
</view>
</view>
</uni-popup>
</view>
</template>
<style lang="scss" scoped>
.preview {
width: 100%;
height: 100vh;
position: relative;
swiper {
width: 100%;
height: 100%;
image {
width: 100%;
height: 100%;
}
}
// 信息
.mask {
&>view {
//让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;
}
.count {
top: 10vh;
background: rgba(0, 0, 0, 0.3); //背景
font-size: 28rpx; //字号
border-radius: 40rpx; //圆角
padding: 8rpx 28rpx; //上下8 左右28
backdrop-filter: blur(10rpx); //模糊
}
.time {
font-size: 140rpx; //字体大小
top: calc(10vh + 80rpx); //计算函数
font-weight: 100; //字体加粗
line-height: 1em; //行高
text-shadow: 0 4rpx rgba(0, 0, 0, 0.3); //文字阴影
}
.date {
font-size: 34rpx;
top: calc(10vh + 230rpx);
text-shadow: 0 2rpx rgba(0, 0, 0, 0.3);
}
.footer {
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 0 rgba(0, 0, 0, 0.1); //阴影
backdrop-filter: blur(20rpx); //模糊
.box {
display: flex;
flex-direction: column; //文本行排列顺序、堆叠的方向
align-items: center;
justify-content: center;
padding: 2rpx 12rpx;
.text {
font-size: 26rpx;
color: $text-font-color-2;
}
}
}
}
// 标题 退出x
.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 {
// 壁纸id
.row {
display: flex;
padding: 16rpx 0;
font-size: 32rpx;
line-height: 1.7em; //行间距
// 文字(不可选中
.label {
color: $text-font-color-3;
width: 140rpx;
text-align: right;
font-size: 30rpx;
}
// 数据(可选
.value {
flex: 1;
width: 0;
}
.roteBox {
display: flex; //显示一行
align-items: center; //垂直居中
//文字分数
.score {
font-size: 26rpx;
color: $text-font-color-2;
padding-left: 10rpx;
}
}
// 标签组
.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: 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.6em;
}
}
}
}
.scorePopup {
background: #fff;
padding: 30rpx;
width: 70vw;
border-radius: 30rpx; //圆角
// 内容
.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; //右对齐
font-size: 28rpx;
}
}
.footer {
padding: 10rpx 0;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>
展示:

17.自定义头部导航栏布局、获取系统信息抽离公共方法
| statusBarHeight | 手机状态栏的高度 |
| getMenuButtonBoundingClientRect | 获取右上角悬浮按钮布局API 概述 |
创建 /utils/system.js
const SYSTEM_INFO = uni.getSystemInfoSync();
// 手机状态栏的高度
export const getStatusBarHeight = () => SYSTEM_INFO.statusBarHeight || 15;
export const getTitleBarHeight = () => {
// 如果有胶囊按钮
if (uni.getMenuButtonBoundingClientRect) {
// 获取右上角悬浮按钮布局
let {
top,
height
} = uni.getMenuButtonBoundingClientRect();
return height + (top - getStatusBarHeight()) * 2
} else {
return 40;
}
}
//导航高度
export const getNavBarHeight = () => getStatusBarHeight() + getTitleBarHeight();
// 针对抖音
export const getLeftIconLeft = () => {
// #ifdef MP-TOUTIAO
let {
leftIcon: {
left,
width
}
} = tt.getCustomButtonBoundingClientRect();
return left + parseInt(width);
// #endif
// #ifndef MP-TOUTIAO
return 0
// #endif
}

18.完善页面布局实现各个页面串联
创建/pages/notice/notice、/pages/notice/detail
/pages/notice/detail
<script setup>
</script>
<template>
<view class="noticeLayout">
<view class="title">
<view class="tag">
<uni-tag inverted text="置顶" type="error" />
</view>
<view class="font">标题</view>
</view>
<view class="info">
<view class="item">作者</view>
<view class="item">
<uni-dateformat :date="Date.now()" format="yyyy-MM-dd hh:mm:ss"></uni-dateformat>
</view>
</view>
<view class="content">
内容
</view>
<view class="count">
阅读量
</view>
</view>
</template>
<style lang="scss" scoped>
</style>

19.调用网络接口,并封装
创建/api/apis.js,/utils/request.js
/api/apis.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
})
}
export function apiGetClassify(data = {}) {
return request({
url: "/classify",
data
})
}
export function apiGetClassList(data = {}) {
return request({
url: "/wallList",
data
})
}
export function apiGetSetupScore(data = {}) {
return request({
url: "/setupScore",
data
})
}
export function apiWriteDownload(data = {}) {
return request({
url: "/downloadWall",
data
})
}
export function apiDetailWall(data = {}) {
return request({
url: "/detailWall",
data
})
}
export function apiUserInfo(data = {}) {
return request({
url: "/userInfo",
data
})
}
export function apiGetHistoryList(data = {}) {
return request({
url: "/userWallList",
data
})
}
export function apiNoticeDetail(data = {}) {
return request({
url: "/wallNewsDetail",
data
})
}
export function apiSearchData(data = {}) {
return request({
url: "/searchWall",
data
})
}
/utils/request.js
const BASE_URL = 'https://tea.qingnian8.com/api/bizhi';
export function request(config={}){
let {
url,
data={},
method="GET",
header={}
} = config
url = BASE_URL+url
header['access-key'] = "ccc#bbb"
return new Promise((resolve,reject)=>{
uni.request({
url,
data,
method,
header,
success:res=>{
if(res.data.errCode===0){
resolve(res.data)
}else if(res.data.errCode === 400){
uni.showModal({
title:"错误提示",
content:res.data.errMsg,
showCancel:false
})
reject(res.data)
}else{
uni.showToast({
title:res.data.errMsg,
icon:"none"
})
reject(res.data)
}
},
fail:err=>{
reject(err)
}
})
})
}
对比axios
vite.config

axios.js

商品评论API

商品评论
<script setup>
import { ref } from "vue"
import Search from "@/components/Search.vue";
import SearchItem from "@/components/SearchItem.vue";
import { toast } from "@/composables/util"
import {
getGoodsCommentList,
updateGoodsCommentStatus,
reviewGoodsComment
} from "@/api/goods_comment"
import { useInitTable } from '@/composables/useCommon.js'
const roles = ref([])
const {
searchForm,
resetSearchForm,
tableData,
loading,
currentPage,
total,
limit,
getData,
handleDelete,
handleStatusChange
} = useInitTable({
searchForm: {
title: ""
},
getList: getGoodsCommentList,
onGetListSuccess: (res) => {
tableData.value = res.list.map(o => {
o.statusLoading = false
// 默认处于无法编辑的状态
o.textareaEdit = false
return o
})
total.value = res.totalCount
roles.value = res.roles
},
updateStatus: updateGoodsCommentStatus
})
const textarea = ref("")
// 评论框
const openTextarea = (row, data = "") => {
textarea.value = data
// 处于编辑中
row.textareaEdit = true
}
const review = (row) => {
if (textarea.value == "") {
return toast("通知", "回复内容不能为空", "error")
}
reviewGoodsComment(row.id, textarea.value)
.then(res => {
row.textareaEdit = false
toast("通知", "回复成功", "success")
getData()
})
}
</script>
<template>
<el-card shadow="never" class="border-0">
<!-- 搜索 -->
<Search :model="searchForm" @search="getData" @reset="resetSearchForm">
<SearchItem label="关键词">
<el-input v-model="searchForm.title" placeholder="商品标题" clearable></el-input>
</SearchItem>
</Search>
<el-table default-expand-all :data="tableData" stripe style="width: 100%" v-loading="loading">
<el-table-column type="expand">
<template #default="{ row }">
<div class="flex pl-18">
<el-avatar :size="50" :src="row.user.avatar" fit="fill" class="mr-3"></el-avatar>
<div class="flex-1">
<!-- 回复 -->
<h6 class="flex items-center">
<!-- 渲染名称 -->
{{ row.user.nickname || row.user.username }}
<!-- 评论时间 -->
<small class=" text-gray-400 ml-2">{{ row.review_time }}</small>
<!-- 按钮 -->
<el-button size="small" class="ml-auto" @click="openTextarea(row)"
v-if="!row.textareaEdit && !row.extra">回复</el-button>
</h6>
<!-- 评论内容 -->
{{ row.review.data }}
<!-- 评论图片 -->
<div class="py-2">
<el-image v-for="(item,index) in row.review.image" :key="index" :src="item" fit="cover"
:lazy="true" style="width: 100px;height: 100px;" class="rounded"></el-image>
</div>
<div v-if="row.textareaEdit">
<el-input v-model="textarea" placeholder="请输入评价内容" type="textarea" :rows="2"></el-input>
<div class="py-2">
<el-button type="primary" size="small" @click="review(row)">回复</el-button>
<el-button size="small" class="ml-2"
@click="row.textareaEdit = false">取消</el-button>
</div>
</div>
<template v-else>
<div class="mt-3 bg-gray-100 p-3 rounded" v-for="(item,index) in row.extra"
:key="index">
<h6 class="flex font-bold">
客服
<el-button type="info" size="small" class="ml-auto"
@click="openTextarea(row,item.data)">修改</el-button>
</h6>
<!-- 客服回复内容 -->
<p>{{ item.data }}</p>
</div>
</template>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="ID" width="70" align="center" prop="id" />
<el-table-column label="商品" width="200">
<template #default="{ row }">
<div class="flex items-center">
<el-image :src="row.goods_item ? row.goods_item.cover : ''" fit="fill" :lazy="true"
style="width:50px;height:50px;" class="rounded"></el-image>
<div class="ml-3">
<h6>{{ row.goods_item?.title ?? '商品已被删除' }}</h6>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="评价信息" width="200">
<template #default="{ row }">
<div>
<p>用户:{{ row.user.nickname || row.user.username }}</p>
<p>
<el-rate v-model="row.rating" disabled show-score text-color="#ff9900" />
</p>
</div>
</template>
</el-table-column>
<el-table-column label="评价时间" width="180" align="center" prop="review_time" />
<el-table-column label="状态">
<template #default="{ row }">
<el-switch :modelValue="row.status" :active-value="1" :inactive-value="0"
:loading="row.statusLoading" :disabled="row.super == 1"
@change="handleStatusChange($event,row)">
</el-switch>
</template>
</el-table-column>
</el-table>
<div class="flex items-center justify-center mt-5">
<el-pagination background layout="prev, pager ,next" :total="total" :current-page="currentPage"
:page-size="limit" @current-change="getData" />
</div>
</el-card>
</template>

432

被折叠的 条评论
为什么被折叠?



