修复主页显示问题
This commit is contained in:
parent
3108a7b903
commit
5c64931308
@ -98,30 +98,32 @@ onMounted(loadAdmins)
|
|||||||
<el-button type="primary" :loading="submitting" @click="createAdmin">创建管理员</el-button>
|
<el-button type="primary" :loading="submitting" @click="createAdmin">创建管理员</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel table-panel">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<h2>管理员列表</h2>
|
<h2>管理员列表</h2>
|
||||||
<el-button @click="loadAdmins">刷新</el-button>
|
<el-button @click="loadAdmins">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="admins" v-loading="loading" border>
|
<div class="table-wrap">
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<el-table :data="admins" v-loading="loading" border table-layout="auto" :fit="true">
|
||||||
<el-table-column prop="username" label="用户名" min-width="140" />
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
<el-table-column prop="roleText" label="角色" width="140" />
|
<el-table-column prop="username" label="用户名" min-width="160" />
|
||||||
<el-table-column prop="statusText" label="状态" width="100" />
|
<el-table-column prop="roleText" label="角色" width="140" />
|
||||||
<el-table-column prop="last_login" label="最后登录" min-width="180" />
|
<el-table-column prop="statusText" label="状态" width="100" />
|
||||||
<el-table-column prop="created_at" label="创建时间" min-width="180" />
|
<el-table-column prop="last_login" label="最后登录" min-width="180" />
|
||||||
<el-table-column label="操作" width="220" fixed="right">
|
<el-table-column prop="created_at" label="创建时间" min-width="180" />
|
||||||
<template #default="scope">
|
<el-table-column label="操作" width="240" fixed="right">
|
||||||
<div class="row-actions">
|
<template #default="scope">
|
||||||
<el-button size="small" @click="resetPassword(scope.row)">重置密码</el-button>
|
<div class="row-actions">
|
||||||
<el-button size="small" type="warning" @click="toggleStatus(scope.row)">
|
<el-button size="small" @click="resetPassword(scope.row)">重置密码</el-button>
|
||||||
{{ scope.row.is_active === 1 ? '停用' : '启用' }}
|
<el-button size="small" type="warning" @click="toggleStatus(scope.row)">
|
||||||
</el-button>
|
{{ scope.row.is_active === 1 ? '停用' : '启用' }}
|
||||||
</div>
|
</el-button>
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
</template>
|
||||||
</el-table>
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -134,11 +136,13 @@ onMounted(loadAdmins)
|
|||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 320px 1fr;
|
grid-template-columns: minmax(320px, 360px) minmax(0, 1fr);
|
||||||
gap: 24px;
|
gap: 20px;
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
|
min-width: 0;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
@ -151,19 +155,30 @@ onMounted(loadAdmins)
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-panel {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-wrap {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.row-actions {
|
.row-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1200px) {
|
||||||
.layout {
|
.layout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
@ -171,13 +186,12 @@ onMounted(loadAdmins)
|
|||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.page {
|
.page {
|
||||||
padding: 20px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -29,8 +29,11 @@ class MatchLikeResponse(BaseModel):
|
|||||||
|
|
||||||
class MyMatchItem(BaseModel):
|
class MyMatchItem(BaseModel):
|
||||||
model_config = ConfigDict(extra="ignore")
|
model_config = ConfigDict(extra="ignore")
|
||||||
match_id: int
|
match_id: int | str
|
||||||
matched_at: str
|
matched_at: str
|
||||||
|
match_status: str
|
||||||
|
match_status_text: str
|
||||||
|
fail_reason: str | None = None
|
||||||
other_user: dict
|
other_user: dict
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -360,7 +360,7 @@ async def like_user(
|
|||||||
|
|
||||||
async def list_my_matches(session: AsyncSession, current_user: User) -> list[dict]:
|
async def list_my_matches(session: AsyncSession, current_user: User) -> list[dict]:
|
||||||
other_user = aliased(User)
|
other_user = aliased(User)
|
||||||
stmt = (
|
match_stmt = (
|
||||||
select(Match, other_user)
|
select(Match, other_user)
|
||||||
.join(
|
.join(
|
||||||
other_user,
|
other_user,
|
||||||
@ -371,17 +371,30 @@ async def list_my_matches(session: AsyncSession, current_user: User) -> list[dic
|
|||||||
)
|
)
|
||||||
.where(or_(Match.user_a_id == current_user.id, Match.user_b_id == current_user.id))
|
.where(or_(Match.user_a_id == current_user.id, Match.user_b_id == current_user.id))
|
||||||
)
|
)
|
||||||
result = await session.execute(stmt)
|
match_result = await session.execute(match_stmt)
|
||||||
rows = result.all()
|
match_rows = match_result.all()
|
||||||
|
|
||||||
|
like_stmt = (
|
||||||
|
select(MatchLike, other_user)
|
||||||
|
.join(other_user, other_user.id == MatchLike.to_user_id)
|
||||||
|
.where(MatchLike.from_user_id == current_user.id)
|
||||||
|
)
|
||||||
|
like_result = await session.execute(like_stmt)
|
||||||
|
like_rows = like_result.all()
|
||||||
|
|
||||||
items: list[dict] = []
|
items: list[dict] = []
|
||||||
for match, joined_user in rows:
|
matched_user_ids: set[int] = set()
|
||||||
|
for match, joined_user in match_rows:
|
||||||
if joined_user.id == current_user.id:
|
if joined_user.id == current_user.id:
|
||||||
continue
|
continue
|
||||||
|
matched_user_ids.add(joined_user.id)
|
||||||
items.append(
|
items.append(
|
||||||
{
|
{
|
||||||
"match_id": match.id,
|
"match_id": match.id,
|
||||||
"matched_at": match.matched_at.isoformat(),
|
"matched_at": match.matched_at.isoformat(),
|
||||||
|
"match_status": "success",
|
||||||
|
"match_status_text": "匹配成功",
|
||||||
|
"fail_reason": None,
|
||||||
"other_user": {
|
"other_user": {
|
||||||
"user_id": joined_user.id,
|
"user_id": joined_user.id,
|
||||||
"nickname": joined_user.nickname,
|
"nickname": joined_user.nickname,
|
||||||
@ -392,6 +405,30 @@ async def list_my_matches(session: AsyncSession, current_user: User) -> list[dic
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for like, joined_user in like_rows:
|
||||||
|
if joined_user.id == current_user.id or joined_user.id in matched_user_ids:
|
||||||
|
continue
|
||||||
|
reverse_like = await _has_liked(session, joined_user.id, current_user.id)
|
||||||
|
items.append(
|
||||||
|
{
|
||||||
|
"match_id": f"like-{like.id}",
|
||||||
|
"matched_at": like.created_at.isoformat(),
|
||||||
|
"match_status": "success" if reverse_like else "pending",
|
||||||
|
"match_status_text": "匹配成功" if reverse_like else "待回应",
|
||||||
|
"fail_reason": None if reverse_like else "等待对方回应",
|
||||||
|
"other_user": {
|
||||||
|
"user_id": joined_user.id,
|
||||||
|
"nickname": joined_user.nickname,
|
||||||
|
"avatar_url": joined_user.avatar_url,
|
||||||
|
"education": joined_user.education,
|
||||||
|
"city": joined_user.city,
|
||||||
|
"hobbies": joined_user.hobbies,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
items.sort(key=lambda item: item["matched_at"], reverse=True)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ from app.models.user import User
|
|||||||
|
|
||||||
|
|
||||||
ALLOWED_IMAGE_TYPES = {"jpeg", "png"}
|
ALLOWED_IMAGE_TYPES = {"jpeg", "png"}
|
||||||
UPLOAD_ROOT = Path(__file__).resolve().parents[2] / "static" / "uploads"
|
UPLOAD_ROOT = Path(__file__).resolve().parent.parent / "static" / "uploads"
|
||||||
|
|
||||||
|
|
||||||
def _validate_image(content: bytes) -> str:
|
def _validate_image(content: bytes) -> str:
|
||||||
|
|||||||
@ -38,13 +38,26 @@ Page({
|
|||||||
|
|
||||||
applyHomeData(homeData) {
|
applyHomeData(homeData) {
|
||||||
const { activities = [], announcements = [], members = [] } = homeData || {}
|
const { activities = [], announcements = [], members = [] } = homeData || {}
|
||||||
|
const safeActivities = activities.slice(0, 5).map((item) => ({
|
||||||
|
...item,
|
||||||
|
id: item.id != null ? String(item.id) : '',
|
||||||
|
title: item.title || '未命名活动',
|
||||||
|
start_time: item.start_time || '时间待定',
|
||||||
|
cover_image: item.cover_image || '',
|
||||||
|
remainText: this.getRemainText(item)
|
||||||
|
}))
|
||||||
|
const safeAnnouncements = announcements.slice(0, 3).map((item) => ({
|
||||||
|
...item,
|
||||||
|
id: item.id != null ? String(item.id) : ''
|
||||||
|
}))
|
||||||
|
const safeMembers = members.slice(0, 6).map((item) => this.normalizeMember(item))
|
||||||
this.setData({
|
this.setData({
|
||||||
activities,
|
activities: safeActivities,
|
||||||
announcements,
|
announcements: safeAnnouncements,
|
||||||
members,
|
members: safeMembers,
|
||||||
featuredMembers: members.slice(0, 3),
|
featuredMembers: safeMembers.slice(0, 3),
|
||||||
heroAnnouncementText: announcements.length ? announcements[0].title : '暂无公告',
|
heroAnnouncementText: safeAnnouncements.length ? safeAnnouncements[0].title : '暂无公告',
|
||||||
heroAnnouncementId: announcements.length ? announcements[0].id : null
|
heroAnnouncementId: safeAnnouncements.length ? safeAnnouncements[0].id : null
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -72,19 +85,9 @@ Page({
|
|||||||
const safeAnnouncements = Array.isArray(announcements) ? announcements : []
|
const safeAnnouncements = Array.isArray(announcements) ? announcements : []
|
||||||
const safeMembers = Array.isArray(members) ? members : []
|
const safeMembers = Array.isArray(members) ? members : []
|
||||||
const nextData = {
|
const nextData = {
|
||||||
activities: safeActivities.map((item) => ({
|
activities: safeActivities.slice(0, 5),
|
||||||
...item,
|
|
||||||
remainText: this.getRemainText(item)
|
|
||||||
})),
|
|
||||||
announcements: safeAnnouncements,
|
announcements: safeAnnouncements,
|
||||||
members: safeMembers.slice(0, 6).map((item) => ({
|
members: safeMembers.slice(0, 6).map((item) => this.normalizeMember(item))
|
||||||
...item,
|
|
||||||
nicknameText: item.nickname || '匿名用户',
|
|
||||||
cityText: item.city || '城市未知',
|
|
||||||
personalityTagsText: (Array.isArray(item.personality_tags) ? item.personality_tags : []).slice(0, 3).join(' / ') || '性格待完善',
|
|
||||||
introText: item.self_intro || '对方还没有填写更多公开信息',
|
|
||||||
birthYearRangeText: item.birth_year_range || '年龄未知'
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
this.applyHomeData(nextData)
|
this.applyHomeData(nextData)
|
||||||
this.saveCachedHomeData(nextData)
|
this.saveCachedHomeData(nextData)
|
||||||
@ -98,6 +101,23 @@ Page({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
normalizeMember(item) {
|
||||||
|
const member = item || {}
|
||||||
|
const personalityTags = Array.isArray(member.personality_tags) ? member.personality_tags : []
|
||||||
|
const userId = member.user_id ?? member.id ?? member.member_id
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
user_id: userId != null ? String(userId) : '',
|
||||||
|
nicknameText: member.nickname || member.name || '匿名用户',
|
||||||
|
cityText: member.city || member.location || '城市未知',
|
||||||
|
personality_tags: personalityTags,
|
||||||
|
personalityTagsText: personalityTags.slice(0, 3).join(' / ') || '性格待完善',
|
||||||
|
introText: member.self_intro || member.intro || '对方还没有填写更多公开信息',
|
||||||
|
birthYearRangeText: member.birth_year_range || member.birth_year || '年龄未知',
|
||||||
|
avatar_blur_url: member.avatar_blur_url || member.avatar_url || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getRemainText(item) {
|
getRemainText(item) {
|
||||||
const maleRemain = Math.max((item.capacity_male || 0) - (item.registered_male || 0), 0)
|
const maleRemain = Math.max((item.capacity_male || 0) - (item.registered_male || 0), 0)
|
||||||
const femaleRemain = Math.max((item.capacity_female || 0) - (item.registered_female || 0), 0)
|
const femaleRemain = Math.max((item.capacity_female || 0) - (item.registered_female || 0), 0)
|
||||||
@ -117,11 +137,11 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
openMemberCard(e) {
|
openMemberCard(e) {
|
||||||
const userId = e.currentTarget.dataset.id
|
const userId = e.currentTarget.dataset.id != null ? String(e.currentTarget.dataset.id) : ''
|
||||||
const activeMember = this.data.members.find((item) => item.user_id === userId) || null
|
const activeMember = this.data.members.find((item) => String(item.user_id) === userId) || null
|
||||||
this.setData({
|
this.setData({
|
||||||
activeMember,
|
activeMember,
|
||||||
activeMemberTagsText: activeMember && activeMember.personality_tags ? activeMember.personality_tags.join(' / ') : ''
|
activeMemberTagsText: activeMember && Array.isArray(activeMember.personality_tags) ? activeMember.personality_tags.join(' / ') : ''
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
<view class="page home-page">
|
<view class="page home-page">
|
||||||
<swiper class="banner-swiper" indicator-dots="{{activities.length > 1}}" autoplay="{{activities.length > 1}}" circular="{{activities.length > 1}}" interval="4000">
|
<swiper class="banner-swiper" indicator-dots="{{activities.length > 1}}" autoplay="{{activities.length > 1}}" circular="{{activities.length > 1}}" interval="4000">
|
||||||
<swiper-item wx:for="{{activities}}" wx:key="id">
|
<block wx:for="{{activities}}" wx:for-item="activity" wx:key="id">
|
||||||
<view class="banner-card" data-id="{{item.id}}" bindtap="goActivityDetail">
|
<swiper-item>
|
||||||
<image wx:if="{{item.cover_image}}" class="banner-image" src="{{item.cover_image}}" mode="aspectFill" />
|
<view class="banner-card" data-id="{{activity.id}}" bindtap="goActivityDetail">
|
||||||
<view wx:else class="banner-image banner-image-placeholder">活动</view>
|
<image wx:if="{{activity.cover_image}}" class="banner-image" src="{{activity.cover_image}}" mode="aspectFill" />
|
||||||
<view class="banner-overlay">
|
<view wx:else class="banner-image banner-image-placeholder">活动</view>
|
||||||
<view class="banner-tag">热度活动</view>
|
<view class="banner-overlay">
|
||||||
<view class="banner-title">{{item.title}}</view>
|
<view class="banner-tag">热度活动</view>
|
||||||
<view class="banner-meta">{{item.start_time}}</view>
|
<view class="banner-title">{{activity.title}}</view>
|
||||||
<view class="banner-remain">{{item.remainText}}</view>
|
<view class="banner-meta">{{activity.start_time}}</view>
|
||||||
|
<view class="banner-remain">{{activity.remainText}}</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</swiper-item>
|
||||||
</swiper-item>
|
</block>
|
||||||
</swiper>
|
</swiper>
|
||||||
|
|
||||||
<view class="entry-grid">
|
<view class="entry-grid">
|
||||||
@ -38,19 +40,24 @@
|
|||||||
<view class="section-title">优质推荐</view>
|
<view class="section-title">优质推荐</view>
|
||||||
<text class="section-tip" bindtap="goMatch">更多推荐</text>
|
<text class="section-tip" bindtap="goMatch">更多推荐</text>
|
||||||
</view>
|
</view>
|
||||||
<swiper class="member-swiper" indicator-dots="{{featuredMembers.length > 1}}" autoplay="{{featuredMembers.length > 1}}" circular="{{featuredMembers.length > 1}}" interval="4500">
|
<view wx:if="{{featuredMembers && featuredMembers.length}}">
|
||||||
<swiper-item wx:for="{{featuredMembers}}" wx:key="user_id">
|
<swiper class="member-swiper" indicator-dots="{{featuredMembers.length > 1}}" autoplay="{{featuredMembers.length > 1}}" circular="{{featuredMembers.length > 1}}" interval="4500">
|
||||||
<view class="featured-member" data-id="{{item.user_id}}" bindtap="openMemberCard">
|
<block wx:for="{{featuredMembers}}" wx:for-item="member" wx:key="user_id">
|
||||||
<image wx:if="{{item.avatar_blur_url}}" class="featured-member-image" src="{{item.avatar_blur_url}}" mode="aspectFill" />
|
<swiper-item>
|
||||||
<view wx:else class="featured-member-image featured-member-placeholder">用户</view>
|
<view wx:if="{{member && member.user_id}}" class="featured-member" data-id="{{member.user_id}}" bindtap="openMemberCard">
|
||||||
<view class="featured-member-overlay">
|
<image wx:if="{{member.avatar_blur_url}}" class="featured-member-image" src="{{member.avatar_blur_url}}" mode="aspectFill" />
|
||||||
<view class="featured-member-name">{{item.nicknameText}}</view>
|
<view wx:else class="featured-member-image featured-member-placeholder">用户</view>
|
||||||
<view class="featured-member-meta">{{item.cityText}}</view>
|
<view class="featured-member-overlay">
|
||||||
<view class="featured-member-intro">{{item.introText}}</view>
|
<view class="featured-member-name">{{member.nicknameText}}</view>
|
||||||
</view>
|
<view class="featured-member-meta">{{member.cityText}}</view>
|
||||||
</view>
|
<view class="featured-member-intro">{{member.introText}}</view>
|
||||||
</swiper-item>
|
</view>
|
||||||
</swiper>
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</block>
|
||||||
|
</swiper>
|
||||||
|
</view>
|
||||||
|
<view wx:else class="featured-empty">暂无优质推荐</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view wx:if="{{activeMember}}" class="member-mask" bindtap="closeMemberCard">
|
<view wx:if="{{activeMember}}" class="member-mask" bindtap="closeMemberCard">
|
||||||
|
|||||||
@ -165,22 +165,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.member-swiper {
|
.member-swiper {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
min-height: 420rpx;
|
height: 520rpx;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.member-swiper swiper-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 520rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.featured-member {
|
.featured-member {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
width: 100%;
|
||||||
min-height: 420rpx;
|
height: 520rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 28rpx;
|
border-radius: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.featured-member-image {
|
.featured-member-image {
|
||||||
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 520rpx;
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +305,22 @@
|
|||||||
|
|
||||||
@media (max-height: 700px) {
|
@media (max-height: 700px) {
|
||||||
.member-swiper,
|
.member-swiper,
|
||||||
.featured-member {
|
.member-swiper swiper-item,
|
||||||
min-height: 360rpx;
|
.featured-member,
|
||||||
|
.featured-member-image {
|
||||||
|
height: 420rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 380px) {
|
||||||
|
.section-card-fill {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-swiper,
|
||||||
|
.member-swiper swiper-item,
|
||||||
|
.featured-member,
|
||||||
|
.featured-member-image {
|
||||||
|
height: 420rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,60 @@
|
|||||||
const request = require('../../utils/request')
|
const request = require('../../utils/request')
|
||||||
|
|
||||||
|
const DEMO_MATCH_DETAILS = {
|
||||||
|
'demo-success-001': {
|
||||||
|
matched_at: '2026-04-16 19:25',
|
||||||
|
other_user: {
|
||||||
|
nickname: '林晓晴',
|
||||||
|
city: '上海',
|
||||||
|
education: '硕士',
|
||||||
|
hobbies: ['摄影', '咖啡', '旅行'],
|
||||||
|
job_industry: '互联网',
|
||||||
|
job_company: '星云科技',
|
||||||
|
income_range: '20k-30k',
|
||||||
|
self_intro: '喜欢用镜头记录生活,周末常去城市周边拍照或找一家安静的咖啡馆。'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'demo-success-002': {
|
||||||
|
matched_at: '2026-04-15 21:10',
|
||||||
|
other_user: {
|
||||||
|
nickname: '周子然',
|
||||||
|
city: '杭州',
|
||||||
|
education: '本科',
|
||||||
|
hobbies: ['跑步', '桌游'],
|
||||||
|
job_industry: '设计',
|
||||||
|
job_company: '原点工作室',
|
||||||
|
income_range: '15k-20k',
|
||||||
|
self_intro: '白天做视觉设计,晚上会去夜跑。喜欢轻松但有想法的交流。'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'demo-failed-001': {
|
||||||
|
matched_at: '2026-04-14 18:42',
|
||||||
|
other_user: {
|
||||||
|
nickname: '陈予安',
|
||||||
|
city: '深圳',
|
||||||
|
education: '博士',
|
||||||
|
hobbies: ['音乐', '滑雪'],
|
||||||
|
job_industry: '医疗',
|
||||||
|
job_company: '南山医院',
|
||||||
|
income_range: '30k以上',
|
||||||
|
self_intro: '工作日主要在医院,周末喜欢听现场音乐和去滑雪场放松。'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'demo-failed-002': {
|
||||||
|
matched_at: '2026-04-13 12:08',
|
||||||
|
other_user: {
|
||||||
|
nickname: '许静宜',
|
||||||
|
city: '成都',
|
||||||
|
education: '本科',
|
||||||
|
hobbies: ['烘焙', '读书'],
|
||||||
|
job_industry: '教育',
|
||||||
|
job_company: '启航学院',
|
||||||
|
income_range: '10k-15k',
|
||||||
|
self_intro: '喜欢研究甜品配方,也会在周末去图书馆看书。'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
loading: true,
|
loading: true,
|
||||||
@ -8,7 +63,7 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
const matchId = options.match_id ? Number(options.match_id) : null
|
const matchId = options.match_id ? String(options.match_id) : null
|
||||||
this.setData({ matchId })
|
this.setData({ matchId })
|
||||||
if (!matchId) {
|
if (!matchId) {
|
||||||
wx.showToast({ title: '缺少匹配信息', icon: 'none' })
|
wx.showToast({ title: '缺少匹配信息', icon: 'none' })
|
||||||
@ -18,9 +73,17 @@ Page({
|
|||||||
this.loadDetail()
|
this.loadDetail()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getDemoDetail(matchId) {
|
||||||
|
return DEMO_MATCH_DETAILS[matchId] || null
|
||||||
|
},
|
||||||
|
|
||||||
async loadDetail() {
|
async loadDetail() {
|
||||||
try {
|
try {
|
||||||
const detail = await request({ url: `/matches/${this.data.matchId}/detail` })
|
const isDemoMatch = String(this.data.matchId).startsWith('demo-')
|
||||||
|
const detail = isDemoMatch ? this.getDemoDetail(this.data.matchId) : await request({ url: `/matches/${this.data.matchId}/detail` })
|
||||||
|
if (!detail) {
|
||||||
|
throw new Error('暂无详情数据')
|
||||||
|
}
|
||||||
const otherUser = detail.other_user || {}
|
const otherUser = detail.other_user || {}
|
||||||
this.setData({
|
this.setData({
|
||||||
userInfo: {
|
userInfo: {
|
||||||
|
|||||||
@ -5,6 +5,8 @@ const GENDER_MAP = {
|
|||||||
2: 'female'
|
2: 'female'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MATCH_STORAGE_KEY = 'my_matches_records'
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
source: 'manual',
|
source: 'manual',
|
||||||
@ -195,6 +197,40 @@ Page({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getMatchRecordsFromStorage() {
|
||||||
|
return wx.getStorageSync(MATCH_STORAGE_KEY) || []
|
||||||
|
},
|
||||||
|
|
||||||
|
saveMatchRecord(record) {
|
||||||
|
const records = this.getMatchRecordsFromStorage()
|
||||||
|
const existingIndex = records.findIndex((item) => item.match_id === record.match_id)
|
||||||
|
const nextRecords = existingIndex >= 0 ? records.map((item, index) => (index === existingIndex ? record : item)) : [record, ...records]
|
||||||
|
wx.setStorageSync(MATCH_STORAGE_KEY, nextRecords)
|
||||||
|
},
|
||||||
|
|
||||||
|
syncMatchRecord(candidate, status, extra = {}) {
|
||||||
|
const matchId = String(candidate.user_id)
|
||||||
|
const baseRecord = {
|
||||||
|
match_id: matchId,
|
||||||
|
matched_at: new Date().toLocaleString().replaceAll('/', '-').slice(0, 16),
|
||||||
|
match_status: status,
|
||||||
|
match_status_text: status === 'success' ? '匹配成功' : '匹配失败',
|
||||||
|
fail_reason: extra.fail_reason || '',
|
||||||
|
other_user: {
|
||||||
|
nickname: candidate.nickname,
|
||||||
|
city: candidate.city,
|
||||||
|
education: candidate.education,
|
||||||
|
hobbies: candidate.personality_tags || [],
|
||||||
|
job_industry: candidate.job_industry,
|
||||||
|
job_company: candidate.job_company,
|
||||||
|
income_range: candidate.income_range,
|
||||||
|
self_intro: candidate.self_intro,
|
||||||
|
avatar_url: candidate.avatar_url || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.saveMatchRecord(baseRecord)
|
||||||
|
},
|
||||||
|
|
||||||
openCard(e) {
|
openCard(e) {
|
||||||
const candidate = this.getCurrentCandidate() || this.data.candidates.find((item) => item.user_id === e.currentTarget.dataset.id)
|
const candidate = this.getCurrentCandidate() || this.data.candidates.find((item) => item.user_id === e.currentTarget.dataset.id)
|
||||||
this.setData({
|
this.setData({
|
||||||
@ -353,11 +389,13 @@ Page({
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (result.is_mutual) {
|
if (result.is_mutual) {
|
||||||
|
this.syncMatchRecord(currentCandidate, 'success')
|
||||||
this.setData({
|
this.setData({
|
||||||
mutualMatchVisible: true,
|
mutualMatchVisible: true,
|
||||||
mutualMatchName: result.match_info.nickname || '对方'
|
mutualMatchName: result.match_info.nickname || '对方'
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
this.syncMatchRecord(currentCandidate, 'failed', { fail_reason: '对方暂未回应喜欢' })
|
||||||
wx.showToast({ title: '已发送喜欢', icon: 'success' })
|
wx.showToast({ title: '已发送喜欢', icon: 'success' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
const request = require('../../utils/request')
|
const request = require('../../utils/request')
|
||||||
|
|
||||||
|
const MATCH_STORAGE_KEY = 'my_matches_records'
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
matches: []
|
matches: []
|
||||||
@ -9,35 +11,79 @@ Page({
|
|||||||
this.loadMatches()
|
this.loadMatches()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
normalizeMatch(item) {
|
||||||
|
const otherUser = item.other_user || {}
|
||||||
|
const hobbies = Array.isArray(otherUser.hobbies) ? otherUser.hobbies : []
|
||||||
|
const matchStatus = item.match_status || (item.fail_reason ? 'pending' : 'success')
|
||||||
|
const statusMap = {
|
||||||
|
success: { text: '匹配成功', statusClass: 'success-badge', itemClass: 'success-item' },
|
||||||
|
pending: { text: '待回应', statusClass: 'pending-badge', itemClass: 'pending-item' },
|
||||||
|
failed: { text: '匹配失败', statusClass: 'failed-badge', itemClass: 'failed-item' }
|
||||||
|
}
|
||||||
|
const statusConfig = statusMap[matchStatus] || statusMap.pending
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
match_status: matchStatus,
|
||||||
|
match_status_text: item.match_status_text || statusConfig.text,
|
||||||
|
match_status_class: statusConfig.statusClass,
|
||||||
|
match_item_class: statusConfig.itemClass,
|
||||||
|
other_user: {
|
||||||
|
...otherUser,
|
||||||
|
nicknameText: otherUser.nickname ? otherUser.nickname : '匿名用户',
|
||||||
|
cityText: otherUser.city ? otherUser.city : '城市未知',
|
||||||
|
educationText: otherUser.education ? otherUser.education : '学历未知',
|
||||||
|
hobbiesText: hobbies.length ? hobbies.join('、') : '未填写爱好',
|
||||||
|
hobbies_text: hobbies.length ? hobbies.join('、') : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getStorageMatches() {
|
||||||
|
return wx.getStorageSync(MATCH_STORAGE_KEY) || []
|
||||||
|
},
|
||||||
|
|
||||||
|
mergeMatches(records, remoteMatches) {
|
||||||
|
const localRecords = Array.isArray(records) ? records : []
|
||||||
|
const remoteRecords = Array.isArray(remoteMatches) ? remoteMatches : []
|
||||||
|
const merged = [...remoteRecords]
|
||||||
|
localRecords.forEach((record) => {
|
||||||
|
const index = merged.findIndex((item) => item.match_id === record.match_id)
|
||||||
|
if (index >= 0) {
|
||||||
|
merged[index] = { ...merged[index], ...record }
|
||||||
|
} else {
|
||||||
|
merged.unshift(record)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return merged
|
||||||
|
},
|
||||||
|
|
||||||
async loadMatches() {
|
async loadMatches() {
|
||||||
try {
|
try {
|
||||||
const matches = await request({ url: '/matches/my' })
|
const remoteMatches = await request({ url: '/matches/my' })
|
||||||
const safeMatches = Array.isArray(matches) ? matches : []
|
const storedMatches = this.getStorageMatches()
|
||||||
this.setData({
|
const mergedMatches = this.mergeMatches(storedMatches, remoteMatches)
|
||||||
matches: safeMatches.map((item) => ({
|
const normalizedMatches = mergedMatches.length ? mergedMatches.map((item) => this.normalizeMatch(item)) : []
|
||||||
...item,
|
this.setData({ matches: normalizedMatches })
|
||||||
other_user: {
|
if (!remoteMatches || !remoteMatches.length) {
|
||||||
...(item.other_user || {}),
|
wx.setStorageSync(MATCH_STORAGE_KEY, mergedMatches)
|
||||||
nicknameText: item.other_user && item.other_user.nickname ? item.other_user.nickname : '匿名用户',
|
}
|
||||||
cityText: item.other_user && item.other_user.city ? item.other_user.city : '城市未知',
|
|
||||||
educationText: item.other_user && item.other_user.education ? item.other_user.education : '学历未知',
|
|
||||||
hobbiesText: item.other_user && Array.isArray(item.other_user.hobbies) ? item.other_user.hobbies.join('、') : '未填写爱好',
|
|
||||||
hobbies_text: item.other_user && Array.isArray(item.other_user.hobbies) ? item.other_user.hobbies.join('、') : ''
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Load my matches failed:', err)
|
console.error('Load my matches failed:', err)
|
||||||
wx.showToast({ title: '我的匹配加载失败', icon: 'none' })
|
const storedMatches = this.getStorageMatches()
|
||||||
this.setData({ matches: [] })
|
this.setData({ matches: storedMatches.map((item) => this.normalizeMatch(item)) })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
viewDetail(e) {
|
viewDetail(e) {
|
||||||
const matchId = e.currentTarget.dataset.matchId
|
const matchId = e.currentTarget.dataset.matchId
|
||||||
|
const status = e.currentTarget.dataset.status
|
||||||
if (!matchId) {
|
if (!matchId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (status === 'pending') {
|
||||||
|
wx.showToast({ title: '待回应记录暂不可查看详情', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
wx.navigateTo({
|
wx.navigateTo({
|
||||||
url: `/pages/match-detail/match-detail?match_id=${matchId}`
|
url: `/pages/match-detail/match-detail?match_id=${matchId}`
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
<view class="page matches-page">
|
<view class="page matches-page">
|
||||||
<view wx:if="{{matches.length}}">
|
<view wx:if="{{matches.length}}">
|
||||||
<view wx:for="{{matches}}" wx:key="match_id" class="match-item" bindtap="viewDetail" data-match-id="{{item.match_id}}">
|
<view wx:for="{{matches}}" wx:key="match_id" class="match-item {{item.match_item_class}}" bindtap="viewDetail" data-match-id="{{item.match_id}}" data-status="{{item.match_status}}">
|
||||||
<image wx:if="{{item.other_user.avatar_url}}" class="avatar" src="{{item.other_user.avatar_url}}" mode="aspectFill" />
|
<image wx:if="{{item.other_user.avatar_url}}" class="avatar" src="{{item.other_user.avatar_url}}" mode="aspectFill" />
|
||||||
<view wx:else class="avatar avatar-placeholder">用户</view>
|
<view wx:else class="avatar avatar-placeholder">用户</view>
|
||||||
<view class="content">
|
<view class="content">
|
||||||
<view class="name-row">
|
<view class="name-row">
|
||||||
<view class="name">{{item.other_user.nicknameText}}</view>
|
<view class="name">{{item.other_user.nicknameText}}</view>
|
||||||
<view class="detail-link">查看信息</view>
|
<view class="status-badge {{item.match_status_class}}">{{item.match_status_text}}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="meta">{{item.other_user.cityText}} · {{item.other_user.educationText}}</view>
|
<view class="meta">{{item.other_user.cityText}} · {{item.other_user.educationText}}</view>
|
||||||
<view class="meta">{{item.other_user.hobbiesText}}</view>
|
<view class="meta">{{item.other_user.hobbiesText}}</view>
|
||||||
|
<view wx:if="{{item.match_status === 'pending'}}" class="pending-reason">状态说明:{{item.fail_reason}}</view>
|
||||||
|
<view wx:elif="{{item.fail_reason}}" class="fail-reason">失败原因:{{item.fail_reason}}</view>
|
||||||
<view class="time">匹配时间:{{item.matched_at}}</view>
|
<view class="time">匹配时间:{{item.matched_at}}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@ -2,14 +2,44 @@
|
|||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
margin: 0 0 16rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-title {
|
||||||
|
color: #1f8a4c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed-title {
|
||||||
|
color: #d64545;
|
||||||
|
}
|
||||||
|
|
||||||
.match-item {
|
.match-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 20rpx;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.success-item {
|
||||||
|
border-left: 8rpx solid #1f8a4c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-item {
|
||||||
|
border-left: 8rpx solid #d18b00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed-item {
|
||||||
|
border-left: 8rpx solid #d64545;
|
||||||
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 120rpx;
|
width: 120rpx;
|
||||||
height: 120rpx;
|
height: 120rpx;
|
||||||
@ -41,17 +71,48 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-link {
|
.status-badge {
|
||||||
font-size: 24rpx;
|
padding: 6rpx 14rpx;
|
||||||
color: #E8593C;
|
border-radius: 999rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-badge {
|
||||||
|
color: #1f8a4c;
|
||||||
|
background: #e6f6ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-badge {
|
||||||
|
color: #d18b00;
|
||||||
|
background: #fff4db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed-badge {
|
||||||
|
color: #d64545;
|
||||||
|
background: #fdecec;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta,
|
.meta,
|
||||||
.time {
|
.time,
|
||||||
|
.fail-reason,
|
||||||
|
.pending-reason {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fail-reason {
|
||||||
|
color: #d64545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-reason {
|
||||||
|
color: #d18b00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-empty {
|
||||||
|
padding: 24rpx 0 8rpx;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
padding: 80rpx 24rpx;
|
padding: 80rpx 24rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@ -9,12 +9,39 @@ function addCacheVersion(url, version) {
|
|||||||
return `${url}${joiner}v=${encodeURIComponent(version)}`
|
return `${url}${joiner}v=${encodeURIComponent(version)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProfileCache() {
|
function isLocalDevUrl(url) {
|
||||||
return wx.getStorageSync('profile_page_cache') || null
|
return /^https?:\/\/(127\.0\.0\.1|localhost|0\.0\.0\.0)(:\d+)?\//i.test(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setProfileCache(userInfo) {
|
function upgradeToHttpsIfNeeded(url) {
|
||||||
wx.setStorageSync('profile_page_cache', userInfo)
|
if (/^http:\/\//i.test(url) && !isLocalDevUrl(url)) {
|
||||||
|
return url.replace(/^http:\/\//i, 'https://')
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveImageUrl(url) {
|
||||||
|
if (!url) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (/^https?:\/\//i.test(url)) {
|
||||||
|
return upgradeToHttpsIfNeeded(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = getApiBaseUrl().replace(/\/api\/v1\/?$/, '')
|
||||||
|
const resolved = url.startsWith('/') ? `${baseUrl}${url}` : `${baseUrl}/${url}`
|
||||||
|
return upgradeToHttpsIfNeeded(resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAvatarState(userInfo) {
|
||||||
|
const avatarUrl = resolveImageUrl(userInfo.avatar_url || '')
|
||||||
|
const avatarBlurUrl = resolveImageUrl(userInfo.avatar_blur_url || '')
|
||||||
|
|
||||||
|
return {
|
||||||
|
...userInfo,
|
||||||
|
avatar_url: avatarUrl,
|
||||||
|
avatar_blur_url: avatarBlurUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AUDIT_REQUIRED_FIELDS = ['personality_tags', 'hobbies', 'wechat_id', 'phone']
|
const AUDIT_REQUIRED_FIELDS = ['personality_tags', 'hobbies', 'wechat_id', 'phone']
|
||||||
@ -39,7 +66,7 @@ function cloneProfileCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveProfileCache(userInfo) {
|
function saveProfileCache(userInfo) {
|
||||||
wx.setStorageSync('profile_page_cache', userInfo)
|
wx.setStorageSync('profile_page_cache', normalizeAvatarState(userInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
@ -126,7 +153,8 @@ Page({
|
|||||||
hobbies: data.hobbies || [],
|
hobbies: data.hobbies || [],
|
||||||
wechat_id: data.wechat_id || '',
|
wechat_id: data.wechat_id || '',
|
||||||
phone: data.phone || '',
|
phone: data.phone || '',
|
||||||
avatar_url: data.avatar_url || ''
|
avatar_url: resolveImageUrl(data.avatar_url || ''),
|
||||||
|
avatar_blur_url: resolveImageUrl(data.avatar_blur_url || '')
|
||||||
}
|
}
|
||||||
const avatarVersion = Date.now().toString()
|
const avatarVersion = Date.now().toString()
|
||||||
this.setData({
|
this.setData({
|
||||||
@ -266,52 +294,6 @@ Page({
|
|||||||
return labels[field] || field
|
return labels[field] || field
|
||||||
},
|
},
|
||||||
|
|
||||||
validateProfilePayload(payload) {
|
|
||||||
const errors = []
|
|
||||||
const checkTextLength = (field, maxLength) => {
|
|
||||||
const value = payload[field]
|
|
||||||
if (typeof value === 'string' && value.length > maxLength) {
|
|
||||||
errors.push(`${this.getValidationLabel(field)}不能超过${maxLength}个字符`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTextLength('nickname', PROFILE_LIMITS.nickname)
|
|
||||||
checkTextLength('real_name', PROFILE_LIMITS.real_name)
|
|
||||||
checkTextLength('city', PROFILE_LIMITS.city)
|
|
||||||
checkTextLength('job_industry', PROFILE_LIMITS.job_industry)
|
|
||||||
checkTextLength('job_company', PROFILE_LIMITS.job_company)
|
|
||||||
checkTextLength('prefer_city', PROFILE_LIMITS.prefer_city)
|
|
||||||
checkTextLength('self_intro', PROFILE_LIMITS.self_intro)
|
|
||||||
checkTextLength('wechat_id', PROFILE_LIMITS.wechat_id)
|
|
||||||
checkTextLength('phone', PROFILE_LIMITS.phone)
|
|
||||||
|
|
||||||
;['personality_tags', 'hobbies'].forEach((field) => {
|
|
||||||
const list = payload[field]
|
|
||||||
const label = this.getValidationLabel(field)
|
|
||||||
if (!Array.isArray(list)) {
|
|
||||||
errors.push(`${label}格式不正确`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (list.length > PROFILE_LIMITS.listItems) {
|
|
||||||
errors.push(`${label}最多${PROFILE_LIMITS.listItems}项`)
|
|
||||||
}
|
|
||||||
list.forEach((item) => {
|
|
||||||
if (item.length > PROFILE_LIMITS.tagItem) {
|
|
||||||
errors.push(`${label}单项不能超过${PROFILE_LIMITS.tagItem}个字符`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
;['gender', 'birth_year', 'height', 'education', 'income_range'].forEach((field) => {
|
|
||||||
const value = payload[field]
|
|
||||||
if (value !== null && value !== undefined && !Number.isFinite(value)) {
|
|
||||||
errors.push(`${this.getValidationLabel(field)}格式不正确`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return errors
|
|
||||||
},
|
|
||||||
|
|
||||||
chooseAvatar() {
|
chooseAvatar() {
|
||||||
wx.chooseMedia({
|
wx.chooseMedia({
|
||||||
count: 1,
|
count: 1,
|
||||||
@ -353,20 +335,20 @@ Page({
|
|||||||
throw new Error(body.message || '上传失败')
|
throw new Error(body.message || '上传失败')
|
||||||
}
|
}
|
||||||
const rawAvatarUrl = body.data.avatar_url || ''
|
const rawAvatarUrl = body.data.avatar_url || ''
|
||||||
const avatarUrl = rawAvatarUrl.startsWith('http')
|
const rawAvatarBlurUrl = body.data.avatar_blur_url || ''
|
||||||
? rawAvatarUrl
|
const avatarUrl = resolveImageUrl(rawAvatarUrl)
|
||||||
: `${getApiBaseUrl()}${rawAvatarUrl}`
|
const avatarBlurUrl = resolveImageUrl(rawAvatarBlurUrl)
|
||||||
const avatarVersion = String(Date.now())
|
const avatarVersion = String(Date.now())
|
||||||
const form = { ...this.data.form, avatar_url: avatarUrl }
|
const form = { ...this.data.form, avatar_url: avatarUrl, avatar_blur_url: avatarBlurUrl }
|
||||||
const cachedProfile = cloneProfileCache() || {}
|
const cachedProfile = cloneProfileCache() || {}
|
||||||
const nextProfile = {
|
const nextProfile = normalizeAvatarState({
|
||||||
...cachedProfile,
|
...cachedProfile,
|
||||||
...form,
|
...form,
|
||||||
avatar_url: avatarUrl,
|
avatar_url: avatarUrl,
|
||||||
avatar_blur_url: body.data.avatar_blur_url || cachedProfile.avatar_blur_url || '',
|
avatar_blur_url: avatarBlurUrl,
|
||||||
nicknameText: form.nickname || cachedProfile.nicknameText || '未设置昵称',
|
nicknameText: form.nickname || cachedProfile.nicknameText || '未设置昵称',
|
||||||
auditStatusText: cachedProfile.auditStatusText || '待审核'
|
auditStatusText: cachedProfile.auditStatusText || '待审核'
|
||||||
}
|
})
|
||||||
saveProfileCache(nextProfile)
|
saveProfileCache(nextProfile)
|
||||||
this.setData({ form, avatarUrl: addCacheVersion(avatarUrl, avatarVersion), avatarVersion })
|
this.setData({ form, avatarUrl: addCacheVersion(avatarUrl, avatarVersion), avatarVersion })
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
@ -458,16 +440,16 @@ Page({
|
|||||||
const nextForm = { ...this.data.form, ...payload }
|
const nextForm = { ...this.data.form, ...payload }
|
||||||
const avatarVersion = this.data.avatarVersion || Date.now().toString()
|
const avatarVersion = this.data.avatarVersion || Date.now().toString()
|
||||||
const cachedProfile = cloneProfileCache() || {}
|
const cachedProfile = cloneProfileCache() || {}
|
||||||
const nextProfile = {
|
const nextProfile = normalizeAvatarState({
|
||||||
...cachedProfile,
|
...cachedProfile,
|
||||||
...nextForm,
|
...nextForm,
|
||||||
avatar_url: nextForm.avatar_url || cachedProfile.avatar_url || '',
|
avatar_url: nextForm.avatar_url || cachedProfile.avatar_url || '',
|
||||||
avatar_blur_url: nextForm.avatar_blur_url || cachedProfile.avatar_blur_url || ''
|
avatar_blur_url: nextForm.avatar_blur_url || cachedProfile.avatar_blur_url || ''
|
||||||
}
|
})
|
||||||
saveProfileCache(nextProfile)
|
saveProfileCache(nextProfile)
|
||||||
this.setData({
|
this.setData({
|
||||||
form: nextForm,
|
form: nextForm,
|
||||||
avatarUrl: addCacheVersion(nextForm.avatar_url || '', avatarVersion),
|
avatarUrl: addCacheVersion(nextProfile.avatar_url || '', avatarVersion),
|
||||||
avatarVersion
|
avatarVersion
|
||||||
})
|
})
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const request = require('../../utils/request')
|
const request = require('../../utils/request')
|
||||||
|
const { getApiBaseUrl } = require('../../utils/constants')
|
||||||
|
|
||||||
function addCacheVersion(url, version) {
|
function addCacheVersion(url, version) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
@ -8,6 +9,38 @@ function addCacheVersion(url, version) {
|
|||||||
return `${url}${joiner}v=${encodeURIComponent(version)}`
|
return `${url}${joiner}v=${encodeURIComponent(version)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLocalDevUrl(url) {
|
||||||
|
return /^https?:\/\/(127\.0\.0\.1|localhost|0\.0\.0\.0)(:\d+)?\//i.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
function upgradeToHttpsIfNeeded(url) {
|
||||||
|
if (/^http:\/\//i.test(url) && !isLocalDevUrl(url)) {
|
||||||
|
return url.replace(/^http:\/\//i, 'https://')
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveImageUrl(url) {
|
||||||
|
if (!url) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (/^https?:\/\//i.test(url)) {
|
||||||
|
return upgradeToHttpsIfNeeded(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = getApiBaseUrl().replace(/\/api\/v1\/?$/, '')
|
||||||
|
const resolved = url.startsWith('/') ? `${baseUrl}${url}` : `${baseUrl}/${url}`
|
||||||
|
return upgradeToHttpsIfNeeded(resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAvatarState(userInfo) {
|
||||||
|
return {
|
||||||
|
...userInfo,
|
||||||
|
avatar_url: resolveImageUrl(userInfo.avatar_url || ''),
|
||||||
|
avatar_blur_url: resolveImageUrl(userInfo.avatar_blur_url || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -32,7 +65,7 @@ Page({
|
|||||||
try {
|
try {
|
||||||
const userInfo = await request({ url: '/users/me' })
|
const userInfo = await request({ url: '/users/me' })
|
||||||
this.applyUserInfo(userInfo)
|
this.applyUserInfo(userInfo)
|
||||||
wx.setStorageSync('profile_page_cache', userInfo)
|
wx.setStorageSync('profile_page_cache', normalizeAvatarState(userInfo))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Load profile failed:', err)
|
console.error('Load profile failed:', err)
|
||||||
if (!this.data.userInfo) {
|
if (!this.data.userInfo) {
|
||||||
@ -47,13 +80,14 @@ Page({
|
|||||||
|
|
||||||
applyUserInfo(userInfo) {
|
applyUserInfo(userInfo) {
|
||||||
const avatarVersion = Date.now().toString()
|
const avatarVersion = Date.now().toString()
|
||||||
|
const normalized = normalizeAvatarState(userInfo)
|
||||||
this.setData({
|
this.setData({
|
||||||
userInfo: {
|
userInfo: {
|
||||||
...userInfo,
|
...normalized,
|
||||||
avatar_url: addCacheVersion(userInfo.avatar_url || '', avatarVersion),
|
avatar_url: addCacheVersion(normalized.avatar_url, avatarVersion),
|
||||||
avatar_blur_url: addCacheVersion(userInfo.avatar_blur_url || '', avatarVersion),
|
avatar_blur_url: addCacheVersion(normalized.avatar_blur_url, avatarVersion),
|
||||||
nicknameText: userInfo.nickname || '未设置昵称',
|
nicknameText: normalized.nickname || '未设置昵称',
|
||||||
auditStatusText: this.data.auditTextMap[userInfo.audit_status] || '未知'
|
auditStatusText: this.data.auditTextMap[normalized.audit_status] || '未知'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
{
|
{
|
||||||
"rules": []
|
"rules": [
|
||||||
|
{
|
||||||
|
"action": "allow",
|
||||||
|
"page": "*"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user