init
项目初始化
This commit is contained in:
74
pages/home/home.js
Normal file
74
pages/home/home.js
Normal 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
5
pages/home/home.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark"
|
||||
}
|
||||
42
pages/home/home.wxml
Normal file
42
pages/home/home.wxml
Normal 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
156
pages/home/home.wxss
Normal 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; }
|
||||
}
|
||||
Reference in New Issue
Block a user