- 使用精美许愿树 PNG 图片作为背景 - 许愿标签用 CSS 动画实现摇摆效果 - 添加萤火虫和花瓣氛围动画 - 预设树冠挂载点,标签位置更自然 - 深色主题 + 金色/红色/绿色标签 - 边缘暗角让树从中心发光 - 最近心愿横向滚动列表
345 lines
8.8 KiB
JavaScript
345 lines
8.8 KiB
JavaScript
const { request } = require('../../utils/request.js');
|
|
|
|
// 预设的树冠挂载点(百分比坐标)
|
|
const CANOPY_SLOTS = [
|
|
{ x: 16, y: 34 },
|
|
{ x: 27, y: 21 },
|
|
{ x: 39, y: 14 },
|
|
{ x: 52, y: 12 },
|
|
{ x: 63, y: 18 },
|
|
{ x: 74, y: 26 },
|
|
{ x: 84, y: 38 },
|
|
{ x: 12, y: 50 },
|
|
{ x: 23, y: 46 },
|
|
{ x: 34, y: 40 },
|
|
{ x: 66, y: 40 },
|
|
{ x: 78, y: 50 },
|
|
{ x: 88, y: 30 },
|
|
{ x: 45, y: 30 },
|
|
];
|
|
|
|
// 标签颜色配置
|
|
const TAG_COLORS = {
|
|
amber: {
|
|
color: '#ffd700',
|
|
bgColor: 'rgba(255, 215, 0, 0.9)',
|
|
borderColor: 'rgba(255, 215, 0, 0.6)',
|
|
shadowColor: 'rgba(255, 215, 0, 0.4)',
|
|
},
|
|
red: {
|
|
color: '#ff6b6b',
|
|
bgColor: 'rgba(255, 107, 107, 0.9)',
|
|
borderColor: 'rgba(255, 107, 107, 0.6)',
|
|
shadowColor: 'rgba(255, 107, 107, 0.4)',
|
|
},
|
|
jade: {
|
|
color: '#51cf66',
|
|
bgColor: 'rgba(81, 207, 102, 0.9)',
|
|
borderColor: 'rgba(81, 207, 102, 0.6)',
|
|
shadowColor: 'rgba(81, 207, 102, 0.4)',
|
|
},
|
|
};
|
|
|
|
// 生成伪随机数(用于萤火虫和花瓣位置)
|
|
function seeded(i, salt) {
|
|
const v = Math.sin((i + 1) * 12.9898 + salt * 78.233) * 43758.5453;
|
|
return v - Math.floor(v);
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
wishes: [],
|
|
recentWishes: [],
|
|
loading: true,
|
|
showCreateModal: false,
|
|
wishContent: '',
|
|
wishType: 'free',
|
|
maxFreeLength: 20,
|
|
maxPaidLength: 100,
|
|
products: [],
|
|
selectedProduct: null,
|
|
fireflies: [],
|
|
petals: [],
|
|
},
|
|
|
|
onLoad() {
|
|
this.initAmbiance();
|
|
this.loadWishes();
|
|
this.loadProducts();
|
|
},
|
|
|
|
onPullDownRefresh() {
|
|
this.loadWishes().then(() => {
|
|
wx.stopPullDownRefresh();
|
|
});
|
|
},
|
|
|
|
// 初始化氛围效果
|
|
initAmbiance() {
|
|
// 萤火虫
|
|
const fireflies = [];
|
|
for (let i = 0; i < 22; i++) {
|
|
fireflies.push({
|
|
left: seeded(i, 1) * 100,
|
|
top: seeded(i, 2) * 100,
|
|
size: 2 + seeded(i, 3) * 4,
|
|
duration: 2.5 + seeded(i, 4) * 3.5,
|
|
delay: seeded(i, 5) * 4,
|
|
});
|
|
}
|
|
|
|
// 花瓣
|
|
const petals = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
petals.push({
|
|
left: seeded(i, 6) * 100,
|
|
size: 5 + seeded(i, 7) * 6,
|
|
duration: 10 + seeded(i, 8) * 8,
|
|
delay: seeded(i, 9) * 12,
|
|
});
|
|
}
|
|
|
|
this.setData({ fireflies, petals });
|
|
},
|
|
|
|
// 加载许愿
|
|
async loadWishes() {
|
|
try {
|
|
const res = await request({
|
|
url: '/api/wish/tree/1/wishes',
|
|
method: 'GET',
|
|
});
|
|
|
|
if (res.code === 0) {
|
|
const wishes = this.formatWishes(res.data.list || []);
|
|
this.setData({
|
|
wishes,
|
|
recentWishes: wishes.slice(0, 8),
|
|
loading: false,
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error('加载许愿失败:', err);
|
|
const wishes = this.formatWishes(this.getMockWishes());
|
|
this.setData({
|
|
wishes,
|
|
recentWishes: wishes.slice(0, 8),
|
|
loading: false,
|
|
});
|
|
}
|
|
},
|
|
|
|
// 格式化许愿数据
|
|
formatWishes(list) {
|
|
const colorKeys = Object.keys(TAG_COLORS);
|
|
|
|
return list.map((wish, index) => {
|
|
const slot = this.pickSlot(index);
|
|
const colorKey = colorKeys[index % colorKeys.length];
|
|
const colors = TAG_COLORS[colorKey];
|
|
|
|
return {
|
|
id: wish.id,
|
|
content: wish.content,
|
|
author: wish.author || '匿名',
|
|
type: wish.type,
|
|
x: slot.x,
|
|
y: slot.y,
|
|
color: colors.color,
|
|
bgColor: colors.bgColor,
|
|
borderColor: colors.borderColor,
|
|
shadowColor: colors.shadowColor,
|
|
swayDuration: 3.4 + (index % 5) * 0.6,
|
|
swayDelay: (index % 7) * 0.35,
|
|
isNew: false,
|
|
};
|
|
});
|
|
},
|
|
|
|
// 选择挂载点
|
|
pickSlot(index) {
|
|
const base = CANOPY_SLOTS[index % CANOPY_SLOTS.length];
|
|
const wrap = Math.floor(index / CANOPY_SLOTS.length);
|
|
|
|
if (wrap === 0) return base;
|
|
|
|
const jitterX = ((wrap * 37) % 9) - 4;
|
|
const jitterY = ((wrap * 53) % 9) - 4;
|
|
|
|
return {
|
|
x: Math.min(92, Math.max(8, base.x + jitterX)),
|
|
y: Math.min(58, Math.max(10, base.y + jitterY)),
|
|
};
|
|
},
|
|
|
|
// 模拟许愿数据
|
|
getMockWishes() {
|
|
return [
|
|
{ id: 1, content: '愿家人平安喜乐,身体健康', author: '小满', type: 'paid' },
|
|
{ id: 2, content: '希望今年考研上岸,一战成硕', author: '阿远', type: 'free' },
|
|
{ id: 3, content: '愿世界温柔以待每一个努力的人', author: '林深', type: 'paid' },
|
|
{ id: 4, content: '早日遇见那个对的人', author: '拾光', type: 'free' },
|
|
{ id: 5, content: '祝爸妈身体硬朗,笑口常开', author: '念念', type: 'paid' },
|
|
{ id: 6, content: '愿所求皆如愿,所行皆坦途', author: '白露', type: 'free' },
|
|
{ id: 7, content: '希望新工作顺顺利利', author: '子夜', type: 'paid' },
|
|
{ id: 8, content: '愿此生尽兴,赤诚善良', author: '青禾', type: 'free' },
|
|
{ id: 9, content: '愿代码零bug,一次通过', author: '小测', type: 'paid' },
|
|
];
|
|
},
|
|
|
|
// 加载许愿商品
|
|
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 },
|
|
{ id: 2, name: '精品许愿条', price: 500, duration: 30 },
|
|
{ id: 3, name: '至尊许愿条', price: 2000, duration: 90 },
|
|
],
|
|
});
|
|
}
|
|
},
|
|
|
|
// 打开许愿弹窗
|
|
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,
|
|
treeId: 1,
|
|
},
|
|
});
|
|
|
|
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: 1,
|
|
},
|
|
});
|
|
|
|
if (res.code === 0) {
|
|
wx.showToast({ title: '许愿成功', icon: 'success' });
|
|
this.closeCreateModal();
|
|
this.loadWishes();
|
|
}
|
|
}
|
|
} 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.loadWishes();
|
|
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',
|
|
};
|
|
},
|
|
});
|