-
8app.json
-
41pages/home/home.js
-
6pages/home/home.wxml
-
18pages/home/home.wxss
-
BINpages/images/sjh.png
-
BINpagesA/images/jh.png
-
66pagesA/pages/askingSy/askingSy.js
-
3pagesA/pages/askingSy/askingSy.json
-
2pagesA/pages/askingSy/askingSy.wxml
-
1pagesA/pages/askingSy/askingSy.wxss
-
154pagesA/pages/carouselDetail/carouselDetail.js
-
4pagesA/pages/carouselDetail/carouselDetail.json
-
104pagesA/pages/carouselDetail/carouselDetail.wxml
-
284pagesA/pages/carouselDetail/carouselDetail.wxss
-
BINpagesB/images/bo.png
-
BINpagesB/images/hf.png
-
BINpagesB/images/lll.png
-
BINpagesB/images/sou.png
-
286pagesB/pages/administrativeDivision/administrativeDivision.js
-
4pagesB/pages/administrativeDivision/administrativeDivision.json
-
95pagesB/pages/administrativeDivision/administrativeDivision.wxml
-
316pagesB/pages/administrativeDivision/administrativeDivision.wxss
-
580pagesB/pages/publishAdd/publishAdd.js
-
4pagesB/pages/publishAdd/publishAdd.json
-
205pagesB/pages/publishAdd/publishAdd.wxml
-
457pagesB/pages/publishAdd/publishAdd.wxss
-
66pagesB/pages/repository/repository.js
-
3pagesB/pages/repository/repository.json
-
2pagesB/pages/repository/repository.wxml
-
1pagesB/pages/repository/repository.wxss
-
381pagesB/pages/spDetails/spDetails.js
-
4pagesB/pages/spDetails/spDetails.json
-
174pagesB/pages/spDetails/spDetails.wxml
-
664pagesB/pages/spDetails/spDetails.wxss
-
402pagesB/pages/training/training.js
-
4pagesB/pages/training/training.json
-
215pagesB/pages/training/training.wxml
-
1105pagesB/pages/training/training.wxss
-
99pagesB/pages/wzDetails/wzDetails.js
-
4pagesB/pages/wzDetails/wzDetails.json
-
62pagesB/pages/wzDetails/wzDetails.wxml
-
322pagesB/pages/wzDetails/wzDetails.wxss
-
123utils/api.js
|
After Width: 200 | Height: 200 | Size: 4.7 KiB |
|
After Width: 200 | Height: 200 | Size: 1.7 KiB |
@ -1,66 +0,0 @@ |
|||||
// pagesA/pages/askingSy/askingSy.js
|
|
||||
Page({ |
|
||||
|
|
||||
/** |
|
||||
* 页面的初始数据 |
|
||||
*/ |
|
||||
data: { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面加载 |
|
||||
*/ |
|
||||
onLoad(options) { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面初次渲染完成 |
|
||||
*/ |
|
||||
onReady() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面显示 |
|
||||
*/ |
|
||||
onShow() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面隐藏 |
|
||||
*/ |
|
||||
onHide() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面卸载 |
|
||||
*/ |
|
||||
onUnload() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 页面相关事件处理函数--监听用户下拉动作 |
|
||||
*/ |
|
||||
onPullDownRefresh() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 页面上拉触底事件的处理函数 |
|
||||
*/ |
|
||||
onReachBottom() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 用户点击右上角分享 |
|
||||
*/ |
|
||||
onShareAppMessage() { |
|
||||
|
|
||||
} |
|
||||
}) |
|
||||
@ -1,3 +0,0 @@ |
|||||
{ |
|
||||
"usingComponents": {} |
|
||||
} |
|
||||
@ -1,2 +0,0 @@ |
|||||
<!--pagesA/pages/askingSy/askingSy.wxml--> |
|
||||
<text>pagesA/pages/askingSy/askingSy.wxml</text> |
|
||||
@ -1 +0,0 @@ |
|||||
/* pagesA/pages/askingSy/askingSy.wxss */ |
|
||||
@ -0,0 +1,154 @@ |
|||||
|
import http from '../../../utils/api'; |
||||
|
const baseUrl = require('../../../utils/baseUrl'); |
||||
|
|
||||
|
Page({ |
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
baseUrl: baseUrl, |
||||
|
detailInfo: {}, |
||||
|
id: null, |
||||
|
cardAnimation: {} // 卡片动画数据
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
if (options.id) { |
||||
|
this.setData({ id: options.id }); |
||||
|
this.getCarouselDetail(options.id); |
||||
|
this.initAnimation(); |
||||
|
} else { |
||||
|
wx.showToast({ |
||||
|
title: '参数错误', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 初始化卡片动画 |
||||
|
*/ |
||||
|
initAnimation() { |
||||
|
const animation = wx.createAnimation({ |
||||
|
duration: 600, |
||||
|
timingFunction: 'ease-out', |
||||
|
delay: 100 |
||||
|
}); |
||||
|
|
||||
|
animation.translateY(0).opacity(1).step(); |
||||
|
|
||||
|
this.setData({ |
||||
|
cardAnimation: animation.export() |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 获取轮播详情 |
||||
|
*/ |
||||
|
getCarouselDetail(id) { |
||||
|
wx.showLoading({ title: '加载中...', mask: true }); |
||||
|
|
||||
|
http.carouselDetail({ |
||||
|
data: { id: id }, |
||||
|
success: (res) => { |
||||
|
wx.hideLoading(); |
||||
|
console.log('轮播详情:', res); |
||||
|
|
||||
|
if (res && res.code === 200 && res.data) { |
||||
|
this.setData({ |
||||
|
detailInfo: res.data |
||||
|
}); |
||||
|
wx.setNavigationBarTitle({ |
||||
|
title: res.data.title || '轮播详情' |
||||
|
}); |
||||
|
} else { |
||||
|
wx.showToast({ |
||||
|
title: res?.msg || '数据加载失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
wx.hideLoading(); |
||||
|
console.error('请求失败:', err); |
||||
|
wx.showToast({ |
||||
|
title: '网络错误', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 颜色调整函数(用于渐变效果) |
||||
|
*/ |
||||
|
adjustColor(hex, percent) { |
||||
|
if (!hex) return '#4CAF50'; |
||||
|
// 简单实现:如果传入颜色,返回稍浅的版本
|
||||
|
// 这里为了简化,直接返回原色稍微变浅,实际项目中可使用颜色处理库
|
||||
|
return hex; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 图片加载错误处理 |
||||
|
*/ |
||||
|
imageLoadError(e) { |
||||
|
console.warn('图片加载失败', e); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 返回上一页 |
||||
|
*/ |
||||
|
goBack() { |
||||
|
wx.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() {}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() {}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() {}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() {}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() {}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() {}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
const { title, imageUrl } = this.data.detailInfo; |
||||
|
return { |
||||
|
title: title || '轮播详情', |
||||
|
imageUrl: this.data.baseUrl + (imageUrl || ''), |
||||
|
path: `/pages/carousel/detail/detail?id=${this.data.id}` |
||||
|
}; |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"轮播详情", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
<view class="carousel-detail-container" style="--bg-color: {{detailInfo.bgColor || '#F8F9FA'}};"> |
||||
|
|
||||
|
<!-- 沉浸式头部,带渐变遮罩和返回按钮 --> |
||||
|
<view class="hero-section"> |
||||
|
<image |
||||
|
class="hero-image" |
||||
|
src="{{baseUrl + detailInfo.imageUrl}}" |
||||
|
mode="aspectFill" |
||||
|
binderror="imageLoadError" |
||||
|
></image> |
||||
|
<view class="hero-overlay" style="background: linear-gradient(to bottom, transparent 30%, {{detailInfo.bgColor || '#F8F9FA'}} 90%);"></view> |
||||
|
|
||||
|
<!-- 返回按钮 --> |
||||
|
<view class="nav-back glass" bindtap="goBack"> |
||||
|
<text class="iconfont icon-back">←</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 页面标题 - 仅在需要时显示 --> |
||||
|
<view class="page-title glass" wx:if="{{detailInfo.adsType}}"> |
||||
|
<text>{{detailInfo.adsType}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 主要内容卡片 --> |
||||
|
<view class="content-card" animation="{{cardAnimation}}" style="--text-color: {{detailInfo.textColor || '#2E7D32'}};"> |
||||
|
|
||||
|
<!-- 标题区域 --> |
||||
|
<view class="title-section"> |
||||
|
<text class="main-title" style="color: {{detailInfo.textColor || '#1A1A1A'}};">{{detailInfo.title || '无标题'}}</text> |
||||
|
<text class="sub-title">{{detailInfo.subtitle || '暂无副标题'}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 装饰分割线 --> |
||||
|
<view class="divider"> |
||||
|
<view class="divider-line" style="background-color: {{detailInfo.textColor || '#2E7D32'}};"></view> |
||||
|
<view class="divider-dot" style="background-color: {{detailInfo.textColor || '#2E7D32'}};"></view> |
||||
|
<view class="divider-line" style="background-color: {{detailInfo.textColor || '#2E7D32'}};"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 信息网格 --> |
||||
|
<view class="info-grid"> |
||||
|
<!-- 尺寸信息卡片 --> |
||||
|
<view class="info-item" wx:if="{{detailInfo.imageSize}}"> |
||||
|
<view class="info-icon">📐</view> |
||||
|
<view class="info-content"> |
||||
|
<text class="info-label">图片尺寸</text> |
||||
|
<text class="info-value">{{detailInfo.imageSize}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 展示次数卡片 --> |
||||
|
<view class="info-item" wx:if="{{detailInfo.displayCount !== undefined}}"> |
||||
|
<view class="info-icon">👁️</view> |
||||
|
<view class="info-content"> |
||||
|
<text class="info-label">浏览次数</text> |
||||
|
<text class="info-value">{{detailInfo.viewCount}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
|
||||
|
<!-- 创建时间卡片 --> |
||||
|
<view class="info-item" wx:if="{{detailInfo.createdAt}}"> |
||||
|
<view class="info-icon">📅</view> |
||||
|
<view class="info-content"> |
||||
|
<text class="info-label">创建时间</text> |
||||
|
<text class="info-value">{{detailInfo.createdAt}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 更新时间卡片 --> |
||||
|
<view class="info-item" wx:if="{{detailInfo.updatedAt}}"> |
||||
|
<view class="info-icon">🔄</view> |
||||
|
<view class="info-content"> |
||||
|
<text class="info-label">更新时间</text> |
||||
|
<text class="info-value">{{detailInfo.updatedAt}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 有效性状态卡片 - 使用优雅的徽章设计 --> |
||||
|
<view class="info-item status-item" wx:if="{{detailInfo.isActive !== undefined}}"> |
||||
|
<view class="info-icon">⚡</view> |
||||
|
<view class="info-content"> |
||||
|
<text class="info-label">状态</text> |
||||
|
<view class="status-badge" style="background-color: {{detailInfo.isActive === 1 ? '#4CAF50' : '#9E9E9E'}}20; border-color: {{detailInfo.isActive === 1 ? '#4CAF50' : '#9E9E9E'}};"> |
||||
|
<text class="status-dot" style="background-color: {{detailInfo.isActive === 1 ? '#4CAF50' : '#9E9E9E'}};"></text> |
||||
|
<text class="status-text" style="color: {{detailInfo.isActive === 1 ? '#4CAF50' : '#9E9E9E'}};">{{detailInfo.isActive === 1 ? '有效' : '无效'}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 描述/备注区域(如果有remark字段) --> |
||||
|
<view class="remark-section" wx:if="{{detailInfo.remark}}"> |
||||
|
<view class="remark-icon">📝</view> |
||||
|
<view class="remark-content">{{detailInfo.remark}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 辅助信息:始终有效标识 --> |
||||
|
<view class="always-valid" wx:if="{{detailInfo.isAlwaysValid === 1}}"> |
||||
|
<text class="valid-icon">✨</text> |
||||
|
<text class="valid-text">长期有效</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,284 @@ |
|||||
|
.carousel-detail-container { |
||||
|
min-height: 100vh; |
||||
|
background-color: var(--bg-color, #F8F9FA); |
||||
|
position: relative; |
||||
|
padding-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
/* 沉浸式头部 */ |
||||
|
.hero-section { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: 600rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.hero-image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
filter: brightness(0.9); |
||||
|
transition: transform 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.hero-image:active { |
||||
|
transform: scale(1.02); |
||||
|
} |
||||
|
|
||||
|
.hero-overlay { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
height: 200rpx; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
/* 毛玻璃返回按钮 */ |
||||
|
.nav-back { |
||||
|
position: absolute; |
||||
|
top: 60rpx; |
||||
|
left: 30rpx; |
||||
|
width: 72rpx; |
||||
|
height: 72rpx; |
||||
|
border-radius: 36rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 40rpx; |
||||
|
color: #333; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.glass { |
||||
|
background: rgba(255, 255, 255, 0.25); |
||||
|
backdrop-filter: blur(10px); |
||||
|
-webkit-backdrop-filter: blur(10px); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.page-title { |
||||
|
position: absolute; |
||||
|
top: 60rpx; |
||||
|
right: 30rpx; |
||||
|
padding: 16rpx 32rpx; |
||||
|
border-radius: 40rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
font-weight: 500; |
||||
|
z-index: 10; |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
/* 主要内容卡片 */ |
||||
|
.content-card { |
||||
|
margin: -100rpx 30rpx 0; |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
backdrop-filter: blur(20px); |
||||
|
-webkit-backdrop-filter: blur(20px); |
||||
|
border-radius: 48rpx; |
||||
|
padding: 48rpx 32rpx; |
||||
|
box-shadow: 0 30rpx 60rpx rgba(0, 0, 0, 0.1), |
||||
|
0 10rpx 30rpx rgba(0, 0, 0, 0.05); |
||||
|
position: relative; |
||||
|
z-index: 5; |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.5); |
||||
|
opacity: 0; |
||||
|
transform: translateY(50rpx); |
||||
|
animation: cardFloat 0.6s ease-out forwards; |
||||
|
} |
||||
|
|
||||
|
@keyframes cardFloat { |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 标题区域 */ |
||||
|
.title-section { |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.main-title { |
||||
|
display: block; |
||||
|
font-size: 56rpx; |
||||
|
font-weight: 700; |
||||
|
line-height: 1.3; |
||||
|
margin-bottom: 16rpx; |
||||
|
letter-spacing: -0.5rpx; |
||||
|
} |
||||
|
|
||||
|
.sub-title { |
||||
|
display: block; |
||||
|
font-size: 30rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.5; |
||||
|
font-weight: 400; |
||||
|
opacity: 0.8; |
||||
|
} |
||||
|
|
||||
|
/* 装饰分割线 */ |
||||
|
.divider { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin: 30rpx 0 40rpx; |
||||
|
} |
||||
|
|
||||
|
.divider-line { |
||||
|
flex: 1; |
||||
|
height: 2rpx; |
||||
|
opacity: 0.2; |
||||
|
} |
||||
|
|
||||
|
.divider-dot { |
||||
|
width: 8rpx; |
||||
|
height: 8rpx; |
||||
|
border-radius: 4rpx; |
||||
|
margin: 0 20rpx; |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
/* 信息网格 */ |
||||
|
.info-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(2, 1fr); |
||||
|
gap: 24rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.info-item { |
||||
|
background: rgba(0, 0, 0, 0.02); |
||||
|
border-radius: 28rpx; |
||||
|
padding: 28rpx 20rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
border: 1rpx solid rgba(0, 0, 0, 0.03); |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.info-item:active { |
||||
|
transform: scale(0.98); |
||||
|
background: rgba(0, 0, 0, 0.04); |
||||
|
} |
||||
|
|
||||
|
.info-icon { |
||||
|
font-size: 48rpx; |
||||
|
margin-right: 20rpx; |
||||
|
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.1)); |
||||
|
} |
||||
|
|
||||
|
.info-content { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.info-label { |
||||
|
font-size: 22rpx; |
||||
|
color: #999; |
||||
|
margin-bottom: 8rpx; |
||||
|
letter-spacing: 0.5rpx; |
||||
|
} |
||||
|
|
||||
|
.info-value { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
|
||||
|
/* 状态特殊样式 */ |
||||
|
.status-item .info-content { |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.status-badge { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 8rpx 16rpx; |
||||
|
border-radius: 40rpx; |
||||
|
border-width: 1rpx; |
||||
|
border-style: solid; |
||||
|
background-color: rgba(76, 175, 80, 0.1); |
||||
|
} |
||||
|
|
||||
|
.status-dot { |
||||
|
width: 16rpx; |
||||
|
height: 16rpx; |
||||
|
border-radius: 8rpx; |
||||
|
margin-right: 8rpx; |
||||
|
animation: pulse 2s infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes pulse { |
||||
|
0%, 100% { opacity: 1; } |
||||
|
50% { opacity: 0.6; } |
||||
|
} |
||||
|
|
||||
|
.status-text { |
||||
|
font-size: 24rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 备注区域 */ |
||||
|
.remark-section { |
||||
|
background: rgba(0, 0, 0, 0.02); |
||||
|
border-radius: 28rpx; |
||||
|
padding: 28rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
display: flex; |
||||
|
border-left: 8rpx solid var(--text-color, #2E7D32); |
||||
|
} |
||||
|
|
||||
|
.remark-icon { |
||||
|
font-size: 40rpx; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.remark-content { |
||||
|
flex: 1; |
||||
|
font-size: 28rpx; |
||||
|
color: #444; |
||||
|
line-height: 1.6; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* 始终有效标识 */ |
||||
|
.always-valid { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-top: 30rpx; |
||||
|
padding: 20rpx; |
||||
|
background: linear-gradient(135deg, rgba(255, 215, 0, 0.1), rgba(255, 215, 0, 0.05)); |
||||
|
border-radius: 40rpx; |
||||
|
border: 1rpx solid rgba(255, 215, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.valid-icon { |
||||
|
font-size: 32rpx; |
||||
|
margin-right: 12rpx; |
||||
|
animation: starTwinkle 1.5s infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes starTwinkle { |
||||
|
0%, 100% { opacity: 1; transform: scale(1); } |
||||
|
50% { opacity: 0.7; transform: scale(1.1); } |
||||
|
} |
||||
|
|
||||
|
.valid-text { |
||||
|
font-size: 26rpx; |
||||
|
color: #B8860B; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 响应式调整 */ |
||||
|
@media (min-width: 768px) { |
||||
|
.content-card { |
||||
|
max-width: 700rpx; |
||||
|
margin: -100rpx auto 0; |
||||
|
} |
||||
|
} |
||||
|
After Width: 200 | Height: 200 | Size: 2.5 KiB |
|
After Width: 200 | Height: 200 | Size: 3.8 KiB |
|
After Width: 200 | Height: 200 | Size: 3.6 KiB |
|
After Width: 200 | Height: 200 | Size: 4.5 KiB |
@ -0,0 +1,286 @@ |
|||||
|
import http from '../../../utils/api' |
||||
|
|
||||
|
Page({ |
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
regionList: [], // 当前显示的区域列表
|
||||
|
selectedRegions: [], // 已选择的区域路径
|
||||
|
currentParentCode: '', // 当前父级code
|
||||
|
loading: false, |
||||
|
showPicker: false, |
||||
|
regionTitle: '请选择区域', |
||||
|
autoShowPicker: false // 新增:控制是否自动弹出选择器
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
// 首次加载,获取第一级数据
|
||||
|
this.getRegionData('', () => { |
||||
|
// 首次加载完成后自动弹出选择器
|
||||
|
this.setData({ |
||||
|
showPicker: true, |
||||
|
regionTitle: '请选择区域' |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 获取区域数据
|
||||
|
getRegionData(parentCode, callback) { |
||||
|
this.setData({ |
||||
|
loading: true |
||||
|
}) |
||||
|
|
||||
|
http.areaChildren({ |
||||
|
data: { |
||||
|
parentCode: parentCode || '' |
||||
|
}, |
||||
|
success: res => { |
||||
|
console.log('区域数据响应:', res) |
||||
|
if (res.code === 200 && res.data && res.data.length > 0) { |
||||
|
this.setData({ |
||||
|
regionList: res.data, |
||||
|
currentParentCode: parentCode, |
||||
|
loading: false |
||||
|
}) |
||||
|
} else { |
||||
|
// 没有更多数据了,说明已经是最后一级
|
||||
|
this.setData({ |
||||
|
loading: false, |
||||
|
regionList: [] |
||||
|
}) |
||||
|
|
||||
|
// 如果是最后一级,关闭选择器
|
||||
|
if (this.data.showPicker) { |
||||
|
this.setData({ |
||||
|
showPicker: false |
||||
|
}) |
||||
|
wx.showToast({ |
||||
|
title: '已选择到最后一级', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 执行回调
|
||||
|
if (callback) { |
||||
|
callback() |
||||
|
} |
||||
|
}, |
||||
|
fail: err => { |
||||
|
console.error('请求失败:', err) |
||||
|
this.setData({ |
||||
|
loading: false |
||||
|
}) |
||||
|
wx.showToast({ |
||||
|
title: '加载失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
|
||||
|
if (callback) { |
||||
|
callback() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 开始选择(点击按钮时调用)
|
||||
|
startSelection() { |
||||
|
// 如果还没有选择任何区域,重新加载第一级
|
||||
|
if (this.data.selectedRegions.length === 0) { |
||||
|
this.getRegionData('', () => { |
||||
|
this.setData({ |
||||
|
showPicker: true, |
||||
|
regionTitle: '请选择区域' |
||||
|
}) |
||||
|
}) |
||||
|
} else { |
||||
|
// 如果有已选择的区域,加载下一级
|
||||
|
const lastRegion = this.data.selectedRegions[this.data.selectedRegions.length - 1] |
||||
|
this.loadAndShowNextLevel(lastRegion.code) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 加载并显示下一级区域选择器
|
||||
|
loadAndShowNextLevel(parentCode) { |
||||
|
this.getRegionData(parentCode, () => { |
||||
|
if (this.data.regionList.length > 0) { |
||||
|
// 有下一级数据,自动弹出选择器
|
||||
|
this.setData({ |
||||
|
showPicker: true, |
||||
|
regionTitle: `请选择${this.data.selectedRegions[this.data.selectedRegions.length - 1].name}的下一级区域` |
||||
|
}) |
||||
|
} else { |
||||
|
// 没有下一级数据,提示用户
|
||||
|
wx.showToast({ |
||||
|
title: '已经是最后一级,无法继续选择', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 关闭选择器
|
||||
|
closePicker() { |
||||
|
this.setData({ |
||||
|
showPicker: false |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 选择区域
|
||||
|
selectRegion(e) { |
||||
|
const index = e.currentTarget.dataset.index |
||||
|
const region = this.data.regionList[index] |
||||
|
|
||||
|
console.log('选择的区域:', region) |
||||
|
|
||||
|
// 获取当前已选择的最后一级
|
||||
|
const lastSelectedRegion = this.data.selectedRegions.length > 0 ? |
||||
|
this.data.selectedRegions[this.data.selectedRegions.length - 1] : |
||||
|
null |
||||
|
|
||||
|
// 检查是否是同级选择(替换最后一级)
|
||||
|
const isSameLevel = lastSelectedRegion && |
||||
|
lastSelectedRegion.parentCode === region.parentCode |
||||
|
|
||||
|
let selectedRegions = [...this.data.selectedRegions] |
||||
|
|
||||
|
if (isSameLevel) { |
||||
|
// 同级选择,替换最后一级
|
||||
|
selectedRegions[selectedRegions.length - 1] = { |
||||
|
code: region.code, |
||||
|
name: region.name, |
||||
|
parentCode: region.parentCode |
||||
|
} |
||||
|
} else { |
||||
|
// 选择下一级,添加到路径
|
||||
|
selectedRegions.push({ |
||||
|
code: region.code, |
||||
|
name: region.name, |
||||
|
parentCode: region.parentCode |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
selectedRegions |
||||
|
}) |
||||
|
|
||||
|
// 关闭当前选择器
|
||||
|
this.setData({ |
||||
|
showPicker: false |
||||
|
}) |
||||
|
|
||||
|
// 延迟一段时间后自动加载并显示下一级选择器
|
||||
|
setTimeout(() => { |
||||
|
this.loadAndShowNextLevel(region.code) |
||||
|
}, 300) |
||||
|
}, |
||||
|
|
||||
|
// 重新选择(点击已选择的任意层级)
|
||||
|
reSelectRegion(e) { |
||||
|
const index = e.currentTarget.dataset.index |
||||
|
|
||||
|
// 截断到指定级别(包括点击的层级)
|
||||
|
const selectedRegions = this.data.selectedRegions.slice(0, index + 1) |
||||
|
|
||||
|
this.setData({ |
||||
|
selectedRegions |
||||
|
}) |
||||
|
|
||||
|
// 获取该层级的数据
|
||||
|
const targetRegion = selectedRegions[selectedRegions.length - 1] |
||||
|
|
||||
|
// 获取点击层级的同级数据并显示选择器
|
||||
|
this.getRegionData(targetRegion.parentCode, () => { |
||||
|
this.setData({ |
||||
|
showPicker: true, |
||||
|
regionTitle: index === 0 ? |
||||
|
'请选择区域' : |
||||
|
`请选择${this.data.selectedRegions[index - 1]?.name || '区域'}的下一级` |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 完成选择
|
||||
|
completeSelection() { |
||||
|
if (this.data.selectedRegions.length === 0) { |
||||
|
wx.showToast({ |
||||
|
title: '请先选择区域', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const lastRegion = this.data.selectedRegions[this.data.selectedRegions.length - 1] |
||||
|
|
||||
|
// 这里可以调用你的业务接口,传递parentCode
|
||||
|
this.submitRegion(lastRegion.code) |
||||
|
}, |
||||
|
|
||||
|
// 提交选择的区域(示例)
|
||||
|
submitRegion(parentCode) { |
||||
|
console.log('提交的parentCode:', parentCode) |
||||
|
console.log('完整选择路径:', this.data.selectedRegions) |
||||
|
http.userCode({ |
||||
|
data: { |
||||
|
areaCode: parentCode |
||||
|
}, |
||||
|
success: res => { |
||||
|
console.log(11111, res); |
||||
|
if (res.code == 200) { |
||||
|
wx.showModal({ |
||||
|
title: '选择完成', |
||||
|
content: `已选择到: ${this.data.selectedRegions.map(r => r.name).join(' > ')}`, |
||||
|
showCancel: false, |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
wx.switchTab({ |
||||
|
url: '/pages/home/home', |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
}, |
||||
|
|
||||
|
// 重置选择
|
||||
|
resetSelection() { |
||||
|
wx.showModal({ |
||||
|
title: '确认重置', |
||||
|
content: '确定要重置所有选择吗?', |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
this.setData({ |
||||
|
selectedRegions: [], |
||||
|
regionList: [], |
||||
|
currentParentCode: '', |
||||
|
showPicker: false |
||||
|
}) |
||||
|
|
||||
|
// 重新获取第一级数据
|
||||
|
this.getRegionData('', () => { |
||||
|
// 重置后自动弹出第一级选择器
|
||||
|
this.setData({ |
||||
|
showPicker: true, |
||||
|
regionTitle: '请选择区域' |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"区域选择", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
<view class="container"> |
||||
|
<!-- 已选择区域路径显示 --> |
||||
|
<view class="selected-path" wx:if="{{selectedRegions.length > 0}}"> |
||||
|
<text class="path-title">已选择:</text> |
||||
|
<view class="path-items"> |
||||
|
<view |
||||
|
wx:for="{{selectedRegions}}" |
||||
|
wx:key="index" |
||||
|
class="path-item {{index === selectedRegions.length - 1 ? 'last' : ''}}" |
||||
|
data-index="{{index}}" |
||||
|
bindtap="reSelectRegion" |
||||
|
> |
||||
|
<text>{{item.name}}</text> |
||||
|
<text class="separator" wx:if="{{index < selectedRegions.length - 1}}">></text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 选择按钮 --> |
||||
|
<view class="select-btn-container"> |
||||
|
<button |
||||
|
class="select-btn" |
||||
|
bindtap="startSelection" |
||||
|
loading="{{loading}}" |
||||
|
> |
||||
|
{{selectedRegions.length === 0 ? '开始选择区域' : '继续选择下一级'}} |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 操作按钮 --> |
||||
|
<view class="action-buttons"> |
||||
|
<button |
||||
|
class="btn complete-btn" |
||||
|
bindtap="completeSelection" |
||||
|
disabled="{{selectedRegions.length === 0}}" |
||||
|
> |
||||
|
完成选择 |
||||
|
</button> |
||||
|
<button |
||||
|
class="btn reset-btn" |
||||
|
bindtap="resetSelection" |
||||
|
wx:if="{{selectedRegions.length > 0}}" |
||||
|
> |
||||
|
重置 |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 选择器弹出层 --> |
||||
|
<view class="picker-modal" wx:if="{{showPicker}}"> |
||||
|
<view class="picker-mask" bindtap="closePicker"></view> |
||||
|
<view class="picker-content"> |
||||
|
<view class="picker-header"> |
||||
|
<text class="picker-title">{{regionTitle}}</text> |
||||
|
<text class="picker-close" bindtap="closePicker">×</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="picker-body"> |
||||
|
<!-- 加载状态 --> |
||||
|
<view class="loading-container" wx:if="{{loading}}"> |
||||
|
<view class="loading-spinner"></view> |
||||
|
<text class="loading-text">加载中...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 区域列表 --> |
||||
|
<scroll-view scroll-y class="region-list" wx:else> |
||||
|
<view |
||||
|
wx:for="{{regionList}}" |
||||
|
wx:key="id" |
||||
|
class="region-item" |
||||
|
data-index="{{index}}" |
||||
|
bindtap="selectRegion" |
||||
|
> |
||||
|
<view class="region-info"> |
||||
|
<text class="region-name">{{item.name}}</text> |
||||
|
</view> |
||||
|
<text class="arrow">›</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 空状态 --> |
||||
|
<view class="empty-state" wx:if="{{regionList.length === 0}}"> |
||||
|
<text class="empty-text"> |
||||
|
{{selectedRegions.length > 0 ? '已选择到最后一级' : '暂无区域数据'}} |
||||
|
</text> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 使用说明 --> |
||||
|
<view class="instruction"> |
||||
|
<text class="instruction-title">使用说明:</text> |
||||
|
<text class="instruction-text">1. 点击"开始选择区域"开始选择\n2. 选择后会加载下一级区域\n3. 点击已选择区域的任意级别可以重新选择\n4. 选择最后一级时会直接替换\n5. 完成选择后点击"完成选择"按钮</text> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,316 @@ |
|||||
|
.container { |
||||
|
padding: 30rpx; |
||||
|
min-height: 100vh; |
||||
|
background: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
/* 已选择路径 */ |
||||
|
.selected-path { |
||||
|
background: white; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 30rpx; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
.path-title { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
display: block; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.path-items { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.path-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 12rpx 20rpx; |
||||
|
background: #f0f8ff; |
||||
|
border-radius: 8rpx; |
||||
|
margin-right: 10rpx; |
||||
|
margin-bottom: 10rpx; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.path-item:active { |
||||
|
background: #e1f0ff; |
||||
|
} |
||||
|
|
||||
|
.path-item.last { |
||||
|
background: #e6f7ff; |
||||
|
border: 1rpx solid #1890ff; |
||||
|
} |
||||
|
|
||||
|
.path-item text:first-child { |
||||
|
font-size: 28rpx; |
||||
|
color: #1890ff; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.separator { |
||||
|
margin-left: 10rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* 选择按钮 */ |
||||
|
.select-btn-container { |
||||
|
margin: 40rpx 0; |
||||
|
} |
||||
|
|
||||
|
.select-btn { |
||||
|
width: 100%; |
||||
|
background: linear-gradient(135deg, #1890ff, #096dd9); |
||||
|
color: white; |
||||
|
border-radius: 12rpx; |
||||
|
font-size: 32rpx; |
||||
|
height: 88rpx; |
||||
|
line-height: 88rpx; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.select-btn::after { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.select-btn[loading] { |
||||
|
opacity: 0.8; |
||||
|
} |
||||
|
|
||||
|
.select-btn:active { |
||||
|
opacity: 0.9; |
||||
|
} |
||||
|
|
||||
|
/* 操作按钮 */ |
||||
|
.action-buttons { |
||||
|
display: flex; |
||||
|
gap: 20rpx; |
||||
|
margin-top: 50rpx; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
flex: 1; |
||||
|
border-radius: 12rpx; |
||||
|
font-size: 28rpx; |
||||
|
height: 80rpx; |
||||
|
line-height: 80rpx; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.btn::after { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.complete-btn { |
||||
|
background: #07c160; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.complete-btn[disabled] { |
||||
|
background: #ccc; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.complete-btn:active:not([disabled]) { |
||||
|
background: #06ad56; |
||||
|
} |
||||
|
|
||||
|
.reset-btn { |
||||
|
background: #fff; |
||||
|
color: #ff4d4f; |
||||
|
border: 1rpx solid #ff4d4f !important; |
||||
|
} |
||||
|
|
||||
|
.reset-btn:active { |
||||
|
background: #fff5f5; |
||||
|
} |
||||
|
|
||||
|
/* 选择器模态框 */ |
||||
|
.picker-modal { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 1000; |
||||
|
} |
||||
|
|
||||
|
.picker-mask { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
animation: fadeIn 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.picker-content { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
background: white; |
||||
|
border-radius: 32rpx 32rpx 0 0; |
||||
|
max-height: 70vh; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
animation: slideUp 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.picker-header { |
||||
|
padding: 32rpx 40rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.picker-title { |
||||
|
font-size: 32rpx; |
||||
|
color: #333; |
||||
|
font-weight: 600; |
||||
|
flex: 1; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.picker-close { |
||||
|
font-size: 48rpx; |
||||
|
color: #999; |
||||
|
position: absolute; |
||||
|
right: 30rpx; |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%); |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
text-align: center; |
||||
|
line-height: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.picker-close:active { |
||||
|
background: #f5f5f5; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
.picker-body { |
||||
|
flex: 1; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
/* 加载状态 */ |
||||
|
.loading-container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 300rpx; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
border: 4rpx solid #f0f0f0; |
||||
|
border-top-color: #1890ff; |
||||
|
border-radius: 50%; |
||||
|
animation: spin 1s linear infinite; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 区域列表 */ |
||||
|
.region-list { |
||||
|
height: 60vh; |
||||
|
} |
||||
|
|
||||
|
.region-item { |
||||
|
padding: 32rpx 40rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.region-item:active { |
||||
|
background: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
.region-info { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.region-name { |
||||
|
font-size: 30rpx; |
||||
|
color: #333; |
||||
|
display: block; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.arrow { |
||||
|
color: #ccc; |
||||
|
font-size: 36rpx; |
||||
|
margin-left: 20rpx; |
||||
|
} |
||||
|
|
||||
|
/* 空状态 */ |
||||
|
.empty-state { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
height: 200rpx; |
||||
|
} |
||||
|
|
||||
|
.empty-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 使用说明 */ |
||||
|
.instruction { |
||||
|
margin-top: 60rpx; |
||||
|
padding: 24rpx; |
||||
|
background: white; |
||||
|
border-radius: 12rpx; |
||||
|
border-left: 6rpx solid #1890ff; |
||||
|
} |
||||
|
|
||||
|
.instruction-title { |
||||
|
font-size: 28rpx; |
||||
|
color: #1890ff; |
||||
|
font-weight: 600; |
||||
|
display: block; |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.instruction-text { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.6; |
||||
|
white-space: pre-line; |
||||
|
} |
||||
|
|
||||
|
/* 动画 */ |
||||
|
@keyframes fadeIn { |
||||
|
from { opacity: 0; } |
||||
|
to { opacity: 1; } |
||||
|
} |
||||
|
|
||||
|
@keyframes slideUp { |
||||
|
from { transform: translateY(100%); } |
||||
|
to { transform: translateY(0); } |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
from { transform: rotate(0deg); } |
||||
|
to { transform: rotate(360deg); } |
||||
|
} |
||||
@ -0,0 +1,580 @@ |
|||||
|
import http from '../../../utils/api' |
||||
|
const baseUrl = require('../../../utils/baseUrl') |
||||
|
|
||||
|
Page({ |
||||
|
data: { |
||||
|
baseUrl, |
||||
|
currentTab: 'article', |
||||
|
|
||||
|
// 文章相关
|
||||
|
articleForm: { |
||||
|
title: '', |
||||
|
subtitle: '', |
||||
|
category: '', |
||||
|
content: '', |
||||
|
coverImage: '' // 这里存储服务器返回的文件名
|
||||
|
}, |
||||
|
articleCoverTemp: '', // 本地临时路径,用于预览
|
||||
|
articleCategory: null, |
||||
|
articleCategories: [], |
||||
|
|
||||
|
// 视频相关
|
||||
|
videoForm: { |
||||
|
title: '', |
||||
|
description: '', |
||||
|
category: '', |
||||
|
videoUrl: '', // 这里存储服务器返回的视频文件名
|
||||
|
coverImage: '' // 这里存储服务器返回的封面文件名
|
||||
|
}, |
||||
|
videoCoverTemp: '', // 本地临时路径,用于预览
|
||||
|
videoUrlTemp: '', // 本地临时路径,用于显示
|
||||
|
videoCategory: null, |
||||
|
videoCategories: [], |
||||
|
|
||||
|
// UI状态
|
||||
|
submitting: false, |
||||
|
isUploading: false, // 防止重复上传
|
||||
|
showLoadingMask: false, // 显示加载遮罩层
|
||||
|
loadingText: '发布中...', // 加载提示文字
|
||||
|
|
||||
|
// 表单验证状态
|
||||
|
articleFormValid: false, |
||||
|
videoFormValid: false, |
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
this.getArticleCategories() |
||||
|
this.getVideoCategories() |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
// 页面卸载时重置状态
|
||||
|
this.setData({ |
||||
|
submitting: false, |
||||
|
showLoadingMask: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 获取文章分类
|
||||
|
getArticleCategories() { |
||||
|
http.articleZd({ |
||||
|
data: { |
||||
|
dictType: 'article_category' |
||||
|
}, |
||||
|
success: res => { |
||||
|
if (res.rows) { |
||||
|
this.setData({ |
||||
|
articleCategories: res.rows |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 获取视频分类
|
||||
|
getVideoCategories() { |
||||
|
http.videoZd({ |
||||
|
data: { |
||||
|
dictType: 'video_category' |
||||
|
}, |
||||
|
success: res => { |
||||
|
if (res.rows) { |
||||
|
this.setData({ |
||||
|
videoCategories: res.rows |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 切换标签
|
||||
|
switchTab(e) { |
||||
|
const type = e.currentTarget.dataset.type |
||||
|
if (type === this.data.currentTab) return |
||||
|
this.setData({ currentTab: type }) |
||||
|
}, |
||||
|
|
||||
|
// 验证文章表单
|
||||
|
validateArticleForm() { |
||||
|
const { articleForm } = this.data |
||||
|
const isValid = !!(articleForm.title?.trim() && articleForm.category && articleForm.content?.trim()) |
||||
|
this.setData({ articleFormValid: isValid }) |
||||
|
return isValid |
||||
|
}, |
||||
|
|
||||
|
// 验证视频表单
|
||||
|
validateVideoForm() { |
||||
|
const { videoForm } = this.data |
||||
|
const isValid = !!(videoForm.title?.trim() && videoForm.category && videoForm.videoUrl) |
||||
|
this.setData({ videoFormValid: isValid }) |
||||
|
return isValid |
||||
|
}, |
||||
|
|
||||
|
// 文章输入处理
|
||||
|
onArticleInput(e) { |
||||
|
const field = e.currentTarget.dataset.field |
||||
|
const value = e.detail.value |
||||
|
this.setData({ |
||||
|
[`articleForm.${field}`]: value |
||||
|
}, () => { |
||||
|
this.validateArticleForm() |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 视频输入处理
|
||||
|
onVideoInput(e) { |
||||
|
const field = e.currentTarget.dataset.field |
||||
|
const value = e.detail.value |
||||
|
this.setData({ |
||||
|
[`videoForm.${field}`]: value |
||||
|
}, () => { |
||||
|
this.validateVideoForm() |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 文章分类选择
|
||||
|
onArticleCategoryChange(e) { |
||||
|
const index = e.detail.value |
||||
|
const category = this.data.articleCategories[index] |
||||
|
this.setData({ |
||||
|
'articleForm.category': category.dictValue, |
||||
|
articleCategory: category |
||||
|
}, () => { |
||||
|
this.validateArticleForm() |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 视频分类选择
|
||||
|
onVideoCategoryChange(e) { |
||||
|
const index = e.detail.value |
||||
|
const category = this.data.videoCategories[index] |
||||
|
this.setData({ |
||||
|
'videoForm.category': category.dictValue, |
||||
|
videoCategory: category |
||||
|
}, () => { |
||||
|
this.validateVideoForm() |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 选择封面图片(文章或视频)
|
||||
|
chooseCover(e) { |
||||
|
if (this.data.isUploading) { |
||||
|
wx.showToast({ |
||||
|
title: '正在上传中,请稍候', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const type = e.currentTarget.dataset.type |
||||
|
|
||||
|
wx.chooseMedia({ |
||||
|
count: 1, |
||||
|
mediaType: ['image'], |
||||
|
sourceType: ['album', 'camera'], |
||||
|
sizeType: ['compressed'], |
||||
|
success: (res) => { |
||||
|
if (res.tempFiles && res.tempFiles.length > 0) { |
||||
|
this.setData({ |
||||
|
isUploading: true |
||||
|
}); |
||||
|
|
||||
|
// 显示加载提示
|
||||
|
wx.showLoading({ |
||||
|
title: '上传图片中...', |
||||
|
mask: true |
||||
|
}); |
||||
|
|
||||
|
// 上传图片
|
||||
|
this.uploadImage(res.tempFiles[0].tempFilePath, type); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 上传单张图片
|
||||
|
uploadImage(tempPath, type) { |
||||
|
wx.uploadFile({ |
||||
|
url: baseUrl + '/common/upload', |
||||
|
header: { |
||||
|
'Authorization': 'Bearer ' + wx.getStorageSync('token') |
||||
|
}, |
||||
|
filePath: tempPath, |
||||
|
name: 'file', |
||||
|
success: (uploadRes) => { |
||||
|
try { |
||||
|
const result = JSON.parse(uploadRes.data); |
||||
|
if (result.code === 200 || result.fileName) { |
||||
|
const serverPath = result.fileName || result.url; |
||||
|
|
||||
|
if (type === 'article') { |
||||
|
// 文章封面
|
||||
|
this.setData({ |
||||
|
articleCoverTemp: tempPath, |
||||
|
'articleForm.coverImage': serverPath, |
||||
|
isUploading: false |
||||
|
}); |
||||
|
} else { |
||||
|
// 视频封面
|
||||
|
this.setData({ |
||||
|
videoCoverTemp: tempPath, |
||||
|
'videoForm.coverImage': serverPath, |
||||
|
isUploading: false |
||||
|
}, () => { |
||||
|
this.validateVideoForm(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
wx.hideLoading(); |
||||
|
wx.showToast({ |
||||
|
title: '上传成功', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
} else { |
||||
|
throw new Error(result.msg || '上传失败'); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
wx.hideLoading(); |
||||
|
this.setData({ |
||||
|
isUploading: false |
||||
|
}); |
||||
|
wx.showToast({ |
||||
|
title: error.message || '上传失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
fail: (error) => { |
||||
|
wx.hideLoading(); |
||||
|
this.setData({ |
||||
|
isUploading: false |
||||
|
}); |
||||
|
wx.showToast({ |
||||
|
title: '网络请求失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 选择视频
|
||||
|
chooseVideo() { |
||||
|
if (this.data.isUploading) { |
||||
|
wx.showToast({ |
||||
|
title: '正在上传中,请稍候', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
wx.chooseMedia({ |
||||
|
count: 1, |
||||
|
mediaType: ['video'], |
||||
|
sourceType: ['album', 'camera'], |
||||
|
maxDuration: 300, |
||||
|
success: (res) => { |
||||
|
if (res.tempFiles && res.tempFiles.length > 0) { |
||||
|
this.setData({ |
||||
|
isUploading: true, |
||||
|
videoUrlTemp: res.tempFiles[0].tempFilePath // 先显示本地路径
|
||||
|
}); |
||||
|
|
||||
|
// 显示加载提示
|
||||
|
wx.showLoading({ |
||||
|
title: '上传视频中...', |
||||
|
mask: true |
||||
|
}); |
||||
|
|
||||
|
// 上传视频
|
||||
|
this.uploadVideo(res.tempFiles[0].tempFilePath); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 上传视频
|
||||
|
uploadVideo(tempPath) { |
||||
|
wx.uploadFile({ |
||||
|
url: baseUrl + '/common/upload', |
||||
|
header: { |
||||
|
'Authorization': 'Bearer ' + wx.getStorageSync('token') |
||||
|
}, |
||||
|
filePath: tempPath, |
||||
|
name: 'file', |
||||
|
success: (uploadRes) => { |
||||
|
try { |
||||
|
const result = JSON.parse(uploadRes.data); |
||||
|
if (result.code === 200 || result.fileName) { |
||||
|
const serverPath = result.fileName || result.url; |
||||
|
|
||||
|
this.setData({ |
||||
|
'videoForm.videoUrl': serverPath, |
||||
|
isUploading: false |
||||
|
}, () => { |
||||
|
this.validateVideoForm(); |
||||
|
}); |
||||
|
|
||||
|
wx.hideLoading(); |
||||
|
wx.showToast({ |
||||
|
title: '上传成功', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
} else { |
||||
|
throw new Error(result.msg || '上传失败'); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
wx.hideLoading(); |
||||
|
this.setData({ |
||||
|
videoUrlTemp: '', // 上传失败清空临时路径
|
||||
|
isUploading: false |
||||
|
}); |
||||
|
wx.showToast({ |
||||
|
title: error.message || '上传失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
fail: (error) => { |
||||
|
wx.hideLoading(); |
||||
|
this.setData({ |
||||
|
videoUrlTemp: '', // 上传失败清空临时路径
|
||||
|
isUploading: false |
||||
|
}); |
||||
|
wx.showToast({ |
||||
|
title: '网络请求失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 提交文章
|
||||
|
submitArticle() { |
||||
|
const { articleForm, submitting, isUploading } = this.data |
||||
|
|
||||
|
if (submitting) return |
||||
|
|
||||
|
// 检查是否还有图片正在上传
|
||||
|
if (isUploading) { |
||||
|
wx.showToast({ |
||||
|
title: '图片正在上传中,请稍后提交', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 表单验证
|
||||
|
if (!this.validateArticleForm()) { |
||||
|
if (!articleForm.title?.trim()) { |
||||
|
this.showError('请输入文章标题') |
||||
|
} else if (!articleForm.category) { |
||||
|
this.showError('请选择文章分类') |
||||
|
} else if (!articleForm.content?.trim()) { |
||||
|
this.showError('请输入文章内容') |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 显示加载遮罩层
|
||||
|
this.setData({ |
||||
|
submitting: true, |
||||
|
showLoadingMask: true, |
||||
|
loadingText: '发布中...' |
||||
|
}); |
||||
|
|
||||
|
// 构建提交数据
|
||||
|
const submitData = { |
||||
|
title: articleForm.title.trim(), |
||||
|
subtitle: articleForm.subtitle?.trim() || '', |
||||
|
content: articleForm.content.trim(), |
||||
|
coverImage: articleForm.coverImage || '', |
||||
|
category: articleForm.category |
||||
|
} |
||||
|
|
||||
|
// 调用接口
|
||||
|
http.articleAdd({ |
||||
|
data: submitData, |
||||
|
success: (res) => { |
||||
|
if (res.code == 200) { |
||||
|
this.setData({ |
||||
|
loadingText: '发布成功' |
||||
|
}); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ |
||||
|
submitting: false, |
||||
|
showLoadingMask: false |
||||
|
}); |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '发布成功', |
||||
|
icon: 'success', |
||||
|
duration: 1500, |
||||
|
success: () => { |
||||
|
setTimeout(() => { |
||||
|
wx.navigateBack() |
||||
|
}, 1500); |
||||
|
} |
||||
|
}); |
||||
|
}, 1000); |
||||
|
} else { |
||||
|
this.setData({ |
||||
|
loadingText: '发布失败' |
||||
|
}); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ |
||||
|
submitting: false, |
||||
|
showLoadingMask: false |
||||
|
}); |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: res.msg || '发布失败,请重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
}, 1000); |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
this.setData({ |
||||
|
loadingText: '网络错误' |
||||
|
}); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ |
||||
|
submitting: false, |
||||
|
showLoadingMask: false |
||||
|
}); |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '网络异常,请检查网络后重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
}, 1000); |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 提交视频
|
||||
|
submitVideo() { |
||||
|
const { videoForm, submitting, isUploading } = this.data |
||||
|
|
||||
|
if (submitting) return |
||||
|
|
||||
|
// 检查是否还有图片或视频正在上传
|
||||
|
if (isUploading) { |
||||
|
wx.showToast({ |
||||
|
title: '文件正在上传中,请稍后提交', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 表单验证
|
||||
|
if (!this.validateVideoForm()) { |
||||
|
if (!videoForm.title?.trim()) { |
||||
|
this.showError('请输入视频标题') |
||||
|
} else if (!videoForm.category) { |
||||
|
this.showError('请选择视频分类') |
||||
|
} else if (!videoForm.videoUrl) { |
||||
|
this.showError('请选择视频') |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 显示加载遮罩层
|
||||
|
this.setData({ |
||||
|
submitting: true, |
||||
|
showLoadingMask: true, |
||||
|
loadingText: '发布中...' |
||||
|
}); |
||||
|
|
||||
|
// 构建提交数据
|
||||
|
const submitData = { |
||||
|
title: videoForm.title.trim(), |
||||
|
description: videoForm.description?.trim() || '', |
||||
|
videoUrl: videoForm.videoUrl, |
||||
|
coverImage: videoForm.coverImage || '', |
||||
|
category: videoForm.category |
||||
|
} |
||||
|
|
||||
|
// 调用接口
|
||||
|
http.videoAdd({ |
||||
|
data: submitData, |
||||
|
success: (res) => { |
||||
|
if (res.code == 200) { |
||||
|
this.setData({ |
||||
|
loadingText: '发布成功' |
||||
|
}); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ |
||||
|
submitting: false, |
||||
|
showLoadingMask: false |
||||
|
}); |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '发布成功', |
||||
|
icon: 'success', |
||||
|
duration: 1500, |
||||
|
success: () => { |
||||
|
setTimeout(() => { |
||||
|
wx.navigateBack() |
||||
|
}, 1500); |
||||
|
} |
||||
|
}); |
||||
|
}, 1000); |
||||
|
} else { |
||||
|
this.setData({ |
||||
|
loadingText: '发布失败' |
||||
|
}); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ |
||||
|
submitting: false, |
||||
|
showLoadingMask: false |
||||
|
}); |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: res.msg || '发布失败,请重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
}, 1000); |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
this.setData({ |
||||
|
loadingText: '网络错误' |
||||
|
}); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ |
||||
|
submitting: false, |
||||
|
showLoadingMask: false |
||||
|
}); |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '网络异常,请检查网络后重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
}, 1000); |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 显示错误提示
|
||||
|
showError(msg) { |
||||
|
wx.showToast({ |
||||
|
title: msg, |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"发布", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,205 @@ |
|||||
|
<view class="container"> |
||||
|
<!-- 顶部背景装饰 --> |
||||
|
<view class="bg-decoration"> |
||||
|
<view class="circle circle-1"></view> |
||||
|
<view class="circle circle-2"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 分类切换卡片 --> |
||||
|
<view class="tab-card"> |
||||
|
<view class="category-tab"> |
||||
|
<view class="tab-item {{currentTab === 'article' ? 'active' : ''}}" data-type="article" bindtap="switchTab"> |
||||
|
<text class="tab-text">发布文章</text> |
||||
|
</view> |
||||
|
<view class="tab-item {{currentTab === 'video' ? 'active' : ''}}" data-type="video" bindtap="switchTab"> |
||||
|
<text class="tab-text">发布视频</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章发布表单 --> |
||||
|
<form wx:if="{{currentTab === 'article'}}" catchsubmit="submitArticle"> |
||||
|
<view class="form-container"> |
||||
|
<!-- 文章标题 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">文章标题</text> |
||||
|
<text class="required">*</text> |
||||
|
</view> |
||||
|
<input class="input" type="text" placeholder="请输入文章标题" placeholder-class="placeholder" bindinput="onArticleInput" data-field="title" value="{{articleForm.title}}" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 副标题 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">副标题</text> |
||||
|
<text class="optional">选填</text> |
||||
|
</view> |
||||
|
<input class="input" type="text" placeholder="请输入副标题" placeholder-class="placeholder" bindinput="onArticleInput" data-field="subtitle" value="{{articleForm.subtitle}}" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章分类 - 新样式 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">文章分类</text> |
||||
|
<text class="required">*</text> |
||||
|
</view> |
||||
|
<picker mode="selector" range="{{articleCategories}}" range-key="dictLabel" bindchange="onArticleCategoryChange" data-type="article"> |
||||
|
<view class="category-selector"> |
||||
|
<block wx:if="{{articleCategory}}"> |
||||
|
<view class="category-badge" style="background: linear-gradient(135deg, {{getCategoryColor(articleCategory.dictLabel).start}} 0%, {{getCategoryColor(articleCategory.dictLabel).end}} 100%);"> |
||||
|
{{articleCategory.dictLabel}} |
||||
|
</view> |
||||
|
</block> |
||||
|
<view wx:else class="category-placeholder"> |
||||
|
<text>请选择文章分类</text> |
||||
|
<text class="arrow">›</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</picker> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章封面 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">封面图片</text> |
||||
|
<text class="optional">选填</text> |
||||
|
</view> |
||||
|
<view class="uploader-wrapper"> |
||||
|
<view class="uploader" bindtap="chooseCover" data-type="article"> |
||||
|
<image class="preview" src="{{articleCoverTemp || ''}}" mode="aspectFill" wx:if="{{articleCoverTemp}}"></image> |
||||
|
<view class="upload-placeholder" wx:else> |
||||
|
<text class="plus">+</text> |
||||
|
<text class="hint">点击上传封面</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<text class="upload-tip">建议尺寸 16:9,不超过5M</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章内容 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">文章内容</text> |
||||
|
<text class="required">*</text> |
||||
|
</view> |
||||
|
<textarea class="textarea" placeholder="写下你的精彩内容吧..." placeholder-class="placeholder" bindinput="onArticleInput" data-field="content" value="{{articleForm.content}}" auto-height maxlength="5000" /> |
||||
|
<text class="word-count">{{articleForm.content.length || 0}}/5000</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<view class="btn-wrapper"> |
||||
|
<button class="submit-btn {{!articleFormValid ? 'disabled' : ''}}" form-type="submit" disabled="{{!articleFormValid || submitting}}"> |
||||
|
<text wx:if="{{!submitting}}">发布文章</text> |
||||
|
<text wx:else>发布中...</text> |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</form> |
||||
|
|
||||
|
<!-- 视频发布表单 --> |
||||
|
<form wx:else catchsubmit="submitVideo"> |
||||
|
<view class="form-container"> |
||||
|
<!-- 视频标题 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">视频标题</text> |
||||
|
<text class="required">*</text> |
||||
|
</view> |
||||
|
<input class="input" type="text" placeholder="请输入视频标题" placeholder-class="placeholder" bindinput="onVideoInput" data-field="title" value="{{videoForm.title}}" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频描述 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">视频描述</text> |
||||
|
<text class="optional">选填</text> |
||||
|
</view> |
||||
|
<input class="input" type="text" placeholder="简单描述一下你的视频内容" placeholder-class="placeholder" bindinput="onVideoInput" data-field="description" value="{{videoForm.description}}" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频分类 - 新样式 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">视频分类</text> |
||||
|
<text class="required">*</text> |
||||
|
</view> |
||||
|
<picker mode="selector" range="{{videoCategories}}" range-key="dictLabel" bindchange="onVideoCategoryChange" data-type="video"> |
||||
|
<view class="category-selector"> |
||||
|
<block wx:if="{{videoCategory}}"> |
||||
|
<view class="category-badge" style="background: linear-gradient(135deg, {{getCategoryColor(videoCategory.dictLabel).start}} 0%, {{getCategoryColor(videoCategory.dictLabel).end}} 100%);"> |
||||
|
{{videoCategory.dictLabel}} |
||||
|
</view> |
||||
|
</block> |
||||
|
<view wx:else class="category-placeholder"> |
||||
|
<text>请选择视频分类</text> |
||||
|
<text class="arrow">›</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</picker> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频封面 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">视频封面</text> |
||||
|
<text class="optional">选填</text> |
||||
|
</view> |
||||
|
<view class="uploader-wrapper"> |
||||
|
<view class="uploader" bindtap="chooseCover" data-type="video"> |
||||
|
<image class="preview" src="{{videoCoverTemp || ''}}" mode="aspectFill" wx:if="{{videoCoverTemp}}"></image> |
||||
|
<view class="upload-placeholder" wx:else> |
||||
|
<text class="plus">+</text> |
||||
|
<text class="hint">点击上传封面</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频地址 --> |
||||
|
<view class="form-card"> |
||||
|
<view class="form-item"> |
||||
|
<view class="label-wrapper"> |
||||
|
<text class="label">视频地址</text> |
||||
|
<text class="required">*</text> |
||||
|
</view> |
||||
|
<view class="video-picker" bindtap="chooseVideo"> |
||||
|
<view wx:if="{{videoUrlTemp}}" class="video-info"> |
||||
|
<text class="video-name">{{videoUrlTemp}}</text> |
||||
|
</view> |
||||
|
<view wx:else class="video-placeholder"> |
||||
|
<text class="plus">+</text> |
||||
|
<text class="hint">点击选择视频</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<view class="btn-wrapper"> |
||||
|
<button class="submit-btn {{!videoFormValid ? 'disabled' : ''}}" form-type="submit" disabled="{{!videoFormValid || submitting}}"> |
||||
|
<text wx:if="{{!submitting}}">发布视频</text> |
||||
|
<text wx:else>发布中...</text> |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</form> |
||||
|
</view> |
||||
@ -0,0 +1,457 @@ |
|||||
|
.container { |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(145deg, #f8f9ff 0%, #f0f2f6 100%); |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
/* 背景装饰 */ |
||||
|
.bg-decoration { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
pointer-events: none; |
||||
|
z-index: 0; |
||||
|
} |
||||
|
|
||||
|
.circle { |
||||
|
position: absolute; |
||||
|
border-radius: 50%; |
||||
|
background: linear-gradient(135deg, rgba(7, 193, 96, 0.1) 0%, rgba(7, 193, 96, 0.05) 100%); |
||||
|
} |
||||
|
|
||||
|
.circle-1 { |
||||
|
width: 400rpx; |
||||
|
height: 400rpx; |
||||
|
top: -100rpx; |
||||
|
right: -100rpx; |
||||
|
background: linear-gradient(135deg, rgba(64, 169, 255, 0.1) 0%, rgba(64, 169, 255, 0.05) 100%); |
||||
|
} |
||||
|
|
||||
|
.circle-2 { |
||||
|
width: 300rpx; |
||||
|
height: 300rpx; |
||||
|
bottom: 100rpx; |
||||
|
left: -100rpx; |
||||
|
background: linear-gradient(135deg, rgba(255, 184, 0, 0.1) 0%, rgba(255, 184, 0, 0.05) 100%); |
||||
|
} |
||||
|
|
||||
|
/* 切换卡片 */ |
||||
|
.tab-card { |
||||
|
margin: 30rpx 30rpx 20rpx; |
||||
|
background: rgba(255, 255, 255, 0.9); |
||||
|
backdrop-filter: blur(20px); |
||||
|
border-radius: 60rpx; |
||||
|
padding: 10rpx; |
||||
|
box-shadow: |
||||
|
0 20rpx 40rpx rgba(0, 0, 0, 0.06), |
||||
|
0 8rpx 20rpx rgba(0, 0, 0, 0.03), |
||||
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.8); |
||||
|
border: 1px solid rgba(255, 255, 255, 0.9); |
||||
|
position: relative; |
||||
|
z-index: 10; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
/* 内层标签容器 */ |
||||
|
.category-tab { |
||||
|
display: flex; |
||||
|
background: rgba(0, 0, 0, 0.02); |
||||
|
border-radius: 56rpx; |
||||
|
padding: 6rpx; |
||||
|
gap: 6rpx; |
||||
|
} |
||||
|
|
||||
|
/* 单个标签项 */ |
||||
|
.tab-item { |
||||
|
flex: 1; |
||||
|
text-align: center; |
||||
|
padding: 24rpx 0; |
||||
|
position: relative; |
||||
|
border-radius: 50rpx; |
||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
||||
|
cursor: pointer; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
/* 标签文字 */ |
||||
|
.tab-text { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 500; |
||||
|
color: #666; |
||||
|
letter-spacing: 2rpx; |
||||
|
position: relative; |
||||
|
z-index: 2; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
/* 选中状态 - 渐变背景 */ |
||||
|
.tab-item.active { |
||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
||||
|
box-shadow: |
||||
|
0 10rpx 20rpx rgba(102, 126, 234, 0.3), |
||||
|
0 4rpx 8rpx rgba(0, 0, 0, 0.1), |
||||
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.5); |
||||
|
transform: translateY(-2rpx); |
||||
|
} |
||||
|
|
||||
|
/* 选中状态的文字 */ |
||||
|
.tab-item.active .tab-text { |
||||
|
color: #ffffff; |
||||
|
font-weight: 600; |
||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
/* 添加光泽效果 */ |
||||
|
.tab-item.active::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: linear-gradient(45deg, |
||||
|
rgba(255, 255, 255, 0.3) 0%, |
||||
|
rgba(255, 255, 255, 0.2) 20%, |
||||
|
transparent 50%); |
||||
|
border-radius: 50rpx; |
||||
|
pointer-events: none; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
/* 添加微光动画 */ |
||||
|
.tab-item.active::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: -50%; |
||||
|
left: -50%; |
||||
|
width: 200%; |
||||
|
height: 200%; |
||||
|
background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, transparent 70%); |
||||
|
opacity: 0.5; |
||||
|
animation: shimmer 3s infinite; |
||||
|
pointer-events: none; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
/* 悬停效果 */ |
||||
|
@media (hover: hover) { |
||||
|
.tab-item:hover:not(.active) { |
||||
|
background: rgba(255, 255, 255, 0.8); |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
.tab-item:hover:not(.active) .tab-text { |
||||
|
color: #333; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 点击效果 */ |
||||
|
.tab-item:active { |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
|
|
||||
|
/* 微光动画 */ |
||||
|
@keyframes shimmer { |
||||
|
0% { |
||||
|
transform: translateX(-100%) translateY(-100%) rotate(45deg); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
20% { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
40% { |
||||
|
transform: translateX(100%) translateY(100%) rotate(45deg); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
transform: translateX(100%) translateY(100%) rotate(45deg); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.tab-item.active:nth-child(1) { |
||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
||||
|
} |
||||
|
|
||||
|
.tab-item.active:nth-child(2) { |
||||
|
background-image: linear-gradient(to right, #f78ca0 0%, #f9748f 19%, #fd868c 60%, #fe9a8b 100%); |
||||
|
} |
||||
|
|
||||
|
.tab-item.active-bottom { |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.tab-item.active-bottom::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
bottom: -10rpx; |
||||
|
left: 50%; |
||||
|
transform: translateX(-50%); |
||||
|
width: 60rpx; |
||||
|
height: 4rpx; |
||||
|
background: linear-gradient(90deg, #667eea, #764ba2); |
||||
|
border-radius: 4rpx; |
||||
|
animation: slideIn 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
/* 表单容器 */ |
||||
|
.form-container { |
||||
|
padding: 0 30rpx 40rpx; |
||||
|
position: relative; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.form-card { |
||||
|
background: rgba(255, 255, 255, 0.9); |
||||
|
backdrop-filter: blur(20px); |
||||
|
border-radius: 32rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
padding: 30rpx; |
||||
|
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.02), |
||||
|
0 2rpx 8rpx rgba(0, 0, 0, 0.01); |
||||
|
border: 1px solid rgba(255, 255, 255, 0.8); |
||||
|
} |
||||
|
|
||||
|
.form-item { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.label-wrapper { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-right: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.required { |
||||
|
font-size: 24rpx; |
||||
|
color: #ff6b6b; |
||||
|
margin-left: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.optional { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background: rgba(0, 0, 0, 0.03); |
||||
|
padding: 4rpx 12rpx; |
||||
|
border-radius: 20rpx; |
||||
|
margin-left: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.input { |
||||
|
width: 100%; |
||||
|
height: 88rpx; |
||||
|
background: #f8f9fc; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 0 30rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
border: 2rpx solid transparent; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.input:focus { |
||||
|
border-color: #07c160; |
||||
|
background: #fff; |
||||
|
} |
||||
|
|
||||
|
.placeholder { |
||||
|
color: #b8b8b8; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.textarea { |
||||
|
width: 100%; |
||||
|
min-height: 240rpx; |
||||
|
background: #f8f9fc; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 24rpx 30rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
border: 2rpx solid transparent; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.textarea:focus { |
||||
|
border-color: #07c160; |
||||
|
background: #fff; |
||||
|
} |
||||
|
|
||||
|
.word-count { |
||||
|
display: block; |
||||
|
text-align: right; |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
margin-top: 12rpx; |
||||
|
} |
||||
|
|
||||
|
/* 分类选择器 */ |
||||
|
.category-selector { |
||||
|
width: 100%; |
||||
|
min-height: 88rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.category-badge { |
||||
|
display: inline-block; |
||||
|
padding: 16rpx 32rpx; |
||||
|
border-radius: 40rpx; |
||||
|
color: #648ac2; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 500; |
||||
|
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.1); |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
.category-placeholder { |
||||
|
width: 100%; |
||||
|
height: 88rpx; |
||||
|
background: #f8f9fc; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 0 30rpx; |
||||
|
font-size: 28rpx; |
||||
|
line-height: 88rpx; |
||||
|
color: #b8b8b8; |
||||
|
border: 2rpx solid transparent; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.category-placeholder .arrow { |
||||
|
font-size: 40rpx; |
||||
|
color: #999; |
||||
|
transform: rotate(90deg); |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
/* 上传器 */ |
||||
|
.uploader-wrapper { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: flex-start; |
||||
|
} |
||||
|
|
||||
|
.uploader { |
||||
|
width: 220rpx; |
||||
|
height: 220rpx; |
||||
|
background: #f8f9fc; |
||||
|
border-radius: 24rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
overflow: hidden; |
||||
|
border: 2rpx dashed #ddd; |
||||
|
} |
||||
|
|
||||
|
.preview { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
object-fit: cover; |
||||
|
} |
||||
|
|
||||
|
.upload-placeholder { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.upload-placeholder .plus { |
||||
|
font-size: 60rpx; |
||||
|
color: #ccc; |
||||
|
line-height: 1; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.upload-placeholder .hint { |
||||
|
font-size: 22rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.upload-tip { |
||||
|
font-size: 22rpx; |
||||
|
color: #999; |
||||
|
margin-top: 12rpx; |
||||
|
} |
||||
|
|
||||
|
/* 视频选择器 */ |
||||
|
.video-picker { |
||||
|
width: 100%; |
||||
|
min-height: 120rpx; |
||||
|
background: #f8f9fc; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 20rpx 30rpx; |
||||
|
box-sizing: border-box; |
||||
|
border: 2rpx dashed #ddd; |
||||
|
} |
||||
|
|
||||
|
.video-info { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.video-name { |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
|
||||
|
.video-placeholder { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
gap: 16rpx; |
||||
|
height: 80rpx; |
||||
|
} |
||||
|
|
||||
|
.video-placeholder .plus { |
||||
|
font-size: 40rpx; |
||||
|
color: #ccc; |
||||
|
} |
||||
|
|
||||
|
.video-placeholder .hint { |
||||
|
font-size: 26rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 提交按钮 */ |
||||
|
.btn-wrapper { |
||||
|
margin-top: 40rpx; |
||||
|
padding: 0 20rpx; |
||||
|
} |
||||
|
|
||||
|
.submit-btn { |
||||
|
width: 100%; |
||||
|
height: 96rpx; |
||||
|
background: linear-gradient(135deg, #07c160 0%, #08994d 100%); |
||||
|
color: #ffffff; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 48rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
box-shadow: 0 20rpx 40rpx rgba(7, 193, 96, 0.3); |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.submit-btn.disabled { |
||||
|
background: linear-gradient(135deg, #c0c0c0 0%, #a0a0a0 100%); |
||||
|
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
@ -1,66 +0,0 @@ |
|||||
// pagesB/pages/repository/repository.js
|
|
||||
Page({ |
|
||||
|
|
||||
/** |
|
||||
* 页面的初始数据 |
|
||||
*/ |
|
||||
data: { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面加载 |
|
||||
*/ |
|
||||
onLoad(options) { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面初次渲染完成 |
|
||||
*/ |
|
||||
onReady() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面显示 |
|
||||
*/ |
|
||||
onShow() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面隐藏 |
|
||||
*/ |
|
||||
onHide() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 生命周期函数--监听页面卸载 |
|
||||
*/ |
|
||||
onUnload() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 页面相关事件处理函数--监听用户下拉动作 |
|
||||
*/ |
|
||||
onPullDownRefresh() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 页面上拉触底事件的处理函数 |
|
||||
*/ |
|
||||
onReachBottom() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 用户点击右上角分享 |
|
||||
*/ |
|
||||
onShareAppMessage() { |
|
||||
|
|
||||
} |
|
||||
}) |
|
||||
@ -1,3 +0,0 @@ |
|||||
{ |
|
||||
"usingComponents": {} |
|
||||
} |
|
||||
@ -1,2 +0,0 @@ |
|||||
<!--pagesB/pages/repository/repository.wxml--> |
|
||||
<text>pagesB/pages/repository/repository.wxml</text> |
|
||||
@ -1 +0,0 @@ |
|||||
/* pagesB/pages/repository/repository.wxss */ |
|
||||
@ -0,0 +1,381 @@ |
|||||
|
// pages/training/videoDetail/videoDetail.js
|
||||
|
import http from '../../../utils/api' |
||||
|
const baseUrl = require('../../../utils/baseUrl') |
||||
|
|
||||
|
Page({ |
||||
|
data: { |
||||
|
baseUrl: baseUrl, |
||||
|
video: {}, |
||||
|
videoUrl: '', |
||||
|
loading: true, |
||||
|
fullScreen: false, |
||||
|
isPlaying: false, |
||||
|
isMuted: false, |
||||
|
loop: false, |
||||
|
playbackRate: 1, |
||||
|
currentTime: 0, |
||||
|
duration: 0, |
||||
|
videoTags: [], |
||||
|
videoError: false, |
||||
|
autoplay: false, |
||||
|
controlsVisible: true, |
||||
|
controlsTimer: null, |
||||
|
lastTouchTime: 0, |
||||
|
}, |
||||
|
|
||||
|
onLoad(options) { |
||||
|
console.log('页面参数:', options) |
||||
|
this.getVideoDetails(options.id) |
||||
|
}, |
||||
|
|
||||
|
onReady() { |
||||
|
this.videoContext = wx.createVideoContext('videoPlayer', this) |
||||
|
console.log('视频上下文创建成功') |
||||
|
}, |
||||
|
|
||||
|
onUnload() { |
||||
|
// 清除定时器
|
||||
|
if (this.data.controlsTimer) { |
||||
|
clearTimeout(this.data.controlsTimer) |
||||
|
} |
||||
|
// 页面卸载时停止播放
|
||||
|
if (this.videoContext) { |
||||
|
this.videoContext.pause() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 获取视频详情
|
||||
|
getVideoDetails(id) { |
||||
|
this.setData({ |
||||
|
loading: true, |
||||
|
videoError: false |
||||
|
}) |
||||
|
|
||||
|
http.videoDetails({ |
||||
|
data: { id }, |
||||
|
success: res => { |
||||
|
console.log('视频详情响应:', res) |
||||
|
if (res.code === 200 && res.data) { |
||||
|
const video = res.data |
||||
|
console.log('视频数据:', video) |
||||
|
|
||||
|
// 处理视频URL
|
||||
|
let videoUrl = '' |
||||
|
if (video.videoUrl) { |
||||
|
videoUrl = this.data.baseUrl + video.videoUrl |
||||
|
console.log('视频完整URL:', videoUrl) |
||||
|
} |
||||
|
|
||||
|
// 处理视频标签
|
||||
|
const tags = video.tags ? video.tags.split(',').filter(tag => tag.trim()) : [] |
||||
|
|
||||
|
this.setData({ |
||||
|
video: video, |
||||
|
videoUrl: videoUrl, |
||||
|
videoTags: tags, |
||||
|
loading: false |
||||
|
}) |
||||
|
|
||||
|
// 设置页面标题
|
||||
|
wx.setNavigationBarTitle({ |
||||
|
title: video.title.substring(0, 12) + (video.title.length > 12 ? '...' : '') |
||||
|
}) |
||||
|
|
||||
|
} else { |
||||
|
wx.showToast({ |
||||
|
title: res.msg || '视频不存在', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
wx.navigateBack() |
||||
|
}, 2000) |
||||
|
} |
||||
|
}, |
||||
|
fail: err => { |
||||
|
console.error('获取视频详情失败:', err) |
||||
|
this.setData({ |
||||
|
loading: false, |
||||
|
videoError: true |
||||
|
}) |
||||
|
wx.showToast({ |
||||
|
title: '加载失败,请重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 返回上一页
|
||||
|
goBack() { |
||||
|
wx.navigateBack() |
||||
|
}, |
||||
|
|
||||
|
// 视频加载完成
|
||||
|
onVideoLoaded(e) { |
||||
|
console.log('视频元数据加载完成:', e.detail) |
||||
|
this.setData({ |
||||
|
duration: e.detail.duration || 0, |
||||
|
videoError: false |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 视频播放错误
|
||||
|
onVideoError(e) { |
||||
|
console.error('视频播放错误详情:', e.detail) |
||||
|
this.setData({ |
||||
|
videoError: true, |
||||
|
isPlaying: false |
||||
|
}) |
||||
|
|
||||
|
wx.showModal({ |
||||
|
title: '播放错误', |
||||
|
content: '视频加载失败,请检查网络或视频链接', |
||||
|
showCancel: true, |
||||
|
confirmText: '重试', |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
this.retryVideo() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 视频播放事件
|
||||
|
onVideoPlay(e) { |
||||
|
console.log('视频开始播放') |
||||
|
this.setData({ |
||||
|
isPlaying: true, |
||||
|
videoError: false |
||||
|
}) |
||||
|
|
||||
|
// 播放时隐藏控制栏
|
||||
|
if (this.data.fullScreen) { |
||||
|
this.hideControls() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 视频暂停事件
|
||||
|
onVideoPause(e) { |
||||
|
console.log('视频暂停') |
||||
|
this.setData({ |
||||
|
isPlaying: false |
||||
|
}) |
||||
|
|
||||
|
// 暂停时显示控制栏
|
||||
|
if (this.data.fullScreen) { |
||||
|
this.showControls() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 视频播放结束
|
||||
|
onVideoEnded(e) { |
||||
|
console.log('视频播放结束') |
||||
|
this.setData({ |
||||
|
isPlaying: false, |
||||
|
currentTime: 0 |
||||
|
}) |
||||
|
|
||||
|
// 结束播放时显示控制栏
|
||||
|
if (this.data.fullScreen) { |
||||
|
this.showControls() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 时间更新事件
|
||||
|
onTimeUpdate(e) { |
||||
|
const currentTime = e.detail.currentTime |
||||
|
const duration = e.detail.duration |
||||
|
|
||||
|
// 更新当前时间和总时长
|
||||
|
this.setData({ |
||||
|
currentTime: currentTime, |
||||
|
duration: duration > 0 ? duration : this.data.duration |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 全屏切换事件
|
||||
|
onFullScreenChange(e) { |
||||
|
const fullScreen = e.detail.fullScreen |
||||
|
console.log('全屏状态变化:', fullScreen) |
||||
|
|
||||
|
this.setData({ |
||||
|
fullScreen: fullScreen, |
||||
|
controlsVisible: !fullScreen |
||||
|
}) |
||||
|
|
||||
|
if (fullScreen) { |
||||
|
wx.setKeepScreenOn({ keepScreenOn: true }) |
||||
|
// 进入全屏后自动播放
|
||||
|
setTimeout(() => { |
||||
|
this.videoContext.play() |
||||
|
}, 300) |
||||
|
} else { |
||||
|
wx.setKeepScreenOn({ keepScreenOn: false }) |
||||
|
// 退出全屏时暂停
|
||||
|
this.videoContext.pause() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 进入全屏
|
||||
|
enterFullScreen() { |
||||
|
this.videoContext.requestFullScreen({ direction: 90 }) |
||||
|
}, |
||||
|
|
||||
|
// 退出全屏
|
||||
|
exitFullScreen() { |
||||
|
this.videoContext.exitFullScreen() |
||||
|
}, |
||||
|
|
||||
|
// 切换播放状态
|
||||
|
togglePlay() { |
||||
|
console.log('切换播放状态,当前状态:', this.data.isPlaying) |
||||
|
if (this.data.videoError) { |
||||
|
this.retryVideo() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (this.data.isPlaying) { |
||||
|
this.videoContext.pause() |
||||
|
} else { |
||||
|
this.videoContext.play() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 切换静音
|
||||
|
toggleMute() { |
||||
|
const isMuted = !this.data.isMuted |
||||
|
this.setData({ isMuted: isMuted }) |
||||
|
this.videoContext.muted(isMuted) |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: isMuted ? '已静音' : '已取消静音', |
||||
|
icon: 'none', |
||||
|
duration: 800 |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 切换循环
|
||||
|
toggleLoop() { |
||||
|
const loop = !this.data.loop |
||||
|
this.setData({ loop: loop }) |
||||
|
this.videoContext.loop(loop) |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: loop ? '开启循环播放' : '关闭循环播放', |
||||
|
icon: 'none', |
||||
|
duration: 800 |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 切换播放速度
|
||||
|
toggleSpeed() { |
||||
|
const speeds = [0.5, 0.75, 1, 1.25, 1.5, 2] |
||||
|
const currentIndex = speeds.indexOf(this.data.playbackRate) |
||||
|
const nextIndex = (currentIndex + 1) % speeds.length |
||||
|
const nextSpeed = speeds[nextIndex] |
||||
|
|
||||
|
this.setData({ playbackRate: nextSpeed }) |
||||
|
this.videoContext.playbackRate(nextSpeed) |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: `播放速度 ${nextSpeed}x`, |
||||
|
icon: 'none', |
||||
|
duration: 800 |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 重试播放
|
||||
|
retryVideo() { |
||||
|
console.log('重试播放视频') |
||||
|
this.setData({ |
||||
|
videoError: false, |
||||
|
loading: true |
||||
|
}) |
||||
|
|
||||
|
// 重新加载视频
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ loading: false }) |
||||
|
if (this.videoContext) { |
||||
|
this.videoContext.seek(0) |
||||
|
this.videoContext.play() |
||||
|
} |
||||
|
}, 500) |
||||
|
}, |
||||
|
|
||||
|
// 触摸控制
|
||||
|
onTouchControl(e) { |
||||
|
const type = e.currentTarget.dataset.type |
||||
|
const currentTime = this.data.currentTime |
||||
|
const duration = this.data.duration |
||||
|
|
||||
|
if (type === 'backward') { |
||||
|
const newTime = Math.max(0, currentTime - 10) |
||||
|
this.setData({ currentTime: newTime }) |
||||
|
this.videoContext.seek(newTime) |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '-10秒', |
||||
|
icon: 'none', |
||||
|
duration: 500 |
||||
|
}) |
||||
|
} else if (type === 'forward') { |
||||
|
const newTime = Math.min(duration, currentTime + 10) |
||||
|
this.setData({ currentTime: newTime }) |
||||
|
this.videoContext.seek(newTime) |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '+10秒', |
||||
|
icon: 'none', |
||||
|
duration: 500 |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 触摸移动
|
||||
|
onTouchMove() { |
||||
|
if (this.data.fullScreen) { |
||||
|
this.showControls() |
||||
|
this.hideControls() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 显示控制栏
|
||||
|
showControls() { |
||||
|
this.setData({ controlsVisible: true }) |
||||
|
|
||||
|
// 清除之前的定时器
|
||||
|
if (this.data.controlsTimer) { |
||||
|
clearTimeout(this.data.controlsTimer) |
||||
|
} |
||||
|
|
||||
|
// 3秒后自动隐藏控制栏
|
||||
|
const timer = setTimeout(() => { |
||||
|
if (this.data.isPlaying && this.data.fullScreen) { |
||||
|
this.setData({ controlsVisible: false }) |
||||
|
} |
||||
|
}, 3000) |
||||
|
|
||||
|
this.setData({ controlsTimer: timer }) |
||||
|
}, |
||||
|
|
||||
|
// 隐藏控制栏
|
||||
|
hideControls() { |
||||
|
if (this.data.controlsTimer) { |
||||
|
clearTimeout(this.data.controlsTimer) |
||||
|
} |
||||
|
|
||||
|
const timer = setTimeout(() => { |
||||
|
if (this.data.isPlaying && this.data.fullScreen) { |
||||
|
this.setData({ controlsVisible: false }) |
||||
|
} |
||||
|
}, 3000) |
||||
|
|
||||
|
this.setData({ controlsTimer: timer }) |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"视频详情", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,174 @@ |
|||||
|
<view class="video-detail-container"> |
||||
|
<!-- 导航栏 --> |
||||
|
<view class="nav-bar" wx:if="{{!fullScreen}}"> |
||||
|
<view class="nav-title">{{video.title}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频播放器区域 --> |
||||
|
<view class="video-player-section" style="{{fullScreen ? 'height: 100vh;' : ''}}"> |
||||
|
<!-- 视频播放器 --> |
||||
|
<video |
||||
|
id="videoPlayer" |
||||
|
src="{{videoUrl}}" |
||||
|
poster="{{video.coverImage ? baseUrl + video.coverImage : ''}}" |
||||
|
initial-time="0" |
||||
|
autoplay="{{autoplay}}" |
||||
|
loop="{{loop}}" |
||||
|
muted="{{isMuted}}" |
||||
|
controls="{{!fullScreen}}" |
||||
|
show-fullscreen-btn="{{!fullScreen}}" |
||||
|
show-play-btn="true" |
||||
|
show-center-play-btn="true" |
||||
|
enable-progress-gesture="true" |
||||
|
enable-play-gesture="{{fullScreen}}" |
||||
|
object-fit="contain" |
||||
|
direction="0" |
||||
|
bindplay="onVideoPlay" |
||||
|
bindpause="onVideoPause" |
||||
|
bindended="onVideoEnded" |
||||
|
bindtimeupdate="onTimeUpdate" |
||||
|
bindfullscreenchange="onFullScreenChange" |
||||
|
bindloadedmetadata="onVideoLoaded" |
||||
|
binderror="onVideoError" |
||||
|
class="video-player" |
||||
|
></video> |
||||
|
|
||||
|
<!-- 视频封面 --> |
||||
|
<view class="video-cover" wx:if="{{video.coverImage && !isPlaying && !fullScreen}}"> |
||||
|
<image |
||||
|
src="{{baseUrl + video.coverImage}}" |
||||
|
mode="aspectFill" |
||||
|
class="cover-image" |
||||
|
></image> |
||||
|
<view class="cover-play-btn" catchtap="togglePlay"> |
||||
|
<image class="play-icon" src="/pagesB/images/bo.png"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 全屏时的自定义控制栏 --> |
||||
|
<view class="fullscreen-controls" wx:if="{{fullScreen}}" catchtouchmove="onTouchMove"> |
||||
|
<!-- 顶部控制栏 --> |
||||
|
<view class="fullscreen-top-bar"> |
||||
|
<view class="top-bar-left" catchtap="exitFullScreen"> |
||||
|
<image class="back-icon" src="/pagesB/images/left.png"></image> |
||||
|
<text class="back-text">返回</text> |
||||
|
</view> |
||||
|
<view class="top-bar-title">{{video.title}}</view> |
||||
|
<view class="top-bar-right"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 播放按钮(中间) --> |
||||
|
<view class="center-play-btn" wx:if="{{!isPlaying}}" catchtap="togglePlay"> |
||||
|
<image class="play-large-icon" src="/pagesB/images/play.png"></image> |
||||
|
<view class="play-ripple"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 底部控制栏 --> |
||||
|
<view class="fullscreen-bottom-bar"> |
||||
|
<!-- 进度条 --> |
||||
|
<view class="fullscreen-progress"> |
||||
|
<view class="progress-time">{{formatTime(currentTime)}}</view> |
||||
|
<view class="progress-slider-container"> |
||||
|
<view class="progress-bg"> |
||||
|
<view class="progress-current" style="width: {{duration ? (currentTime / duration * 100) + '%' : '0%'}}"></view> |
||||
|
</view> |
||||
|
<view |
||||
|
class="progress-thumb" |
||||
|
style="left: {{duration ? (currentTime / duration * 100) + '%' : '0%'}}" |
||||
|
></view> |
||||
|
</view> |
||||
|
<view class="progress-time">{{formatTime(duration)}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="bottom-controls"> |
||||
|
<view class="control-item" catchtap="togglePlay"> |
||||
|
<image class="control-icon" src="{{isPlaying ? '/pagesB/images/pause.png' : '/pagesB/images/play.png'}}"></image> |
||||
|
</view> |
||||
|
<view class="control-item" catchtap="toggleMute"> |
||||
|
<image class="control-icon" src="{{isMuted ? '/pagesB/images/volume_mute.png' : '/pagesB/images/volume.png'}}"></image> |
||||
|
</view> |
||||
|
<view class="control-item" catchtap="toggleLoop"> |
||||
|
<image class="control-icon" src="{{loop ? '/pagesB/images/loop_on.png' : '/pagesB/images/loop.png'}}"></image> |
||||
|
</view> |
||||
|
<view class="control-item" catchtap="toggleSpeed"> |
||||
|
<text class="speed-text">{{playbackRate}}x</text> |
||||
|
</view> |
||||
|
<view class="control-item" catchtap="enterFullScreen"> |
||||
|
<image class="control-icon" src="/pagesB/images/fullscreen.png"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 触摸控制区域 --> |
||||
|
<view class="touch-control-left" catchtap="onTouchControl" data-type="backward"></view> |
||||
|
<view class="touch-control-center" catchtap="togglePlay"></view> |
||||
|
<view class="touch-control-right" catchtap="onTouchControl" data-type="forward"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 非全屏播放按钮 --> |
||||
|
<view class="normal-play-btn" wx:if="{{!fullScreen && !isPlaying && !video.coverImage}}" catchtap="togglePlay"> |
||||
|
<image class="play-icon" src="/pagesB/images/bo.png"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频信息区域 --> |
||||
|
<scroll-view |
||||
|
class="video-info-section" |
||||
|
scroll-y |
||||
|
wx:if="{{!fullScreen}}" |
||||
|
scroll-with-animation |
||||
|
> |
||||
|
<!-- 视频标题和描述 --> |
||||
|
<view class="video-header"> |
||||
|
<view class="video-title">{{video.title}}</view> |
||||
|
<view class="video-description">{{video.description}}</view> |
||||
|
|
||||
|
<!-- 视频统计数据 --> |
||||
|
<view class="video-stats"> |
||||
|
<view class="stat-item"> |
||||
|
<text>{{video.viewCount}} 播放</text> |
||||
|
</view> |
||||
|
<view class="stat-item"> |
||||
|
<text>{{video.publishTime}}</text> |
||||
|
</view> |
||||
|
<view class="stat-item"> |
||||
|
<text>{{video.category}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 发布者信息 --> |
||||
|
<view class="publisher-section" wx:if="{{video.publisherName}}"> |
||||
|
<image class="publisher-avatar" src="{{video.publisherAvatar ? baseUrl + video.publisherAvatar : '/pagesB/images/default_avatar.png'}}"></image> |
||||
|
<view class="publisher-info"> |
||||
|
<view class="publisher-name">{{video.publisherName}}</view> |
||||
|
<view class="publisher-desc">{{video.publisherDesc || '视频发布者'}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频标签 --> |
||||
|
<view class="tags-section" wx:if="{{videoTags.length > 0}}"> |
||||
|
<view class="section-title">标签</view> |
||||
|
<view class="tags-container"> |
||||
|
<view class="tag-item" wx:for="{{videoTags}}" wx:key="index"> |
||||
|
{{item}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
|
||||
|
<!-- 加载中 --> |
||||
|
<view class="loading-container" wx:if="{{loading}}"> |
||||
|
<view class="loading-spinner"> |
||||
|
<view class="spinner-circle"></view> |
||||
|
</view> |
||||
|
<text class="loading-text">加载中...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 错误提示 --> |
||||
|
<view class="error-container" wx:if="{{videoError}}"> |
||||
|
<image class="error-icon" src="/pagesB/images/error.png"></image> |
||||
|
<text class="error-text">视频加载失败</text> |
||||
|
<view class="retry-btn" catchtap="retryVideo">重试</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,664 @@ |
|||||
|
.video-detail-container { |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #0f172a 0%, #1a1e2c 100%); |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
/* 导航栏 */ |
||||
|
.nav-bar { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
height: 88rpx; |
||||
|
padding: 0 30rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
background: linear-gradient(180deg, rgba(15, 23, 42, 0.98) 0%, rgba(15, 23, 42, 0.9) 100%); |
||||
|
backdrop-filter: blur(20rpx); |
||||
|
z-index: 1000; |
||||
|
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08); |
||||
|
} |
||||
|
|
||||
|
.nav-title { |
||||
|
font-size: 34rpx; |
||||
|
font-weight: 700; |
||||
|
color: #fff; |
||||
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
/* 视频播放器区域 */ |
||||
|
.video-player-section { |
||||
|
width: 100%; |
||||
|
height: 500rpx; |
||||
|
position: relative; |
||||
|
background: #000; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.video-player { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: #000; |
||||
|
} |
||||
|
|
||||
|
/* 视频封面 */ |
||||
|
.video-cover { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: #000; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.cover-image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
opacity: 0.9; |
||||
|
} |
||||
|
|
||||
|
.cover-play-btn { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
background: rgba(0, 0, 0, 0.6); |
||||
|
backdrop-filter: blur(20rpx); |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
border: 3rpx solid rgba(255, 255, 255, 0.3); |
||||
|
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.5); |
||||
|
} |
||||
|
|
||||
|
.cover-play-btn .play-icon { |
||||
|
width: 50rpx; |
||||
|
height: 50rpx; |
||||
|
margin-left: 8rpx; |
||||
|
filter: brightness(2); |
||||
|
} |
||||
|
|
||||
|
/* 全屏控制栏 */ |
||||
|
.fullscreen-controls { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 10; |
||||
|
opacity: 1; |
||||
|
transition: opacity 0.3s; |
||||
|
} |
||||
|
|
||||
|
.fullscreen-controls.hidden { |
||||
|
opacity: 0; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.fullscreen-top-bar { |
||||
|
padding: 80rpx 40rpx 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0) 100%); |
||||
|
height: 120rpx; |
||||
|
} |
||||
|
|
||||
|
.top-bar-left { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 16rpx; |
||||
|
padding: 16rpx 28rpx; |
||||
|
background: rgba(0, 0, 0, 0.6); |
||||
|
border-radius: 40rpx; |
||||
|
backdrop-filter: blur(20rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.1); |
||||
|
} |
||||
|
|
||||
|
.back-text { |
||||
|
color: #fff; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 500; |
||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.top-bar-title { |
||||
|
font-size: 32rpx; |
||||
|
color: #fff; |
||||
|
max-width: 400rpx; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
white-space: nowrap; |
||||
|
font-weight: 600; |
||||
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.5); |
||||
|
padding: 12rpx 24rpx; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
border-radius: 20rpx; |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
} |
||||
|
|
||||
|
/* 中间播放按钮 */ |
||||
|
.center-play-btn { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
background: rgba(0, 0, 0, 0.7); |
||||
|
backdrop-filter: blur(30rpx); |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
border: 4rpx solid rgba(255, 255, 255, 0.3); |
||||
|
z-index: 20; |
||||
|
box-shadow: |
||||
|
0 12rpx 60rpx rgba(0, 0, 0, 0.5), |
||||
|
inset 0 0 0 1rpx rgba(255, 255, 255, 0.1); |
||||
|
animation: pulse 2s infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes pulse { |
||||
|
0% { |
||||
|
box-shadow: |
||||
|
0 12rpx 60rpx rgba(0, 0, 0, 0.5), |
||||
|
inset 0 0 0 1rpx rgba(255, 255, 255, 0.1); |
||||
|
} |
||||
|
50% { |
||||
|
box-shadow: |
||||
|
0 12rpx 80rpx rgba(52, 152, 219, 0.4), |
||||
|
inset 0 0 0 1rpx rgba(52, 152, 219, 0.3); |
||||
|
} |
||||
|
100% { |
||||
|
box-shadow: |
||||
|
0 12rpx 60rpx rgba(0, 0, 0, 0.5), |
||||
|
inset 0 0 0 1rpx rgba(255, 255, 255, 0.1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.play-large-icon { |
||||
|
width: 70rpx; |
||||
|
height: 70rpx; |
||||
|
margin-left: 10rpx; |
||||
|
filter: brightness(2) drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.5)); |
||||
|
} |
||||
|
|
||||
|
.play-ripple { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 0; |
||||
|
height: 0; |
||||
|
border-radius: 50%; |
||||
|
border: 2rpx solid rgba(255, 255, 255, 0.4); |
||||
|
animation: ripple 2s infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes ripple { |
||||
|
0% { |
||||
|
width: 0; |
||||
|
height: 0; |
||||
|
opacity: 1; |
||||
|
} |
||||
|
100% { |
||||
|
width: 240rpx; |
||||
|
height: 240rpx; |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 底部控制栏 */ |
||||
|
.fullscreen-bottom-bar { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
padding: 0 40rpx 80rpx; |
||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.95) 0%, rgba(0, 0, 0, 0) 100%); |
||||
|
} |
||||
|
|
||||
|
.fullscreen-progress { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 24rpx; |
||||
|
margin-bottom: 60rpx; |
||||
|
padding: 0 20rpx; |
||||
|
} |
||||
|
|
||||
|
.progress-time { |
||||
|
font-size: 26rpx; |
||||
|
color: rgba(255, 255, 255, 0.95); |
||||
|
min-width: 90rpx; |
||||
|
text-align: center; |
||||
|
font-weight: 500; |
||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5); |
||||
|
} |
||||
|
|
||||
|
.progress-slider-container { |
||||
|
flex: 1; |
||||
|
position: relative; |
||||
|
height: 8rpx; |
||||
|
background: rgba(255, 255, 255, 0.2); |
||||
|
border-radius: 4rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.progress-bg { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: rgba(255, 255, 255, 0.1); |
||||
|
border-radius: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.progress-current { |
||||
|
height: 100%; |
||||
|
background: linear-gradient(90deg, #3498db, #9b59b6); |
||||
|
border-radius: 4rpx; |
||||
|
transition: width 0.1s; |
||||
|
} |
||||
|
|
||||
|
.progress-thumb { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 28rpx; |
||||
|
height: 28rpx; |
||||
|
background: #fff; |
||||
|
border-radius: 50%; |
||||
|
box-shadow: |
||||
|
0 4rpx 12rpx rgba(0, 0, 0, 0.5), |
||||
|
0 0 0 2rpx #3498db; |
||||
|
} |
||||
|
|
||||
|
.bottom-controls { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
gap: 80rpx; |
||||
|
padding: 30rpx 50rpx; |
||||
|
background: rgba(0, 0, 0, 0.7); |
||||
|
border-radius: 80rpx; |
||||
|
backdrop-filter: blur(30rpx); |
||||
|
margin: 0 auto; |
||||
|
max-width: 700rpx; |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.1); |
||||
|
} |
||||
|
|
||||
|
.control-item { |
||||
|
width: 90rpx; |
||||
|
height: 90rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
border-radius: 50%; |
||||
|
background: rgba(255, 255, 255, 0.1); |
||||
|
transition: all 0.2s; |
||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.control-item:active { |
||||
|
background: rgba(255, 255, 255, 0.2); |
||||
|
transform: scale(0.9); |
||||
|
} |
||||
|
|
||||
|
.control-icon { |
||||
|
width: 44rpx; |
||||
|
height: 44rpx; |
||||
|
filter: brightness(2) drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3)); |
||||
|
} |
||||
|
|
||||
|
.speed-text { |
||||
|
color: #fff; |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 700; |
||||
|
background: linear-gradient(135deg, #3498db, #9b59b6); |
||||
|
padding: 12rpx 28rpx; |
||||
|
border-radius: 40rpx; |
||||
|
box-shadow: 0 6rpx 24rpx rgba(52, 152, 219, 0.4); |
||||
|
} |
||||
|
|
||||
|
/* 触摸控制区域 */ |
||||
|
.touch-control-left, |
||||
|
.touch-control-center, |
||||
|
.touch-control-right { |
||||
|
position: absolute; |
||||
|
top: 120rpx; |
||||
|
bottom: 200rpx; |
||||
|
z-index: 5; |
||||
|
} |
||||
|
|
||||
|
.touch-control-left { |
||||
|
left: 0; |
||||
|
width: 30%; |
||||
|
} |
||||
|
|
||||
|
.touch-control-center { |
||||
|
left: 30%; |
||||
|
width: 40%; |
||||
|
} |
||||
|
|
||||
|
.touch-control-right { |
||||
|
left: 70%; |
||||
|
width: 30%; |
||||
|
} |
||||
|
|
||||
|
/* 非全屏播放按钮 */ |
||||
|
.normal-play-btn { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
background: rgba(0, 0, 0, 0.7); |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 3; |
||||
|
border: 3rpx solid rgba(255, 255, 255, 0.3); |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.5); |
||||
|
} |
||||
|
|
||||
|
.normal-play-btn .play-icon { |
||||
|
width: 50rpx; |
||||
|
height: 50rpx; |
||||
|
margin-left: 8rpx; |
||||
|
filter: brightness(2); |
||||
|
} |
||||
|
|
||||
|
/* 视频信息区域 */ |
||||
|
.video-info-section { |
||||
|
height: calc(100vh - 500rpx); |
||||
|
background: linear-gradient(180deg, #1a1e2c 0%, #0f172a 100%); |
||||
|
} |
||||
|
|
||||
|
.video-header { |
||||
|
padding: 50rpx 40rpx 40rpx; |
||||
|
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08); |
||||
|
} |
||||
|
|
||||
|
.video-title { |
||||
|
font-size: 40rpx; |
||||
|
font-weight: 800; |
||||
|
color: #fff; |
||||
|
line-height: 1.3; |
||||
|
margin-bottom: 30rpx; |
||||
|
letter-spacing: 0.5rpx; |
||||
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.video-description { |
||||
|
font-size: 30rpx; |
||||
|
color: rgba(255, 255, 255, 0.85); |
||||
|
line-height: 1.6; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.video-stats { |
||||
|
display: flex; |
||||
|
gap: 30rpx; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
|
||||
|
.stat-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 14rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: rgba(255, 255, 255, 0.7); |
||||
|
padding: 14rpx 28rpx; |
||||
|
background: rgba(255, 255, 255, 0.08); |
||||
|
border-radius: 40rpx; |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
/* 发布者信息 */ |
||||
|
.publisher-section { |
||||
|
padding: 40rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 30rpx; |
||||
|
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08); |
||||
|
background: rgba(255, 255, 255, 0.03); |
||||
|
margin: 0 20rpx; |
||||
|
border-radius: 24rpx; |
||||
|
margin-top: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.publisher-avatar { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
border-radius: 50%; |
||||
|
border: 4rpx solid #3498db; |
||||
|
box-shadow: |
||||
|
0 12rpx 40rpx rgba(52, 152, 219, 0.5), |
||||
|
0 0 0 1rpx rgba(255, 255, 255, 0.1); |
||||
|
background: #1e293b; |
||||
|
} |
||||
|
|
||||
|
.publisher-info { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.publisher-name { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
color: #fff; |
||||
|
margin-bottom: 12rpx; |
||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.publisher-desc { |
||||
|
font-size: 26rpx; |
||||
|
color: rgba(255, 255, 255, 0.6); |
||||
|
line-height: 1.4; |
||||
|
} |
||||
|
|
||||
|
/* 标签区域 */ |
||||
|
.tags-section { |
||||
|
padding: 40rpx; |
||||
|
background: rgba(255, 255, 255, 0.03); |
||||
|
margin: 20rpx; |
||||
|
border-radius: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 700; |
||||
|
color: #fff; |
||||
|
margin-bottom: 30rpx; |
||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.tags-container { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.tag-item { |
||||
|
padding: 16rpx 36rpx; |
||||
|
background: linear-gradient(135deg, #3498db, #2ecc71); |
||||
|
color: white; |
||||
|
border-radius: 40rpx; |
||||
|
font-size: 26rpx; |
||||
|
font-weight: 600; |
||||
|
box-shadow: 0 6rpx 24rpx rgba(52, 152, 219, 0.4); |
||||
|
transition: all 0.2s; |
||||
|
} |
||||
|
|
||||
|
.tag-item:active { |
||||
|
transform: scale(0.95); |
||||
|
box-shadow: 0 4rpx 16rpx rgba(52, 152, 219, 0.3); |
||||
|
} |
||||
|
|
||||
|
/* 加载中 */ |
||||
|
.loading-container { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: linear-gradient(135deg, #0f172a 0%, #1a1e2c 100%); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2000; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
position: relative; |
||||
|
width: 140rpx; |
||||
|
height: 140rpx; |
||||
|
margin-bottom: 50rpx; |
||||
|
} |
||||
|
|
||||
|
.spinner-circle { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
border: 10rpx solid transparent; |
||||
|
border-top-color: #3498db; |
||||
|
border-right-color: #3498db; |
||||
|
border-radius: 50%; |
||||
|
animation: spinnerRotate 1.2s linear infinite; |
||||
|
box-shadow: 0 0 20rpx rgba(52, 152, 219, 0.3); |
||||
|
} |
||||
|
|
||||
|
@keyframes spinnerRotate { |
||||
|
0% { |
||||
|
transform: translate(-50%, -50%) rotate(0deg); |
||||
|
} |
||||
|
100% { |
||||
|
transform: translate(-50%, -50%) rotate(360deg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
font-size: 32rpx; |
||||
|
color: rgba(255, 255, 255, 0.8); |
||||
|
letter-spacing: 3rpx; |
||||
|
font-weight: 500; |
||||
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
/* 错误提示 */ |
||||
|
.error-container { |
||||
|
position: fixed; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2000; |
||||
|
background: linear-gradient(135deg, rgba(15, 23, 42, 0.95), rgba(26, 30, 44, 0.95)); |
||||
|
padding: 80rpx 60rpx; |
||||
|
border-radius: 40rpx; |
||||
|
backdrop-filter: blur(40rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.1); |
||||
|
min-width: 500rpx; |
||||
|
box-shadow: |
||||
|
0 20rpx 80rpx rgba(0, 0, 0, 0.5), |
||||
|
inset 0 1rpx 0 rgba(255, 255, 255, 0.1); |
||||
|
} |
||||
|
|
||||
|
.error-icon { |
||||
|
width: 140rpx; |
||||
|
height: 140rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
filter: brightness(1.5) drop-shadow(0 4rpx 12rpx rgba(0, 0, 0, 0.3)); |
||||
|
} |
||||
|
|
||||
|
.error-text { |
||||
|
font-size: 36rpx; |
||||
|
color: #fff; |
||||
|
margin-bottom: 50rpx; |
||||
|
font-weight: 700; |
||||
|
text-align: center; |
||||
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.retry-btn { |
||||
|
padding: 24rpx 80rpx; |
||||
|
background: linear-gradient(135deg, #3498db, #2980b9); |
||||
|
color: white; |
||||
|
border-radius: 50rpx; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 700; |
||||
|
box-shadow: |
||||
|
0 12rpx 40rpx rgba(52, 152, 219, 0.5), |
||||
|
inset 0 1rpx 0 rgba(255, 255, 255, 0.3); |
||||
|
transition: all 0.2s; |
||||
|
letter-spacing: 2rpx; |
||||
|
} |
||||
|
|
||||
|
.retry-btn:active { |
||||
|
transform: scale(0.95); |
||||
|
box-shadow: |
||||
|
0 6rpx 24rpx rgba(52, 152, 219, 0.4), |
||||
|
inset 0 1rpx 0 rgba(255, 255, 255, 0.2); |
||||
|
} |
||||
|
|
||||
|
/* 响应式调整 */ |
||||
|
@media screen and (max-width: 750rpx) { |
||||
|
.video-player-section { |
||||
|
height: 450rpx; |
||||
|
} |
||||
|
|
||||
|
.video-info-section { |
||||
|
height: calc(100vh - 450rpx); |
||||
|
} |
||||
|
|
||||
|
.video-title { |
||||
|
font-size: 36rpx; |
||||
|
} |
||||
|
|
||||
|
.video-description { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.bottom-controls { |
||||
|
gap: 60rpx; |
||||
|
padding: 25rpx 40rpx; |
||||
|
max-width: 650rpx; |
||||
|
} |
||||
|
|
||||
|
.center-play-btn { |
||||
|
width: 140rpx; |
||||
|
height: 140rpx; |
||||
|
} |
||||
|
|
||||
|
.play-large-icon { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.fullscreen-top-bar { |
||||
|
padding: 60rpx 30rpx 0; |
||||
|
} |
||||
|
|
||||
|
.fullscreen-bottom-bar { |
||||
|
padding: 0 30rpx 60rpx; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,402 @@ |
|||||
|
import http from '../../../utils/api' |
||||
|
const baseUrl = require('../../../utils/baseUrl') |
||||
|
|
||||
|
Page({ |
||||
|
data: { |
||||
|
currentTab: 0, |
||||
|
searchKeyword: '', |
||||
|
articleActiveCategory: 0, |
||||
|
videoActiveCategory: '全部', |
||||
|
baseUrl: baseUrl, |
||||
|
|
||||
|
// 文章相关
|
||||
|
allArticles: [], |
||||
|
filteredArticles: [], |
||||
|
articlePageNo: 1, |
||||
|
articlePageSize: 10, |
||||
|
articleTotal: 0, |
||||
|
articleHasMore: true, |
||||
|
articleLoading: false, |
||||
|
articleRequested: false, // 标记文章数据是否已请求
|
||||
|
|
||||
|
// 视频相关
|
||||
|
allVideos: [], |
||||
|
filteredVideos: [], |
||||
|
videoPageNo: 1, |
||||
|
videoPageSize: 10, |
||||
|
videoTotal: 0, |
||||
|
videoHasMore: true, |
||||
|
videoLoading: false, |
||||
|
videoRequested: false, // 标记视频数据是否已请求
|
||||
|
|
||||
|
// 字典数据
|
||||
|
wzzd: [], |
||||
|
videoType: [] |
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
this.getarticleZd() |
||||
|
this.getvideoZd() |
||||
|
this.loadInitialData() |
||||
|
}, |
||||
|
|
||||
|
// 加载初始数据
|
||||
|
loadInitialData() { |
||||
|
if (this.data.currentTab === 0) { |
||||
|
this.resetArticleParams() |
||||
|
this.getArticleList(true) |
||||
|
} else { |
||||
|
this.resetVideoParams() |
||||
|
this.getVideoList(true) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 文章字典
|
||||
|
getarticleZd() { |
||||
|
http.articleZd({ |
||||
|
data: { |
||||
|
dictType: 'article_category' |
||||
|
}, |
||||
|
success: res => { |
||||
|
this.setData({ |
||||
|
wzzd: res.rows |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 视频字典
|
||||
|
getvideoZd() { |
||||
|
http.videoZd({ |
||||
|
data: { |
||||
|
dictType: 'video_category' |
||||
|
}, |
||||
|
success: res => { |
||||
|
this.setData({ |
||||
|
videoType: res.rows |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 获取文章列表(支持搜索和分类)
|
||||
|
getArticleList(isRefresh = false) { |
||||
|
if (this.data.articleLoading && !isRefresh) return |
||||
|
|
||||
|
this.setData({ |
||||
|
articleLoading: true |
||||
|
}) |
||||
|
|
||||
|
const params = { |
||||
|
pageNo: this.data.articlePageNo, |
||||
|
pageSize: this.data.articlePageSize |
||||
|
} |
||||
|
|
||||
|
// 添加搜索关键词
|
||||
|
if (this.data.searchKeyword && this.data.searchKeyword.trim()) { |
||||
|
params.searchKey = this.data.searchKeyword.trim() |
||||
|
} |
||||
|
|
||||
|
// 添加分类筛选
|
||||
|
if (this.data.articleActiveCategory !== 0) { |
||||
|
const categoryDict = this.data.wzzd.find(item => item.dictSort === this.data.articleActiveCategory) |
||||
|
if (categoryDict) { |
||||
|
params.category = categoryDict.dictLabel |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
http.article({ |
||||
|
data: params, |
||||
|
success: res => { |
||||
|
const newArticles = res.rows || [] |
||||
|
const total = res.total || 0 |
||||
|
|
||||
|
let allArticles, filteredArticles |
||||
|
if (isRefresh) { |
||||
|
allArticles = newArticles |
||||
|
filteredArticles = newArticles |
||||
|
} else { |
||||
|
allArticles = [...this.data.allArticles, ...newArticles] |
||||
|
filteredArticles = [...this.data.filteredArticles, ...newArticles] |
||||
|
} |
||||
|
|
||||
|
const hasMore = this.data.articlePageNo * this.data.articlePageSize < total |
||||
|
|
||||
|
this.setData({ |
||||
|
allArticles, |
||||
|
filteredArticles, |
||||
|
articleTotal: total, |
||||
|
articleHasMore: hasMore, |
||||
|
articleLoading: false, |
||||
|
articleRequested: true |
||||
|
}) |
||||
|
|
||||
|
// 如果是下拉刷新,停止刷新动画
|
||||
|
if (isRefresh) { |
||||
|
wx.stopPullDownRefresh() |
||||
|
} |
||||
|
}, |
||||
|
fail: () => { |
||||
|
this.setData({ |
||||
|
articleLoading: false |
||||
|
}) |
||||
|
wx.showToast({ |
||||
|
title: '加载失败', |
||||
|
icon: 'error', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 获取视频列表(支持搜索和分类)
|
||||
|
getVideoList(isRefresh = false) { |
||||
|
if (this.data.videoLoading && !isRefresh) return |
||||
|
|
||||
|
this.setData({ |
||||
|
videoLoading: true |
||||
|
}) |
||||
|
|
||||
|
const params = { |
||||
|
pageNo: this.data.videoPageNo, |
||||
|
pageSize: this.data.videoPageSize |
||||
|
} |
||||
|
|
||||
|
// 添加搜索关键词
|
||||
|
if (this.data.searchKeyword && this.data.searchKeyword.trim()) { |
||||
|
params.searchKey = this.data.searchKeyword.trim() |
||||
|
} |
||||
|
|
||||
|
// 添加分类筛选
|
||||
|
if (this.data.videoActiveCategory !== '全部') { |
||||
|
params.category = this.data.videoActiveCategory |
||||
|
} |
||||
|
|
||||
|
http.videoList({ |
||||
|
data: params, |
||||
|
success: res => { |
||||
|
const newVideos = res.rows || [] |
||||
|
const total = res.total || 0 |
||||
|
|
||||
|
let allVideos, filteredVideos |
||||
|
if (isRefresh) { |
||||
|
allVideos = newVideos |
||||
|
filteredVideos = newVideos |
||||
|
} else { |
||||
|
allVideos = [...this.data.allVideos, ...newVideos] |
||||
|
filteredVideos = [...this.data.filteredVideos, ...newVideos] |
||||
|
} |
||||
|
|
||||
|
const hasMore = this.data.videoPageNo * this.data.videoPageSize < total |
||||
|
|
||||
|
this.setData({ |
||||
|
allVideos, |
||||
|
filteredVideos, |
||||
|
videoTotal: total, |
||||
|
videoHasMore: hasMore, |
||||
|
videoLoading: false, |
||||
|
videoRequested: true |
||||
|
}) |
||||
|
|
||||
|
// 如果是下拉刷新,停止刷新动画
|
||||
|
if (isRefresh) { |
||||
|
wx.stopPullDownRefresh() |
||||
|
} |
||||
|
}, |
||||
|
fail: () => { |
||||
|
this.setData({ |
||||
|
videoLoading: false |
||||
|
}) |
||||
|
wx.showToast({ |
||||
|
title: '加载失败', |
||||
|
icon: 'error', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 重置文章参数
|
||||
|
resetArticleParams() { |
||||
|
this.setData({ |
||||
|
articlePageNo: 1, |
||||
|
articleTotal: 0, |
||||
|
articleHasMore: true, |
||||
|
allArticles: [], |
||||
|
filteredArticles: [] |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 重置视频参数
|
||||
|
resetVideoParams() { |
||||
|
this.setData({ |
||||
|
videoPageNo: 1, |
||||
|
videoTotal: 0, |
||||
|
videoHasMore: true, |
||||
|
allVideos: [], |
||||
|
filteredVideos: [] |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 切换主选项卡
|
||||
|
switchTab(e) { |
||||
|
const tab = parseInt(e.currentTarget.dataset.tab) |
||||
|
if (tab === this.data.currentTab) return |
||||
|
|
||||
|
this.setData({ |
||||
|
currentTab: tab, |
||||
|
searchKeyword: '' |
||||
|
}) |
||||
|
|
||||
|
// 延迟加载新tab的数据,确保切换动画完成
|
||||
|
setTimeout(() => { |
||||
|
if (tab === 0) { |
||||
|
if (!this.data.articleRequested) { |
||||
|
this.resetArticleParams() |
||||
|
this.getArticleList(true) |
||||
|
} |
||||
|
} else { |
||||
|
if (!this.data.videoRequested) { |
||||
|
this.resetVideoParams() |
||||
|
this.getVideoList(true) |
||||
|
} |
||||
|
} |
||||
|
}, 300) |
||||
|
}, |
||||
|
|
||||
|
// 搜索输入
|
||||
|
onSearchInput(e) { |
||||
|
const keyword = e.detail.value |
||||
|
this.setData({ |
||||
|
searchKeyword: keyword |
||||
|
}) |
||||
|
|
||||
|
// 防抖处理,500ms后执行搜索
|
||||
|
clearTimeout(this.searchTimer) |
||||
|
this.searchTimer = setTimeout(() => { |
||||
|
this.performSearch() |
||||
|
}, 500) |
||||
|
}, |
||||
|
|
||||
|
// 执行搜索
|
||||
|
performSearch() { |
||||
|
if (this.data.currentTab === 0) { |
||||
|
this.resetArticleParams() |
||||
|
this.getArticleList(true) |
||||
|
} else { |
||||
|
this.resetVideoParams() |
||||
|
this.getVideoList(true) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 选择文章分类
|
||||
|
selectArticleCategory(e) { |
||||
|
const category = Number(e.currentTarget.dataset.category) |
||||
|
if (category === this.data.articleActiveCategory) return |
||||
|
|
||||
|
this.setData({ |
||||
|
articleActiveCategory: category |
||||
|
}) |
||||
|
|
||||
|
// 重置并重新加载数据
|
||||
|
this.resetArticleParams() |
||||
|
this.getArticleList(true) |
||||
|
}, |
||||
|
|
||||
|
// 选择视频分类
|
||||
|
selectVideoCategory(e) { |
||||
|
const category = e.currentTarget.dataset.category |
||||
|
if (category === this.data.videoActiveCategory) return |
||||
|
|
||||
|
this.setData({ |
||||
|
videoActiveCategory: category |
||||
|
}) |
||||
|
|
||||
|
// 重置并重新加载数据
|
||||
|
this.resetVideoParams() |
||||
|
this.getVideoList(true) |
||||
|
}, |
||||
|
|
||||
|
// 查看文章详情
|
||||
|
viewArticleDetail(e) { |
||||
|
const id = e.currentTarget.dataset.id |
||||
|
console.log('查看文章详情', id) |
||||
|
wx.navigateTo({ |
||||
|
url: `/pagesB/pages/wzDetails/wzDetails?id=${id}` |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 播放视频
|
||||
|
playVideo(e) { |
||||
|
const id = e.currentTarget.dataset.id |
||||
|
console.log('播放视频', id) |
||||
|
wx.navigateTo({ |
||||
|
url: `/pagesB/pages/spDetails/spDetails?id=${id}` |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
// 跳转发布页
|
||||
|
bindPublish(){ |
||||
|
wx.navigateTo({ |
||||
|
url: '/pagesB/pages/publishAdd/publishAdd', |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
// 下拉刷新
|
||||
|
onPullDownRefresh() { |
||||
|
wx.showNavigationBarLoading() |
||||
|
|
||||
|
if (this.data.currentTab === 0) { |
||||
|
this.resetArticleParams() |
||||
|
this.getArticleList(true) |
||||
|
} else { |
||||
|
this.resetVideoParams() |
||||
|
this.getVideoList(true) |
||||
|
} |
||||
|
|
||||
|
// 重置搜索关键词
|
||||
|
this.setData({ |
||||
|
searchKeyword: '' |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 上拉加载更多
|
||||
|
onReachBottom() { |
||||
|
if (this.data.currentTab === 0) { |
||||
|
if (!this.data.articleHasMore || this.data.articleLoading) { |
||||
|
wx.showToast({ |
||||
|
title: '已加载全部', |
||||
|
icon: 'none', |
||||
|
duration: 1000 |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
articlePageNo: this.data.articlePageNo + 1 |
||||
|
}) |
||||
|
this.getArticleList(false) |
||||
|
} else { |
||||
|
if (!this.data.videoHasMore || this.data.videoLoading) { |
||||
|
wx.showToast({ |
||||
|
title: '已加载全部', |
||||
|
icon: 'none', |
||||
|
duration: 1000 |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
videoPageNo: this.data.videoPageNo + 1 |
||||
|
}) |
||||
|
this.getVideoList(false) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 页面显示时刷新数据
|
||||
|
onShow() { |
||||
|
// 如果需要返回时刷新数据,可以在这里调用
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"在线培训", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,215 @@ |
|||||
|
<!-- pages/training/training.wxml --> |
||||
|
<view class="training-container"> |
||||
|
<!-- 顶部标题 --> |
||||
|
<view class="header"> |
||||
|
<view class="title">在线培训</view> |
||||
|
<view class="subtitle">专业学习资源,助您快速成长</view> |
||||
|
<view class="header-decoration"> |
||||
|
<view class="decoration-circle circle-1"></view> |
||||
|
<view class="decoration-circle circle-2"></view> |
||||
|
<view class="decoration-circle circle-3"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
|
||||
|
<!-- 主选项卡切换 --> |
||||
|
<view class="tabs"> |
||||
|
<view class="tab-item {{currentTab === 0 ? 'active' : ''}}" bindtap="switchTab" data-tab="0"> |
||||
|
<text>文章</text> |
||||
|
<view class="tab-highlight {{currentTab === 0 ? 'active' : ''}}"></view> |
||||
|
</view> |
||||
|
<view class="tab-item {{currentTab === 1 ? 'active' : ''}}" bindtap="switchTab" data-tab="1"> |
||||
|
<text>视频</text> |
||||
|
<view class="tab-highlight {{currentTab === 1 ? 'active' : ''}}"></view> |
||||
|
</view> |
||||
|
<view class="tab-slider {{currentTab === 0 ? 'left' : 'right'}}"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章板块 --> |
||||
|
<view class="content-section" wx:if="{{currentTab === 0}}"> |
||||
|
<!-- 搜索框 --> |
||||
|
<view class="search-box"> |
||||
|
<image class="search-icon" src="/pagesB/images/sou.png"></image> |
||||
|
<input class="search-input" placeholder="搜索文章..." placeholder-class="placeholder-style" bindinput="onSearchInput" value="{{searchKeyword}}" /> |
||||
|
<view class="search-decoration"> |
||||
|
<view class="search-wave"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章分类筛选 --> |
||||
|
<scroll-view class="category-scroll" scroll-x scroll-with-animation> |
||||
|
<view class="category-list"> |
||||
|
<view class="category-item {{articleActiveCategory === 0 ? 'active' : ''}}" bindtap="selectArticleCategory" data-category="0"> |
||||
|
<text>全部</text> |
||||
|
<view class="category-indicator"></view> |
||||
|
</view> |
||||
|
<view class="category-item {{item.dictSort === articleActiveCategory? 'active' : ''}}" bindtap="selectArticleCategory" data-category="{{item.dictSort}}" wx:for="{{wzzd}}" wx:key="index"> |
||||
|
<text>{{item.dictLabel}}</text> |
||||
|
<view class="category-indicator"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
|
||||
|
<!-- 文章列表 --> |
||||
|
<view class="articles-list"> |
||||
|
<view class="article-card" wx:for="{{filteredArticles}}" wx:key="id" bindtap="viewArticleDetail" data-id="{{item.id}}"> |
||||
|
<!-- 封面图片 --> |
||||
|
<view class="article-cover-container"> |
||||
|
<image class="article-cover" src="{{baseUrl+item.coverImage}}" mode="aspectFill"></image> |
||||
|
<view class="cover-overlay"></view> |
||||
|
<view class="category-tag">{{item.category}}</view> |
||||
|
<view class="article-hover-effect"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章信息 --> |
||||
|
<view class="article-info"> |
||||
|
<view class="article-title">{{item.title}}</view> |
||||
|
<view class="article-desc">{{item.subtitle}}</view> |
||||
|
|
||||
|
<view class="article-meta"> |
||||
|
<view class="meta-item"> |
||||
|
<image class="meta-icon" src="{{baseUrl+item.expertAvatar}}"></image> |
||||
|
<text class="meta-text">{{item.expertName}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="meta-item"> |
||||
|
<text class="meta-text">{{item.publishTime}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="article-stats"> |
||||
|
<view class="stat-item"> |
||||
|
<image class="stat-icon" src="/pagesB/images/lll.png"></image> |
||||
|
<text>{{item.viewCount}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 加载更多状态 --> |
||||
|
<view wx:if="{{currentTab === 0 && filteredArticles.length > 0}}"> |
||||
|
<view class="load-more" wx:if="{{articleHasMore && !articleLoading}}"> |
||||
|
<text>上拉加载更多</text> |
||||
|
</view> |
||||
|
<view class="loading-more" wx:if="{{articleLoading}}"> |
||||
|
<view class="loading-dot"></view> |
||||
|
<view class="loading-dot"></view> |
||||
|
<view class="loading-dot"></view> |
||||
|
<text>加载中...</text> |
||||
|
</view> |
||||
|
<view class="no-more" wx:if="{{!articleHasMore && filteredArticles.length > 0}}"> |
||||
|
<text>已加载全部</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 空状态 --> |
||||
|
<view class="empty-state" wx:if="{{filteredArticles.length === 0}}"> |
||||
|
<view class="empty-animation"> |
||||
|
<view class="empty-circle"></view> |
||||
|
<view class="empty-circle circle-2"></view> |
||||
|
<view class="empty-circle circle-3"></view> |
||||
|
</view> |
||||
|
<text class="empty-text">暂无相关文章</text> |
||||
|
<text class="empty-hint">换个关键词试试吧</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频板块 --> |
||||
|
<view class="content-section" wx:if="{{currentTab === 1}}"> |
||||
|
<!-- 搜索框 --> |
||||
|
<view class="search-box"> |
||||
|
<image class="search-icon" src="/pagesB/images/sou.png"></image> |
||||
|
<input class="search-input" placeholder="搜索视频..." placeholder-class="placeholder-style" bindinput="onSearchInput" value="{{searchKeyword}}" /> |
||||
|
<view class="search-decoration"> |
||||
|
<view class="search-wave"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频分类筛选 --> |
||||
|
<scroll-view class="category-scroll" scroll-x scroll-with-animation> |
||||
|
<view class="category-list"> |
||||
|
<view class="category-item {{videoActiveCategory === '全部' ? 'active' : ''}}" bindtap="selectVideoCategory" data-category="全部"> |
||||
|
<text>全部视频</text> |
||||
|
<view class="category-indicator"></view> |
||||
|
</view> |
||||
|
<view wx:for="{{videoType}}" wx:key="index" class="category-item {{ item.dictLabel=== videoActiveCategory ? 'active' : ''}}" bindtap="selectVideoCategory" data-category="{{item.dictLabel}}"> |
||||
|
<text>{{item.dictLabel}}</text> |
||||
|
<view class="category-indicator"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
|
||||
|
<!-- 视频列表 --> |
||||
|
<view class="videos-grid"> |
||||
|
<view class="video-card" wx:for="{{filteredVideos}}" wx:key="id" bindtap="playVideo" data-id="{{item.id}}"> |
||||
|
<!-- 视频封面 --> |
||||
|
<view class="video-cover-container"> |
||||
|
<image class="video-cover" src="{{baseUrl+item.coverImage}}" mode="aspectFill"></image> |
||||
|
<view class="video-duration">{{item.category}}</view> |
||||
|
<view class="cover-overlay"></view> |
||||
|
<view class="play-button"> |
||||
|
<image class="play-icon" src="/pagesB/images/bo.png"></image> |
||||
|
<view class="play-ripple"></view> |
||||
|
</view> |
||||
|
<view class="video-hover-effect"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频信息 --> |
||||
|
<view class="video-info"> |
||||
|
<view class="video-title">{{item.title}}</view> |
||||
|
<view class="video-instructor"> |
||||
|
<image class="instructor-avatar" src="{{baseUrl+item.publisherAvatar}}"></image> |
||||
|
<text class="instructor-name">{{item.publisherName}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="video-meta"> |
||||
|
<view class="video-meta-item"> |
||||
|
<text>{{item.auditTime}}</text> |
||||
|
</view> |
||||
|
<view class="video-meta-item"> |
||||
|
<text>{{item.viewCount}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
|
||||
|
<!-- 加载更多状态 --> |
||||
|
<view wx:if="{{currentTab === 1 && filteredVideos.length > 0}}"> |
||||
|
<view class="load-more" wx:if="{{videoHasMore && !videoLoading}}"> |
||||
|
<text>上拉加载更多</text> |
||||
|
</view> |
||||
|
<view class="loading-more" wx:if="{{videoLoading}}"> |
||||
|
<view class="loading-dot"></view> |
||||
|
<view class="loading-dot"></view> |
||||
|
<view class="loading-dot"></view> |
||||
|
<text>加载中...</text> |
||||
|
</view> |
||||
|
<view class="no-more" wx:if="{{!videoHasMore && filteredVideos.length > 0}}"> |
||||
|
<text>已加载全部</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 空状态 --> |
||||
|
<view class="empty-state" wx:if="{{filteredVideos.length === 0}}"> |
||||
|
<view class="empty-animation"> |
||||
|
<view class="empty-circle"></view> |
||||
|
<view class="empty-circle circle-2"></view> |
||||
|
<view class="empty-circle circle-3"></view> |
||||
|
</view> |
||||
|
<text class="empty-text">暂无相关视频</text> |
||||
|
<text class="empty-hint">换个关键词试试吧</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 新增视频气泡按钮 --> |
||||
|
<view class="create-btn-container" bind:tap="bindPublish"> |
||||
|
<view class="create-btn" bindtap="showCreateModal"> |
||||
|
<image class="btn-icon" src="/pagesA/images/jh.png"></image> |
||||
|
<text class="btn-text">发布</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
1105
pagesB/pages/training/training.wxss
File diff suppressed because it is too large
View File
@ -0,0 +1,99 @@ |
|||||
|
import http from '../../../utils/api' |
||||
|
const baseUrl = require('../../../utils/baseUrl') |
||||
|
|
||||
|
Page({ |
||||
|
data: { |
||||
|
baseUrl: baseUrl, |
||||
|
article: {}, // 文章详情
|
||||
|
loading: true, // 加载状态
|
||||
|
}, |
||||
|
|
||||
|
onLoad(options) { |
||||
|
this.getArticleDetails(options.id) |
||||
|
}, |
||||
|
|
||||
|
// 获取文章详情
|
||||
|
getArticleDetails(id) { |
||||
|
this.setData({ |
||||
|
loading: true |
||||
|
}) |
||||
|
|
||||
|
http.articleDetails({ |
||||
|
data: { id }, |
||||
|
success: res => { |
||||
|
console.log('文章详情:', res) |
||||
|
if (res.code === 200 && res.data) { |
||||
|
// 解析富文本内容
|
||||
|
const article = res.data |
||||
|
// 这里可以处理富文本内容的样式
|
||||
|
article.content = this.formatRichContent(article.content) |
||||
|
|
||||
|
this.setData({ |
||||
|
article: article, |
||||
|
loading: false |
||||
|
}) |
||||
|
|
||||
|
// 设置页面标题
|
||||
|
wx.setNavigationBarTitle({ |
||||
|
title: article.title.substring(0, 10) + (article.title.length > 10 ? '...' : '') |
||||
|
}) |
||||
|
} else { |
||||
|
wx.showToast({ |
||||
|
title: '文章不存在', |
||||
|
icon: 'error' |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
wx.navigateBack() |
||||
|
}, 2000) |
||||
|
} |
||||
|
}, |
||||
|
fail: err => { |
||||
|
console.error('获取文章详情失败:', err) |
||||
|
this.setData({ |
||||
|
loading: false |
||||
|
}) |
||||
|
wx.showToast({ |
||||
|
title: '加载失败', |
||||
|
icon: 'error' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
// 格式化富文本内容
|
||||
|
formatRichContent(content) { |
||||
|
// 这里可以添加自定义样式,比如给图片添加样式
|
||||
|
return content.replace(/<img/g, '<img style="max-width:100%;height:auto;border-radius:15rpx;margin:20rpx 0;"') |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
|
||||
|
// 滚动事件
|
||||
|
onPageScroll(e) { |
||||
|
this.setData({ |
||||
|
scrollTop: e.scrollTop |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 回到顶部
|
||||
|
scrollToTop() { |
||||
|
wx.pageScrollTo({ |
||||
|
scrollTop: 0, |
||||
|
duration: 300 |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 下拉刷新
|
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
// 页面卸载
|
||||
|
onUnload() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"文章详情", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
<view class="article-detail-container"> |
||||
|
|
||||
|
<!-- 文章内容 --> |
||||
|
<scroll-view class="article-content" scroll-y> |
||||
|
<!-- 文章封面 --> |
||||
|
<view class="article-cover"> |
||||
|
<image |
||||
|
class="cover-image" |
||||
|
src="{{baseUrl + article.coverImage}}" |
||||
|
mode="widthFix" |
||||
|
lazy-load |
||||
|
></image> |
||||
|
<view class="cover-overlay"></view> |
||||
|
<view class="cover-gradient"></view> |
||||
|
<view class="cover-category">{{article.category}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章主体 --> |
||||
|
<view class="article-body"> |
||||
|
<!-- 标题区域 --> |
||||
|
<view class="title-section"> |
||||
|
<view class="article-title">{{article.title}}</view> |
||||
|
<view class="article-subtitle">{{article.subtitle}}</view> |
||||
|
|
||||
|
<!-- 专家信息 --> |
||||
|
<view class="expert-info"> |
||||
|
<image class="expert-avatar" src="{{baseUrl + article.expertAvatar}}"></image> |
||||
|
<view class="expert-detail"> |
||||
|
<view class="expert-name">{{article.expertName}}</view> |
||||
|
<view class="publish-time">{{article.publishTime}}</view> |
||||
|
</view> |
||||
|
<view class="view-count"> |
||||
|
<image class="view-icon" src="/pagesB/images/lll.png"></image> |
||||
|
<text>{{article.viewCount}} 阅读</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文章内容(富文本) --> |
||||
|
<view class="rich-content"> |
||||
|
<rich-text nodes="{{article.content}}"></rich-text> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
|
||||
|
|
||||
|
<!-- 加载中 --> |
||||
|
<view class="loading-container" wx:if="{{loading}}"> |
||||
|
<view class="loading-spinner"> |
||||
|
<view class="spinner-circle"></view> |
||||
|
<view class="spinner-circle circle-2"></view> |
||||
|
<view class="spinner-circle circle-3"></view> |
||||
|
</view> |
||||
|
<text class="loading-text">加载中...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 回到顶部按钮 --> |
||||
|
<view class="back-to-top" wx:if="{{scrollTop > 400}}" catchtap="scrollToTop"> |
||||
|
<image class="top-icon" src="/pagesB/images/top.png"></image> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,322 @@ |
|||||
|
.article-detail-container { |
||||
|
min-height: 100vh; |
||||
|
background: #f8fafc; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* 文章内容容器 */ |
||||
|
.article-content { |
||||
|
height: 100vh; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
/* 文章封面 */ |
||||
|
.article-cover { |
||||
|
position: relative; |
||||
|
height: 500rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.cover-image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
object-fit: cover; |
||||
|
} |
||||
|
|
||||
|
.cover-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.1)); |
||||
|
} |
||||
|
|
||||
|
.cover-gradient { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
height: 200rpx; |
||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent); |
||||
|
} |
||||
|
|
||||
|
.cover-category { |
||||
|
position: absolute; |
||||
|
top: 40rpx; |
||||
|
right: 40rpx; |
||||
|
background: rgba(52, 152, 219, 0.95); |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
color: white; |
||||
|
padding: 12rpx 28rpx; |
||||
|
border-radius: 25rpx; |
||||
|
font-size: 24rpx; |
||||
|
font-weight: 600; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2); |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
/* 文章主体 */ |
||||
|
.article-body { |
||||
|
background: white; |
||||
|
border-radius: 40rpx 40rpx 0 0; |
||||
|
margin-top: -40rpx; |
||||
|
position: relative; |
||||
|
z-index: 10; |
||||
|
padding-bottom: 50rpx; |
||||
|
} |
||||
|
|
||||
|
.title-section { |
||||
|
padding: 50rpx 40rpx 40rpx; |
||||
|
border-bottom: 1rpx solid #f1f5f9; |
||||
|
} |
||||
|
|
||||
|
.article-title { |
||||
|
font-size: 44rpx; |
||||
|
font-weight: 800; |
||||
|
color: #1e293b; |
||||
|
line-height: 1.4; |
||||
|
margin-bottom: 20rpx; |
||||
|
letter-spacing: 0.5rpx; |
||||
|
} |
||||
|
|
||||
|
.article-subtitle { |
||||
|
font-size: 30rpx; |
||||
|
color: #64748b; |
||||
|
line-height: 1.6; |
||||
|
margin-bottom: 40rpx; |
||||
|
padding-bottom: 30rpx; |
||||
|
border-bottom: 1rpx dashed #e2e8f0; |
||||
|
} |
||||
|
|
||||
|
/* 专家信息 */ |
||||
|
.expert-info { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 20rpx; |
||||
|
margin-top: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.expert-avatar { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 50%; |
||||
|
border: 3rpx solid white; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.expert-detail { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.expert-name { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #1e293b; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.publish-time { |
||||
|
font-size: 24rpx; |
||||
|
color: #94a3b8; |
||||
|
} |
||||
|
|
||||
|
.view-count { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10rpx; |
||||
|
background: rgba(226, 232, 240, 0.3); |
||||
|
padding: 12rpx 20rpx; |
||||
|
border-radius: 25rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #64748b; |
||||
|
} |
||||
|
|
||||
|
.view-icon { |
||||
|
width: 28rpx; |
||||
|
height: 28rpx; |
||||
|
opacity: 0.7; |
||||
|
} |
||||
|
|
||||
|
/* 富文本内容 */ |
||||
|
.rich-content { |
||||
|
padding: 40rpx; |
||||
|
font-size: 32rpx; |
||||
|
line-height: 1.8; |
||||
|
color: #334155; |
||||
|
} |
||||
|
|
||||
|
.rich-content h1, |
||||
|
.rich-content h2, |
||||
|
.rich-content h3 { |
||||
|
color: #1e293b; |
||||
|
margin: 40rpx 0 20rpx; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
.rich-content h1 { |
||||
|
font-size: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.rich-content h2 { |
||||
|
font-size: 36rpx; |
||||
|
} |
||||
|
|
||||
|
.rich-content h3 { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.rich-content p { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.rich-content ul, |
||||
|
.rich-content ol { |
||||
|
margin: 20rpx 0 20rpx 40rpx; |
||||
|
} |
||||
|
|
||||
|
.rich-content li { |
||||
|
margin-bottom: 15rpx; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.rich-content ul li:before { |
||||
|
content: '•'; |
||||
|
color: #3498db; |
||||
|
font-weight: bold; |
||||
|
position: absolute; |
||||
|
left: -25rpx; |
||||
|
} |
||||
|
|
||||
|
.rich-content strong { |
||||
|
color: #1e293b; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
.rich-content a { |
||||
|
color: #3498db; |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
/* 加载中 */ |
||||
|
.loading-container { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2000; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
position: relative; |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.spinner-circle { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
border: 8rpx solid rgba(52, 152, 219, 0.1); |
||||
|
border-radius: 50%; |
||||
|
animation: spinnerRotate 2s linear infinite; |
||||
|
} |
||||
|
|
||||
|
.spinner-circle.circle-2 { |
||||
|
width: 80%; |
||||
|
height: 80%; |
||||
|
border-width: 6rpx; |
||||
|
animation-delay: 0.2s; |
||||
|
} |
||||
|
|
||||
|
.spinner-circle.circle-3 { |
||||
|
width: 60%; |
||||
|
height: 60%; |
||||
|
border-width: 4rpx; |
||||
|
animation-delay: 0.4s; |
||||
|
} |
||||
|
|
||||
|
@keyframes spinnerRotate { |
||||
|
0% { |
||||
|
transform: translate(-50%, -50%) rotate(0deg); |
||||
|
} |
||||
|
100% { |
||||
|
transform: translate(-50%, -50%) rotate(360deg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #64748b; |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
/* 回到顶部 */ |
||||
|
.back-to-top { |
||||
|
position: fixed; |
||||
|
right: 40rpx; |
||||
|
bottom: 160rpx; |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.15); |
||||
|
border: 1rpx solid rgba(226, 232, 240, 0.8); |
||||
|
transition: all 0.3s ease; |
||||
|
z-index: 90; |
||||
|
} |
||||
|
|
||||
|
.back-to-top:active { |
||||
|
transform: scale(0.95); |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
.top-icon { |
||||
|
width: 36rpx; |
||||
|
height: 36rpx; |
||||
|
} |
||||
|
|
||||
|
/* 响应式调整 */ |
||||
|
@media screen and (max-width: 750rpx) { |
||||
|
.article-cover { |
||||
|
height: 400rpx; |
||||
|
} |
||||
|
|
||||
|
.article-title { |
||||
|
font-size: 38rpx; |
||||
|
} |
||||
|
|
||||
|
.article-subtitle { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.rich-content { |
||||
|
font-size: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.action-bar { |
||||
|
padding: 0 30rpx; |
||||
|
} |
||||
|
|
||||
|
.comment-btn { |
||||
|
padding: 18rpx 30rpx; |
||||
|
} |
||||
|
} |
||||