增加品牌方案管理

This commit is contained in:
2026-07-23 18:56:35 +08:00
parent 4c07ec5455
commit b689656faf
67 changed files with 5572 additions and 231 deletions
+38 -15
View File
@@ -1,10 +1,10 @@
<template>
<view class="search-page">
<view class="header-wrap" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-bar" :style="{ height: navBarHeight + 'px' }">
<view class="header-wrap" :style="headerStyle">
<view class="nav-bar" :style="navStyle">
<text class="nav-title">&#8981; 团课搜索</text>
</view>
<view class="search-area">
<view class="search-area" :style="{ backgroundColor: brandConfig.secondaryColor }">
<view class="search-box">
<text class="search-symbol">&#8981;</text>
<input class="search-input" v-model="searchKeyword" placeholder="搜索课程名称、教练、场地..."
@@ -31,7 +31,8 @@
<view class="period-row">
<view v-for="(period, idx) in periods" :key="idx" class="period-item"
:class="{ active: currentPeriod === period.value }" @click="selectPeriod(period.value)">
:class="{ active: currentPeriod === period.value }" @click="selectPeriod(period.value)"
:style="currentPeriod === period.value ? activeBrandStyle : ''">
<text>{{ period.label }}</text>
</view>
</view>
@@ -40,18 +41,20 @@
<view class="type-section">
<view class="type-all-row">
<view class="filter-tab" :class="{ active: currentFilter === 'all' }" @click="switchFilter('all')">全部</view>
<view class="filter-tab" :class="{ active: currentFilter === 'all' }" @click="switchFilter('all')"
:style="currentFilter === 'all' ? activeBrandStyle : ''">全部</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)">
:class="{ active: currentFilter === tab.value }" @click="selectType(tab)"
:style="currentFilter === tab.value ? activeBrandStyle : ''">
{{ tab.label }}
</view>
</view>
</view>
<view v-if="typeRows.length > 1" class="type-expand-row">
<text class="type-expand-btn" @click="typesExpanded = !typesExpanded">
<text class="type-expand-btn" @click="typesExpanded = !typesExpanded" :style="{ color: brandConfig.primaryColor }">
{{ typesExpanded ? '收起 ▲' : '展开更多 ▾' }}
</text>
</view>
@@ -61,7 +64,8 @@
<text class="result-count"> {{ filteredCourses.length }} 个课程</text>
<view class="sort-options">
<text v-for="(sort, idx) in sortOptions" :key="idx" class="sort-item"
:class="{ active: currentSort === sort.value }" @click="switchSort(sort.value)">
:class="{ active: currentSort === sort.value }" @click="switchSort(sort.value)"
:style="currentSort === sort.value ? 'color:' + brandConfig.primaryColor : ''">
{{ sort.label }}
</text>
</view>
@@ -88,20 +92,20 @@
<view class="course-body">
<view class="course-top">
<text class="course-name">{{ course.courseName }}</text>
<text class="course-price" :class="{ free: course.storedValueAmount === 0 }">
<text class="course-price" :class="{ free: course.storedValueAmount === 0 }" :style="{ color: brandConfig.primaryColor }">
{{ course.storedValueAmount > 0 ? '¥' + course.storedValueAmount + '/次' : '免费' }}
</text>
</view>
<view class="course-labels" v-if="course.labels && course.labels.length">
<text v-for="(label, li) in course.labels" :key="li" class="course-label"
:style="{ background: label.color + '1a', color: label.color }">{{ label.labelName }}</text>
:style="label._style">{{ label.labelName }}</text>
</view>
<view class="course-meta">
<text class="meta-icon">&#128339;</text>
<text class="course-date">{{ course.shortDate }}</text>
<text class="course-time">{{ course.shortTime }} - {{ course.endShortTime }}</text>
<text class="course-duration" v-if="course.duration">{{ course.duration }}</text>
<text class="course-duration" v-if="course.duration" :style="{ color: brandConfig.primaryColor }">{{ course.duration }}</text>
</view>
<view class="course-meta">
<text class="meta-icon">&#128205;</text>
@@ -110,7 +114,8 @@
<view class="course-bottom">
<text class="course-capacity">已预约 {{ course.currentMembers }}/{{ course.maxMembers }}</text>
<button class="btn-book" :class="{ disabled: !course.canBook }" :disabled="!course.canBook"
@click.stop="bookCourse(course)">
@click.stop="bookCourse(course)"
:style="course.canBook ? activeBrandStyle : ''">
{{ course.canBook ? '立即预约' : course.statusLabel }}
</button>
</view>
@@ -133,6 +138,7 @@
const courseApi = require('../../api/course')
const bookingApi = require('../../api/booking')
const store = require('../../store/index')
const brandStore = require('../../store/brand')
const { resolveCoverUrl } = require('../../utils/request')
export default {
@@ -161,7 +167,8 @@
{ label: '热度', value: 'popular' }
],
courses: [],
activeBookedCourseIds: new Set() // 用户当前已预约(status=0)的课程ID集合
activeBookedCourseIds: new Set(), // 用户当前已预约(status=0)的课程ID集合
brandConfig: brandStore.config
}
},
onLoad() {
@@ -179,15 +186,28 @@
this.totalHeaderHeight = statusBarHeight + navBarHeight
this.loadCourses()
this.loadTypes()
uni.$on('brand:updated', (config) => { this.brandConfig = config })
},
onUnload() {
uni.$off('brand:updated')
},
onShow() {
this.loadActiveBookings()
},
onPullDownRefresh() {
Promise.all([this.loadCourses(), this.loadTypes(), this.loadActiveBookings()])
Promise.all([this.loadCourses(), this.loadTypes(), this.loadActiveBookings(), brandStore.fetchConfig()])
.finally(() => { uni.stopPullDownRefresh() })
},
computed: {
headerStyle() {
return 'padding-top:' + this.statusBarHeight + 'px;background-color:' + this.brandConfig.secondaryColor
},
navStyle() {
return 'height:' + this.navBarHeight + 'px;background-color:' + this.brandConfig.secondaryColor
},
activeBrandStyle() {
return 'background:' + this.brandConfig.primaryColor + ';color:' + this.brandConfig.secondaryColor
},
// 将类型列表按每行 COLS_PER_ROW 个拆分为二维数组
typeRows() {
const tabs = this.courseTypes.map(t => ({
@@ -266,7 +286,7 @@
status: statusStr,
statusLabel: statusLabel,
storedValueAmount: c.storedValueAmount != null ? c.storedValueAmount : 0,
labels: this.getTypeLabels(c.courseType),
labels: this.getTypeLabels(c.courseType).map(l => ({ ...l, _style: { background: l.color + '1a', color: l.color } })),
coverImage: resolveCoverUrl(c.coverImage),
}
})
@@ -305,6 +325,9 @@
}
},
methods: {
labelStyle(label) {
return { background: label.color + '1a', color: label.color }
},
// 从已加载的课程类型中匹配标签
getTypeLabels(courseType) {
const type = this.getTypeInfo(courseType)