会员,未读消息
This commit is contained in:
parent
23a620d69d
commit
de2b59ca90
|
|
@ -112,6 +112,7 @@ export interface MemberIconsConfig {
|
|||
unlimitedMemberIcon?: string
|
||||
sincereMemberIcon?: string
|
||||
familyMemberIcon?: string
|
||||
timeLimitedMemberIcon?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -65,3 +65,17 @@ export function updateMemberTier(id: number, data: UpdateMemberTierRequest) {
|
|||
export function deleteMemberTier(id: number) {
|
||||
return request.delete(`/admin/memberTiers/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认选中会员等级
|
||||
*/
|
||||
export function getDefaultTierLevel() {
|
||||
return request.get<number | null>('/admin/memberTiers/defaultLevel')
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认选中会员等级
|
||||
*/
|
||||
export function setDefaultTierLevel(level: number) {
|
||||
return request.put('/admin/memberTiers/defaultLevel', { level })
|
||||
}
|
||||
|
|
|
|||
2
admin/src/types/user.d.ts
vendored
2
admin/src/types/user.d.ts
vendored
|
|
@ -68,6 +68,7 @@ export interface UserQueryParams {
|
|||
registerEndTime?: string
|
||||
gender?: number
|
||||
city?: string
|
||||
isTestUser?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -94,6 +95,7 @@ export interface UserListItem {
|
|||
contactCount: number
|
||||
createTime: string
|
||||
lastLoginTime: string
|
||||
isTestUser: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@
|
|||
<el-tag :type="tier.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ tier.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
<el-tag v-if="defaultLevel === tier.level" type="warning" size="small" style="margin-left: 4px;">
|
||||
默认选中
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 等级信息 -->
|
||||
|
|
@ -70,6 +73,15 @@
|
|||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="tier-actions">
|
||||
<el-button
|
||||
v-if="defaultLevel !== tier.level"
|
||||
type="warning"
|
||||
size="small"
|
||||
plain
|
||||
@click="handleSetDefault(tier)"
|
||||
>
|
||||
设为默认
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="handleEdit(tier)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
|
|
@ -220,6 +232,8 @@ import {
|
|||
createMemberTier,
|
||||
updateMemberTier,
|
||||
deleteMemberTier,
|
||||
getDefaultTierLevel,
|
||||
setDefaultTierLevel,
|
||||
type MemberTier
|
||||
} from '@/api/memberTier'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
|
@ -235,6 +249,7 @@ const isEdit = ref(false)
|
|||
const submitting = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const editingId = ref<number | null>(null)
|
||||
const defaultLevel = ref<number | null>(null)
|
||||
|
||||
const formData = reactive({
|
||||
level: 1,
|
||||
|
|
@ -270,8 +285,12 @@ const getFullUrl = (url: string) => {
|
|||
const loadList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await getMemberTierList()
|
||||
const [res, defaultRes]: any[] = await Promise.all([
|
||||
getMemberTierList(),
|
||||
getDefaultTierLevel()
|
||||
])
|
||||
tierList.value = res || []
|
||||
defaultLevel.value = defaultRes ?? null
|
||||
} catch (error) {
|
||||
console.error('加载列表失败:', error)
|
||||
} finally {
|
||||
|
|
@ -332,6 +351,16 @@ const handleDelete = async (row: MemberTier) => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleSetDefault = async (tier: MemberTier) => {
|
||||
try {
|
||||
await setDefaultTierLevel(tier.level)
|
||||
defaultLevel.value = tier.level
|
||||
ElMessage.success(`已设置「${tier.name}」为默认选中`)
|
||||
} catch (error) {
|
||||
console.error('设置默认失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUploadSuccess = (response: any) => {
|
||||
if (response.code === 0 && response.data) {
|
||||
formData.benefitsImage = response.data.url
|
||||
|
|
@ -575,11 +604,10 @@ onMounted(() => {
|
|||
|
||||
/* 排序标识 */
|
||||
.tier-sort {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 12px;
|
||||
text-align: right;
|
||||
font-size: 11px;
|
||||
color: #c0c4cc;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
|
|
|
|||
|
|
@ -147,6 +147,28 @@
|
|||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="限时会员图标">
|
||||
<div class="icon-upload">
|
||||
<el-upload
|
||||
class="icon-uploader"
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="handleTimeLimitedMemberIconSuccess"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
accept="image/*"
|
||||
>
|
||||
<img v-if="configForm.timeLimitedMemberIcon" :src="getFullUrl(configForm.timeLimitedMemberIcon)" class="icon-preview" />
|
||||
<el-icon v-else class="icon-uploader-icon"><Plus /></el-icon>
|
||||
</el-upload>
|
||||
<div class="avatar-tip">
|
||||
<p>建议尺寸:64x64像素</p>
|
||||
<p>支持格式:PNG(透明背景)</p>
|
||||
<p>限时会员用户显示的图标</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 会员入口图设置 -->
|
||||
<el-form-item label="会员入口图">
|
||||
<div class="banner-upload">
|
||||
|
|
@ -276,6 +298,7 @@ const configForm = ref({
|
|||
unlimitedMemberIcon: '',
|
||||
sincereMemberIcon: '',
|
||||
familyMemberIcon: '',
|
||||
timeLimitedMemberIcon: '',
|
||||
memberEntryImage: '',
|
||||
realNamePrice: 88
|
||||
})
|
||||
|
|
@ -324,6 +347,7 @@ const loadConfig = async () => {
|
|||
configForm.value.unlimitedMemberIcon = memberIconsRes.unlimitedMemberIcon || ''
|
||||
configForm.value.sincereMemberIcon = memberIconsRes.sincereMemberIcon || ''
|
||||
configForm.value.familyMemberIcon = memberIconsRes.familyMemberIcon || ''
|
||||
configForm.value.timeLimitedMemberIcon = memberIconsRes.timeLimitedMemberIcon || ''
|
||||
}
|
||||
if (memberEntryRes) {
|
||||
configForm.value.memberEntryImage = memberEntryRes.imageUrl || ''
|
||||
|
|
@ -409,6 +433,15 @@ const handleFamilyMemberIconSuccess = (response) => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleTimeLimitedMemberIconSuccess = (response) => {
|
||||
if (response.code === 0 && response.data) {
|
||||
configForm.value.timeLimitedMemberIcon = response.data.url
|
||||
ElMessage.success('上传成功')
|
||||
} else {
|
||||
ElMessage.error(response.message || '上传失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleMemberEntryImageSuccess = (response) => {
|
||||
if (response.code === 0 && response.data) {
|
||||
configForm.value.memberEntryImage = response.data.url
|
||||
|
|
@ -453,9 +486,10 @@ const saveBasicConfig = async () => {
|
|||
const memberIcons = {
|
||||
unlimitedMemberIcon: configForm.value.unlimitedMemberIcon || undefined,
|
||||
sincereMemberIcon: configForm.value.sincereMemberIcon || undefined,
|
||||
familyMemberIcon: configForm.value.familyMemberIcon || undefined
|
||||
familyMemberIcon: configForm.value.familyMemberIcon || undefined,
|
||||
timeLimitedMemberIcon: configForm.value.timeLimitedMemberIcon || undefined
|
||||
}
|
||||
if (memberIcons.unlimitedMemberIcon || memberIcons.sincereMemberIcon || memberIcons.familyMemberIcon) {
|
||||
if (memberIcons.unlimitedMemberIcon || memberIcons.sincereMemberIcon || memberIcons.familyMemberIcon || memberIcons.timeLimitedMemberIcon) {
|
||||
promises.push(setMemberIcons(memberIcons))
|
||||
}
|
||||
// 保存实名认证费用
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ const searchForm = reactive<Partial<UserQueryParams>>({
|
|||
isMember: undefined,
|
||||
memberLevel: undefined,
|
||||
isRealName: undefined,
|
||||
gender: undefined
|
||||
gender: undefined,
|
||||
isTestUser: undefined
|
||||
})
|
||||
|
||||
// 分页参数
|
||||
|
|
@ -77,6 +78,13 @@ const genderOptions = [
|
|||
{ label: '女', value: 2 }
|
||||
]
|
||||
|
||||
// 用户类型选项
|
||||
const userTypeOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '真实用户', value: false },
|
||||
{ label: '测试用户', value: true }
|
||||
]
|
||||
|
||||
// 获取用户列表
|
||||
const fetchUserList = async () => {
|
||||
loading.value = true
|
||||
|
|
@ -320,6 +328,21 @@ onMounted(() => {
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户类型">
|
||||
<el-select
|
||||
v-model="searchForm.isTestUser"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userTypeOptions"
|
||||
:key="String(item.value)"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</SearchForm>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
|
|
@ -351,6 +374,16 @@ onMounted(() => {
|
|||
fixed="left"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="用户类型"
|
||||
min-width="80"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isTestUser" type="info" size="small">测试</el-tag>
|
||||
<el-tag v-else type="success" size="small">真实</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="用户信息"
|
||||
min-width="180"
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@
|
|||
import { get, post, del } from './request'
|
||||
|
||||
/**
|
||||
* 获取会员信息
|
||||
* WHEN a user visits member page, THE XiangYi_MiniApp SHALL display three membership tiers
|
||||
* 获取会员信息(未登录时只返回等级配置)
|
||||
* Requirements: 10.1
|
||||
*
|
||||
* @returns {Promise<Object>} 会员信息
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@
|
|||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/store/user.js'
|
||||
import { useConfigStore } from '@/store/config.js'
|
||||
import { useChatStore } from '@/store/chat.js'
|
||||
import { getRecommend } from '@/api/user.js'
|
||||
import { checkUnlock, unlock } from '@/api/interact.js'
|
||||
import { getFullImageUrl } from '@/utils/image.js'
|
||||
|
|
@ -1017,6 +1018,7 @@ export default {
|
|||
onShow() {
|
||||
const configStore = useConfigStore()
|
||||
const userStore = useUserStore()
|
||||
const chatStore = useChatStore()
|
||||
|
||||
// 从服务器刷新用户信息(带节流,防止频繁请求)
|
||||
if (userStore.isLoggedIn) {
|
||||
|
|
@ -1027,6 +1029,9 @@ export default {
|
|||
if (now - lastRefresh > REFRESH_INTERVAL) {
|
||||
userStore.refreshFromServer()
|
||||
}
|
||||
|
||||
// 刷新未读消息数并更新底部导航栏badge
|
||||
chatStore.fetchAllUnreadCounts()
|
||||
}
|
||||
|
||||
// 检查各类弹窗显示
|
||||
|
|
|
|||
|
|
@ -125,65 +125,48 @@ export default {
|
|||
}
|
||||
|
||||
/**
|
||||
* 会员等级配置
|
||||
* 会员等级配置(从后端加载)
|
||||
*/
|
||||
const memberTiers = ref([
|
||||
{
|
||||
level: 1,
|
||||
name: '永久会员',
|
||||
price: 1299,
|
||||
originalPrice: 1899,
|
||||
discount: '8折优惠',
|
||||
badge: ''
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
name: '诚意会员',
|
||||
price: 1999,
|
||||
originalPrice: 2899,
|
||||
discount: '7折优惠',
|
||||
badge: ''
|
||||
},
|
||||
{
|
||||
level: 3,
|
||||
name: '诚意会员',
|
||||
price: 2999,
|
||||
originalPrice: 4299,
|
||||
discount: '7折优惠',
|
||||
badge: '家庭版'
|
||||
}
|
||||
])
|
||||
const memberTiers = ref([])
|
||||
|
||||
// 计算属性
|
||||
const selectedTierInfo = computed(() => {
|
||||
return memberTiers.value.find(t => t.level === selectedTier.value)
|
||||
})
|
||||
|
||||
// 加载会员配置(包括权益长图)
|
||||
// 更新tiers数据
|
||||
const updateTiers = (tiers, configDefaultLevel) => {
|
||||
memberTiers.value = tiers.map(tier => ({
|
||||
level: tier.level,
|
||||
name: tier.name,
|
||||
badge: tier.badge || '',
|
||||
price: tier.price,
|
||||
originalPrice: tier.originalPrice,
|
||||
discount: tier.discount || '',
|
||||
benefitsImage: tier.benefitsImage ? getFullImageUrl(tier.benefitsImage) : ''
|
||||
}))
|
||||
// 优先级:页面参数 > 后端配置的默认等级 > 第一个
|
||||
if (defaultLevel.value && memberTiers.value.some(t => t.level === defaultLevel.value)) {
|
||||
selectedTier.value = defaultLevel.value
|
||||
} else if (configDefaultLevel && memberTiers.value.some(t => t.level === configDefaultLevel)) {
|
||||
selectedTier.value = configDefaultLevel
|
||||
} else if (memberTiers.value.length > 0) {
|
||||
selectedTier.value = memberTiers.value[0].level
|
||||
}
|
||||
}
|
||||
|
||||
// 加载会员配置
|
||||
const loadMemberConfig = async () => {
|
||||
try {
|
||||
const res = await getMemberInfo()
|
||||
if (res && (res.success || res.code === 0) && res.data) {
|
||||
// 更新用户会员状态
|
||||
userStore.setMemberStatus(res.data.isMember, res.data.memberLevel)
|
||||
|
||||
// 如果后台返回了会员等级配置,更新本地配置
|
||||
// 已登录时更新用户会员状态
|
||||
if (userStore.isLoggedIn) {
|
||||
userStore.setMemberStatus(res.data.isMember, res.data.memberLevel)
|
||||
}
|
||||
// 更新会员等级配置
|
||||
if (res.data.tiers && res.data.tiers.length > 0) {
|
||||
memberTiers.value = res.data.tiers.map(tier => ({
|
||||
level: tier.level,
|
||||
name: tier.name,
|
||||
badge: tier.badge || '',
|
||||
price: tier.price,
|
||||
originalPrice: tier.originalPrice,
|
||||
discount: tier.discount || '',
|
||||
benefitsImage: tier.benefitsImage ? getFullImageUrl(tier.benefitsImage) : ''
|
||||
}))
|
||||
// 如果有页面参数指定的等级,使用该等级;否则默认选中第一个
|
||||
if (defaultLevel.value && memberTiers.value.some(t => t.level === defaultLevel.value)) {
|
||||
selectedTier.value = defaultLevel.value
|
||||
} else if (memberTiers.value.length > 0) {
|
||||
selectedTier.value = memberTiers.value[0].level
|
||||
}
|
||||
updateTiers(res.data.tiers, res.data.defaultTierLevel)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -235,6 +218,12 @@ export default {
|
|||
const handlePurchase = async () => {
|
||||
if (!selectedTier.value || purchasing.value) return
|
||||
|
||||
// 未登录跳转登录页
|
||||
if (!userStore.isLoggedIn) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
return
|
||||
}
|
||||
|
||||
purchasing.value = true
|
||||
console.log('开始购买会员...')
|
||||
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@
|
|||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/store/user.js'
|
||||
import { useConfigStore } from '@/store/config.js'
|
||||
import { useChatStore } from '@/store/chat.js'
|
||||
import { getInteractCounts, markInteractAsRead } from '@/api/interact.js'
|
||||
import { bindFamilyByXiangQinNo, getFamilyMembers, unbindFamilyMember, getMemberInfo } from '@/api/member.js'
|
||||
import { getFullImageUrl } from '@/utils/image.js'
|
||||
|
|
@ -541,6 +542,7 @@ export default {
|
|||
},
|
||||
async onShow() {
|
||||
const userStore = useUserStore()
|
||||
const chatStore = useChatStore()
|
||||
userStore.restoreFromStorage()
|
||||
// 重置头像加载错误状态,以便重新尝试加载
|
||||
if (this.avatarLoadError !== undefined) {
|
||||
|
|
@ -552,6 +554,8 @@ export default {
|
|||
if (this.loadInteractCounts) {
|
||||
this.loadInteractCounts()
|
||||
}
|
||||
// 刷新未读消息数并更新底部导航栏badge
|
||||
chatStore.fetchAllUnreadCounts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ export const useConfigStore = defineStore('config', {
|
|||
memberIcons: {
|
||||
unlimitedMemberIcon: '', // 不限时会员图标
|
||||
sincereMemberIcon: '', // 诚意会员图标
|
||||
familyMemberIcon: '' // 家庭版会员图标
|
||||
familyMemberIcon: '', // 家庭版会员图标
|
||||
timeLimitedMemberIcon: '' // 限时会员图标
|
||||
},
|
||||
|
||||
// 弹窗配置
|
||||
|
|
@ -99,6 +100,8 @@ export const useConfigStore = defineStore('config', {
|
|||
return state.memberIcons.sincereMemberIcon || ''
|
||||
case 3:
|
||||
return state.memberIcons.familyMemberIcon || ''
|
||||
case 4:
|
||||
return state.memberIcons.timeLimitedMemberIcon || ''
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
|
|
@ -135,7 +138,8 @@ export const useConfigStore = defineStore('config', {
|
|||
this.memberIcons = {
|
||||
unlimitedMemberIcon: config.memberIcons.unlimitedMemberIcon || '',
|
||||
sincereMemberIcon: config.memberIcons.sincereMemberIcon || '',
|
||||
familyMemberIcon: config.memberIcons.familyMemberIcon || ''
|
||||
familyMemberIcon: config.memberIcons.familyMemberIcon || '',
|
||||
timeLimitedMemberIcon: config.memberIcons.timeLimitedMemberIcon || ''
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using XiangYi.Application.DTOs.Responses;
|
||||
using XiangYi.Application.Interfaces;
|
||||
using XiangYi.Application.Services;
|
||||
using XiangYi.Core.Entities.Biz;
|
||||
using XiangYi.Core.Interfaces;
|
||||
|
||||
|
|
@ -15,13 +17,16 @@ namespace XiangYi.AdminApi.Controllers;
|
|||
public class AdminMemberTierController : ControllerBase
|
||||
{
|
||||
private readonly IRepository<MemberTierConfig> _tierRepository;
|
||||
private readonly ISystemConfigService _systemConfigService;
|
||||
private readonly ILogger<AdminMemberTierController> _logger;
|
||||
|
||||
public AdminMemberTierController(
|
||||
IRepository<MemberTierConfig> tierRepository,
|
||||
ISystemConfigService systemConfigService,
|
||||
ILogger<AdminMemberTierController> logger)
|
||||
{
|
||||
_tierRepository = tierRepository;
|
||||
_systemConfigService = systemConfigService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -189,6 +194,31 @@ public class AdminMemberTierController : ControllerBase
|
|||
|
||||
return ApiResponse.Success("删除成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认选中会员等级
|
||||
/// </summary>
|
||||
[HttpGet("defaultLevel")]
|
||||
public async Task<ApiResponse<int?>> GetDefaultLevel()
|
||||
{
|
||||
var value = await _systemConfigService.GetConfigValueAsync(SystemConfigService.DefaultMemberTierLevelKey);
|
||||
int? level = int.TryParse(value, out var l) ? l : null;
|
||||
return ApiResponse<int?>.Success(level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置默认选中会员等级
|
||||
/// </summary>
|
||||
[HttpPut("defaultLevel")]
|
||||
public async Task<ApiResponse> SetDefaultLevel([FromBody] SetDefaultLevelRequest request)
|
||||
{
|
||||
await _systemConfigService.SetConfigValueAsync(
|
||||
SystemConfigService.DefaultMemberTierLevelKey,
|
||||
request.Level.ToString(),
|
||||
"默认选中会员等级");
|
||||
_logger.LogInformation("设置默认选中会员等级: Level={Level}", request.Level);
|
||||
return ApiResponse.Success("设置成功");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -244,3 +274,11 @@ public class UpdateMemberTierRequest
|
|||
public int Status { get; set; }
|
||||
public int DurationMonths { get; set; } = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置默认选中等级请求
|
||||
/// </summary>
|
||||
public class SetDefaultLevelRequest
|
||||
{
|
||||
public int Level { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,22 +17,32 @@ namespace XiangYi.AppApi.Controllers;
|
|||
public class MemberController : ControllerBase
|
||||
{
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly ISystemConfigService _systemConfigService;
|
||||
private readonly ILogger<MemberController> _logger;
|
||||
|
||||
public MemberController(IMemberService memberService, ILogger<MemberController> logger)
|
||||
public MemberController(IMemberService memberService, ISystemConfigService systemConfigService, ILogger<MemberController> logger)
|
||||
{
|
||||
_memberService = memberService;
|
||||
_systemConfigService = systemConfigService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取会员信息
|
||||
/// 获取会员信息(未登录时只返回等级配置)
|
||||
/// </summary>
|
||||
/// <returns>会员信息</returns>
|
||||
[HttpGet("info")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ApiResponse<MemberInfoResponse>> GetMemberInfo()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
// 未登录:只返回会员等级配置和默认选中等级
|
||||
var tiers = await _memberService.GetMemberTiersAsync();
|
||||
var defaultTierLevel = await GetDefaultTierLevelAsync();
|
||||
return ApiResponse<MemberInfoResponse>.Success(new MemberInfoResponse { Tiers = tiers, DefaultTierLevel = defaultTierLevel });
|
||||
}
|
||||
var result = await _memberService.GetMemberInfoAsync(userId);
|
||||
return ApiResponse<MemberInfoResponse>.Success(result);
|
||||
}
|
||||
|
|
@ -45,7 +55,7 @@ public class MemberController : ControllerBase
|
|||
[HttpPost("purchase")]
|
||||
public async Task<ApiResponse<MemberPurchaseResponse>> Purchase([FromBody] MemberPurchaseRequest request)
|
||||
{
|
||||
if (request.MemberLevel < 1 || request.MemberLevel > 3)
|
||||
if (request.MemberLevel < 1 || request.MemberLevel > 4)
|
||||
{
|
||||
return ApiResponse<MemberPurchaseResponse>.Error(ErrorCodes.InvalidParameter, "会员等级无效");
|
||||
}
|
||||
|
|
@ -141,4 +151,14 @@ public class MemberController : ControllerBase
|
|||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return long.TryParse(userIdClaim, out var userId) ? userId : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认选中会员等级
|
||||
/// </summary>
|
||||
private async Task<int?> GetDefaultTierLevelAsync()
|
||||
{
|
||||
var value = await _systemConfigService.GetConfigValueAsync(
|
||||
XiangYi.Application.Services.SystemConfigService.DefaultMemberTierLevelKey);
|
||||
return int.TryParse(value, out var level) ? level : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,11 @@ public class AdminUserQueryRequest
|
|||
/// 城市
|
||||
/// </summary>
|
||||
public string? City { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否测试用户
|
||||
/// </summary>
|
||||
public bool? IsTestUser { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -104,6 +104,11 @@ public class AdminUserListDto
|
|||
/// 最后登录时间
|
||||
/// </summary>
|
||||
public DateTime? LastLoginTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否测试用户
|
||||
/// </summary>
|
||||
public bool IsTestUser { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ public class MemberInfoResponse
|
|||
/// 会员等级配置列表
|
||||
/// </summary>
|
||||
public List<MemberTierResponse> Tiers { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 默认选中的会员等级(管理后台配置)
|
||||
/// </summary>
|
||||
public int? DefaultTierLevel { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,12 @@ public interface IMemberService
|
|||
/// <returns>是否成功</returns>
|
||||
Task<bool> UnbindFamilyMemberAsync(long userId, long bindUserId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取会员等级配置列表(无需登录)
|
||||
/// </summary>
|
||||
/// <returns>会员等级配置列表</returns>
|
||||
Task<List<MemberTierResponse>> GetMemberTiersAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 计算家庭版绑定后的数量(用于属性测试)
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -110,6 +110,19 @@ public class AdminUserService : IAdminUserService
|
|||
// Exclude soft deleted users
|
||||
query = query.Where(u => !u.IsDeleted);
|
||||
|
||||
// Filter by test user
|
||||
if (request.IsTestUser.HasValue)
|
||||
{
|
||||
if (request.IsTestUser.Value)
|
||||
{
|
||||
query = query.Where(u => u.OpenId.StartsWith("test_openid_"));
|
||||
}
|
||||
else
|
||||
{
|
||||
query = query.Where(u => !u.OpenId.StartsWith("test_openid_"));
|
||||
}
|
||||
}
|
||||
|
||||
// Get total count
|
||||
var total = query.Count();
|
||||
|
||||
|
|
@ -620,7 +633,8 @@ public class AdminUserService : IAdminUserService
|
|||
AuditStatusText = profile != null ? GetAuditStatusText(profile.AuditStatus) : null,
|
||||
ContactCount = user.ContactCount,
|
||||
CreateTime = user.CreateTime,
|
||||
LastLoginTime = user.LastLoginTime
|
||||
LastLoginTime = user.LastLoginTime,
|
||||
IsTestUser = user.OpenId.StartsWith("test_openid_")
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ public class MemberService : IMemberService
|
|||
BenefitsImage = t.BenefitsImage
|
||||
}).ToList();
|
||||
|
||||
// 获取默认选中等级配置
|
||||
var defaultTierLevelStr = await _systemConfigService.GetConfigValueAsync(SystemConfigService.DefaultMemberTierLevelKey);
|
||||
int? defaultTierLevel = int.TryParse(defaultTierLevelStr, out var dtl) ? dtl : null;
|
||||
|
||||
// 判断是否可以绑定家庭成员
|
||||
// 条件:是家庭版会员 且 不是被别人绑定的用户
|
||||
var canBindFamily = false;
|
||||
|
|
@ -124,7 +128,8 @@ public class MemberService : IMemberService
|
|||
CanBindFamily = canBindFamily,
|
||||
FamilyBindCount = familyBindCount,
|
||||
MaxFamilyBindCount = IMemberService.MaxFamilyBindCount,
|
||||
Tiers = tiers
|
||||
Tiers = tiers,
|
||||
DefaultTierLevel = defaultTierLevel
|
||||
};
|
||||
|
||||
// 获取会员记录详情
|
||||
|
|
@ -448,6 +453,22 @@ public class MemberService : IMemberService
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<MemberTierResponse>> GetMemberTiersAsync()
|
||||
{
|
||||
var tierConfigs = await _tierConfigRepository.GetListAsync(t => t.Status == 1);
|
||||
return tierConfigs.OrderBy(t => t.Sort).Select(t => new MemberTierResponse
|
||||
{
|
||||
Level = t.Level,
|
||||
Name = t.Name,
|
||||
Badge = t.Badge,
|
||||
Price = t.Price,
|
||||
OriginalPrice = t.OriginalPrice,
|
||||
Discount = t.Discount,
|
||||
BenefitsImage = t.BenefitsImage
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
#region 静态辅助方法(用于属性测试)
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -63,6 +63,16 @@ public class SystemConfigService : ISystemConfigService
|
|||
/// </summary>
|
||||
public const string FamilyMemberIconKey = "family_member_icon";
|
||||
|
||||
/// <summary>
|
||||
/// 限时会员图标配置键
|
||||
/// </summary>
|
||||
public const string TimeLimitedMemberIconKey = "time_limited_member_icon";
|
||||
|
||||
/// <summary>
|
||||
/// 默认选中会员等级配置键
|
||||
/// </summary>
|
||||
public const string DefaultMemberTierLevelKey = "default_member_tier_level";
|
||||
|
||||
/// <summary>
|
||||
/// 会员入口图配置键
|
||||
/// </summary>
|
||||
|
|
@ -231,12 +241,14 @@ public class SystemConfigService : ISystemConfigService
|
|||
var unlimited = await GetConfigValueAsync(UnlimitedMemberIconKey);
|
||||
var sincere = await GetConfigValueAsync(SincereMemberIconKey);
|
||||
var family = await GetConfigValueAsync(FamilyMemberIconKey);
|
||||
var timeLimited = await GetConfigValueAsync(TimeLimitedMemberIconKey);
|
||||
|
||||
return new MemberIconsDto
|
||||
{
|
||||
UnlimitedMemberIcon = unlimited,
|
||||
SincereMemberIcon = sincere,
|
||||
FamilyMemberIcon = family
|
||||
FamilyMemberIcon = family,
|
||||
TimeLimitedMemberIcon = timeLimited
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -257,6 +269,10 @@ public class SystemConfigService : ISystemConfigService
|
|||
{
|
||||
await SetConfigValueAsync(FamilyMemberIconKey, icons.FamilyMemberIcon, "家庭版会员图标URL");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(icons.TimeLimitedMemberIcon))
|
||||
{
|
||||
await SetConfigValueAsync(TimeLimitedMemberIconKey, icons.TimeLimitedMemberIcon, "限时会员图标URL");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -315,4 +331,9 @@ public class MemberIconsDto
|
|||
/// 家庭版会员图标URL
|
||||
/// </summary>
|
||||
public string? FamilyMemberIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 限时会员图标URL
|
||||
/// </summary>
|
||||
public string? TimeLimitedMemberIcon { get; set; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user