细节优化.

This commit is contained in:
18631081161 2026-01-07 17:52:35 +08:00
parent 6f1892b0e7
commit b833b83d02
19 changed files with 631 additions and 56 deletions

View File

@ -42,8 +42,19 @@ export async function getAllPopupConfigs() {
return response
}
/**
* 获取默认头像配置
*
* @returns {Promise<Object>} 默认头像URL
*/
export async function getDefaultAvatarConfig() {
const response = await get('/config/defaultAvatar', {}, { needAuth: false })
return response
}
export default {
getHomeConfig,
getPopupConfig,
getAllPopupConfigs
getAllPopupConfigs,
getDefaultAvatarConfig
}

44
miniapp/api/message.js Normal file
View File

@ -0,0 +1,44 @@
/**
* 消息相关 API
*/
import { get, post } from './request.js'
/**
* 获取系统消息列表
* @param {Object} params - 查询参数
* @param {number} params.pageIndex - 页码
* @param {number} params.pageSize - 每页数量
* @returns {Promise}
*/
export const getSystemMessages = (params) => {
return get('/notification/list', {
pageIndex: params.pageIndex,
pageSize: params.pageSize
})
}
/**
* 标记消息已读
* @param {number} messageId - 消息ID
* @returns {Promise}
*/
export const markMessageRead = (messageId) => {
return post('/notification/read', { notificationId: messageId })
}
/**
* 标记所有系统消息已读
* @returns {Promise}
*/
export const markAllSystemMessagesRead = () => {
return post('/notification/read', { markAll: true })
}
/**
* 获取未读消息数量
* @returns {Promise}
*/
export const getUnreadCount = () => {
return get('/notification/unreadCount')
}

View File

