开发学习任务页,接我的收藏页接口

This commit is contained in:
田岩
2025-08-15 17:59:03 +08:00
parent a62a5f611e
commit 1f31ce6d17
25 changed files with 1427 additions and 267 deletions
+9
View File
@@ -33,4 +33,13 @@ export const queryPaperExamScore = (data) => {
};
// ↓↓↓↓↓↓↓↓↓课程考试↓↓↓↓↓↓↓↓
export const startExamByCrs = (data) => {
return request({
url: base_url + '/traCrsPapers/startExamByCrs',
method: 'post',
toastErrors: true,
data
});
};
+33
View File
@@ -0,0 +1,33 @@
import request from '@/api/request'
const base_url = '/trastudy'
// 课程收藏分页查询
export const queryTraPersonalCollectionCrsPaging = (data) => {
return request({
url: base_url + '/traPersonalCollection/queryTraPersonalCollectionCrsPaging',
method: 'post',
toastErrors: true,
data
});
};
// 任务收藏分页查询
export const queryTraPersonalCollectionTaskPaging = (data) => {
return request({
url: base_url + '/traPersonalCollection/queryTraPersonalCollectionTaskPaging',
method: 'post',
toastErrors: true,
data
});
};
// 根据收藏ID删除收藏
export const deleteTraPersonalCollectionById = (data) => {
return request({
url: base_url + '/traPersonalCollection/deleteTraPersonalCollectionById',
method: 'post',
toastErrors: true,
data
});
};
+22
View File
@@ -0,0 +1,22 @@
import request from '@/api/request'
const base_url = '/traexam'
// 错题本列表页
export const queryTraMistakesCollectionPaging = (data) => {
return request({
url: base_url + '/traMistakesCollection/queryTraMistakesCollectionPaging',
method: 'post',
toastErrors: true,
data
});
};
// 错题订正
export const mistakesCorrect = (data) => {
return request({
url: base_url + '/traMistakesCollection/mistakesCorrect',
method: 'post',
toastErrors: true,
data
});
};
+18
View File
@@ -217,6 +217,24 @@
}
}
},
{
"path": "pages/learningTasks/index",
"style": {
"navigationBarTitleText": "学习任务",
"app-plus": {
"titleNView": false
}
}
},
{
"path": "pages/learningTasks/details",
"style": {
"navigationBarTitleText": "学习详情",
"app-plus": {
"titleNView": false
}
}
},
{
"path": "pages/course/index",
"style": {
+16 -19
View File
@@ -99,7 +99,9 @@
import {
getCourseDetailApi
} from '@/api/courseDetail.js';
import {
startExamByCrs
} from '@/api/examination.js'
import common from '@/common/common';
import introduceVue from './components/introduce.vue';
import knowledge from './components/knowledge.vue';
@@ -124,6 +126,7 @@
const tabSwitch = (item) => {
tabAct.value = item.index;
};
// 上拉加载
const scrolltolower = (item) => {
if (tabAct.value == 2) {
commentRef.value?.getData();
@@ -161,25 +164,19 @@
tabAct.value = res.detail.current
}
// 去考试
const goto_examination = () => {
// papersId.value = e.papersId
// paperData.value = setPageCache('examination_'+papersId.value)
// if (!papersId.value || !paperData.value) {
// common.navigateBack()
// return
// }
const goto_examination = async () => {
common.loading()
startExamByCrs({
// crsId : form.value.crsId
crsId : 'CRS0250181220722025070408313900000018729'
}).then(res => {
setPageCache('examination', {...res.body, examId:detailData.examId})
common.navigateTo('/pages/examination/index')
}).finally(() => {
common.hideLoading()
})
}
// const getElementPosition = () => {
// return new Promise((resolve) => {
// uni.createSelectorQuery()
// .select('#tabsDiv')
// .boundingClientRect(rect => {
// if (rect) {
// resolve(rect) // 距离视口顶部的距离
// }
// }).exec()
// })
// }
onMounted(async () => {
uni.$on('refresh_comment_data', () => {
+239 -144
View File
@@ -9,125 +9,142 @@
<view class="back-icon"></view>
</view>
</view>
<view class="information-details-div">
<view class="top">
<view class="left">
<view class="top">得分</view>
<view class="mid">
<text v-if="pass_status===0" class="loading-pass">--</text>
<text v-if="pass_status===-1" class="no-pass">40</text>
<text v-if="pass_status===1" class="pass">50</text>
</view>
<view class="bottom">
<text v-if="pass_status===-1">失败乃成功之母继续努力</text>
<text v-if="pass_status===1">考的不错啊继续加油</text>
<text v-if="pass_status===0">考试结果计算中...</text>
<image v-if="pass_status===1" src="@/static/images/login/clap.png" mode="widthFix"
style="width: 32rpx;"></image>
</view>
</view>
<view class="right">
<view class="img">
<image v-if="pass_status===1" src="@/static/images/examination/qualified.png" mode="widthFix"
style="width: 270rpx;"></image>
<image v-if="pass_status===-1" src="@/static/images/examination/unqualified.png" mode="widthFix"
style="width: 270rpx;"></image>
<image v-if="pass_status===0" src="@/static/images/examination/getting_in.gif" mode="widthFix"
style="width: 270rpx;"></image>
</view>
</view>
</view>
<view class="bottom">
<!-- 考试标题 -->
<view class="title-div">
<view class="title-icon">
<image src="@/static/images/examination/examination-icon.png" mode="widthFix" class="img">
</image>
</view>
<view class="title">
考试这里是考试名称
</view>
</view>
<!-- 时间和考试按钮 -->
<view class="time-and-exam-button">
<view class="time-icon">
<image src="@/static/images/examination/duration-icon.png" mode="widthFix" class="img"></image>
</view>
<view class="time">
时长12 15
</view>
<view class="fl1">
</view>
<view class="examination">
<view>考试排行榜</view>
<view class="back-icon"></view>
</view>
</view>
</view>
</view>
<view class="main-div">
<view class="main-div-title">
答题详情
</view>
<!-- 获取答题结果时候的图片 -->
<view class="loading-pass-image-div" v-if="pass_status===0">
<image src="@/static/images/examination/getting_robot.png" mode="widthFix" class="img"></image>
<view class="tip">
你的等待会有更精确的结果要耐心哦~
</view>
</view>
<view class="sheet_table">
<view class="sheet_table_item" v-for="(item,index) in topicList">
<view class="circle">
{{index + 1}}
</view>
</view>
</view>
<view class="item" :class="getStatusClass(index)" v-for="(item,index) in dataList">
<view class="top">
<view class="type">
单选题
</view>
<view class="score">
5
</view>
<view class="fl1">
</view>
<view class="right">
<view class="right_text">
展开
</view>
<uv-icon class="arrow-icon" name="arrow-right"></uv-icon>
</view>
</view>
<view class="text">
属于商业银行风险管理三道防线的哪些风险管理部门为什么
</view>
<view class="additional_div">
<RadioSubject></RadioSubject>
</view>
</view>
</view>
<!-- <view class="scroll_div">
<swiper class="swiper" :current="tabAct" :indicator-dots="false" :disable-touch="true">
<swiper-item>
<view class="content">
<view class="expose_shadow"></view>
<scroll-view :scroll-y="true" class="scroll-Y" :show-scrollbar="false" :refresher-threshold="0">
<RadioSubject></RadioSubject>
</scroll-view>
</view>
<!-- <view class="sheet_table_div_fixed" v-if="sheet_table_status">
<swiper :indicator-dots="false" class="sheet_swiper" :current="sheet_swiper_index" @change="sheet_swiper_change">
<swiper-item v-for="(group, groupIndex) in groupedTopicList" :key="groupIndex">
<uv-grid :col="5" class="sheet_table" @click="sheet_click">
<uv-grid-item v-for="(item,index) in group" :key="index">
<view class="circle">
{{ groupIndex * 10 + index + 1 }}
</view>
</uv-grid-item>
</uv-grid>
</swiper-item>
</swiper>
<view class="swiper-dots">
</view>
</view> -->
</view>
<scroll-view :scroll-y="true" @scroll="scroll" :scroll-top="scrollTop" class="scroll scroll-Y"
:scroll-with-animation="true" :show-scrollbar="false">
<view class="information-details-div">
<view class="top">
<view class="left">
<view class="top">得分</view>
<view class="mid">
<text v-if="pass_status===0" class="loading-pass">--</text>
<text v-if="pass_status===-1" class="no-pass">40</text>
<text v-if="pass_status===1" class="pass">50</text>
</view>
<view class="bottom">
<text v-if="pass_status===-1">失败乃成功之母继续努力</text>
<text v-if="pass_status===1">考的不错啊继续加油</text>
<text v-if="pass_status===0">考试结果计算中...</text>
<image v-if="pass_status===1" src="@/static/images/login/clap.png" mode="widthFix"
style="width: 32rpx;"></image>
</view>
</view>
<view class="right">
<view class="img">
<image v-if="pass_status===1" src="@/static/images/examination/qualified.png"
mode="widthFix" style="width: 270rpx;"></image>
<image v-if="pass_status===-1" src="@/static/images/examination/unqualified.png"
mode="widthFix" style="width: 270rpx;"></image>
<image v-if="pass_status===0" src="@/static/images/examination/getting_in.gif"
mode="widthFix" style="width: 270rpx;"></image>
</view>
</view>
</view>
<view class="bottom">
<!-- 考试标题 -->
<view class="title-div">
<view class="title-icon">
<image src="@/static/images/examination/examination-icon.png" mode="widthFix" class="img">
</image>
</view>
<view class="title ellipsis-text">
考试这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称这里是考试名称
</view>
</view>
<!-- 时间和考试按钮 -->
<view class="time-and-exam-button">
<view class="time-icon">
<image src="@/static/images/examination/duration-icon.png" mode="widthFix" class="img">
</image>
</view>
<view class="time">
时长12 15
</view>
<view class="fl1">
</view>
<view class="examination">
<view>考试排行榜</view>
<view class="back-icon"></view>
</view>
</view>
</view>
</view>
<view class="main-div">
<!-- <uv-sticky id='tabsDiv' class="sticky"> -->
<view class="sheet_table_div">
<swiper :indicator-dots="false" class="sheet_swiper" :current="sheet_swiper_index" @change="sheet_swiper_change">
<!-- 循环渲染分组后的数组 -->
<swiper-item v-for="(group, groupIndex) in groupedTopicList" :key="groupIndex">
<uv-grid :col="5" class="sheet_table" @click="sheet_click">
<uv-grid-item v-for="(item,index) in group" :key="index">
<view class="circle">
{{ groupIndex * 10 + index + 1 }}
</view>
</uv-grid-item>
</uv-grid>
</swiper-item>
</swiper>
<view class="swiper-dots">
</view>
</view>
<!-- </uv-sticky> -->
<!-- 获取答题结果时候的图片 -->
<view class="loading-pass-image-div" v-if="pass_status===0">
<image src="@/static/images/examination/getting_robot.png" mode="widthFix" class="img"></image>
<view class="tip">
你的等待会有更精确的结果要耐心哦~
</view>
</view>
<view class="item" :id="'id_'+index" :class="getStatusClass(index)" v-for="(item,index) in dataList">
<view class="top">
<view class="type">
单选题
</view>
<view class="score">
5
</view>
<view class="fl1">
</view>
<!-- <view class="right">
<view class="right_text">
展开
</view>
<uv-icon class="arrow-icon" name="arrow-right"></uv-icon>
</view> -->
</view>
<view class="text">
属于商业银行风险管理三道防线的哪些风险管理部门为什么
</view>
<view class="additional_div">
<RadioSubject></RadioSubject>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
@@ -140,15 +157,61 @@
getCurrentInstance
} from 'vue';
import RadioSubject from '../wrong_question_record/components/radio-subject.vue'
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
const pass_status = ref(1)
const topicList = reactive([1, 2, 3, 4, 5, 6])
const sheet_swiper_index = ref(0)
const sheet_swiper_change = (event) => {
sheet_swiper_index.value = event.detail.current
}
const topicList = reactive([1, 2, 3, 4, 5, 6, 3, 4, 5, 6, 3, 4, 5, 6, 3, 4, 5, 6, 3, 4, 5, 6])
const dataList = reactive([1, 2, 3, 4, 5, 6])
// 答题卡计算属性
const groupedTopicList = computed(() => {
if (!topicList || topicList.length === 0) {
return [];
}
const groups = [];
for (let i = 0; i < topicList.length; i += 10) {
// 从索引i开始,截取最多10个元素
const group = topicList.slice(i, i + 10);
groups.push(group);
}
return groups;
});
const sheet_table_status = ref(false)
const scrollTop = ref(0)
// 答题卡点击
const sheet_click = (index) => {
scrollTop.value = index * 200
console.log('index', index);
// sheet_table_status.value = false
// nextTick(() => {
// sss.value = 'id_' + index
// nextTick(() => {
// sheet_table_status.value = true
// })
// })
}
const scroll = (event) => {
const top = event.detail.scrollTop
console.log('top', top);
if (top > 257) {
sheet_table_status.value = true
} else {
sheet_table_status.value = false
}
}
const getStatusClass = (index) => {
const remainder = index % 3;
if (remainder === 0) return 'in_progress';
if (remainder === 1) return 'completed';
return 'expired';
};
const animationfinish = (event) => {
console.log('event', event);
};
// 初始化时检查
onMounted(() => {
@@ -159,10 +222,20 @@
<style scoped lang="scss">
$bg-margin-left: 30rpx;
.scroll-Y {
// max-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx);
height: calc(100vh - var(--status-bar-height) - 70rpx - 10rpx);
// background-color: red;
}
.item {
border-radius: 16rpx;
margin: 32rpx 0;
padding: 34rpx 30rpx 34rpx 36rpx;
margin: 18rpx 0;
padding: 22rpx 30rpx 34rpx 36rpx;
background-color: #fff;
display: flex;
flex-direction: column;
@@ -222,30 +295,52 @@
}
.sheet_table {
margin:30rpx 48rpx 48rpx 48rpx;
display: flex;
flex-wrap: wrap;
width: calc(100vw - 96rpx - 56rpx);
// height: 20rpx;
align-content: space-between;
justify-content: space-between;
// background-color: red;
.sticky {}
.sheet_table_item {
width: calc((100vw - 96rpx) / 5 - 46rpx);
height: calc((100vw - 96rpx) / 5 - 46rpx);
margin: 16rpx;
margin-bottom: 16rpx;
border: 2rpx solid #E5E5E5;
border-radius: 50%;
.sheet_table_div_fixed {
position: absolute;
top: calc(var(--status-bar-height) + 70rpx);
left: 0;
z-index: 600;
height: 230rpx;
width: calc(100% - 56rpx);
margin: 0 28rpx 0 28rpx;
background-color: #fff;
// opacity: .5;
}
.sheet_table_div {
position: sticky;
left: 0;
top: 0;
width: 100%;
z-index: 600;
background-color: #fff;
}
.sheet_swiper {
height: 230rpx;
}
.swiper-dots {
height: 20rpx;
}
.sheet_table {
padding: 0 12rpx;
width: 100%;
display: flex;
.circle {
width: 78rpx;
height: 78rpx;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #999999;
border: 2rpx solid #E5E5E5;
margin: 20rpx;
}
.current {
@@ -275,9 +370,10 @@
border-color: #E8B531;
}
}
// 主内容框
.main-div {
display: flex;
flex-direction: column;
padding: 34rpx 0 30rpx 0;
@@ -285,7 +381,7 @@
border-radius: 16rpx;
position: relative;
background-color: #fff;
min-height: calc(100vh - var(--status-bar-height) - 70rpx - 380rpx - 100rpx);
// min-height: calc(100vh - var(--status-bar-height) - 70rpx - 380rpx - 100rpx);
.main-div-title {
width: 100%;
@@ -468,10 +564,7 @@
}
}
.scroll-Y {
max-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx);
min-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx - 380rpx);
}
.nav-div {
position: relative;
@@ -533,5 +626,7 @@
background-repeat: no-repeat;
min-height: 100vh;
margin: 0;
overflow: hidden;
position: relative;
}
</style>
@@ -1,47 +1,38 @@
<template>
<view class="scroll_div">
<scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="scrolltolower">
<view class="item" v-for="item in data_list">
<view class="img_div">
<image class="img" src="@/static/images/test/4.png" mode="scaleToFill"></image>
<view class="subscript">
7.9
</view>
</view>
<view class="right_div">
<view class="title ellipsis-text">
对公小程序开户操作流对公小程序开户操作流程
</view>
<view class="tag_div">
<view class="tag">
公金业务
<scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="scrolltolower" :show-scrollbar="false">
<uv-swipe-action :autoClose="true">
<uv-swipe-action-item class="item-swipe-action" :options="options" :threshold="40" :index="index"
:name="index" v-for="(item,index) in data_list" @click="swipeActionClick">
<view class="item" @touchmove.stop>
<view class="img_div">
<image-preview class="img" :src="item?.ossAddr" mode="scaleToFill"></image-preview>
<view class="subscript">
{{item.evalGrade ?? 0}}
</view>
</view>
<view class="tag">
中级
</view>
<view class="tag">
中级
</view>
<view class="tag">
中级
</view>
<view class="tag">
中级
</view>
<view class="tag">
中级
<view class="right_div">
<view class="title ellipsis-text">
{{item.crsName}}
</view>
<view class="tag_div">
<view class="tag" v-for="tagItem in item?.tagCataList ?? []">
{{tagItem?.tagName}}
</view>
</view>
<view class="time_and_num">
<view class="mr-10">
发布{{item.issuTm?.substr(0, 10)}}
</view>
<view>
已学{{item.accmStdyCnt}}
</view>
</view>
</view>
</view>
<view class="time_and_num">
<view class="mr-10">
发布2025-05-13
</view>
<view>
已学22345
</view>
</view>
</view>
</view>
</uv-swipe-action-item>
</uv-swipe-action>
<list-no-data v-if="total == 0 && status=='nomore'"></list-no-data>
<uv-load-more v-if="total != 0" :status="status" loadmore-text="轻轻上拉加载" :height="30"></uv-load-more>
</scroll-view>
</view>
@@ -59,8 +50,16 @@
nextTick
} from 'vue'
import {
queryTraCrsBbsInfoByCrsId
} from '@/api/courseDetail.js';
queryTraPersonalCollectionCrsPaging,
deleteTraPersonalCollectionById
} from '@/api/favorites.js';
import common from '@/common/common';
const options = [{
text: '删除',
style: {
backgroundColor: '#f56c6c'
}
}]
const getData = (from = '') => {
if (from == 'first') {
if (total.value !== -1) {
@@ -70,17 +69,14 @@
total.value = -1
page.value = 1
data_list.length = 0
} else {
if (status.value !== 'loadmore' && status.value !== '') return
status.value = 'loading'
page.value++
}
console.log(status.value, '阿斯顿撒');
setTimeout(() => {
queryTraCrsBbsInfoByCrsId({
crsId: 'CRS0250181220722025070408313900000018729',
queryTraPersonalCollectionCrsPaging({
page: page.value,
limit: limit
}).then(res => {
@@ -95,6 +91,21 @@
})
}, 200)
}
const swipeActionClick = (click_item) => {
const index = click_item['name']
const item = data_list[index]
common.show('提示', '确认删除吗?').then(res => {
if (!res) return;
common.loading()
const collectId = '11'
deleteTraPersonalCollectionById({collectId:item.collectId}).then(()=>{
data_list.splice(index, 1); // 从列表移除
total.value -= 1 // 更新下总数
}).finally(() => {
common.hideLoading()
})
})
}
const scrolltolower = (item) => {
getData()
};
@@ -104,6 +115,8 @@
</script>
<style scoped lang="scss">
.scroll_div {
background-color: #F1F5FA;
padding: 22rpx;
@@ -114,21 +127,33 @@
background-color: #fff;
border-radius: 16rpx;
}
.item-swipe-action {
margin: 32rpx 22rpx;
overflow: hidden;
:deep(.uv-swipe-action-item__right__button__wrapper) {
border-radius: 20rpx !important;
}
}
.scroll-Y .item {
height: 170rpx;
margin: 32rpx 22rpx;
display: flex;
// background-color: red;
.img_div {
position: relative;
width: 224rpx;
height: 168rpx;
overflow: hidden;
border-radius: 22rpx;
.img {
width: 224rpx;
height: 168rpx;
}
.subscript {
position: absolute;
right: 0;
@@ -143,7 +168,7 @@
align-items: center;
justify-content: center;
background-color: #EF5705;
color: #FFFFFF ;
color: #FFFFFF;
}
}
@@ -154,7 +179,7 @@
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
font-family: PingFangSC;
font-weight: 500;
@@ -168,6 +193,7 @@
flex-wrap: wrap;
height: 38rpx;
overflow: hidden;
.tag {
height: 38rpx;
padding: 0 10rpx;
@@ -3,7 +3,7 @@
<scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="scrolltolower">
<view class="item" :class="getStatusClass(index)" v-for="(item,index) in data_list">
<view class="img_div">
<image class="img" src="@/static/images/test/4.png" mode="scaleToFill"></image>
<image-preview class="img" :src="item?.ossAddr" mode="scaleToFill"></image-preview>
<view class="subscript" v-if="index % 3==0">
进行中
</view>
@@ -16,21 +16,22 @@
</view>
<view class="right_div">
<view class="title ellipsis-text">
对公小程序开户操作流对公小程序开户操作流程
{{item.taskName}}
</view>
<view class="time_div">
起止时间<text class="blod_color">2025-05-13至2025-08-13</text>
起止时间<text class="blod_color">{{item.startTm?.substr(0, 10)}}{{item.endTm?.substr(0, 10)}}</text>
</view>
<view class="people_progress_div">
<view class="mr-10">
完成人数<text class="blod_color">34/125</text>
完成人数<text class="blod_color">{{item.taskFinishSums ?? 0}}/{{item.taskSums ?? 0}}</text>
</view>
<view>
我的进度<text class="blod_color">25%</text>
我的进度<text class="blod_color">{{item.myProcess ?? 0}}%</text>
</view>
</view>
</view>
</view>
<list-no-data v-if="total == 0 && status=='nomore'"></list-no-data>
<uv-load-more v-if="total != 0" :status="status" loadmore-text="轻轻上拉加载" :height="30"></uv-load-more>
</scroll-view>
</view>
@@ -48,8 +49,8 @@
nextTick
} from 'vue'
import {
queryTraCrsBbsInfoByCrsId
} from '@/api/courseDetail.js';
queryTraPersonalCollectionTaskPaging
} from '@/api/favorites.js';
const getData = (from = '') => {
if (from == 'first') {
if (total.value !== -1) {
@@ -67,8 +68,7 @@
console.log(status.value, '阿斯顿撒');
setTimeout(() => {
queryTraCrsBbsInfoByCrsId({
crsId: 'CRS0250181220722025070408313900000018729',
queryTraPersonalCollectionTaskPaging({
page: page.value,
limit: limit
}).then(res => {
@@ -148,7 +148,7 @@
color: #D70C18;
}
}
.scroll-Y .item {
height: 170rpx;
margin: 32rpx 22rpx;
+18 -18
View File
@@ -13,10 +13,10 @@
:scrollable="false" line-color="#0066FF"
:inactive-style="{ color: '#333333', fontSize: '30rpx', fontWeight:'600'}"
:activeStyle="{color:'#0066FF',fontSize:'30rpx', fontWeight:'600'}"
:lineColor="`url(${lineBg}) 10% 10%`"></uv-tabs>
:lineColor="`url(${LineBg}) 10% 10%`"></uv-tabs>
</view>
<view class="uni-margin-wrap no-overflow">
<swiper class="swiper" :current="tabAct" :indicator-dots="false" @change="swiperChange">
<swiper class="swiper" :current="tabAct" :indicator-dots="false" :disable-touch="true" @change="swiperChange">
<swiper-item>
<FavoritesCourse ref="courseRef"></FavoritesCourse>
</swiper-item>
@@ -39,17 +39,13 @@
computed,
nextTick,
onMounted,
watch
watch
} from 'vue';
import {
queryTraCrsBbsInfoByCrsId,
replyTraCrsBbsInfo,
qryChildTraCrsBbsInfo
} from '@/api/courseDetail.js';
LineBg
} from '@/enum.js'
import common from '@/common/common';
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
const lineBg =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAABL0lEQVQ4T6XSv0uCURTG8e9jgpRNDS1BU1sELUFLQVM1tgQFtcR7A8m1LQqaLGhOkQKH/o5aKiiHlgiE0K1oCYLAMk9ov/RF8e313u2ew4dzzzmi2+NsA9gH3hCr6srzbAJxDkS/nbvwYNJilMkDow1FXYUHneWAlV/MqN3ZcKCzHWDb164MGa3/H/Qshdj0YbdUmORIL8HBNRsgShZjoQkznvioY/e19wCgCccSsAcM+bBnjHmyuvx5bw8mrJ93FoEkYrzFej0g5kjrpjH2BTrro8oIYhgYQ0xhTCPibfb0ggjLHKrojwvPEogDIBZgycsYKQrscqpKq3zh7BXo7YBVgRMibLWqqvnLzh6BwTZgESNHD8edoL+hOJupT9CII0oYJcQ14oy0CgHa0JTyCeBLVNeWygE5AAAAAElFTkSuQmCC";
const courseRef = ref(null);
const taskRef = ref(null);
const tabAct = ref(0);
@@ -58,7 +54,7 @@
}, {
name: '任务收藏'
}]);
const tabChange = (item) => {
tabAct.value = item.index;
};
@@ -66,13 +62,15 @@
tabAct.value = item.detail.current;
};
onMounted(() => {
watch(tabAct, (newValue, oldValue) => {
if(newValue === 0) {
courseRef.value?.getData('first')
} else if(newValue === 1) {
taskRef.value?.getData('first')
}
}, { immediate: true }) // 这里的immediate会在onMounted之后执行
watch(tabAct, (newValue, oldValue) => {
if (newValue === 0) {
courseRef.value?.getData('first')
} else if (newValue === 1) {
taskRef.value?.getData('first')
}
}, {
immediate: true
}) // 这里的immediate会在onMounted之后执行
})
</script>
@@ -80,6 +78,7 @@
$bg-margin-left: 50rpx;
.my_tabs {
// 解决这个圆角的图片,开穿透
:deep(.uv-tabs__wrapper__nav__line) {
height: 26rpx !important;
@@ -88,7 +87,7 @@
background-repeat: no-repeat !important;
}
}
.swiper {
height: calc(100vh - var(--status-bar-height) - 54rpx - 88rpx - 20rpx - 10rpx);
}
@@ -132,6 +131,7 @@
align-items: center;
justify-content: center;
background-color: #fff;
.back_div {
position: absolute;
left: 20rpx;
+2 -2
View File
@@ -89,11 +89,11 @@
<view class="title">AI问答</view>
</view>
<view class="item" @click="common.navigateTo('/pages/examination/result?execId=1')">
<view class="item">
<image class="icon" src="@/static/images/index/navigation_ai.png"></image>
<view class="title">AI陪练</view>
</view>
<view class="item">
<view class="item" @click="common.navigateTo('/pages/learningTasks/index')">
<image class="icon" src="@/static/images/index/navigation_learning_tasks.png"></image>
<view class="title">学习任务</view>
</view>
@@ -0,0 +1,232 @@
<template>
<view class="scroll_div">
<scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="scrolltolower" :show-scrollbar='false'>
<view class="item" :class="getStatusClass(index)" v-for="(item,index) in data_list">
<view class="img_div">
<image class="img" src="@/static/images/test/4.png" mode="scaleToFill"></image>
<view class="subscript" v-if="index % 3==0">
进行中
</view>
<view class="subscript" v-if="index % 3==1">
已完成
</view>
<view class="subscript" v-if="index % 3==2">
已超时
</view>
</view>
<view class="right_div">
<view class="title ellipsis-text">
JBank银行服务礼仪与投诉...
</view>
<view class="time_div">
起止时间<text class="blod_color">2025-05-13至2025-05-13</text>
</view>
<view class="people_progress_div">
<view class="mr-10">
完成人数<text class="">{{item.taskFinishSums ?? 0}}/{{item.taskSums ?? 0}}</text>
</view>
<view>
进度<text class="">2/4门课程</text>
</view>
</view>
</view>
</view>
<uv-load-more v-if="total != 0" :status="status" loadmore-text="轻轻上拉加载" :height="30"></uv-load-more>
</scroll-view>
</view>
</template>
<script setup>
const status = ref('loading') // loadmore - 加载前,loading - 加载中,nomore - 没有数据
const limit = 15
const total = ref(-1)
const page = ref(0)
const data_list = reactive([])
import {
ref,
reactive,
onMounted,
nextTick
} from 'vue'
import {
queryTraCrsBbsInfoByCrsId,
} from '@/api/courseDetail.js';
const getData = (from = '') => {
if (from == 'first') {
if (total.value !== -1) {
return
}
status.value = 'loading'
total.value = -1
page.value = 1
data_list.length = 0
} else {
if (status.value !== 'loadmore' && status.value !== '') return
status.value = 'loading'
page.value++
}
setTimeout(() => {
queryTraCrsBbsInfoByCrsId({
crsId: 'CRS0250181220722025070408313900000018729',
page: page.value,
limit: limit
}).then(res => {
total.value = res.total
data_list.push(...res.body)
}).finally(() => {
if (data_list.length >= total.value) {
status.value = 'nomore'
} else {
status.value = 'loadmore'
}
console.log(status.value, '阿斯顿撒');
})
}, 200)
}
const getStatusClass = (index) => {
const remainder = index % 3;
if (remainder === 0) return 'in_progress';
if (remainder === 1) return 'completed';
return 'expired';
};
const scrolltolower = (item) => {
getData()
};
defineExpose({
getData
})
</script>
<style scoped lang="scss">
.scroll_div {
background-color: #F1F5FA;
padding: 22rpx;
}
.scroll-Y {
height: calc(100vh - var(--status-bar-height) - 88rpx - 80rpx - 88rpx - 20rpx - 30rpx);
// height: calc(100vh - var(--status-bar-height) - 54rpx - 88rpx - 20rpx - 44rpx);
background-color: #fff;
border-radius: 16rpx;
}
.scroll-Y {
.item.in_progress {
// 进行中
.subscript {
color: #0066FF;
background-color: #DAF0FF;
}
.blod_color {
color: #0066FF;
}
}
.item.completed {
// 已完成
.subscript {
color: #FFFFFF;
background-color: #33C583 !important;
}
.blod_color {
color: #0066FF;
}
}
.item.expired {
// 已超时
.subscript {
color: #FFFFFF;
background-color: #EF5705;
}
.blod_color {
color: #D70C18;
}
}
}
.scroll-Y .item {
height: 170rpx;
margin: 32rpx 22rpx;
display: flex;
.img_div {
position: relative;
width: 224rpx;
height: 168rpx;
overflow: hidden;
border-radius: 22rpx;
.img {
width: 226rpx;
height: 168rpx;
}
.subscript {
position: absolute;
right: 0;
top: 0;
width: 92rpx;
height: 46rpx;
border-radius: 0px 0px 0px 22rpx;
font-family: PingFangSC;
font-weight: 400;
font-size: 22rpx;
line-height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
}
.right_div {
flex: 1;
overflow: hidden;
margin-left: 24rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
font-family: PingFangSC;
font-size: 30rpx;
font-weight: 600;
color: #333333;
line-height: 44rpx;
}
.time_div {
font-family: PingFangSC;
font-weight: 400;
font-size: 23rpx;
color: #666;
line-height: 23rpx;
flex-wrap: nowrap;
height: 23rpx;
overflow: hidden;
}
.people_progress_div {
display: flex;
font-family: PingFangSC;
font-weight: 400;
font-size: 23rpx;
color: #666;
margin-bottom: 23rpx;
line-height: 23rpx;
flex-wrap: nowrap;
height: 23rpx;
overflow: hidden;
}
}
}
</style>
+8
View File
@@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>
+208
View File
@@ -0,0 +1,208 @@
<template>
<view class="max_page detail-main">
<nav-bar :is_seat="false"></nav-bar>
<view class="fixed_search_div">
<view class="back_div" @click="common.navigateBack()">
<view class="back-icon"></view>
</view>
<view class="title font_pf">学习任务</view>
</view>
<view class="fixed_search_input_div">
<uv-input v-model="inputValue" shape="" :customStyle="fixedCustomStyles"
placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入任务名" suffixIcon="search"
:suffixIconStyle="suffixIconStyle">
</uv-input>
</view>
<view class="detail-body">
<view class="body-tab">
<uv-tabs class="font_pf my_tabs" :current="tabAct" :list="tabList" @change="tabChange"
:scrollable="false" line-color="#0066FF"
:inactive-style="{ color: '#333333', fontSize: '30rpx', fontWeight:'600'}"
:activeStyle="{color:'#0066FF',fontSize:'30rpx', fontWeight:'600'}"
:lineColor="`url(${LineBg}) 10% 10%`"></uv-tabs>
</view>
<view class="uni-margin-wrap no-overflow">
<swiper class="swiper" :current="tabAct" :indicator-dots="false" @change="swiperChange">
<swiper-item>
<listItem :ref="el => listItemRefs[0] = el" status="01"></listItem>
</swiper-item>
<swiper-item>
<listItem :ref="el => listItemRefs[1] = el" status="01"></listItem>
</swiper-item>
<swiper-item>
<listItem :ref="el => listItemRefs[2] = el" status="01"></listItem>
</swiper-item>
</swiper>
</view>
</view>
</view>
</template>
<script setup>
import {
LineBg
} from '@/enum.js'
import listItem from './components/list-item.vue'
// 我的收藏
import {
reactive,
ref,
computed,
nextTick,
onMounted,
watch
} from 'vue';
import common from '@/common/common';
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
// 静态样式配置
const fixedCustomStyles = {
backgroundColor: "#F1F5FA",
paddingLeft: "40rpx",
paddingTop: "5px",
paddingBottom: "5px",
border: "none"
}
const suffixIconStyle = {
color: "#2c8ef2",
fontSize: "28px"
}
const listItemRefs = ref([]);
const taskRef = ref(null);
const tabAct = ref(0);
const inputValue = ref('');
const tabList = ref([{
name: '全 部'
}, {
name: '已完成'
}, {
name: '未完成'
}]);
const tabChange = (item) => {
tabAct.value = item.index;
};
const swiperChange = (item) => {
tabAct.value = item.detail.current;
};
onMounted(() => {
watch(tabAct, (newValue, oldValue) => {
typeof listItemRefs.value[newValue]?.getData === 'function' && listItemRefs.value[newValue]
?.getData('first')
}, {
immediate: true
}) // 这里的immediate会在onMounted之后执行
})
</script>
<style lang="scss" scoped>
$bg-margin-left: 50rpx;
.my_tabs {
// 解决这个圆角的图片,开穿透
:deep(.uv-tabs__wrapper__nav__line) {
height: 26rpx !important;
left: 60rpx;
background-size: 28rpx !important;
background-repeat: no-repeat !important;
}
}
.swiper {
/*
状态栏 var(--status-bar-height)
标题高度88rpx
搜索栏高度80rpx
tab切换栏高度88rpx
下方滑动区域上下边距各10共20rpx
**/
height: calc(100vh - var(--status-bar-height) - 88rpx - 80rpx - 88rpx - 20rpx);
}
.swiper-item {
display: block;
height: 100%;
line-height: 300rpx;
text-align: center;
}
.detail-body {
background: #ffffff;
border-radius: 15rpx;
z-index: 101;
overflow: hidden;
.body-tab {
height: 88rpx;
}
}
.detail-main {
background-color: #F6F8FF;
overflow: hidden;
}
.title {
color: #000000;
font-size: 34rpx;
height: 34rpx;
font-weight: 600;
line-height: 34rpx;
}
.fixed_search_input_div{
width: 100%;
height: 80rpx;
padding: 0 22rpx;
// background-color: red;
background-color: #fff;
:deep(.uv-input.uv-input--radius) {
border-radius:16rpx !important;
}
}
.fixed_search_div {
padding: calc(20rpx + var(--status-bar-height)) 15rpx 34rpx 30rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
background-color: #fff;
// background-color: red;
// height: 180rpx;
position: relative;
.back_div {
position: absolute;
left: 20rpx;
top: var(--status-bar-height);
padding: 10rpx;
.back-icon {
position: relative;
width: 50rpx;
height: 50rpx;
cursor: pointer;
display: flex;
align-items: center;
}
.back-icon::before,
.back-icon::after {
content: '';
position: absolute;
transition: all 0.3s ease;
}
.back-icon::after {
top: 25%;
left: 25%;
width: 40%;
height: 40%;
border-top: 4rpx solid #000000;
border-left: 4rpx solid #000000;
transform: rotate(-45deg);
}
}
}
</style>
+4 -3
View File
@@ -13,7 +13,7 @@
:scrollable="false" line-color="#0066FF"
:inactive-style="{ color: '#333333', fontSize: '30rpx', fontWeight:'600'}"
:activeStyle="{color:'#0066FF',fontSize:'30rpx', fontWeight:'600'}"
:lineColor="`url(${lineBg}) 10% 10%`"></uv-tabs>
:lineColor="`url(${LineBg}) 10% 10%`"></uv-tabs>
</view>
<view class="uni-margin-wrap">
<swiper class="swiper" :current="tabAct" :indicator-dots="false" @change="swiperChange">
@@ -48,8 +48,9 @@
} from '@/api/courseDetail.js';
import common from '@/common/common';
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
const lineBg =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAABL0lEQVQ4T6XSv0uCURTG8e9jgpRNDS1BU1sELUFLQVM1tgQFtcR7A8m1LQqaLGhOkQKH/o5aKiiHlgiE0K1oCYLAMk9ov/RF8e313u2ew4dzzzmi2+NsA9gH3hCr6srzbAJxDkS/nbvwYNJilMkDow1FXYUHneWAlV/MqN3ZcKCzHWDb164MGa3/H/Qshdj0YbdUmORIL8HBNRsgShZjoQkznvioY/e19wCgCccSsAcM+bBnjHmyuvx5bw8mrJ93FoEkYrzFej0g5kjrpjH2BTrro8oIYhgYQ0xhTCPibfb0ggjLHKrojwvPEogDIBZgycsYKQrscqpKq3zh7BXo7YBVgRMibLWqqvnLzh6BwTZgESNHD8edoL+hOJupT9CII0oYJcQ14oy0CgHa0JTyCeBLVNeWygE5AAAAAElFTkSuQmCC";
import {
LineBg
} from '@/enum.js'
const courseRef = ref(null);
const aiSparringRef = ref(null);
const tabAct = ref(0);
+18 -17
View File
@@ -9,8 +9,7 @@
<view class="input-view">
<uv-input shape="circle" placeholderStyle='color:#999999;font-size:28rpx' placeholder="请输入关键词"
suffixIcon="search" :customStyle="customStyle" :suffixIconStyle="suffixIconStyle" confirmType="search"
v-model="searchText"
@confirm="search">
v-model="searchText" @confirm="search">
</uv-input>
<view class="search_div" @click="search"></view>
</view>
@@ -18,18 +17,21 @@
<uv-tabs class="font_pf my_tabs" :current="tabAct" :list="tabList" @change="tabChange" :scrollable="false"
line-color="#0066FF" :inactive-style="{ color: '#333333', fontSize: '30rpx', fontWeight:'600'}"
:activeStyle="{color:'#0066FF',fontSize:'30rpx', fontWeight:'600'}"
:lineColor="`url(${lineBg}) 10% 10%`"></uv-tabs>
:lineColor="`url(${LineBg}) 10% 10%`"></uv-tabs>
</view>
<swiper class="swiper" :current="tabAct" :indicator-dots="false" @change="swiperChange">
<swiper-item>
<Content :ref="el => contentRefs[0] = el" :index="tabAct" :searchText="searchText" @gotoDetails="gotoDetails"></Content>
<Content :ref="el => contentRefs[0] = el" :index="tabAct" :searchText="searchText"
@gotoDetails="gotoDetails"></Content>
</swiper-item>
<swiper-item>
<Content :ref="el => contentRefs[1] = el" :index="tabAct" :searchText="searchText" @gotoDetails="gotoDetails"></Content>
<Content :ref="el => contentRefs[1] = el" :index="tabAct" :searchText="searchText"
@gotoDetails="gotoDetails"></Content>
</swiper-item>
<swiper-item>
<Content :ref="el => contentRefs[2] = el" :index="tabAct" :searchText="searchText" @gotoDetails="gotoDetails"></Content>
<Content :ref="el => contentRefs[2] = el" :index="tabAct" :searchText="searchText"
@gotoDetails="gotoDetails"></Content>
</swiper-item>
</swiper>
</view>
@@ -47,7 +49,9 @@
import {
onLoad
} from '@dcloudio/uni-app'
import {setPageCache} from '@/common/common';
import {
setPageCache
} from '@/common/common';
import common from '@/common/common';
const customStyle = {
backgroundColor: "#F1F5FA",
@@ -63,9 +67,9 @@
}
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
const lineBg =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAABL0lEQVQ4T6XSv0uCURTG8e9jgpRNDS1BU1sELUFLQVM1tgQFtcR7A8m1LQqaLGhOkQKH/o5aKiiHlgiE0K1oCYLAMk9ov/RF8e313u2ew4dzzzmi2+NsA9gH3hCr6srzbAJxDkS/nbvwYNJilMkDow1FXYUHneWAlV/MqN3ZcKCzHWDb164MGa3/H/Qshdj0YbdUmORIL8HBNRsgShZjoQkznvioY/e19wCgCccSsAcM+bBnjHmyuvx5bw8mrJ93FoEkYrzFej0g5kjrpjH2BTrro8oIYhgYQ0xhTCPibfb0ggjLHKrojwvPEogDIBZgycsYKQrscqpKq3zh7BXo7YBVgRMibLWqqvnLzh6BwTZgESNHD8edoL+hOJupT9CII0oYJcQ14oy0CgHa0JTyCeBLVNeWygE5AAAAAElFTkSuQmCC";
import {
LineBg
} from '@/enum.js'
const contentRef = ref(null);
const tabAct = ref(0);
const searchText = ref('');
@@ -76,7 +80,7 @@
}, {
name: '未完成'
}]);
const contentRefs = ref([])
const status = ref('loadmore') // loadmore - 加载前,loading - 加载中,nomore - 没有数据
const limit = 15
@@ -101,12 +105,9 @@
common.navigateTo('/pages/testingHall/details');
}
onMounted(() => {
triggered.value = true
watch(tabAct, (newValue, oldValue) => {
console.log('resresresresresxxxxxxxxxx', contentRefs.value);
console.log('resresresresresxxxxxxxxxx', newValue);
console.log('resresresresresxxxxxxxxxx', contentRefs.value[newValue]);
typeof contentRefs.value[newValue]?.getData === 'function' && contentRefs.value[newValue]
?.getData('first')
}, {
@@ -209,7 +210,7 @@
width: 90%;
margin: 20rpx auto;
height: 78rpx;
position: relative;
.search_div {
@@ -47,6 +47,9 @@
import {
queryTraCrsBbsInfoByCrsId
} from '@/api/courseDetail.js';
import {
queryTraMistakesCollectionPaging,
} from '@/api/wrong_question_record.js';
const getData = (from = '') => {
if (from == 'first') {
if (total.value !== -1) {
@@ -64,8 +67,8 @@
console.log(status.value, '阿斯顿撒');
setTimeout(() => {
queryTraCrsBbsInfoByCrsId({
crsId: 'CRS0250181220722025070408313900000018729',
queryTraMistakesCollectionPaging({
isCorrect: 'N',
page: page.value,
limit: limit
}).then(res => {
+4 -1
View File
@@ -43,12 +43,15 @@
} from 'vue';
import {
LineBg
} from '@/enum.js';
} from '@/enum.js'
import {
queryTraCrsBbsInfoByCrsId,
replyTraCrsBbsInfo,
qryChildTraCrsBbsInfo
} from '@/api/courseDetail.js';
import common from '@/common/common';
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
+19 -2
View File
@@ -152,7 +152,24 @@
"titleNView": false
}
}
}
},
{
"path": "pages/learningTasks/index",
"style": {
"navigationBarTitleText": "学习任务",
"app-plus": {
"titleNView": false
}
}
},
{
"path": "pages/learningTasks/details",
"style": {
"navigationBarTitleText": "学习详情",
"app-plus": {
"titleNView": false
}
}
},
]
}
+21
View File
@@ -0,0 +1,21 @@
## 1.0.92023-12-06
1. 优化
## 1.0.82023-12-06
1. 阻止事件冒泡问题
## 1.0.72023-10-13
1. unmounted兼容vue3
## 1.0.62023-08-14
1. 修复初始的时候闪动的BUG
## 1.0.52023-06-22
1. 优化修改
## 1.0.42023-06-21
1. 修复BUG
## 1.0.32023-06-01
1. 修复点击触发两次事件的BUG
## 1.0.22023-05-23
1. 优化
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
uv-grid 宫格布局
@@ -0,0 +1,15 @@
export default {
props: {
// 宫格的name
name: {
type: [String, Number, null],
default: null
},
// 背景颜色
bgColor: {
type: String,
default: 'transparent'
},
...uni.$uv?.props?.gridItem
}
}
@@ -0,0 +1,226 @@
<template>
<!-- #ifndef APP-NVUE -->
<view
class="uv-grid-item"
hover-class="uv-grid-item--hover-class"
:hover-stay-time="200"
@tap="clickHandler"
:class="classes"
:style="[itemStyle]"
>
<slot />
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view
class="uv-grid-item"
:hover-stay-time="200"
@tap="clickHandler"
:class="classes"
:style="[itemStyle]"
>
<slot />
</view>
<!-- #endif -->
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
/**
* gridItem 提示
* @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配uv-grid使用
* @tutorial https://www.uvui.cn/components/grid.html
* @property {String | Number} name 宫格的name ( 默认 null )
* @property {String} bgColor 宫格的背景颜色 (默认 'transparent'
* @property {Object} customStyle 自定义样式,对象形式
* @event {Function} click 点击宫格触发
* @example <uv-grid-item></uv-grid-item>
*/
export default {
name: "uv-grid-item",
mixins: [mpMixin, mixin, props],
emits: ['$uvGridItem','click'],
data() {
return {
parentData: {
col: 3, // 父组件划分的宫格数
border: true, // 是否显示边框,根据父组件决定
},
// #ifdef APP-NVUE
width: 0, // nvue下才这么计算,vue下放到computed中,否则会因为延时造成闪烁
// #endif
classes: [], // 类名集合,用于判断是否显示右边和下边框
};
},
created() {
// 父组件的实例
this.updateParentData()
},
mounted() {
this.init()
},
computed: {
// #ifndef APP-NVUE
// vue下放到computed中,否则会因为延时造成闪烁
width() {
return 100 / Number(this.parentData.col) + '%'
},
// #endif
itemStyle() {
const style = {
background: this.bgColor,
width: this.width
}
return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
}
},
methods: {
init() {
// 用于在父组件uv-grid的children中被添加入子组件时,
// 重新计算item的边框
uni.$on('$uvGridItem', () => {
this.gridItemClasses()
})
// #ifdef APP-NVUE
// 获取元素该有的长度,nvue下要延时才准确
this.$nextTick(function(){
this.getItemWidth()
})
// #endif
// 发出事件,通知所有的grid-item都重新计算自己的边框
uni.$emit('$uvGridItem')
this.gridItemClasses()
},
// 获取父组件的参数
updateParentData() {
// 此方法写在mixin中
this.getParentData('uv-grid');
},
clickHandler() {
let name = this.name
// 如果没有设置name属性,历遍父组件的children数组,判断当前的元素是否和本实例this相等,找出当前组件的索引
const children = this.parent?.children
if(children && this.name === null) {
name = children.findIndex(child => child === this)
}
// 调用父组件方法,发出事件
this.parent && this.parent.childClick(name)
this.$emit('click', name)
},
async getItemWidth() {
// 如果是nvue,不能使用百分比,只能使用固定宽度
let width = 0
if(this.parent) {
// 获取父组件宽度后,除以栅格数,得出每个item的宽度
const parentWidth = await this.getParentWidth()
width = parentWidth / Number(this.parentData.col) + 'px'
}
this.width = width
},
// 获取父元素的尺寸
getParentWidth() {
// #ifdef APP-NVUE
// 返回一个promise,让调用者可以用await同步获取
const dom = uni.requireNativePlugin('dom')
return new Promise(resolve => {
// 调用父组件的ref
dom.getComponentRect(this.parent.$refs['uv-grid'], res => {
resolve(res.size.width)
})
})
// #endif
},
gridItemClasses() {
if(this.parentData.border) {
let classes = []
this.parent.children.map((child, index) =>{
if(this === child) {
const len = this.parent.children.length
// 贴近右边屏幕边沿的child,并且最后一个(比如只有横向2个的时候),无需右边框
if((index + 1) % this.parentData.col !== 0 && index + 1 !== len) {
classes.push('uv-border-right')
}
// 总的宫格数量对列数取余的值
// 如果取余后,值为0,则意味着要将最后一排的宫格,都不需要下边框
const lessNum = len % this.parentData.col === 0 ? this.parentData.col : len % this.parentData.col
// 最下面的一排child,无需下边框
if(index < len - lessNum) {
classes.push('uv-border-bottom')
}
}
})
// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
// #ifdef MP-ALIPAY || MP-TOUTIAO
classes = classes.join(' ')
// #endif
this.classes = classes
}
}
},
// #ifdef VUE2
beforeDestroy() {
// 移除事件监听,释放性能
uni.$off('$uvGridItem')
},
// #endif
// #ifdef VUE3
unmounted() {
// 移除事件监听,释放性能
uni.$off('$uvGridItem')
}
// #endif
};
</script>
<style lang="scss" scoped>
$show-border: 1;
$show-border-right: 1;
$show-border-bottom: 1;
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
$uv-grid-item-hover-class-opcatiy:.5 !default;
$uv-grid-item-margin-top:1rpx !default;
$uv-grid-item-border-right-width:0.5px !default;
$uv-grid-item-border-bottom-width:0.5px !default;
$uv-grid-item-border-right-color:$uv-border-color !default;
$uv-grid-item-border-bottom-color:$uv-border-color !default;
.uv-grid-item {
align-items: center;
justify-content: center;
position: relative;
flex-direction: column;
/* #ifndef APP-NVUE */
box-sizing: border-box;
display: flex;
/* #endif */
/* #ifdef MP */
position: relative;
float: left;
/* #endif */
/* #ifdef MP-WEIXIN */
margin-top:$uv-grid-item-margin-top;
/* #endif */
&--hover-class {
opacity:$uv-grid-item-hover-class-opcatiy;
}
}
/* #ifdef APP-NVUE */
// 由于nvue不支持组件内引入app.vue中再引入的样式,所以需要写在这里
.uv-border-right {
border-right-width:$uv-grid-item-border-right-width;
border-color: $uv-grid-item-border-right-color;
}
.uv-border-bottom {
border-bottom-width:$uv-grid-item-border-bottom-width;
border-color:$uv-grid-item-border-bottom-color;
}
/* #endif */
</style>
@@ -0,0 +1,20 @@
export default {
props: {
// 分成几列
col: {
type: [String, Number],
default: 3
},
// 是否显示边框
border: {
type: Boolean,
default: false
},
// 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右
align: {
type: String,
default: 'left'
},
...uni.$uv?.props?.grid
}
}
@@ -0,0 +1,100 @@
<template>
<view
class="uv-grid"
ref='uv-grid'
:style="[gridStyle]"
>
<slot />
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
/**
* grid 宫格布局
* @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。
* @tutorial https://www.uvui.cn/components/grid.html
* @property {String | Number} col 宫格的列数(默认 3
* @property {Boolean} border 是否显示宫格的边框(默认 false )
* @property {String} align 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右 (默认 'left'
* @property {Object} customStyle 定义需要用到的外部样式
* @event {Function} click 点击宫格触发
* @example <uv-grid :col="3" @click="click"></uv-grid>
*/
export default {
name: 'uv-grid',
mixins: [mpMixin, mixin, props],
emits: ['click'],
data() {
return {
index: 0,
width: 0
}
},
watch: {
// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
parentData() {
if (this.children.length) {
this.children.map(child => {
// 判断子组件(uv-radio)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
typeof(child.updateParentData) == 'function' && child.updateParentData();
})
}
},
},
created() {
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
this.children = []
},
computed: {
// 计算父组件的值是否发生变化
parentData() {
return [this.hoverClass, this.col, this.size, this.border];
},
// 宫格对齐方式
gridStyle() {
let style = {};
switch (this.align) {
case 'left':
style.justifyContent = 'flex-start';
break;
case 'center':
style.justifyContent = 'center';
break;
case 'right':
style.justifyContent = 'flex-end';
break;
default:
style.justifyContent = 'flex-start';
};
return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
}
},
methods: {
// 此方法由uv-grid-item触发,用于在uv-grid发出事件
childClick(name) {
this.$emit('click', name)
}
}
};
</script>
<style lang="scss" scoped>
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
$uv-grid-width:100% !default;
.uv-grid {
/* #ifdef MP */
width: $uv-grid-width;
position: relative;
box-sizing: border-box;
overflow: hidden;
display: block;
/* #endif */
justify-content: center;
@include flex;
flex-wrap: wrap;
align-items: center;
}
</style>
+88
View File
@@ -0,0 +1,88 @@
{
"id": "uv-grid",
"displayName": "uv-grid 宫格布局 全面兼容vue3+2、app、h5、小程序等多端",
"version": "1.0.9",
"description": "uv-grid 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。",
"keywords": [
"uv-grid",
"uvui",
"uv-ui",
"宫格布局",
"grid"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"uv-ui-tools",
"uv-icon"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
## Grid 宫格布局
> **组件名:uv-grid**
宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。
# <a href="https://www.uvui.cn/components/grid.html" target="_blank">查看文档</a>
### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
</a>
#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>