Add 卓米会 mini program and admin server.

Initial commit of the WeChat mini program, Express backend, and admin console.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Michelle Zan
2026-09-29 19:01:36 +08:00
co-authored by Cursor
commit cd05113755
67 changed files with 4845 additions and 0 deletions
@@ -0,0 +1,41 @@
const media = require('../../utils/media')
Component({
properties: {
activity: {
type: Object,
value: null
},
showPast: {
type: Boolean,
value: false
}
},
data: {
poster: '',
video: '',
ended: false
},
observers: {
activity(activity) {
const item = activity || {}
this.setData({
poster: media.absUrl(item.poster || item.cover),
video: media.absUrl(item.video),
ended: item.status === 'past'
})
}
},
methods: {
onMap() {
this.triggerEvent('map')
},
onSignup() {
if (!this.data.activity || this.data.ended) return
this.triggerEvent('signup')
},
onPast() {
this.triggerEvent('past')
}
}
})
@@ -0,0 +1,3 @@
{
"component": true
}
@@ -0,0 +1,48 @@
<view class="board" wx:if="{{activity}}">
<view class="banner">
<video
wx:if="{{video}}"
class="banner-media"
src="{{video}}"
controls
show-center-play-btn
object-fit="cover"
poster="{{poster}}"
/>
<block wx:else>
<image wx:if="{{poster}}" class="banner-media" src="{{poster}}" mode="aspectFill" />
<view class="banner-shade"></view>
<text class="banner-title">{{activity.title}}</text>
</block>
<view class="badge" wx:if="{{activity.isCurrent}}">当前活动</view>
</view>
<view class="card">
<text class="brand">{{activity.brandName}}</text>
<text wx:if="{{activity.brandDesc}}" class="desc">{{activity.brandDesc}}</text>
</view>
<view class="card info">
<view class="row">
<text class="label">活动名称</text>
<text class="value">{{activity.title}}</text>
</view>
<view class="row">
<text class="label">活动时间</text>
<text class="value">{{activity.startAt || '待定'}}</text>
</view>
<view class="row last">
<text class="label">会场地址</text>
<text class="value">{{activity.address || '待定'}}</text>
</view>
</view>
<view class="btn green" hover-class="btn-pressed" bindtap="onMap">📍 一键导航</view>
<view
class="btn teal {{ended ? 'disabled' : ''}}"
hover-class="{{ended ? 'none' : 'btn-pressed'}}"
bindtap="onSignup"
>{{ended ? '活动已结束' : '立即报名'}}</view>
<view wx:if="{{showPast}}" class="more" bindtap="onPast">查看往期活动 ›</view>
</view>
@@ -0,0 +1,139 @@
.board {
padding: 28rpx 32rpx 48rpx;
}
.banner {
position: relative;
height: 320rpx;
border-radius: 28rpx;
overflow: hidden;
background: linear-gradient(180deg, #d5dbe3 0%, #8e98a6 100%);
}
.banner-media,
.banner-shade {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.banner-shade {
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 45%, rgba(0, 0, 0, 0.28) 100%);
}
.badge {
position: absolute;
left: 24rpx;
top: 24rpx;
z-index: 2;
height: 48rpx;
padding: 0 20rpx;
border-radius: 24rpx;
background: rgba(55, 65, 81, 0.45);
color: #ffffff;
font-size: 24rpx;
line-height: 48rpx;
}
.banner-title {
position: absolute;
left: 32rpx;
right: 32rpx;
bottom: 32rpx;
z-index: 1;
color: #ffffff;
font-size: 40rpx;
font-weight: 700;
line-height: 1.35;
}
.card {
margin-top: 24rpx;
padding: 32rpx;
background: #ffffff;
border-radius: 28rpx;
}
.brand {
display: block;
color: #128a80;
font-size: 36rpx;
font-weight: 700;
}
.desc {
display: block;
margin-top: 16rpx;
color: #6b7280;
font-size: 28rpx;
line-height: 1.65;
}
.info {
padding: 8rpx 32rpx;
}
.row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24rpx;
padding: 28rpx 0;
border-bottom: 1rpx dashed #e5e7eb;
}
.row.last {
border-bottom: none;
}
.label {
flex-shrink: 0;
color: #9ca3af;
font-size: 28rpx;
}
.value {
flex: 1;
color: #111827;
font-size: 28rpx;
text-align: right;
line-height: 1.5;
}
.btn {
height: 96rpx;
margin-top: 24rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 32rpx;
font-weight: 600;
}
.btn-pressed {
opacity: 0.85;
}
.green {
background: #07c160;
}
.teal {
background: #0d8b87;
}
.disabled {
background: #cbd5e1;
}
.more {
margin-top: 36rpx;
padding: 8rpx 0;
text-align: center;
color: #9ca3af;
font-size: 28rpx;
}