@ -15,6 +15,13 @@
"navigationBarTitleText": "消息"
}
},
{
"path": "pages/message/system",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "系统消息"
}
},
{
"path": "pages/mine/index",
"style": {

View File

@ -121,7 +121,8 @@
} from '@/store/config.js'
import {
getHomeConfig,
getPopupConfig
getPopupConfig,
getDefaultAvatarConfig
} from '@/api/config.js'
import {
getRecommend
@ -247,6 +248,18 @@
}
}
//
const loadDefaultAvatarConfig = async () => {
try {
const res = await getDefaultAvatarConfig()
if (res && res.code === 0 && res.data?.avatarUrl) {
configStore.setDefaultAvatarConfig(res.data.avatarUrl)
}
} catch (error) {
console.error('加载默认头像配置失败:', error)
}
}
//
const loadRecommendList = async (isLoadMore = false) => {
if (listLoading.value) return
@ -309,7 +322,8 @@
await Promise.all([
loadHomeConfig(),
loadDailyPopup(),
loadMemberAdConfig()
loadMemberAdConfig(),
loadDefaultAvatarConfig()
])
//

View File

@ -84,10 +84,11 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { getFavoritedMe, getMyFavorite } from '@/api/interact.js'
import { formatTimestamp } from '@/utils/format.js'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
@ -99,6 +100,7 @@ export default {
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
@ -111,8 +113,8 @@ export default {
const pageIndex = ref(1)
const pageSize = 20
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const loadList = async (refresh = false) => {

View File

@ -84,10 +84,11 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { getFavoritedMe, getMyFavorite } from '@/api/interact.js'
import { formatTimestamp } from '@/utils/format.js'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
@ -99,6 +100,7 @@ export default {
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
@ -111,8 +113,8 @@ export default {
const pageIndex = ref(1)
const pageSize = 20
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const loadList = async (refresh = false) => {

View File

@ -84,10 +84,11 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { getUnlockedMe, getMyUnlocked } from '@/api/interact.js'
import { formatTimestamp } from '@/utils/format.js'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
@ -99,6 +100,7 @@ export default {
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
@ -111,8 +113,8 @@ export default {
const pageIndex = ref(1)
const pageSize = 20
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const loadList = async (refresh = false) => {

View File

@ -84,10 +84,11 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { getViewedMe, getMyViewed } from '@/api/interact.js'
import { formatTimestamp } from '@/utils/format.js'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
@ -99,6 +100,7 @@ export default {
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
@ -111,8 +113,8 @@ export default {
const pageIndex = ref(1)
const pageSize = 20
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const loadList = async (refresh = false) => {

View File

@ -84,10 +84,11 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { getUnlockedMe, getMyUnlocked } from '@/api/interact.js'
import { formatTimestamp } from '@/utils/format.js'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
@ -99,6 +100,7 @@ export default {
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
@ -111,8 +113,8 @@ export default {
const pageIndex = ref(1)
const pageSize = 20
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const loadList = async (refresh = false) => {

View File

@ -84,10 +84,11 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { getViewedMe, getMyViewed } from '@/api/interact.js'
import { formatTimestamp } from '@/utils/format.js'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
@ -99,6 +100,7 @@ export default {
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
@ -111,8 +113,8 @@ export default {
const pageIndex = ref(1)
const pageSize = 20
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const loadList = async (refresh = false) => {

View File

@ -90,6 +90,7 @@
<script>
import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import { getMemberInfo } from '@/api/member.js'
import { createOrder } from '@/api/order.js'
import Loading from '@/components/Loading/index.vue'
@ -101,14 +102,15 @@ export default {
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
const purchasing = ref(false)
const selectedTier = ref(1) //
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const memberLevelMap = {

View File

@ -115,9 +115,10 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useChatStore } from '@/store/chat.js'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import { getSessions } from '@/api/chat.js'
import { getViewedMe, getFavoritedMe, getUnlockedMe } from '@/api/interact.js'
import { formatTimestamp } from '@/utils/format.js'
@ -131,6 +132,7 @@ export default {
setup() {
const chatStore = useChatStore()
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
@ -151,8 +153,8 @@ export default {
unlockedMe: 0
})
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const getSystemInfo = () => {

View File

@ -0,0 +1,377 @@
<template>
<view class="system-message-page">
<!-- 页面加载状态 -->
<Loading type="page" :loading="pageLoading" />
<!-- 顶部背景图 -->
<view class="top-bg">
<image src="/static/title_bg.png" mode="aspectFill" class="bg-img" />
</view>
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="navbar-back" @click="handleBack">
<text class="back-icon"></text>
</view>
<text class="navbar-title">系统消息</text>
<view class="navbar-placeholder"></view>
</view>
</view>
<!-- 筛选标签 -->
<view class="filter-section" :style="{ top: (statusBarHeight + 44) + 'px' }">
<view
class="filter-tag"
:class="{ active: currentFilter === 'all' }"
@click="handleFilterChange('all')"
>
全部
</view>
</view>
<!-- 消息列表 -->
<scroll-view
class="message-scroll"
scroll-y
:style="{
top: (statusBarHeight + 44 + 60) + 'px',
height: scrollHeight + 'px'
}"
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="handleRefresh"
@scrolltolower="handleLoadMore"
>
<view class="message-list" v-if="messageList.length > 0">
<view
class="message-item"
v-for="item in messageList"
:key="item.id"
@click="handleMessageClick(item)"
>
<view class="message-title">{{ item.title }}</view>
<view class="message-content">{{ item.content }}</view>
<view class="message-time">{{ formatTime(item.createTime) }}</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-wrapper" v-else-if="!pageLoading">
<Empty text="暂无系统消息" :showButton="false" />
</view>
<!-- 加载更多 -->
<Loading type="more" :loading="listLoading" :noMore="noMoreData" />
</scroll-view>
</view>
</template>
<script>
import { ref, onMounted } from 'vue'
import { useUserStore } from '@/store/user.js'
import { getSystemMessages } from '@/api/message.js'
import { formatTimestamp } from '@/utils/format.js'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
export default {
name: 'SystemMessagePage',
components: {
Loading,
Empty
},
setup() {
const userStore = useUserStore()
//
const statusBarHeight = ref(20)
const scrollHeight = ref(500)
//
const pageLoading = ref(true)
const listLoading = ref(false)
const noMoreData = ref(false)
const isRefreshing = ref(false)
//
const currentFilter = ref('all')
//
const pageIndex = ref(1)
const pageSize = 20
//
const messageList = ref([])
//
const getSystemInfo = () => {
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20
// - -
const navbarHeight = statusBarHeight.value + 44
const filterHeight = 60
scrollHeight.value = res.windowHeight - navbarHeight - filterHeight
}
})
}
//
const handleBack = () => {
uni.navigateBack()
}
//
const loadMessages = async (isLoadMore = false) => {
if (listLoading.value) return
if (isLoadMore && noMoreData.value) return
listLoading.value = true
try {
if (!isLoadMore) {
pageIndex.value = 1
messageList.value = []
noMoreData.value = false
}
const res = await getSystemMessages({
pageIndex: pageIndex.value,
pageSize: pageSize
})
if (res && (res.code === 0 || res.success) && res.data) {
const items = res.data.items || res.data.list || []
const total = res.data.total || 0
if (isLoadMore) {
messageList.value = [...messageList.value, ...items]
} else {
messageList.value = items
}
noMoreData.value = messageList.value.length >= total || items.length < pageSize
pageIndex.value++
}
} catch (error) {
console.error('加载系统消息失败:', error)
} finally {
listLoading.value = false
}
}
//
const initPage = async () => {
pageLoading.value = true
try {
getSystemInfo()
userStore.restoreFromStorage()
await loadMessages()
} catch (error) {
console.error('初始化页面失败:', error)
} finally {
pageLoading.value = false
}
}
//
const handleFilterChange = (filter) => {
if (currentFilter.value === filter) return
currentFilter.value = filter
loadMessages()
}
//
const handleRefresh = async () => {
isRefreshing.value = true
try {
await loadMessages()
} finally {
isRefreshing.value = false
}
}
//
const handleLoadMore = () => {
if (!noMoreData.value && !listLoading.value) {
loadMessages(true)
}
}
//
const handleMessageClick = (item) => {
//
if (item.linkUrl) {
uni.navigateTo({ url: item.linkUrl })
}
}
//
const formatTime = (timestamp) => {
return formatTimestamp(timestamp)
}
onMounted(() => {
initPage()
})
return {
statusBarHeight,
scrollHeight,
pageLoading,
listLoading,
noMoreData,
isRefreshing,
currentFilter,
messageList,
handleBack,
handleFilterChange,
handleRefresh,
handleLoadMore,
handleMessageClick,
formatTime
}
}
}
</script>
<style lang="scss" scoped>
.system-message-page {
height: 100vh;
background-color: #f5f6fa;
}
//
.top-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 400rpx;
z-index: 0;
.bg-img {
width: 100%;
height: 100%;
}
}
//
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
.navbar-content {
position: relative;
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
.navbar-back {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.back-icon {
font-size: 56rpx;
color: #333;
font-weight: 300;
}
}
.navbar-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.navbar-placeholder {
width: 80rpx;
}
}
}
//
.filter-section {
position: fixed;
left: 0;
right: 0;
z-index: 10;
padding: 16rpx 24rpx;
.filter-tag {
display: inline-block;
padding: 12rpx 32rpx;
font-size: 28rpx;
color: #ff6b6b;
background: #fff;
border: 2rpx solid #ff6b6b;
border-radius: 32rpx;
&.active {
color: #ff6b6b;
background: #fff;
}
}
}
//
.message-scroll {
position: fixed;
left: 0;
right: 0;
background-color: #f5f6fa;
}
//
.message-list {
padding: 24rpx;
min-height: 100%;
.message-item {
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
&:active {
background: #f8f8f8;
}
.message-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
line-height: 1.4;
}
.message-content {
font-size: 28rpx;
color: #666;
line-height: 1.8;
margin-bottom: 16rpx;
}
.message-time {
font-size: 24rpx;
color: #999;
}
}
}
//
.empty-wrapper {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -20,8 +20,9 @@
<view class="user-info-row" @click="handlePersonalProfile">
<image
class="user-avatar"
:src="userInfo.avatar || defaultAvatar"
:src="avatarLoadError ? defaultAvatar : (userInfo.avatar || defaultAvatar)"
mode="aspectFill"
@error="onAvatarError"
/>
<view class="user-detail">
<view class="user-name-row">
@ -130,9 +131,11 @@
<script>
import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import { getMyProfile } from '@/api/profile.js'
import { login } from '@/api/auth.js'
import { getViewedMe, getFavoritedMe, getUnlockedMe } from '@/api/interact.js'
import { getFullImageUrl } from '@/utils/image.js'
import Loading from '@/components/Loading/index.vue'
export default {
@ -142,13 +145,14 @@ export default {
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
//
const pageLoading = ref(true)
const statusBarHeight = ref(20)
//
const defaultAvatar = '/static/logo.png'
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
//
const interactCounts = ref({
@ -172,7 +176,7 @@ export default {
const userInfo = computed(() => ({
userId: userStore.userId,
nickname: userStore.nickname,
avatar: userStore.avatar,
avatar: getFullImageUrl(userStore.avatar),
xiangQinNo: userStore.xiangQinNo,
isProfileCompleted: userStore.isProfileCompleted,
isMember: userStore.isMember,
@ -289,6 +293,12 @@ export default {
uni.navigateTo({ url })
}
//
const avatarLoadError = ref(false)
const onAvatarError = () => {
avatarLoadError.value = true
}
onMounted(() => {
getSystemInfo()
initPage()
@ -301,6 +311,8 @@ export default {
userInfo,
interactCounts,
defaultAvatar,
avatarLoadError,
onAvatarError,
handleLogin,
handlePersonalProfile,
handlePreviewProfile,
@ -318,6 +330,10 @@ export default {
onShow() {
const userStore = useUserStore()
userStore.restoreFromStorage()
// 便
if (this.avatarLoadError !== undefined) {
this.avatarLoadError = false
}
if (userStore.isLoggedIn && this.loadInteractCounts) {
this.loadInteractCounts()
}

View File

@ -68,7 +68,8 @@
<text class="gender-year" :class="{ male: userDetail.childGender === 1 }">
{{ genderText }} · {{ userDetail.birthYear }}
</text>
<button class="share-btn" @click="handleShare">分享</button>
<view class="flex-spacer"></view>
<button class="share-btn" open-type="share">分享</button>
</view>
<view class="info-grid">
@ -257,6 +258,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShareAppMessage } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/user.js'
import { getUserDetail } from '@/api/user.js'
import { favorite, unlock } from '@/api/interact.js'
@ -292,7 +294,16 @@ const getSystemInfo = () => {
//
const handleBack = () => {
uni.navigateBack()
const pages = getCurrentPages()
if (pages.length > 1) {
//
uni.navigateBack()
} else {
//
uni.switchTab({
url: '/pages/index/index'
})
}
}
//
@ -521,11 +532,6 @@ const handleFavorite = async () => {
}
}
//
const handleShare = () => {
uni.showToast({ title: '请点击右上角分享', icon: 'none' })
}
//
const handleContact = () => {
//
@ -653,12 +659,11 @@ onMounted(() => {
})
//
defineExpose({
onShareAppMessage() {
return {
title: userDetail.value?.nickname ? `${userDetail.value.nickname}的相亲资料` : '相宜相亲',
path: `/pages/profile/detail?userId=${userId.value}`
}
onShareAppMessage(() => {
return {
title: userDetail.value?.nickname ? `${userDetail.value.nickname}的相亲资料` : '相宜相亲',
path: `/pages/profile/detail?userId=${userId.value}`,
imageUrl: '/static/logo.png'
}
})
</script>
@ -826,7 +831,6 @@ defineExpose({
.gender-year-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
.gender-year {
@ -839,13 +843,17 @@ defineExpose({
}
}
.flex-spacer {
flex: 1;
}
.share-btn {
padding: 12rpx 32rpx;
font-size: 26rpx;
color: #ff6b6b;
background: #fff;
border: 2rpx solid #ff6b6b;
border-radius: 30rpx;
padding: 16rpx 40rpx;
font-size: 28rpx;
color: #fff;
background: #4cd964;
border: none;
border-radius: 12rpx;
line-height: 1;
&::after {

View File

@ -21,8 +21,9 @@
<view class="avatar-wrapper" @click="handleChangeAvatar">
<image
class="avatar-img"
:src="userInfo.avatar || defaultAvatar"
:src="avatarLoadError ? defaultAvatar : (userInfo.avatar || defaultAvatar)"
mode="aspectFill"
@error="onAvatarError"
/>
<view class="avatar-edit-icon">
<text></text>
@ -64,27 +65,38 @@
<script>
import { ref, computed, onMounted, watch } from 'vue'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import { updateAvatar, updateNickname } from '@/api/user.js'
import { getMyProfile } from '@/api/profile.js'
import { getFullImageUrl } from '@/utils/image.js'
import config from '@/config/index.js'
export default {
name: 'PersonalPage',
setup() {
const userStore = useUserStore()
const defaultAvatar = '/static/logo.png'
const configStore = useConfigStore()
const statusBarHeight = ref(20)
const nickname = ref('')
// API
const API_BASE_URL = config.API_BASE_URL
// configStore
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
const userInfo = computed(() => ({
avatar: userStore.avatar,
avatar: getFullImageUrl(userStore.avatar),
xiangQinNo: userStore.xiangQinNo,
nickname: userStore.nickname
}))
//
const avatarLoadError = ref(false)
const onAvatarError = () => {
avatarLoadError.value = true
}
// store nickname
watch(() => userStore.nickname, (newVal) => {
if (newVal && !nickname.value) {
@ -142,7 +154,9 @@ export default {
// API
const saveRes = await updateAvatar(uploadRes.data.url)
if (saveRes && saveRes.code === 0) {
// store
userStore.updateUserInfo({ avatar: uploadRes.data.url })
avatarLoadError.value = false
uni.showToast({ title: '头像更新成功', icon: 'success' })
} else {
uni.showToast({ title: saveRes?.message || '保存失败', icon: 'none' })
@ -240,6 +254,8 @@ export default {
defaultAvatar,
statusBarHeight,
nickname,
avatarLoadError,
onAvatarError,
handleBack,
handleChangeAvatar,
handleCopyXiangQinNo,
@ -249,6 +265,10 @@ export default {
},
//
onShow() {
//
if (this.avatarLoadError !== undefined) {
this.avatarLoadError = false
}
if (this.initNickname) {
this.initNickname()
}

View File

@ -8,7 +8,8 @@ import {
getLastPopupDate, setLastPopupDate,
getMemberAdClosedDate, setMemberAdClosedDate,
getMemberAdClosedForever, setMemberAdClosedForever,
getGenderPreference
getGenderPreference,
getDefaultAvatar, setDefaultAvatar
} from '../utils/storage.js'
/**
@ -46,6 +47,9 @@ export const useConfigStore = defineStore('config', {
memberAdClosedDate: getMemberAdClosedDate() || '',
memberAdClosedForever: getMemberAdClosedForever() || false,
// 默认头像(从后台配置获取)
defaultAvatar: getDefaultAvatar() || '/static/logo.png',
// 弹窗显示状态
showGenderPopup: false,
showDailyPopup: false,
@ -71,7 +75,12 @@ export const useConfigStore = defineStore('config', {
/**
* 是否有会员广告配置
*/
hasMemberAdConfig: (state) => state.memberAdConfig !== null && state.memberAdConfig.status === 1
hasMemberAdConfig: (state) => state.memberAdConfig !== null && state.memberAdConfig.status === 1,
/**
* 获取默认头像URL
*/
getDefaultAvatar: (state) => state.defaultAvatar || '/static/logo.png'
},
actions: {
@ -80,6 +89,7 @@ export const useConfigStore = defineStore('config', {
* @param {Object} config - 首页配置
* @param {Array} config.banners - Banner列表
* @param {Array} config.kingKongs - 金刚位列表
* @param {string} config.defaultAvatar - 默认头像URL
*/
setHomeConfig(config) {
if (config.banners) {
@ -88,6 +98,10 @@ export const useConfigStore = defineStore('config', {
if (config.kingKongs) {
this.kingKongs = config.kingKongs
}
if (config.defaultAvatar) {
this.defaultAvatar = config.defaultAvatar
setDefaultAvatar(config.defaultAvatar)
}
},
/**
@ -247,6 +261,18 @@ export const useConfigStore = defineStore('config', {
this.showGenderPopup = false
this.showDailyPopup = false
this.showMemberAd = false
// 不重置 defaultAvatar保留后台配置
},
/**
* 设置默认头像
* @param {string} avatar - 默认头像URL
*/
setDefaultAvatarConfig(avatar) {
if (avatar) {
this.defaultAvatar = avatar
setDefaultAvatar(avatar)
}
}
}
})

View File

@ -11,7 +11,8 @@ const STORAGE_KEYS = {
LAST_POPUP_DATE: 'lastPopupDate',
MEMBER_AD_CLOSED_DATE: 'memberAdClosedDate',
MEMBER_AD_CLOSED_FOREVER: 'memberAdClosedForever',
GENDER_PREFERENCE: 'genderPreference'
GENDER_PREFERENCE: 'genderPreference',
DEFAULT_AVATAR: 'defaultAvatar'
}
/**
@ -143,4 +144,13 @@ export function getGenderPreference() {
return getStorage(STORAGE_KEYS.GENDER_PREFERENCE, 0)
}
// 默认头像
export function setDefaultAvatar(avatar) {
return setStorage(STORAGE_KEYS.DEFAULT_AVATAR, avatar)
}
export function getDefaultAvatar() {
return getStorage(STORAGE_KEYS.DEFAULT_AVATAR, '/static/logo.png')
}
export { STORAGE_KEYS }

View File

@ -92,6 +92,21 @@ public class ConfigController : ControllerBase
Content = content ?? "暂无隐私协议内容"
});
}
/// <summary>
/// 获取默认头像配置
/// </summary>
/// <returns>默认头像URL</returns>
[HttpGet("defaultAvatar")]
[AllowAnonymous]
public async Task<ApiResponse<DefaultAvatarConfigResponse>> GetDefaultAvatar()
{
var avatarUrl = await _systemConfigService.GetDefaultAvatarAsync();
return ApiResponse<DefaultAvatarConfigResponse>.Success(new DefaultAvatarConfigResponse
{
AvatarUrl = avatarUrl
});
}
}
/// <summary>
@ -104,3 +119,14 @@ public class AgreementContentResponse
/// </summary>
public string Content { get; set; } = string.Empty;
}
/// <summary>
/// 默认头像配置响应
/// </summary>
public class DefaultAvatarConfigResponse
{
/// <summary>
/// 默认头像URL
/// </summary>
public string? AvatarUrl { get; set; }
}