Compare commits
10 Commits
dev
...
b17d08ffa8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b17d08ffa8 | ||
|
|
54644dbb72 | ||
|
|
1d8d2ea6e8 | ||
|
|
3f4acc5e1d | ||
|
|
69f2f87f4b | ||
|
|
0cfff98edf | ||
|
|
77789446f3 | ||
|
|
2530f25b86 | ||
|
|
6efd77d2b5 | ||
|
|
6253abc617 |
@@ -48,6 +48,15 @@ class ResumeInfoServer {
|
|||||||
analyzeWithAI(resumeId) {
|
analyzeWithAI(resumeId) {
|
||||||
return window.framework.http.post('/resume/analyze-with-ai', { 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()
|
export default new ResumeInfoServer()
|
||||||
|
|||||||
9
admin/src/framework/node-core-framework.js
Normal file
9
admin/src/framework/node-core-framework.js
Normal file
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@
|
|||||||
@back="handleBack"
|
@back="handleBack"
|
||||||
>
|
>
|
||||||
<template #header-right>
|
<template #header-right>
|
||||||
|
<Button type="info" @click="handleSyncOnline" :loading="syncing" style="margin-right: 8px;">同步在线简历</Button>
|
||||||
<Button type="primary" @click="handleAnalyzeAI" :loading="analyzing">AI 分析</Button>
|
<Button type="primary" @click="handleAnalyzeAI" :loading="analyzing">AI 分析</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -272,6 +273,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
analyzing: false,
|
analyzing: false,
|
||||||
|
syncing: false,
|
||||||
resumeData: null,
|
resumeData: null,
|
||||||
skillTags: [],
|
skillTags: [],
|
||||||
workExperience: [],
|
workExperience: [],
|
||||||
@@ -317,6 +319,32 @@ export default {
|
|||||||
}
|
}
|
||||||
return field
|
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() {
|
async handleAnalyzeAI() {
|
||||||
if (!this.resumeData || !this.resumeData.resumeId) {
|
if (!this.resumeData || !this.resumeData.resumeId) {
|
||||||
this.$Message.warning('简历ID不存在')
|
this.$Message.warning('简历ID不存在')
|
||||||
|
|||||||
@@ -358,6 +358,100 @@ return ctx.success({ message: '简历删除成功' });
|
|||||||
console.error('AI 分析失败:', error);
|
console.error('AI 分析失败:', error);
|
||||||
return ctx.fail('AI 分析失败: ' + error.message);
|
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 || '未知错误'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,20 @@ module.exports = {
|
|||||||
where.feedbackStatus = seachOption.feedbackStatus;
|
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) {
|
if (seachOption.key && seachOption.value) {
|
||||||
const key = seachOption.key;
|
const key = seachOption.key;
|
||||||
@@ -93,7 +107,7 @@ module.exports = {
|
|||||||
where,
|
where,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
order: [['applyTime', 'DESC']]
|
order: [['create_time', 'DESC']]
|
||||||
});
|
});
|
||||||
|
|
||||||
return ctx.success({
|
return ctx.success({
|
||||||
@@ -109,7 +123,7 @@ module.exports = {
|
|||||||
* /api/apply/statistics:
|
* /api/apply/statistics:
|
||||||
* get:
|
* get:
|
||||||
* summary: 获取投递统计
|
* summary: 获取投递统计
|
||||||
* description: 根据设备SN码获取投递统计数据(包含今日、本周、本月统计)
|
* description: 根据设备SN码获取投递统计数据(包含今日、本周、本月统计),支持时间范围筛选
|
||||||
* tags: [前端-投递管理]
|
* tags: [前端-投递管理]
|
||||||
* parameters:
|
* parameters:
|
||||||
* - in: query
|
* - in: query
|
||||||
@@ -118,21 +132,50 @@ module.exports = {
|
|||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 设备SN码
|
* 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:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: 获取成功
|
* description: 获取成功
|
||||||
*/
|
*/
|
||||||
'GET /apply/statistics': async (ctx) => {
|
'POST /apply/statistics': async (ctx) => {
|
||||||
const models = Framework.getModels();
|
const models = Framework.getModels();
|
||||||
const { apply_records, op } = models;
|
const { apply_records, op, job_postings } = models;
|
||||||
const { sn_code } = ctx.query;
|
const { sn_code, startTime, endTime } = ctx.getBody();
|
||||||
|
console.log(startTime, endTime);
|
||||||
const final_sn_code = sn_code;
|
const final_sn_code = sn_code;
|
||||||
|
|
||||||
if (!final_sn_code) {
|
if (!final_sn_code) {
|
||||||
return ctx.fail('请提供设备SN码');
|
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();
|
const now = new Date();
|
||||||
|
|
||||||
// 今天的开始时间(00:00:00)
|
// 今天的开始时间(00:00:00)
|
||||||
@@ -150,6 +193,8 @@ module.exports = {
|
|||||||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
monthStart.setHours(0, 0, 0, 0);
|
monthStart.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const [
|
const [
|
||||||
totalCount,
|
totalCount,
|
||||||
successCount,
|
successCount,
|
||||||
@@ -158,35 +203,44 @@ module.exports = {
|
|||||||
interviewCount,
|
interviewCount,
|
||||||
todayCount,
|
todayCount,
|
||||||
weekCount,
|
weekCount,
|
||||||
monthCount
|
monthCount,
|
||||||
|
totalJobCount
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
// 总计
|
// 总计(如果提供了时间范围,则只统计该范围内的)
|
||||||
apply_records.count({ where: { sn_code: final_sn_code } }),
|
apply_records.count({ where: baseWhere }),
|
||||||
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'success' } }),
|
apply_records.count({ where: { ...baseWhere, applyStatus: 'success' } }),
|
||||||
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'failed' } }),
|
apply_records.count({ where: { ...baseWhere, applyStatus: 'failed' } }),
|
||||||
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'pending' } }),
|
apply_records.count({ where: { ...baseWhere, applyStatus: 'pending' } }),
|
||||||
apply_records.count({ where: { sn_code: final_sn_code, feedbackStatus: 'interview' } }),
|
apply_records.count({ where: { ...baseWhere, feedbackStatus: 'interview' } }),
|
||||||
// 今日
|
|
||||||
apply_records.count({
|
// 今日(如果提供了时间范围,则返回0,否则统计今日)
|
||||||
|
startTime || endTime ? 0 : apply_records.count({
|
||||||
where: {
|
where: {
|
||||||
sn_code: final_sn_code,
|
sn_code: final_sn_code,
|
||||||
applyTime: { [op.gte]: todayStart }
|
create_time: { [op.gte]: todayStart }
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
// 本周
|
// 本周(如果提供了时间范围,则返回0,否则统计本周)
|
||||||
apply_records.count({
|
startTime || endTime ? 0 : apply_records.count({
|
||||||
where: {
|
where: {
|
||||||
sn_code: final_sn_code,
|
sn_code: final_sn_code,
|
||||||
applyTime: { [op.gte]: weekStart }
|
create_time: { [op.gte]: weekStart }
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
// 本月
|
// 本月(如果提供了时间范围,则返回0,否则统计本月)
|
||||||
apply_records.count({
|
startTime || endTime ? 0 : apply_records.count({
|
||||||
where: {
|
where: {
|
||||||
sn_code: final_sn_code,
|
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({
|
return ctx.success({
|
||||||
@@ -198,6 +252,7 @@ module.exports = {
|
|||||||
todayCount,
|
todayCount,
|
||||||
weekCount,
|
weekCount,
|
||||||
monthCount,
|
monthCount,
|
||||||
|
totalJobCount,
|
||||||
successRate: totalCount > 0 ? ((successCount / totalCount) * 100).toFixed(2) : 0,
|
successRate: totalCount > 0 ? ((successCount / totalCount) * 100).toFixed(2) : 0,
|
||||||
interviewRate: totalCount > 0 ? ((interviewCount / 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({
|
const records = await apply_records.findAll({
|
||||||
where: {
|
where: {
|
||||||
sn_code: sn_code,
|
sn_code: sn_code,
|
||||||
applyTime: {
|
create_time: {
|
||||||
[op.gte]: sevenDaysAgo,
|
[op.gte]: sevenDaysAgo,
|
||||||
[op.lte]: today
|
[op.lte]: today
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
attributes: ['applyTime'],
|
attributes: ['create_time'],
|
||||||
raw: true
|
raw: true
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -299,7 +354,7 @@ module.exports = {
|
|||||||
|
|
||||||
// 统计当天的投递数量
|
// 统计当天的投递数量
|
||||||
const count = records.filter(record => {
|
const count = records.filter(record => {
|
||||||
const recordDate = new Date(record.applyTime);
|
const recordDate = new Date(record.create_time);
|
||||||
recordDate.setHours(0, 0, 0, 0);
|
recordDate.setHours(0, 0, 0, 0);
|
||||||
return recordDate.getTime() === date.getTime();
|
return recordDate.getTime() === date.getTime();
|
||||||
}).length;
|
}).length;
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ class ScheduleManager {
|
|||||||
console.log('[调度管理器] 心跳监听已启动');
|
console.log('[调度管理器] 心跳监听已启动');
|
||||||
|
|
||||||
// 5. 启动定时任务
|
// 5. 启动定时任务
|
||||||
// this.scheduledJobs.start();
|
this.scheduledJobs.start();
|
||||||
// console.log('[调度管理器] 定时任务已启动');
|
console.log('[调度管理器] 定时任务已启动');
|
||||||
|
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
|
|
||||||
|
|||||||
@@ -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
1
app/assets/index-yg6NAGeT.css
Normal file
1
app/assets/index-yg6NAGeT.css
Normal file
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>boss - 远程监听服务</title>
|
<title>boss - 远程监听服务</title>
|
||||||
<script type="module" crossorigin src="/app/assets/index-BEa_v6Fs.js"></script>
|
<script type="module" crossorigin src="/app/assets/index---wtnUW1.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/app/assets/index-BHUtbpCz.css">
|
<link rel="stylesheet" crossorigin href="/app/assets/index-yg6NAGeT.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ module.exports = {
|
|||||||
acquire: 30000,
|
acquire: 30000,
|
||||||
idle: 10000
|
idle: 10000
|
||||||
},
|
},
|
||||||
logging: false
|
logging: true
|
||||||
},
|
},
|
||||||
|
|
||||||
// API 路径配置(必需)
|
// API 路径配置(必需)
|
||||||
|
|||||||
Reference in New Issue
Block a user