Compare commits

...

10 Commits

Author SHA1 Message Date
张成
b17d08ffa8 1 2025-12-26 14:26:04 +08:00
张成
54644dbb72 1 2025-12-26 14:22:33 +08:00
张成
1d8d2ea6e8 1 2025-12-26 14:01:47 +08:00
张成
3f4acc5e1d 1 2025-12-26 13:49:07 +08:00
张成
69f2f87f4b 1 2025-12-26 13:44:57 +08:00
张成
0cfff98edf 1 2025-12-26 13:39:27 +08:00
张成
77789446f3 1 2025-12-26 13:30:20 +08:00
张成
2530f25b86 1 2025-12-26 13:26:11 +08:00
张成
6efd77d2b5 1 2025-12-26 13:12:53 +08:00
张成
6253abc617 1 2025-12-26 11:38:41 +08:00
12 changed files with 315 additions and 120 deletions

View File

@@ -48,6 +48,15 @@ class ResumeInfoServer {
analyzeWithAI(resumeId) {
return window.framework.http.post('/resume/analyze-with-ai', { resumeId })
}
/**
* 同步在线简历
* @param {String} resumeId - 简历ID
* @returns {Promise}
*/
syncOnline(resumeId) {
return window.framework.http.post('/resume/sync-online', { resumeId })
}
}
export default new ResumeInfoServer()

File diff suppressed because one or more lines are too long

View File

@@ -8,6 +8,7 @@
@back="handleBack"
>
<template #header-right>
<Button type="info" @click="handleSyncOnline" :loading="syncing" style="margin-right: 8px;">同步在线简历</Button>
<Button type="primary" @click="handleAnalyzeAI" :loading="analyzing">AI 分析</Button>
</template>
@@ -272,6 +273,7 @@ export default {
return {
loading: false,
analyzing: false,
syncing: false,
resumeData: null,
skillTags: [],
workExperience: [],
@@ -317,6 +319,32 @@ export default {
}
return field
},
async handleSyncOnline() {
if (!this.resumeData || !this.resumeData.resumeId) {
this.$Message.warning('简历ID不存在')
return
}
if (!this.resumeData.sn_code) {
this.$Message.warning('该简历未绑定设备,无法同步在线简历')
return
}
this.syncing = true
try {
const res = await resumeInfoServer.syncOnline(this.resumeData.resumeId)
this.$Message.success(res.message || '同步在线简历成功')
// 重新加载数据
await this.loadResumeData(this.resumeData.resumeId)
} catch (error) {
console.error('同步在线简历失败:', error)
// 优先从 error.response.data.message 获取,然后是 error.message
const errorMsg = error.response?.data?.message || error.message || '请稍后重试'
this.$Message.error(errorMsg)
} finally {
this.syncing = false
}
},
async handleAnalyzeAI() {
if (!this.resumeData || !this.resumeData.resumeId) {
this.$Message.warning('简历ID不存在')

View File

@@ -358,6 +358,100 @@ return ctx.success({ message: '简历删除成功' });
console.error('AI 分析失败:', error);
return ctx.fail('AI 分析失败: ' + error.message);
}
},
/**
* @swagger
* /admin_api/resume/sync-online:
* post:
* summary: 同步在线简历
* description: 通过MQTT指令获取用户在线简历并更新到数据库
* tags: [后台-简历管理]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - resumeId
* properties:
* resumeId:
* type: string
* description: 简历ID
* responses:
* 200:
* description: 同步成功
*/
'POST /resume/sync-online': async (ctx) => {
const models = Framework.getModels();
const { resume_info } = models;
const { resumeId } = ctx.getBody();
if (!resumeId) {
return ctx.fail('简历ID不能为空');
}
const resume = await resume_info.findOne({ where: { resumeId } });
if (!resume) {
return ctx.fail('简历不存在');
}
const { sn_code, platform } = resume;
if (!sn_code) {
return ctx.fail('该简历未绑定设备SN码');
}
try {
const scheduleManager = require('../middleware/schedule');
const resumeManager = require('../middleware/job/resumeManager');
// 检查 MQTT 客户端是否已初始化
if (!scheduleManager.mqttClient) {
return ctx.fail('MQTT客户端未初始化请检查调度系统是否正常启动');
}
// 检查设备是否在线
// const deviceManager = require('../middleware/schedule/deviceManager');
// if (!deviceManager.isDeviceOnline(sn_code)) {
// return ctx.fail('设备离线,无法同步在线简历');
// }
// 调用简历管理器获取并保存简历
const resumeData = await resumeManager.get_online_resume(sn_code, scheduleManager.mqttClient, {
platform: platform || 'boss'
});
// 重新获取更新后的简历数据
const updatedResume = await resume_info.findOne({ where: { resumeId } });
if (!updatedResume) {
return ctx.fail('同步成功但未找到更新后的简历记录');
}
const resumeDetail = updatedResume.toJSON();
// 解析 JSON 字段
const jsonFields = ['skills', 'certifications', 'projectExperience', 'workExperience', 'aiSkillTags'];
jsonFields.forEach(field => {
if (resumeDetail[field]) {
try {
resumeDetail[field] = JSON.parse(resumeDetail[field]);
} catch (e) {
console.error(`解析字段 ${field} 失败:`, e);
}
}
});
return ctx.success({
message: '同步在线简历成功',
data: resumeDetail
});
} catch (error) {
console.error('同步在线简历失败:', error);
return ctx.fail('同步在线简历失败: ' + (error.message || '未知错误'));
}
}
};

