diff --git a/.omo/run-continuation/ses_15492f54bffepl01q9FkaRyN28.json b/.omo/run-continuation/ses_15492f54bffepl01q9FkaRyN28.json new file mode 100644 index 0000000..e576056 --- /dev/null +++ b/.omo/run-continuation/ses_15492f54bffepl01q9FkaRyN28.json @@ -0,0 +1,10 @@ +{ + "sessionID": "ses_15492f54bffepl01q9FkaRyN28", + "updatedAt": "2026-06-16T01:20:56.855Z", + "sources": { + "background-task": { + "state": "idle", + "updatedAt": "2026-06-16T01:20:56.855Z" + } + } +} \ No newline at end of file diff --git a/backend/src/modules/admin/admin.controller.ts b/backend/src/modules/admin/admin.controller.ts index 031baac..242dde3 100644 --- a/backend/src/modules/admin/admin.controller.ts +++ b/backend/src/modules/admin/admin.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post, Body, Query, HttpException, HttpStatus, UseGuards } from '@nestjs/common' +import { Controller, Get, Post, Body, Query, Param, HttpException, HttpStatus, UseGuards } from '@nestjs/common' import { InjectModel } from '@nestjs/mongoose' import { Model } from 'mongoose' import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard' @@ -226,7 +226,7 @@ export class AdminController { } @Get('user/:id') - async getUserDetail(@Query('id') id: string) { + async getUserDetail(@Param('id') id: string) { const user = await this.userModel.findById(id).select('-password -openid').lean().exec() if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND) const [interviews, resumes] = await Promise.all([ diff --git a/backend/src/modules/contribution/contribution.controller.ts b/backend/src/modules/contribution/contribution.controller.ts index bd766ca..bbba775 100644 --- a/backend/src/modules/contribution/contribution.controller.ts +++ b/backend/src/modules/contribution/contribution.controller.ts @@ -2,6 +2,7 @@ import { Controller, Post, Get, Body, Param, UseGuards, Logger } from '@nestjs/c import { InjectModel } from '@nestjs/mongoose' import { Model } from 'mongoose' import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard' +import { Public } from '../../common/decorators/public.decorator' import { CurrentUser } from '../../common/decorators/current-user.decorator' import { AiService } from '../ai/ai.service' import { Contribution, ContributionDocument } from '../schemas/contribution.schema' @@ -186,4 +187,19 @@ export class ContributionController { .select('company position rounds experience createdAt') .exec() } + + @Public() + @Get('companies/hot') + async getHotCompanies() { + const banks = await this.companyBankModel.aggregate([ + { $group: { _id: '$company', positionCount: { $sum: 1 }, totalContributions: { $sum: '$contributionCount' } } }, + { $sort: { totalContributions: -1, positionCount: -1 } }, + { $project: { _id: 0, name: '$_id', positionCount: 1 } }, + ]).exec() + + if (banks.length > 0) return banks + + const DEFAULT_COMPANIES = ['腾讯', '字节跳动', '阿里巴巴', '美团', '百度', '京东', '网易', '小红书'] + return DEFAULT_COMPANIES.map((name, i) => ({ name, positionCount: 0, sort: i })) + } } \ No newline at end of file diff --git a/backend/src/modules/schemas/pricing.service.ts b/backend/src/modules/schemas/pricing.service.ts index b12ef5c..2eb17aa 100644 --- a/backend/src/modules/schemas/pricing.service.ts +++ b/backend/src/modules/schemas/pricing.service.ts @@ -18,8 +18,8 @@ const DEFAULT_PRICING: PricingConfig = { resumeOptimize: { freeLimit: 3, pricePerOptimize: 300, creditsPerPurchase: 1 }, resumeDownload: { pricePerDownload: 200, creditsPerPurchase: 1 }, plans: { - growth: { price: 1990, durationDays: 30, credits: { interview: 999, resumeOptimize: 20, resumeDownload: 10 }, features: [] }, - sprint: { price: 4990, durationDays: 30, credits: { interview: 999, resumeOptimize: 50, resumeDownload: 30 }, features: [] }, + growth: { price: 1990, durationDays: 30, credits: { interview: 999, resumeOptimize: 20, resumeDownload: 10 }, features: ['免费版全部权益', 'AI 数字人面试无限次', '详细面试报告(四维评分)', '进步轨迹雷达图 + 打卡', '每日一题推送', '参考回答思路', '公司真题库', '简历优化 20 次/月', '简历下载 10 次/月'] }, + sprint: { price: 4990, durationDays: 30, credits: { interview: 999, resumeOptimize: 50, resumeDownload: 30 }, features: ['成长版全部权益', 'AI 语音分析(语气词/语速检测)', '技能缺口分析报告', '学习路径推荐', '真人导师 1v1 点评(每月 1 次)', '简历精修(每月 1 次)', '内推优先', '简历优化 50 次/月', '简历下载 30 次/月'] }, }, } diff --git a/backend/src/modules/user/quota.service.ts b/backend/src/modules/user/quota.service.ts index 82bbc99..fb42abd 100644 --- a/backend/src/modules/user/quota.service.ts +++ b/backend/src/modules/user/quota.service.ts @@ -47,15 +47,6 @@ export class QuotaService { if (!user) throw new HttpException('用户不存在', HttpStatus.NOT_FOUND) if (user.plan !== 'free') return - // Backward compat: migrate remaining → freeOptimizeUsed - if ((user.freeOptimizeUsed ?? 0) <= 0 && (user.remaining ?? 0) > 0 && (user.resumeOptimizeCredits ?? 0) <= 0) { - const migrateCount = Math.min(user.remaining, FREE_OPTIMIZE_LIMIT) - await this.userModel.findByIdAndUpdate(userId, { - $set: { freeOptimizeUsed: migrateCount, remaining: Math.max(0, user.remaining - migrateCount) }, - }).exec() - this.logger.log(`Migrated remaining=${user.remaining} → freeOptimizeUsed=${migrateCount} for user ${userId}`) - } - // Try paid credits first const paid = await this.userModel.findOneAndUpdate( { _id: userId, resumeOptimizeCredits: { $gt: 0 } }, @@ -63,6 +54,13 @@ export class QuotaService { ).exec() if (paid) return + // Try old remaining credits (backward compat) + const oldRemaining = await this.userModel.findOneAndUpdate( + { _id: userId, remaining: { $gt: 0 } }, + { $inc: { remaining: -1 } }, + ).exec() + if (oldRemaining) return + // Then free limit const freeResult = await this.userModel.findOneAndUpdate( { _id: userId, freeOptimizeUsed: { $lt: FREE_OPTIMIZE_LIMIT } }, diff --git a/zhiyin-app/src/pages/admin/admin.vue b/zhiyin-app/src/pages/admin/admin.vue index 464abd8..3a0b128 100644 --- a/zhiyin-app/src/pages/admin/admin.vue +++ b/zhiyin-app/src/pages/admin/admin.vue @@ -64,14 +64,14 @@ {{ u.nickname || '--' }} - {{ u.plan === 'growth' || u.plan === 'vip' ? '会员' : '免费' }} + {{ u.plan === 'growth' || u.plan === 'sprint' ? '会员' : '免费' }} 面试:{{ u.interviewCredits ?? 0 }} 优化:{{ u.resumeOptimizeCredits ?? 0 }} 下载:{{ u.resumeDownloadCredits ?? 0 }} 分享:{{ u.shareCredits ?? 0 }} - 设为会员 + 设为会员 调整额度 @@ -339,7 +339,7 @@ const resumeLoading = ref(false) const adminKeyword = ref('') const adminList = ref([]) const searchResult = ref(null) -const memberConfig = ref({ interview: { maxRoundsFree: 5, maxRoundsVip: 10, dailyFreeLimit: 3 }, diagnosis: { dailyFreeLimit: 2 }, optimize: { dailyFreeLimit: 2 }, price: { monthly: 2900 } }) +const memberConfig = ref({ interview: { maxRoundsFree: 5, maxRoundsVip: 10, dailyFreeLimit: 3 }, diagnosis: { dailyFreeLimit: 2 }, optimize: { dailyFreeLimit: 2 }, price: { monthly: 1990 } }) const cfgLoading = ref(false) const pricing = ref({ interview: { pricePerSession: 500 }, @@ -360,7 +360,7 @@ const growthPriceDisplay = computed(() => growthPriceTemp.value.toFixed(1)) const sprintPriceDisplay = computed(() => sprintPriceTemp.value.toFixed(1)) const calcInterviewPrice = () => { - // Convert to 分 on save + // Handled in savePricing via growthPriceTemp / sprintPriceTemp } const orders = ref([]) const ordersTotal = ref(0) @@ -399,7 +399,7 @@ const doVerify = async () => { try { const res = await apiAdmin('/check') if (res.statusCode === 200 && res.data?.isAdmin) { - adminName.value = '管理员' + adminName.value = res.data.nickname || res.data.username || '管理员' verified.value = true loadOverview() } else throw new Error('无管理员权限') @@ -500,15 +500,6 @@ const savePricing = async () => { finally { pricingLoading.value = false } } -const loadConfig = async () => { - cfgLoading.value = true - try { - const res = await apiAdmin('/config') - if (res.statusCode === 200) memberConfig.value = res.data - } catch(e) { console.error(e) } - finally { cfgLoading.value = false } -} - const loadOrders = async () => { orderLoading.value = true ordersPage.value = 1 diff --git a/zhiyin-app/src/pages/company-bank/bank.vue b/zhiyin-app/src/pages/company-bank/bank.vue index 85f5bcd..96255a8 100644 --- a/zhiyin-app/src/pages/company-bank/bank.vue +++ b/zhiyin-app/src/pages/company-bank/bank.vue @@ -17,7 +17,7 @@ @tap="selectCompany(c.name)" > {{ c.name }} - {{ c.positions }} 个岗位 + {{ c.positionCount > 0 ? c.positionCount + ' 个岗位' : '暂无题库' }} @@ -84,23 +84,12 @@