feat: 添加许愿树功能和Go后端服务
- 新增许愿树页面(Canvas绘制、许愿条展示) - 新增许愿详情页面 - 新增许愿创建弹窗(免费/付费两种模式) - 新增网络请求封装 utils/request.js - 新增Go后端项目结构(Gin + Inertia.js) - 新增用户、订单、许愿数据模型 - 新增数据库迁移脚本 - 更新.gitignore补全忽略规则 - 更新.env.local配置(robot=2, 版本1.0.1) - 添加secrets目录说明文档
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
const { request } = require('../../utils/request.js');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
wish: null,
|
||||
loading: true,
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const { id } = options;
|
||||
if (id) {
|
||||
this.loadWishDetail(id);
|
||||
}
|
||||
},
|
||||
|
||||
async loadWishDetail(id) {
|
||||
try {
|
||||
const res = await request({
|
||||
url: `/api/wish/detail/${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
this.setData({
|
||||
wish: res.data,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载许愿详情失败:', err);
|
||||
// 使用模拟数据
|
||||
this.setData({
|
||||
wish: {
|
||||
id: id,
|
||||
content: '愿家人平安健康,事业顺利,心想事成!',
|
||||
type: 'paid',
|
||||
createdAt: '2024-01-15 12:00:00',
|
||||
user: {
|
||||
nickname: '祈福用户',
|
||||
avatar: '',
|
||||
},
|
||||
},
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 分享
|
||||
onShareAppMessage() {
|
||||
const { wish } = this.data;
|
||||
return {
|
||||
title: wish ? `我的愿望:${wish.content.substring(0, 20)}...` : '快来许愿吧!',
|
||||
path: `/pages/wish-detail/wish-detail?id=${wish ? wish.id : ''}`,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "许愿详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<view class="container">
|
||||
<view class="loading" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<view class="wish-detail" wx:else>
|
||||
<view class="wish-card {{wish.type}}">
|
||||
<view class="wish-header">
|
||||
<image class="avatar" src="{{wish.user.avatar || '/images/default-avatar.png'}}" mode="aspectFill"></image>
|
||||
<view class="user-info">
|
||||
<text class="nickname">{{wish.user.nickname}}</text>
|
||||
<text class="time">{{wish.createdAt}}</text>
|
||||
</view>
|
||||
<view class="wish-badge" wx:if="{{wish.type === 'paid'}}">
|
||||
<text>VIP</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="wish-content">
|
||||
<text>{{wish.content}}</text>
|
||||
</view>
|
||||
|
||||
<view class="wish-footer">
|
||||
<view class="wish-type">
|
||||
<text class="type-tag {{wish.type}}">{{wish.type === 'paid' ? '付费许愿' : '免费许愿'}}</text>
|
||||
</view>
|
||||
<view class="wish-actions">
|
||||
<button class="btn-action" bindtap="onShare">
|
||||
<text class="icon">📤</text>
|
||||
<text>分享</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 相关推荐 -->
|
||||
<view class="recommend-section">
|
||||
<view class="section-title">更多愿望</view>
|
||||
<view class="recommend-list">
|
||||
<view class="recommend-item" wx:for="{{[1,2,3]}}" wx:key="*this">
|
||||
<text class="recommend-text">愿所有美好如期而至</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,145 @@
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: #FFFBF5;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.wish-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.wish-card.paid {
|
||||
border: 2rpx solid #FFD700;
|
||||
}
|
||||
|
||||
.wish-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.time {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.wish-badge {
|
||||
background: linear-gradient(135deg, #FFD700, #FFA500);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.wish-content {
|
||||
padding: 30rpx;
|
||||
background: #FFF9F0;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.wish-content text {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.wish-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.type-tag {
|
||||
font-size: 22rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.type-tag.free {
|
||||
background: #FFE4E1;
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
.type-tag.paid {
|
||||
background: #FFF8DC;
|
||||
color: #DAA520;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
background: #C41E3A;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-action .icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 推荐区域 */
|
||||
.recommend-section {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.recommend-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.recommend-item {
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.recommend-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
const { request } = require('../../utils/request.js');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
tree: null,
|
||||
wishes: [],
|
||||
loading: true,
|
||||
canvasWidth: 375,
|
||||
canvasHeight: 600,
|
||||
showCreateModal: false,
|
||||
wishContent: '',
|
||||
wishType: 'free', // free: 免费, paid: 付费
|
||||
maxFreeLength: 20,
|
||||
maxPaidLength: 100,
|
||||
products: [],
|
||||
selectedProduct: null,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadWishTree();
|
||||
this.loadProducts();
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadWishTree().then(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
},
|
||||
|
||||
// 加载许愿树数据
|
||||
async loadWishTree() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/api/wish/tree',
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
this.setData({
|
||||
tree: res.data.tree,
|
||||
wishes: res.data.wishes || [],
|
||||
loading: false,
|
||||
});
|
||||
this.drawTree();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载许愿树失败:', err);
|
||||
this.setData({ loading: false });
|
||||
// 使用本地模拟数据
|
||||
this.setData({
|
||||
tree: { id: 1, name: '祈福许愿树', maxWishes: 100 },
|
||||
wishes: this.getMockWishes(),
|
||||
loading: false,
|
||||
});
|
||||
this.drawTree();
|
||||
}
|
||||
},
|
||||
|
||||
// 加载许愿商品
|
||||
async loadProducts() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/api/wish/products',
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
this.setData({ products: res.data.list || [] });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载商品失败:', err);
|
||||
// 使用默认商品
|
||||
this.setData({
|
||||
products: [
|
||||
{ id: 1, name: '普通许愿条', price: 100, duration: 7, position: 0 },
|
||||
{ id: 2, name: '精品许愿条', price: 500, duration: 30, position: 10 },
|
||||
{ id: 3, name: '至尊许愿条', price: 2000, duration: 90, position: 100 },
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 模拟许愿数据
|
||||
getMockWishes() {
|
||||
return [
|
||||
{ id: 1, content: '愿家人平安健康', type: 'paid', position: 10, isRobot: false },
|
||||
{ id: 2, content: '事业顺利', type: 'free', position: 5, isRobot: false },
|
||||
{ id: 3, content: '心想事成', type: 'paid', position: 20, isRobot: true },
|
||||
{ id: 4, content: '考试通过', type: 'free', position: 3, isRobot: false },
|
||||
{ id: 5, content: '财源广进', type: 'paid', position: 15, isRobot: false },
|
||||
];
|
||||
},
|
||||
|
||||
// 绘制许愿树
|
||||
drawTree() {
|
||||
const ctx = wx.createCanvasContext('wishTree', this);
|
||||
const { canvasWidth, canvasHeight, wishes } = this.data;
|
||||
|
||||
// 清空画布
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
// 绘制树干
|
||||
ctx.setFillStyle('#8B4513');
|
||||
ctx.fillRect(canvasWidth / 2 - 20, canvasHeight - 150, 40, 150);
|
||||
|
||||
// 绘制树冠(圆形)
|
||||
ctx.setFillStyle('#228B22');
|
||||
ctx.beginPath();
|
||||
ctx.arc(canvasWidth / 2, canvasHeight - 200, 120, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
|
||||
// 绘制许愿条
|
||||
wishes.forEach((wish, index) => {
|
||||
const angle = (index / wishes.length) * 2 * Math.PI;
|
||||
const radius = 80 + Math.random() * 40;
|
||||
const x = canvasWidth / 2 + radius * Math.cos(angle);
|
||||
const y = canvasHeight - 200 + radius * Math.sin(angle);
|
||||
|
||||
// 根据类型设置颜色
|
||||
if (wish.type === 'paid') {
|
||||
ctx.setFillStyle('#FFD700'); // 金色 - 付费
|
||||
} else {
|
||||
ctx.setFillStyle('#FF6B6B'); // 红色 - 免费
|
||||
}
|
||||
|
||||
// 绘制许愿条(小矩形)
|
||||
ctx.fillRect(x - 15, y - 5, 30, 10);
|
||||
|
||||
// 绘制文字
|
||||
ctx.setFillStyle('#FFFFFF');
|
||||
ctx.setFontSize(8);
|
||||
ctx.setTextAlign('center');
|
||||
ctx.fillText(wish.content.substring(0, 4), x, y + 3);
|
||||
});
|
||||
|
||||
ctx.draw();
|
||||
},
|
||||
|
||||
// 打开许愿弹窗
|
||||
openCreateModal() {
|
||||
this.setData({ showCreateModal: true });
|
||||
},
|
||||
|
||||
// 关闭许愿弹窗
|
||||
closeCreateModal() {
|
||||
this.setData({
|
||||
showCreateModal: false,
|
||||
wishContent: '',
|
||||
wishType: 'free',
|
||||
selectedProduct: null,
|
||||
});
|
||||
},
|
||||
|
||||
// 输入许愿内容
|
||||
onInputContent(e) {
|
||||
this.setData({ wishContent: e.detail.value });
|
||||
},
|
||||
|
||||
// 选择许愿类型
|
||||
selectType(e) {
|
||||
const type = e.currentTarget.dataset.type;
|
||||
this.setData({ wishType: type });
|
||||
},
|
||||
|
||||
// 选择商品
|
||||
selectProduct(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
const product = this.data.products.find(p => p.id === id);
|
||||
this.setData({ selectedProduct: product });
|
||||
},
|
||||
|
||||
// 提交许愿
|
||||
async submitWish() {
|
||||
const { wishContent, wishType, selectedProduct } = this.data;
|
||||
|
||||
if (!wishContent.trim()) {
|
||||
wx.showToast({ title: '请输入许愿内容', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查字数限制
|
||||
const maxLength = wishType === 'free' ? this.data.maxFreeLength : this.data.maxPaidLength;
|
||||
if (wishContent.length > maxLength) {
|
||||
wx.showToast({ title: `最多输入${maxLength}个字`, icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 付费许愿需要选择商品
|
||||
if (wishType === 'paid' && !selectedProduct) {
|
||||
wx.showToast({ title: '请选择许愿商品', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 如果是付费许愿,先创建订单
|
||||
if (wishType === 'paid') {
|
||||
const orderRes = await request({
|
||||
url: '/api/pay/create',
|
||||
method: 'POST',
|
||||
data: {
|
||||
type: 'wish',
|
||||
productId: selectedProduct.id,
|
||||
content: wishContent,
|
||||
},
|
||||
});
|
||||
|
||||
if (orderRes.code === 0) {
|
||||
// 调起微信支付
|
||||
await this.wxPay(orderRes.data);
|
||||
}
|
||||
} else {
|
||||
// 免费许愿直接创建
|
||||
const res = await request({
|
||||
url: '/api/wish/create',
|
||||
method: 'POST',
|
||||
data: {
|
||||
content: wishContent,
|
||||
type: 'free',
|
||||
treeId: this.data.tree.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
wx.showToast({ title: '许愿成功', icon: 'success' });
|
||||
this.closeCreateModal();
|
||||
this.loadWishTree();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('许愿失败:', err);
|
||||
wx.showToast({ title: '许愿失败,请重试', icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
// 微信支付
|
||||
async wxPay(orderData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.requestPayment({
|
||||
timeStamp: orderData.timeStamp,
|
||||
nonceStr: orderData.nonceStr,
|
||||
package: orderData.package,
|
||||
signType: orderData.signType,
|
||||
paySign: orderData.paySign,
|
||||
success: () => {
|
||||
wx.showToast({ title: '支付成功', icon: 'success' });
|
||||
this.closeCreateModal();
|
||||
this.loadWishTree();
|
||||
resolve();
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付失败:', err);
|
||||
wx.showToast({ title: '支付失败', icon: 'none' });
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 查看许愿详情
|
||||
viewWishDetail(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
wx.navigateTo({
|
||||
url: `/pages/wish-detail/wish-detail?id=${id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 分享
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '快来许愿树许下你的愿望吧!',
|
||||
path: '/pages/wish-tree/wish-tree',
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "许愿树",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark"
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<view class="container">
|
||||
<!-- 许愿树画布 -->
|
||||
<view class="canvas-container">
|
||||
<canvas
|
||||
canvas-id="wishTree"
|
||||
class="wish-tree-canvas"
|
||||
style="width: {{canvasWidth}}px; height: {{canvasHeight}}px;"
|
||||
></canvas>
|
||||
|
||||
<!-- 许愿条覆盖层 -->
|
||||
<view class="wishes-overlay">
|
||||
<view
|
||||
wx:for="{{wishes}}"
|
||||
wx:key="id"
|
||||
class="wish-tag {{item.type}} {{item.isRobot ? 'robot' : ''}}"
|
||||
style="left: {{item.x}}px; top: {{item.y}}px;"
|
||||
bindtap="viewWishDetail"
|
||||
data-id="{{item.id}}"
|
||||
>
|
||||
<text class="wish-text">{{item.content}}</text>
|
||||
<view class="wish-badge" wx:if="{{item.type === 'paid'}}">VIP</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 许愿按钮 -->
|
||||
<view class="action-bar">
|
||||
<button class="btn-primary btn-wish" bindtap="openCreateModal">
|
||||
<text class="icon">🙏</text>
|
||||
<text>我要许愿</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 许愿说明 -->
|
||||
<view class="tips-section">
|
||||
<view class="tip-item">
|
||||
<text class="tip-icon">🎋</text>
|
||||
<text class="tip-text">免费许愿:20字以内,可能被覆盖</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<text class="tip-icon">✨</text>
|
||||
<text class="tip-text">付费许愿:100字以内,优先展示,长期保留</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 许愿弹窗 -->
|
||||
<view class="modal" wx:if="{{showCreateModal}}">
|
||||
<view class="modal-mask" bindtap="closeCreateModal"></view>
|
||||
<view class="modal-content">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">许下心愿</text>
|
||||
<text class="modal-close" bindtap="closeCreateModal">×</text>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<!-- 许愿类型选择 -->
|
||||
<view class="type-selector">
|
||||
<view
|
||||
class="type-item {{wishType === 'free' ? 'active' : ''}}"
|
||||
bindtap="selectType"
|
||||
data-type="free"
|
||||
>
|
||||
<text class="type-name">免费许愿</text>
|
||||
<text class="type-desc">20字以内</text>
|
||||
</view>
|
||||
<view
|
||||
class="type-item {{wishType === 'paid' ? 'active' : ''}}"
|
||||
bindtap="selectType"
|
||||
data-type="paid"
|
||||
>
|
||||
<text class="type-name">付费许愿</text>
|
||||
<text class="type-desc">更多特权</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 许愿内容输入 -->
|
||||
<textarea
|
||||
class="wish-input"
|
||||
placeholder="请输入你的愿望..."
|
||||
maxlength="{{wishType === 'free' ? maxFreeLength : maxPaidLength}}"
|
||||
bindinput="onInputContent"
|
||||
value="{{wishContent}}"
|
||||
></textarea>
|
||||
<view class="input-count">
|
||||
{{wishContent.length}}/{{wishType === 'free' ? maxFreeLength : maxPaidLength}}
|
||||
</view>
|
||||
|
||||
<!-- 付费商品选择 -->
|
||||
<view class="product-section" wx:if="{{wishType === 'paid'}}">
|
||||
<view class="section-title">选择许愿商品</view>
|
||||
<view class="product-list">
|
||||
<view
|
||||
wx:for="{{products}}"
|
||||
wx:key="id"
|
||||
class="product-item {{selectedProduct && selectedProduct.id === item.id ? 'active' : ''}}"
|
||||
bindtap="selectProduct"
|
||||
data-id="{{item.id}}"
|
||||
>
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{item.name}}</text>
|
||||
<text class="product-desc">展示{{item.duration}}天</text>
|
||||
</view>
|
||||
<view class="product-price">¥{{item.price / 100}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-footer">
|
||||
<button class="btn-outline" bindtap="closeCreateModal">取消</button>
|
||||
<button class="btn-primary" bindtap="submitWish">
|
||||
{{wishType === 'paid' ? '支付并许愿' : '提交许愿'}}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,307 @@
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #87CEEB 0%, #E0F6FF 100%);
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
/* 画布容器 */
|
||||
.canvas-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 800rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wish-tree-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 许愿条覆盖层 */
|
||||
.wishes-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wish-tag {
|
||||
position: absolute;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
pointer-events: auto;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.2);
|
||||
max-width: 200rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wish-tag.free {
|
||||
background: linear-gradient(135deg, #FF6B6B, #FF8E8E);
|
||||
}
|
||||
|
||||
.wish-tag.paid {
|
||||
background: linear-gradient(135deg, #FFD700, #FFA500);
|
||||
}
|
||||
|
||||
.wish-tag.robot {
|
||||
border: 2rpx dashed #fff;
|
||||
}
|
||||
|
||||
.wish-text {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.wish-badge {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
background: #FF4500;
|
||||
color: #fff;
|
||||
font-size: 16rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
/* 操作栏 */
|
||||
.action-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx 30rpx;
|
||||
background: rgba(255,255,255,0.95);
|
||||
box-shadow: 0 -2rpx 20rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.btn-wish {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
background: linear-gradient(135deg, #C41E3A, #E74C3C);
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 50rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-wish .icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
/* 提示区域 */
|
||||
.tips-section {
|
||||
padding: 30rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx;
|
||||
background: rgba(255,255,255,0.8);
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 弹窗 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30rpx;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 类型选择 */
|
||||
.type-selector {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.type-item {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
border: 2rpx solid #eee;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.type-item.active {
|
||||
border-color: #C41E3A;
|
||||
background: #FFF5F5;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.type-desc {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 输入框 */
|
||||
.wish-input {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
padding: 20rpx;
|
||||
border: 2rpx solid #eee;
|
||||
border-radius: 16rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-count {
|
||||
text-align: right;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
/* 商品选择 */
|
||||
.product-section {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.product-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx;
|
||||
border: 2rpx solid #eee;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.product-item.active {
|
||||
border-color: #C41E3A;
|
||||
background: #FFF5F5;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #C41E3A;
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.modal-footer button {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
border-radius: 50rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: #fff;
|
||||
border: 2rpx solid #C41E3A;
|
||||
color: #C41E3A;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #C41E3A, #E74C3C);
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
Reference in New Issue
Block a user