feat(ai): 集成DeepSeek AI优化简历功能

- 配置DeepSeek AI相关依赖和配置项
- 创建ChatController提供AI聊天测试接口
- 实现AiService和AiServiceImpl提供AI优化服务
- 添加ResumeAiOptimizeRequest和ResumeAiOptimizeVO数据传输对象
- 在ResumeController中添加AI优化简历的API端点
- 前端ResumeEdit页面集成AI优化按钮和进度显示
- 实现前端调用AI优化接口并更新表单内容
- 添加AI优化相关的样式和交互效果
This commit is contained in:
2026-01-15 00:03:43 +08:00
parent 44a379bd0e
commit 6cc2725923
12 changed files with 544 additions and 23 deletions

View File

@@ -96,3 +96,32 @@ export async function deleteResumeFile(
...(options || {}),
})
}
/** AI优化简历请求 */
export interface ResumeAiOptimizeRequest {
resumeTitle?: string
summary?: string
content?: string
}
/** AI优化简历响应 */
export interface ResumeAiOptimizeVO {
resumeTitle?: string
summary?: string
content?: string
}
/** AI优化简历 POST /resume/ai/optimize */
export async function aiOptimizeResume(
body: ResumeAiOptimizeRequest,
options?: { [key: string]: any }
) {
return request<API.BaseResponse<ResumeAiOptimizeVO>>('/resume/ai/optimize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
})
}

View File

@@ -35,26 +35,6 @@
</a-table>
</a-card>
<!-- <a-card title="我的简历PDF" :bordered="false" style="margin-top: 16px">-->
<!-- <div v-if="addressLoading">-->
<!-- <a-spin />-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <div v-if="myAddress?.address">-->
<!-- <a-space>-->
<!-- <a :href="myAddress.address" target="_blank">查看PDF</a>-->
<!-- <a-popconfirm title="确定删除该PDF吗" @confirm="deleteAddress">-->
<!-- <a-button danger>删除</a-button>-->
<!-- </a-popconfirm>-->
<!-- </a-space>-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <a-upload :before-upload="beforeUpload" :show-upload-list="false" :custom-request="(opt:any)=>doUpload(opt.file)">-->
<!-- <a-button type="primary">上传PDF</a-button>-->
<!-- </a-upload>-->
<!-- </div>-->
<!-- </div>-->
<!-- </a-card>-->
</div>
</template>

View File

@@ -69,6 +69,37 @@
<a-switch v-model:checked="isDefaultChecked" />
</a-form-item>
<!-- AI优化区域 -->
<a-form-item label="AI优化" :wrapper-col="{ span: 16 }">
<a-space direction="vertical" style="width: 100%">
<a-button
type="default"
:loading="aiOptimizing"
@click="handleAiOptimize"
:disabled="aiOptimizing"
class="ai-optimize-btn"
>
<template #icon>
<RobotOutlined />
</template>
{{ aiOptimizing ? 'AI优化中...' : 'DeepSeek AI 智能优化' }}
</a-button>
<a-progress
v-if="aiOptimizing"
:percent="Math.round(aiProgress)"
:status="aiProgress >= 100 ? 'success' : 'active'"
:stroke-color="{
'0%': '#108ee9',
'100%': '#87d068',
}"
size="small"
/>
<div v-if="!aiOptimizing" style="color: #999; font-size: 12px">
使用 DeepSeek AI 智能优化您的简历内容让简历更专业更有吸引力
</div>
</a-space>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 4, span: 16 }">
<a-button type="primary" html-type="submit" :loading="loading">保存</a-button>
<a-button style="margin-left: 10px" @click="router.back()">取消</a-button>
@@ -82,9 +113,9 @@
import NavBar from '@/components/NavBar.vue';
import { reactive, ref, onMounted, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { addResume, updateResume, getResumeVoById, deleteResumeFile } from '@/api/api/resumeController';
import { addResume, updateResume, getResumeVoById, deleteResumeFile, aiOptimizeResume } from '@/api/api/resumeController';
import { message, Upload } from 'ant-design-vue';
import { UploadOutlined, FilePdfOutlined } from '@ant-design/icons-vue';
import { UploadOutlined, FilePdfOutlined, RobotOutlined } from '@ant-design/icons-vue';
const route = useRoute();
const router = useRouter();
@@ -95,6 +126,11 @@ const uploading = ref(false);
const uploadProgress = ref(0);
const uploadFileName = ref('');
// AI优化相关状态
const aiOptimizing = ref(false);
const aiProgress = ref(0);
let aiProgressTimer: ReturnType<typeof setInterval> | null = null;
const formState = reactive({
resumeTitle: '',
summary: '',
@@ -229,6 +265,86 @@ const clearAttachment = async () => {
uploadFileName.value = '';
};
// 开始伪进度条
const startFakeProgress = () => {
aiProgress.value = 0;
aiProgressTimer = setInterval(() => {
// 伪进度条逻辑快速增长到30%然后慢慢增长到90%
if (aiProgress.value < 30) {
aiProgress.value += Math.random() * 8 + 2; // 2-10%
} else if (aiProgress.value < 60) {
aiProgress.value += Math.random() * 4 + 1; // 1-5%
} else if (aiProgress.value < 90) {
aiProgress.value += Math.random() * 2 + 0.5; // 0.5-2.5%
}
// 最多到90%等待AI返回后才变成100%
if (aiProgress.value > 90) {
aiProgress.value = 90;
}
}, 300);
};
// 停止伪进度条
const stopFakeProgress = (success: boolean) => {
if (aiProgressTimer) {
clearInterval(aiProgressTimer);
aiProgressTimer = null;
}
if (success) {
aiProgress.value = 100;
// 100%后延迟重置
setTimeout(() => {
aiProgress.value = 0;
aiOptimizing.value = false;
}, 500);
} else {
aiProgress.value = 0;
aiOptimizing.value = false;
}
};
// AI优化简历
const handleAiOptimize = async () => {
// 检查是否有内容可优化
if (!formState.resumeTitle?.trim() && !formState.summary?.trim() && !formState.content?.trim()) {
message.warning('请至少填写一项内容后再进行AI优化');
return;
}
aiOptimizing.value = true;
startFakeProgress();
try {
const res = await aiOptimizeResume({
resumeTitle: formState.resumeTitle,
summary: formState.summary,
content: formState.content,
});
if (res.code === 0 && res.data) {
// 填充AI优化后的内容
if (res.data.resumeTitle) {
formState.resumeTitle = res.data.resumeTitle;
}
if (res.data.summary) {
formState.summary = res.data.summary;
}
if (res.data.content) {
formState.content = res.data.content;
}
stopFakeProgress(true);
message.success('AI优化完成');
} else {
stopFakeProgress(false);
message.error(res.message || 'AI优化失败');
}
} catch (error: any) {
console.error('AI优化失败:', error);
stopFakeProgress(false);
message.error(error.message || 'AI优化失败请稍后重试');
}
};
const onFinish = async (values: any) => {
loading.value = true;
try {
@@ -266,4 +382,25 @@ onMounted(() => {
background-color: #f0f2f5;
min-height: calc(100vh - 64px);
}
.ai-optimize-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
font-weight: 500;
transition: all 0.3s ease;
}
.ai-optimize-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
color: white;
}
.ai-optimize-btn:disabled {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
opacity: 0.7;
color: white;
}
</style>