chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
@@ -0,0 +1,17 @@
<template>
<!-- #ifdef APP -->
<scroll-view style="flex:1">
<!-- #endif -->
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script setup>
</script>
<style>
</style>
@@ -0,0 +1,613 @@
<template>
<scroll-view class="page" :show-scrollbar="false" style="padding-bottom: 140rpx;">
<view class="-status-bar"></view>
<!-- 照片墙 -->
<swiper class="photo-swiper" :indicator-dots="true" :circular="true" v-if="userInfo.albumThumbs.length > 0">
<swiper-item v-for="(img, i) in userInfo.albumThumbs" :key="i">
<image :src="img" mode="aspectFill" class="photo"></image>
</swiper-item>
</swiper>
<view v-else class="photo-swiper photo-empty">
<uni-icons type="person" size="80" color="#ccc"></uni-icons>
</view>
<!-- 昵称 / 年龄 / 在线 -->
<view class="head">
<text class="nickname">{{ userInfo.nickname }}</text>
<text class="age" v-if="userInfo.age > 0">{{ userInfo.age }}岁</text>
<text class="online" v-if="userInfo.isOnline">在线</text>
</view>
<!-- 标签切换 -->
<view class="tabsbox" style="justify-content: space-around;">
<text class="tabsbox-item" style="flex:1"
:class="currentTab == 'about' ? 'tabsbox-active tabsbox-active-txt' : ''"
@click="switchTab('about')">关于TA</text>
<text class="tabsbox-item" style="flex:1"
:class="currentTab == 'moments' ? 'tabsbox-active tabsbox-active-txt' : ''"
@click="switchTab('moments')">动态 ({{ userInfo.profile.momentCount }})</text>
</view>
<!-- 关于TA -->
<view v-if="currentTab == 'about'">
<view class="panel">
<text class="panel-title">个人资料</text>
<view class="panel-content">
<text class="panel-text" v-if="userInfo.profile.age > 0">{{ userInfo.profile.age }}岁</text>
<text class="panel-text" v-if="userInfo.profile.height > 0">{{ userInfo.profile.height }}cm</text>
<text class="panel-text" v-if="userInfo.profile.weight > 0">{{ userInfo.profile.weight }}kg</text>
<text class="panel-text" v-if="userInfo.profile.live">{{ userInfo.profile.live }}</text>
<text class="panel-text" v-if="userInfo.profile.hometown">{{ userInfo.profile.hometown }}</text>
<text class="panel-text" v-if="userInfo.profile.education">{{ userInfo.profile.education }}</text>
<text class="panel-text" v-if="userInfo.profile.profession">{{ userInfo.profile.profession }}</text>
<text class="panel-text" v-if="userInfo.profile.constellation">{{ userInfo.profile.constellation }}</text>
<text class="panel-text" v-if="userInfo.profile.annualIncome">{{ userInfo.profile.annualIncome }}</text>
</view>
</view>
<view class="panel" v-if="userInfo.profile.bio">
<text class="panel-title">个性签名</text>
<view class="panel-content" style="flex-flow: column; align-items: flex-start;">
<text class="bio">{{ userInfo.profile.bio }}</text>
</view>
</view>
<view class="panel">
<text class="panel-title">收到的礼物</text>
<view class="panel-content" style="flex-flow: column; align-items: flex-start;">
<text class="bio tip">在聊天中赠送礼物,表达你的心意~</text>
</view>
</view>
</view>
<!-- 动态 -->
<view v-if="currentTab == 'moments'">
<view class="panel">
<view class="panel-content" style="flex-flow: column; align-items: center;">
<text class="bio tip">该用户暂未公开动态</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="tabbar-op">
<view class="tabbar-item" @click="onFollowClick">
<uni-icons :type="isFollowed ? 'star-filled' : 'star'" size="40"
:color="isFollowed ? '#FFB400' : '#666'"></uni-icons>
<text class="tabbar-text">{{ isFollowed ? '已关注' : '关注' }}</text>
</view>
<view class="tabbar-item" @click="onChatClick">
<uni-icons type="chat" size="40" color="#666"></uni-icons>
<text class="tabbar-text">私聊</text>
</view>
<view class="tabbar-item" @click="onGiftClick">
<uni-icons type="gift" size="40" color="#666"></uni-icons>
<text class="tabbar-text">礼物</text>
</view>
<view class="tabbar-item tabbar-hion" @click="onAccostClick">
<uni-icons type="heart" size="40" color="#FF5C8A"></uni-icons>
<text class="tabbar-text">搭讪</text>
</view>
</view>
<!-- 送礼弹窗 -->
<view v-if="showGift" class="gift-mask" @click="closeGift">
<view class="gift-sheet" @click.stop>
<view class="gift-sheet-head">
<text class="gift-sheet-title">赠送礼物</text>
<uni-icons type="close" size="22" color="#999" @click="closeGift"></uni-icons>
</view>
<scroll-view class="gift-sheet-list" direction="vertical" :show-scrollbar="false">
<view v-for="(g, i) in gifts" :key="g.id" class="gift-sheet-cell"
:class="{ active: selectedGift != null && selectedGift.id == g.id }" @click="selectGift(g)">
<text class="gift-sheet-name">{{ g.name }}</text>
<text class="gift-sheet-price">{{ g.price }}金币</text>
</view>
<view v-if="gifts.length == 0" class="gift-sheet-empty">
<text class="empty-text">暂无可赠送的礼物</text>
</view>
</scroll-view>
<view class="gift-sheet-count">
<text class="count-label">数量</text>
<view class="count-ctrl">
<text class="count-btn" @click="giftCount > 1 ? giftCount = giftCount - 1 : null">-</text>
<text class="count-num">{{ giftCount }}</text>
<text class="count-btn" @click="giftCount = giftCount + 1">+</text>
</view>
</view>
<view class="gift-sheet-send" @click="sendGift">
<text class="send-text">赠送({{ giftTotal }}金币)</text>
</view>
</view>
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref, reactive, computed, onLoad } from 'vue'
import { Api } from '@/common/api-service.uts'
import { getUserInfo } from '@/stores/user.uts'
type IViewProfile = {
age: number
height: number
weight: number
live: string
hometown: string
education: string
profession: string
constellation: string
annualIncome: string
bio: string
momentCount: number
uid: number
}
type IViewUser = {
uid: number
nickname: string
avatar: string
albumThumbs: string[]
age: number
isOnline: boolean
profile: IViewProfile
}
const userInfo = reactive<IViewUser>({
uid: 0,
nickname: '',
avatar: '',
albumThumbs: [],
age: 0,
isOnline: false,
profile: {
age: 0, height: 0, weight: 0, live: '', hometown: '', education: '',
profession: '', constellation: '', annualIncome: '', bio: '', momentCount: 0, uid: 0
}
})
const currentTab = ref('about')
const isFollowed = ref(false)
const targetUid = ref(0)
const switchTab = (tab : string) => {
currentTab.value = tab
}
const loadDetail = async (uid : number) => {
try {
const data = await Api.user.detail(uid)
const p = data
const city = p.getString('residecity') ?? ''
const province = p.getString('resideprovince') ?? ''
const age = p.getNumber('age') ?? 0
const photos = p.getJSONArray('photos')
const album : string[] = []
if (photos != null && photos.size() > 0) {
for (let i = 0; i < photos.size(); i++) {
const s = photos.get(i) as string
if (s != null && s.length > 0) album.push(s)
}
}
const avatar = p.getString('avatar') ?? ''
if (album.length == 0 && avatar.length > 0) album.push(avatar)
userInfo.uid = uid
userInfo.nickname = p.getString('nickname') ?? '匿名用户'
userInfo.avatar = avatar
userInfo.albumThumbs = album
userInfo.age = age
userInfo.isOnline = p.getBoolean('is_online') ?? false
userInfo.profile = {
age: age,
height: p.getNumber('height') ?? 0,
weight: p.getNumber('weight') ?? 0,
live: city.length > 0 ? city : province,
hometown: province,
education: p.getString('education') ?? '',
profession: p.getString('occupation') ?? '',
constellation: p.getString('constellation') ?? '',
annualIncome: p.getString('revenue') ?? '',
bio: p.getString('bio') ?? '',
momentCount: p.getNumber('moment_count') ?? 0,
uid: uid
}
isFollowed.value = (p.getNumber('is_followed') ?? 0) == 1
} catch (e : any) {
uni.showToast({ title: e?.message ?? '加载失败', icon: 'none' })
}
}
const meUid = () : number => {
return getUserInfo()?.uid ?? 0
}
const onFollowClick = () => {
if (targetUid.value <= 0) return
const action = isFollowed.value ? 0 : 1
Api.follow.toggle(meUid(), targetUid.value, action).then(() => {
isFollowed.value = ! isFollowed.value
uni.showToast({ title: isFollowed.value ? '已关注' : '已取消关注', icon: 'none' })
}).catch((err : any) => {
uni.showToast({ title: err?.message ?? '操作失败', icon: 'none' })
})
}
const onChatClick = () => {
if (targetUid.value <= 0) return
uni.navigateTo({ url: `/pages/chat/chat?uid=${targetUid.value}` })
}
const onGiftClick = () => {
showGift.value = true
loadGifts()
}
// ===== 送礼弹窗 =====
type IGift = {
id : number
name : string
icon : string
price : number
}
const showGift = ref<boolean>(false)
const gifts = reactive<IGift[]>([])
const selectedGift = ref<IGift | null>(null)
const giftCount = ref<number>(1)
const giftLoading = ref<boolean>(false)
const giftTotal = computed<number>(() => {
const p = selectedGift.value?.price ?? 0
return p * giftCount.value
})
const loadGifts = () => {
if (giftLoading.value) return
giftLoading.value = true
Api.gift.list()
.then((res : any) => {
const list = (res as Array<IGift>) ?? []
gifts.splice(0, gifts.length)
list.forEach((g : IGift) => gifts.push(g))
if (gifts.length > 0 && selectedGift.value == null) {
selectedGift.value = gifts[0]
}
})
.catch(() => {
uni.showToast({ title: '礼物加载失败', icon: 'none' })
})
.finally(() => { giftLoading.value = false })
}
const selectGift = (g : IGift) => {
selectedGift.value = g
}
const sendGift = () => {
if (selectedGift.value == null) {
uni.showToast({ title: '请选择礼物', icon: 'none' })
return
}
if (targetUid.value <= 0) return
uni.showLoading({ title: '赠送中...', mask: true })
Api.gift.send({
to_uid: targetUid.value,
gift_id: selectedGift.value.id,
count: giftCount.value,
message: ''
})
.then(() => {
uni.hideLoading()
showGift.value = false
uni.showToast({ title: '赠送成功', icon: 'success' })
})
.catch((e : any) => {
uni.hideLoading()
const msg = (e as UTSJSONObject)?.get('message') as string
uni.showToast({ title: msg ?? '赠送失败', icon: 'none' })
})
}
const closeGift = () => {
showGift.value = false
}
const onAccostClick = () => {
if (targetUid.value <= 0) return
Api.message.send({
receiver_id: targetUid.value,
receiver_type: 0,
content: '你好,很高兴认识你~'
}).then(() => {
uni.showToast({ title: '搭讪已送达', icon: 'success' })
}).catch((err : any) => {
uni.showToast({ title: err?.message ?? '搭讪失败', icon: 'none' })
})
}
onLoad((options : any) => {
const uid = Number(options?.uid ?? options?.id ?? 0)
targetUid.value = uid
if (uid > 0) {
loadDetail(uid)
}
})
</script>
<style>
.page {
display: flex;
flex-direction: column;
background-color: #f6f6f6;
min-height: 100%;
}
.photo-swiper {
width: 100%;
height: 760rpx;
background-color: #eee;
}
.photo-empty {
display: flex;
align-items: center;
justify-content: center;
}
.photo {
width: 100%;
height: 100%;
}
.head {
display: flex;
flex-direction: row;
align-items: center;
background: #fff;
padding: 24rpx 30rpx;
}
.nickname {
font-size: 38rpx;
font-weight: bold;
color: #222;
}
.age {
font-size: 30rpx;
color: #666;
margin-left: 16rpx;
}
.online {
font-size: 22rpx;
color: #fff;
background: #4CD964;
border-radius: 20rpx;
padding: 4rpx 14rpx;
margin-left: 16rpx;
}
.tabsbox {
display: flex;
flex-direction: row;
background: #fff;
margin-top: 2rpx;
padding: 20rpx 0;
}
.tabsbox-item {
text-align: center;
font-size: 28rpx;
color: #666;
}
.tabsbox-active {
font-weight: bold;
color: #FF5C8A;
}
.panel {
display: flex;
flex-direction: column;
background: #fff;
margin-top: 16rpx;
padding: 20rpx 30rpx;
}
.panel-title {
font-size: 28rpx;
font-weight: bold;
padding: 10rpx 0;
border-bottom: 1rpx solid #eee;
}
.panel-content {
display: flex;
flex-flow: row wrap;
padding: 24rpx 0;
}
.panel-text {
font-size: 24rpx;
border-radius: 25rpx;
padding: 6rpx 18rpx;
margin: 6rpx;
background: #f3f3f3;
color: #555;
}
.bio {
font-size: 26rpx;
color: #444;
line-height: 40rpx;
}
.tip {
color: #999;
}
.tabbar-op {
width: 100%;
position: fixed;
bottom: var(--window-bottom);
display: flex;
justify-content: space-between;
flex-flow: row nowrap;
align-items: center;
padding: 16rpx 20rpx;
background-color: #fff;
border-top: 1rpx solid #e2e2e2;
}
.tabbar-item {
width: 16%;
display: flex;
justify-content: center;
flex-flow: column nowrap;
align-items: center;
}
.tabbar-text {
font-size: 22rpx;
color: #666;
margin-top: 4rpx;
}
.tabbar-hion {
width: 40%;
flex-flow: row nowrap;
background-image: linear-gradient(to bottom, #ffd2fb, #ffaaff);
border-radius: 40rpx;
padding: 16rpx 0;
}
.gift-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 999;
}
.gift-sheet {
width: 100%;
background-color: #fff;
border-top-left-radius: 28rpx;
border-top-right-radius: 28rpx;
padding: 24rpx 30rpx calc(30rpx + var(--uni-safe-area-inset-bottom));
display: flex;
flex-direction: column;
}
.gift-sheet-head {
display: flex;
flex-flow: row nowrap;
align-items: center;
justify-content: space-between;
padding-bottom: 16rpx;
}
.gift-sheet-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.gift-sheet-list {
max-height: 420rpx;
display: flex;
flex-flow: row wrap;
}
.gift-sheet-cell {
width: 22%;
margin: 10rpx 1.5%;
padding: 20rpx 0;
border-radius: 16rpx;
background-color: #f7f7f7;
display: flex;
flex-direction: column;
align-items: center;
}
.gift-sheet-cell.active {
background-color: #fff0ec;
border: 2rpx solid #FF6B6B;
}
.gift-sheet-name {
font-size: 26rpx;
color: #333;
}
.gift-sheet-price {
font-size: 22rpx;
color: #FF6B6B;
margin-top: 6rpx;
}
.gift-sheet-empty {
width: 100%;
padding: 60rpx 0;
display: flex;
align-items: center;
justify-content: center;
}
.gift-sheet-count {
display: flex;
flex-flow: row nowrap;
align-items: center;
justify-content: space-between;
margin: 20rpx 0;
}
.count-label {
font-size: 28rpx;
color: #333;
}
.count-ctrl {
display: flex;
flex-flow: row nowrap;
align-items: center;
}
.count-btn {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
background-color: #f0f0f0;
text-align: center;
line-height: 60rpx;
font-size: 36rpx;
color: #666;
}
.count-num {
min-width: 60rpx;
text-align: center;
font-size: 30rpx;
color: #333;
}
.gift-sheet-send {
height: 92rpx;
border-radius: 46rpx;
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
display: flex;
align-items: center;
justify-content: center;
}
.send-text {
font-size: 32rpx;
font-weight: bold;
color: #fff;
}
</style>