mrr.sj.front/pages/jingxuan/selected-skills.vue

505 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="skill-market-page">
<custom-navbar
title="技能集市"
:showBack="true"
backgroundColor="#FFFFFF"
:show-headle="true"
borderBottom="none"
headleSrc="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/51ebca5d-95c5-4fb8-8aa3-560a79100d25.png"
@onHeadleClick="goToSearch">
</custom-navbar>
<view class="nav-placeholder" :style="{ height: navHeight + 'px' }"></view>
<view class="official-author-block" v-if="linkType === 1">
<image class="author-avatar" src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/bbe832bf-aae6-4579-b3cc-246d252488c3" mode="aspectFill"></image>
<view class="author-info">
<text class="author-name">美融融官方</text>
<text class="author-desc">发布了658视频</text>
</view>
</view>
<view class="sort-bar-wrap" :style="{ top: navHeight + 'px' }">
<view class="sort-bar">
<view class="left-sorts">
<view class="sort-item" @tap="toggleSort('time')">
<text class="sort-text" :class="{ active: orderType === 1 || orderType === 2 }">发布时间</text>
<view class="arrows">
<image class="arrow-icon" :src="orderType === 1 ? iconPinkUp : iconGreyUp"></image>
<image class="arrow-icon" :src="orderType === 2 ? iconPinkDown : iconGreyDown"></image>
</view>
</view>
<view class="sort-item" @tap="toggleSort('view')">
<text class="sort-text" :class="{ active: orderType === 3 || orderType === 4 }">浏览量</text>
<view class="arrows">
<image class="arrow-icon" :src="orderType === 3 ? iconPinkUp : iconGreyUp"></image>
<image class="arrow-icon" :src="orderType === 4 ? iconPinkDown : iconGreyDown"></image>
</view>
</view>
</view>
<view class="right-filter" @tap="toggleFilterPanel">
<text class="filter-text">筛选</text>
<image class="filter-icon" :src="filterIcon" :class="{ 'rotate': filterVisible }"></image>
</view>
</view>
<view class="filter-panel" :class="{ 'show': filterVisible }" catchtouchmove="true">
<view class="filter-mask" @tap="closeFilterPanel"></view>
<view class="filter-container">
<text class="group-title">课程类型</text>
<view class="course-filter">
<view class="course-type-item" :class="{ active: linkType === '' }" @tap="selectAndApply('')">全部</view>
<view class="course-type-item" :class="{ active: linkType === 1 }" @tap="selectAndApply(1)">平台课程</view>
<view class="course-type-item" :class="{ active: linkType === 2 }" @tap="selectAndApply(2)">品牌课程</view>
</view>
</view>
</view>
</view> <view class="list-container">
<view v-if="skillList.length > 0">
<skill-card
v-for="(item, index) in skillList"
:key="item.id || index"
:item="item"
@click="goToDetail(item)"
></skill-card>
</view>
<view v-else>
<noData></noData>
</view>
</view>
<view style="height: 60rpx;"></view>
</view>
</template>
<script>
import request from "@/utils/request"
import SkillCard from "@/pages/home/components/skill-card.vue"
import noData from "@/components/noData/noData.vue";
// 数据模式开关true=调用真实接口false=使用模拟数据
const USE_REAL_API = true
export default {
components: { SkillCard, noData },
data() {
return {
navHeight: 0,
// 排序与筛选参数
orderType: 0, // 1:时间降序, 2:时间升序, 3:浏览量降序, 4:浏览量升序 (默认0)
linkType: '', // '':全部, 1:平台课程, 2:品牌课程
filterVisible: false,
// 列表数据与分页
skillList: [],
page: 1,
limit: 10,
isFinished: false,
// 排序图标资源
iconGreyUp: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e00ab2ac-bccf-42d8-820c-65727327e279.png',
iconGreyDown: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/dfffb601-6877-4608-a3eb-88695b05c6c2.png',
iconPinkUp: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/5bc84dff-e120-4d4b-a33e-25d68e065654.png',
iconPinkDown: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ff2c2c9d-694a-4adf-b737-66b07f4a63ae.png',
// 模拟的本地全量数据(仅在 USE_REAL_API=false 时使用)
mockDatabase: []
}
},
computed: {
filterIcon() {
// 漏斗图标,默认灰色
return 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e00ab2ac-bccf-42d8-820c-65727327e279.png'
}
},
onLoad() {
// 动态计算导航栏与下拉起点
const sysInfo = uni.getSystemInfoSync()
const sbh = sysInfo.statusBarHeight || 0
this.navHeight = sbh
// Navbar(44px) + SortBar(大概88rpx -> 44px) = 88px 左右
this.filterTop = this.navHeight + uni.upx2px(106)
this.initMockData()
this.fetchData(true)
},
// 触底加载更多
onReachBottom() {
if (!this.isFinished) {
this.fetchData(false)
}
},
methods: {
goToSearch() {
uni.navigateTo({
url: '/pages/search_common/search?type=skill' // 告诉搜索页,我要搜技能
});
},
// 点击排序
toggleSort(type) {
if (type === 'time') {
if (this.orderType === 1) this.orderType = 2
else this.orderType = 1
} else if (type === 'view') {
if (this.orderType === 3) this.orderType = 4
else this.orderType = 3
}
this.fetchData(true)
},
// 筛选面板控制
toggleFilterPanel() {
this.filterVisible = !this.filterVisible
},
closeFilterPanel() {
this.filterVisible = false
},
// 💡 匹配设计图交互:选择选项后立刻应用并收起下拉框
selectAndApply(typeVal) {
this.linkType = typeVal;
this.closeFilterPanel();
this.fetchData(true);
},
// 获取列表数据 (统一入口)
async fetchData(isReset = false) {
if (isReset) {
this.page = 1
this.isFinished = false
this.skillList = []
}
if (USE_REAL_API) {
uni.showLoading({ title: '加载中...' })
try {
// 1. 初始化基础参数
const params = {
page: this.page,
limit: this.limit
}
// 2. 仅当 orderType 不为 0 时,才将 order_type 传给后端,避免 500 报错
if (this.orderType !== 0) {
params.order_type = this.orderType
}
// 3. 课程类型筛选
if (this.linkType !== '') {
params.link_type = this.linkType
}
const res = await request.post('/sj/skill/list', params)
if ((res.code === 200 || res.state === 1) && res.data && res.data.list) {
const list = res.data.list.map(item => this.formatApiData(item))
if (list.length < this.limit) this.isFinished = true
this.skillList = isReset ? list : this.skillList.concat(list)
this.page++
} else {
this.isFinished = true
}
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
uni.hideLoading()
}
} else {
// ========== 模拟数据请求环境 ==========
uni.showLoading({ title: '模拟加载中...' })
setTimeout(() => {
let list = [...this.mockDatabase]
// 1. 模拟筛选
if (this.linkType !== '') {
list = list.filter(item => item.link_type === this.linkType)
}
// 2. 模拟排序 (仅当 orderType 不为 0 时才进行排序操作)
if (this.orderType !== 0) {
list.sort((a, b) => {
if (this.orderType === 1) return b.timestamp - a.timestamp // 时间降序
if (this.orderType === 2) return a.timestamp - b.timestamp // 时间升序
if (this.orderType === 3) return b.views - a.views // 浏览量降序
if (this.orderType === 4) return a.views - b.views // 浏览量升序
return 0
})
}
// 3. 模拟分页
const start = (this.page - 1) * this.limit
const end = start + this.limit
const pageData = list.slice(start, end)
if (pageData.length < this.limit) this.isFinished = true
this.skillList = isReset ? pageData : this.skillList.concat(pageData)
this.page++
uni.hideLoading()
}, 500)
}
},
// 格式化接口数据
formatApiData(item) {
return {
id: item.id,
title: item.name,
coverSrc: item.cover_img || 'https://dummyimage.com/300x200/ccc/fff.png',
type: item.link_name || (item.link_type === 1 ? '平台课程' : '品牌课程'),
link_type: item.link_type,
views: item.browse_num || 0,
time: item.create_time ? this.formatTimeAgo(item.create_time) : '',
typeIcon: item.link_logo || ''
}
},
formatTimeAgo(timeStr) {
const now = new Date()
const time = new Date(timeStr.replace(/-/g, '/'))
const diff = Math.floor((now - time) / 1000)
if (diff < 3600) return Math.floor(diff / 60) + '分钟前'
if (diff < 86400) return Math.floor(diff / 3600) + '小时前'
if (diff < 604800) return Math.floor(diff / 86400) + '天前'
return timeStr.split(' ')[0]
},
goToDetail(item) {
if (item.id) {
uni.navigateTo({ url: `/pages/skill/detail?id=${item.id}` })
} else {
uni.showToast({ title: '详情页开发中', icon: 'none' })
}
},
// 初始化模拟数据
initMockData() {
this.mockDatabase = [
{ id: 1, title: '穿戴甲的操作视频扫码验券操作教程', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '平台课程', link_type: 1, views: 836000, time: '7小时前', timestamp: Date.now() - 7*3600000 },
{ id: 2, title: '脸部按摩操作步骤详情', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '品牌课程', link_type: 2, views: 8334, time: '1天前', timestamp: Date.now() - 24*3600000 },
{ id: 3, title: '全身SPA的操作视频操作教程', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '平台课程', link_type: 1, views: 2344, time: '3天前', timestamp: Date.now() - 3*24*3600000 },
{ id: 4, title: '高级面部护理手法详解最新版', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '平台课程', link_type: 1, views: 836, time: '4月26日', timestamp: Date.now() - 30*24*3600000 },
{ id: 5, title: '光疗美甲进阶全方位解析', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '品牌课程', link_type: 2, views: 12000, time: '2小时前', timestamp: Date.now() - 2*3600000 }
]
}
}
}
</script>
<style lang="scss" scoped>
.skill-market-page {
min-height: 100vh;
background: #fff;
}
/* 官方作者展示块 */
.official-author-block {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
background: #fff;
border-bottom: 10rpx solid #F5F5F5;
.author-avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
margin-right: 20rpx;
// border: 2rpx solid #F5F5F5;
}
.author-info {
display: flex;
flex-direction: column;
.author-name {
font-weight: 500;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
margin-bottom: 4rpx;
}
.author-desc {
font-weight: 400;
font-size: 20rpx;
color: #999999;
line-height: 28rpx;
}
}
}
.sort-bar-wrap {
position: sticky;
background: #fff;
z-index: 101;
// border-bottom: 1rpx solid #F5F5F5;
}
.sort-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
.left-sorts {
display: flex;
align-items: center;
gap: 100rpx;
.sort-item {
display: flex;
align-items: center;
.sort-text {
font-size: 28rpx;
color: #333333;
margin-right: 6rpx;
font-weight: 400;
&.active {
color: #FF4767;
font-weight: 500;
}
}
.arrows {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.arrow-icon {
width: 14rpx;
height: 8rpx;
margin: 2rpx 0;
}
}
}
}
.right-filter {
display: flex;
align-items: center;
.filter-text {
font-size: 26rpx;
color: #666666;
margin-right: 8rpx;
}
.filter-icon {
width: 18rpx;
height: 12rpx;
transition: transform 0.3s ease;
&.rotate {
transform: rotate(180deg);
}
}
}
}
.list-container {
padding: 30rpx 24rpx;
}
/* 抽屉筛选面板:使用 absolute 脱离计算 */
.filter-panel {
position: absolute;
top: 100%; /* 从 sort-bar-wrap 底部开始向下延伸 */
left: 0; right: 0;
height: 100vh;
z-index: 100;
pointer-events: none;
overflow: hidden;
&.show {
pointer-events: auto;
.filter-mask { opacity: 1; }
.filter-container { transform: translateY(0); }
}
.filter-mask {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.4);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.filter-container {
position: absolute;
top: 0; left: 0; right: 0;
background: #fff;
border-radius: 0 0 24rpx 24rpx;
padding: 30rpx 30rpx 40rpx;
display: flex;
flex-direction: column;
// box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.05);
transform: translateY(-100%);
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
}
.group-title {
font-weight: 500;
font-size: 28rpx;
color: #333333;
margin-bottom: 24rpx;
display: block;
}
.course-filter {
display: flex;
gap: 20rpx;
.course-type-item {
background: #F5F5F5;
color: #333333;
font-size: 26rpx;
padding: 12rpx 36rpx;
border-radius: 12rpx;
border: 1rpx solid transparent;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
&.active {
background: #FFF0F2;
color: #FF4767;
border-color: #FFB0BD;
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 120rpx 0;
.empty-icon {
width: 346rpx;
height: 373rpx;
}
}
::v-deep .icon-headle {
width: 32rpx !important;
height: 32rpx !important;
}
::v-deep .back-icon {
width: 28rpx !important;
height: 36rpx !important;
}
</style>