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 @@