逻辑优化
This commit is contained in:
parent
4827573849
commit
cb4c207ca8
|
|
@ -148,6 +148,27 @@ export async function checkUnlock(targetUserId) {
|
|||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取互动新增数量统计
|
||||
*
|
||||
* @returns {Promise<Object>} 各类互动的新增数量 { viewedMeCount, favoritedMeCount, unlockedMeCount }
|
||||
*/
|
||||
export async function getInteractCounts() {
|
||||
const response = await get('/interact/counts')
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记互动为已读
|
||||
*
|
||||
* @param {string} type - 互动类型:viewedMe, favoritedMe, unlockedMe
|
||||
* @returns {Promise<Object>} 操作结果
|
||||
*/
|
||||
export async function markInteractAsRead(type) {
|
||||
const response = await post(`/interact/markRead/${type}`)
|
||||
return response
|
||||
}
|
||||
|
||||
export default {
|
||||
recordView,
|
||||
favorite,
|
||||
|
|
@ -159,5 +180,7 @@ export default {
|
|||
getFavoritedMe,
|
||||
getMyFavorite,
|
||||
getUnlockedMe,
|
||||
getMyUnlocked
|
||||
getMyUnlocked,
|
||||
getInteractCounts,
|
||||
markInteractAsRead
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,8 @@ export default {
|
|||
}
|
||||
},
|
||||
handleButtonClick() {
|
||||
if (this.linkUrl) {
|
||||
// 服务号弹窗由父组件处理跳转逻辑,不在这里处理
|
||||
if (this.type !== 'serviceAccount' && this.linkUrl) {
|
||||
this.navigateToLink()
|
||||
}
|
||||
this.$emit('confirm')
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -158,9 +158,8 @@
|
|||
deleteSession
|
||||
} from '@/api/chat.js'
|
||||
import {
|
||||
getViewedMe,
|
||||
getFavoritedMe,
|
||||
getUnlockedMe
|
||||
getInteractCounts,
|
||||
markInteractAsRead
|
||||
} from '@/api/interact.js'
|
||||
import {
|
||||
formatTimestamp
|
||||
|
|
@ -173,7 +172,6 @@
|
|||
|
||||
// 页面状态
|
||||
const pageLoading = ref(true)
|
||||
const listLoading = ref(false)
|
||||
const isRefreshing = ref(false)
|
||||
|
||||
// 系统信息
|
||||
|
|
@ -222,52 +220,20 @@
|
|||
}).exec()
|
||||
}
|
||||
|
||||
// 加载互动统计
|
||||
/**
|
||||
* 加载互动统计(从后端获取新增数量)
|
||||
*/
|
||||
const loadInteractCounts = async () => {
|
||||
if (!userStore.isLoggedIn) return
|
||||
|
||||
try {
|
||||
// 获取本地存储的最后查看时间
|
||||
const lastViewedTime = uni.getStorageSync('lastViewedMeTime') || 0
|
||||
const lastFavoritedTime = uni.getStorageSync('lastFavoritedMeTime') || 0
|
||||
const lastUnlockedTime = uni.getStorageSync('lastUnlockedMeTime') || 0
|
||||
|
||||
const [viewedRes, favoritedRes, unlockedRes] = await Promise.all([
|
||||
getViewedMe(1, 100).catch(() => null),
|
||||
getFavoritedMe(1, 100).catch(() => null),
|
||||
getUnlockedMe(1, 100).catch(() => null)
|
||||
])
|
||||
|
||||
// 计算新增数量(在最后查看时间之后的记录)
|
||||
let viewedCount = 0
|
||||
let favoritedCount = 0
|
||||
let unlockedCount = 0
|
||||
|
||||
if (viewedRes?.data?.items) {
|
||||
viewedCount = viewedRes.data.items.filter(item => {
|
||||
const itemTime = new Date(item.viewTime || item.createTime).getTime()
|
||||
return itemTime > lastViewedTime
|
||||
}).length
|
||||
}
|
||||
|
||||
if (favoritedRes?.data?.items) {
|
||||
favoritedCount = favoritedRes.data.items.filter(item => {
|
||||
const itemTime = new Date(item.favoriteTime || item.createTime).getTime()
|
||||
return itemTime > lastFavoritedTime
|
||||
}).length
|
||||
}
|
||||
|
||||
if (unlockedRes?.data?.items) {
|
||||
unlockedCount = unlockedRes.data.items.filter(item => {
|
||||
const itemTime = new Date(item.unlockTime || item.createTime).getTime()
|
||||
return itemTime > lastUnlockedTime
|
||||
}).length
|
||||
}
|
||||
|
||||
interactCounts.value = {
|
||||
viewedMe: viewedCount,
|
||||
favoritedMe: favoritedCount,
|
||||
unlockedMe: unlockedCount
|
||||
const res = await getInteractCounts()
|
||||
if (res?.code === 0 && res.data) {
|
||||
interactCounts.value = {
|
||||
viewedMe: res.data.viewedMeCount || 0,
|
||||
favoritedMe: res.data.favoritedMeCount || 0,
|
||||
unlockedMe: res.data.unlockedMeCount || 0
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载互动统计失败:', error)
|
||||
|
|
@ -278,7 +244,6 @@
|
|||
const loadSessions = async () => {
|
||||
if (!userStore.isLoggedIn) return
|
||||
|
||||
listLoading.value = true
|
||||
try {
|
||||
const res = await getSessions()
|
||||
if (res?.success || res?.code === 0) {
|
||||
|
|
@ -291,8 +256,6 @@
|
|||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
listLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,23 +291,28 @@
|
|||
const formatTime = (timestamp) => formatTimestamp(timestamp)
|
||||
|
||||
// 导航到互动页面
|
||||
const navigateTo = (url) => {
|
||||
// 记录最后查看时间
|
||||
const now = Date.now()
|
||||
const navigateTo = async (url) => {
|
||||
// 根据 URL 确定互动类型并标记已读
|
||||
let interactType = ''
|
||||
if (url.includes('viewedMe')) {
|
||||
uni.setStorageSync('lastViewedMeTime', now)
|
||||
interactType = 'viewedMe'
|
||||
interactCounts.value.viewedMe = 0
|
||||
} else if (url.includes('favoritedMe')) {
|
||||
uni.setStorageSync('lastFavoritedMeTime', now)
|
||||
interactType = 'favoritedMe'
|
||||
interactCounts.value.favoritedMe = 0
|
||||
} else if (url.includes('unlockedMe')) {
|
||||
uni.setStorageSync('lastUnlockedMeTime', now)
|
||||
interactType = 'unlockedMe'
|
||||
interactCounts.value.unlockedMe = 0
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url
|
||||
})
|
||||
// 调用后端标记已读(异步,不阻塞跳转)
|
||||
if (interactType) {
|
||||
markInteractAsRead(interactType).catch(err => {
|
||||
console.error('标记已读失败:', err)
|
||||
})
|
||||
}
|
||||
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
// 点击会话
|
||||
|
|
|
|||
|
|
@ -147,9 +147,7 @@
|
|||
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 { getInteractCounts, markInteractAsRead } from '@/api/interact.js'
|
||||
import { getFullImageUrl } from '@/utils/image.js'
|
||||
import Loading from '@/components/Loading/index.vue'
|
||||
|
||||
|
|
@ -211,52 +209,20 @@ export default {
|
|||
return configStore.memberEntryImage ? getFullImageUrl(configStore.memberEntryImage) : ''
|
||||
})
|
||||
|
||||
// 加载互动统计
|
||||
/**
|
||||
* 加载互动统计(从后端获取新增数量)
|
||||
*/
|
||||
const loadInteractCounts = async () => {
|
||||
if (!userStore.isLoggedIn) return
|
||||
|
||||
try {
|
||||
// 获取本地存储的最后查看时间
|
||||
const lastViewedTime = uni.getStorageSync('lastViewedMeTime') || 0
|
||||
const lastFavoritedTime = uni.getStorageSync('lastFavoritedMeTime') || 0
|
||||
const lastUnlockedTime = uni.getStorageSync('lastUnlockedMeTime') || 0
|
||||
|
||||
const [viewedRes, favoritedRes, unlockedRes] = await Promise.all([
|
||||
getViewedMe(1, 100).catch(() => null),
|
||||
getFavoritedMe(1, 100).catch(() => null),
|
||||
getUnlockedMe(1, 100).catch(() => null)
|
||||
])
|
||||
|
||||
// 计算新增数量(在最后查看时间之后的记录)
|
||||
let viewedCount = 0
|
||||
let favoritedCount = 0
|
||||
let unlockedCount = 0
|
||||
|
||||
if (viewedRes?.data?.items) {
|
||||
viewedCount = viewedRes.data.items.filter(item => {
|
||||
const itemTime = new Date(item.viewTime || item.createTime).getTime()
|
||||
return itemTime > lastViewedTime
|
||||
}).length
|
||||
}
|
||||
|
||||
if (favoritedRes?.data?.items) {
|
||||
favoritedCount = favoritedRes.data.items.filter(item => {
|
||||
const itemTime = new Date(item.favoriteTime || item.createTime).getTime()
|
||||
return itemTime > lastFavoritedTime
|
||||
}).length
|
||||
}
|
||||
|
||||
if (unlockedRes?.data?.items) {
|
||||
unlockedCount = unlockedRes.data.items.filter(item => {
|
||||
const itemTime = new Date(item.unlockTime || item.createTime).getTime()
|
||||
return itemTime > lastUnlockedTime
|
||||
}).length
|
||||
}
|
||||
|
||||
interactCounts.value = {
|
||||
viewedMe: viewedCount,
|
||||
favoritedMe: favoritedCount,
|
||||
unlockedMe: unlockedCount
|
||||
const res = await getInteractCounts()
|
||||
if (res?.code === 0 && res.data) {
|
||||
interactCounts.value = {
|
||||
viewedMe: res.data.viewedMeCount || 0,
|
||||
favoritedMe: res.data.favoritedMeCount || 0,
|
||||
unlockedMe: res.data.unlockedMeCount || 0
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载互动统计失败:', error)
|
||||
|
|
@ -288,15 +254,6 @@ export default {
|
|||
uni.navigateTo({ url: '/pages/profile/personal' })
|
||||
}
|
||||
|
||||
// 预览资料
|
||||
const handlePreviewProfile = () => {
|
||||
if (!userStore.isProfileCompleted) {
|
||||
handleEditProfile()
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/profile/preview' })
|
||||
}
|
||||
|
||||
// 编辑资料
|
||||
const handleEditProfile = () => {
|
||||
uni.navigateTo({ url: '/pages/profile/edit' })
|
||||
|
|
@ -347,20 +304,27 @@ export default {
|
|||
}
|
||||
|
||||
// 导航到互动页面
|
||||
const navigateTo = (url) => {
|
||||
// 记录最后查看时间
|
||||
const now = Date.now()
|
||||
const navigateTo = async (url) => {
|
||||
// 根据 URL 确定互动类型并标记已读
|
||||
let interactType = ''
|
||||
if (url.includes('viewedMe')) {
|
||||
uni.setStorageSync('lastViewedMeTime', now)
|
||||
interactType = 'viewedMe'
|
||||
interactCounts.value.viewedMe = 0
|
||||
} else if (url.includes('favoritedMe')) {
|
||||
uni.setStorageSync('lastFavoritedMeTime', now)
|
||||
interactType = 'favoritedMe'
|
||||
interactCounts.value.favoritedMe = 0
|
||||
} else if (url.includes('unlockedMe')) {
|
||||
uni.setStorageSync('lastUnlockedMeTime', now)
|
||||
interactType = 'unlockedMe'
|
||||
interactCounts.value.unlockedMe = 0
|
||||
}
|
||||
|
||||
// 调用后端标记已读(异步,不阻塞跳转)
|
||||
if (interactType) {
|
||||
markInteractAsRead(interactType).catch(err => {
|
||||
console.error('标记已读失败:', err)
|
||||
})
|
||||
}
|
||||
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
|
|
@ -388,7 +352,6 @@ export default {
|
|||
onAvatarError,
|
||||
handleLogin,
|
||||
handlePersonalProfile,
|
||||
handlePreviewProfile,
|
||||
handleEditProfile,
|
||||
handleMember,
|
||||
handleButler,
|
||||
|
|
|
|||
|
|
@ -396,7 +396,8 @@
|
|||
console.log('调起微信支付...')
|
||||
await requestPayment(paymentParams)
|
||||
|
||||
// 支付成功,进入上传步骤 (Requirements 12.2)
|
||||
// 支付成功,更新状态并进入上传步骤 (Requirements 12.2)
|
||||
isPaid.value = true
|
||||
currentStep.value = 2
|
||||
|
||||
uni.showToast({
|
||||
|
|
@ -685,9 +686,25 @@
|
|||
handleDone
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
async onShow() {
|
||||
const userStore = useUserStore()
|
||||
userStore.restoreFromStorage()
|
||||
|
||||
// 如果已登录,重新获取实名认证状态(用户可能从会员页面返回,已开通会员)
|
||||
const { getToken } = require('@/utils/storage.js')
|
||||
if (getToken()) {
|
||||
const { get } = require('@/api/request.js')
|
||||
try {
|
||||
const res = await get('/realname/status')
|
||||
if (res && (res.success || res.code === 0) && res.data) {
|
||||
if (res.data.isRealName) {
|
||||
userStore.setRealNameStatus(true)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新实名状态失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ export const useUserStore = defineStore('user', {
|
|||
isMember: false,
|
||||
memberLevel: 0,
|
||||
isRealName: false,
|
||||
genderPreference: getGenderPreference() || 0
|
||||
genderPreference: getGenderPreference() || 0,
|
||||
/** 上次从服务器刷新用户信息的时间戳(用于节流) */
|
||||
lastRefreshTime: 0
|
||||
}),
|
||||
|
||||
getters: {
|
||||
|
|
@ -215,6 +217,8 @@ export const useUserStore = defineStore('user', {
|
|||
memberLevel: data.memberLevel,
|
||||
isRealName: data.isRealName
|
||||
})
|
||||
// 更新刷新时间戳
|
||||
this.lastRefreshTime = Date.now()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新用户信息失败:', error)
|
||||
|
|
|
|||
200
miniapp/utils/navigate.js
Normal file
200
miniapp/utils/navigate.js
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/**
|
||||
* 页面导航工具函数
|
||||
* 统一处理小程序内的各种跳转逻辑
|
||||
*/
|
||||
|
||||
/**
|
||||
* TabBar 页面路径列表
|
||||
* 这些页面需要使用 switchTab 而不是 navigateTo
|
||||
*/
|
||||
export const TABBAR_PAGES = [
|
||||
'/pages/index/index',
|
||||
'/pages/message/index',
|
||||
'/pages/mine/index'
|
||||
]
|
||||
|
||||
/**
|
||||
* 链接类型枚举
|
||||
*/
|
||||
export const LINK_TYPE = {
|
||||
/** 内部页面 */
|
||||
INTERNAL: 1,
|
||||
/** 外部链接(H5) */
|
||||
EXTERNAL: 2,
|
||||
/** 其他小程序 */
|
||||
MINI_PROGRAM: 3
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 TabBar 页面
|
||||
* @param {string} url - 页面路径
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isTabBarPage(url) {
|
||||
return TABBAR_PAGES.includes(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一页面跳转函数
|
||||
* 根据链接类型自动选择合适的跳转方式
|
||||
*
|
||||
* @param {string} url - 跳转地址
|
||||
* @param {number} linkType - 链接类型:1=内部页面, 2=外部链接, 3=小程序
|
||||
* @param {Object} options - 额外配置
|
||||
* @param {Function} options.onFail - 跳转失败回调
|
||||
* @param {Function} options.onSuccess - 跳转成功回调
|
||||
*/
|
||||
export function navigateToPage(url, linkType = LINK_TYPE.INTERNAL, options = {}) {
|
||||
if (!url) {
|
||||
console.warn('[navigate] url 为空,跳过跳转')
|
||||
return
|
||||
}
|
||||
|
||||
const { onFail, onSuccess } = options
|
||||
|
||||
switch (linkType) {
|
||||
case LINK_TYPE.EXTERNAL:
|
||||
// 外部链接:复制到剪贴板(小程序限制无法直接打开外部链接)
|
||||
handleExternalLink(url)
|
||||
break
|
||||
|
||||
case LINK_TYPE.MINI_PROGRAM:
|
||||
// 跳转其他小程序
|
||||
handleMiniProgramLink(url, { onFail, onSuccess })
|
||||
break
|
||||
|
||||
case LINK_TYPE.INTERNAL:
|
||||
default:
|
||||
// 内部页面跳转
|
||||
handleInternalLink(url, { onFail, onSuccess })
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理内部页面跳转
|
||||
* 自动判断是否为 TabBar 页面
|
||||
*
|
||||
* @param {string} url - 页面路径
|
||||
* @param {Object} callbacks - 回调函数
|
||||
*/
|
||||
function handleInternalLink(url, { onFail, onSuccess } = {}) {
|
||||
const navigateMethod = isTabBarPage(url) ? uni.switchTab : uni.navigateTo
|
||||
|
||||
navigateMethod({
|
||||
url,
|
||||
success: () => {
|
||||
onSuccess && onSuccess()
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('[navigate] 页面跳转失败:', url, err)
|
||||
if (onFail) {
|
||||
onFail(err)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '页面跳转失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理外部链接
|
||||
* 由于小程序限制,外部链接只能复制到剪贴板
|
||||
*
|
||||
* @param {string} url - 外部链接地址
|
||||
*/
|
||||
function handleExternalLink(url) {
|
||||
uni.setClipboardData({
|
||||
data: url,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '链接已复制',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '复制失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理小程序跳转
|
||||
* URL 格式:appId:path(path 可选)
|
||||
*
|
||||
* @param {string} url - 格式为 "appId:path" 的字符串
|
||||
* @param {Object} callbacks - 回调函数
|
||||
*/
|
||||
function handleMiniProgramLink(url, { onFail, onSuccess } = {}) {
|
||||
const [appId, path] = url.split(':')
|
||||
|
||||
if (!appId) {
|
||||
console.error('[navigate] 小程序 appId 为空')
|
||||
uni.showToast({
|
||||
title: '跳转配置错误',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.navigateToMiniProgram({
|
||||
appId,
|
||||
path: path || '',
|
||||
success: () => {
|
||||
onSuccess && onSuccess()
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('[navigate] 跳转小程序失败:', appId, err)
|
||||
if (onFail) {
|
||||
onFail(err)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '跳转失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到 WebView 页面
|
||||
* 用于在小程序内打开 H5 页面
|
||||
*
|
||||
* @param {string} url - H5 页面地址
|
||||
*/
|
||||
export function navigateToWebView(url) {
|
||||
if (!url) return
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/webview/index?url=${encodeURIComponent(url)}`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上一页
|
||||
* @param {number} delta - 返回的页面数,默认 1
|
||||
*/
|
||||
export function navigateBack(delta = 1) {
|
||||
uni.navigateBack({ delta })
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定向到指定页面(关闭当前页)
|
||||
* @param {string} url - 页面路径
|
||||
*/
|
||||
export function redirectTo(url) {
|
||||
if (!url) return
|
||||
|
||||
if (isTabBarPage(url)) {
|
||||
uni.switchTab({ url })
|
||||
} else {
|
||||
uni.redirectTo({ url })
|
||||
}
|
||||
}
|
||||
|
|
@ -250,6 +250,36 @@ public class InteractController : ControllerBase
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取互动新增数量统计
|
||||
/// </summary>
|
||||
/// <returns>各类互动的新增数量</returns>
|
||||
[HttpGet("counts")]
|
||||
public async Task<ApiResponse<InteractCountsResponse>> GetInteractCounts()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
var result = await _interactService.GetInteractCountsAsync(userId);
|
||||
return ApiResponse<InteractCountsResponse>.Success(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记互动为已读
|
||||
/// </summary>
|
||||
/// <param name="type">互动类型:viewedMe, favoritedMe, unlockedMe</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[HttpPost("markRead/{type}")]
|
||||
public async Task<ApiResponse<bool>> MarkInteractAsRead(string type)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(type))
|
||||
{
|
||||
return ApiResponse<bool>.Error(ErrorCodes.InvalidParameter, "互动类型不能为空");
|
||||
}
|
||||
|
||||
var userId = GetCurrentUserId();
|
||||
await _interactService.MarkInteractAsReadAsync(userId, type);
|
||||
return ApiResponse<bool>.Success(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户ID
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -328,3 +328,24 @@ public class UnlockRecordResponse
|
|||
/// </summary>
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 互动新增数量统计响应
|
||||
/// </summary>
|
||||
public class InteractCountsResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// "看过我"新增数量
|
||||
/// </summary>
|
||||
public int ViewedMeCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "收藏我"新增数量
|
||||
/// </summary>
|
||||
public int FavoritedMeCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "解锁我"新增数量
|
||||
/// </summary>
|
||||
public int UnlockedMeCount { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,4 +86,18 @@ public interface IInteractService
|
|||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>剩余解锁次数</returns>
|
||||
Task<int> GetRemainingUnlockQuotaAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取互动新增数量统计
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>各类互动的新增数量</returns>
|
||||
Task<InteractCountsResponse> GetInteractCountsAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 标记互动为已读
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="type">互动类型:viewedMe, favoritedMe, unlockedMe</param>
|
||||
Task MarkInteractAsReadAsync(long userId, string type);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -776,4 +776,72 @@ public class InteractService : IInteractService
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 互动统计
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<InteractCountsResponse> GetInteractCountsAsync(long userId)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return new InteractCountsResponse();
|
||||
}
|
||||
|
||||
// 获取用户最后查看各类互动的时间,如果为空则使用用户创建时间
|
||||
var lastViewedMeTime = user.LastViewedMeReadTime ?? user.CreateTime;
|
||||
var lastFavoritedMeTime = user.LastFavoritedMeReadTime ?? user.CreateTime;
|
||||
var lastUnlockedMeTime = user.LastUnlockedMeReadTime ?? user.CreateTime;
|
||||
|
||||
// 统计各类互动的新增数量
|
||||
var viewedMeCount = await _viewRepository.CountAsync(v =>
|
||||
v.TargetUserId == userId && v.LastViewTime > lastViewedMeTime);
|
||||
|
||||
var favoritedMeCount = await _favoriteRepository.CountAsync(f =>
|
||||
f.TargetUserId == userId && f.CreateTime > lastFavoritedMeTime);
|
||||
|
||||
var unlockedMeCount = await _unlockRepository.CountAsync(u =>
|
||||
u.TargetUserId == userId && u.CreateTime > lastUnlockedMeTime);
|
||||
|
||||
return new InteractCountsResponse
|
||||
{
|
||||
ViewedMeCount = (int)viewedMeCount,
|
||||
FavoritedMeCount = (int)favoritedMeCount,
|
||||
UnlockedMeCount = (int)unlockedMeCount
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task MarkInteractAsReadAsync(long userId, string type)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
|
||||
}
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
switch (type.ToLower())
|
||||
{
|
||||
case "viewedme":
|
||||
user.LastViewedMeReadTime = now;
|
||||
break;
|
||||
case "favoritedme":
|
||||
user.LastFavoritedMeReadTime = now;
|
||||
break;
|
||||
case "unlockedme":
|
||||
user.LastUnlockedMeReadTime = now;
|
||||
break;
|
||||
default:
|
||||
throw new BusinessException(ErrorCodes.InvalidParameter, "无效的互动类型");
|
||||
}
|
||||
|
||||
user.UpdateTime = now;
|
||||
await _userRepository.UpdateAsync(user);
|
||||
|
||||
_logger.LogInformation("标记互动已读: UserId={UserId}, Type={Type}", userId, type);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,21 @@ public class User : SoftDeleteEntity
|
|||
/// </summary>
|
||||
public DateTime? LastLoginTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后查看"看过我"的时间
|
||||
/// </summary>
|
||||
public DateTime? LastViewedMeReadTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后查看"收藏我"的时间
|
||||
/// </summary>
|
||||
public DateTime? LastFavoritedMeReadTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后查看"解锁我"的时间
|
||||
/// </summary>
|
||||
public DateTime? LastUnlockedMeReadTime { get; set; }
|
||||
|
||||
#region 导航属性
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user