View File

@@ -71,6 +71,20 @@ module.exports = {
where.feedbackStatus = seachOption.feedbackStatus;
}
// 时间范围筛选
console.log(seachOption.startTime, seachOption.endTime);
if (seachOption.startTime || seachOption.endTime) {
where.create_time = {};
if (seachOption.startTime) {
where.create_time[op.gte] = new Date(seachOption.startTime);
}
if (seachOption.endTime) {
const endTime = new Date(seachOption.endTime);
endTime.setHours(23, 59, 59, 999); // 设置为当天的最后一刻
where.create_time[op.lte] = endTime;
}
}
// 搜索:岗位名称、公司名称
if (seachOption.key && seachOption.value) {
const key = seachOption.key;
@@ -93,7 +107,7 @@ module.exports = {
where,
limit,
offset,
order: [['applyTime', 'DESC']]
order: [['create_time', 'DESC']]
});
return ctx.success({
@@ -109,7 +123,7 @@ module.exports = {
* /api/apply/statistics:
* get:
* summary: 获取投递统计
* description: 根据设备SN码获取投递统计数据包含今日、本周、本月统计
* description: 根据设备SN码获取投递统计数据包含今日、本周、本月统计,支持时间范围筛选
* tags: [前端-投递管理]
* parameters:
* - in: query
@@ -118,21 +132,50 @@ module.exports = {
* schema:
* type: string
* description: 设备SN码
* - in: query
* name: startTime
* schema:
* type: string
* format: date-time
* description: 开始时间(可选)
* - in: query
* name: endTime
* schema:
* type: string
* format: date-time
* description: 结束时间(可选)
* responses:
* 200:
* description: 获取成功
*/
'GET /apply/statistics': async (ctx) => {
'POST /apply/statistics': async (ctx) => {
const models = Framework.getModels();
const { apply_records, op } = models;
const { sn_code } = ctx.query;
const { apply_records, op, job_postings } = models;
const { sn_code, startTime, endTime } = ctx.getBody();
console.log(startTime, endTime);
const final_sn_code = sn_code;
if (!final_sn_code) {
return ctx.fail('请提供设备SN码');
}
// 计算时间范围
// 构建基础查询条件
const baseWhere = { sn_code: final_sn_code };
// 如果提供了时间范围,则添加到查询条件中
if (startTime || endTime) {
baseWhere.create_time = {};
if (startTime) {
baseWhere.create_time[op.gte] = new Date(startTime);
}
if (endTime) {
const endTimeDate = new Date(endTime);
endTimeDate.setHours(23, 59, 59, 999); // 设置为当天的最后一刻
baseWhere.create_time[op.lte] = endTimeDate;
}
}
// 计算时间范围(如果未提供时间范围,则使用默认的今日、本周、本月)
const now = new Date();
// 今天的开始时间00:00:00
@@ -150,6 +193,8 @@ module.exports = {
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
monthStart.setHours(0, 0, 0, 0);
const [
totalCount,
successCount,
@@ -158,35 +203,44 @@ module.exports = {
interviewCount,
todayCount,
weekCount,
monthCount
monthCount,
totalJobCount
] = await Promise.all([
// 总计
apply_records.count({ where: { sn_code: final_sn_code } }),
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'success' } }),
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'failed' } }),
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'pending' } }),
apply_records.count({ where: { sn_code: final_sn_code, feedbackStatus: 'interview' } }),
// 今日
apply_records.count({
// 总计(如果提供了时间范围,则只统计该范围内的)
apply_records.count({ where: baseWhere }),
apply_records.count({ where: { ...baseWhere, applyStatus: 'success' } }),
apply_records.count({ where: { ...baseWhere, applyStatus: 'failed' } }),
apply_records.count({ where: { ...baseWhere, applyStatus: 'pending' } }),
apply_records.count({ where: { ...baseWhere, feedbackStatus: 'interview' } }),
// 今日如果提供了时间范围则返回0否则统计今日
startTime || endTime ? 0 : apply_records.count({
where: {
sn_code: final_sn_code,
applyTime: { [op.gte]: todayStart }
create_time: { [op.gte]: todayStart }
}
}),
// 本周
apply_records.count({
// 本周如果提供了时间范围则返回0否则统计本周
startTime || endTime ? 0 : apply_records.count({
where: {
sn_code: final_sn_code,
applyTime: { [op.gte]: weekStart }
create_time: { [op.gte]: weekStart }
}
}),
// 本月
apply_records.count({
// 本月如果提供了时间范围则返回0否则统计本月
startTime || endTime ? 0 : apply_records.count({
where: {
sn_code: final_sn_code,
applyTime: { [op.gte]: monthStart }
create_time: { [op.gte]: monthStart }
}
})
}),
// 总职位数
job_postings.count({
where: {
sn_code: final_sn_code,
create_time: { [op.gte]: todayStart }
}
}),
]);
return ctx.success({
@@ -198,6 +252,7 @@ module.exports = {
todayCount,
weekCount,
monthCount,
totalJobCount,
successRate: totalCount > 0 ? ((successCount / totalCount) * 100).toFixed(2) : 0,
interviewRate: totalCount > 0 ? ((interviewCount / totalCount) * 100).toFixed(2) : 0
});
@@ -279,12 +334,12 @@ module.exports = {
const records = await apply_records.findAll({
where: {
sn_code: sn_code,
applyTime: {
create_time: {
[op.gte]: sevenDaysAgo,
[op.lte]: today
}
},
attributes: ['applyTime'],
attributes: ['create_time'],
raw: true
});
@@ -299,7 +354,7 @@ module.exports = {
// 统计当天的投递数量
const count = records.filter(record => {
const recordDate = new Date(record.applyTime);
const recordDate = new Date(record.create_time);
recordDate.setHours(0, 0, 0, 0);
return recordDate.getTime() === date.getTime();
}).length;

View File

@@ -54,8 +54,8 @@ class ScheduleManager {
console.log('[调度管理器] 心跳监听已启动');
// 5. 启动定时任务
// this.scheduledJobs.start();
// console.log('[调度管理器] 定时任务已启动');
this.scheduledJobs.start();
console.log('[调度管理器] 定时任务已启动');
this.isInitialized = true;

View File

@@ -1 +1 @@
import{a as t}from"./index-BEa_v6Fs.js";class s{async getConfig(r){try{return await t.post("/user/delivery-config/get",{sn_code:r})}catch(e){throw console.error("获取投递配置失败:",e),e}}async saveConfig(r,e){try{return await t.post("/user/delivery-config/save",{sn_code:r,deliver_config:e})}catch(o){throw console.error("保存投递配置失败:",o),o}}}const i=new s;export{i as default};
import{a as t}from"./index---wtnUW1.js";class s{async getConfig(r){try{return await t.post("/user/delivery-config/get",{sn_code:r})}catch(e){throw console.error("获取投递配置失败:",e),e}}async saveConfig(r,e){try{return await t.post("/user/delivery-config/save",{sn_code:r,deliver_config:e})}catch(o){throw console.error("保存投递配置失败:",o),o}}}const i=new s;export{i as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,8 +5,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>boss - 远程监听服务</title>
<script type="module" crossorigin src="/app/assets/index-BEa_v6Fs.js"></script>
<link rel="stylesheet" crossorigin href="/app/assets/index-BHUtbpCz.css">
<script type="module" crossorigin src="/app/assets/index---wtnUW1.js"></script>
<link rel="stylesheet" crossorigin href="/app/assets/index-yg6NAGeT.css">
</head>
<body>

View File

@@ -25,7 +25,7 @@ module.exports = {
acquire: 30000,
idle: 10000
},
logging: false
logging: true
},
// API 路径配置(必需)