feat(admin): enrich admin panel fields; add user index constraint and customer service
- admin controller: add updatedAt to interview/resume selects; add orderCount, todayOrders, totalRevenue to overview - admin.vue: enrich all tabs with more fields - overview: order cards (count, revenue) - users: wxOpenid, email, createdAt, interviewCount, vipExpireAt, role badge - interviews: user email, updatedAt, summary preview - orders: title, type, channel, paidAt, wxTransactionId, refund info - resumes: user email, updatedAt - share: sharer phone, shareCode, isActive, visitorId(IP), creditedAt - admins: email, createdAt - user.schema: add unique indexes on phone/wxOpenid/email; pre-save hook requiring at least one contact method - user/about: add WeChat contact button (open-type=contact) for customer service
This commit is contained in:
@@ -48,7 +48,7 @@ export class AdminController {
|
||||
const [
|
||||
userCount, interviewCount, todayUsers, todayInterviews,
|
||||
resumeCount, paidDownloadCount,
|
||||
planStats,
|
||||
planStats, orderCount, todayOrders, totalRevenue,
|
||||
] = await Promise.all([
|
||||
this.userModel.countDocuments().exec(),
|
||||
this.interviewModel.countDocuments().exec(),
|
||||
@@ -59,12 +59,19 @@ export class AdminController {
|
||||
this.userModel.aggregate([
|
||||
{ $group: { _id: '$plan', count: { $sum: 1 } } },
|
||||
]).exec(),
|
||||
this.orderModel.countDocuments().exec(),
|
||||
this.orderModel.countDocuments({ status: 'success', createdAt: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) } }).exec(),
|
||||
this.orderModel.aggregate([
|
||||
{ $match: { status: 'success' } },
|
||||
{ $group: { _id: null, total: { $sum: '$amount' } } },
|
||||
]).exec(),
|
||||
])
|
||||
const planBreakdown: Record<string, number> = {}
|
||||
planStats.forEach(p => { planBreakdown[p._id || 'free'] = p.count })
|
||||
return {
|
||||
userCount, interviewCount, todayUsers, todayInterviews,
|
||||
resumeCount, paidDownloadCount,
|
||||
resumeCount, paidDownloadCount, orderCount, todayOrders,
|
||||
totalRevenue: totalRevenue[0]?.total || 0,
|
||||
planBreakdown,
|
||||
}
|
||||
}
|
||||
@@ -95,8 +102,8 @@ export class AdminController {
|
||||
this.interviewModel.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip).limit(+limit)
|
||||
.populate('userId', 'phone nickname')
|
||||
.select('position status totalScore questionCount fillerScore fillerDensity summary createdAt')
|
||||
.populate('userId', 'phone nickname email wxOpenid')
|
||||
.select('position status totalScore questionCount fillerScore fillerDensity summary createdAt updatedAt')
|
||||
.lean().exec(),
|
||||
this.interviewModel.countDocuments().exec(),
|
||||
])
|
||||
@@ -217,8 +224,8 @@ export class AdminController {
|
||||
this.resumeModel.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip).limit(+limit)
|
||||
.populate('userId', 'phone nickname')
|
||||
.select('title targetPosition version paidDownload createdAt')
|
||||
.populate('userId', 'phone nickname email')
|
||||
.select('title targetPosition version paidDownload createdAt updatedAt contentHash')
|
||||
.lean().exec(),
|
||||
this.resumeModel.countDocuments().exec(),
|
||||
])
|
||||
|
||||
@@ -5,10 +5,10 @@ export type UserDocument = User & Document
|
||||
|
||||
@Schema({ timestamps: true })
|
||||
export class User {
|
||||
@Prop({ sparse: true })
|
||||
@Prop({ unique: true, sparse: true })
|
||||
phone?: string
|
||||
|
||||
@Prop({ sparse: true })
|
||||
@Prop({ unique: true, sparse: true })
|
||||
wxOpenid?: string
|
||||
|
||||
@Prop({ default: '' })
|
||||
@@ -60,11 +60,18 @@ export class User {
|
||||
@Prop({ default: false })
|
||||
isSystemAdmin: boolean
|
||||
|
||||
@Prop({ sparse: true })
|
||||
@Prop({ unique: true, sparse: true })
|
||||
email?: string
|
||||
|
||||
@Prop({ default: '', select: false })
|
||||
password?: string
|
||||
}
|
||||
|
||||
export const UserSchema = SchemaFactory.createForClass(User)
|
||||
export const UserSchema = SchemaFactory.createForClass(User)
|
||||
|
||||
UserSchema.pre('save', function (next) {
|
||||
if (!this.phone && !this.wxOpenid && !this.email) {
|
||||
return next(new Error('用户必须至少有一个联系方式(手机号/微信/邮箱)'))
|
||||
}
|
||||
next()
|
||||
})
|
||||
Reference in New Issue
Block a user