Files
MBTI_wang/miniprogram/pages/match-job/index.js

265 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// pages/match-job/index.js - 匹配工作(简历 + 性格 + AI 综合分析)
const app = getApp()
const { request } = require('../../utils/request')
const DEFAULT_MBTI_JOB = {
ISTJ: '适合财务、审计、行政、项目经理等结构化岗位。',
ISFJ: '护理、教师、客户服务、HR 等以关怀为主的岗位。',
INFJ: '心理咨询、人力资源、作家、战略策划等职业。',
INTJ: '战略顾问、架构师、产品总监、科研等。',
ISTP: '工程师、技术员、运维、维修、运动相关。',
ISFP: '设计师、艺术家、摄影师、UX 等。',
INFP: '作家、心理咨询、社工、内容策划等。',
INTP: '程序员、研究员、数据分析师、系统架构师。',
ESTP: '销售、创业、运动员、媒体、现场管理。',
ESFP: '演员、销售、主持、公关、活动策划。',
ENFP: '市场营销、新媒体、咨询、品牌。',
ENTP: '律师、企业家、创投、产品、战略咨询。',
ESTJ: '管理者、项目经理、运营总监、律师。',
ESFJ: '教师、护士、HR、运营管理、社区。',
ENFJ: '培训师、HR、教师、销售总监、咨询。',
ENTJ: 'CEO、创业者、咨询合伙人、业务负责人。'
}
const DEFAULT_PDP_JOB = {
Tiger: '老虎型适合担任决策者/领导者岗位CEO、业务负责人、销售总监。',
Peacock: '孔雀型适合表达/影响岗位:市场、公关、培训、销售、主播。',
Koala: '无尾熊型适合支持/稳定岗位HR、客户服务、行政、项目协作。',
Owl: '猫头鹰型适合分析/精准岗位:数据分析、研发、审计、质量管理。',
Chameleon: '变色龙型适合协调/多面手岗位:顾问、项目经理、咨询、创业。'
}
const DEFAULT_DISC_JOB = {
D: 'D 型适合业务开拓/领导类岗位:销售、创业、管理。',
I: 'I 型适合表达影响类岗位:市场、公关、培训、社群。',
S: 'S 型适合服务支持类岗位HR、客服、运营、项目协作。',
C: 'C 型适合分析严谨类岗位:研发、数据、财务、质量控制。'
}
Page({
data: {
mbtiType: '',
pdpType: '',
discType: '',
sbtiType: '',
faceType: '',
mbtiJobHint: '',
pdpJobHint: '',
discJobHint: '',
hasAnyTest: false,
resumeUrl: '',
resumeFileName: '',
uploading: false,
analyzing: false,
analyzed: false,
analyzeResult: null
},
onLoad() {
try { require('../../utils/analytics').track('page_view', { path: 'pages/match-job/index' }) } catch (e) {}
app.ensureLogin && app.ensureLogin().then(() => {
this.loadPersonalityTags()
this.loadDefaultResume()
})
},
onShow() {
this.loadPersonalityTags()
},
loadPersonalityTags() {
request({
url: '/api/test/recent',
method: 'GET',
needAuth: true,
success: (res) => {
const d = (res && res.data && res.data.code === 200 && res.data.data) || {}
const mbti = this._pickType(d.mbti, 'mbti')
const pdp = this._pickType(d.pdp, 'pdp')
const disc = this._pickType(d.disc, 'disc')
const sbti = this._pickType(d.sbti, 'sbti')
const face = this._pickType(d.face, 'face')
const hasAnyTest = !!(mbti || pdp || disc || sbti || face)
this.setData({
mbtiType: mbti,
pdpType: pdp,
discType: disc,
sbtiType: sbti,
faceType: face,
hasAnyTest,
mbtiJobHint: mbti ? (DEFAULT_MBTI_JOB[mbti] || '结合所属类别的典型岗位方向。') : '',
pdpJobHint: pdp ? (DEFAULT_PDP_JOB[pdp] || '') : '',
discJobHint: disc ? (DEFAULT_DISC_JOB[(disc || '').charAt(0).toUpperCase()] || '') : ''
})
},
fail: () => {}
})
},
_pickType(rec, type) {
if (!rec) return ''
try {
const rd = typeof rec.resultData === 'string' ? JSON.parse(rec.resultData) : (rec.resultData || {})
if (type === 'mbti') return rd.mbtiType || rd.mbti || ''
if (type === 'pdp') return (rd.description && rd.description.type) || rd.dominantType || ''
if (type === 'disc') return rd.dominantType || rd.disc || ''
if (type === 'sbti') return rd.sbtiType || (rd.finalType && rd.finalType.code) || ''
if (type === 'face') return rd.mbti || rd.faceType || ''
} catch (e) {}
return ''
},
loadDefaultResume() {
// 取默认简历(若有)
request({
url: '/api/enterprise/resume-uploads?pageSize=1',
method: 'GET',
needAuth: true,
success: (res) => {
const list = (res && res.data && res.data.code === 200 && res.data.data && res.data.data.list) || []
const def = list.find((r) => r.isDefault) || list[0]
if (def) {
this.setData({ resumeUrl: def.url || '', resumeFileName: def.fileName || '' })
}
},
fail: () => {}
})
},
chooseResume() {
try { require('../../utils/analytics').track('tap_match_upload_resume', {}) } catch (e) {}
wx.chooseMessageFile({
count: 1,
type: 'file',
extension: ['jpg', 'jpeg', 'png', 'webp', 'pdf', 'doc', 'docx'],
success: (fileRes) => {
const files = fileRes.tempFiles || []
if (!files.length) return
const file = files[0]
const filePath = file.path || file.tempFilePath
const fileName = file.name || '简历文件'
this._doUpload(filePath, fileName)
}
})
},
_doUpload(filePath, fileName) {
this.setData({ uploading: true })
const apiBase = (app.globalData && app.globalData.apiBase) ? app.globalData.apiBase.replace(/\/$/, '') : ''
const token = (app.globalData && app.globalData.token) || wx.getStorageSync('token') || ''
wx.showLoading({ title: '上传中...', mask: true })
wx.uploadFile({
url: apiBase + '/api/upload/file',
filePath,
name: 'file',
header: token ? { Authorization: 'Bearer ' + token } : {},
success: (res) => {
wx.hideLoading()
try {
const data = JSON.parse(res.data)
if (data.code === 200 && data.data && data.data.url) {
const url = data.data.url
// 记录一条简历上传(方便历史页看)
request({
url: '/api/enterprise/resume-uploads',
method: 'POST',
needAuth: true,
data: { url, fileName },
success: () => {},
fail: () => {}
})
this.setData({ resumeUrl: url, resumeFileName: fileName })
wx.showToast({ title: '上传成功', icon: 'success' })
} else {
wx.showToast({ title: data.message || '上传失败', icon: 'none' })
}
} catch (e) {
wx.showToast({ title: '解析上传结果失败', icon: 'none' })
}
},
fail: () => {
wx.hideLoading()
wx.showToast({ title: '上传失败', icon: 'none' })
},
complete: () => this.setData({ uploading: false })
})
},
previewResume() {
const url = this.data.resumeUrl
if (!url) return
const apiBase = (app.globalData && app.globalData.apiBase) ? app.globalData.apiBase.replace(/\/$/, '') : ''
const full = url.startsWith('http') ? url : apiBase + url
wx.downloadFile({
url: full,
success: (res) => {
if (res.statusCode === 200 && res.tempFilePath) {
wx.openDocument({ filePath: res.tempFilePath, showMenu: true })
} else {
wx.showToast({ title: '打开失败', icon: 'none' })
}
},
fail: () => wx.showToast({ title: '打开失败', icon: 'none' })
})
},
runAnalyze() {
if (!this.data.hasAnyTest) {
wx.showToast({ title: '请先完成至少一项性格测评', icon: 'none' })
return
}
if (!this.data.resumeUrl) {
wx.showToast({ title: '请先上传简历', icon: 'none' })
return
}
this.setData({ analyzing: true })
try { require('../../utils/analytics').track('tap_match_analyze', { mbti: this.data.mbtiType, pdp: this.data.pdpType }) } catch (e) {}
wx.showLoading({ title: 'AI 分析中...', mask: true })
request({
url: '/api/resume/analyze',
method: 'POST',
needAuth: true,
data: { resumeUrl: this.data.resumeUrl },
success: (res) => {
wx.hideLoading()
if (res.statusCode === 200 && res.data && res.data.code === 200) {
const d = res.data.data || {}
this.setData({
analyzeResult: this._normalizeAnalyzeResult(d),
analyzed: true
})
} else {
wx.showToast({ title: (res.data && res.data.message) || '分析失败', icon: 'none' })
}
},
fail: () => {
wx.hideLoading()
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
},
complete: () => this.setData({ analyzing: false })
})
},
_normalizeAnalyzeResult(d) {
const fit = Array.isArray(d.fitRoles)
? d.fitRoles
: (d.portrait && Array.isArray(d.portrait.bestFit) ? d.portrait.bestFit : [])
const strengths = Array.isArray(d.strengths)
? d.strengths
: (d.portrait && Array.isArray(d.portrait.coreStrengths) ? d.portrait.coreStrengths : [])
const weaknesses = Array.isArray(d.weaknesses)
? d.weaknesses
: (d.portrait && Array.isArray(d.portrait.coreRisks) ? d.portrait.coreRisks : [])
return {
score: typeof d.score === 'number' ? d.score : (d.matchScore || null),
fitRoles: fit.slice(0, 5),
summary: d.summary || (d.portrait && d.portrait.workStyle) || '',
strengths: strengths.slice(0, 8),
weaknesses: weaknesses.slice(0, 6),
advice: d.advice || d.nextStep || ''
}
},
goToTestSelect() {
wx.navigateTo({ url: '/pages/test-select/index' })
}
})