diff --git a/mobile-uni/src/pages/supervisor/exams/edit.vue b/mobile-uni/src/pages/supervisor/exams/edit.vue
index eca34428..c5bd94e8 100644
--- a/mobile-uni/src/pages/supervisor/exams/edit.vue
+++ b/mobile-uni/src/pages/supervisor/exams/edit.vue
@@ -29,6 +29,15 @@
当前项目暂无可派发的在职员工。
+
+ AI 出题
+ 输入考察提示,AI 按岗位生成题目与答案,请逐题核对后发布
+
+
+
+
+
+
题目({{ questions.length }})· 共 {{ totalScore }} 分
发布前题目分值合计必须为 100 分
@@ -60,7 +69,7 @@ import { computed, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import type { ExamQuestion, PracticeTeamMember, SupervisorExamDraft } from '@/types/api';
import { getPracticeTeamSnapshot } from '@/services/practice';
-import { getSupervisorExam, publishSupervisorExam, saveSupervisorExam, updateSupervisorExam } from '@/services/exam';
+import { generateSupervisorExamQuestions, getSupervisorExam, publishSupervisorExam, saveSupervisorExam, updateSupervisorExam } from '@/services/exam';
import SupervisorTabBar from '@/components/SupervisorTabBar.vue';
const positions = ['生活顾问', '客服', '保安', '保洁', '保修'];
@@ -69,6 +78,9 @@ const quickDates = [{ label: '当天', days: 0 }, { label: '明天', days: 1 },
const examId = ref(0); const title = ref(''); const positionIndex = ref(0); const passingScore = ref(80); const dueDate = ref('');
const members = ref([]); const selectedMemberIds = ref([]); const questions = ref([]); const singleAnswerModels = ref([]);
const saving = ref(false); const message = ref('');
+const aiHints = ref(''); const aiCount = ref(5); const aiGenerating = ref(false);
+const aiHintPresets = ['投诉接待', '催缴沟通', '电梯故障', '服务态度', '消防安全'];
+const aiCountOptions = [5, 8, 10];
const formatDate = (date: Date) => `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`;
const dateAfter = (days: number) => { const date = new Date(); date.setHours(12,0,0,0); date.setDate(date.getDate()+days); return formatDate(date); };
const today = dateAfter(0);
@@ -104,6 +116,23 @@ const selectFormat = (index: number, event: { detail: { value: string } }) => {
const syncAnswer = (index: number) => { questions.value[index].answer = questions.value[index].format === 'multiple' ? singleAnswerModels.value[index].split('|').map((value) => value.trim()).filter(Boolean) : singleAnswerModels.value[index]; };
const toggleMember = (id: string) => { selectedMemberIds.value = selectedMemberIds.value.includes(id) ? selectedMemberIds.value.filter((item) => item !== id) : selectedMemberIds.value.length < 100 ? [...selectedMemberIds.value,id] : selectedMemberIds.value; };
const selectAll = () => { selectedMemberIds.value = members.value.slice(0,100).map((item) => item.extPartyId); };
+const appendAiHint = (hint: string) => {
+ const parts = aiHints.value.split(/[、,,\s]+/).map((value) => value.trim()).filter(Boolean);
+ if (!parts.includes(hint)) parts.push(hint);
+ aiHints.value = parts.join('、');
+};
+const generateAiQuestions = async () => {
+ if (saving.value || aiGenerating.value) return; aiGenerating.value = true; message.value = '';
+ try {
+ const result = await generateSupervisorExamQuestions({ positionCode: positions[positionIndex.value], hints: aiHints.value.trim(), count: aiCount.value, formats: ['single','multiple','judge'] });
+ questions.value = result.questions;
+ singleAnswerModels.value = result.questions.map((item) => Array.isArray(item.answer) ? item.answer.join('|') : String(item.answer || ''));
+ message.value = result.groundedFragments > 0
+ ? `已生成 ${result.questions.length} 题(参考 ${result.groundedFragments} 条 SOP 片段),请逐题核对修改后再发布`
+ : `已生成 ${result.questions.length} 题,请逐题核对修改后再发布`;
+ uni.pageScrollTo({ selector: '.question-editor', duration: 200 });
+ } catch (error) { message.value = error instanceof Error ? error.message : 'AI 出题失败,请稍后重试'; } finally { aiGenerating.value = false; }
+};
const draft = (): SupervisorExamDraft => ({
title: title.value.trim(),
projectCode: '',
@@ -168,4 +197,9 @@ onLoad((options) => { examId.value = Number(options?.id || 0); void load(); });
.compact-textarea { min-height:82px; }
.score-warning { color:#a86100; font-size:12px; font-weight:800; }
.publish-dock { position:fixed; z-index:10; right:0; bottom:calc(76px + env(safe-area-inset-bottom)); left:0; display:grid; grid-template-columns:1fr 1fr; gap:10px; max-width:var(--mobile-shell-max-width); margin:0 auto; padding:12px 16px; border-top:1px solid var(--line); background:rgba(255,255,255,.97); }
+.ai-panel { display:grid; gap:10px; }
+.ai-chip-row { justify-content:flex-start; flex-wrap:wrap; }
+.ai-chip { display:flex; align-items:center; justify-content:center; min-height:34px; padding:0 12px; line-height:1; }
+.ai-chip.active { border-color:var(--brand); color:var(--brand); font-weight:900; }
+.ai-generate-button { display:flex; align-items:center; justify-content:center; min-height:44px; line-height:1; }
diff --git a/mobile-uni/src/services/exam.ts b/mobile-uni/src/services/exam.ts
index 7f58cef6..e6f7cbd6 100644
--- a/mobile-uni/src/services/exam.ts
+++ b/mobile-uni/src/services/exam.ts
@@ -1,4 +1,4 @@
-import type { ExamDetail, ExamResultItem, ExamSubmitResult, ExamSummary, SupervisorExamDraft } from '@/types/api';
+import type { ExamDetail, ExamQuestion, ExamResultItem, ExamSubmitResult, ExamSummary, SupervisorExamDraft } from '@/types/api';
import { apiRequest } from './api';
type RawExamQuestion = {
@@ -149,3 +149,35 @@ export const getSupervisorExamResults = (id: number) =>
method: 'GET',
timeout: 15000
});
+
+
+type RawAiGenerateResponse = {
+ questions: RawExamQuestion[];
+ groundedFragments?: number;
+};
+
+export const generateSupervisorExamQuestions = async (data: {
+ positionCode: string;
+ hints: string;
+ count: number;
+ formats: string[];
+}): Promise<{ questions: ExamQuestion[]; groundedFragments: number }> => {
+ const response = await apiRequest({
+ url: '/api/aihr/mobile/exams/supervisor/ai-generate',
+ method: 'POST',
+ data,
+ timeout: 60000
+ });
+ return {
+ groundedFragments: response.groundedFragments || 0,
+ questions: (response.questions || []).map((question, index) => ({
+ id: Date.now() + index,
+ format: question.questionType,
+ stem: question.stem,
+ options: question.options || [],
+ score: question.score,
+ answer: question.questionType === 'multiple' ? (question.correctAnswers || []) : (question.correctAnswers?.[0] || ''),
+ explanation: question.explanation || ''
+ }))
+ };
+};