项目初始化
This commit is contained in:
王宏豪
2026-09-03 16:49:59 +08:00
commit d7fc8c8982
40 changed files with 2688 additions and 0 deletions

74
pages/home/home.js Normal file
View File

@@ -0,0 +1,74 @@
const api = require('../../utils/api');
const auth = require('../../utils/auth');
function errMessage(err) {
if (err && typeof err === 'object') {
if (err.detail) return String(err.detail);
if (err.error) return String(err.error);
if (err.message) return String(err.message);
}
return '加载失败,请稍后再试';
}
Page({
data: {
greeting: '',
displayName: '',
encouragement: '',
modules: [],
loading: true,
error: '',
skeletonItems: [0, 1, 2, 3, 4, 5, 6, 7, 8]
},
onShow() { this.loadHome(); },
loadHome(silent) {
if (!silent) {
this.setData({ loading: true, error: '' });
}
return auth.ensureLogin()
.then((token) => {
if (!token) {
// 新用户未绑定手机号,引导去「我的」完成绑定,避免无 token 请求受保护接口导致 401
this.setData({ loading: false, error: '请先完成微信绑定后再进入' });
setTimeout(() => wx.switchTab({ url: '/pages/profile/profile' }), 500);
return;
}
return api.request({ url: '/api/v1/pages/home' });
})
.then((res) => {
if (!res) return;
this.setData({
loading: false,
error: '',
greeting: res.greeting || '',
displayName: res.display_name || '',
encouragement: res.encouragement || '',
modules: (res.modules || []).filter((m) => m.visible)
});
})
.catch((err) => {
this.setData({ loading: false, error: errMessage(err) });
});
},
onPullDownRefresh() {
this.loadHome(true).then(() => {
wx.stopPullDownRefresh();
});
},
onRetry() {
this.loadHome();
},
onModuleTap(e) {
const url = e.currentTarget.dataset.url;
if (!url) return;
const token = auth.getToken();
const sep = url.indexOf('?') >= 0 ? '&' : '?';
const target = url + sep + 'token=' + encodeURIComponent(token);
wx.navigateTo({ url: '/pages/webview/webview?url=' + encodeURIComponent(target) });
}
});

5
pages/home/home.json Normal file
View File

@@ -0,0 +1,5 @@
{
"usingComponents": {},
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}

42
pages/home/home.wxml Normal file
View File

@@ -0,0 +1,42 @@
<view class="page">
<block wx:if="{{loading}}">
<view class="hero hero-skeleton"></view>
<view class="grid">
<view class="module module-skeleton" wx:for="{{skeletonItems}}" wx:key="*this">
<view class="sk sk-circle"></view>
<view class="sk sk-line w-70"></view>
<view class="sk sk-line w-40"></view>
</view>
</view>
</block>
<block wx:else>
<view class="hero">
<view class="hero-chip" wx:if="{{greeting}}">{{greeting}}</view>
<view class="hero-name">{{displayName || '欢迎回来'}}</view>
<view class="hero-enc" wx:if="{{encouragement}}">{{encouragement}}</view>
</view>
<view class="error-bar" wx:if="{{error}}">
<text class="error-text">{{error}}</text>
<view class="error-retry" hover-class="link-hover" bindtap="onRetry">重试</view>
</view>
<view class="grid">
<view
class="module"
wx:for="{{modules}}"
wx:key="id"
hover-class="module-hover"
data-url="{{item.url}}"
bindtap="onModuleTap"
>
<view class="module-icon">{{item.icon}}</view>
<view class="module-label">{{item.label}}</view>
<view class="module-desc">{{item.desc}}</view>
</view>
</view>
<view class="empty" wx:if="{{modules.length === 0 && !error}}">暂无功能入口</view>
</block>
</view>

156
pages/home/home.wxss Normal file
View File

@@ -0,0 +1,156 @@
.page {
padding: 32rpx;
min-height: 100vh;
box-sizing: border-box;
background: #FDFBF7;
}
/* ---- Hero 品牌头图 ---- */
.hero {
position: relative;
overflow: hidden;
padding: 48rpx 40rpx 56rpx;
border-radius: 24rpx;
background: linear-gradient(135deg, #2C1810 0%, #4A2B1D 55%, #6B3F24 100%);
box-shadow: 0 12rpx 32rpx rgba(44, 24, 16, 0.18);
color: #FDFBF7;
}
.hero-chip {
display: inline-block;
padding: 10rpx 22rpx;
border-radius: 999rpx;
background: rgba(198, 123, 48, 0.18);
color: #E3B37C;
font-size: 22rpx;
margin-bottom: 22rpx;
}
.hero-name {
font-size: 48rpx;
font-weight: 700;
color: #FFFFFF;
letter-spacing: 2rpx;
}
.hero-enc {
margin-top: 14rpx;
font-size: 26rpx;
color: #E8DCCB;
line-height: 1.6;
}
.hero-skeleton {
min-height: 240rpx;
background: linear-gradient(90deg, #EFE6D9 25%, #FAF4EC 50%, #EFE6D9 75%);
background-size: 200% 100%;
animation: shimmer 1.4s ease-in-out infinite;
}
/* ---- 模块网格 ---- */
.grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 32rpx;
}
.module {
width: 31%;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
background: #FFF;
border-radius: 22rpx;
padding: 30rpx 12rpx 26rpx;
box-shadow: 0 6rpx 24rpx rgba(44, 24, 16, 0.06);
transition: transform 0.18s ease, opacity 0.18s ease;
}
.module-hover {
transform: scale(1.03);
opacity: 0.9;
}
.module-icon {
width: 84rpx;
height: 84rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #F6EFE6;
font-size: 40rpx;
flex-shrink: 0;
}
.module-label {
margin-top: 16rpx;
max-width: 100%;
font-size: 28rpx;
font-weight: 700;
color: #2C1810;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.module-desc {
margin-top: 8rpx;
max-width: 100%;
font-size: 22rpx;
color: #8B7A6A;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ---- 骨架屏 ---- */
.module-skeleton {
box-shadow: none;
}
.sk {
background: #EDE3D6;
border-radius: 10rpx;
}
.sk-circle {
width: 84rpx;
height: 84rpx;
border-radius: 50%;
margin-bottom: 16rpx;
}
.sk-line {
height: 24rpx;
margin-top: 12rpx;
}
.w-70 { width: 70%; }
.w-40 { width: 40%; }
/* ---- 错误 / 空状态 ---- */
.error-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
margin-top: 32rpx;
padding: 22rpx 24rpx;
border-radius: 14rpx;
background: #FDEAEA;
color: #C0392B;
font-size: 24rpx;
}
.error-text {
flex: 1;
min-width: 0;
}
.error-retry {
flex-shrink: 0;
color: #C67B30;
font-weight: 700;
padding: 4rpx 0 4rpx 16rpx;
}
.link-hover { opacity: 0.7; }
.empty {
padding: 80rpx 0;
text-align: center;
color: #B6A996;
font-size: 26rpx;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}