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

577 lines
17 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" class="nav-img-size">
</custom-navbar>
<view class="sticky-header-container" :style="{ top: navHeight + 'px' }">
<view class="official-author-block" v-if="authorInfo.show">
<view class="author-left">
<image class="author-avatar" :src="authorInfo.avatar" mode="aspectFill"></image>
<view class="author-info">
<text class="author-name">{{ authorInfo.name }}</text>
<text class="author-desc">发布了{{ authorInfo.videoCount }}条技能</text>
</view>
</view>
<view class="view-more-btn" v-if="!authorInfo.isOfficial" @click="goToBrandDetail">
<text>查看更多</text>
<view class="arrow-right"></view>
</view>
</view>
<view class="sort-bar-wrap">
<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>
<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";
const USE_REAL_API = true
export default {
components: { SkillCard, noData },
data() {
return {
navHeight: 0,
orderType: 0,
linkType: '',
// 【新增】:统一管理作者/官方/品牌信息
authorInfo: {
show: false, // 控制是否显示作者头部
isOfficial: false, // 区分是官方还是普通品牌
brandId: null, // 品牌ID
name: '', // 作者/品牌名称
avatar: '', // 头像
videoCount: 0 // 视频数量
},
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',
mockDatabase: []
}
},
computed: {
filterIcon() {
return this.filterVisible
? 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e00ab2ac-bccf-42d8-820c-65727327e279.png'
: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/bfe7a104-bef8-4432-9487-42f05e3e9035.png'
}
},
// 接收页面跳转传过来的作者参数
onLoad(options) {
if (options && options.showAuthor === '1') {
this.authorInfo.show = true;
this.authorInfo.isOfficial = options.isOfficial === '1';
this.authorInfo.brandId = options.linkId || null;
// 用 decodeURIComponent 把转义的字符解析回正常的中文和链接
this.authorInfo.name = options.authorName ? decodeURIComponent(options.authorName) : '未知作者';
this.authorInfo.avatar = options.authorAvatar ? decodeURIComponent(options.authorAvatar) : 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/bbe832bf-aae6-4579-b3cc-246d252488c3';
this.authorInfo.videoCount = options.videoCount || 0;
// 强制设置筛选类型
this.linkType = this.authorInfo.isOfficial ? 1 : 2;
} else if (options && options.linkType !== undefined) {
this.linkType = Number(options.linkType);
}
const sysInfo = uni.getSystemInfoSync()
const sbh = sysInfo.statusBarHeight || 0
this.navHeight = sbh
this.initMockData()
this.fetchData(true)
},
onReachBottom() {
if (!this.isFinished) {
this.fetchData(false)
}
},
methods: {
goToSearch() {
uni.navigateTo({
url: '/pages/search_common/search?type=skill'
});
},
// 跳转到品牌详情页面
goToBrandDetail() {
if (this.authorInfo.brandId) {
uni.navigateTo({
url: `/pages/jingxuan/brand-detail?id=${this.authorInfo.brandId}`
});
} else {
uni.showToast({ title: '缺少品牌信息', icon: 'none' });
}
},
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.authorInfo.show = false;
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 {
const params = {
page: this.page,
limit: this.limit
}
if (this.orderType !== 0) params.order_type = this.orderType;
if (this.linkType !== '') params.link_type = this.linkType;
// 【核心接口预留】如果当前在看某个品牌的专属页需要传link_id给后端过滤
if (this.authorInfo.show && !this.authorInfo.isOfficial && this.authorInfo.brandId) {
params.link_id = this.authorInfo.brandId;
}
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]
if (this.linkType !== '') {
list = list.filter(item => item.link_type === this.linkType)
}
// 模拟品牌过滤
if (this.authorInfo.show && !this.authorInfo.isOfficial && this.authorInfo.brandId) {
list = list.filter(item => item.brand_id == this.authorInfo.brandId)
}
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
})
}
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, brand_id: 101, 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, brand_id: 101, views: 12000, time: '2小时前', timestamp: Date.now() - 2 * 3600000 }
]
}
}
}
</script>
<style lang="scss" scoped>
.skill-market-page {
min-height: 100vh;
background: #fff;
}
/* 统一吸顶容器 */
.sticky-header-container {
position: sticky;
background: #fff;
z-index: 101;
/* 确保整体吸顶时下方有稍微一点阴影或边框分割更好看,可根据 UI 需求开启 */
// border-bottom: 1rpx solid #F5F5F5;
}
/* 官方/品牌 作者展示块样式 */
.official-author-block {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
background: #fff;
border-bottom: 10rpx solid #F5F5F5;
.author-left {
display: flex;
align-items: center;
.author-avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.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;
}
}
}
.view-more-btn {
display: flex;
align-items: center;
font-size: 24rpx;
color: #FF4767;
border: 2rpx solid #FF4767;
padding: 8rpx 20rpx;
border-radius: 45rpx;
.arrow-right {
width: 10rpx;
height: 10rpx;
border-top: 2rpx solid #FF4767;
border-right: 2rpx solid #FF4767;
transform: rotate(45deg);
margin-left: 6rpx;
}
}
}
.sort-bar-wrap {
/* 移除单独的 sticky由外层 container 统一控制 */
background: #fff;
position: relative;
}
.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: 24rpx;
height: 24rpx;
transition: transform 0.3s ease;
&.rotate {
transform: rotate(180deg);
width: 18rpx;
height: 12rpx;
}
}
}
}
.list-container {
padding: 30rpx 24rpx;
}
.filter-panel {
position: absolute;
top: 100%;
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;
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;
}
.nav-img-size::v-deep .icon-headle {
width: 36rpx !important;
height: 36rpx !important;
}
</style>