chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
|
||||
<view class="logo-box">
|
||||
<view class="logo">
|
||||
<uni-icons type="heart-filled" size="56" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="app-name">缘友</text>
|
||||
<text class="app-slogan">让相遇更有温度</text>
|
||||
</view>
|
||||
|
||||
<view class="list-container">
|
||||
<view class="list-item" @click="toast('用户协议')">
|
||||
<text class="list-text">用户协议</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="list-item" @click="toast('隐私政策')">
|
||||
<text class="list-text">隐私政策</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="list-item" @click="toast('去评分')">
|
||||
<text class="list-text">给我们评分</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="intro">
|
||||
<text class="intro-text">
|
||||
缘友是一款专注于真实社交的交友应用,基于同城与兴趣为你推荐合适的人。
|
||||
我们倡导真诚、友善的互动,帮助你遇见那个对的人。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<text class="version">缘友 v{{ appVersion }}</text>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
const appVersion = ref('2.5.0')
|
||||
|
||||
function toast(msg : string) {
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.logo-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80rpx 0 50rpx;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 36rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10rpx 30rpx rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.app-slogan {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
margin: 0 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.list-item:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.list-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin: 30rpx;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 26rpx;
|
||||
color: #888;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.version {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #bbb;
|
||||
margin: 40rpx 0 80rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
||||
<view class="list">
|
||||
<view v-for="(m, idx) in list" :key="idx" class="moment-item" @click="openDetail(m)">
|
||||
<view class="m-head">
|
||||
<image class="m-avatar" :src="imgUrl(m.userAvatar)" mode="aspectFill"></image>
|
||||
<view class="m-meta">
|
||||
<text class="m-name">{{ m.userName }}</text>
|
||||
<text class="m-time">{{ formatTime(m.time) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="m-content">{{ m.content }}</text>
|
||||
<view v-if="m.images && m.images.length > 0" class="m-images">
|
||||
<image v-for="(img, i) in m.images.slice(0,3)" :key="i" class="m-img"
|
||||
:src="imgUrl(img)" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="m-foot">
|
||||
<view class="m-stat"><uni-icons type="heart" size="22" color="#FF2D55"></uni-icons>
|
||||
<text>{{ m.likes }}</text></view>
|
||||
<view class="m-stat"><uni-icons type="chatbubble" size="22" color="#999"></uni-icons>
|
||||
<text>{{ m.comments }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="list.length === 0 && !loading" class="empty-state">
|
||||
<uni-icons type="star" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">还没有收藏任何动态</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
const BASE = 'https://dev.xixingwl.cn'
|
||||
|
||||
type IMoment = {
|
||||
id : number
|
||||
uid : number
|
||||
userName : string
|
||||
userAvatar : string
|
||||
content : string
|
||||
images : string[]
|
||||
videoUrl : string
|
||||
type : string
|
||||
time : number
|
||||
likes : number
|
||||
comments : number
|
||||
shares : number
|
||||
}
|
||||
|
||||
const list = reactive<IMoment[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
|
||||
function imgUrl(src : string) : string {
|
||||
if (! src) return ''
|
||||
return src.startsWith('http') ? src : (BASE + src)
|
||||
}
|
||||
|
||||
function formatTime(ts : number) : string {
|
||||
if (! ts) return ''
|
||||
const d = new Date(ts)
|
||||
const pad = (n : number) : string => n < 10 ? '0' + n : '' + n
|
||||
return `${d.getMonth() + 1}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
|
||||
function openDetail(m : IMoment) {
|
||||
uni.navigateTo({ url: '/pages/moment/details?id=' + m.id })
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
Api.moment.collectedList({ page: 1, page_size: 20 })
|
||||
.then((res : UTSJSONObject) => {
|
||||
const arr = (res.get('list') as Array<IMoment>) ?? []
|
||||
list.splice(0, list.length)
|
||||
arr.forEach((m : IMoment) => list.push(m))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => { loadData() })
|
||||
onShow(() => { loadData() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
.moment-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-head {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
background-color: #f0f0f0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.m-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.m-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.m-time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.m-content {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.m-images {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.m-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
margin: 0 12rpx 12rpx 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.m-foot {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.m-stat {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-right: 40rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.m-stat text {
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
||||
<view class="list">
|
||||
<view v-for="(m, idx) in list" :key="idx" class="moment-item" @click="openDetail(m)">
|
||||
<view class="m-head">
|
||||
<image class="m-avatar" :src="imgUrl(m.userAvatar)" mode="aspectFill"></image>
|
||||
<view class="m-meta">
|
||||
<text class="m-name">{{ m.userName }}</text>
|
||||
<text class="m-time">{{ formatTime(m.time) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="m-content">{{ m.content }}</text>
|
||||
<view v-if="m.images && m.images.length > 0" class="m-images">
|
||||
<image v-for="(img, i) in m.images.slice(0,3)" :key="i" class="m-img"
|
||||
:src="imgUrl(img)" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="m-foot">
|
||||
<view class="m-stat"><uni-icons type="heart" size="22" color="#FF2D55"></uni-icons>
|
||||
<text>{{ m.likes }}</text></view>
|
||||
<view class="m-stat"><uni-icons type="chatbubble" size="22" color="#999"></uni-icons>
|
||||
<text>{{ m.comments }}</text></view>
|
||||
<view class="m-stat"><uni-icons type="star" size="22" color="#FF9500"></uni-icons>
|
||||
<text>{{ m.collectCount }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="list.length === 0 && !loading" class="empty-state">
|
||||
<uni-icons type="pyq" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">你还没有发布动态</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
const BASE = 'https://dev.xixingwl.cn'
|
||||
|
||||
type IMoment = {
|
||||
id : number
|
||||
uid : number
|
||||
userName : string
|
||||
userAvatar : string
|
||||
content : string
|
||||
images : string[]
|
||||
videoUrl : string
|
||||
type : string
|
||||
time : number
|
||||
likes : number
|
||||
comments : number
|
||||
shares : number
|
||||
collectCount : number
|
||||
}
|
||||
|
||||
const list = reactive<IMoment[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
|
||||
function imgUrl(src : string) : string {
|
||||
if (! src) return ''
|
||||
return src.startsWith('http') ? src : (BASE + src)
|
||||
}
|
||||
|
||||
function formatTime(ts : number) : string {
|
||||
if (! ts) return ''
|
||||
const d = new Date(ts)
|
||||
const pad = (n : number) : string => n < 10 ? '0' + n : '' + n
|
||||
return `${d.getMonth() + 1}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
|
||||
function openDetail(m : IMoment) {
|
||||
uni.navigateTo({ url: '/pages/moment/details?id=' + m.id })
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
Api.moment.mine({ page: 1, page_size: 50 })
|
||||
.then((res : UTSJSONObject) => {
|
||||
const arr = (res.get('list') as Array<IMoment>) ?? []
|
||||
list.splice(0, list.length)
|
||||
arr.forEach((m : IMoment) => {
|
||||
const obj = (m as UTSJSONObject)
|
||||
const stats = obj.get('stats') as UTSJSONObject | null
|
||||
m.collectCount = stats != null ? ((stats.get('collect_count') as number) ?? 0) : 0
|
||||
list.push(m)
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => { loadData() })
|
||||
onShow(() => { loadData() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
.moment-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-head {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
background-color: #f0f0f0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.m-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.m-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.m-time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.m-content {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.m-images {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.m-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
margin: 0 12rpx 12rpx 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.m-foot {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.m-stat {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-right: 40rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.m-stat text {
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh"
|
||||
@scrolltolower="onReachBottom">
|
||||
<view class="list">
|
||||
<view v-for="(item, index) in records" :key="index" class="user-item" @click="goProfile(item.id)">
|
||||
<image class="avatar" :src="item.avatar" mode="aspectFill"></image>
|
||||
<view class="info">
|
||||
<view class="name-row">
|
||||
<text class="nickname">{{ item.nickname }}</text>
|
||||
<view v-if="item.is_mutual == 1" class="mutual-tag">
|
||||
<text class="mutual-txt">互相关注</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="time">{{ item.follow_time }}</text>
|
||||
</view>
|
||||
<view v-if="item.is_mutual == 0" class="follow-btn" @click.stop="follow(item)">
|
||||
<text class="follow-txt">回关</text>
|
||||
</view>
|
||||
<uni-icons v-else type="right" size="18" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
|
||||
<view v-if="records.length == 0 && !loading" class="empty-state">
|
||||
<uni-icons type="staff" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">还没有粉丝</text>
|
||||
</view>
|
||||
<view v-if="loading" class="loading-tip">
|
||||
<text class="loading-txt">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
import { getUserInfo } from '@/stores/user.uts'
|
||||
|
||||
type IFollowUser = {
|
||||
id : number
|
||||
nickname : string
|
||||
avatar : string
|
||||
is_mutual : number
|
||||
follow_time : string
|
||||
}
|
||||
|
||||
const records = reactive<IFollowUser[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
const page = ref<number>(1)
|
||||
const size = ref<number>(20)
|
||||
const total = ref<number>(0)
|
||||
const uid = ref<number>(0)
|
||||
|
||||
function loadData(reset : boolean) {
|
||||
if (loading.value) return
|
||||
if (uid.value <= 0) {
|
||||
const info = getUserInfo()
|
||||
uid.value = info?.uid ?? 0
|
||||
}
|
||||
if (uid.value <= 0) return
|
||||
loading.value = true
|
||||
if (reset) page.value = 1
|
||||
Api.follow.followerList({ uid: uid.value, page: page.value, size: size.value })
|
||||
.then((res : UTSJSONObject) => {
|
||||
total.value = (res.get('total') as number) ?? 0
|
||||
const list = (res.get('list') as Array<IFollowUser>) ?? []
|
||||
if (reset) records.splice(0, records.length)
|
||||
list.forEach((r : IFollowUser) => records.push(r))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData(true)
|
||||
}
|
||||
|
||||
function onReachBottom() {
|
||||
if (records.length >= total.value) return
|
||||
page.value = page.value + 1
|
||||
loadData(false)
|
||||
}
|
||||
|
||||
function follow(item : IFollowUser) {
|
||||
if (uid.value <= 0 || item.id <= 0) return
|
||||
Api.follow.toggle(uid.value, item.id, 1)
|
||||
.then(() => {
|
||||
item.is_mutual = 1
|
||||
uni.showToast({ title: '已回关', icon: 'success' })
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
function goProfile(targetUid : number) {
|
||||
if (targetUid <= 0) return
|
||||
uni.navigateTo({ url: '/pages/user/profile?uid=' + targetUid })
|
||||
}
|
||||
|
||||
onMounted(() => { loadData(true) })
|
||||
onShow(() => { loadData(true) })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
background-color: #eee;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mutual-tag {
|
||||
margin-left: 14rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 16rpx;
|
||||
background-color: #fff0ec;
|
||||
}
|
||||
|
||||
.mutual-txt {
|
||||
font-size: 20rpx;
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.follow-btn {
|
||||
padding: 10rpx 28rpx;
|
||||
border-radius: 30rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
}
|
||||
|
||||
.follow-txt {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.loading-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.loading-txt {
|
||||
font-size: 24rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="-status-bar"></view>
|
||||
<view class="header">
|
||||
<text class="title">意见反馈</text>
|
||||
</view>
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;" class="form">
|
||||
<view class="section">
|
||||
<text class="label">反馈类型</text>
|
||||
<view class="type-row">
|
||||
<view v-for="(t, idx) in types" :key="t.value" class="type-item" :class="{ active: type == t.value }" @click="type = t.value">
|
||||
<text class="type-text">{{ t.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<text class="label">反馈内容</text>
|
||||
<textarea class="textarea" v-model="content" placeholder="请描述您遇到的问题或建议(最多500字)" maxlength="500" />
|
||||
<text class="counter">{{ content.length }}/500</text>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<text class="label">联系方式(选填)</text>
|
||||
<input class="input" v-model="contact" placeholder="手机号/微信,方便我们回复您" />
|
||||
</view>
|
||||
|
||||
<view class="submit-btn" :class="{ disabled: submitting }" @click="onSubmit">
|
||||
<text class="submit-text">{{ submitting ? '提交中...' : '提交反馈' }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
type IType = { label : string, value : number }
|
||||
|
||||
const types = reactive<IType[]>([
|
||||
{ label: '功能建议', value: 1 },
|
||||
{ label: '投诉举报', value: 2 },
|
||||
{ label: '其他', value: 3 }
|
||||
])
|
||||
const type = ref<number>(1)
|
||||
const content = ref<string>('')
|
||||
const contact = ref<string>('')
|
||||
const submitting = ref<boolean>(false)
|
||||
|
||||
function onSubmit() {
|
||||
if (submitting.value) return
|
||||
if (content.value.trim() == '') {
|
||||
uni.showToast({ title: '请输入反馈内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
Api.feedback.save({ type: type.value, content: content.value, contact: contact.value })
|
||||
.then(() => {
|
||||
uni.showToast({ title: '提交成功', icon: 'success' })
|
||||
content.value = ''
|
||||
contact.value = ''
|
||||
})
|
||||
.catch((e : any) => {
|
||||
const msg = e && e.message ? e.message : '提交失败'
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
})
|
||||
.finally(() => { submitting.value = false })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.-status-bar {
|
||||
height: var(--status-bar-height);
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
padding: 24rpx 30rpx 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form {
|
||||
padding: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.type-row {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.type-item {
|
||||
padding: 14rpx 36rpx;
|
||||
border-radius: 30rpx;
|
||||
background-color: #f2f2f2;
|
||||
margin: 0 20rpx 16rpx 0;
|
||||
}
|
||||
|
||||
.type-item.active {
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
}
|
||||
|
||||
.type-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.type-item.active .type-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.counter {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
float: right;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin: 40rpx 30rpx 60rpx;
|
||||
height: 92rpx;
|
||||
border-radius: 46rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10rpx 30rpx rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
.submit-btn.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh"
|
||||
@scrolltolower="onReachBottom">
|
||||
<view class="list">
|
||||
<view v-for="(item, index) in records" :key="index" class="user-item" @click="goProfile(item.id)">
|
||||
<image class="avatar" :src="item.avatar" mode="aspectFill"></image>
|
||||
<view class="info">
|
||||
<view class="name-row">
|
||||
<text class="nickname">{{ item.nickname }}</text>
|
||||
<view v-if="item.is_mutual == 1" class="mutual-tag">
|
||||
<text class="mutual-txt">互相关注</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="time">{{ item.follow_time }}</text>
|
||||
</view>
|
||||
<view v-if="item.is_mutual == 0" class="follow-btn" @click.stop="follow(item)">
|
||||
<text class="follow-txt">回关</text>
|
||||
</view>
|
||||
<uni-icons v-else type="right" size="18" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
|
||||
<view v-if="records.length == 0 && !loading" class="empty-state">
|
||||
<uni-icons type="staff" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">还没有粉丝</text>
|
||||
</view>
|
||||
<view v-if="loading" class="loading-tip">
|
||||
<text class="loading-txt">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
import { getUserInfo } from '@/stores/user.uts'
|
||||
|
||||
type IFollowUser = {
|
||||
id : number
|
||||
nickname : string
|
||||
avatar : string
|
||||
is_mutual : number
|
||||
follow_time : string
|
||||
}
|
||||
|
||||
const records = reactive<IFollowUser[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
const page = ref<number>(1)
|
||||
const size = ref<number>(20)
|
||||
const total = ref<number>(0)
|
||||
const uid = ref<number>(0)
|
||||
|
||||
function loadData(reset : boolean) {
|
||||
if (loading.value) return
|
||||
if (uid.value <= 0) {
|
||||
const info = getUserInfo()
|
||||
uid.value = info?.uid ?? 0
|
||||
}
|
||||
if (uid.value <= 0) return
|
||||
loading.value = true
|
||||
if (reset) page.value = 1
|
||||
Api.follow.followerList({ uid: uid.value, page: page.value, size: size.value })
|
||||
.then((res : UTSJSONObject) => {
|
||||
total.value = (res.get('total') as number) ?? 0
|
||||
const list = (res.get('list') as Array<IFollowUser>) ?? []
|
||||
if (reset) records.splice(0, records.length)
|
||||
list.forEach((r : IFollowUser) => records.push(r))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData(true)
|
||||
}
|
||||
|
||||
function onReachBottom() {
|
||||
if (records.length >= total.value) return
|
||||
page.value = page.value + 1
|
||||
loadData(false)
|
||||
}
|
||||
|
||||
function follow(item : IFollowUser) {
|
||||
if (uid.value <= 0 || item.id <= 0) return
|
||||
Api.follow.toggle(uid.value, item.id, 1)
|
||||
.then(() => {
|
||||
item.is_mutual = 1
|
||||
uni.showToast({ title: '已回关', icon: 'success' })
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
function goProfile(targetUid : number) {
|
||||
if (targetUid <= 0) return
|
||||
uni.navigateTo({ url: '/pages/user/profile?uid=' + targetUid })
|
||||
}
|
||||
|
||||
onMounted(() => { loadData(true) })
|
||||
onShow(() => { loadData(true) })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
background-color: #eee;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mutual-tag {
|
||||
margin-left: 14rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 16rpx;
|
||||
background-color: #fff0ec;
|
||||
}
|
||||
|
||||
.mutual-txt {
|
||||
font-size: 20rpx;
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.follow-btn {
|
||||
padding: 10rpx 28rpx;
|
||||
border-radius: 30rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
}
|
||||
|
||||
.follow-txt {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.loading-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.loading-txt {
|
||||
font-size: 24rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh"
|
||||
@scrolltolower="onReachBottom">
|
||||
<view class="list">
|
||||
<view v-for="(item, index) in records" :key="index" class="user-item" @click="goProfile(item.id)">
|
||||
<image class="avatar" :src="item.avatar" mode="aspectFill"></image>
|
||||
<view class="info">
|
||||
<view class="name-row">
|
||||
<text class="nickname">{{ item.nickname }}</text>
|
||||
<view v-if="item.is_mutual == 1" class="mutual-tag">
|
||||
<text class="mutual-txt">互相关注</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="time">{{ item.follow_time }}</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="18" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
|
||||
<view v-if="records.length == 0 && !loading" class="empty-state">
|
||||
<uni-icons type="staff" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">还没有关注任何人</text>
|
||||
</view>
|
||||
<view v-if="loading" class="loading-tip">
|
||||
<text class="loading-txt">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
import { getUserInfo } from '@/stores/user.uts'
|
||||
|
||||
type IFollowUser = {
|
||||
id : number
|
||||
nickname : string
|
||||
avatar : string
|
||||
is_mutual : number
|
||||
follow_time : string
|
||||
}
|
||||
|
||||
const records = reactive<IFollowUser[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
const page = ref<number>(1)
|
||||
const size = ref<number>(20)
|
||||
const total = ref<number>(0)
|
||||
const uid = ref<number>(0)
|
||||
|
||||
function loadData(reset : boolean) {
|
||||
if (loading.value) return
|
||||
if (uid.value <= 0) {
|
||||
const info = getUserInfo()
|
||||
uid.value = info?.uid ?? 0
|
||||
}
|
||||
if (uid.value <= 0) return
|
||||
loading.value = true
|
||||
if (reset) page.value = 1
|
||||
Api.follow.followingList({ uid: uid.value, page: page.value, size: size.value })
|
||||
.then((res : UTSJSONObject) => {
|
||||
total.value = (res.get('total') as number) ?? 0
|
||||
const list = (res.get('list') as Array<IFollowUser>) ?? []
|
||||
if (reset) records.splice(0, records.length)
|
||||
list.forEach((r : IFollowUser) => records.push(r))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData(true)
|
||||
}
|
||||
|
||||
function onReachBottom() {
|
||||
if (records.length >= total.value) return
|
||||
page.value = page.value + 1
|
||||
loadData(false)
|
||||
}
|
||||
|
||||
function goProfile(targetUid : number) {
|
||||
if (targetUid <= 0) return
|
||||
uni.navigateTo({ url: '/pages/user/profile?uid=' + targetUid })
|
||||
}
|
||||
|
||||
onMounted(() => { loadData(true) })
|
||||
onShow(() => { loadData(true) })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
background-color: #eee;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mutual-tag {
|
||||
margin-left: 14rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 16rpx;
|
||||
background-color: #fff0ec;
|
||||
}
|
||||
|
||||
.mutual-txt {
|
||||
font-size: 20rpx;
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.loading-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.loading-txt {
|
||||
font-size: 24rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
||||
<!-- 汇总卡片 -->
|
||||
<view class="summary-card">
|
||||
<view class="summary-item">
|
||||
<text class="summary-num">{{ totalCount }}</text>
|
||||
<text class="summary-label">收到礼物</text>
|
||||
</view>
|
||||
<view class="summary-divider"></view>
|
||||
<view class="summary-item">
|
||||
<text class="summary-num">{{ totalValue }}</text>
|
||||
<text class="summary-label">礼物价值(金币)</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 礼物记录 -->
|
||||
<view class="list">
|
||||
<view v-for="(item, index) in records" :key="index" class="gift-item">
|
||||
<view class="gift-icon">
|
||||
<uni-icons type="gift-filled" size="30" color="#FF6B6B"></uni-icons>
|
||||
</view>
|
||||
<view class="gift-info">
|
||||
<text class="gift-name">{{ item.gift_name }} x{{ item.count }}</text>
|
||||
<text v-if="item.message.length > 0" class="gift-msg">{{ item.message }}</text>
|
||||
<text class="gift-time">{{ formatTime(item.create_at) }}</text>
|
||||
</view>
|
||||
<view class="gift-value">
|
||||
<text class="value-num">+{{ item.total_price }}</text>
|
||||
<text class="value-label">金币</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="records.length === 0 && !loading" class="empty-state">
|
||||
<uni-icons type="gift" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">还没有收到礼物哦</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
type IGiftRecord = {
|
||||
id : number
|
||||
sender_uid : number
|
||||
receiver_uid : number
|
||||
gift_id : number
|
||||
gift_name : string
|
||||
price : number
|
||||
count : number
|
||||
total_price : number
|
||||
message : string
|
||||
create_at : number
|
||||
}
|
||||
|
||||
const records = reactive<IGiftRecord[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
|
||||
const totalCount = computed<number>(() => {
|
||||
let c = 0
|
||||
records.forEach((r : IGiftRecord) => { c += r.count })
|
||||
return c
|
||||
})
|
||||
const totalValue = computed<number>(() => {
|
||||
let v = 0
|
||||
records.forEach((r : IGiftRecord) => { v += r.total_price })
|
||||
return v
|
||||
})
|
||||
|
||||
function loadData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
Api.gift.received({})
|
||||
.then((res : UTSJSONObject) => {
|
||||
const list = (res as Array<IGiftRecord>) ?? []
|
||||
records.splice(0, records.length)
|
||||
list.forEach((r : IGiftRecord) => records.push(r))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData()
|
||||
}
|
||||
|
||||
function formatTime(ts : number) : string {
|
||||
if (ts <= 0) return ''
|
||||
const d = new Date(ts * 1000)
|
||||
const m = d.getMonth() + 1
|
||||
const day = d.getDate()
|
||||
const h = d.getHours()
|
||||
const min = d.getMinutes()
|
||||
const pad = (n : number) : string => n < 10 ? '0' + n : '' + n
|
||||
return `${m}-${pad(day)} ${pad(h)}:${pad(min)}`
|
||||
}
|
||||
|
||||
onMounted(() => { loadData() })
|
||||
onShow(() => { loadData() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin: 24rpx 30rpx;
|
||||
padding: 40rpx 0;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary-num {
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 24rpx;
|
||||
color: #ffe;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.summary-divider {
|
||||
width: 2rpx;
|
||||
height: 60rpx;
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
|
||||
.gift-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.gift-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #fff0ec;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.gift-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gift-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gift-msg {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.gift-time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.gift-value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.value-num {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
.value-label {
|
||||
font-size: 22rpx;
|
||||
color: #FF6B6B;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
|
||||
<view class="hero">
|
||||
<uni-icons type="help-filled" size="48" color="#FF6B6B"></uni-icons>
|
||||
<text class="hero-title">常见问题</text>
|
||||
</view>
|
||||
|
||||
<view class="qa" v-for="(item, idx) in qaList" :key="idx">
|
||||
<view class="q" @click="toggle(idx)">
|
||||
<text class="q-text">Q:{{ item.q }}</text>
|
||||
<uni-icons :type="item.open ? 'top' : 'right'" size="18" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view v-if="item.open" class="a">
|
||||
<text class="a-text">{{ item.a }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="contact">
|
||||
<text class="contact-title">仍未解决?</text>
|
||||
<view class="contact-btn" @click="copyMail">
|
||||
<uni-icons type="email" size="20" color="#fff"></uni-icons>
|
||||
<text class="contact-btn-text">联系客服:support@xixingwl.cn</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
type QA = {
|
||||
q : string
|
||||
a : string
|
||||
open : boolean
|
||||
}
|
||||
|
||||
const qaList = reactive<QA[]>([
|
||||
{ q: '如何修改个人资料?', a: '在「我的」页面点击左上角头像区域,或在资料卡中点击编辑即可修改昵称、签名、择偶条件等信息。', open: true },
|
||||
{ q: '金币有什么用?', a: '金币可用于每日抽奖、赠送礼物给喜欢的用户,部分特权功能也需消耗金币。', open: false },
|
||||
{ q: '为什么看不到对方的在线状态?', a: '对方可能在隐私设置中隐藏了在线状态、年龄或距离,这是对方的个人选择,无法强制查看。', open: false },
|
||||
{ q: '动态被收藏/点赞会有提醒吗?', a: '当前版本点赞与收藏会累计到「我的」统计中,消息中心会同步相关互动通知。', open: false },
|
||||
{ q: '如何注销账号?', a: '进入「账号安全」页面,点击「注销账号」,按提示操作即可。注销后资料将被匿名化处理。', open: false },
|
||||
])
|
||||
|
||||
function toggle(idx : number) {
|
||||
qaList[idx].open = ! qaList[idx].open
|
||||
}
|
||||
|
||||
function copyMail() {
|
||||
uni.setClipboardData({
|
||||
data: 'support@xixingwl.cn',
|
||||
success: () => {
|
||||
uni.showToast({ title: '邮箱已复制', icon: 'success' })
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 50rpx 0 30rpx;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.qa {
|
||||
margin: 0 30rpx 16rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.q {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 24rpx;
|
||||
}
|
||||
|
||||
.q-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.a {
|
||||
padding: 0 24rpx 28rpx;
|
||||
}
|
||||
|
||||
.a-text {
|
||||
font-size: 26rpx;
|
||||
color: #888;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.contact {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 40rpx 0 80rpx;
|
||||
}
|
||||
|
||||
.contact-title {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.contact-btn {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
padding: 20rpx 36rpx;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
.contact-btn-text {
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
||||
<view class="list">
|
||||
<view v-for="(m, idx) in list" :key="idx" class="moment-item" @click="openDetail(m)">
|
||||
<view class="m-head">
|
||||
<image class="m-avatar" :src="imgUrl(m.userAvatar)" mode="aspectFill"></image>
|
||||
<view class="m-meta">
|
||||
<text class="m-name">{{ m.userName }}</text>
|
||||
<text class="m-time">{{ formatTime(m.time) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="m-content">{{ m.content }}</text>
|
||||
<view v-if="m.images && m.images.length > 0" class="m-images">
|
||||
<image v-for="(img, i) in m.images.slice(0,3)" :key="i" class="m-img"
|
||||
:src="imgUrl(img)" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="m-foot">
|
||||
<view class="m-stat"><uni-icons type="heart" size="22" color="#FF2D55"></uni-icons>
|
||||
<text>{{ m.likes }}</text></view>
|
||||
<view class="m-stat"><uni-icons type="chatbubble" size="22" color="#999"></uni-icons>
|
||||
<text>{{ m.comments }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="list.length === 0 && !loading" class="empty-state">
|
||||
<uni-icons type="eye" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">还没有浏览记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
const BASE = 'https://dev.xixingwl.cn'
|
||||
|
||||
type IMoment = {
|
||||
id : number
|
||||
uid : number
|
||||
userName : string
|
||||
userAvatar : string
|
||||
content : string
|
||||
images : string[]
|
||||
videoUrl : string
|
||||
type : string
|
||||
time : number
|
||||
likes : number
|
||||
comments : number
|
||||
shares : number
|
||||
}
|
||||
|
||||
const list = reactive<IMoment[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
|
||||
function imgUrl(src : string) : string {
|
||||
if (! src) return ''
|
||||
return src.startsWith('http') ? src : (BASE + src)
|
||||
}
|
||||
|
||||
function formatTime(ts : number) : string {
|
||||
if (! ts) return ''
|
||||
const d = new Date(ts)
|
||||
const pad = (n : number) : string => n < 10 ? '0' + n : '' + n
|
||||
return `${d.getMonth() + 1}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
|
||||
function openDetail(m : IMoment) {
|
||||
uni.navigateTo({ url: '/pages/moment/details?id=' + m.id })
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
Api.moment.historyList({ page: 1, page_size: 20 })
|
||||
.then((res : UTSJSONObject) => {
|
||||
const arr = (res.get('list') as Array<IMoment>) ?? []
|
||||
list.splice(0, list.length)
|
||||
arr.forEach((m : IMoment) => list.push(m))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => { loadData() })
|
||||
onShow(() => { loadData() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
.moment-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-head {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
background-color: #f0f0f0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.m-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.m-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.m-time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.m-content {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.m-images {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.m-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
margin: 0 12rpx 12rpx 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.m-foot {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.m-stat {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-right: 40rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.m-stat text {
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="-status-bar"></view>
|
||||
<view class="header">
|
||||
<uni-icons type="vip" size="44" color="#FFD700"></uni-icons>
|
||||
<text class="title">会员中心</text>
|
||||
<text class="sub">开通会员,尊享更多特权</text>
|
||||
</view>
|
||||
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
|
||||
<view class="plan-row">
|
||||
<view v-for="(p, idx) in plans" :key="p.id" class="plan-card" :class="{ active: selected == p.id }" @click="selected = p.id">
|
||||
<text class="plan-name">{{ p.name }}</text>
|
||||
<view class="plan-price">
|
||||
<text class="price-num">¥{{ p.price }}</text>
|
||||
<text class="price-unit">/{{ p.unit }}</text>
|
||||
</view>
|
||||
<text class="plan-tip">{{ p.tip }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="benefits">
|
||||
<text class="benefits-title">会员特权</text>
|
||||
<view class="benefit-item" v-for="(b, idx) in benefits" :key="idx">
|
||||
<uni-icons type="checkmark" size="18" color="#34C759"></uni-icons>
|
||||
<text class="benefit-text">{{ b }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="open-btn" @click="onOpen">
|
||||
<text class="open-text">立即开通</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
type IPlan = { id : number, name : string, price : number, unit : string, tip : string }
|
||||
|
||||
const selected = ref<number>(2)
|
||||
const plans = reactive<IPlan[]>([
|
||||
{ id: 1, name: '月度会员', price: 30, unit: '月', tip: '灵活体验' },
|
||||
{ id: 2, name: '季度会员', price: 78, unit: '季', tip: '省21元' },
|
||||
{ id: 3, name: '年度会员', price: 268, unit: '年', tip: '最划算' }
|
||||
])
|
||||
const benefits = reactive<string[]>([
|
||||
'无限畅聊,消息免打扰',
|
||||
'查看谁看过我 / 超级喜欢',
|
||||
'每日专属推荐位曝光',
|
||||
'专属动态置顶与标识',
|
||||
'会员专属装扮与客服优先'
|
||||
])
|
||||
|
||||
function onOpen() {
|
||||
uni.showToast({ title: '跳转支付(演示)', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.-status-bar {
|
||||
height: var(--status-bar-height);
|
||||
background: linear-gradient(135deg, #2C2C3A 0%, #1A1A24 100%);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #2C2C3A 0%, #1A1A24 100%);
|
||||
padding: 50rpx 30rpx 60rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #FFD700;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 24rpx;
|
||||
color: #bbb;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.plan-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding: 30rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.plan-card {
|
||||
width: 31%;
|
||||
padding: 30rpx 0;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.plan-card.active {
|
||||
border-color: #FFD700;
|
||||
background: linear-gradient(180deg, #FFF8E1 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
.plan-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.plan-price {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: baseline;
|
||||
margin: 16rpx 0 8rpx;
|
||||
}
|
||||
|
||||
.price-num {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #FF9500;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.plan-tip {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.benefits {
|
||||
margin: 10rpx 30rpx 0;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.benefits-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.benefit-text {
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.open-btn {
|
||||
margin: 40rpx 60rpx 60rpx;
|
||||
height: 92rpx;
|
||||
border-radius: 46rpx;
|
||||
background: linear-gradient(135deg, #FFD700 0%, #FFA000 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10rpx 30rpx rgba(255, 168, 0, 0.3);
|
||||
}
|
||||
|
||||
.open-text {
|
||||
color: #5a3d00;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,815 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="-status-bar"></view>
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="height: 100%;">
|
||||
<!-- 顶部背景 -->
|
||||
<view class="top-background"></view>
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="user-header">
|
||||
<view class="avatar-section">
|
||||
<image class="avatar" :src=" avatar" mode="aspectFill"></image>
|
||||
<!-- <text class="vip-badge" v-if="userInfo.vip">
|
||||
<uni-icons type="vip" size="16" color="#fff"></uni-icons>
|
||||
</text> -->
|
||||
<!-- <view class="online-status" v-if="userInfo.online"></view> -->
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<view class="name-section">
|
||||
<text class="nickname">{{ userInfo?.nickname }}</text>
|
||||
<!-- <text class="level-badge">Lv.{{ userInfo.level }}</text> -->
|
||||
<!-- <view class="gender-badge" :class="userInfo.gender">
|
||||
<uni-icons :type="userInfo.gender === 'male' ? 'male' : 'female'" size="12"
|
||||
:color="userInfo.gender === 'male' ? '#007AFF' : '#FF2D55'"></uni-icons>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<text class="user-id">ID: {{ userInfo?.uid }}</text>
|
||||
<!-- <text class="signature">{{ userInfo.signature !=null ?userInfo.signature : '这家伙很懒,什么都没留下' }}</text> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-row">
|
||||
<view class="stat-item" @click="navigateTo('followers')">
|
||||
<text class="stat-value">{{ formatNumber(stats.friends) }}</text>
|
||||
<text class="stat-label">缘友</text>
|
||||
</view>
|
||||
<view class="stat-item" @click="navigateTo('followers')">
|
||||
<text class="stat-value">{{ formatNumber(stats.followers) }}</text>
|
||||
<text class="stat-label">粉丝</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item" @click="navigateTo('following')">
|
||||
<text class="stat-value">{{ formatNumber(stats.following) }}</text>
|
||||
<text class="stat-label">关注</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item" @click="navigateTo('likes')">
|
||||
<text class="stat-value">{{ formatNumber(stats.likes) }}</text>
|
||||
<text class="stat-label">获赞</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 钱包和收益区域 -->
|
||||
<view class="wallet-section">
|
||||
<view class="wallet-item" @click="navigateTo('wallet')">
|
||||
<uni-icons type="wallet" size="32" color="#FF9500"></uni-icons>
|
||||
<text class="wallet-label">我的钱包</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view class="wallet-item" @click="navigateTo('income')">
|
||||
<uni-icons type="vip" size="32" color="#FF3B30"></uni-icons>
|
||||
<text class="wallet-label">会员中心</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 功能区网格 -->
|
||||
<view class="function-grid">
|
||||
<view class="grid-row">
|
||||
<view class="grid-item" @click="navigateTo('dynamic')">
|
||||
<view class="grid-icon">
|
||||
<uni-icons type="pyq" size="32" color="#ffd790"></uni-icons>
|
||||
</view>
|
||||
<text class="grid-text">我的动态</text>
|
||||
<!-- <view v-if="userInfo.newDynamic" class="badge-dot"></view> -->
|
||||
</view>
|
||||
<view class="grid-item" @click="navigateTo('collect')">
|
||||
<view class="grid-icon">
|
||||
<uni-icons type="star" size="32" color="#FF9500"></uni-icons>
|
||||
</view>
|
||||
<text class="grid-text">我的收藏</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="navigateTo('history')">
|
||||
<view class="grid-icon">
|
||||
<uni-icons type="heart" size="32" color="#FF2D55"></uni-icons>
|
||||
</view>
|
||||
<text class="grid-text">浏览记录</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="navigateTo('gift')">
|
||||
<view class="grid-icon">
|
||||
<uni-icons type="gift" size="32" color="#5856D6"></uni-icons>
|
||||
</view>
|
||||
<text class="grid-text">我的礼物</text>
|
||||
<!-- <view v-if="userInfo.newGift" class="badge-dot"></view> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="grid-row">
|
||||
<view class="grid-item" @click="navigateTo('fans')">
|
||||
<view class="grid-icon">
|
||||
<uni-icons type="person" size="32" color="#007AFF"></uni-icons>
|
||||
</view>
|
||||
<text class="grid-text">粉丝团</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="navigateTo('ranking')">
|
||||
<view class="grid-icon">
|
||||
<uni-icons type="list" size="32" color="#FFCC00"></uni-icons>
|
||||
</view>
|
||||
<text class="grid-text">排行榜</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="navigateTo('task')">
|
||||
<view class="grid-icon">
|
||||
<uni-icons type="calendar" size="32" color="#5AC8FA"></uni-icons>
|
||||
</view>
|
||||
<text class="grid-text">每日任务</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="navigateTo('lottery')">
|
||||
<view class="grid-icon">
|
||||
<uni-icons type="help" size="32" color="#AF52DE"></uni-icons>
|
||||
</view>
|
||||
<text class="grid-text">幸运抽奖</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 列表功能区 -->
|
||||
<view class="list-section">
|
||||
<text class="list-title">账号与安全</text>
|
||||
<view class="list-container">
|
||||
<view class="list-item" @click="navigateTo('security')">
|
||||
<view class="list-left">
|
||||
<uni-icons type="locked" size="20" color="#007AFF"></uni-icons>
|
||||
<text class="list-text">账号安全</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('privacy')">
|
||||
<view class="list-left">
|
||||
<uni-icons type="eye" size="20" color="#34C759"></uni-icons>
|
||||
<text class="list-text">隐私设置</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('notification')">
|
||||
<view class="list-left">
|
||||
<uni-icons type="sound" size="20" color="#FF9500"></uni-icons>
|
||||
<text class="list-text">消息通知</text>
|
||||
</view>
|
||||
<view class="list-right">
|
||||
<!-- <text class="list-desc">{{ userInfo.notification ? '已开启' : '已关闭' }}</text> -->
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="list-title">服务与支持</text>
|
||||
<view class="list-container">
|
||||
<view class="list-item" @click="navigateTo('feedback')">
|
||||
<view class="list-left">
|
||||
<uni-icons type="chatbubble" size="20" color="#5856D6"></uni-icons>
|
||||
<text class="list-text">意见反馈</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('help')">
|
||||
<view class="list-left">
|
||||
<uni-icons type="help" size="20" color="#FF3B30"></uni-icons>
|
||||
<text class="list-text">帮助中心</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('about')">
|
||||
<view class="list-left">
|
||||
<uni-icons type="info" size="20" color="#5AC8FA"></uni-icons>
|
||||
<text class="list-text">关于我们</text>
|
||||
</view>
|
||||
<view class="list-right">
|
||||
<text class="list-desc">v{{ appVersion }}</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-item" @click="showLogoutDialog">
|
||||
<view class="list-left">
|
||||
<uni-icons type="redo" size="20" color="#FF9500"></uni-icons>
|
||||
<text class="list-text logout-text">退出登录</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import userState from "@/stores/user.uts"
|
||||
import { IUserInfo } from "@/types/user.uts"
|
||||
import Api from "@/common/api-service.uts"
|
||||
|
||||
const userInfo = computed(() : IUserInfo | null => {
|
||||
return userState.info ?? null
|
||||
})
|
||||
|
||||
// 我的聚合统计(缘友/粉丝/关注/获赞/动态)
|
||||
const stats = reactive({
|
||||
friends: 0,
|
||||
followers: 0,
|
||||
following: 0,
|
||||
likes: 0,
|
||||
moments: 0
|
||||
})
|
||||
const loadStats = () => {
|
||||
Api.user.summary()
|
||||
.then((res : UTSJSONObject) => {
|
||||
stats.friends = (res.get('friends') as number) ?? 0
|
||||
stats.followers = (res.get('followers') as number) ?? 0
|
||||
stats.following = (res.get('following') as number) ?? 0
|
||||
stats.likes = (res.get('likes') as number) ?? 0
|
||||
stats.moments = (res.get('moments') as number) ?? 0
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
const avatar = computed(() => {
|
||||
return 'https://dev.xixingwl.cn' + userInfo.value?.avatar
|
||||
})
|
||||
// 响应式数据
|
||||
/* const userInfo = reactive<IUserInfo>({
|
||||
userId: '123456789',
|
||||
nickname: '心动玩家',
|
||||
avatar: "https://dev.xixingwl.cn/" + userState.user?.avatar,//'https://randomuser.me/api/portraits/men/32.jpg',
|
||||
signature: '阳光正好,微风不燥,遇见你真好~',
|
||||
gender: 'male',
|
||||
age: 25,
|
||||
level: 12,
|
||||
vip: true,
|
||||
online: true,
|
||||
balance: 888.88,
|
||||
todayIncome: 128.50,
|
||||
followers: 12560,
|
||||
following: 230,
|
||||
likes: 45620,
|
||||
newDynamic: true,
|
||||
newGift: false,
|
||||
notification: true
|
||||
} as IUserInfo) */
|
||||
|
||||
const appVersion = ref('2.5.0')
|
||||
// 加载用户信息
|
||||
const loadUserInfo = () => {
|
||||
console.log('加载用户信息...')
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 这里应该是从API获取数据
|
||||
console.log('用户信息加载完成')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
console.log('我的页面加载完成')
|
||||
// 这里可以加载用户数据
|
||||
loadUserInfo()
|
||||
loadStats()
|
||||
})
|
||||
onShow(() => {
|
||||
loadStats()
|
||||
})
|
||||
|
||||
|
||||
// 格式化数字(千分位)
|
||||
const formatNumber = (num : number) : string => {
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + 'w'
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'k'
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
// 编辑资料
|
||||
const editProfile = () => {
|
||||
console.log('编辑资料')
|
||||
uni.navigateTo({
|
||||
url: '/pages/edit-profile/edit-profile'
|
||||
})
|
||||
}
|
||||
|
||||
// 开通VIP
|
||||
const openVip = () => {
|
||||
console.log('开通VIP')
|
||||
uni.showModal({
|
||||
title: '开通VIP会员',
|
||||
content: '开通VIP享受更多特权,包括去广告、专属装扮、优先匹配等',
|
||||
confirmText: '立即开通',
|
||||
cancelText: '再想想',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: '跳转支付页面',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// VIP中心
|
||||
const vipCenter = () => {
|
||||
console.log('VIP中心')
|
||||
uni.navigateTo({
|
||||
url: '/pages/vip-center/vip-center'
|
||||
})
|
||||
}
|
||||
|
||||
// 页面跳转
|
||||
const navigateTo = (page : string) => {
|
||||
console.log('跳转到页面:', page)
|
||||
const pageUrl = ref<string>('')
|
||||
switch (page) {
|
||||
case 'wallet':
|
||||
pageUrl.value = '/pages/my/wallet';
|
||||
break;
|
||||
case 'income':
|
||||
pageUrl.value = '/pages/my/income';
|
||||
break;
|
||||
case 'dynamic':
|
||||
pageUrl.value = '/pages/my/dynamic';
|
||||
break;
|
||||
case 'collect':
|
||||
pageUrl.value = '/pages/my/collect';
|
||||
break;
|
||||
case 'history':
|
||||
pageUrl.value = '/pages/my/history';
|
||||
break;
|
||||
case 'gift':
|
||||
pageUrl.value = '/pages/my/gift';
|
||||
break;
|
||||
case 'fans':
|
||||
pageUrl.value = '/pages/my/fans';
|
||||
break;
|
||||
case 'ranking':
|
||||
pageUrl.value = '/pages/my/ranking';
|
||||
break;
|
||||
case 'task':
|
||||
pageUrl.value = '/pages/my/task';
|
||||
break;
|
||||
case 'lottery':
|
||||
pageUrl.value = '/pages/my/lottery';
|
||||
break;
|
||||
case 'security':
|
||||
pageUrl.value = '/pages/my/security';
|
||||
break;
|
||||
case 'privacy':
|
||||
pageUrl.value = '/pages/my/privacy';
|
||||
break;
|
||||
case 'notification':
|
||||
pageUrl.value = '/pages/notification/notification';
|
||||
break;
|
||||
case 'feedback':
|
||||
pageUrl.value = '/pages/my/feedback';
|
||||
break;
|
||||
case 'help':
|
||||
pageUrl.value = '/pages/my/help';
|
||||
break;
|
||||
case 'about':
|
||||
pageUrl.value = '/pages/my/about';
|
||||
break;
|
||||
case 'followers':
|
||||
pageUrl.value = '/pages/my/followers';
|
||||
break;
|
||||
case 'following':
|
||||
pageUrl.value = '/pages/my/following';
|
||||
break;
|
||||
case 'likes':
|
||||
pageUrl.value = '/pages/my/likes';
|
||||
break;
|
||||
}
|
||||
|
||||
if (pageUrl.value != '') {
|
||||
uni.navigateTo({
|
||||
url: pageUrl.value
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '页面开发中',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
}
|
||||
// 退出登录
|
||||
const logout = () => {
|
||||
console.log('退出登录')
|
||||
uni.showLoading({
|
||||
title: '退出中...',
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 清除本地登录态(token / 用户信息)
|
||||
Api.auth.logout()
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '退出成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
|
||||
// 跳转到登录页面
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
}, 1500)
|
||||
}, 500)
|
||||
}
|
||||
// 显示退出登录对话框
|
||||
const showLogoutDialog = () => {
|
||||
uni.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出当前账号吗?',
|
||||
confirmText: '退出',
|
||||
confirmColor: '#FF3B30',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
logout()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 顶部背景 */
|
||||
.top-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 300rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
border-bottom-left-radius: 40rpx;
|
||||
border-bottom-right-radius: 40rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.user-card {
|
||||
position: relative;
|
||||
margin: 40rpx 30rpx 0;
|
||||
padding: 40rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 10rpx 30rpx rgba(255, 107, 107, 0.1);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.avatar-section {
|
||||
position: relative;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid #fff;
|
||||
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.vip-badge {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
border: 4rpx solid #fff;
|
||||
}
|
||||
|
||||
.online-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background-color: #4CD964;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid #fff;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.name-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-flow: row nowrap;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 20rpx;
|
||||
max-width: 300rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
background: linear-gradient(135deg, #007AFF 0%, #5856D6 100%);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.gender-badge {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gender-badge.male {
|
||||
background-color: rgba(0, 122, 255, 0.1);
|
||||
}
|
||||
|
||||
.gender-badge.female {
|
||||
background-color: rgba(255, 45, 85, 0.1);
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.signature {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 30rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex-flow: row nowrap;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 20rpx;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2rpx;
|
||||
height: 40rpx;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.edit-btn,
|
||||
.vip-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.edit-btn:active,
|
||||
.vip-btn:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.vip-btn {
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.vip-btn.active {
|
||||
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 钱包区域 */
|
||||
.wallet-section {
|
||||
margin: 30rpx;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 5rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.wallet-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.wallet-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin: 0 20rpx 0 15rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.wallet-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #FF9500;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.wallet-amount+.uni-icons {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 2rpx;
|
||||
height: 60rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 功能区网格 */
|
||||
.function-grid {
|
||||
margin: 0 30rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx 0;
|
||||
box-shadow: 0 5rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.grid-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.grid-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.grid-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 25rpx;
|
||||
background-color: #f8f8f8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 15rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.grid-item:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.grid-icon:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.grid-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.badge-dot {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 20rpx;
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
background-color: #FF3B30;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #fff;
|
||||
}
|
||||
|
||||
/* 列表区域 */
|
||||
.list-section {
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin: 20rpx 0 15rpx;
|
||||
padding-left: 10rpx;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 5rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.list-item:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.list-left {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.list-left .uni-icons {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.list-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.logout-text {
|
||||
color: #FF3B30;
|
||||
}
|
||||
|
||||
.list-right {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-desc {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
||||
<view class="summary-card">
|
||||
<text class="summary-num">{{ totalLikes }}</text>
|
||||
<text class="summary-label">共获得 {{ list.length }} 条动态的点赞</text>
|
||||
</view>
|
||||
|
||||
<view class="list">
|
||||
<view v-for="(m, idx) in sorted" :key="idx" class="moment-item" @click="openDetail(m)">
|
||||
<view class="m-head">
|
||||
<image class="m-avatar" :src="imgUrl(m.userAvatar)" mode="aspectFill"></image>
|
||||
<view class="m-meta">
|
||||
<text class="m-name">{{ m.userName }}</text>
|
||||
<text class="m-time">{{ formatTime(m.time) }}</text>
|
||||
</view>
|
||||
<view class="like-badge">
|
||||
<uni-icons type="heart-filled" size="22" color="#FF2D55"></uni-icons>
|
||||
<text class="like-num">{{ m.likes }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="m-content">{{ m.content }}</text>
|
||||
<view v-if="m.images && m.images.length > 0" class="m-images">
|
||||
<image v-for="(img, i) in m.images.slice(0,3)" :key="i" class="m-img"
|
||||
:src="imgUrl(img)" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="m-foot">
|
||||
<view class="m-stat"><uni-icons type="chatbubble" size="22" color="#999"></uni-icons>
|
||||
<text>{{ m.comments }}</text></view>
|
||||
<view class="m-stat"><uni-icons type="redo" size="22" color="#999"></uni-icons>
|
||||
<text>{{ m.shares }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="list.length === 0 && !loading" class="empty-state">
|
||||
<uni-icons type="heart" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">还没有人给你点赞哦</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
const BASE = 'https://dev.xixingwl.cn'
|
||||
|
||||
type IMoment = {
|
||||
id : number
|
||||
uid : number
|
||||
userName : string
|
||||
userAvatar : string
|
||||
content : string
|
||||
images : string[]
|
||||
videoUrl : string
|
||||
type : string
|
||||
time : number
|
||||
likes : number
|
||||
comments : number
|
||||
shares : number
|
||||
}
|
||||
|
||||
const list = reactive<IMoment[]>([])
|
||||
const sorted = computed<IMoment[]>(() => {
|
||||
return list.slice().sort((a : IMoment, b : IMoment) => b.likes - a.likes)
|
||||
})
|
||||
const totalLikes = computed<number>(() => {
|
||||
let s = 0
|
||||
list.forEach((m : IMoment) => { s += m.likes })
|
||||
return s
|
||||
})
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
|
||||
function imgUrl(src : string) : string {
|
||||
if (! src) return ''
|
||||
return src.startsWith('http') ? src : (BASE + src)
|
||||
}
|
||||
|
||||
function formatTime(ts : number) : string {
|
||||
if (! ts) return ''
|
||||
const d = new Date(ts)
|
||||
const pad = (n : number) : string => n < 10 ? '0' + n : '' + n
|
||||
return `${d.getMonth() + 1}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
|
||||
function openDetail(m : IMoment) {
|
||||
uni.navigateTo({ url: '/pages/moment/details?id=' + m.id })
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
Api.moment.mine({ page: 1, page_size: 50 })
|
||||
.then((res : UTSJSONObject) => {
|
||||
const arr = (res.get('list') as Array<IMoment>) ?? []
|
||||
list.splice(0, list.length)
|
||||
arr.forEach((m : IMoment) => list.push(m))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => { loadData() })
|
||||
onShow(() => { loadData() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 24rpx 30rpx;
|
||||
padding: 36rpx 0;
|
||||
background: linear-gradient(135deg, #FF2D55 0%, #FF6B6B 100%);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.summary-num {
|
||||
font-size: 52rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 24rpx;
|
||||
color: #ffe;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 30rpx 24rpx;
|
||||
}
|
||||
|
||||
.moment-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-head {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
background-color: #f0f0f0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.m-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.m-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.m-time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.like-badge {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
background-color: rgba(255, 45, 85, 0.08);
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.like-num {
|
||||
font-size: 26rpx;
|
||||
color: #FF2D55;
|
||||
font-weight: bold;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.m-content {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.m-images {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.m-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
margin: 0 12rpx 12rpx 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.m-foot {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.m-stat {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-right: 40rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.m-stat text {
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
|
||||
<!-- 顶部金币信息 -->
|
||||
<view class="top-card">
|
||||
<view class="coin-row">
|
||||
<uni-icons type="wallet-filled" size="28" color="#FFD700"></uni-icons>
|
||||
<text class="coin-num">{{ coins }}</text>
|
||||
<text class="coin-label">我的金币</text>
|
||||
</view>
|
||||
<view class="free-tip">
|
||||
今日免费抽奖剩余 <text class="free-num">{{ freeLeft }}</text> 次(每次消耗 {{ cost }} 金币)
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 奖品展示 -->
|
||||
<view class="section-title">奖品池</view>
|
||||
<view class="prize-grid">
|
||||
<view v-for="(p, idx) in prizes" :key="idx" class="prize-item">
|
||||
<view class="prize-icon">
|
||||
<uni-icons :type="prizeIcon(p.type)" size="30"
|
||||
:color="p.type == 1 ? '#FF9500' : (p.type == 2 ? '#FF2D55' : '#999')"></uni-icons>
|
||||
</view>
|
||||
<text class="prize-name">{{ p.name }}</text>
|
||||
<text class="prize-sub" v-if="p.type == 1">{{ p.value }} 金币</text>
|
||||
<text class="prize-sub" v-else-if="p.type == 2">实物</text>
|
||||
<text class="prize-sub" v-else>谢谢参与</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 抽奖按钮 -->
|
||||
<view class="draw-btn" :class="{ disabled: drawing }" @click="onDraw">
|
||||
<text v-if="!drawing">{{ freeLeft > 0 ? '免费抽奖' : ('抽一次(' + cost + '金币)') }}</text>
|
||||
<text v-else>抽奖中...</text>
|
||||
</view>
|
||||
<text class="draw-hint">每日 {{ freeDaily }} 次免费机会,点击即抽</text>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 结果弹窗 -->
|
||||
<view v-if="showResult" class="mask" @click="showResult = false">
|
||||
<view class="result-card" @click.stop>
|
||||
<uni-icons :type="resultIcon" size="56"
|
||||
:color="resultType == 1 ? '#FF9500' : (resultType == 2 ? '#FF2D55' : '#bbb')"></uni-icons>
|
||||
<text class="result-title">{{ resultPrize }}</text>
|
||||
<text class="result-sub" v-if="resultType == 1">+{{ resultValue }} 金币已到账</text>
|
||||
<view class="result-btn" @click="showResult = false">
|
||||
<text>开心收下</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
const BASE = 'https://dev.xixingwl.cn'
|
||||
|
||||
type IPrize = {
|
||||
id : number
|
||||
name : string
|
||||
type : number
|
||||
value : number
|
||||
probability : number
|
||||
}
|
||||
|
||||
const coins = ref<number>(0)
|
||||
const cost = ref<number>(10)
|
||||
const freeDaily = ref<number>(1)
|
||||
const freeLeft = ref<number>(0)
|
||||
const prizes = reactive<IPrize[]>([])
|
||||
const drawing = ref<boolean>(false)
|
||||
|
||||
const showResult = ref<boolean>(false)
|
||||
const resultPrize = ref<string>('')
|
||||
const resultType = ref<number>(3)
|
||||
const resultValue = ref<number>(0)
|
||||
const resultIcon = computed<string>(() => {
|
||||
if (resultType.value == 1) return 'wallet-filled'
|
||||
if (resultType.value == 2) return 'gift-filled'
|
||||
return 'close'
|
||||
})
|
||||
|
||||
function prizeIcon(type : number) : string {
|
||||
if (type == 1) return 'wallet-filled'
|
||||
if (type == 2) return 'gift-filled'
|
||||
return 'more'
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
Api.lottery.config()
|
||||
.then((res : UTSJSONObject) => {
|
||||
coins.value = (res.get('coins') as number) ?? 0
|
||||
cost.value = (res.get('cost') as number) ?? 10
|
||||
freeDaily.value = (res.get('free_daily') as number) ?? 1
|
||||
freeLeft.value = (res.get('free_left') as number) ?? 0
|
||||
const list = (res.get('prizes') as Array<IPrize>) ?? []
|
||||
prizes.splice(0, prizes.length)
|
||||
list.forEach((p : IPrize) => prizes.push(p))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '配置加载失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
function onDraw() {
|
||||
if (drawing.value) return
|
||||
if (freeLeft.value <= 0 && coins.value < cost.value) {
|
||||
uni.showToast({ title: '金币不足', icon: 'none' })
|
||||
return
|
||||
}
|
||||
drawing.value = true
|
||||
Api.lottery.draw()
|
||||
.then((res : UTSJSONObject) => {
|
||||
resultPrize.value = (res.get('prize') as string) ?? '谢谢参与'
|
||||
resultType.value = (res.get('prize_type') as number) ?? 3
|
||||
resultValue.value = (res.get('prize_value') as number) ?? 0
|
||||
coins.value = (res.get('coins_left') as number) ?? coins.value
|
||||
showResult.value = true
|
||||
loadConfig()
|
||||
})
|
||||
.catch((e : any) => {
|
||||
uni.showToast({ title: (e && e.message) ? e.message : '抽奖失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
drawing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => { loadConfig() })
|
||||
onShow(() => { loadConfig() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.top-card {
|
||||
margin: 24rpx 30rpx;
|
||||
padding: 40rpx;
|
||||
background: linear-gradient(135deg, #6A5ACD 0%, #FF6B6B 100%);
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.coin-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.coin-num {
|
||||
font-size: 56rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin: 0 12rpx;
|
||||
}
|
||||
|
||||
.coin-label {
|
||||
font-size: 26rpx;
|
||||
color: #ffe;
|
||||
}
|
||||
|
||||
.free-tip {
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.free-num {
|
||||
font-weight: bold;
|
||||
color: #FFD700;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin: 10rpx 30rpx 16rpx;
|
||||
}
|
||||
|
||||
.prize-grid {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
margin: 0 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.prize-item {
|
||||
width: 33.33%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.prize-icon {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 48rpx;
|
||||
background-color: #f7f7f7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.prize-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.prize-sub {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.draw-btn {
|
||||
margin: 40rpx 60rpx 0;
|
||||
height: 96rpx;
|
||||
border-radius: 48rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10rpx 30rpx rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
.draw-btn.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.draw-btn text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.draw-hint {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin: 16rpx 0 60rpx;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
width: 560rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 60rpx 40rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.result-sub {
|
||||
font-size: 26rpx;
|
||||
color: #FF9500;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.result-btn {
|
||||
margin-top: 40rpx;
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.result-btn text {
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
|
||||
<view class="tip-bar">
|
||||
<uni-icons type="locked" size="20" color="#34C759"></uni-icons>
|
||||
<text class="tip-text">以下设置仅对你生效,管理他人查看你的方式</text>
|
||||
</view>
|
||||
|
||||
<view class="list-container">
|
||||
<view class="list-item" v-for="(item, idx) in items" :key="idx">
|
||||
<view class="item-left">
|
||||
<text class="item-title">{{ item.title }}</text>
|
||||
<text class="item-sub">{{ item.sub }}</text>
|
||||
</view>
|
||||
<switch :checked="item.value == 1" color="#34C759" @change="() => onToggle(item)" />
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
type SettingItem = {
|
||||
key : string
|
||||
title : string
|
||||
sub : string
|
||||
value : number
|
||||
}
|
||||
|
||||
const items = reactive<SettingItem[]>([
|
||||
{ key: 'hide_age', title: '隐藏年龄', sub: '他人将无法看到你的真实年龄', value: 0 },
|
||||
{ key: 'hide_online', title: '隐藏在线状态', sub: '他人看不到你是否在线', value: 0 },
|
||||
{ key: 'hide_distance', title: '隐藏距离', sub: '同城/附近不再显示与你的距离', value: 0 },
|
||||
{ key: 'hide_album', title: '隐藏相册', sub: '他人访问你的资料时看不到相册', value: 0 },
|
||||
{ key: 'danmaku_global', title: '弹幕总开关', sub: '关闭后全站不显示互动弹幕', value: 1 },
|
||||
])
|
||||
|
||||
function loadPrivacy() {
|
||||
Api.user.privacy()
|
||||
.then((res : UTSJSONObject) => {
|
||||
items.forEach((it : SettingItem) => {
|
||||
const v = res.get(it.key)
|
||||
if (v != null) {
|
||||
it.value = (v as number) == 1 ? 1 : 0
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
function onToggle(item : SettingItem) {
|
||||
const next = item.value == 1 ? 0 : 1
|
||||
item.value = next
|
||||
Api.user.updatePrivacy({ [item.key]: next })
|
||||
.then(() => {
|
||||
uni.showToast({ title: '已更新', icon: 'success', duration: 1000 })
|
||||
})
|
||||
.catch((err : any) => {
|
||||
item.value = item.value == 1 ? 0 : 1
|
||||
uni.showToast({ title: (err && err.message) ? err.message : '更新失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => { loadPrivacy() })
|
||||
onShow(() => { loadPrivacy() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.tip-bar {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin: 24rpx 30rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: rgba(52, 199, 89, 0.1);
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 24rpx;
|
||||
color: #34C759;
|
||||
margin-left: 12rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
margin: 0 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-sub {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="tabs">
|
||||
<view class="tab" :class="{ active: type === 'charm' }" @click="switchTab('charm')">
|
||||
<text>魅力榜</text>
|
||||
</view>
|
||||
<view class="tab" :class="{ active: type === 'rich' }" @click="switchTab('rich')">
|
||||
<text>财富榜</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;"
|
||||
:refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
||||
<view class="list">
|
||||
<view v-for="(u, idx) in list" :key="idx" class="rank-item" @click="openUser(u)">
|
||||
<view class="rank-no" :class="'top' + u.rank">
|
||||
<text v-if="u.rank <= 3">{{ ['', '①', '②', '③'][u.rank] }}</text>
|
||||
<text v-else>{{ u.rank }}</text>
|
||||
</view>
|
||||
<image class="rank-avatar" :src="imgUrl(u.avatar)" mode="aspectFill"></image>
|
||||
<view class="rank-info">
|
||||
<text class="rank-name">{{ u.nickname }}</text>
|
||||
</view>
|
||||
<view class="rank-value">
|
||||
<text class="value-num">{{ u.value }}</text>
|
||||
<text class="value-unit">{{ type === 'rich' ? '金币' : '粉丝' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="list.length === 0 && !loading" class="empty-state">
|
||||
<uni-icons type="list" size="60" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">暂无排行数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
const BASE = 'https://dev.xixingwl.cn'
|
||||
|
||||
type IRank = {
|
||||
rank : number
|
||||
uid : number
|
||||
nickname : string
|
||||
avatar : string
|
||||
value : number
|
||||
coins : number
|
||||
}
|
||||
|
||||
const type = ref<string>('charm')
|
||||
const list = reactive<IRank[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const refreshing = ref<boolean>(false)
|
||||
|
||||
function imgUrl(src : string) : string {
|
||||
if (! src) return ''
|
||||
return src.startsWith('http') ? src : (BASE + src)
|
||||
}
|
||||
|
||||
function switchTab(t : string) {
|
||||
if (type.value === t) return
|
||||
type.value = t
|
||||
loadData()
|
||||
}
|
||||
|
||||
function openUser(u : IRank) {
|
||||
uni.navigateTo({ url: '/pages/user/profile?uid=' + u.uid })
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
Api.user.ranking({ type: type.value })
|
||||
.then((res : UTSJSONObject) => {
|
||||
const arr = (res.get('list') as Array<IRank>) ?? []
|
||||
list.splice(0, list.length)
|
||||
arr.forEach((u : IRank) => list.push(u))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
refreshing.value = true
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => { loadData() })
|
||||
onShow(() => { loadData() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
background-color: #fff;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
color: #999;
|
||||
padding: 12rpx 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #FF6B6B;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%);
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 3rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
.rank-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.rank-no {
|
||||
width: 56rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.rank-no.top1 { color: #FF9500; }
|
||||
.rank-no.top2 { color: #FF6B6B; }
|
||||
.rank-no.top3 { color: #AF52DE; }
|
||||
|
||||
.rank-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
|
||||
.rank-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.rank-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rank-value {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.value-num {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
.value-unit {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
|
||||
<view class="list-title">账号绑定</view>
|
||||
<view class="list-container">
|
||||
<view class="list-item" @click="toast('手机号已绑定')">
|
||||
<view class="item-left">
|
||||
<uni-icons type="phone" size="20" color="#007AFF"></uni-icons>
|
||||
<text class="list-text">手机号</text>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<text class="item-desc">{{ mobileMask }}</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-item" @click="toast('修改密码功能开发中')">
|
||||
<view class="item-left">
|
||||
<uni-icons type="locked" size="20" color="#FF9500"></uni-icons>
|
||||
<text class="list-text">修改密码</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list-title">隐私与安全</view>
|
||||
<view class="list-container">
|
||||
<view class="list-item" @click="goPage('/pages/my/privacy')">
|
||||
<view class="item-left">
|
||||
<uni-icons type="eye" size="20" color="#34C759"></uni-icons>
|
||||
<text class="list-text">隐私设置</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="list-item" @click="toast('登录设备管理开发中')">
|
||||
<view class="item-left">
|
||||
<uni-icons type="staff" size="20" color="#5AC8FA"></uni-icons>
|
||||
<text class="list-text">登录设备管理</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list-title">其他</view>
|
||||
<view class="list-container">
|
||||
<view class="list-item danger" @click="onCancel">
|
||||
<view class="item-left">
|
||||
<uni-icons type="trash" size="20" color="#FF3B30"></uni-icons>
|
||||
<text class="list-text danger-text">注销账号</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="version">当前版本 v{{ appVersion }}</text>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
import userState from '@/stores/user.uts'
|
||||
|
||||
const appVersion = ref('2.5.0')
|
||||
|
||||
const mobileMask = computed<string>(() => {
|
||||
const info = userState.info
|
||||
const m = info && info.mobile ? (info.mobile as string) : ''
|
||||
if (m.length >= 11) {
|
||||
return m.substring(0, 3) + '****' + m.substring(7)
|
||||
}
|
||||
return m || '未绑定'
|
||||
})
|
||||
|
||||
function toast(msg : string) {
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
}
|
||||
|
||||
function goPage(url : string) {
|
||||
uni.navigateTo({ url: url })
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
uni.showModal({
|
||||
title: '注销账号',
|
||||
content: '注销后你的资料将被匿名化处理,且无法恢复,确定继续吗?',
|
||||
confirmText: '注销',
|
||||
confirmColor: '#FF3B30',
|
||||
success: (res) => {
|
||||
if (! res.confirm) return
|
||||
Api.user.cancelAccount()
|
||||
.then(() => {
|
||||
Api.auth.logout()
|
||||
uni.reLaunch({ url: '/pages/login/index' })
|
||||
})
|
||||
.catch((e : any) => {
|
||||
uni.showToast({ title: (e && e.message) ? e.message : '操作失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin: 24rpx 30rpx 16rpx;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
margin: 0 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.list-item:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.item-left {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-left .uni-icons {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.list-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-right {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
color: #FF3B30;
|
||||
}
|
||||
|
||||
.version {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #bbb;
|
||||
margin: 60rpx 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="-status-bar"></view>
|
||||
<view class="header">
|
||||
<view class="coins-card">
|
||||
<uni-icons type="wallet-filled" size="36" color="#FFB300"></uni-icons>
|
||||
<view class="coins-info">
|
||||
<text class="coins-num">{{ coins }}</text>
|
||||
<text class="coins-label">我的金币</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
|
||||
<view class="task-item" v-for="(item, idx) in tasks" :key="item.id">
|
||||
<view class="task-icon">
|
||||
<uni-icons :type="item.icon" size="30" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="task-main">
|
||||
<text class="task-title">{{ item.title }}</text>
|
||||
<text class="task-desc">{{ item.desc }}</text>
|
||||
<view class="task-progress" v-if="item.status != 2">
|
||||
<view class="bar">
|
||||
<view class="bar-inner" :style="{ width: progressWidth(item) }"></view>
|
||||
</view>
|
||||
<text class="bar-text">{{ item.current }}/{{ item.target }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="task-reward">+{{ item.reward }}</view>
|
||||
<button class="task-btn" :class="btnClass(item.status)" :disabled="item.status == 2"
|
||||
@click="onReceive(item)">
|
||||
{{ btnText(item.status) }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-if="tasks.length === 0 && !loading" class="empty">
|
||||
<text class="empty-text">暂无任务</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
type ITask = {
|
||||
id : number
|
||||
title : string
|
||||
desc : string
|
||||
icon : string
|
||||
reward : number
|
||||
target : number
|
||||
current : number
|
||||
status : number
|
||||
}
|
||||
|
||||
const coins = ref<number>(0)
|
||||
const loading = ref<boolean>(false)
|
||||
const tasks = reactive<ITask[]>([])
|
||||
|
||||
const btnText = (status : number) : string => {
|
||||
if (status == 2) return '已领取'
|
||||
if (status == 1) return '领取'
|
||||
return '去完成'
|
||||
}
|
||||
const btnClass = (status : number) : string => {
|
||||
if (status == 2) return 'done'
|
||||
if (status == 1) return 'ready'
|
||||
return 'todo'
|
||||
}
|
||||
const progressWidth = (item : ITask) : string => {
|
||||
const pct = item.target > 0 ? (item.current / item.target * 100) : 0
|
||||
return Math.min(100, pct) + '%'
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
Api.task.list()
|
||||
.then((res : UTSJSONObject) => {
|
||||
coins.value = (res.get('coins') as number) ?? 0
|
||||
const list = (res.get('list') as Array<ITask>) ?? []
|
||||
tasks.splice(0, tasks.length)
|
||||
list.forEach((t : ITask) => tasks.push(t))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => { loading.value = false })
|
||||
}
|
||||
|
||||
function onReceive(item : ITask) {
|
||||
if (item.status == 2) return
|
||||
if (item.status == 0) {
|
||||
uni.showToast({ title: '任务未完成', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showLoading({ title: '领取中...', mask: true })
|
||||
Api.task.receive(item.id)
|
||||
.then((res : UTSJSONObject) => {
|
||||
item.status = 2
|
||||
coins.value = (res.get('coins_left') as number) ?? coins.value
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '领取成功 +' + item.reward + '金币', icon: 'none' })
|
||||
})
|
||||
.catch((e : any) => {
|
||||
uni.hideLoading()
|
||||
const msg = (e as UTSJSONObject)?.get('message') as string
|
||||
uni.showToast({ title: msg ?? '领取失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => { loadData() })
|
||||
onShow(() => { loadData() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.-status-bar {
|
||||
height: var(--status-bar-height);
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
padding-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.coins-card {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin: 24rpx 30rpx 0;
|
||||
padding: 30rpx;
|
||||
background-color: rgba(255, 255, 255, 0.18);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.coins-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.coins-num {
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.coins-label {
|
||||
font-size: 24rpx;
|
||||
color: #ffe;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin: 20rpx 30rpx 0;
|
||||
padding: 28rpx 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 18rpx;
|
||||
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.task-icon {
|
||||
width: 84rpx;
|
||||
height: 84rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, #FF8E53 0%, #FF6B6B 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.task-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.task-desc {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.task-progress {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.bar {
|
||||
flex: 1;
|
||||
height: 12rpx;
|
||||
border-radius: 6rpx;
|
||||
background-color: #f0f0f0;
|
||||
overflow: hidden;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.bar-inner {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.bar-text {
|
||||
font-size: 20rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.task-reward {
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #FF9500;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.task-btn {
|
||||
height: 60rpx;
|
||||
padding: 0 28rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.task-btn.ready {
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.task-btn.todo {
|
||||
background-color: #f0f0f0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.task-btn.done {
|
||||
background-color: #f0f0f0;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="-status-bar"></view>
|
||||
<view class="header">
|
||||
<view class="balance-card">
|
||||
<text class="balance-label">账户余额(元)</text>
|
||||
<text class="balance-num">{{ balance.toFixed(2) }}</text>
|
||||
<view class="coin-row">
|
||||
<uni-icons type="wallet-filled" size="22" color="#FFD700"></uni-icons>
|
||||
<text class="coin-num">{{ coins }}</text>
|
||||
<text class="coin-label">金币</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="summary-row">
|
||||
<view class="summary-item">
|
||||
<text class="s-num">{{ totalRecharge.toFixed(2) }}</text>
|
||||
<text class="s-label">累计充值</text>
|
||||
</view>
|
||||
<view class="summary-item">
|
||||
<text class="s-num">{{ totalConsume.toFixed(2) }}</text>
|
||||
<text class="s-label">累计消费</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
|
||||
<view class="block-title">明细记录</view>
|
||||
<view v-if="logs.length === 0 && !loading" class="empty">
|
||||
<text class="empty-text">暂无明细</text>
|
||||
</view>
|
||||
<view class="log-item" v-for="(log, idx) in logs" :key="log.id">
|
||||
<view class="log-left">
|
||||
<text class="log-type">{{ log.remark && log.remark != '' ? log.remark : log.type }}</text>
|
||||
<text class="log-time">{{ formatTime(log.create_at) }}</text>
|
||||
</view>
|
||||
<text class="log-num" :class="log.change_num >= 0 ? 'plus' : 'minus'">{{ log.change_num >= 0 ? '+' : '' }}{{ log.change_num }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import Api from '@/common/api-service.uts'
|
||||
|
||||
type ILog = {
|
||||
id : number
|
||||
type : string
|
||||
change_num : number
|
||||
balance_after : number
|
||||
remark : string
|
||||
create_at : number
|
||||
}
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
const balance = ref<number>(0)
|
||||
const coins = ref<number>(0)
|
||||
const totalRecharge = ref<number>(0)
|
||||
const totalConsume = ref<number>(0)
|
||||
const logs = reactive<ILog[]>([])
|
||||
|
||||
function loadData() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
Api.user.wallet()
|
||||
.then((res : UTSJSONObject) => {
|
||||
balance.value = (res.get('balance') as number) ?? 0
|
||||
coins.value = (res.get('coins') as number) ?? 0
|
||||
totalRecharge.value = (res.get('total_recharge') as number) ?? 0
|
||||
totalConsume.value = (res.get('total_consume') as number) ?? 0
|
||||
const arr = (res.get('logs') as Array<ILog>) ?? []
|
||||
logs.splice(0, logs.length)
|
||||
arr.forEach((l : ILog) => logs.push(l))
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => { loading.value = false })
|
||||
}
|
||||
|
||||
function formatTime(ts : number) : string {
|
||||
if (!ts) return ''
|
||||
const d = new Date(ts * 1000)
|
||||
const pad = (n : number) : string => { return n < 10 ? ('0' + n) : ('' + n) }
|
||||
return (d.getMonth() + 1) + '-' + d.getDate() + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes())
|
||||
}
|
||||
|
||||
onMounted(() => { loadData() })
|
||||
onShow(() => { loadData() })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.-status-bar {
|
||||
height: var(--status-bar-height);
|
||||
background: linear-gradient(135deg, #FFB75E 0%, #ED8F03 100%);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #FFB75E 0%, #ED8F03 100%);
|
||||
padding: 30rpx 30rpx 50rpx;
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.balance-num {
|
||||
font-size: 64rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin: 8rpx 0 20rpx;
|
||||
}
|
||||
|
||||
.coin-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.coin-num {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin: 0 8rpx 0 12rpx;
|
||||
}
|
||||
|
||||
.coin-label {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
margin-top: 36rpx;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-right: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.summary-item:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.s-num {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.s-label {
|
||||
font-size: 22rpx;
|
||||
color: #fff;
|
||||
opacity: 0.9;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin: 30rpx 30rpx 16rpx;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f2f2f2;
|
||||
}
|
||||
|
||||
.log-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.log-type {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.log-num {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.log-num.plus {
|
||||
color: #34C759;
|
||||
}
|
||||
|
||||
.log-num.minus {
|
||||
color: #FF3B30;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user