实现大部分页面前后端互通
This commit is contained in:
@@ -13,15 +13,20 @@
|
||||
</view>
|
||||
|
||||
<view class="date-range-row">
|
||||
<view class="date-range-item" @click="openStartDatePicker">
|
||||
<text class="range-label">开始</text>
|
||||
<text class="range-value" :class="{ placeholder: !startDate }">{{ startDate || '选择日期' }}</text>
|
||||
</view>
|
||||
<picker mode="date" :value="startDate" @change="onStartDateChange" :end="endDate || '2099-12-31'">
|
||||
<view class="date-range-item">
|
||||
<text class="range-label">开始</text>
|
||||
<text class="range-value" :class="{ placeholder: !startDate }">{{ startDate || '选择日期' }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<text class="range-sep">至</text>
|
||||
<view class="date-range-item" @click="openEndDatePicker">
|
||||
<text class="range-label">结束</text>
|
||||
<text class="range-value" :class="{ placeholder: !endDate }">{{ endDate || '选择日期' }}</text>
|
||||
</view>
|
||||
<picker mode="date" :value="endDate" @change="onEndDateChange" :start="startDate || '2020-01-01'">
|
||||
<view class="date-range-item">
|
||||
<text class="range-label">结束</text>
|
||||
<text class="range-value" :class="{ placeholder: !endDate }">{{ endDate || '选择日期' }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<text v-if="startDate || endDate" class="date-clear" @click="clearDateRange">✕</text>
|
||||
</view>
|
||||
|
||||
<view class="period-row">
|
||||
@@ -33,14 +38,24 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="filter-tabs" scroll-x>
|
||||
<view class="filter-tabs-inner">
|
||||
<view v-for="(tab, idx) in filterTabs" :key="idx" class="filter-tab"
|
||||
:class="{ active: currentFilter === tab.value }" @click="switchFilter(tab.value)">
|
||||
{{ tab.label }}
|
||||
<view class="type-section">
|
||||
<view class="type-all-row">
|
||||
<view class="filter-tab" :class="{ active: currentFilter === 'all' }" @click="switchFilter('all')">全部</view>
|
||||
</view>
|
||||
<view class="type-grid" :class="{ expanded: typesExpanded }">
|
||||
<view v-for="(row, ri) in typeRows" :key="ri" class="type-row" :class="{ 'row-visible': isRowVisible(ri) }">
|
||||
<view v-for="(tab, ti) in row" :key="ti" class="filter-tab"
|
||||
:class="{ active: currentFilter === tab.value }" @click="selectType(tab)">
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="typeRows.length > 1" class="type-expand-row">
|
||||
<text class="type-expand-btn" @click="typesExpanded = !typesExpanded">
|
||||
{{ typesExpanded ? '收起 ▲' : '展开更多 ▾' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="sort-bar">
|
||||
<text class="result-count">共 {{ filteredCourses.length }} 个课程</text>
|
||||
@@ -127,11 +142,10 @@
|
||||
{ label: '下午', value: 'afternoon' },
|
||||
{ label: '晚间', value: 'evening' }
|
||||
],
|
||||
filterTabs: [
|
||||
{ label: '全部', value: 'all' }, { label: '有氧运动', value: '有氧运动' },
|
||||
{ label: '高强度', value: '高强度' }, { label: '柔韧平衡', value: '柔韧平衡' },
|
||||
{ label: '力量训练', value: '力量训练' }, { label: '免费', value: 'free' }
|
||||
],
|
||||
courseTypes: [], // 从后端获取的团课类型列表
|
||||
typesExpanded: false, // 类型列表是否展开
|
||||
visibleRowStart: 0, // 折叠时显示的起始行索引
|
||||
COLS_PER_ROW: 4, // 每行类型数量
|
||||
sortOptions: [
|
||||
{ label: '时间', value: 'time' },
|
||||
{ label: '难度', value: 'difficulty' },
|
||||
@@ -154,8 +168,21 @@
|
||||
this.navBarHeight = navBarHeight
|
||||
this.totalHeaderHeight = statusBarHeight + navBarHeight
|
||||
this.loadCourses()
|
||||
this.loadTypes()
|
||||
},
|
||||
computed: {
|
||||
// 将类型列表按每行 COLS_PER_ROW 个拆分为二维数组
|
||||
typeRows() {
|
||||
const tabs = this.courseTypes.map(t => ({
|
||||
label: t.typeName,
|
||||
value: String(t.id)
|
||||
}))
|
||||
const rows = []
|
||||
for (let i = 0; i < tabs.length; i += this.COLS_PER_ROW) {
|
||||
rows.push(tabs.slice(i, i + this.COLS_PER_ROW))
|
||||
}
|
||||
return rows
|
||||
},
|
||||
filteredCourses() {
|
||||
let list = this.courses.map(c => {
|
||||
const bd = c.baseDifficulty || c.calculatedDifficulty || 5
|
||||
@@ -226,15 +253,38 @@
|
||||
(c.location || '').toLowerCase().includes(kw)
|
||||
)
|
||||
}
|
||||
if (this.currentFilter === 'free') list = list.filter(c => c.storedValueAmount === 0)
|
||||
else if (this.currentFilter !== 'all') list = list.filter(c => c.category === this.currentFilter)
|
||||
if (this.currentFilter !== 'all') list = list.filter(c => String(c.courseType) === this.currentFilter)
|
||||
if (this.currentPeriod !== 'all') list = list.filter(c => c.period === this.currentPeriod)
|
||||
// 日期范围过滤:根据课程 startTime 的日期部分进行筛选
|
||||
if (this.startDate || this.endDate) {
|
||||
list = list.filter(c => {
|
||||
const courseDate = (c.startTime || '').substring(0, 10)
|
||||
if (!courseDate) return false
|
||||
if (this.startDate && courseDate < this.startDate) return false
|
||||
if (this.endDate && courseDate > this.endDate) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
if (this.currentSort === 'difficulty') list = [...list].sort((a, b) => a.baseDifficulty - b.baseDifficulty)
|
||||
else if (this.currentSort === 'popular') list = [...list].sort((a, b) => (b.currentMembers || 0) - (a.currentMembers || 0))
|
||||
else if (this.currentSort === 'time') {
|
||||
list = [...list].sort((a, b) => {
|
||||
const ta = a.startTime || ''
|
||||
const tb = b.startTime || ''
|
||||
return ta < tb ? -1 : ta > tb ? 1 : 0
|
||||
})
|
||||
}
|
||||
// 只显示可预约的团课
|
||||
list = list.filter(c => c.canBook)
|
||||
return list
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 判断某行是否可见:展开时全部可见,折叠时只有 visibleRowStart 行可见
|
||||
isRowVisible(rowIndex) {
|
||||
if (this.typesExpanded) return true
|
||||
return rowIndex === this.visibleRowStart
|
||||
},
|
||||
async loadCourses() {
|
||||
this.loading = true
|
||||
try {
|
||||
@@ -248,14 +298,31 @@
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async loadTypes() {
|
||||
try {
|
||||
const types = await courseApi.getCourseTypes()
|
||||
// 按 id 排序
|
||||
this.courseTypes = (Array.isArray(types) ? types : []).sort((a, b) => (a.id || 0) - (b.id || 0))
|
||||
} catch (e) {
|
||||
console.error('加载课程类型失败:', e)
|
||||
}
|
||||
},
|
||||
onSearchInput() {},
|
||||
clearSearch() { this.searchKeyword = '' },
|
||||
onCoverLoad(course) { course.coverLoaded = true },
|
||||
onCoverError(course) { course.coverError = true },
|
||||
openStartDatePicker() { uni.showToast({ title: '选择开始日期', icon: 'none' }) },
|
||||
openEndDatePicker() { uni.showToast({ title: '选择结束日期', icon: 'none' }) },
|
||||
onStartDateChange(e) { this.startDate = e.detail.value },
|
||||
onEndDateChange(e) { this.endDate = e.detail.value },
|
||||
clearDateRange() { this.startDate = ''; this.endDate = '' },
|
||||
selectPeriod(value) { this.currentPeriod = value },
|
||||
switchFilter(value) { this.currentFilter = value },
|
||||
selectType(tab) {
|
||||
this.currentFilter = tab.value
|
||||
// 找到该类型所在的行索引,折叠到那一行
|
||||
const idx = this.typeRows.findIndex(row => row.some(t => t.value === tab.value))
|
||||
if (idx >= 0) this.visibleRowStart = idx
|
||||
this.typesExpanded = false
|
||||
},
|
||||
switchSort(value) { this.currentSort = value },
|
||||
goToDetail(course) { uni.navigateTo({ url: '/pages/course-detail/course-detail?id=' + course.id }) },
|
||||
bookCourse(course) { if (course.canBook) uni.navigateTo({ url: '/pages/course-detail/course-detail?id=' + course.id }) }
|
||||
@@ -276,20 +343,33 @@
|
||||
.clear-btn { font-size: 15px; color: rgba(255,255,255,0.5); padding: 4px; flex-shrink: 0; }
|
||||
|
||||
.date-range-row { display: flex; align-items: center; margin-bottom: 14px; }
|
||||
.date-range-item { flex: 1; background: rgba(255,255,255,0.1); border-radius: 12px; padding: 10px 14px; margin-right: 10px; }
|
||||
.date-range-row picker { flex: 1; margin-right: 10px; }
|
||||
.date-range-item { background: rgba(255,255,255,0.1); border-radius: 12px; padding: 10px 14px; }
|
||||
.range-label { font-size: 11px; color: rgba(255,255,255,0.5); display: block; margin-bottom: 2px; }
|
||||
.range-value { font-size: 14px; font-weight: 600; color: #FFFFFF; }
|
||||
.range-value.placeholder { color: rgba(255,255,255,0.35); font-weight: 400; }
|
||||
.range-sep { color: rgba(255,255,255,0.5); font-size: 14px; flex-shrink: 0; margin-right: 10px; }
|
||||
.date-clear { font-size: 16px; color: rgba(255,255,255,0.4); padding: 8px; flex-shrink: 0; }
|
||||
|
||||
.period-row { display: flex; }
|
||||
.period-item { flex: 1; text-align: center; padding: 7px 0; border-radius: 40px; font-size: 13px; color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.08); margin: 0 3px; }
|
||||
.period-item.active { background: #00E676; color: #1A1A1A; font-weight: 600; }
|
||||
|
||||
.filter-tabs { background: #FFFFFF; display: flex; white-space: nowrap; border-bottom: 1px solid #EEEEEE; }
|
||||
.filter-tabs-inner { padding: 14px 16px; display: flex; white-space: nowrap; }
|
||||
.filter-tab { display: inline-block; padding: 6px 16px; border-radius: 40px; font-size: 13px; color: #7A7E84; background: #F5F7FA; margin-right: 8px; }
|
||||
.type-section { background: #FFFFFF; padding: 12px 16px 14px; border-bottom: 1px solid #EEEEEE; }
|
||||
.type-all-row { margin-bottom: 6px; }
|
||||
.type-grid { }
|
||||
.type-row {
|
||||
display: flex; flex-wrap: wrap;
|
||||
max-height: 0; opacity: 0;
|
||||
margin-bottom: 0; overflow: hidden;
|
||||
transition: max-height 0.25s ease, opacity 0.25s ease, margin-bottom 0.25s ease;
|
||||
}
|
||||
.type-row.row-visible { max-height: 100px; opacity: 1; margin-bottom: 6px; }
|
||||
.type-grid.expanded .type-row { max-height: 100px; opacity: 1; margin-bottom: 6px; }
|
||||
.filter-tab { display: inline-block; padding: 6px 16px; border-radius: 40px; font-size: 13px; color: #7A7E84; background: #F5F7FA; margin-right: 8px; margin-bottom: 6px; }
|
||||
.filter-tab.active { background: #00E676; color: #1A1A1A; font-weight: 600; }
|
||||
.type-expand-row { text-align: center; padding-top: 2px; }
|
||||
.type-expand-btn { font-size: 12px; color: #00C853; font-weight: 500; }
|
||||
|
||||
.sort-bar { display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; background: #FFFFFF; border-bottom: 1px solid #EEEEEE; }
|
||||
.result-count { font-size: 13px; color: #7A7E84; }
|
||||
|
||||
Reference in New Issue
Block a user