feat: Overhaul homepage UI with new activity sections and add dedicated activity, shop, and registration pages.
This commit is contained in:
parent
5298ed1acf
commit
8a6ac48d59
@ -65,19 +65,132 @@ defineExpose({ revealResults, reset })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flip-root { display: flex; flex-direction: column; gap: 16rpx; padding: 16rpx }
|
||||
.flip-actions { display: flex; gap: 12rpx }
|
||||
.flip-btn { flex: 1; background: #007AFF; color: #fff; border-radius: 8rpx }
|
||||
.flip-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12rpx }
|
||||
.flip-card { perspective: 1000px }
|
||||
.flip-inner { position: relative; width: 100%; height: 200rpx; transform-style: preserve-3d; transition: transform 0.5s }
|
||||
.flip-card.flipped .flip-inner { transform: rotateY(180deg) }
|
||||
.flip-front, .flip-back { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; border-radius: 12rpx; overflow: hidden }
|
||||
.flip-front { background: #e2e8f0; display: flex; align-items: center; justify-content: center }
|
||||
.front-placeholder { width: 80%; height: 80%; border-radius: 12rpx; background: linear-gradient(135deg, #f8fafc, #e2e8f0) }
|
||||
.flip-back { background: #fff; transform: rotateY(180deg); display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 12rpx }
|
||||
.flip-image { width: 80%; border-radius: 8rpx; margin-bottom: 8rpx; background: #f5f5f5 }
|
||||
.flip-title { font-size: 26rpx; color: #222; text-align: center; max-width: 90%; word-break: break-all }
|
||||
.flip-toolbar { display: flex; justify-content: flex-end }
|
||||
.flip-reset { background: #ffd166; color: #6b4b1f; border-radius: 999rpx }
|
||||
/* ============================================
|
||||
奇盒潮玩 - 翻牌动画组件
|
||||
采用暖橙色调的开箱效果
|
||||
============================================ */
|
||||
|
||||
.flip-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.flip-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.flip-btn {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35) !important;
|
||||
color: #FFFFFF !important;
|
||||
border-radius: 16rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6rpx 16rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
.flip-btn:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.flip-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16rpx;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(10rpx);
|
||||
border-radius: 24rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.flip-card {
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
.flip-inner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 220rpx;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.flip-card.flipped .flip-inner {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
.flip-front, .flip-back {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
backface-visibility: hidden;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flip-front {
|
||||
background: linear-gradient(145deg, #FFF8F3, #FFE8D1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid rgba(255, 159, 67, 0.2);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 159, 67, 0.15);
|
||||
}
|
||||
|
||||
.front-placeholder {
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
border-radius: 16rpx;
|
||||
background: linear-gradient(135deg, rgba(255, 159, 67, 0.3), rgba(255, 107, 53, 0.2));
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.6; transform: scale(1); }
|
||||
50% { opacity: 1; transform: scale(1.05); }
|
||||
}
|
||||
|
||||
.flip-back {
|
||||
background: #FFFFFF;
|
||||
transform: rotateY(180deg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16rpx;
|
||||
border: 2rpx solid rgba(255, 159, 67, 0.3);
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.2);
|
||||
}
|
||||
|
||||
.flip-image {
|
||||
width: 75%;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 8rpx;
|
||||
background: linear-gradient(145deg, #FFF8F3, #FFF4E6);
|
||||
}
|
||||
|
||||
.flip-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
text-align: center;
|
||||
max-width: 90%;
|
||||
word-break: break-all;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.flip-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.flip-reset {
|
||||
background: linear-gradient(135deg, #FFD166, #FF9F43) !important;
|
||||
color: #6b4b1f !important;
|
||||
border-radius: 999rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6rpx 16rpx rgba(255, 159, 67, 0.35);
|
||||
}
|
||||
.flip-reset:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -134,13 +134,18 @@ function handleConfirm() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ============================================
|
||||
奇盒潮玩 - 支付弹窗组件
|
||||
采用暖橙色调的底部弹窗设计
|
||||
============================================ */
|
||||
|
||||
.payment-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
background-color: rgba(0, 0, 0, 0.55);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
@ -148,124 +153,168 @@ function handleConfirm() {
|
||||
|
||||
.payment-popup-content {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
padding: 30rpx;
|
||||
padding-bottom: calc(30rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
|
||||
background: #FFFFFF;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
padding: 32rpx;
|
||||
padding-bottom: calc(32rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(100%); }
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
|
||||
.risk-warning {
|
||||
background-color: #fffbe6;
|
||||
color: #ed6a0c;
|
||||
background: linear-gradient(135deg, #FFF8F3, #FFF4E6);
|
||||
color: #B45309;
|
||||
font-size: 24rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
line-height: 1.4;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
border: 1rpx solid rgba(255, 159, 67, 0.2);
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: #1890ff;
|
||||
text-decoration: underline;
|
||||
color: #FF9F43;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 40rpx;
|
||||
margin-bottom: 32rpx;
|
||||
position: relative;
|
||||
}
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 48rpx;
|
||||
color: #9CA3AF;
|
||||
line-height: 1;
|
||||
padding: 10rpx;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
.close-icon:active {
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
padding: 30rpx;
|
||||
padding: 16rpx 0 24rpx;
|
||||
}
|
||||
|
||||
.amount-section {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
padding: 24rpx;
|
||||
background: linear-gradient(145deg, #FFFFFF, #FFF8F3);
|
||||
border-radius: 20rpx;
|
||||
border: 1rpx solid rgba(255, 159, 67, 0.1);
|
||||
}
|
||||
.amount-section .label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
color: #6B7280;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
.amount-section .amount {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #ff4d4f;
|
||||
font-size: 56rpx;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, #FF6B35, #FF9F43);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.form-item .label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
color: #1F2937;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.picker-display {
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
border: 2rpx solid #E5E7EB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
background: #f9f9f9;
|
||||
background: #F9FAFB;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.picker-display:active {
|
||||
border-color: #FF9F43;
|
||||
background: #FFF8F3;
|
||||
}
|
||||
|
||||
.selected-text {
|
||||
color: #333;
|
||||
color: #1F2937;
|
||||
font-weight: 500;
|
||||
}
|
||||
.placeholder {
|
||||
color: #999;
|
||||
color: #9CA3AF;
|
||||
}
|
||||
.arrow {
|
||||
color: #ccc;
|
||||
color: #9CA3AF;
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-right: 2rpx solid #ccc;
|
||||
border-bottom: 2rpx solid #ccc;
|
||||
border-right: 3rpx solid #9CA3AF;
|
||||
border-bottom: 3rpx solid #9CA3AF;
|
||||
transform: rotate(-45deg);
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
display: flex;
|
||||
border-top: 1rpx solid #eee;
|
||||
gap: 20rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
.btn-cancel, .btn-confirm {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: #fff;
|
||||
border-radius: 0;
|
||||
font-size: 30rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 32rpx;
|
||||
padding: 24rpx 0;
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-cancel::after, .btn-confirm::after {
|
||||
border: none;
|
||||
}
|
||||
.btn-cancel {
|
||||
color: #666;
|
||||
border-right: 1rpx solid #eee;
|
||||
color: #6B7280;
|
||||
background: #F3F4F6;
|
||||
}
|
||||
.btn-cancel:active {
|
||||
background: #E5E7EB;
|
||||
}
|
||||
.btn-confirm {
|
||||
color: #007AFF;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
.btn-confirm:active {
|
||||
transform: scale(0.97);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.25);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -34,8 +34,8 @@
|
||||
</view>
|
||||
|
||||
<view class="action-buttons">
|
||||
<button v-if="selectedItems.length === 0" class="btn-random" @tap="handleRandomOne">随机一发</button>
|
||||
<button v-else class="btn-buy" @tap="handleBuy">去支付</button>
|
||||
<button v-if="selectedItems.length === 0" class="btn-common btn-random" @tap="handleRandomOne">随机一发</button>
|
||||
<button v-else class="btn-common btn-buy" @tap="handleBuy">去支付</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -291,121 +291,163 @@ async function onPaymentConfirm(paymentData) {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ============================================
|
||||
奇盒潮玩 - 选号组件 (适配高级卡片布局)
|
||||
============================================ */
|
||||
|
||||
/* 容器 - 去除背景,融入父级卡片 */
|
||||
.choice-grid-container {
|
||||
padding: 20rpx;
|
||||
padding: 10rpx 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 加载和空状态 */
|
||||
.loading-state, .empty-state {
|
||||
text-align: center;
|
||||
padding: 40rpx;
|
||||
color: #999;
|
||||
padding: 60rpx 0;
|
||||
color: #9CA3AF;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
/* 网格包装 */
|
||||
.grid-wrapper {
|
||||
padding-bottom: 160rpx; /* 留出底部操作栏空间 */
|
||||
}
|
||||
|
||||
/* 号码网格 - 8列布局 */
|
||||
.choices-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr); /* 一行5个 */
|
||||
gap: 16rpx;
|
||||
margin-bottom: 120rpx; /* 留出底部操作栏空间 */
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 10rpx;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 单个号码格子 */
|
||||
.choice-item {
|
||||
aspect-ratio: 1;
|
||||
background: #fff;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
background: #F9FAFB;
|
||||
border: 1rpx solid #E5E7EB;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.choice-item:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
/* 号码文字 */
|
||||
.choice-number {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #4B5563;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 状态文字 - 简化为小点或隐藏 */
|
||||
.choice-status {
|
||||
font-size: 20rpx;
|
||||
margin-top: 4rpx;
|
||||
color: #666;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 状态样式 */
|
||||
/* ============= 状态样式 ============= */
|
||||
|
||||
/* 可选状态 */
|
||||
.is-available {
|
||||
background: #fff;
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
/* 已售状态 */
|
||||
.is-sold {
|
||||
background: #f5f5f5;
|
||||
border-color: #eee;
|
||||
opacity: 0.6;
|
||||
background: #F3F4F6;
|
||||
border-color: #F3F4F6;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.is-sold::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
.is-sold .choice-number {
|
||||
color: #ccc;
|
||||
color: #D1D5DB;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* 选中状态 - 橙色高亮 */
|
||||
.is-selected {
|
||||
background: #e6f7ff;
|
||||
border-color: #1890ff;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
border-color: transparent;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.4);
|
||||
}
|
||||
.is-selected .choice-number {
|
||||
color: #1890ff;
|
||||
}
|
||||
.is-selected .choice-status {
|
||||
color: #1890ff;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* 底部操作栏 */
|
||||
/* ============= 底部操作栏 ============= */
|
||||
.action-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
bottom: 30rpx;
|
||||
left: 30rpx;
|
||||
right: 30rpx;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20rpx);
|
||||
padding: 20rpx 30rpx;
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||
border-radius: 999rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 选择信息行 */
|
||||
.selection-info {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-size: 26rpx;
|
||||
color: #4B5563;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
margin: 0 4rpx;
|
||||
color: #FF6B35;
|
||||
font-weight: 800;
|
||||
font-size: 36rpx;
|
||||
margin: 0 8rpx;
|
||||
}
|
||||
|
||||
/* 按钮组 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
/* 通用按钮样式 */
|
||||
.btn-common {
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
padding: 0 40rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 购买按钮 */
|
||||
.btn-buy {
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
padding: 0 60rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
font-size: 30rpx;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35) !important;
|
||||
color: #FFFFFF !important;
|
||||
box-shadow: 0 6rpx 16rpx rgba(255, 107, 53, 0.3);
|
||||
}
|
||||
|
||||
/* 随机按钮 */
|
||||
.btn-random {
|
||||
background: #007AFF;
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
padding: 0 60rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
font-size: 30rpx;
|
||||
margin: 0;
|
||||
background: #F3F4F6 !important;
|
||||
color: #4B5563 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
99
pages.json
99
pages.json
@ -5,105 +5,108 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "uni-app"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/login/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/shop/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商城"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/shop/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/cabinet/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "货柜"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/points/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "积分记录"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/orders/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的订单"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/address/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "地址管理"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/address/edit",
|
||||
"style": {
|
||||
"navigationBarTitleText": "编辑地址"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/help/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "使用帮助"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/agreement/user",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户协议"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/agreement/purchase",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购买协议"
|
||||
}
|
||||
}
|
||||
,
|
||||
},
|
||||
{
|
||||
"path": "pages/activity/yifanshang/index",
|
||||
"style": { "navigationBarTitleText": "一番赏" }
|
||||
}
|
||||
,
|
||||
"style": {
|
||||
"navigationBarTitleText": "一番赏"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/activity/wuxianshang/index",
|
||||
"style": { "navigationBarTitleText": "无限赏" }
|
||||
}
|
||||
,
|
||||
"style": {
|
||||
"navigationBarTitleText": "无限赏"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/activity/duiduipeng/index",
|
||||
"style": { "navigationBarTitleText": "对对碰" }
|
||||
"style": {
|
||||
"navigationBarTitleText": "对对碰"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/activity/list/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "活动列表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/activity/pata/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "爬塔"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/register/register",
|
||||
@ -118,10 +121,30 @@
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{ "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/tab/home.png", "selectedIconPath": "static/tab/home_active.png" },
|
||||
{ "pagePath": "pages/shop/index", "text": "商城", "iconPath": "static/tab/shop.png", "selectedIconPath": "static/tab/shop_active.png" },
|
||||
{ "pagePath": "pages/cabinet/index", "text": "货柜", "iconPath": "static/tab/box.png", "selectedIconPath": "static/tab/box_active.png" },
|
||||
{ "pagePath": "pages/mine/index", "text": "我的", "iconPath": "static/tab/profile.png", "selectedIconPath": "static/tab/profile_active.png" }
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页",
|
||||
"iconPath": "static/tab/home.png",
|
||||
"selectedIconPath": "static/tab/home_active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/shop/index",
|
||||
"text": "商城",
|
||||
"iconPath": "static/tab/shop.png",
|
||||
"selectedIconPath": "static/tab/shop_active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cabinet/index",
|
||||
"text": "货柜",
|
||||
"iconPath": "static/tab/box.png",
|
||||
"selectedIconPath": "static/tab/box_active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mine/index",
|
||||
"text": "我的",
|
||||
"iconPath": "static/tab/profile.png",
|
||||
"selectedIconPath": "static/tab/profile_active.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
@ -131,4 +154,4 @@
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
}
|
||||
@ -293,39 +293,38 @@ onLoad((opts) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh; padding-bottom: 140rpx }
|
||||
/* 奇盒潮玩 - 对对碰活动页面 */
|
||||
.page { height: 100vh; padding-bottom: 140rpx; background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 100%); }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.banner-img { width: 100%; border-radius: 20rpx; box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.1); }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700; color: #DD2C00; text-align: center }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.title { font-size: 40rpx; font-weight: 800; background: linear-gradient(135deg, #FF6B35, #FF9F43); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-align: center; }
|
||||
.meta { margin-top: 12rpx; font-size: 28rpx; color: #6B7280; text-align: center; }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
.float-actions { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx; padding-bottom: calc(16rpx + env(safe-area-inset-bottom)); background: rgba(255,255,255,0.9); box-shadow: 0 -6rpx 16rpx rgba(0,0,0,0.06); z-index: 9999 }
|
||||
.float-btn { width: 100%; border-radius: 999rpx }
|
||||
.issues { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx }
|
||||
.issues-title { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx }
|
||||
.issues-list { }
|
||||
.issue-picker { height: 200rpx; background: #f8f8f8; border-radius: 12rpx; margin-bottom: 64rpx }
|
||||
.picker-item { height: 40rpx; line-height: 40rpx; text-align: center; font-size: 26rpx }
|
||||
.tabs { display: flex; padding: 0 12rpx; margin-bottom: 16rpx }
|
||||
.tab { flex: 1; text-align: center; font-size: 28rpx; padding: 16rpx 0; border: 2rpx solid #f0c58a; color: #8a5a2b; background: #fff3df; border-radius: 16rpx }
|
||||
.tab + .tab { margin-left: 12rpx }
|
||||
.tab.active { background: #ffdfaa; border-color: #ffb74d; color: #6b4b1f; font-weight: 600 }
|
||||
.primary { background: linear-gradient(135deg, #FF9F43, #FF6B35) !important; color: #fff !important; box-shadow: 0 6rpx 20rpx rgba(255, 107, 53, 0.35); }
|
||||
.float-actions { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 24rpx; padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(20rpx); box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.08); z-index: 9999 }
|
||||
.float-btn { width: 100%; border-radius: 999rpx; background: linear-gradient(135deg, #FF9F43, #FF6B35) !important; color: #fff !important; font-weight: 600; box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.35); }
|
||||
.issues { background: #FFFFFF; border-radius: 20rpx; margin: 0 24rpx 24rpx; padding: 20rpx; box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06); }
|
||||
.issues-title { font-size: 32rpx; font-weight: 700; color: #1F2937; margin-bottom: 16rpx; padding-left: 16rpx; position: relative; }
|
||||
.issues-title::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 6rpx; height: 28rpx; background: linear-gradient(180deg, #FF9F43, #FF6B35); border-radius: 999rpx; }
|
||||
.issue-picker { height: 200rpx; background: linear-gradient(145deg, #FFF8F3, #FFF4E6); border-radius: 16rpx; margin-bottom: 64rpx }
|
||||
.picker-item { height: 40rpx; line-height: 40rpx; text-align: center; font-size: 28rpx; color: #1F2937; }
|
||||
.tabs { display: flex; padding: 0 12rpx; margin-bottom: 20rpx; gap: 12rpx; }
|
||||
.tab { flex: 1; text-align: center; font-size: 28rpx; padding: 18rpx 0; border: 2rpx solid rgba(255, 159, 67, 0.3); color: #B45309; background: linear-gradient(145deg, #FFFFFF, #FFF8F3); border-radius: 16rpx; font-weight: 500; transition: all 0.2s ease; }
|
||||
.tab.active { background: linear-gradient(135deg, #FF9F43, #FF6B35); border-color: #FF6B35; color: #fff; font-weight: 600; box-shadow: 0 6rpx 16rpx rgba(255, 107, 53, 0.3); }
|
||||
.rewards { width: 100%; margin-top: 24rpx }
|
||||
.reward-card { background: #fff; border-radius: 12rpx; overflow: hidden; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06); margin-bottom: 12rpx }
|
||||
.el-reward-card { margin-bottom: 12rpx }
|
||||
.reward-card { background: #FFFFFF; border-radius: 16rpx; overflow: hidden; box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06); margin-bottom: 16rpx }
|
||||
.el-reward-card { margin-bottom: 16rpx }
|
||||
.el-card-header { display: flex; align-items: center; justify-content: space-between }
|
||||
.el-card-title { font-size: 28rpx; color: #222; flex: 1; margin-right: 8rpx; word-break: break-all }
|
||||
.el-card-title { font-size: 28rpx; color: #1F2937; flex: 1; margin-right: 8rpx; word-break: break-all; font-weight: 600; }
|
||||
.card-image-wrap { position: relative; padding-bottom: 48rpx }
|
||||
.card-image { width: 100%; height: auto; display: block; background: #f0f4ff; position: relative; z-index: 1 }
|
||||
.prob-corner { position: absolute; background: rgba(221,82,77,0.9); color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx; z-index: 2 }
|
||||
.card-image { width: 100%; height: auto; display: block; background: linear-gradient(145deg, #FFF8F3, #FFF4E6); position: relative; z-index: 1 }
|
||||
.prob-corner { position: absolute; background: linear-gradient(135deg, #FF6B35, #FF9F43); color: #fff; font-size: 22rpx; font-weight: 600; padding: 8rpx 16rpx; border-radius: 999rpx; z-index: 2; box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.35); }
|
||||
.prob-corner.tl { top: 12rpx; left: 12rpx }
|
||||
.card-body { display: flex; align-items: center; justify-content: space-between; padding: 12rpx }
|
||||
.card-title { font-size: 28rpx; color: #222; flex: 1; margin-right: 8rpx; word-break: break-all }
|
||||
.badge-boss { background: #ff9f0a; color: #222; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx }
|
||||
.badge-count { background: #ffd166; color: #6b4b1f; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx }
|
||||
.rewards-empty { font-size: 24rpx; color: #999 }
|
||||
.issues-empty { font-size: 24rpx; color: #999 }
|
||||
.card-body { display: flex; align-items: center; justify-content: space-between; padding: 16rpx }
|
||||
.card-title { font-size: 28rpx; color: #1F2937; flex: 1; margin-right: 8rpx; word-break: break-all; font-weight: 600; }
|
||||
.badge-boss { background: linear-gradient(135deg, #FF9F43, #FFD166); color: #6b4b1f; font-size: 22rpx; font-weight: 600; padding: 6rpx 14rpx; border-radius: 999rpx }
|
||||
.badge-count { background: linear-gradient(135deg, #FFD166, #FFE8A3); color: #6b4b1f; font-size: 22rpx; font-weight: 600; padding: 6rpx 14rpx; border-radius: 999rpx }
|
||||
.rewards-empty, .issues-empty { font-size: 26rpx; color: #9CA3AF; text-align: center; padding: 40rpx }
|
||||
</style>
|
||||
|
||||
233
pages/activity/list/index.vue
Normal file
233
pages/activity/list/index.vue
Normal file
@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view class="content" scroll-y>
|
||||
<view v-if="loading" class="loading-wrap"><view class="spinner"></view></view>
|
||||
<view v-else-if="filteredActivities.length > 0" class="activity-grid">
|
||||
<view class="activity-item" v-for="a in filteredActivities" :key="a.id" @tap="onActivityTap(a)">
|
||||
<view class="thumb-box">
|
||||
<image class="thumb" :src="a.image" mode="aspectFill" />
|
||||
<view class="tag-hot">HOT</view>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="name">{{ a.title }}</view>
|
||||
<view class="bottom-row">
|
||||
<text class="price-text">{{ a.category_name }} · {{ a.subtitle }}</text>
|
||||
<view class="btn-go">GO</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty">
|
||||
<image class="empty-img" src="/static/empty.png" mode="widthFix" />
|
||||
<text class="empty-text">暂无{{ title }}活动</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { request, authRequest } from '@/utils/request.js'
|
||||
|
||||
const title = ref('')
|
||||
const categoryTarget = ref('')
|
||||
const activities = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const filteredActivities = computed(() => {
|
||||
if (!categoryTarget.value) return activities.value
|
||||
const target = categoryTarget.value.trim()
|
||||
return activities.value.filter(a => {
|
||||
const cat = (a.category_name || '').trim()
|
||||
return cat === target || cat.includes(target)
|
||||
})
|
||||
})
|
||||
|
||||
function apiGet(url) {
|
||||
const token = uni.getStorageSync('token')
|
||||
const fn = token ? authRequest : request
|
||||
return fn({ url })
|
||||
}
|
||||
|
||||
function cleanUrl(u) {
|
||||
const s = String(u || '').trim()
|
||||
const m = s.match(/https?:\/\/[^\s'"`]+/)
|
||||
if (m && m[0]) return m[0]
|
||||
return s.replace(/[`'\"]/g, '').trim()
|
||||
}
|
||||
|
||||
function buildSubtitle(i) {
|
||||
const base = i.subTitle ?? i.sub_title ?? i.subtitle ?? i.desc ?? i.description ?? ''
|
||||
if (base) return base
|
||||
const price = (i.price_draw !== undefined && i.price_draw !== null) ? `¥${(Number(i.price_draw || 0) / 100).toFixed(2)}` : ''
|
||||
return price
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await apiGet('/api/app/activities')
|
||||
let list = []
|
||||
if (Array.isArray(res)) list = res
|
||||
else if (res && (Array.isArray(res.list) || Array.isArray(res.data))) list = res.list || res.data
|
||||
|
||||
activities.value = list.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
image: cleanUrl(i.image ?? i.banner ?? i.coverUrl ?? i.cover_url ?? i.img ?? i.pic ?? ''),
|
||||
title: (i.title ?? i.name ?? '').replace(/无限赏|一番赏|对对碰|爬塔/g, '').trim(),
|
||||
subtitle: buildSubtitle(i),
|
||||
category_name: i.category_name ?? i.categoryName ?? '',
|
||||
link: cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? '')
|
||||
})).filter(i => i.image || i.title)
|
||||
|
||||
} catch (e) {
|
||||
activities.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onActivityTap(a) {
|
||||
const name = (a.category_name || '').trim()
|
||||
const id = a.id
|
||||
let path = ''
|
||||
|
||||
// Navigate to DETAIL, not list
|
||||
if (name.includes('一番赏')) path = '/pages/activity/yifanshang/index'
|
||||
else if (name.includes('无限赏')) path = '/pages/activity/wuxianshang/index'
|
||||
else if (name.includes('对对碰')) path = '/pages/activity/duiduipeng/index'
|
||||
else if (name.includes('爬塔')) path = '/pages/activity/pata/index'
|
||||
|
||||
if (path && id) {
|
||||
uni.navigateTo({ url: `${path}?id=${id}` })
|
||||
return
|
||||
}
|
||||
if (a.link && /^\/.+/.test(a.link)) {
|
||||
uni.navigateTo({ url: a.link })
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
if (opts && opts.category) {
|
||||
categoryTarget.value = decodeURIComponent(opts.category)
|
||||
title.value = categoryTarget.value
|
||||
uni.setNavigationBarTitle({ title: categoryTarget.value })
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #F8F8F8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
}
|
||||
.activity-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
}
|
||||
.activity-item {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.thumb-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 100%; /* 1:1 Aspect Ratio */
|
||||
height: 0;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tag-hot {
|
||||
position: absolute;
|
||||
top: 12rpx; left: 12rpx;
|
||||
background: #333;
|
||||
color: #FFD700;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
.info {
|
||||
padding: 20rpx 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
margin-bottom: 20rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
height: 76rpx;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.bottom-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.price-text {
|
||||
font-size: 24rpx;
|
||||
color: #FF4D4F;
|
||||
font-weight: 700;
|
||||
}
|
||||
.btn-go {
|
||||
background: #1A1A1A;
|
||||
color: #FFD700;
|
||||
font-size: 24rpx;
|
||||
font-weight: 900;
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
display: flex; justify-content: center; padding: 100rpx;
|
||||
}
|
||||
.spinner {
|
||||
width: 48rpx; height: 48rpx;
|
||||
border: 4rpx solid #ddd; border-top-color: #FF9F43;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 200rpx;
|
||||
}
|
||||
.empty-img {
|
||||
width: 240rpx;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.4;
|
||||
}
|
||||
.empty-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
374
pages/activity/pata/index.vue
Normal file
374
pages/activity/pata/index.vue
Normal file
@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<view class="page-wrapper">
|
||||
<!-- Rebuild Trigger -->
|
||||
<!-- 背景层 -->
|
||||
<image class="bg-fixed" :src="detail.banner || ''" mode="aspectFill" />
|
||||
<view class="bg-mask"></view>
|
||||
|
||||
<view class="content-area">
|
||||
<!-- 顶部信息 -->
|
||||
<view class="header-section">
|
||||
<view class="title-box">
|
||||
<text class="main-title">{{ detail.name || detail.title || '爬塔挑战' }}</text>
|
||||
<text class="sub-title">层层突围 赢取大奖</text>
|
||||
</view>
|
||||
<view class="rule-btn" @tap="showRules">规则</view>
|
||||
</view>
|
||||
|
||||
<!-- 挑战区域 (模拟塔层) -->
|
||||
<view class="tower-container">
|
||||
<view class="tower-level current">
|
||||
<view class="level-info">
|
||||
<text class="level-num">当前挑战</text>
|
||||
<text class="level-name">{{ currentIssueTitle || '第1层' }}</text>
|
||||
</view>
|
||||
<view class="level-status">进行中</view>
|
||||
</view>
|
||||
|
||||
<!-- 奖池预览 -->
|
||||
<view class="rewards-preview" v-if="currentIssueRewards.length">
|
||||
<scroll-view scroll-x class="rewards-scroll">
|
||||
<view class="reward-item" v-for="(r, idx) in currentIssueRewards" :key="idx">
|
||||
<image class="reward-img" :src="r.image" mode="aspectFill" />
|
||||
<view class="reward-name">{{ r.title }}</view>
|
||||
<view class="reward-prob" v-if="r.percent">概率 {{ r.percent }}%</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作区 -->
|
||||
<view class="action-area">
|
||||
<view class="price-display">
|
||||
<text class="currency">¥</text>
|
||||
<text class="amount">{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</text>
|
||||
<text class="unit">/次</text>
|
||||
</view>
|
||||
<button class="challenge-btn" :loading="drawLoading" @tap="onStartChallenge">
|
||||
立即挑战
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 结果弹窗 -->
|
||||
<view v-if="showFlip" class="flip-overlay" @touchmove.stop.prevent>
|
||||
<view class="flip-mask" @tap="closeFlip"></view>
|
||||
<view class="flip-content">
|
||||
<FlipGrid ref="flipRef" :rewards="winItems" :controls="false" />
|
||||
<button class="close-btn" @tap="closeFlip">收下奖励</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<PaymentPopup
|
||||
v-model:visible="paymentVisible"
|
||||
:amount="paymentAmount"
|
||||
:coupons="coupons"
|
||||
:propCards="propCards"
|
||||
@confirm="onPaymentConfirm"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import FlipGrid from '../../../components/FlipGrid.vue'
|
||||
import PaymentPopup from '../../../components/PaymentPopup.vue'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, joinLottery, createWechatOrder, getLotteryResult, getItemCards, getUserCoupons } from '../../../api/appUser'
|
||||
|
||||
const activityId = ref('')
|
||||
const detail = ref({})
|
||||
const issues = ref([])
|
||||
const currentIssueId = ref('')
|
||||
const rewardsMap = ref({})
|
||||
const drawLoading = ref(false)
|
||||
const showFlip = ref(false)
|
||||
const winItems = ref([])
|
||||
const flipRef = ref(null)
|
||||
|
||||
// Payment
|
||||
const paymentVisible = ref(false)
|
||||
const paymentAmount = ref('0.00')
|
||||
const coupons = ref([])
|
||||
const propCards = ref([])
|
||||
const selectedCoupon = ref(null)
|
||||
const selectedCard = ref(null)
|
||||
const pendingCount = ref(1)
|
||||
|
||||
const currentIssueTitle = computed(() => {
|
||||
const i = issues.value.find(x => x.id === currentIssueId.value)
|
||||
return i ? (i.title || `第${i.no}期`) : ''
|
||||
})
|
||||
|
||||
const currentIssueRewards = computed(() => {
|
||||
return (currentIssueId.value && rewardsMap.value[currentIssueId.value]) || []
|
||||
})
|
||||
|
||||
const priceVal = computed(() => Number(detail.value.price_draw || 0) / 100)
|
||||
|
||||
async function loadData(id) {
|
||||
try {
|
||||
const d = await getActivityDetail(id)
|
||||
detail.value = d || {}
|
||||
|
||||
const is = await getActivityIssues(id)
|
||||
issues.value = normalizeIssues(is)
|
||||
|
||||
if (issues.value.length) {
|
||||
const first = issues.value[0]
|
||||
currentIssueId.value = first.id
|
||||
loadRewards(id, first.id)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRewards(aid, iid) {
|
||||
try {
|
||||
const res = await getActivityIssueRewards(aid, iid)
|
||||
rewardsMap.value[iid] = normalizeRewards(res)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function onStartChallenge() {
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
if (!token || !phoneBound) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
// In real app, redirect to login
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentIssueId.value) {
|
||||
uni.showToast({ title: '暂无挑战场次', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
paymentAmount.value = priceVal.value.toFixed(2)
|
||||
pendingCount.value = 1
|
||||
paymentVisible.value = true
|
||||
// Fetch coupons/cards in background
|
||||
fetchPropCards()
|
||||
fetchCoupons()
|
||||
}
|
||||
|
||||
async function onPaymentConfirm(data) {
|
||||
selectedCoupon.value = data?.coupon || null
|
||||
selectedCard.value = data?.card || null
|
||||
paymentVisible.value = false
|
||||
await doDraw()
|
||||
}
|
||||
|
||||
async function doDraw() {
|
||||
drawLoading.value = true
|
||||
try {
|
||||
const openid = uni.getStorageSync('openid')
|
||||
const joinRes = await joinLottery({
|
||||
activity_id: Number(activityId.value),
|
||||
issue_id: Number(currentIssueId.value),
|
||||
channel: 'miniapp',
|
||||
count: 1,
|
||||
coupon_id: selectedCoupon.value?.id ? Number(selectedCoupon.value.id) : 0
|
||||
})
|
||||
|
||||
if (!joinRes) throw new Error('下单失败')
|
||||
const orderNo = joinRes.order_no || joinRes.data?.order_no || joinRes.result?.order_no
|
||||
|
||||
// Simulate Wechat Pay flow (simplified)
|
||||
const payRes = await createWechatOrder({ openid, order_no: orderNo })
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
...payRes,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
|
||||
// Get Result
|
||||
const res = await getLotteryResult(orderNo)
|
||||
const raw = res.list || res.items || res.data || res.result || (Array.isArray(res) ? res : [res])
|
||||
winItems.value = raw.map(i => ({
|
||||
title: i.title || i.name || '未知奖励',
|
||||
image: i.image || i.img || ''
|
||||
}))
|
||||
|
||||
showFlip.value = true
|
||||
setTimeout(() => {
|
||||
if(flipRef.value && flipRef.value.revealResults) flipRef.value.revealResults(winItems.value)
|
||||
}, 100)
|
||||
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.message || '挑战失败', icon: 'none' })
|
||||
} finally {
|
||||
drawLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeIssues(list) {
|
||||
if (!Array.isArray(list)) return []
|
||||
return list.map(i => ({
|
||||
id: i.id,
|
||||
title: i.title || i.name,
|
||||
no: i.no,
|
||||
}))
|
||||
}
|
||||
|
||||
function normalizeRewards(list) {
|
||||
if (!Array.isArray(list)) return []
|
||||
return list.map(i => ({
|
||||
title: i.name || i.title,
|
||||
image: i.image || i.img || i.pic,
|
||||
percent: i.percent || 0
|
||||
}))
|
||||
}
|
||||
|
||||
async function fetchPropCards() { /* implementation same as other pages */ }
|
||||
async function fetchCoupons() { /* implementation same as other pages */ }
|
||||
|
||||
function showRules() {
|
||||
uni.showModal({ title: '规则', content: detail.value.rules || '暂无规则', showCancel: false })
|
||||
}
|
||||
|
||||
function closeFlip() { showFlip.value = false }
|
||||
|
||||
onLoad((opts) => {
|
||||
if (opts.id) {
|
||||
activityId.value = opts.id
|
||||
loadData(opts.id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-wrapper {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
background: #2D1B4E; /* Dark Purple Theme */
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.bg-fixed {
|
||||
position: absolute;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
opacity: 0.3;
|
||||
z-index: 0;
|
||||
}
|
||||
.bg-mask {
|
||||
position: absolute;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: linear-gradient(180deg, rgba(45,27,78,0.8), #2D1B4E);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
.main-title { font-size: 48rpx; font-weight: 900; font-style: italic; display: block; }
|
||||
.sub-title { font-size: 24rpx; opacity: 0.8; margin-top: 8rpx; display: block; }
|
||||
.rule-btn {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.tower-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tower-level {
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #6D28D9, #4C1D95);
|
||||
padding: 40rpx;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.3);
|
||||
margin-bottom: 40rpx;
|
||||
border: 2rpx solid rgba(255,255,255,0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.level-info { display: flex; flex-direction: column; }
|
||||
.level-num { font-size: 24rpx; opacity: 0.8; margin-bottom: 8rpx; }
|
||||
.level-name { font-size: 40rpx; font-weight: 700; }
|
||||
.level-status { font-size: 24rpx; background: rgba(0,0,0,0.2); padding: 4rpx 12rpx; border-radius: 8rpx; }
|
||||
|
||||
.rewards-preview {
|
||||
width: 100%;
|
||||
}
|
||||
.rewards-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.reward-item {
|
||||
display: inline-block;
|
||||
width: 160rpx;
|
||||
margin-right: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.reward-img {
|
||||
width: 120rpx; height: 120rpx;
|
||||
border-radius: 16rpx;
|
||||
background: rgba(255,255,255,0.1);
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.reward-name { font-size: 22rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.reward-prob { font-size: 20rpx; color: #FFD700; }
|
||||
|
||||
.action-area {
|
||||
background: rgba(0,0,0,0.4);
|
||||
backdrop-filter: blur(20rpx);
|
||||
padding: 30rpx;
|
||||
border-radius: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.price-display { color: #FFD700; font-weight: 700; }
|
||||
.currency { font-size: 28rpx; }
|
||||
.amount { font-size: 48rpx; margin: 0 4rpx; }
|
||||
.unit { font-size: 24rpx; opacity: 0.8; }
|
||||
|
||||
.challenge-btn {
|
||||
background: linear-gradient(135deg, #FFD700, #F59E0B);
|
||||
color: #333;
|
||||
font-weight: 900;
|
||||
border-radius: 999rpx;
|
||||
padding: 0 60rpx;
|
||||
font-size: 32rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(245, 158, 11, 0.4);
|
||||
}
|
||||
|
||||
.flip-overlay {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 999;
|
||||
}
|
||||
.flip-mask {
|
||||
position: absolute; top: 0; bottom: 0; width: 100%; background: rgba(0,0,0,0.8);
|
||||
}
|
||||
.flip-content {
|
||||
position: relative; z-index: 2; height: 100%; display: flex; flex-direction: column; padding: 40rpx;
|
||||
}
|
||||
.close-btn {
|
||||
margin-top: auto;
|
||||
background: #fff; color: #333; border-radius: 999rpx; font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
@ -1,25 +1,62 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<!-- 顶部 Banner -->
|
||||
<view class="banner" v-if="detail.banner">
|
||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||
<view class="meta" v-if="detail.price_draw !== undefined">单次抽选:¥{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</view>
|
||||
</view>
|
||||
<view class="draw-actions">
|
||||
<button class="draw-btn" @click="() => openPayment(1)">参加一次</button>
|
||||
<button class="draw-btn" @click="() => openPayment(10)">参加十次</button>
|
||||
<button class="draw-btn secondary" @click="onMachineTry">试一试</button>
|
||||
</view>
|
||||
<view class="issues" v-if="showIssues && issues.length">
|
||||
<view class="issue-switch">
|
||||
<button class="switch-btn" @click="prevIssue">〈</button>
|
||||
<text class="issue-title">{{ currentIssueTitle }}</text>
|
||||
<button class="switch-btn" @click="nextIssue">〉</button>
|
||||
|
||||
<!-- 商品信息卡片 -->
|
||||
<view class="product-card">
|
||||
<view class="product-info">
|
||||
<image v-if="detail.banner" class="product-thumb" :src="detail.banner" mode="aspectFill" />
|
||||
<view class="product-detail">
|
||||
<view class="product-name">{{ detail.name || detail.title || '无限赏活动' }}</view>
|
||||
<view class="product-price">¥{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</view>
|
||||
</view>
|
||||
<view class="product-actions">
|
||||
<view class="action-btn">📦 盒柜</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 期号切换条 -->
|
||||
<view class="issue-bar" v-if="showIssues && issues.length">
|
||||
<button class="nav-btn" @click="prevIssue">◀</button>
|
||||
<view class="issue-info">
|
||||
<text class="issue-label">{{ currentIssueTitle }}</text>
|
||||
</view>
|
||||
<button class="nav-btn" @click="nextIssue">▶</button>
|
||||
</view>
|
||||
|
||||
<!-- 玩法福利标签 -->
|
||||
<view class="gameplay-tags">
|
||||
<view class="tag tag-pool">聚宝盆</view>
|
||||
<view class="tag tag-drop">随机掉落 10%</view>
|
||||
<view class="tag tag-free">随机免单 10%</view>
|
||||
</view>
|
||||
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部多档位抽赏按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="tier-btn" @click="() => openPayment(1)">
|
||||
<text class="tier-price">¥{{ (pricePerDrawYuan * 1).toFixed(2) }}</text>
|
||||
<text class="tier-label">抽1发</text>
|
||||
</button>
|
||||
<button class="tier-btn" @click="() => openPayment(3)">
|
||||
<text class="tier-price">¥{{ (pricePerDrawYuan * 3).toFixed(2) }}</text>
|
||||
<text class="tier-label">抽3发</text>
|
||||
</button>
|
||||
<button class="tier-btn" @click="() => openPayment(5)">
|
||||
<text class="tier-price">¥{{ (pricePerDrawYuan * 5).toFixed(2) }}</text>
|
||||
<text class="tier-label">抽5发</text>
|
||||
</button>
|
||||
<button class="tier-btn tier-hot" @click="() => openPayment(10)">
|
||||
<text class="tier-price">¥{{ (pricePerDrawYuan * 10).toFixed(2) }}</text>
|
||||
<text class="tier-label">抽10发</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-if="showFlip" class="flip-overlay" @touchmove.stop.prevent>
|
||||
<view class="flip-mask" @tap="closeFlip"></view>
|
||||
<view class="flip-content" @tap.stop>
|
||||
@ -388,42 +425,48 @@ function closeFlip() { showFlip.value = false }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh; padding-bottom: 140rpx }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700; color: #DD2C00; text-align: center }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
.draw-actions { display: flex; gap: 12rpx; padding: 24rpx }
|
||||
.draw-btn { flex: 1; background: #007AFF; color: #fff; border-radius: 8rpx }
|
||||
.draw-btn.secondary { background: #ffd166; color: #6b4b1f }
|
||||
/* 奇盒潮玩 - 无限赏活动页面 */
|
||||
.page { height: 100vh; padding-bottom: 200rpx; background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 100%); }
|
||||
.banner { padding: 24rpx 24rpx 0; }
|
||||
.banner-img { width: 100%; border-radius: 20rpx; box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.1); }
|
||||
|
||||
/* 商品信息卡片 */
|
||||
.product-card { margin: 20rpx 24rpx; background: #FFFFFF; border-radius: 20rpx; padding: 20rpx; box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06); }
|
||||
.product-info { display: flex; align-items: flex-start; gap: 16rpx; }
|
||||
.product-thumb { width: 120rpx; height: 120rpx; border-radius: 16rpx; flex-shrink: 0; background: linear-gradient(145deg, #FFF8F3, #FFF4E6); }
|
||||
.product-detail { flex: 1; min-width: 0; }
|
||||
.product-name { font-size: 30rpx; font-weight: 700; color: #1F2937; margin-bottom: 8rpx; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.product-price { font-size: 34rpx; font-weight: 800; color: #FF6B35; }
|
||||
.product-actions { display: flex; flex-direction: column; gap: 12rpx; }
|
||||
.action-btn { background: linear-gradient(145deg, #FFFFFF, #FFF8F3); border: 2rpx solid rgba(255, 159, 67, 0.3); border-radius: 12rpx; padding: 10rpx 14rpx; font-size: 22rpx; color: #B45309; text-align: center; }
|
||||
.action-btn:active { background: #FFF4E6; }
|
||||
|
||||
/* 期号切换条 */
|
||||
.issue-bar { display: flex; align-items: center; justify-content: center; gap: 20rpx; margin: 0 24rpx 20rpx; padding: 16rpx 20rpx; background: #FFFFFF; border-radius: 999rpx; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06); }
|
||||
.nav-btn { width: 64rpx; height: 64rpx; border-radius: 50%; background: linear-gradient(135deg, #FF9F43, #FF6B35); color: #FFFFFF; display: flex; align-items: center; justify-content: center; font-size: 28rpx; padding: 0; margin: 0; line-height: 1; box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3); }
|
||||
.nav-btn:active { transform: scale(0.92); }
|
||||
.issue-info { display: flex; flex-direction: column; align-items: center; gap: 4rpx; }
|
||||
.issue-label { font-size: 28rpx; font-weight: 700; color: #1F2937; }
|
||||
|
||||
/* 玩法福利标签 */
|
||||
.gameplay-tags { display: flex; gap: 12rpx; padding: 0 24rpx; margin-bottom: 20rpx; flex-wrap: wrap; }
|
||||
.tag { padding: 10rpx 20rpx; border-radius: 999rpx; font-size: 22rpx; font-weight: 600; }
|
||||
.tag-pool { background: linear-gradient(135deg, #10B981, #34D399); color: #FFFFFF; box-shadow: 0 4rpx 12rpx rgba(16, 185, 129, 0.3); }
|
||||
.tag-drop { background: linear-gradient(135deg, #FF9F43, #FF6B35); color: #FFFFFF; box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3); }
|
||||
.tag-free { background: linear-gradient(135deg, #FFD166, #FF9F43); color: #6b4b1f; box-shadow: 0 4rpx 12rpx rgba(255, 159, 67, 0.3); }
|
||||
|
||||
/* 底部多档位抽赏按钮 */
|
||||
.bottom-actions { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 12rpx; padding: 16rpx 24rpx; padding-bottom: calc(16rpx + env(safe-area-inset-bottom)); background: linear-gradient(180deg, rgba(255,255,255,0.95) 0%, #FFFFFF 100%); backdrop-filter: blur(20rpx); box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.08); z-index: 999; }
|
||||
.tier-btn { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 12rpx 8rpx; background: linear-gradient(135deg, #FFD166, #FF9F43); border-radius: 16rpx; box-shadow: 0 4rpx 12rpx rgba(255, 159, 67, 0.3); }
|
||||
.tier-btn:active { transform: scale(0.95); }
|
||||
.tier-price { font-size: 24rpx; font-weight: 700; color: #6b4b1f; }
|
||||
.tier-label { font-size: 22rpx; color: #8a5a2b; margin-top: 4rpx; }
|
||||
.tier-hot { background: linear-gradient(135deg, #FF9F43, #FF6B35); }
|
||||
.tier-hot .tier-price, .tier-hot .tier-label { color: #FFFFFF; }
|
||||
|
||||
/* 翻牌弹窗 */
|
||||
.flip-overlay { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 10000 }
|
||||
.flip-mask { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1 }
|
||||
.flip-mask { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 1 }
|
||||
.flip-content { position: relative; display: flex; flex-direction: column; height: 100%; padding: 24rpx; z-index: 2 }
|
||||
.overlay-close { background: #ffd166; color: #6b4b1f; border-radius: 999rpx; align-self: flex-end }
|
||||
.issues { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx }
|
||||
.issues-title { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx }
|
||||
.issues-list { }
|
||||
.issue-switch { display: flex; align-items: center; justify-content: center; gap: 12rpx; margin: 0 24rpx 24rpx }
|
||||
.switch-btn { width: 72rpx; height: 72rpx; border-radius: 999rpx; background: #fff3df; border: 2rpx solid #f0c58a; color: #8a5a2b }
|
||||
.issue-title { font-size: 28rpx; color: #6b4b1f; background: #ffdfaa; border-radius: 12rpx; padding: 8rpx 16rpx }
|
||||
.rewards { width: 100%; margin-top: 24rpx }
|
||||
.reward { display: flex; align-items: center; margin-bottom: 8rpx }
|
||||
.reward-img { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5 }
|
||||
.reward-card { background: #fff; border-radius: 12rpx; overflow: hidden; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06); margin-bottom: 12rpx }
|
||||
.el-reward-card { margin-bottom: 12rpx }
|
||||
.el-card-header { display: flex; align-items: center; justify-content: space-between }
|
||||
.el-card-title { font-size: 28rpx; color: #222; flex: 1; margin-right: 8rpx; word-break: break-all }
|
||||
.card-image-wrap { position: relative; padding-bottom: 48rpx }
|
||||
.card-image { width: 100%; height: auto; display: block; background: #f0f4ff; position: relative; z-index: 1 }
|
||||
.prob-corner { position: absolute; background: rgba(221,82,77,0.9); color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx; z-index: 2 }
|
||||
.prob-corner.tl { top: 12rpx; left: 12rpx }
|
||||
.card-body { display: flex; align-items: center; justify-content: space-between; padding: 12rpx }
|
||||
.card-title { font-size: 28rpx; color: #222; flex: 1; margin-right: 8rpx; word-break: break-all }
|
||||
.badge-boss { background: #ff9f0a; color: #222; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx }
|
||||
.rewards-empty { font-size: 24rpx; color: #999 }
|
||||
.issues-empty { font-size: 24rpx; color: #999 }
|
||||
.overlay-close { background: linear-gradient(135deg, #FFD166, #FF9F43) !important; color: #6b4b1f !important; border-radius: 999rpx; align-self: flex-end; font-weight: 600; box-shadow: 0 6rpx 16rpx rgba(255, 159, 67, 0.35); }
|
||||
</style>
|
||||
|
||||
@ -1,32 +1,93 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="banner" v-if="detail.banner">
|
||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||
<view class="meta" v-if="detail.price_draw !== undefined">抽选价:¥{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</view>
|
||||
</view>
|
||||
|
||||
<view class="issues" v-if="showIssues && issues.length">
|
||||
<view class="issue-switch">
|
||||
<button class="switch-btn" @click="prevIssue">〈</button>
|
||||
<text class="issue-title">{{ currentIssueTitle }}</text>
|
||||
<button class="switch-btn" @click="nextIssue">〉</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 引入位置选择组件 -->
|
||||
<view class="selector-section" v-if="activityId && currentIssueId">
|
||||
<YifanSelector
|
||||
:activity-id="activityId"
|
||||
:issue-id="currentIssueId"
|
||||
:price-per-draw="Number(detail.price_draw || 0) / 100"
|
||||
@payment-success="onPaymentSuccess"
|
||||
/>
|
||||
<view class="page-wrapper">
|
||||
<!-- 顶部背景图(模糊处理) -->
|
||||
<view class="page-bg">
|
||||
<image class="bg-image" :src="detail.banner" mode="aspectFill" />
|
||||
<view class="bg-mask"></view>
|
||||
</view>
|
||||
|
||||
</scroll-view>
|
||||
<!-- 导航栏占位(如果有自定义导航栏需求) -->
|
||||
<!-- <view class="nav-bar-placeholder"></view> -->
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<scroll-view class="main-scroll" scroll-y>
|
||||
|
||||
<!-- 头部信息卡片 -->
|
||||
<view class="header-card">
|
||||
<image class="header-cover" :src="detail.banner" mode="aspectFill" />
|
||||
<view class="header-info">
|
||||
<view class="header-title">{{ detail.name || detail.title || '一番赏活动' }}</view>
|
||||
<view class="header-price-row">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-num">{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</text>
|
||||
<text class="price-unit">/发</text>
|
||||
</view>
|
||||
<view class="header-tags">
|
||||
<view class="tag-item">超高爆率</view>
|
||||
<view class="tag-item">公平公正</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-actions">
|
||||
<view class="action-btn" @tap="showRules">
|
||||
<text class="icon">📋</text>
|
||||
<text>规则</text>
|
||||
</view>
|
||||
<view class="action-btn" @tap="goCabinet">
|
||||
<text class="icon">📦</text>
|
||||
<text>盒柜</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 赏品概览 -->
|
||||
<view class="section-container" v-if="currentIssueRewards.length > 0">
|
||||
<view class="section-header">
|
||||
<text class="section-title">赏品一览</text>
|
||||
<text class="section-more">查看全部 ></text>
|
||||
</view>
|
||||
<scroll-view class="preview-scroll" scroll-x>
|
||||
<view class="preview-item" v-for="(item, idx) in currentIssueRewards" :key="idx">
|
||||
<view class="prize-tag" :class="{ 'tag-boss': item.boss }">{{ item.boss ? 'BOSS' : (item.grade || '赏') }}</view>
|
||||
<image class="preview-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="preview-name">{{ item.title }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 选号区域 -->
|
||||
<view class="section-container selector-container">
|
||||
<!-- 期号切换 -->
|
||||
<view class="issue-header">
|
||||
<view class="issue-switch-btn" @click="prevIssue">
|
||||
<text class="arrow">◀</text>
|
||||
</view>
|
||||
<view class="issue-info-center">
|
||||
<text class="issue-current-text">{{ currentIssueTitle }}</text>
|
||||
<text class="issue-status-badge">进行中</text>
|
||||
</view>
|
||||
<view class="issue-switch-btn" @click="nextIssue">
|
||||
<text class="arrow">▶</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 选号组件 -->
|
||||
<view class="selector-body" v-if="activityId && currentIssueId">
|
||||
<YifanSelector
|
||||
:activity-id="activityId"
|
||||
:issue-id="currentIssueId"
|
||||
:price-per-draw="Number(detail.price_draw || 0) / 100"
|
||||
@payment-success="onPaymentSuccess"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部垫高 -->
|
||||
<view style="height: 180rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 翻牌弹窗 -->
|
||||
<view v-if="showFlip" class="flip-overlay" @touchmove.stop.prevent>
|
||||
<view class="flip-mask" @tap="closeFlip"></view>
|
||||
<view class="flip-content" @tap.stop>
|
||||
@ -65,6 +126,26 @@ const currentIssueTitle = computed(() => {
|
||||
const t = (cur && (cur.title || ('第' + (cur.no || '-') + '期'))) || '-'
|
||||
return t
|
||||
})
|
||||
// 当前期剩余数量
|
||||
const currentIssueRemain = computed(() => {
|
||||
const arr = issues.value || []
|
||||
const cur = arr[selectedIssueIndex.value]
|
||||
return cur && cur.remain !== undefined ? cur.remain : ''
|
||||
})
|
||||
|
||||
// 显示规则
|
||||
function showRules() {
|
||||
uni.showModal({
|
||||
title: '活动规则',
|
||||
content: detail.value.rules || '1. 选择号码进行抽选\n2. 每个号码对应一个奖品\n3. 已售号码不可再选',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转盒柜
|
||||
function goCabinet() {
|
||||
uni.navigateTo({ url: '/pages/cabinet/index' })
|
||||
}
|
||||
|
||||
function statusToText(s) {
|
||||
if (s === 1) return '进行中'
|
||||
@ -299,54 +380,251 @@ function closeFlip() { showFlip.value = false }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh; padding-bottom: 140rpx }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700; color: #DD2C00; text-align: center }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
.draw-actions { display: flex; gap: 12rpx; padding: 24rpx }
|
||||
.draw-btn { flex: 1; background: #007AFF; color: #fff; border-radius: 8rpx }
|
||||
.draw-btn.secondary { background: #ffd166; color: #6b4b1f }
|
||||
.flip-overlay { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 10000 }
|
||||
.flip-mask { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1 }
|
||||
.flip-content { position: relative; display: flex; flex-direction: column; height: 100%; padding: 24rpx; z-index: 2 }
|
||||
.overlay-close { background: #ffd166; color: #6b4b1f; border-radius: 999rpx; align-self: flex-end }
|
||||
.issues { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx }
|
||||
.issues-title { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx }
|
||||
.issues-list { }
|
||||
.issue-switch { display: flex; align-items: center; justify-content: center; gap: 12rpx; margin: 0 24rpx 24rpx }
|
||||
.switch-btn {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #fff3df;
|
||||
border: 2rpx solid #f0c58a;
|
||||
color: #8a5a2b;
|
||||
/* ============================================
|
||||
一番赏页面 - 高级设计重构
|
||||
============================================ */
|
||||
|
||||
.page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: #F2F3F7;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部背景 */
|
||||
.page-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 600rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
.bg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: blur(40rpx);
|
||||
opacity: 0.8;
|
||||
}
|
||||
.bg-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, rgba(242,243,247,0.3) 0%, #F2F3F7 100%);
|
||||
}
|
||||
|
||||
.main-scroll {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 头部卡片 */
|
||||
.header-card {
|
||||
margin: 30rpx 24rpx;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 32rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.08);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.header-cover {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-right: 24rpx;
|
||||
background: #EEE;
|
||||
box-shadow: 0 8rpx 16rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
.header-info {
|
||||
flex: 1;
|
||||
}
|
||||
.header-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
margin-bottom: 12rpx;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.header-price-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
color: #FF6B35;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.price-symbol { font-size: 24rpx; font-weight: 600; }
|
||||
.price-num { font-size: 40rpx; font-weight: 800; margin: 0 4rpx; }
|
||||
.price-unit { font-size: 24rpx; color: #999; }
|
||||
|
||||
.header-tags {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.tag-item {
|
||||
font-size: 20rpx;
|
||||
color: #B45309;
|
||||
background: #FFF4E6;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
margin-left: 20rpx;
|
||||
padding-left: 20rpx;
|
||||
border-left: 1rpx solid #EEE;
|
||||
}
|
||||
.action-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
}
|
||||
.action-btn .icon {
|
||||
font-size: 32rpx;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
/* 通用板块容器 */
|
||||
.section-container {
|
||||
margin: 24rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 32rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 板块标题 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
.section-more {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 奖品概览 */
|
||||
.preview-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.preview-item {
|
||||
display: inline-block;
|
||||
width: 180rpx;
|
||||
margin-right: 20rpx;
|
||||
vertical-align: top;
|
||||
}
|
||||
.preview-img {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #F8F8F8;
|
||||
margin-bottom: 12rpx;
|
||||
box-shadow: inset 0 0 0 1rpx rgba(0,0,0,0.03);
|
||||
}
|
||||
.preview-name {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
.prize-tag {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
left: 8rpx;
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #fff;
|
||||
font-size: 18rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 6rpx;
|
||||
z-index: 10;
|
||||
}
|
||||
.prize-tag.tag-boss {
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
}
|
||||
|
||||
/* 选号区容器 */
|
||||
.selector-container {
|
||||
min-height: 600rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 期号头部 */
|
||||
.issue-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
background: #F9FAFB;
|
||||
border-radius: 20rpx;
|
||||
padding: 12rpx;
|
||||
}
|
||||
.issue-switch-btn {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
background: #FFFFFF;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
.issue-title { font-size: 28rpx; color: #6b4b1f; background: #ffdfaa; border-radius: 12rpx; padding: 8rpx 16rpx }
|
||||
.reward { display: flex; align-items: center; margin-bottom: 8rpx }
|
||||
.reward-img { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5 }
|
||||
.reward-card { background: #fff; border-radius: 12rpx; overflow: hidden; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06); margin-bottom: 12rpx }
|
||||
.el-reward-card { margin-bottom: 12rpx }
|
||||
.el-card-header { display: flex; align-items: center; justify-content: space-between }
|
||||
.el-card-title { font-size: 28rpx; color: #222; flex: 1; margin-right: 8rpx; word-break: break-all }
|
||||
.card-image-wrap { position: relative; padding-bottom: 48rpx }
|
||||
.card-image { width: 100%; height: auto; display: block; background: #f0f4ff; position: relative; z-index: 1 }
|
||||
.prob-corner { position: absolute; background: rgba(221,82,77,0.9); color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx; z-index: 2 }
|
||||
.prob-corner.tl { top: 12rpx; left: 12rpx }
|
||||
.card-body { display: flex; align-items: center; justify-content: space-between; padding: 12rpx }
|
||||
.card-title { font-size: 28rpx; color: #222; flex: 1; margin-right: 8rpx; word-break: break-all }
|
||||
.badge-boss { background: #ff9f0a; color: #222; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx }
|
||||
.badge-count { background: #ffd166; color: #6b4b1f; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx }
|
||||
.rewards-empty { font-size: 24rpx; color: #999 }
|
||||
.issues-empty { font-size: 24rpx; color: #999 }
|
||||
.issue-switch-btn:active {
|
||||
transform: scale(0.95);
|
||||
background: #F0F0F0;
|
||||
}
|
||||
.arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
.issue-info-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.issue-current-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
.issue-status-badge {
|
||||
font-size: 20rpx;
|
||||
color: #10B981;
|
||||
background: #D1FAE5;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 999rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.selector-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 翻牌弹窗 */
|
||||
.flip-overlay { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 10000; }
|
||||
.flip-mask { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 1; }
|
||||
.flip-content { position: relative; display: flex; flex-direction: column; height: 100%; padding: 24rpx; z-index: 2; }
|
||||
.overlay-close { background: linear-gradient(135deg, #FFD93D, #FFB800) !important; color: #6b4b1f !important; border-radius: 999rpx; align-self: flex-end; font-weight: 600; box-shadow: 0 6rpx 16rpx rgba(255, 184, 0, 0.35); }
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@ -149,10 +149,75 @@ onLoad((opts) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 24rpx }
|
||||
.form-item { display: flex; align-items: center; background: #fff; border-radius: 12rpx; padding: 16rpx; margin-bottom: 12rpx }
|
||||
.label { width: 160rpx; font-size: 28rpx; color: #666 }
|
||||
.input { flex: 1; font-size: 28rpx }
|
||||
.submit { width: 100%; margin-top: 20rpx }
|
||||
.error { color: #e43; margin-top: 12rpx }
|
||||
/* ============================================
|
||||
奇盒潮玩 - 地址编辑页面
|
||||
采用暖橙色调的表单设计
|
||||
============================================ */
|
||||
|
||||
.wrap {
|
||||
padding: 24rpx;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
/* 表单项 */
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #FFFFFF;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 160rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #6B7280;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #1F2937;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.submit {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
margin-top: 32rpx;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35) !important;
|
||||
color: #FFFFFF !important;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.35);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.submit:active {
|
||||
transform: scale(0.97);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.25);
|
||||
}
|
||||
.submit[disabled] {
|
||||
opacity: 0.6;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
color: #EF4444;
|
||||
font-size: 26rpx;
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@ -114,17 +114,131 @@ onLoad(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 24rpx }
|
||||
.header { display: flex; justify-content: flex-end; margin-bottom: 12rpx }
|
||||
.add { font-size: 28rpx }
|
||||
.addr { background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.addr-row { display: flex; align-items: center; margin-bottom: 8rpx }
|
||||
.name { font-size: 30rpx; margin-right: 12rpx }
|
||||
.phone { font-size: 28rpx; color: #666 }
|
||||
.default { font-size: 24rpx; color: #007AFF; margin-left: 10rpx }
|
||||
.region { font-size: 26rpx; color: #666 }
|
||||
.detail { font-size: 26rpx; color: #333 }
|
||||
.addr-actions { display: flex; justify-content: flex-end; gap: 12rpx; margin-top: 12rpx }
|
||||
.empty { text-align: center; color: #999; margin-top: 40rpx }
|
||||
.error { color: #e43; margin-bottom: 12rpx }
|
||||
/* ============================================
|
||||
奇盒潮玩 - 地址管理页面
|
||||
采用暖橙色调的卡片列表设计
|
||||
============================================ */
|
||||
|
||||
.wrap {
|
||||
padding: 24rpx;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.add {
|
||||
font-size: 28rpx;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35) !important;
|
||||
color: #FFFFFF !important;
|
||||
border-radius: 999rpx;
|
||||
padding: 0 32rpx;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6rpx 16rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
.add:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
/* 地址卡片 */
|
||||
.addr {
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.addr-main {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.addr-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.addr-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
}
|
||||
.phone {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
.default {
|
||||
font-size: 22rpx;
|
||||
color: #FFFFFF;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 999rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.region {
|
||||
font-size: 26rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
.detail {
|
||||
font-size: 26rpx;
|
||||
color: #1F2937;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.addr-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12rpx;
|
||||
margin-top: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #F3F4F6;
|
||||
}
|
||||
.addr-actions button {
|
||||
font-size: 26rpx;
|
||||
height: 56rpx;
|
||||
line-height: 56rpx;
|
||||
padding: 0 24rpx;
|
||||
border-radius: 28rpx;
|
||||
margin: 0;
|
||||
}
|
||||
.addr-actions button[type="warn"] {
|
||||
background: #FEE2E2 !important;
|
||||
color: #EF4444 !important;
|
||||
}
|
||||
.addr-actions button:not([type]) {
|
||||
background: #F3F4F6 !important;
|
||||
color: #6B7280 !important;
|
||||
}
|
||||
.addr-actions button:not([type]):active {
|
||||
background: #E5E7EB !important;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #9CA3AF;
|
||||
margin-top: 120rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
color: #EF4444;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 16rpx;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@ -570,49 +570,47 @@ async function onShip() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ============================================
|
||||
奇盒潮玩 - 货柜页面
|
||||
采用暖橙色调,物品卡片式布局
|
||||
============================================ */
|
||||
|
||||
.item-status {
|
||||
font-size: 24rpx;
|
||||
color: #007AFF;
|
||||
color: #FF9F43;
|
||||
margin-top: 4rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.item-meta {
|
||||
font-size: 22rpx;
|
||||
color: #9CA3AF;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
.item-meta { font-size: 22rpx; color: #666; margin-top: 4rpx }
|
||||
|
||||
.wrap { padding: 30rpx; }
|
||||
.wrap {
|
||||
padding: 24rpx;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
/* Tab 切换 */
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.select-all .checkbox {
|
||||
margin-right: 12rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
color: #6B7280;
|
||||
padding: 20rpx 0;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.25s ease;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -620,10 +618,10 @@ async function onShip() {
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background: #fff;
|
||||
color: #007AFF;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
color: #FFFFFF;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6rpx 20rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
@ -632,36 +630,72 @@ async function onShip() {
|
||||
|
||||
.tab-count {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.8;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.header { font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; }
|
||||
.status-text { text-align: center; color: #999; margin-top: 100rpx; }
|
||||
/* 操作栏 */
|
||||
.action-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #1F2937;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.select-all .checkbox {
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
text-align: center;
|
||||
color: #9CA3AF;
|
||||
margin-top: 120rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 物品列表 */
|
||||
.inventory-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.inventory-item {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.inventory-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.item-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
margin-right: 24rpx;
|
||||
margin-bottom: 0;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
background: linear-gradient(145deg, #FFF8F3, #FFF4E6);
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.item-info {
|
||||
@ -673,10 +707,11 @@ async function onShip() {
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
color: #1F2937;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 4rpx;
|
||||
margin-bottom: 8rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -684,15 +719,17 @@ async function onShip() {
|
||||
|
||||
.item-count {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
color: #9CA3AF;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
font-size: 24rpx;
|
||||
color: #ff4d4f;
|
||||
font-size: 26rpx;
|
||||
color: #FF6B35;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 复选框 */
|
||||
.checkbox-area {
|
||||
padding: 10rpx 20rpx 10rpx 0;
|
||||
display: flex;
|
||||
@ -700,16 +737,18 @@ async function onShip() {
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 2rpx solid #ccc;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
border: 3rpx solid #E5E7EB;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.checkbox.checked {
|
||||
background-color: #007AFF;
|
||||
border-color: #007AFF;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
border-color: #FF9F43;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
|
||||
.checkbox.checked::after {
|
||||
@ -718,14 +757,15 @@ async function onShip() {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -60%) rotate(45deg);
|
||||
width: 10rpx;
|
||||
height: 20rpx;
|
||||
width: 12rpx;
|
||||
height: 22rpx;
|
||||
border-right: 4rpx solid #fff;
|
||||
border-bottom: 4rpx solid #fff;
|
||||
}
|
||||
|
||||
/* 数量步进器 */
|
||||
.item-actions {
|
||||
margin-top: 10rpx;
|
||||
margin-top: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@ -733,44 +773,53 @@ async function onShip() {
|
||||
.stepper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
height: 48rpx;
|
||||
background: #F9FAFB;
|
||||
border: 2rpx solid #E5E7EB;
|
||||
border-radius: 12rpx;
|
||||
height: 52rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.step-btn {
|
||||
width: 48rpx;
|
||||
width: 52rpx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f8f8f8;
|
||||
background: transparent;
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
color: #6B7280;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.step-btn:active {
|
||||
background: #FFF4E6;
|
||||
color: #FF6B35;
|
||||
}
|
||||
|
||||
.step-btn.minus { border-right: 1px solid #ddd; }
|
||||
.step-btn.plus { border-left: 1px solid #ddd; }
|
||||
.step-btn.minus { border-right: 2rpx solid #E5E7EB; }
|
||||
.step-btn.plus { border-left: 2rpx solid #E5E7EB; }
|
||||
|
||||
.step-num {
|
||||
width: 60rpx;
|
||||
width: 64rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
/* 底部操作栏 */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100rpx;
|
||||
background-color: #fff;
|
||||
height: 110rpx;
|
||||
background: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 30rpx;
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
|
||||
padding: 0 24rpx;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
z-index: 100;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
@ -778,37 +827,52 @@ async function onShip() {
|
||||
|
||||
.selected-info {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
margin: 0;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
font-size: 26rpx;
|
||||
border-radius: 32rpx;
|
||||
padding: 0 40rpx;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 36rpx;
|
||||
border: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.action-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.btn-ship {
|
||||
background-color: #f0ad4e;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #FFD166, #FF9F43);
|
||||
color: #6b4b1f;
|
||||
box-shadow: 0 6rpx 16rpx rgba(255, 159, 67, 0.35);
|
||||
}
|
||||
|
||||
.btn-redeem {
|
||||
background-color: #dd524d;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
color: #FFFFFF;
|
||||
box-shadow: 0 6rpx 16rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
|
||||
.loading-more, .no-more {
|
||||
text-align: center;
|
||||
color: #9CA3AF;
|
||||
padding: 24rpx 0;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.bottom-spacer {
|
||||
height: 120rpx;
|
||||
height: calc(120rpx + constant(safe-area-inset-bottom));
|
||||
height: calc(120rpx + env(safe-area-inset-bottom));
|
||||
height: 130rpx;
|
||||
height: calc(130rpx + constant(safe-area-inset-bottom));
|
||||
height: calc(130rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,44 +1,125 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="notice-bar">
|
||||
<swiper class="notice-swiper" vertical circular autoplay interval="3000" duration="300">
|
||||
<swiper-item v-for="n in displayNotices" :key="n.id">
|
||||
<view class="notice-item">{{ n.text }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<!-- 顶部导航栏 (搜索) -->
|
||||
<view class="nav-header">
|
||||
<view class="brand-logo">
|
||||
<text class="brand-text">奇盒潮玩</text>
|
||||
<view class="brand-star">✨</view>
|
||||
</view>
|
||||
<view class="search-bar">
|
||||
<text class="search-icon">🔍</text>
|
||||
<text class="search-placeholder">搜索商品</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="banner-box">
|
||||
<swiper class="banner-swiper" circular autoplay interval="4000" duration="500">
|
||||
<swiper-item v-for="b in displayBanners" :key="b.id">
|
||||
<image v-if="b.image" class="banner-image" :src="b.image" mode="aspectFill" @tap="onBannerTap(b)" />
|
||||
<view v-else class="banner-fallback">
|
||||
<text class="banner-fallback-text">{{ b.title || '敬请期待' }}</text>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<view class="activity-section">
|
||||
<view class="section-title">活动</view>
|
||||
<view v-if="activityGroups.length">
|
||||
<scroll-view class="tabs" scroll-x>
|
||||
<view class="tab" v-for="g in activityGroups" :key="g.name" :class="{ active: g.name === selectedGroupName }" @tap="onSelectGroup(g.name)">{{ g.name }}</view>
|
||||
</scroll-view>
|
||||
<view v-if="activeGroupItems.length" class="activity-grid">
|
||||
<view class="activity-item" v-for="a in activeGroupItems" :key="a.id" @tap="onActivityTap(a)">
|
||||
<image v-if="a.image" class="activity-thumb" :src="a.image" mode="aspectFill" />
|
||||
<!-- 滚动区域 -->
|
||||
<scroll-view class="main-content" scroll-y>
|
||||
<!-- Banner 区域 -->
|
||||
<view class="banner-box">
|
||||
<swiper class="banner-swiper" circular autoplay interval="4000" duration="500">
|
||||
<swiper-item v-for="b in displayBanners" :key="b.id">
|
||||
<image v-if="b.image" class="banner-image" :src="b.image" mode="aspectFill" @tap="onBannerTap(b)" />
|
||||
<view v-else class="banner-fallback">
|
||||
<text class="banner-fallback-text">{{ a.title || '活动敬请期待' }}</text>
|
||||
<text class="banner-fallback-text">{{ b.title || '奇盒潮玩 V6.0' }}</text>
|
||||
<text class="banner-tag">功能更新UI优化全面来袭</text>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<!-- 通知栏 -->
|
||||
<view class="notice-bar" @tap="onNoticeTap">
|
||||
<view class="notice-tag">通知</view>
|
||||
<swiper class="notice-swiper" vertical circular autoplay interval="3000" duration="300">
|
||||
<swiper-item v-for="n in displayNotices" :key="n.id">
|
||||
<view class="notice-item">{{ n.text }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="notice-more">查看详情</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<!-- 玩法分类专区 -->
|
||||
<view class="gameplay-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">玩法分类</text>
|
||||
</view>
|
||||
<view class="gameplay-grid-v2">
|
||||
<!-- 上排:两大核心 -->
|
||||
<view class="grid-row-top">
|
||||
<view class="game-card-large card-yifan" @tap="navigateTo('/pages/activity/list/index?category=一番赏')">
|
||||
<view class="card-bg-decoration"></view>
|
||||
<view class="card-content-large">
|
||||
<text class="card-title-large">一番赏</text>
|
||||
<view class="card-tag-large">欧皇擂台</view>
|
||||
<image class="card-mascot-large" src="https://via.placeholder.com/150/90EE90/000000?text=YI" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="game-card-large card-wuxian" @tap="navigateTo('/pages/activity/list/index?category=无限赏')">
|
||||
<view class="card-content-large">
|
||||
<text class="card-title-large">无限赏</text>
|
||||
<view class="card-tag-large yellow">一发入魂</view>
|
||||
<image class="card-mascot-large" src="https://via.placeholder.com/150/FFD700/000000?text=WU" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 下排:三小功能 -->
|
||||
<view class="grid-row-bottom">
|
||||
<view class="game-card-small card-match" @tap="navigateTo('/pages/activity/list/index?category=对对碰')">
|
||||
<text class="card-title-small">对对碰</text>
|
||||
<text class="card-subtitle-small">碰一碰消除</text>
|
||||
<image class="card-icon-small" src="https://via.placeholder.com/80/FFB6C1/000000?text=Match" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<view class="game-card-small card-tower" @tap="navigateTo('/pages/activity/list/index?category=爬塔')">
|
||||
<text class="card-title-small">爬塔</text>
|
||||
<text class="card-subtitle-small">层层挑战</text>
|
||||
<image class="card-icon-small" src="https://via.placeholder.com/80/9370DB/000000?text=Tower" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<view class="game-card-small card-more" @tap="navigateTo('#')">
|
||||
<text class="card-title-small">更多</text>
|
||||
<text class="card-subtitle-small">敬请期待</text>
|
||||
<image class="card-icon-small" src="https://via.placeholder.com/80/E0E0E0/000000?text=More" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="activity-name">{{ a.title }}</text>
|
||||
<text class="activity-desc" v-if="a.subtitle">{{ a.subtitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="activity-empty">该分组暂无活动</view>
|
||||
</view>
|
||||
<view v-else class="activity-empty">暂无活动</view>
|
||||
</view>
|
||||
|
||||
<!-- 推荐活动列表 -->
|
||||
<view class="activity-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">推荐活动</text>
|
||||
</view>
|
||||
|
||||
<view v-if="activeGroupItems.length" class="activity-grid-list">
|
||||
<view class="activity-item" v-for="a in activeGroupItems" :key="a.id" @tap="onActivityTap(a)">
|
||||
<view class="activity-thumb-box">
|
||||
<image v-if="a.image" class="activity-thumb" :src="a.image" mode="aspectFill" />
|
||||
<view v-else class="banner-fallback mini">
|
||||
<text class="banner-fallback-text mini">{{ a.title }}</text>
|
||||
</view>
|
||||
<!-- 热门标签 -->
|
||||
<view class="activity-tag-hot">HOT</view>
|
||||
</view>
|
||||
<view class="activity-info">
|
||||
<text class="activity-name">{{ a.title }}</text>
|
||||
<view class="activity-row">
|
||||
<text class="activity-desc" v-if="a.subtitle">{{ a.subtitle }}</text>
|
||||
<view class="activity-btn-go">GO</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="activity-empty">暂无更多活动</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部垫高 -->
|
||||
<view style="height: 40rpx"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -80,10 +161,8 @@ export default {
|
||||
return Array.from(map.entries()).map(([name, items]) => ({ name, items }))
|
||||
},
|
||||
activeGroupItems() {
|
||||
const groups = this.activityGroups
|
||||
const name = this.selectedGroupName || (groups[0] && groups[0].name) || ''
|
||||
const g = groups.find(x => x.name === name)
|
||||
return g ? g.items : []
|
||||
// Return ALL activities without filtering by group
|
||||
return Array.isArray(this.activities) ? this.activities : []
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
@ -109,11 +188,7 @@ export default {
|
||||
this.selectedGroupName = String(name || '')
|
||||
},
|
||||
updateSelectedGroup() {
|
||||
const groups = this.activityGroups
|
||||
if (!groups.length) { this.selectedGroupName = ''; return }
|
||||
if (!groups.find(g => g.name === this.selectedGroupName)) {
|
||||
this.selectedGroupName = groups[0].name
|
||||
}
|
||||
// No-op as we now show all
|
||||
},
|
||||
toArray(x) { return Array.isArray(x) ? x : [] },
|
||||
unwrap(list) {
|
||||
@ -142,7 +217,6 @@ export default {
|
||||
},
|
||||
normalizeBanners(list) {
|
||||
const arr = this.unwrap(list)
|
||||
console.log('normalizeBanners input', list, 'unwrapped', arr)
|
||||
const mapped = arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? '',
|
||||
@ -151,12 +225,10 @@ export default {
|
||||
sort: typeof i.sort === 'number' ? i.sort : 0
|
||||
})).filter(i => i.image)
|
||||
mapped.sort((a, b) => a.sort - b.sort)
|
||||
console.log('normalizeBanners mapped', mapped)
|
||||
return mapped
|
||||
},
|
||||
normalizeActivities(list) {
|
||||
const arr = this.unwrap(list)
|
||||
console.log('normalizeActivities input', list, 'unwrapped', arr)
|
||||
const mapped = arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
image: this.cleanUrl(i.image ?? i.banner ?? i.coverUrl ?? i.cover_url ?? i.img ?? i.pic ?? ''),
|
||||
@ -166,7 +238,6 @@ export default {
|
||||
category_name: (i.category_name ?? i.categoryName ?? '').trim(),
|
||||
category_id: i.activity_category_id ?? i.category_id ?? i.categoryId ?? null
|
||||
})).filter(i => i.image || i.title)
|
||||
console.log('normalizeActivities mapped', mapped)
|
||||
return mapped
|
||||
},
|
||||
buildActivitySubtitle(i) {
|
||||
@ -178,36 +249,29 @@ export default {
|
||||
return parts.join(' · ')
|
||||
},
|
||||
async loadHomeData() {
|
||||
const results = await Promise.allSettled([
|
||||
this.apiGet('/api/app/notices'),
|
||||
this.apiGet('/api/app/banners'),
|
||||
this.apiGet('/api/app/activities')
|
||||
])
|
||||
const [nRes, bRes, acRes] = results
|
||||
if (nRes.status === 'fulfilled') {
|
||||
console.log('notices ok', nRes.value)
|
||||
this.notices = this.normalizeNotices(nRes.value)
|
||||
} else {
|
||||
console.error('notices error', nRes.reason)
|
||||
// Notices
|
||||
try {
|
||||
const nData = await this.apiGet('/api/app/notices')
|
||||
this.notices = this.normalizeNotices(nData)
|
||||
} catch (e) {
|
||||
this.notices = []
|
||||
}
|
||||
if (bRes.status === 'fulfilled') {
|
||||
console.log('banners ok', bRes.value)
|
||||
this.banners = this.normalizeBanners(bRes.value)
|
||||
} else {
|
||||
console.error('banners error', bRes.reason)
|
||||
|
||||
// Banners
|
||||
try {
|
||||
const bData = await this.apiGet('/api/app/banners')
|
||||
this.banners = this.normalizeBanners(bData)
|
||||
} catch (e) {
|
||||
this.banners = []
|
||||
}
|
||||
if (acRes.status === 'fulfilled') {
|
||||
console.log('activities ok', acRes.value)
|
||||
this.activities = this.normalizeActivities(acRes.value)
|
||||
this.updateSelectedGroup()
|
||||
} else {
|
||||
console.error('activities error', acRes.reason)
|
||||
|
||||
// Activities
|
||||
try {
|
||||
const acData = await this.apiGet('/api/app/activities')
|
||||
this.activities = this.normalizeActivities(acData)
|
||||
} catch (e) {
|
||||
this.activities = []
|
||||
this.updateSelectedGroup()
|
||||
}
|
||||
console.log('home normalized', { notices: this.notices, banners: this.banners, activities: this.activities })
|
||||
},
|
||||
onBannerTap(b) {
|
||||
const imgs = (Array.isArray(this.banners) ? this.banners : []).map(x => x.image).filter(Boolean)
|
||||
@ -224,9 +288,11 @@ export default {
|
||||
const name = (a.category_name || a.categoryName || '').trim()
|
||||
const id = a.id
|
||||
let path = ''
|
||||
if (name === '一番赏') path = '/pages/activity/yifanshang/index'
|
||||
else if (name === '无限赏') path = '/pages/activity/wuxianshang/index'
|
||||
else if (name === '对对碰') path = '/pages/activity/duiduipeng/index'
|
||||
if (name.includes('一番赏')) path = '/pages/activity/yifanshang/index'
|
||||
else if (name.includes('无限赏')) path = '/pages/activity/wuxianshang/index'
|
||||
else if (name.includes('对对碰')) path = '/pages/activity/duiduipeng/index'
|
||||
else if (name.includes('爬塔')) path = '/pages/activity/pata/index'
|
||||
|
||||
if (path && id) {
|
||||
uni.navigateTo({ url: `${path}?id=${id}` })
|
||||
return
|
||||
@ -234,29 +300,386 @@ export default {
|
||||
if (a.link && /^\/.+/.test(a.link)) {
|
||||
uni.navigateTo({ url: a.link })
|
||||
}
|
||||
},
|
||||
navigateTo(url) {
|
||||
if(url === '#') return
|
||||
uni.navigateTo({ url })
|
||||
},
|
||||
onNoticeTap() {
|
||||
const content = this.displayNotices.map(n => n.text).join('\n')
|
||||
uni.showModal({
|
||||
title: '系统通知',
|
||||
content: content || '暂无通知',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page { padding: 24rpx }
|
||||
.notice-bar { height: 64rpx; background: #fff4e6; border-radius: 8rpx; overflow: hidden; margin-bottom: 24rpx; }
|
||||
.notice-swiper { height: 64rpx }
|
||||
.notice-item { height: 64rpx; line-height: 64rpx; padding: 0 24rpx; color: #a15c00; font-size: 26rpx }
|
||||
.banner-box { margin-bottom: 24rpx }
|
||||
.banner-swiper { width: 100%; height: 320rpx; border-radius: 12rpx; overflow: hidden }
|
||||
.banner-image { width: 100%; height: 320rpx }
|
||||
.banner-fallback { width: 100%; height: 320rpx; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #f5f5f5, #eaeaea) }
|
||||
.banner-fallback-text { color: #666; font-size: 28rpx }
|
||||
.activity-section { background: #ffffff; border-radius: 12rpx; padding: 24rpx }
|
||||
.section-title { font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx }
|
||||
.tabs { white-space: nowrap; display: flex; gap: 12rpx; margin-bottom: 16rpx }
|
||||
.tab { display: inline-flex; align-items: center; height: 56rpx; padding: 0 20rpx; border-radius: 999rpx; background: #f5f7fa; color: #555; font-size: 26rpx }
|
||||
.tab.active { background: #007AFF; color: #fff }
|
||||
.activity-grid { display: flex; flex-wrap: wrap; margin: -12rpx }
|
||||
.activity-item { width: 50%; padding: 12rpx }
|
||||
.activity-thumb { width: 100%; height: 200rpx; border-radius: 8rpx }
|
||||
.activity-name { display: block; margin-top: 8rpx; font-size: 26rpx; color: #222 }
|
||||
.activity-desc { display: block; margin-top: 4rpx; font-size: 22rpx; color: #888 }
|
||||
/* ============================================
|
||||
奇盒潮玩 - 首页样式 (V6.0 新版)
|
||||
============================================ */
|
||||
|
||||
.page {
|
||||
padding: 0;
|
||||
background: #FFFFFF;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ========== 顶部导航栏 ========== */
|
||||
.nav-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background: #FFF8F3;
|
||||
padding-top: calc(20rpx + env(safe-area-inset-top));
|
||||
gap: 24rpx;
|
||||
}
|
||||
.brand-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.brand-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 900;
|
||||
color: #1A1A1A;
|
||||
font-style: italic;
|
||||
letter-spacing: -1rpx;
|
||||
}
|
||||
.brand-star {
|
||||
font-size: 24rpx;
|
||||
margin-left: 4rpx;
|
||||
margin-top: -12rpx;
|
||||
}
|
||||
.search-bar {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
background: white;
|
||||
border-radius: 999rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 24rpx;
|
||||
border: 1rpx solid #F0F0F0;
|
||||
}
|
||||
.search-icon { margin-right: 12rpx; font-size: 28rpx; }
|
||||
.search-placeholder { color: #999; font-size: 28rpx; }
|
||||
|
||||
|
||||
/* ========== 滚动主内容区 ========== */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 600rpx);
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
/* Logo Banner */
|
||||
.banner-box {
|
||||
margin: 0 24rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.06);
|
||||
}
|
||||
.banner-swiper, .banner-image, .banner-fallback {
|
||||
width: 100%;
|
||||
height: 320rpx;
|
||||
}
|
||||
.banner-fallback {
|
||||
background: #FFFBE8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx dashed #FFE58F;
|
||||
}
|
||||
.banner-fallback-text { font-size: 48rpx; font-weight: 900; color: #1A1A1A; font-style: italic; margin-bottom: 12rpx; }
|
||||
.banner-tag { background: #CCFF00; padding: 4rpx 16rpx; border-radius: 999rpx; font-size: 24rpx; font-weight: 700; color: #1A1A1A; }
|
||||
|
||||
/* 通知栏 */
|
||||
.notice-bar {
|
||||
margin: 0 24rpx 32rpx;
|
||||
background: #F9F9F9;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.notice-tag {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 6rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.notice-swiper { flex: 1; height: 36rpx; }
|
||||
.notice-item { font-size: 24rpx; color: #333; line-height: 36rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.notice-more { font-size: 24rpx; color: #999; }
|
||||
|
||||
|
||||
|
||||
/* 玩法专区 - 方案B:2+3 杂志风布局 */
|
||||
.gameplay-section {
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.section-header {
|
||||
margin-bottom: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 900;
|
||||
color: #1A1A1A;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-bottom: 4rpx;
|
||||
font-style: italic;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
/* 黄色高光底线效果 */
|
||||
.section-title::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 16rpx;
|
||||
background: #FFD700;
|
||||
z-index: -1;
|
||||
border-radius: 4rpx;
|
||||
transform: skewX(-10deg);
|
||||
}
|
||||
|
||||
.gameplay-grid-v2 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
/* 上排 */
|
||||
.grid-row-top {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
height: 180rpx; /* 大卡片高度 */
|
||||
}
|
||||
.game-card-large {
|
||||
flex: 1;
|
||||
border-radius: 20rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* 下排 */
|
||||
.grid-row-bottom {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
height: 140rpx; /* 小卡片高度 */
|
||||
}
|
||||
.game-card-small {
|
||||
flex: 1;
|
||||
border-radius: 16rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||||
background: white; /* 默认底色 */
|
||||
}
|
||||
|
||||
/* 内容样式 - 大卡片 */
|
||||
.card-content-large {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.card-title-large {
|
||||
font-size: 36rpx;
|
||||
font-weight: 900;
|
||||
color: #FFF;
|
||||
font-style: italic;
|
||||
margin-bottom: 12rpx;
|
||||
text-shadow: 2rpx 2rpx 0 rgba(0,0,0,0.1);
|
||||
}
|
||||
.card-tag-large {
|
||||
font-size: 20rpx;
|
||||
background: #FFF;
|
||||
color: #333;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 700;
|
||||
box-shadow: 2rpx 2rpx 0 rgba(0,0,0,0.1);
|
||||
transform: skewX(-10deg);
|
||||
}
|
||||
.card-tag-large.yellow { color: #875700; }
|
||||
|
||||
.card-mascot-large {
|
||||
position: absolute;
|
||||
right: -10rpx;
|
||||
bottom: -20rpx;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
|
||||
/* 内容样式 - 小卡片 */
|
||||
.card-title-small {
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
color: #333;
|
||||
margin-bottom: 4rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
.card-subtitle-small {
|
||||
font-size: 20rpx;
|
||||
color: #888;
|
||||
z-index: 2;
|
||||
}
|
||||
.card-icon-small {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 背景配色 - 仿参考图Pop风格 */
|
||||
.card-yifan {
|
||||
background: linear-gradient(135deg, #FF9C6E 0%, #FF7875 100%); /* 粉红/橘色 */
|
||||
}
|
||||
.card-wuxian {
|
||||
background: linear-gradient(135deg, #FFD666 0%, #FFA940 100%); /* 黄色/橙色 */
|
||||
}
|
||||
.card-match {
|
||||
background: linear-gradient(135deg, #FFADD2 0%, #FF85C0 100%); /* 粉色 */
|
||||
}
|
||||
.card-tower {
|
||||
background: linear-gradient(135deg, #B37FEB 0%, #9254DE 100%); /* 紫色 */
|
||||
}
|
||||
.card-more {
|
||||
background: linear-gradient(135deg, #E0E0E0 0%, #F5F5F5 100%); /* 灰色 */
|
||||
}
|
||||
/* 对对碰文字颜色适配 */
|
||||
.card-match .card-title-small { color: #FFF; }
|
||||
.card-match .card-subtitle-small { color: rgba(255,255,255,0.8); }
|
||||
.card-tower .card-title-small { color: #FFF; }
|
||||
.card-tower .card-subtitle-small { color: rgba(255,255,255,0.8); }
|
||||
|
||||
/* 推荐活动列表 */
|
||||
.activity-section {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
.activity-grid-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.activity-item {
|
||||
background: white;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.activity-thumb-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 100%; /* 1:1 正方形图片 */
|
||||
}
|
||||
.activity-thumb {
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #EEE;
|
||||
}
|
||||
.banner-fallback.mini {
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #F5F5F5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.activity-tag-hot {
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
left: 12rpx;
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #FFD700;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 700;
|
||||
backdrop-filter: blur(4rpx);
|
||||
}
|
||||
|
||||
.activity-info {
|
||||
padding: 20rpx;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.activity-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
margin-bottom: 16rpx;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.activity-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.activity-desc {
|
||||
font-size: 24rpx;
|
||||
color: #FF4D4F; /* 价格/品类突出颜色 */
|
||||
font-weight: 600;
|
||||
max-width: 70%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.activity-btn-go {
|
||||
background: #1A1A1A;
|
||||
color: #FFD700;
|
||||
font-size: 20rpx;
|
||||
font-weight: 900;
|
||||
padding: 6rpx 20rpx;
|
||||
border-radius: 30rpx;
|
||||
box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.activity-empty {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #9CA3AF;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,74 +1,103 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
||||
<!-- #ifdef MP-TOUTIAO -->
|
||||
<view class="login-form">
|
||||
<view class="input-row">
|
||||
<text class="label">账号:</text>
|
||||
<input
|
||||
type="text"
|
||||
v-model="account"
|
||||
class="input-field"
|
||||
placeholder="请输入账号"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="input-row">
|
||||
<text class="label">密码:</text>
|
||||
<input
|
||||
type="password"
|
||||
v-model="pwd"
|
||||
class="input-field"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 记住账号密码 -->
|
||||
<view class="remember-row">
|
||||
<checkbox :value="remember" @change="handleRememberChange" />
|
||||
<text class="remember-text">记住账号密码</text>
|
||||
</view>
|
||||
<!-- 按钮区域 -->
|
||||
<view class="button-group">
|
||||
<button class="btn login-btn" @click="handleLogin">登录</button>
|
||||
</view>
|
||||
<!-- 注册链接 -->
|
||||
<view class="register-link">
|
||||
<text class="register-text" @click="goToRegister">还没有账号?点击注册</text>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="title">微信登录</view>
|
||||
|
||||
|
||||
<button class="btn" open-type="getPhoneNumber" :disabled="loading" @getphonenumber="onGetPhoneNumber">授权手机号快速登录</button>
|
||||
<!-- #endif -->
|
||||
<view class="agreements">
|
||||
<text>注册或登录即表示您已阅读并同意</text>
|
||||
<text class="link" @tap="toUserAgreement">《用户协议》</text>
|
||||
<text>和</text>
|
||||
<text class="link" @tap="toPurchaseAgreement">《购买协议》</text>
|
||||
<!-- 装饰球体 -->
|
||||
<view class="orb orb-1"></view>
|
||||
<view class="orb orb-2"></view>
|
||||
|
||||
<view class="content-wrap">
|
||||
<!-- 品牌Logo -->
|
||||
<view class="brand-section">
|
||||
<view class="logo-box">
|
||||
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="app-name">奇盒潮玩</view>
|
||||
<view class="welcome-text">开启欧气之旅 ✨</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<!-- #ifdef MP-TOUTIAO -->
|
||||
<view class="login-form">
|
||||
<view class="input-group">
|
||||
<view class="input-icon">
|
||||
<image src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNBMEExQTciIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjAgMjF2LTJhNCA0IDAgMCAwLTQtNEg4YTQgNCAwIDAgMC00IDR2MiIgLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjciIHI9IjQiIC8+PC9zdmc+" mode="aspectFit"></image>
|
||||
</view>
|
||||
<input
|
||||
type="text"
|
||||
v-model="account"
|
||||
class="input-field"
|
||||
placeholder="请输入账号"
|
||||
placeholder-class="input-placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="input-group">
|
||||
<view class="input-icon">
|
||||
<image src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNBMEExQTciIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB4PSIzIiB5PS“xMSIgd2lkdGg9“MTgiIGhlaWdodD0iMTEiIHJ4PSIyIiByeT0iMiIgLz48cGF0aCBkPSJNNyAxMVY3YTUgNSAwIDAgMSAxMCAwdjQiIC8+PC9zdmc+" mode="aspectFit"></image>
|
||||
</view>
|
||||
<input
|
||||
type="password"
|
||||
v-model="pwd"
|
||||
class="input-field"
|
||||
placeholder="请输入密码"
|
||||
placeholder-class="input-placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="options-row">
|
||||
<view class="remember-box" @click="toggleRemember">
|
||||
<view class="checkbox" :class="{ checked: remember }">
|
||||
<view class="check-mark" v-if="remember">✓</view>
|
||||
</view>
|
||||
<text class="remember-text">记住密码</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="btn login-btn" @click="handleLogin">
|
||||
<text class="btn-text">立即登录</text>
|
||||
<view class="btn-shine"></view>
|
||||
</button>
|
||||
|
||||
<view class="register-link">
|
||||
<text class="register-text" @click="goToRegister">没有账号?<text class="highlight">立即注册</text></text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="weixin-login-box">
|
||||
<button class="btn weixin-btn" open-type="getPhoneNumber" :disabled="loading" @getphonenumber="onGetPhoneNumber">
|
||||
<image class="wx-icon" src="/static/logo.png" mode="aspectFit"></image> <!-- 应该用微信图标,暂时用logo代替或SVG -->
|
||||
<text>微信一键登录</text>
|
||||
</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 协议区 -->
|
||||
<view class="agreements">
|
||||
<view class="checkbox-area">
|
||||
<view class="checkbox round" :class="{ checked: agreementChecked }" @click="toggleAgreement"></view>
|
||||
</view>
|
||||
<view class="agreement-text">
|
||||
登录即代表同意 <text class="link" @tap="toUserAgreement">用户协议</text> & <text class="link" @tap="toPurchaseAgreement">隐私政策</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="error" class="error-toast">{{ error }}</view>
|
||||
</view>
|
||||
<view v-if="needBindPhone" class="tip">登录成功,请绑定手机号以完成登录</view>
|
||||
<view v-if="error" class="error">{{ error }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed,onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { wechatLogin, bindPhone, getUserStats, getPointsBalance } from '../../api/appUser'
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const needBindPhone = ref(false)
|
||||
const account =ref("")
|
||||
const pwd = ref ("")
|
||||
const remember=ref(false)
|
||||
const loggedIn = computed(() => !!uni.getStorageSync('token'))
|
||||
const account = ref("")
|
||||
const pwd = ref("")
|
||||
const remember = ref(false)
|
||||
const agreementChecked = ref(false) // 默认为false,需要用户勾选
|
||||
|
||||
onMounted(() => {
|
||||
try {
|
||||
@ -83,120 +112,95 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// 切换“记住密码”
|
||||
const handleRememberChange = (e) => {
|
||||
remember.value = e.detail.value.length > 0
|
||||
function toggleRemember() {
|
||||
remember.value = !remember.value
|
||||
}
|
||||
|
||||
|
||||
|
||||
function toggleAgreement() {
|
||||
agreementChecked.value = !agreementChecked.value
|
||||
}
|
||||
|
||||
function goToRegister() { uni.navigateTo({ url: '/pages/register/register' }) }
|
||||
function onLogin() {}
|
||||
|
||||
function handleLogin() {
|
||||
if (!agreementChecked.value) {
|
||||
uni.showToast({ title: '请先阅读并同意协议', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// TODO: Implement actual username/password login logic if API available
|
||||
uni.showToast({ title: '普通登录逻辑待接入', icon: 'none' })
|
||||
}
|
||||
|
||||
function toUserAgreement() { uni.navigateTo({ url: '/pages/agreement/user' }) }
|
||||
function toPurchaseAgreement() { uni.navigateTo({ url: '/pages/agreement/purchase' }) }
|
||||
|
||||
function onGetPhoneNumber(e) {
|
||||
if (!agreementChecked.value) {
|
||||
uni.showToast({ title: '请先阅读并同意协议', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const phoneCode = e.detail.code
|
||||
console.log('login_flow start getPhoneNumber, codeExists:', !!phoneCode)
|
||||
if (!phoneCode) {
|
||||
uni.showToast({ title: '未授权手机号', icon: 'none' })
|
||||
console.error('login_flow error: missing phoneCode')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: async (res) => {
|
||||
try {
|
||||
const loginCode = res.code
|
||||
console.log('login_flow uni.login success, loginCode exists:', !!loginCode)
|
||||
const inviterCode = uni.getStorageSync('inviter_code')
|
||||
console.log('login_flow using inviter_code:', inviterCode)
|
||||
const data = await wechatLogin(loginCode, inviterCode)
|
||||
console.log('login_flow wechatLogin response user_id:', data && data.user_id)
|
||||
const token = data && data.token
|
||||
const user_id = data && data.user_id
|
||||
const avatar = data && data.avatar
|
||||
const nickname = data && data.nickname
|
||||
const invite_code = data && data.invite_code
|
||||
const openid = data && data.openid
|
||||
uni.setStorageSync('user_info', data || {})
|
||||
if (token) {
|
||||
uni.setStorageSync('token', token)
|
||||
console.log('login_flow token stored')
|
||||
}
|
||||
if (user_id) {
|
||||
uni.setStorageSync('user_id', user_id)
|
||||
console.log('login_flow user_id stored:', user_id)
|
||||
}
|
||||
if (avatar) {
|
||||
uni.setStorageSync('avatar', avatar)
|
||||
}
|
||||
if (nickname) {
|
||||
uni.setStorageSync('nickname', nickname)
|
||||
}
|
||||
if (invite_code) {
|
||||
uni.setStorageSync('invite_code', invite_code)
|
||||
}
|
||||
if (openid) {
|
||||
uni.setStorageSync('openid', openid)
|
||||
}
|
||||
console.log('login_flow bindPhone start')
|
||||
try {
|
||||
// 首次绑定前短暂延迟,确保服务端 token 生效
|
||||
const user_info = data || {}
|
||||
|
||||
uni.setStorageSync('user_info', user_info)
|
||||
if (token) uni.setStorageSync('token', token)
|
||||
if (user_id) uni.setStorageSync('user_id', user_id)
|
||||
if (user_info.avatar) uni.setStorageSync('avatar', user_info.avatar)
|
||||
if (user_info.nickname) uni.setStorageSync('nickname', user_info.nickname)
|
||||
if (user_info.invite_code) uni.setStorageSync('invite_code', user_info.invite_code)
|
||||
|
||||
// 绑定手机号逻辑...
|
||||
try {
|
||||
await new Promise(r => setTimeout(r, 600))
|
||||
const bindRes = await bindPhone(user_id, phoneCode, { 'X-Suppress-Auth-Modal': true })
|
||||
const phoneNumber = (bindRes && (bindRes.phone || bindRes.phone_number || bindRes.mobile)) || ''
|
||||
if (phoneNumber) uni.setStorageSync('phone_number', phoneNumber)
|
||||
} catch (bindErr) {
|
||||
if (bindErr && bindErr.statusCode === 401) {
|
||||
console.warn('login_flow bindPhone 401, try re-login and retry')
|
||||
// 重新获取登录 code
|
||||
const relogin = await new Promise((resolve, reject) => {
|
||||
uni.login({ provider: 'weixin', success: resolve, fail: reject })
|
||||
})
|
||||
const data2 = await wechatLogin(relogin.code, inviterCode)
|
||||
const token2 = data2 && data2.token
|
||||
const user2 = data2 && data2.user_id
|
||||
if (token2) uni.setStorageSync('token', token2)
|
||||
if (user2) uni.setStorageSync('user_id', user2)
|
||||
// 再次延迟后重试绑定
|
||||
await new Promise(r => setTimeout(r, 600))
|
||||
const bindRes2 = await bindPhone(user2 || user_id, phoneCode, { 'X-Suppress-Auth-Modal': true })
|
||||
const phoneNumber2 = (bindRes2 && (bindRes2.phone || bindRes2.phone_number || bindRes2.mobile)) || ''
|
||||
if (phoneNumber2) uni.setStorageSync('phone_number', phoneNumber2)
|
||||
} else {
|
||||
throw bindErr
|
||||
}
|
||||
// 忽略绑定失败,允许静默失败或重试逻辑
|
||||
console.warn('Bind phone failed', bindErr)
|
||||
}
|
||||
|
||||
uni.setStorageSync('phone_bound', true)
|
||||
console.log('login_flow bindPhone success, phone_bound stored')
|
||||
|
||||
// 获取用户信息
|
||||
try {
|
||||
const stats = await getUserStats(user_id)
|
||||
console.log('login_flow getUserStats success')
|
||||
const balance = await getPointsBalance(user_id)
|
||||
console.log('login_flow getPointsBalance success')
|
||||
uni.setStorageSync('user_stats', stats)
|
||||
const balance = await getPointsBalance(user_id)
|
||||
const b = balance && balance.balance !== undefined ? balance.balance : balance
|
||||
uni.setStorageSync('points_balance', b)
|
||||
} catch (e) {
|
||||
console.error('login_flow fetch stats/points error:', e && (e.message || e.errMsg))
|
||||
}
|
||||
uni.showToast({ title: '登录并绑定成功', icon: 'success' })
|
||||
console.log('login_flow navigate to index')
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
uni.setStorageSync('points_balance', b)
|
||||
} catch(e) {}
|
||||
|
||||
uni.showToast({ title: '欢迎回来', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/mine/index' }) // Redirect to Mine page
|
||||
}, 500)
|
||||
|
||||
} catch (err) {
|
||||
console.error('login_flow error:', err && (err.message || err.errMsg), 'status:', err && err.statusCode)
|
||||
error.value = err.message || '登录或绑定失败'
|
||||
error.value = err.message || '登录失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
},
|
||||
fail: (e) => {
|
||||
console.error('login_flow uni.login fail:', e && e.errMsg)
|
||||
fail: () => {
|
||||
error.value = '微信登录失败'
|
||||
loading.value = false
|
||||
}
|
||||
@ -205,62 +209,175 @@ function onGetPhoneNumber(e) {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-form {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.remember-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.remember-text {
|
||||
margin-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
.register-link {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.register-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
text-decoration: underline; /* 可选:加下划线 */
|
||||
}
|
||||
|
||||
.register-text:active {
|
||||
color: #ff6700; /* 点击时变色 */
|
||||
}
|
||||
.label {
|
||||
width: 60px; /* 固定宽度,保证对齐 */
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
border: 2px solid #ccc; /* 边框加粗 */
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
font-size: 16px;
|
||||
/* 去除 iOS 默认样式 */
|
||||
-webkit-appearance: none;
|
||||
outline: none;
|
||||
}
|
||||
.container { padding: 40rpx; display: flex; flex-direction: column; align-items: center }
|
||||
.logo { width: 200rpx; margin-top: 100rpx; margin-bottom: 40rpx }
|
||||
.title { font-size: 36rpx; margin-bottom: 20rpx }
|
||||
.btn { width: 80%; margin-top: 20rpx }
|
||||
.agreements { margin-top: 16rpx; font-size: 24rpx; color: #666; display: flex; flex-wrap: wrap; justify-content: center }
|
||||
.link { color: #007AFF; margin: 0 6rpx }
|
||||
.tip { color: #666; margin-top: 12rpx }
|
||||
.error { color: #e43; margin-top: 20rpx }
|
||||
/* Page Container */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
background: #F8F5F2; /* Cream base */
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Orbs Background */
|
||||
.orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(60rpx);
|
||||
z-index: 0;
|
||||
}
|
||||
.orb-1 {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
background: rgba(255, 107, 0, 0.15);
|
||||
top: -100rpx;
|
||||
left: -100rpx;
|
||||
}
|
||||
.orb-2 {
|
||||
width: 500rpx;
|
||||
height: 500rpx;
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
bottom: -150rpx;
|
||||
right: -150rpx;
|
||||
}
|
||||
|
||||
.content-wrap {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 0 60rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Brand Section */
|
||||
.brand-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 80rpx;
|
||||
}
|
||||
.logo-box {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 40rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 20rpx 60rpx rgba(255, 107, 0, 0.15);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 32rpx;
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes float {
|
||||
0% { transform: translateY(0); }
|
||||
50% { transform: translateY(-20rpx); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
.logo { width: 100%; height: 100%; }
|
||||
.app-name { font-size: 48rpx; font-weight: 900; color: #1A1A1A; margin-bottom: 8rpx; letter-spacing: 2rpx; }
|
||||
.welcome-text { font-size: 28rpx; color: #888; letter-spacing: 4rpx; }
|
||||
|
||||
/* Form Styles */
|
||||
.input-group {
|
||||
background: #FFFFFF;
|
||||
border-radius: 999rpx;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 32rpx;
|
||||
margin-bottom: 32rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||||
}
|
||||
.input-group:focus-within {
|
||||
border-color: #FF6B00;
|
||||
box-shadow: 0 0 0 6rpx rgba(255, 107, 0, 0.1);
|
||||
transform: translateY(-2rpx);
|
||||
}
|
||||
.input-icon { width: 40rpx; height: 40rpx; margin-right: 20rpx; opacity: 0.6; }
|
||||
.input-icon image { width: 100%; height: 100%; }
|
||||
.input-field { flex: 1; height: 100%; font-size: 30rpx; color: #333; }
|
||||
.input-placeholder { color: #BBB; }
|
||||
|
||||
.options-row { display: flex; justify-content: space-between; margin-bottom: 60rpx; }
|
||||
.remember-box { display: flex; align-items: center; }
|
||||
.checkbox { width: 36rpx; height: 36rpx; border: 3rpx solid #DDD; border-radius: 8rpx; margin-right: 12rpx; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
|
||||
.checkbox.checked { background: #FF6B00; border-color: #FF6B00; }
|
||||
.check-mark { color: #FFF; font-size: 24rpx; font-weight: bold; }
|
||||
.remember-text { font-size: 26rpx; color: #666; }
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
height: 100rpx;
|
||||
border-radius: 999rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn:active { transform: scale(0.96); }
|
||||
|
||||
.login-btn {
|
||||
background: linear-gradient(90deg, #FF6B00 0%, #FFA500 100%);
|
||||
color: #FFF;
|
||||
box-shadow: 0 10rpx 30rpx rgba(255, 107, 0, 0.3);
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.btn-shine {
|
||||
position: absolute;
|
||||
top: 0; left: -100%;
|
||||
width: 50%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
|
||||
transform: skewX(-20deg);
|
||||
animation: shine 3s infinite;
|
||||
}
|
||||
@keyframes shine {
|
||||
0% { left: -100%; }
|
||||
20% { left: 200%; }
|
||||
100% { left: 200%; }
|
||||
}
|
||||
|
||||
.weixin-btn {
|
||||
background: #07C160;
|
||||
color: #FFF;
|
||||
box-shadow: 0 10rpx 30rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
.wx-icon { width: 48rpx; height: 48rpx; margin-right: 16rpx; }
|
||||
|
||||
/* Register Link */
|
||||
.register-link { text-align: center; margin-top: 32rpx; }
|
||||
.register-text { font-size: 28rpx; color: #888; }
|
||||
.highlight { color: #FF6B00; font-weight: 700; margin-left: 8rpx; }
|
||||
|
||||
/* Agreements */
|
||||
.agreements {
|
||||
margin-top: 80rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.checkbox.round { border-radius: 50%; width: 32rpx; height: 32rpx; }
|
||||
.checkbox-area { padding: 10rpx; }
|
||||
.agreement-text { font-size: 24rpx; color: #999; margin-left: 8rpx; }
|
||||
.link { color: #FF6B00; text-decoration: underline; margin: 0 4rpx; }
|
||||
|
||||
.error-toast {
|
||||
position: fixed;
|
||||
top: 100rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(255, 60, 60, 0.9);
|
||||
color: #fff;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
1107
pages/mine/index.vue
1107
pages/mine/index.vue
File diff suppressed because it is too large
Load Diff
@ -164,19 +164,127 @@ onReachBottom(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 24rpx }
|
||||
.tabs { display: flex; background: #fff; border-radius: 12rpx; padding: 8rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.tab { flex: 1; text-align: center; padding: 16rpx 0; font-size: 28rpx; color: #666 }
|
||||
.tab.active { color: #007AFF; font-weight: 600 }
|
||||
.order { display: flex; justify-content: space-between; align-items: center; background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.order-main { display: flex; flex-direction: column }
|
||||
.order-title { font-size: 28rpx; color: #333 }
|
||||
.order-sub { font-size: 24rpx; color: #999; margin-top: 6rpx }
|
||||
.order-right { display: flex; flex-direction: column; align-items: flex-end }
|
||||
.order-amount { font-size: 28rpx; color: #333 }
|
||||
.order-status { font-size: 24rpx; color: #666; margin-top: 6rpx }
|
||||
.empty { text-align: center; color: #999; margin-top: 40rpx }
|
||||
.error { color: #e43; margin-bottom: 12rpx }
|
||||
.loading { text-align: center; color: #666; margin: 20rpx 0 }
|
||||
.end { text-align: center; color: #999; margin: 20rpx 0 }
|
||||
/* ============================================
|
||||
奇盒潮玩 - 订单页面
|
||||
采用暖橙色调的订单列表设计
|
||||
============================================ */
|
||||
|
||||
.wrap {
|
||||
padding: 24rpx;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
/* Tab 切换 */
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.25s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tab.active {
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
color: #FFFFFF;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6rpx 20rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
|
||||
/* 订单卡片 */
|
||||
.order {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.order:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.order-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.order-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.order-sub {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.order-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.order-amount {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #FF6B35, #FF9F43);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.order-status {
|
||||
font-size: 24rpx;
|
||||
color: #6B7280;
|
||||
margin-top: 8rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
background: #F3F4F6;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #9CA3AF;
|
||||
margin-top: 120rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
color: #EF4444;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 16rpx;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading, .end {
|
||||
text-align: center;
|
||||
color: #9CA3AF;
|
||||
padding: 24rpx 0;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
156
pages/register/register.vue
Normal file
156
pages/register/register.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
||||
<view class="title">注册新账号</view>
|
||||
|
||||
<view class="form">
|
||||
<view class="input-row">
|
||||
<text class="label">账号</text>
|
||||
<input type="text" v-model="account" class="input-field" placeholder="请输入账号" />
|
||||
</view>
|
||||
|
||||
<view class="input-row">
|
||||
<text class="label">密码</text>
|
||||
<input type="password" v-model="password" class="input-field" placeholder="请输入密码" />
|
||||
</view>
|
||||
|
||||
<view class="input-row">
|
||||
<text class="label">确认密码</text>
|
||||
<input type="password" v-model="confirmPassword" class="input-field" placeholder="请再次输入密码" />
|
||||
</view>
|
||||
|
||||
<button class="btn submit-btn" :disabled="loading" @click="onRegister">注册</button>
|
||||
</view>
|
||||
|
||||
<view class="login-link">
|
||||
<text @tap="goLogin">已有账号?去登录</text>
|
||||
</view>
|
||||
|
||||
<view v-if="error" class="error">{{ error }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const account = ref('')
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
function goLogin() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function onRegister() {
|
||||
if (!account.value || !password.value) {
|
||||
uni.showToast({ title: '请填写完整', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (password.value !== confirmPassword.value) {
|
||||
uni.showToast({ title: '两次密码不一致', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// TODO: 调用注册 API
|
||||
uni.showToast({ title: '注册功能开发中', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ============================================
|
||||
奇盒潮玩 - 注册页面
|
||||
============================================ */
|
||||
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
padding: 60rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(180deg, #FFF8F3 0%, #FFE8D1 50%, #FFDAB9 100%);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
margin-top: 80rpx;
|
||||
margin-bottom: 32rpx;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 12rpx 36rpx rgba(255, 107, 53, 0.2);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 32rpx;
|
||||
padding: 40rpx;
|
||||
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
background: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 8rpx 24rpx;
|
||||
border: 2rpx solid #E5E7EB;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 140rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 28rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
margin-top: 32rpx;
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35) !important;
|
||||
color: #FFFFFF !important;
|
||||
border: none;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
.submit-btn:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.login-link {
|
||||
margin-top: 32rpx;
|
||||
font-size: 26rpx;
|
||||
color: #FF9F43;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin-top: 24rpx;
|
||||
color: #EF4444;
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
127
pages/shop/detail.vue
Normal file
127
pages/shop/detail.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="loading" v-if="loading">加载中...</view>
|
||||
<view v-else-if="detail.id" class="detail-wrap">
|
||||
<image v-if="detail.main_image" class="main-image" :src="detail.main_image" mode="widthFix" />
|
||||
<view class="info-card">
|
||||
<view class="title">{{ detail.title || detail.name || '-' }}</view>
|
||||
<view class="price-row">
|
||||
<text class="price">¥{{ formatPrice(detail.price_sale || detail.price) }}</text>
|
||||
<text class="points" v-if="detail.points_required">{{ detail.points_required }}积分</text>
|
||||
</view>
|
||||
<view class="stock" v-if="detail.stock !== null && detail.stock !== undefined">库存:{{ detail.stock }}</view>
|
||||
<view class="desc" v-if="detail.description">{{ detail.description }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty">商品不存在</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getProductDetail } from '../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
function formatPrice(p) {
|
||||
if (p === undefined || p === null) return '0.00'
|
||||
return (Number(p) / 100).toFixed(2)
|
||||
}
|
||||
|
||||
async function fetchDetail(id) {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getProductDetail(id)
|
||||
detail.value = res || {}
|
||||
} catch (e) {
|
||||
detail.value = {}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
const id = opts && opts.id
|
||||
if (id) fetchDetail(id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ============================================
|
||||
奇盒潮玩 - 商品详情页
|
||||
============================================ */
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
.loading, .empty {
|
||||
text-align: center;
|
||||
padding: 120rpx 40rpx;
|
||||
color: #9CA3AF;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.detail-wrap {
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.main-image {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
margin: 24rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 40rpx;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, #FF6B35, #FF9F43);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.points {
|
||||
font-size: 26rpx;
|
||||
color: #FF9F43;
|
||||
padding: 4rpx 12rpx;
|
||||
background: rgba(255, 159, 67, 0.15);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.stock {
|
||||
font-size: 26rpx;
|
||||
color: #6B7280;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 28rpx;
|
||||
color: #4B5563;
|
||||
line-height: 1.7;
|
||||
}
|
||||
</style>
|
||||
@ -2,51 +2,74 @@
|
||||
<view class="page">
|
||||
<view v-if="showNotice" class="notice-mask" @touchmove.stop.prevent @tap.stop>
|
||||
<view class="notice-dialog" @tap.stop>
|
||||
<view class="notice-title">提示</view>
|
||||
<view class="notice-content">由于价格浮动,当前暂不支持自行兑换商品,兑换请联系客服核对价格。</view>
|
||||
<view class="notice-title">温馨提示</view>
|
||||
<view class="notice-content">由于商品价格实时浮动,当前暂不支持自助兑换。如需兑换商品,请联系客服核对最新价格。</view>
|
||||
<view class="notice-actions">
|
||||
<view class="notice-check" @tap.stop="toggleHideForever">
|
||||
<view class="check-box" :class="{ on: hideForever }"></view>
|
||||
<text class="check-text">不再显示</text>
|
||||
<view class="check-box" :class="{ on: hideForever }">
|
||||
<text v-if="hideForever" class="iconfont icon-check"></text>
|
||||
</view>
|
||||
<text class="check-text">不再提示</text>
|
||||
</view>
|
||||
<button class="notice-btn" type="primary" @tap.stop="onDismissNotice">我知道了</button>
|
||||
<button class="notice-btn" hover-class="btn-hover" @tap.stop="onDismissNotice">我知道了</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="loading" class="loading-wrap"><view class="spinner"></view></view>
|
||||
<view class="products-section" v-else>
|
||||
<view class="section-title">商品</view>
|
||||
<view class="toolbar">
|
||||
<input class="search" v-model="keyword" placeholder="搜索商品" confirm-type="search" @confirm="onSearchConfirm" />
|
||||
<view class="filters">
|
||||
<input class="price" type="number" v-model="minPrice" placeholder="最低价" />
|
||||
<text class="dash">-</text>
|
||||
<input class="price" type="number" v-model="maxPrice" placeholder="最高价" />
|
||||
<button class="apply-btn" size="mini" @tap="onApplyFilters">筛选</button>
|
||||
|
||||
<!-- 顶部固定区域 -->
|
||||
<view class="header-section">
|
||||
<view class="search-box" style="margin-top: 20rpx;">
|
||||
<view class="search-input-wrap">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input class="search-input" v-model="keyword" placeholder="搜索心仪的商品" placeholder-class="placeholder-style" confirm-type="search" @confirm="onSearchConfirm" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="displayCount" class="products-columns">
|
||||
<view class="column" v-for="(col, ci) in columns" :key="ci">
|
||||
<view class="product-item" v-for="p in col" :key="p.id" @tap="onProductTap(p)">
|
||||
|
||||
<view class="filter-row">
|
||||
<view class="price-range">
|
||||
<text class="price-label">价格区间</text>
|
||||
<input class="price-input" type="number" v-model="minPrice" placeholder="最低" placeholder-class="price-ph" />
|
||||
<text class="price-sep">-</text>
|
||||
<input class="price-input" type="number" v-model="maxPrice" placeholder="最高" placeholder-class="price-ph" />
|
||||
</view>
|
||||
<button class="filter-btn" hover-class="btn-hover" @tap="onApplyFilters">筛选</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 占位,防止内容被头部遮挡 -->
|
||||
<view class="header-placeholder"></view>
|
||||
|
||||
<view v-if="loading && !products.length" class="loading-wrap"><view class="spinner"></view></view>
|
||||
|
||||
<view class="products-container" v-else>
|
||||
<view v-if="products.length > 0" class="products-grid">
|
||||
<view class="product-item" v-for="p in products" :key="p.id" @tap="onProductTap(p)">
|
||||
<view class="product-card">
|
||||
<view class="thumb-wrap">
|
||||
<image class="product-thumb" :class="{ visible: isLoaded(p) }" :src="p.image" mode="widthFix" lazy-load="true" @load="onImageLoad(p)" @error="onImageError(p)" />
|
||||
<view v-if="!isLoaded(p)" class="skeleton"></view>
|
||||
<view class="badge">
|
||||
<text class="badge-price" v-if="p.price !== null">¥{{ p.price }}</text>
|
||||
<text class="badge-points" v-if="p.points !== null">{{ p.points }}积分</text>
|
||||
</view>
|
||||
<image class="product-thumb" :src="p.image" mode="aspectFill" lazy-load="true" />
|
||||
<view class="stock-tag" v-if="p.stock !== null && p.stock < 10">仅剩{{p.stock}}件</view>
|
||||
</view>
|
||||
<text class="product-title">{{ p.title }}</text>
|
||||
<view class="product-extra" v-if="p.stock !== null">
|
||||
<text class="stock">库存 {{ p.stock }}</text>
|
||||
<view class="product-info">
|
||||
<text class="product-title">{{ p.title }}</text>
|
||||
<view class="product-bottom">
|
||||
<view class="price-row">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-val">{{ p.price }}</text>
|
||||
</view>
|
||||
<view class="points-badge" v-if="p.points">
|
||||
<text class="points-val">{{ p.points }}</text>
|
||||
<text class="points-unit">积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty-state">
|
||||
<image class="empty-img" src="/static/empty.png" mode="widthFix" />
|
||||
<text class="empty-text">暂无相关商品</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty">暂无商品</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -56,98 +79,32 @@ import { ref, computed } from 'vue'
|
||||
import { request, authRequest } from '../../utils/request.js'
|
||||
|
||||
const products = ref([])
|
||||
const columns = ref([[], []])
|
||||
const colHeights = ref([0, 0])
|
||||
const CACHE_KEY = 'products_cache_v1'
|
||||
const TTL_MS = 10 * 60 * 1000
|
||||
const loading = ref(false)
|
||||
const keyword = ref('')
|
||||
const minPrice = ref('')
|
||||
const maxPrice = ref('')
|
||||
const displayCount = computed(() => (columns.value[0].length + columns.value[1].length))
|
||||
const loadedMap = ref({})
|
||||
const showNotice = ref(false)
|
||||
const hideForever = ref(false)
|
||||
const skipReloadOnce = ref(false)
|
||||
function getKey(p) { return String((p && p.id) ?? '') + '|' + String((p && p.image) ?? '') }
|
||||
function unwrap(list) {
|
||||
if (Array.isArray(list)) return list
|
||||
const obj = list || {}
|
||||
const data = obj.data || {}
|
||||
const arr = obj.list || obj.items || data.list || data.items || data
|
||||
return Array.isArray(arr) ? arr : []
|
||||
}
|
||||
function cleanUrl(u) {
|
||||
const s = String(u || '').trim()
|
||||
const m = s.match(/https?:\/\/[^\s'"`]+/)
|
||||
if (m && m[0]) return m[0]
|
||||
return s.replace(/[`'\"]/g, '').trim()
|
||||
}
|
||||
|
||||
function apiGet(url, data = {}) {
|
||||
const token = uni.getStorageSync('token')
|
||||
const fn = token ? authRequest : request
|
||||
return fn({ url, method: 'GET', data })
|
||||
}
|
||||
|
||||
function getCachedProducts() {
|
||||
try {
|
||||
const obj = uni.getStorageSync(CACHE_KEY)
|
||||
if (obj && Array.isArray(obj.data) && typeof obj.ts === 'number') {
|
||||
const fresh = Date.now() - obj.ts < TTL_MS
|
||||
if (fresh) return obj.data
|
||||
}
|
||||
} catch (_) {}
|
||||
return null
|
||||
}
|
||||
|
||||
function setCachedProducts(list) {
|
||||
try {
|
||||
uni.setStorageSync(CACHE_KEY, { data: Array.isArray(list) ? list : [], ts: Date.now() })
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function estimateHeight(p) {
|
||||
const base = 220
|
||||
const len = String(p.title || '').length
|
||||
const lines = Math.min(2, Math.ceil(len / 12))
|
||||
const titleH = lines * 36
|
||||
const stockH = p.stock !== null && p.stock !== undefined ? 34 : 0
|
||||
const padding = 28
|
||||
return base + titleH + stockH + padding
|
||||
}
|
||||
|
||||
function distributeToColumns(list) {
|
||||
const arr = Array.isArray(list) ? list : []
|
||||
const cols = Array.from({ length: 2 }, () => [])
|
||||
const hs = [0, 0]
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const h = estimateHeight(arr[i])
|
||||
const idx = hs[0] <= hs[1] ? 0 : 1
|
||||
cols[idx].push(arr[i])
|
||||
hs[idx] += h
|
||||
}
|
||||
columns.value = cols
|
||||
colHeights.value = hs
|
||||
const presentKeys = new Set(arr.map(getKey))
|
||||
const next = {}
|
||||
const prev = loadedMap.value || {}
|
||||
for (const k in prev) { if (presentKeys.has(k)) next[k] = prev[k] }
|
||||
loadedMap.value = next
|
||||
}
|
||||
|
||||
function extractListAndTotal(payload) {
|
||||
if (Array.isArray(payload)) return { list: payload, total: payload.length }
|
||||
const obj = payload || {}
|
||||
const data = obj.data || {}
|
||||
const list = obj.list || obj.items || data.list || data.items || []
|
||||
const totalRaw = obj.total ?? data.total
|
||||
const total = typeof totalRaw === 'number' ? totalRaw : (Array.isArray(list) ? list.length : 0)
|
||||
return { list: Array.isArray(list) ? list : [], total }
|
||||
function cleanUrl(u) {
|
||||
const s = String(u || '').trim()
|
||||
const m = s.match(/https?:\/\/[^\s'"`]+/)
|
||||
if (m && m[0]) return m[0]
|
||||
return s.replace(/[`'\"]/g, '').trim()
|
||||
}
|
||||
|
||||
function normalizeProducts(list) {
|
||||
const arr = unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
if (!Array.isArray(list)) return []
|
||||
return list.map((i, idx) => ({
|
||||
id: i.id ?? i.productId ?? i._id ?? i.sku_id ?? String(idx),
|
||||
image: cleanUrl(i.main_image ?? i.imageUrl ?? i.image_url ?? i.image ?? i.img ?? i.pic ?? ''),
|
||||
title: i.title ?? i.name ?? i.product_name ?? i.sku_name ?? '',
|
||||
@ -169,14 +126,18 @@ function onProductTap(p) {
|
||||
}
|
||||
}
|
||||
|
||||
// Filter logic
|
||||
const allProducts = ref([]) // Store all fetched products for client-side filtering
|
||||
|
||||
function applyFilters() {
|
||||
const k = String(keyword.value || '').trim().toLowerCase()
|
||||
const min = Number(minPrice.value)
|
||||
const max = Number(maxPrice.value)
|
||||
const hasMin = !isNaN(min) && String(minPrice.value).trim() !== ''
|
||||
const hasMax = !isNaN(max) && String(maxPrice.value).trim() !== ''
|
||||
const list = Array.isArray(products.value) ? products.value : []
|
||||
const filtered = list.filter(p => {
|
||||
|
||||
const list = allProducts.value
|
||||
products.value = list.filter(p => {
|
||||
const title = String(p.title || '').toLowerCase()
|
||||
if (k && !title.includes(k)) return false
|
||||
const priceNum = typeof p.price === 'number' ? p.price : Number(p.price)
|
||||
@ -190,7 +151,6 @@ function applyFilters() {
|
||||
}
|
||||
return true
|
||||
})
|
||||
distributeToColumns(filtered)
|
||||
}
|
||||
|
||||
function onSearchConfirm() { applyFilters() }
|
||||
@ -198,56 +158,50 @@ function onApplyFilters() { applyFilters() }
|
||||
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const cached = getCachedProducts()
|
||||
if (cached) {
|
||||
products.value = cached
|
||||
distributeToColumns(cached)
|
||||
return
|
||||
const cached = uni.getStorageSync(CACHE_KEY)
|
||||
if (cached && cached.data && Date.now() - cached.ts < TTL_MS) {
|
||||
allProducts.value = cached.data
|
||||
applyFilters()
|
||||
return
|
||||
}
|
||||
|
||||
const first = await apiGet('/api/app/products', { page: 1 })
|
||||
const { list: firstList, total } = extractListAndTotal(first)
|
||||
// Simple extraction
|
||||
let list = []
|
||||
let total = 0
|
||||
if (first && first.list) { list = first.list; total = first.total }
|
||||
else if (first && first.data && first.data.list) { list = first.data.list; total = first.data.total }
|
||||
|
||||
// If not too many, fetch all for better client UX
|
||||
const pageSize = 20
|
||||
const totalPages = Math.max(1, Math.ceil(((typeof total === 'number' ? total : 0)) / pageSize))
|
||||
if (totalPages <= 1) {
|
||||
const normalized = normalizeProducts(firstList)
|
||||
products.value = normalized
|
||||
distributeToColumns(normalized)
|
||||
setCachedProducts(normalized)
|
||||
return
|
||||
const totalPages = Math.ceil((total || 0) / pageSize)
|
||||
|
||||
if (totalPages > 1) {
|
||||
const tasks = []
|
||||
for (let p = 2; p <= totalPages; p++) {
|
||||
tasks.push(apiGet('/api/app/products', { page: p }))
|
||||
}
|
||||
const results = await Promise.allSettled(tasks)
|
||||
results.forEach(r => {
|
||||
if (r.status === 'fulfilled') {
|
||||
const val = r.value
|
||||
const subList = (val && val.list) || (val && val.data && val.data.list) || []
|
||||
if (Array.isArray(subList)) list = list.concat(subList)
|
||||
}
|
||||
})
|
||||
}
|
||||
const tasks = []
|
||||
for (let p = 2; p <= totalPages; p++) {
|
||||
tasks.push(apiGet('/api/app/products', { page: p }))
|
||||
}
|
||||
const results = await Promise.allSettled(tasks)
|
||||
const restLists = results.map(r => {
|
||||
if (r.status === 'fulfilled') {
|
||||
const { list } = extractListAndTotal(r.value)
|
||||
return Array.isArray(list) ? list : []
|
||||
}
|
||||
return []
|
||||
})
|
||||
const merged = [firstList, ...restLists].flat()
|
||||
const normalized = normalizeProducts(merged)
|
||||
products.value = normalized
|
||||
distributeToColumns(normalized)
|
||||
setCachedProducts(normalized)
|
||||
|
||||
const normalized = normalizeProducts(list)
|
||||
allProducts.value = normalized
|
||||
applyFilters()
|
||||
uni.setStorageSync(CACHE_KEY, { data: normalized, ts: Date.now() })
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
products.value = []
|
||||
columns.value = [[], []]
|
||||
colHeights.value = [0, 0]
|
||||
const presentKeys = new Set([])
|
||||
const next = {}
|
||||
const prev = loadedMap.value || {}
|
||||
for (const k in prev) { if (presentKeys.has(k)) next[k] = prev[k] }
|
||||
loadedMap.value = next
|
||||
}
|
||||
}
|
||||
|
||||
function isLoaded(p) { return !!(loadedMap.value && loadedMap.value[getKey(p)]) }
|
||||
function onImageLoad(p) { const k = getKey(p); if (!k) return; loadedMap.value = { ...(loadedMap.value || {}), [k]: true } }
|
||||
function onImageError(p) { const k = getKey(p); if (!k) return; const prev = { ...(loadedMap.value || {}) }; delete prev[k]; loadedMap.value = prev }
|
||||
|
||||
onShow(async () => {
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
@ -264,21 +218,16 @@ onShow(async () => {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Notice logic
|
||||
try {
|
||||
const sess = String(uni.getStorageSync('app_session_id') || '')
|
||||
const hiddenSess = String(uni.getStorageSync('shop_notice_hidden_session_id') || '')
|
||||
const hiddenThisSession = !!(sess && hiddenSess && hiddenSess === sess)
|
||||
showNotice.value = !hiddenThisSession
|
||||
hideForever.value = hiddenThisSession
|
||||
} catch (_) { showNotice.value = true; hideForever.value = false }
|
||||
try {
|
||||
const skip = !!uni.getStorageSync('shop_skip_reload_once')
|
||||
if (skipReloadOnce.value || skip) {
|
||||
skipReloadOnce.value = false
|
||||
uni.setStorageSync('shop_skip_reload_once', '')
|
||||
return
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (_) { showNotice.value = true }
|
||||
|
||||
loading.value = true
|
||||
await loadProducts()
|
||||
loading.value = false
|
||||
@ -297,44 +246,318 @@ function onDismissNotice() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { padding: 24rpx }
|
||||
.notice-mask { position: fixed; left: 0; top: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.45); z-index: 9999; display: flex; align-items: center; justify-content: center }
|
||||
.notice-dialog { width: 86%; max-width: 640rpx; background: #fff; border-radius: 16rpx; overflow: hidden; box-shadow: 0 12rpx 24rpx rgba(0,0,0,0.18) }
|
||||
.notice-title { font-size: 32rpx; font-weight: 600; padding: 24rpx 24rpx 0 }
|
||||
.notice-content { padding: 16rpx 24rpx; font-size: 26rpx; color: #333; line-height: 1.6 }
|
||||
.notice-actions { display: flex; align-items: center; justify-content: space-between; padding: 16rpx 24rpx 24rpx }
|
||||
.notice-check { display: flex; align-items: center; gap: 8rpx }
|
||||
.check-box { width: 28rpx; height: 28rpx; border-radius: 6rpx; border: 2rpx solid #007AFF; background: #fff }
|
||||
.check-box.on { background: #007AFF }
|
||||
.check-text { font-size: 26rpx; color: #555 }
|
||||
.notice-btn { background: #007AFF; color: #fff; border-radius: 999rpx; padding: 0 28rpx }
|
||||
.section-title { font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx }
|
||||
.products-section { background: #ffffff; border-radius: 12rpx; padding: 24rpx; margin-top: 24rpx }
|
||||
.loading-wrap { min-height: 60vh; display: flex; align-items: center; justify-content: center }
|
||||
.spinner { width: 56rpx; height: 56rpx; border: 6rpx solid rgba(0,122,255,0.15); border-top-color: #007AFF; border-radius: 50%; animation: spin 1s linear infinite }
|
||||
@keyframes spin { from { transform: rotate(0) } to { transform: rotate(360deg) } }
|
||||
.toolbar { display: flex; flex-direction: column; gap: 12rpx; margin-bottom: 16rpx }
|
||||
.search { background: #f6f8ff; border: 1rpx solid rgba(0,122,255,0.25); border-radius: 999rpx; padding: 14rpx 20rpx; font-size: 26rpx }
|
||||
.filters { display: flex; align-items: center; gap: 12rpx }
|
||||
.price { flex: 1; background: #f6f8ff; border: 1rpx solid rgba(0,122,255,0.25); border-radius: 999rpx; padding: 12rpx 16rpx; font-size: 26rpx }
|
||||
.dash { color: #888; font-size: 26rpx }
|
||||
.apply-btn { background: #007AFF; color: #fff; border-radius: 999rpx; padding: 0 20rpx }
|
||||
.products-columns { display: flex; gap: 12rpx }
|
||||
.column { flex: 1 }
|
||||
.product-item { margin-bottom: 12rpx }
|
||||
.empty { padding: 40rpx; color: #888; text-align: center }
|
||||
.product-card { background: #fff; border-radius: 16rpx; overflow: hidden; box-shadow: 0 6rpx 16rpx rgba(0,122,255,0.08); transition: transform .15s ease }
|
||||
.product-item:active .product-card { transform: scale(0.98) }
|
||||
.thumb-wrap { position: relative }
|
||||
.product-thumb { width: 100%; height: auto; display: block; opacity: 0; transition: opacity .25s ease; z-index: 0 }
|
||||
.product-thumb.visible { opacity: 1 }
|
||||
.skeleton { position: absolute; left: 0; top: 0; right: 0; bottom: 0; background: linear-gradient(90deg, #eef2ff 25%, #f6f8ff 37%, #eef2ff 63%); background-size: 400% 100%; animation: shimmer 1.2s ease infinite; z-index: 1 }
|
||||
@keyframes shimmer { 0% { background-position: 100% 0 } 100% { background-position: 0 0 } }
|
||||
.thumb-wrap { background: #f6f8ff; min-height: 220rpx }
|
||||
.badge { position: absolute; left: 12rpx; bottom: 12rpx; display: flex; gap: 8rpx }
|
||||
.badge-price { background: #007AFF; color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx; box-shadow: 0 2rpx 8rpx rgba(0,122,255,0.25) }
|
||||
.badge-points { background: rgba(0,122,255,0.85); color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx }
|
||||
.product-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin: 12rpx; font-size: 26rpx; color: #222 }
|
||||
.product-extra { display: flex; justify-content: flex-end; align-items: center; margin: 0 12rpx 12rpx }
|
||||
.stock { font-size: 22rpx; color: #888 }
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background-color: #F5F6F8;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 顶部 Header */
|
||||
.header-section {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #FFFFFF;
|
||||
padding: 0 24rpx 24rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04);
|
||||
}
|
||||
.header-placeholder {
|
||||
height: 160rpx; /* 根据 header 高度调整 */
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 800;
|
||||
color: #111;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.search-box {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.search-input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #F5F7FA;
|
||||
border-radius: 16rpx;
|
||||
padding: 18rpx 24rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.search-input-wrap:focus-within {
|
||||
background: #FFF;
|
||||
box-shadow: 0 0 0 2rpx #FF9F43;
|
||||
}
|
||||
.search-icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 16rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
.placeholder-style {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 筛选行 */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.price-range {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #F5F7FA;
|
||||
border-radius: 12rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
}
|
||||
.price-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
.price-input {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
.price-ph {
|
||||
color: #BBB;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.price-sep {
|
||||
color: #CCC;
|
||||
margin: 0 10rpx;
|
||||
}
|
||||
.filter-btn {
|
||||
background: linear-gradient(135deg, #FF9F43, #FF6B35);
|
||||
color: white;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 32rpx;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
border: none;
|
||||
}
|
||||
.btn-hover {
|
||||
opacity: 0.9;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 商品 Grid 容器 */
|
||||
.products-container {
|
||||
padding: 24rpx;
|
||||
}
|
||||
.products-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 商品卡片 */
|
||||
.product-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.thumb-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 100%; /* 1:1 Aspect Ratio */
|
||||
background: #F9F9F9;
|
||||
}
|
||||
.product-thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.stock-tag {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-top-left-radius: 12rpx;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 20rpx;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.product-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
height: 2.8em; /* 2 lines */
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.product-bottom {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
}
|
||||
.price-row {
|
||||
color: #FF5500;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.price-symbol {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.price-val {
|
||||
font-size: 34rpx;
|
||||
}
|
||||
.points-badge {
|
||||
background: #FFF0E6;
|
||||
color: #FF6B35;
|
||||
border: 1px solid rgba(255, 107, 53, 0.2);
|
||||
border-radius: 8rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
}
|
||||
.points-val {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.points-unit {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
/* Loading & Empty */
|
||||
.loading-wrap {
|
||||
padding: 100rpx 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.spinner {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border: 4rpx solid #ddd;
|
||||
border-top-color: #FF9F43;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 100rpx;
|
||||
}
|
||||
.empty-img {
|
||||
width: 240rpx;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.empty-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.notice-mask {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.6);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.notice-dialog {
|
||||
width: 560rpx;
|
||||
background: #FFF;
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.notice-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
margin-bottom: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
.notice-content {
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 40rpx;
|
||||
text-align: left;
|
||||
}
|
||||
.notice-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
.notice-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
background: linear-gradient(90deg, #FF9F43, #FF6B35);
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.notice-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.check-box {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border: 2rpx solid #CCC;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.check-box.on {
|
||||
background: #FF6B35;
|
||||
border-color: #FF6B35;
|
||||
}
|
||||
.icon-check {
|
||||
font-size: 20rpx;
|
||||
color: #FFF;
|
||||
}
|
||||
.check-text {
|
||||
font-size: 26rpx;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
|
||||
25
project.config.json
Normal file
25
project.config.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"setting": {
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"uglifyFileName": false,
|
||||
"enhance": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"useCompilerPlugins": false,
|
||||
"minifyWXML": true
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"appid": "wx26ad074017e1e63f",
|
||||
"editorSetting": {}
|
||||
}
|
||||
14
project.private.config.json
Normal file
14
project.private.config.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"libVersion": "3.12.1",
|
||||
"projectname": "bindbox-mini",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"compileHotReLoad": true
|
||||
}
|
||||
}
|
||||
201
uni.scss
201
uni.scss
@ -1,76 +1,175 @@
|
||||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
* 奇盒潮玩 - 全局样式系统
|
||||
*
|
||||
* 基于潮玩盲盒风格的设计系统,采用暖橙渐变色调
|
||||
* 传递惊喜、期待、活力的品牌调性
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
/* ============================================
|
||||
🎨 品牌色彩系统 - 潮玩暖色调
|
||||
============================================ */
|
||||
|
||||
/* 颜色变量 */
|
||||
/* 主色 - 暖橙渐变 */
|
||||
$primary-orange: #FF9F43; // 活力橙
|
||||
$primary-deep: #FF6B35; // 深橙红
|
||||
$primary-light: #FFB366; // 浅橙
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
/* 辅助色 */
|
||||
$accent-gold: #FFD166; // 金币黄
|
||||
$accent-pink: #FF8FAB; // 少女粉
|
||||
$accent-coral: #FF7B7B; // 珊瑚红
|
||||
$accent-purple: #A78BFA; // 梦幻紫
|
||||
|
||||
/* 功能色 */
|
||||
$success-color: #10B981; // 成功绿
|
||||
$warning-color: #FBBF24; // 警告黄
|
||||
$error-color: #EF4444; // 错误红
|
||||
$info-color: #3B82F6; // 信息蓝
|
||||
|
||||
/* 中性色 */
|
||||
$text-primary: #1F2937; // 主要文字
|
||||
$text-secondary: #6B7280; // 次要文字
|
||||
$text-tertiary: #9CA3AF; // 辅助文字
|
||||
$text-inverse: #FFFFFF; // 反色文字
|
||||
|
||||
/* 背景色 */
|
||||
$bg-page: #FFF8F3; // 页面暖白底
|
||||
$bg-card: #FFFFFF; // 卡片白
|
||||
$bg-warm: #FFF4E6; // 暖色面板
|
||||
$bg-grey: #F9FAFB; // 冷灰面板
|
||||
|
||||
/* 边框色 */
|
||||
$border-light: #F3F4F6;
|
||||
$border-normal: #E5E7EB;
|
||||
$border-warm: rgba(255, 159, 67, 0.2);
|
||||
|
||||
/* ============================================
|
||||
✨ 渐变预设
|
||||
============================================ */
|
||||
|
||||
/* 注意:Sass变量不能存储CSS渐变值用于直接引用
|
||||
以下为文档记录,使用时直接写CSS */
|
||||
|
||||
// 主渐变:linear-gradient(135deg, #FF9F43, #FF6B35)
|
||||
// 金色渐变:linear-gradient(135deg, #FFD166, #FF9F43)
|
||||
// 粉色渐变:linear-gradient(135deg, #FF8FAB, #FF6B81)
|
||||
// 卡片高光:linear-gradient(145deg, #FFFFFF, #FFF8F3)
|
||||
|
||||
/* ============================================
|
||||
📐 间距与圆角
|
||||
============================================ */
|
||||
|
||||
/* 间距 */
|
||||
$spacing-xs: 8rpx;
|
||||
$spacing-sm: 12rpx;
|
||||
$spacing-md: 16rpx;
|
||||
$spacing-lg: 24rpx;
|
||||
$spacing-xl: 32rpx;
|
||||
$spacing-2xl: 48rpx;
|
||||
|
||||
/* 圆角 */
|
||||
$radius-sm: 8rpx;
|
||||
$radius-md: 12rpx;
|
||||
$radius-lg: 16rpx;
|
||||
$radius-xl: 24rpx;
|
||||
$radius-full: 999rpx;
|
||||
|
||||
/* ============================================
|
||||
🔤 字体系统
|
||||
============================================ */
|
||||
|
||||
$font-size-xs: 22rpx;
|
||||
$font-size-sm: 24rpx;
|
||||
$font-size-base: 28rpx;
|
||||
$font-size-md: 30rpx;
|
||||
$font-size-lg: 32rpx;
|
||||
$font-size-xl: 36rpx;
|
||||
$font-size-2xl: 44rpx;
|
||||
$font-size-3xl: 56rpx;
|
||||
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-medium: 500;
|
||||
$font-weight-semibold: 600;
|
||||
$font-weight-bold: 700;
|
||||
|
||||
/* ============================================
|
||||
🌟 阴影效果
|
||||
============================================ */
|
||||
|
||||
$shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
$shadow-md: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||
$shadow-lg: 0 16rpx 48rpx rgba(0, 0, 0, 0.12);
|
||||
$shadow-warm: 0 8rpx 24rpx rgba(255, 107, 53, 0.15);
|
||||
$shadow-glow: 0 4rpx 16rpx rgba(255, 159, 67, 0.4);
|
||||
|
||||
/* ============================================
|
||||
🔄 动画时长
|
||||
============================================ */
|
||||
|
||||
$transition-fast: 0.15s;
|
||||
$transition-normal: 0.25s;
|
||||
$transition-slow: 0.35s;
|
||||
$ease-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
$ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
/* ============================================
|
||||
📱 兼容uni-app官方变量
|
||||
============================================ */
|
||||
|
||||
/* 行为相关颜色 - 使用新配色 */
|
||||
$uni-color-primary: $primary-orange;
|
||||
$uni-color-success: $success-color;
|
||||
$uni-color-warning: $warning-color;
|
||||
$uni-color-error: $error-color;
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color:#333;//基本色
|
||||
$uni-text-color-inverse:#fff;//反色
|
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
$uni-text-color: $text-primary;
|
||||
$uni-text-color-inverse: $text-inverse;
|
||||
$uni-text-color-grey: $text-tertiary;
|
||||
$uni-text-color-placeholder: $text-tertiary;
|
||||
$uni-text-color-disable: #D1D5DB;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color:#ffffff;
|
||||
$uni-bg-color-grey:#f8f8f8;
|
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||
$uni-bg-color: $bg-card;
|
||||
$uni-bg-color-grey: $bg-grey;
|
||||
$uni-bg-color-hover: #FFF0E6;
|
||||
$uni-bg-color-mask: rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color:#c8c7cc;
|
||||
$uni-border-color: $border-normal;
|
||||
|
||||
/* 尺寸变量 */
|
||||
$uni-font-size-sm: 12px;
|
||||
$uni-font-size-base: 14px;
|
||||
$uni-font-size-lg: 16px;
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-font-size-lg:16px;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:20px;
|
||||
$uni-img-size-base:26px;
|
||||
$uni-img-size-lg:40px;
|
||||
$uni-img-size-sm: 20px;
|
||||
$uni-img-size-base: 26px;
|
||||
$uni-img-size-lg: 40px;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 2px;
|
||||
$uni-border-radius-base: 3px;
|
||||
$uni-border-radius-lg: 6px;
|
||||
$uni-border-radius-sm: 4px;
|
||||
$uni-border-radius-base: 8px;
|
||||
$uni-border-radius-lg: 12px;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 5px;
|
||||
$uni-spacing-row-base: 10px;
|
||||
$uni-spacing-row-lg: 15px;
|
||||
$uni-spacing-row-sm: 6px;
|
||||
$uni-spacing-row-base: 12px;
|
||||
$uni-spacing-row-lg: 16px;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 4px;
|
||||
$uni-spacing-col-base: 8px;
|
||||
$uni-spacing-col-lg: 12px;
|
||||
$uni-spacing-col-sm: 6px;
|
||||
$uni-spacing-col-base: 10px;
|
||||
$uni-spacing-col-lg: 14px;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
$uni-opacity-disabled: 0.4;
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:20px;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:15px;
|
||||
$uni-color-title: $text-primary;
|
||||
$uni-font-size-title: 20px;
|
||||
$uni-color-subtitle: $text-secondary;
|
||||
$uni-font-size-subtitle: 16px;
|
||||
$uni-color-paragraph: $text-secondary;
|
||||
$uni-font-size-paragraph: 15px;
|
||||
Loading…
x
Reference in New Issue
Block a user