|
|
@@ -4,6 +4,8 @@ import { Team } from '../entities/team.entity'
|
|
|
import { IncomeRecords, IncomeType } from '../entities/income-records.entity'
|
|
|
import { Member } from '../entities/member.entity'
|
|
|
import { User } from '../entities/user.entity'
|
|
|
+import { TeamMembers } from '../entities/team-members.entity'
|
|
|
+import { TeamDomain } from '../entities/team-domain.entity'
|
|
|
import { PaginationResponse } from '../dto/common.dto'
|
|
|
import { CreateTeamBody, UpdateTeamBody, ListTeamQuery } from '../dto/team.dto'
|
|
|
import { UserService } from './user.service'
|
|
|
@@ -16,6 +18,8 @@ export class TeamService {
|
|
|
private incomeRecordsRepository: Repository<IncomeRecords>
|
|
|
private memberRepository: Repository<Member>
|
|
|
private userRepository: Repository<User>
|
|
|
+ private teamMembersRepository: Repository<TeamMembers>
|
|
|
+ private teamDomainRepository: Repository<TeamDomain>
|
|
|
private userService: UserService
|
|
|
private sysConfigService: SysConfigService
|
|
|
|
|
|
@@ -24,6 +28,8 @@ export class TeamService {
|
|
|
this.incomeRecordsRepository = app.dataSource.getRepository(IncomeRecords)
|
|
|
this.memberRepository = app.dataSource.getRepository(Member)
|
|
|
this.userRepository = app.dataSource.getRepository(User)
|
|
|
+ this.teamMembersRepository = app.dataSource.getRepository(TeamMembers)
|
|
|
+ this.teamDomainRepository = app.dataSource.getRepository(TeamDomain)
|
|
|
this.userService = new UserService(app)
|
|
|
this.sysConfigService = new SysConfigService(app)
|
|
|
}
|
|
|
@@ -108,6 +114,12 @@ export class TeamService {
|
|
|
return this.findById(id)
|
|
|
}
|
|
|
|
|
|
+ async updateThemeColor(userId: number, themeColor: string): Promise<Team> {
|
|
|
+ const team = await this.findByUserId(userId)
|
|
|
+ team.themeColor = themeColor
|
|
|
+ return this.teamRepository.save(team)
|
|
|
+ }
|
|
|
+
|
|
|
async delete(id: number): Promise<void> {
|
|
|
await this.teamRepository.delete(id)
|
|
|
}
|
|
|
@@ -454,12 +466,77 @@ export class TeamService {
|
|
|
return this.teamRepository.findOneOrFail({ where: { userId } })
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 根据用户ID和角色获取团队主题颜色
|
|
|
+ * 支持所有用户角色:TEAM、PROMOTER、USER(会员)
|
|
|
+ */
|
|
|
+ async getTeamThemeColorByUser(userId: number, userRole: UserRole): Promise<{ themeColor: string } | null> {
|
|
|
+ try {
|
|
|
+ let teamId: number | null = null
|
|
|
+
|
|
|
+ if (userRole === UserRole.TEAM) {
|
|
|
+ // 团队用户:直接从team表查找
|
|
|
+ const team = await this.teamRepository.findOne({ where: { userId } })
|
|
|
+ if (team) {
|
|
|
+ teamId = team.id
|
|
|
+ }
|
|
|
+ } else if (userRole === UserRole.PROMOTER) {
|
|
|
+ // 推广员:从team_members表查找团队ID
|
|
|
+ const teamMember = await this.teamMembersRepository.findOne({ where: { userId } })
|
|
|
+ if (teamMember && teamMember.teamId) {
|
|
|
+ teamId = teamMember.teamId
|
|
|
+ }
|
|
|
+ } else if (userRole === UserRole.USER) {
|
|
|
+ // 会员:从member表查找,优先使用teamId,其次通过domainId查找
|
|
|
+ const member = await this.memberRepository.findOne({ where: { userId } })
|
|
|
+ if (member) {
|
|
|
+ if (member.teamId && member.teamId > 0) {
|
|
|
+ // 直接有团队ID
|
|
|
+ teamId = member.teamId
|
|
|
+ } else if (member.domainId && member.domainId > 0) {
|
|
|
+ // 通过域名查找团队ID
|
|
|
+ const teamDomain = await this.teamDomainRepository.findOne({ where: { id: member.domainId } })
|
|
|
+ if (teamDomain && teamDomain.teamId) {
|
|
|
+ teamId = teamDomain.teamId
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // ADMIN角色和其他情况返回null
|
|
|
+
|
|
|
+ if (!teamId) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据teamId获取团队主题颜色
|
|
|
+ const team = await this.teamRepository.findOne({
|
|
|
+ where: { id: teamId },
|
|
|
+ select: ['themeColor']
|
|
|
+ })
|
|
|
+
|
|
|
+ if (!team) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ themeColor: team.themeColor || 'dark' // 如果没有设置,返回默认值
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ // 发生错误时返回null
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取团队IP成交率统计
|
|
|
* 今日IP成交率 = 今日付费用户数 / 今日登录用户数
|
|
|
* 总IP成交率 = 总付费用户数 / 总用户数
|
|
|
+ *
|
|
|
+ * 支持团队用户和推广员:
|
|
|
+ * - 团队用户:统计整个团队的数据(通过teamId)
|
|
|
+ * - 推广员:统计绑定域名的数据(通过domainId),如果没有绑定域名则返回0
|
|
|
*/
|
|
|
- async getIpConversionRate(userId: number): Promise<{
|
|
|
+ async getIpConversionRate(userId: number, userRole: UserRole): Promise<{
|
|
|
todayIpConversionRate: number
|
|
|
totalIpConversionRate: number
|
|
|
todayPaidUsers: number
|
|
|
@@ -467,37 +544,94 @@ export class TeamService {
|
|
|
totalPaidUsers: number
|
|
|
totalUsers: number
|
|
|
}> {
|
|
|
- // 获取团队信息
|
|
|
- const team = await this.findByUserId(userId)
|
|
|
- const teamId = team.id
|
|
|
-
|
|
|
// 获取今天的开始时间(使用本地时区),并使用半开区间 [today, tomorrow)
|
|
|
const today = new Date()
|
|
|
today.setHours(0, 0, 0, 0)
|
|
|
const tomorrow = new Date(today)
|
|
|
tomorrow.setDate(tomorrow.getDate() + 1)
|
|
|
|
|
|
- // 统计今日登录用户数(基于会员的lastLoginAt字段)
|
|
|
- const todayLoginUsers = await this.memberRepository.count({
|
|
|
- where: {
|
|
|
- teamId: teamId,
|
|
|
- lastLoginAt: Between(today, tomorrow)
|
|
|
+ let domainIds: number[] = []
|
|
|
+ let teamId: number | null = null
|
|
|
+
|
|
|
+ // 根据用户角色决定统计方式
|
|
|
+ if (userRole === UserRole.TEAM) {
|
|
|
+ // 团队用户:通过teamId统计
|
|
|
+ const team = await this.findByUserId(userId)
|
|
|
+ teamId = team.id
|
|
|
+ } else if (userRole === UserRole.PROMOTER) {
|
|
|
+ // 推广员:通过绑定的域名统计
|
|
|
+ const teamMember = await this.teamMembersRepository.findOne({ where: { userId } })
|
|
|
+ if (!teamMember) {
|
|
|
+ // 如果没有找到团队成员信息,返回0
|
|
|
+ return {
|
|
|
+ todayIpConversionRate: 0,
|
|
|
+ totalIpConversionRate: 0,
|
|
|
+ todayPaidUsers: 0,
|
|
|
+ todayLoginUsers: 0,
|
|
|
+ totalPaidUsers: 0,
|
|
|
+ totalUsers: 0
|
|
|
+ }
|
|
|
}
|
|
|
- })
|
|
|
|
|
|
- // 统计总用户数(基于会员的teamId)
|
|
|
- const totalUsers = await this.memberRepository.count({
|
|
|
- where: {
|
|
|
- teamId: teamId
|
|
|
+ // 查找绑定的域名
|
|
|
+ const teamDomains = await this.teamDomainRepository.find({
|
|
|
+ where: { teamMemberId: teamMember.id },
|
|
|
+ order: { createdAt: 'DESC' }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 如果没有绑定域名,返回0
|
|
|
+ if (teamDomains.length === 0) {
|
|
|
+ return {
|
|
|
+ todayIpConversionRate: 0,
|
|
|
+ totalIpConversionRate: 0,
|
|
|
+ todayPaidUsers: 0,
|
|
|
+ todayLoginUsers: 0,
|
|
|
+ totalPaidUsers: 0,
|
|
|
+ totalUsers: 0
|
|
|
+ }
|
|
|
}
|
|
|
- })
|
|
|
|
|
|
- // 统计今日付费用户数(通过IncomeRecords表,查询今日有付费记录的用户,去重)
|
|
|
- const todayPaidUsersResult = await this.incomeRecordsRepository
|
|
|
+ domainIds = teamDomains.map(d => d.id)
|
|
|
+ } else {
|
|
|
+ throw new Error('不支持的用户角色')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建基础查询条件
|
|
|
+ let todayLoginUsersQuery = this.memberRepository.createQueryBuilder('member')
|
|
|
+ let totalUsersQuery = this.memberRepository.createQueryBuilder('member')
|
|
|
+ let todayPaidUsersQuery = this.incomeRecordsRepository
|
|
|
+ .createQueryBuilder('record')
|
|
|
+ .innerJoin('member', 'm', 'm.userId = record.userId')
|
|
|
+ let totalPaidUsersQuery = this.incomeRecordsRepository
|
|
|
.createQueryBuilder('record')
|
|
|
.innerJoin('member', 'm', 'm.userId = record.userId')
|
|
|
+
|
|
|
+ if (userRole === UserRole.TEAM && teamId !== null) {
|
|
|
+ // 团队用户:通过teamId过滤
|
|
|
+ todayLoginUsersQuery = todayLoginUsersQuery.where('member.teamId = :teamId', { teamId })
|
|
|
+ totalUsersQuery = totalUsersQuery.where('member.teamId = :teamId', { teamId })
|
|
|
+ todayPaidUsersQuery = todayPaidUsersQuery.where('m.teamId = :teamId', { teamId })
|
|
|
+ totalPaidUsersQuery = totalPaidUsersQuery.where('m.teamId = :teamId', { teamId })
|
|
|
+ } else if (userRole === UserRole.PROMOTER && domainIds.length > 0) {
|
|
|
+ // 推广员:通过domainId过滤
|
|
|
+ todayLoginUsersQuery = todayLoginUsersQuery.where('member.domainId IN (:...domainIds)', { domainIds })
|
|
|
+ totalUsersQuery = totalUsersQuery.where('member.domainId IN (:...domainIds)', { domainIds })
|
|
|
+ todayPaidUsersQuery = todayPaidUsersQuery.where('m.domainId IN (:...domainIds)', { domainIds })
|
|
|
+ totalPaidUsersQuery = totalPaidUsersQuery.where('m.domainId IN (:...domainIds)', { domainIds })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统计今日登录用户数(基于会员的lastLoginAt字段)
|
|
|
+ const todayLoginUsers = await todayLoginUsersQuery
|
|
|
+ .andWhere('member.lastLoginAt >= :today', { today })
|
|
|
+ .andWhere('member.lastLoginAt < :tomorrow', { tomorrow })
|
|
|
+ .getCount()
|
|
|
+
|
|
|
+ // 统计总用户数
|
|
|
+ const totalUsers = await totalUsersQuery.getCount()
|
|
|
+
|
|
|
+ // 统计今日付费用户数(通过IncomeRecords表,查询今日有付费记录的用户,去重)
|
|
|
+ const todayPaidUsersResult = await todayPaidUsersQuery
|
|
|
.select('COUNT(DISTINCT record.userId)', 'count')
|
|
|
- .where('m.teamId = :teamId', { teamId })
|
|
|
.andWhere('record.createdAt >= :today', { today })
|
|
|
.andWhere('record.createdAt < :tomorrow', { tomorrow })
|
|
|
.andWhere('record.delFlag = :delFlag', { delFlag: false })
|
|
|
@@ -507,11 +641,8 @@ export class TeamService {
|
|
|
const todayPaidUsers = Number(todayPaidUsersResult?.count) || 0
|
|
|
|
|
|
// 统计总付费用户数(通过IncomeRecords表,查询有付费记录的用户,去重)
|
|
|
- const totalPaidUsersResult = await this.incomeRecordsRepository
|
|
|
- .createQueryBuilder('record')
|
|
|
- .innerJoin('member', 'm', 'm.userId = record.userId')
|
|
|
+ const totalPaidUsersResult = await totalPaidUsersQuery
|
|
|
.select('COUNT(DISTINCT record.userId)', 'count')
|
|
|
- .where('m.teamId = :teamId', { teamId })
|
|
|
.andWhere('record.delFlag = :delFlag', { delFlag: false })
|
|
|
.andWhere('record.status = :status', { status: true })
|
|
|
.getRawOne()
|