feat(aihr): complete uni-app role workflows
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<div class="review-page">
|
||||
<section class="page-head">
|
||||
<div>
|
||||
<h1>复盘校准</h1>
|
||||
<p>集中处理主管待复盘、逐句标注、复盘建议、学分奖励和下一次训练派发。</p>
|
||||
</div>
|
||||
<div class="head-actions">
|
||||
<el-button :icon="Refresh" :loading="loading" @click="loadReviews">刷新</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="review-layout">
|
||||
<article class="panel list-panel">
|
||||
<div class="panel-title">
|
||||
<h2>待复盘记录</h2>
|
||||
<el-tag type="danger">{{ reviews.length }} 条</el-tag>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="reviews" size="small" highlight-current-row @row-click="selectReview">
|
||||
<el-table-column prop="trainee" label="学员" width="96" />
|
||||
<el-table-column prop="scene" label="场景" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="score" label="得分" width="70" />
|
||||
<el-table-column prop="status" label="状态" width="92">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '待复盘' ? 'warning' : 'success'" size="small">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="time" label="时间" width="128" />
|
||||
</el-table>
|
||||
</article>
|
||||
|
||||
<article class="panel detail-panel">
|
||||
<template v-if="detail">
|
||||
<div class="panel-title">
|
||||
<h2>{{ detail.trainee }} · {{ detail.scene }}</h2>
|
||||
<el-tag :type="detail.score >= 80 ? 'success' : 'danger'">{{ detail.score }} 分</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="score-grid">
|
||||
<div v-for="item in detail.scoreItems" :key="item.label" class="score-card">
|
||||
<span>{{ item.label }}</span>
|
||||
<strong>{{ item.value }}</strong>
|
||||
<small>{{ item.note }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialogue-list">
|
||||
<div v-for="(turn, index) in detail.dialogue" :key="`${turn.role}-${index}`" class="dialogue-item">
|
||||
<strong>{{ turn.label }}</strong>
|
||||
<p>{{ turn.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="annotation-list">
|
||||
<h3>逐句标注</h3>
|
||||
<el-table :data="detail.annotations" size="small" empty-text="暂无逐句标注">
|
||||
<el-table-column prop="sourceText" label="原句" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="issue" label="问题" min-width="160" />
|
||||
<el-table-column prop="rewrite" label="推荐改写" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="sopPoint" label="SOP点" min-width="160" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-form class="review-form" label-position="top">
|
||||
<el-form-item label="复盘建议">
|
||||
<el-input v-model="reviewForm.advice" type="textarea" :rows="3" placeholder="写给员工的复盘建议" />
|
||||
</el-form-item>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="贡献学分">
|
||||
<el-input-number v-model="reviewForm.incentivePoint" :min="0" :max="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下次训练">
|
||||
<el-select v-model="reviewForm.nextScenarioId" clearable placeholder="不派发">
|
||||
<el-option v-for="scenario in scenarios" :key="scenario.id" :label="scenario.name" :value="scenario.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="派发原因">
|
||||
<el-input v-model="reviewForm.nextReason" placeholder="例如:低分维度定向再练" />
|
||||
</el-form-item>
|
||||
<div class="form-actions">
|
||||
<el-button type="danger" :loading="saving" @click="submitReview">标记已复盘</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
<el-empty v-else description="选择一条待复盘记录" />
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="TrainReviews" lang="ts">
|
||||
import { Refresh } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { listPracticeScenarios, type PracticeScenarioResponse } from '@/api/aihr/practice';
|
||||
import { getPracticeReview, listPracticeReviews, markPracticeReviewed, type ReviewDetail, type ReviewRecord } from '@/api/aihr/mobile-admin';
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const reviews = ref<ReviewRecord[]>([]);
|
||||
const detail = ref<ReviewDetail | null>(null);
|
||||
const scenarios = ref<PracticeScenarioResponse[]>([]);
|
||||
|
||||
const reviewForm = reactive({
|
||||
reviewer: '主管',
|
||||
advice: '',
|
||||
incentivePoint: 10,
|
||||
nextScenarioId: '',
|
||||
nextReason: ''
|
||||
});
|
||||
|
||||
const loadScenarios = async () => {
|
||||
try {
|
||||
const result = await listPracticeScenarios();
|
||||
scenarios.value = result.data || [];
|
||||
} catch (error) {
|
||||
scenarios.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const loadReviews = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await listPracticeReviews();
|
||||
reviews.value = result.data || [];
|
||||
if (!detail.value && reviews.value.length > 0) {
|
||||
await selectReview(reviews.value[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('待复盘列表加载失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const selectReview = async (row: ReviewRecord) => {
|
||||
const result = await getPracticeReview(row.id);
|
||||
detail.value = result.data;
|
||||
reviewForm.advice = result.data.reviewAdvice || '';
|
||||
reviewForm.incentivePoint = result.data.incentivePoint || 10;
|
||||
reviewForm.nextScenarioId = '';
|
||||
reviewForm.nextReason = '';
|
||||
};
|
||||
|
||||
const submitReview = async () => {
|
||||
if (!detail.value) return;
|
||||
saving.value = true;
|
||||
try {
|
||||
await markPracticeReviewed(detail.value.id, {
|
||||
reviewer: reviewForm.reviewer,
|
||||
advice: reviewForm.advice,
|
||||
incentivePoint: reviewForm.incentivePoint,
|
||||
nextScenarioId: reviewForm.nextScenarioId,
|
||||
nextReason: reviewForm.nextReason
|
||||
});
|
||||
ElMessage.success('已完成复盘处理');
|
||||
detail.value = null;
|
||||
await loadReviews();
|
||||
} catch (error) {
|
||||
ElMessage.error('复盘提交失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadScenarios();
|
||||
loadReviews();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.review-page {
|
||||
min-height: 100%;
|
||||
padding: 24px;
|
||||
background: #f5f7fb;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.page-head,
|
||||
.panel,
|
||||
.score-card {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.page-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 20px 22px;
|
||||
|
||||
h1 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #667085;
|
||||
}
|
||||
}
|
||||
|
||||
.review-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(420px, 0.9fr) minmax(0, 1.4fr);
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.score-grid,
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.score-card {
|
||||
padding: 12px;
|
||||
|
||||
span,
|
||||
small {
|
||||
display: block;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
margin: 6px 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialogue-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.dialogue-item {
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
padding: 12px;
|
||||
|
||||
p {
|
||||
margin: 6px 0 0;
|
||||
color: #475467;
|
||||
}
|
||||
}
|
||||
|
||||
.annotation-list {
|
||||
margin-top: 18px;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.review-form {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.review-layout,
|
||||
.score-grid,
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user