fix(aihr): close Figma UI regression gaps
This commit is contained in:
@@ -101,7 +101,11 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty">暂无入库案例</view>
|
||||
<view v-else class="case-empty-state">
|
||||
<view class="case-empty-icon">案</view>
|
||||
<text class="case-empty-title">暂时还没有可学习案例</text>
|
||||
<text class="case-empty-desc">案例经主管整理并入库后会出现在这里;提交权限由组织角色决定。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="caseDetail" class="panel case-detail-panel">
|
||||
@@ -535,4 +539,40 @@ onUnload(clearCaseMediaUrl);
|
||||
font-weight: 900;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.case-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 28px 16px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.case-empty-icon {
|
||||
display: flex;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 18px;
|
||||
background: #fff0f1;
|
||||
color: #e60012;
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.case-empty-title {
|
||||
margin-top: 14px;
|
||||
color: #1f2937;
|
||||
font-size: 15px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.case-empty-desc {
|
||||
max-width: 280px;
|
||||
margin-top: 7px;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
<template v-else-if="detail">
|
||||
<view class="panel summary-panel">
|
||||
<text class="section-kicker">训练摘要</text>
|
||||
<view class="summary-head">
|
||||
<view>
|
||||
<text class="detail-title">{{ detail.scene }}</text>
|
||||
@@ -33,17 +34,17 @@
|
||||
|
||||
<view v-if="detail.scoreItems?.length" class="panel">
|
||||
<text class="panel-title">评分明细</text>
|
||||
<view v-for="item in detail.scoreItems" :key="item.label" class="score-row">
|
||||
<view>
|
||||
<view class="score-summary-grid">
|
||||
<view v-for="item in detail.scoreItems" :key="item.label" class="score-row">
|
||||
<text class="score-label">{{ item.label }}</text>
|
||||
<text class="score-value">{{ item.value }}</text>
|
||||
<text class="score-note">{{ item.note }}</text>
|
||||
</view>
|
||||
<text class="score-value">{{ item.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="detail.dialogue?.length" class="panel">
|
||||
<text class="panel-title">对话记录</text>
|
||||
<text class="panel-title">录音与逐句复盘</text>
|
||||
<view v-for="(turn, index) in detail.dialogue" :key="`${turn.role}-${index}`" class="dialogue-row">
|
||||
<text class="dialogue-label">{{ turn.label }}</text>
|
||||
<text class="dialogue-text">{{ turn.text }}</text>
|
||||
@@ -58,7 +59,7 @@
|
||||
</view>
|
||||
|
||||
<view v-if="detail.annotations?.length || detail.mentorRewrite" class="panel">
|
||||
<text class="panel-title">复盘建议</text>
|
||||
<text class="panel-title">SOP 依据与改进建议</text>
|
||||
<view v-for="item in detail.annotations" :key="`${item.turnIndex}-${item.issue}`" class="annotation-row">
|
||||
<text class="annotation-issue">{{ item.issue }}</text>
|
||||
<text class="annotation-rewrite">建议:{{ item.rewrite }}</text>
|
||||
@@ -258,11 +259,24 @@ onUnload(releaseAudio);
|
||||
border-top: 1px solid #ececee;
|
||||
}
|
||||
|
||||
.score-summary-grid {
|
||||
display: grid;
|
||||
margin-top: 10px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.score-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-height: 96px;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.score-note,
|
||||
@@ -342,4 +356,13 @@ onUnload(releaseAudio);
|
||||
.empty-panel button {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.section-kicker {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
color: #e60012;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
{{ helpLoading ? '求助中' : '求助' }}
|
||||
</button>
|
||||
<button class="secondary-button" :disabled="busy || !canFinishPractice" @click="finish()">结束评分</button>
|
||||
<button class="primary-button" :disabled="busy" @click="submit">
|
||||
<button class="primary-button tabbar-safe-action" :disabled="busy" @click="submit">
|
||||
{{ status === 'submitting' ? '提交中' : '提交回应' }}
|
||||
</button>
|
||||
</view>
|
||||
@@ -209,6 +209,12 @@
|
||||
<text class="result-label">本次评分</text>
|
||||
<text class="result-score">{{ result.totalScore }}</text>
|
||||
<text class="result-comment">{{ result.aiComment }}</text>
|
||||
<view v-if="result.score?.length" class="result-dimensions">
|
||||
<view v-for="item in result.score" :key="item.label" class="result-dimension">
|
||||
<text>{{ item.value }}</text>
|
||||
<text>{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="result.mentorRewrite" class="mentor-rewrite">
|
||||
<text>老师傅建议</text>
|
||||
<text>{{ result.mentorRewrite }}</text>
|
||||
@@ -911,6 +917,7 @@ const finish = async (force = false) => {
|
||||
message.value = '';
|
||||
try {
|
||||
result.value = await finishPractice(sessionId.value, activeAssignmentId.value);
|
||||
resetPageScroll();
|
||||
status.value = 'finished';
|
||||
message.value = '训练完成,主管端待复盘已更新';
|
||||
activeAssignmentId.value = undefined;
|
||||
@@ -1464,6 +1471,35 @@ onShow(() => {
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.result-dimensions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.result-dimension {
|
||||
display: flex;
|
||||
min-height: 66px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
border-radius: 14px;
|
||||
background: #f7f8fa;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-dimension text:first-child {
|
||||
color: var(--employee-primary);
|
||||
font-size: 22px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.result-dimension text:last-child {
|
||||
color: var(--employee-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mentor-rewrite {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<view class="profile-head">
|
||||
<text class="profile-eyebrow">银城物业 · 员工成长</text>
|
||||
<text class="page-title">我的成长</text>
|
||||
<text class="page-subtitle">{{ phone || '登录后查看训练记录和能力画像' }}</text>
|
||||
<text class="page-subtitle">{{ displayPhone || '登录后查看训练记录和能力画像' }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="message" class="status-message">{{ message }}</view>
|
||||
@@ -11,7 +11,7 @@
|
||||
<view class="panel account-card">
|
||||
<view>
|
||||
<text class="panel-title">账号</text>
|
||||
<text class="account-phone">{{ phone || '未登录' }}</text>
|
||||
<text class="account-phone">{{ displayPhone || '未登录' }}</text>
|
||||
</view>
|
||||
<button v-if="!loggedIn" class="primary-button account-action" @click="goLogin">去登录</button>
|
||||
<button v-else class="secondary-button account-action" @click="logout">退出</button>
|
||||
@@ -153,7 +153,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import type { CompetencyProfile, PracticeRecord, PromotionEvidence } from '@/types/api';
|
||||
import {
|
||||
@@ -162,6 +162,7 @@ import {
|
||||
getPositionProfile,
|
||||
getSelectedPositionProfile,
|
||||
isLoggedIn,
|
||||
maskPhone,
|
||||
rememberLoginRedirect,
|
||||
saveSelectedPosition,
|
||||
userPositionOptions,
|
||||
@@ -173,6 +174,7 @@ import { ensureEmployeePosition } from '@/services/position';
|
||||
|
||||
const loggedIn = ref(false);
|
||||
const phone = ref('');
|
||||
const displayPhone = computed(() => maskPhone(phone.value));
|
||||
const loading = ref(false);
|
||||
const message = ref('');
|
||||
const profile = ref<CompetencyProfile | null>(null);
|
||||
|
||||
@@ -84,20 +84,51 @@
|
||||
<button
|
||||
class="secondary-button"
|
||||
:class="{ active: feedbackVerdict === 'up' }"
|
||||
:disabled="feedbackSubmitting || Boolean(feedbackVerdict)"
|
||||
:disabled="feedbackSubmitting || Boolean(feedbackVerdict) || showNegativeFeedback"
|
||||
@click="feedback('up')"
|
||||
>
|
||||
有帮助
|
||||
</button>
|
||||
<button
|
||||
class="secondary-button"
|
||||
:class="{ active: feedbackVerdict === 'down', negative: feedbackVerdict === 'down' }"
|
||||
:class="{ active: feedbackVerdict === 'down' || showNegativeFeedback, negative: feedbackVerdict === 'down' || showNegativeFeedback }"
|
||||
:disabled="feedbackSubmitting || Boolean(feedbackVerdict)"
|
||||
@click="feedback('down')"
|
||||
@click="openNegativeFeedback"
|
||||
>
|
||||
没解决
|
||||
</button>
|
||||
</view>
|
||||
<view v-if="showNegativeFeedback && !feedbackVerdict" class="negative-feedback-panel">
|
||||
<text class="negative-feedback-title">哪里没有解决?(可多选)</text>
|
||||
<view class="negative-reason-list">
|
||||
<button
|
||||
v-for="item in feedbackReasonOptions"
|
||||
:key="item.code"
|
||||
class="negative-reason"
|
||||
:class="{ active: selectedFeedbackReasons.includes(item.code) }"
|
||||
:disabled="feedbackSubmitting"
|
||||
@click="toggleFeedbackReason(item.code)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</view>
|
||||
<textarea
|
||||
v-model="feedbackComment"
|
||||
class="textarea negative-feedback-comment"
|
||||
maxlength="500"
|
||||
placeholder="补充说明(可选)"
|
||||
/>
|
||||
<view class="negative-feedback-actions">
|
||||
<button class="secondary-button" :disabled="feedbackSubmitting" @click="closeNegativeFeedback">取消</button>
|
||||
<button
|
||||
class="primary-button"
|
||||
:disabled="feedbackSubmitting || !selectedFeedbackReasons.length"
|
||||
@click="submitNegativeFeedback"
|
||||
>
|
||||
{{ feedbackSubmitting ? '提交中' : '提交反馈' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="feedbackVerdict" class="feedback-status">
|
||||
{{ feedbackVerdict === 'up' ? '已记录为有帮助' : '已提交未解决反馈,后续会继续优化这条答案' }}
|
||||
</text>
|
||||
@@ -220,15 +251,28 @@ const loading = ref(false);
|
||||
const summaryLoading = ref(false);
|
||||
const feedbackSubmitting = ref(false);
|
||||
const feedbackVerdict = ref<AnswerFeedbackVerdict | ''>('');
|
||||
const showNegativeFeedback = ref(false);
|
||||
const selectedFeedbackReasons = ref<string[]>([]);
|
||||
const feedbackComment = ref('');
|
||||
const message = ref('');
|
||||
const loggedIn = ref(false);
|
||||
const pendingSopQueryKey = 'aihr_sop_pending_query';
|
||||
const feedbackReasonOptions = [
|
||||
{ code: 'no_answer', label: '没有回答问题' },
|
||||
{ code: 'not_specific', label: '步骤不够具体' },
|
||||
{ code: 'wrong_reference', label: '引用依据不准确' },
|
||||
{ code: 'outdated', label: '内容已经过期' },
|
||||
{ code: 'not_applicable', label: '话术不适合现场' }
|
||||
];
|
||||
|
||||
const resetResultState = () => {
|
||||
result.value = null;
|
||||
summaryCard.value = null;
|
||||
feedbackVerdict.value = '';
|
||||
feedbackSubmitting.value = false;
|
||||
showNegativeFeedback.value = false;
|
||||
selectedFeedbackReasons.value = [];
|
||||
feedbackComment.value = '';
|
||||
};
|
||||
|
||||
const refreshAuthState = () => {
|
||||
@@ -271,6 +315,7 @@ const search = async () => {
|
||||
resetResultState();
|
||||
try {
|
||||
result.value = await searchKnowledge(value);
|
||||
resetPageScroll();
|
||||
if (!result.value.answer && !result.value.snippets?.length) {
|
||||
message.value = '未检索到可引用内容';
|
||||
}
|
||||
@@ -297,7 +342,10 @@ const cleanOrderedText = (text = '') =>
|
||||
.replace(/^(?:[一二三四五六七八九十]+[、..]|[0-9]+[.)、..])\s*/, '')
|
||||
.trim();
|
||||
|
||||
const feedback = async (verdict: AnswerFeedbackVerdict) => {
|
||||
const feedback = async (
|
||||
verdict: AnswerFeedbackVerdict,
|
||||
details: { reasonCodes?: string[]; comment?: string } = {}
|
||||
) => {
|
||||
if (!result.value || feedbackSubmitting.value || feedbackVerdict.value) return;
|
||||
const fragmentIds = Array.from(
|
||||
new Set(
|
||||
@@ -317,9 +365,12 @@ const feedback = async (verdict: AnswerFeedbackVerdict) => {
|
||||
queryText: result.value.queryText || query.value,
|
||||
fragmentIds,
|
||||
verdict,
|
||||
reviewId: result.value.reviewId
|
||||
reviewId: result.value.reviewId,
|
||||
reasonCodes: details.reasonCodes,
|
||||
comment: details.comment
|
||||
});
|
||||
feedbackVerdict.value = verdict;
|
||||
showNegativeFeedback.value = false;
|
||||
uni.showToast({ title: verdict === 'down' ? '已进入复核' : '已记录', icon: 'none' });
|
||||
} catch (error) {
|
||||
if (!handleRequestError(error, '反馈失败', true)) {
|
||||
@@ -333,6 +384,32 @@ const feedback = async (verdict: AnswerFeedbackVerdict) => {
|
||||
}
|
||||
};
|
||||
|
||||
const openNegativeFeedback = () => {
|
||||
if (feedbackSubmitting.value || feedbackVerdict.value) return;
|
||||
showNegativeFeedback.value = true;
|
||||
selectedFeedbackReasons.value = [];
|
||||
feedbackComment.value = '';
|
||||
};
|
||||
|
||||
const closeNegativeFeedback = () => {
|
||||
if (feedbackSubmitting.value) return;
|
||||
showNegativeFeedback.value = false;
|
||||
};
|
||||
|
||||
const toggleFeedbackReason = (code: string) => {
|
||||
selectedFeedbackReasons.value = selectedFeedbackReasons.value.includes(code)
|
||||
? selectedFeedbackReasons.value.filter((item) => item !== code)
|
||||
: [...selectedFeedbackReasons.value, code];
|
||||
};
|
||||
|
||||
const submitNegativeFeedback = () => {
|
||||
if (!selectedFeedbackReasons.value.length) return;
|
||||
void feedback('down', {
|
||||
reasonCodes: selectedFeedbackReasons.value,
|
||||
comment: feedbackComment.value.trim()
|
||||
});
|
||||
};
|
||||
|
||||
const buildSummaryCard = async () => {
|
||||
if (summaryLoading.value) return;
|
||||
const value = (result.value?.queryText || query.value).trim();
|
||||
@@ -624,6 +701,54 @@ onShow(() => {
|
||||
color: #c34d45;
|
||||
}
|
||||
|
||||
.negative-feedback-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
border-radius: 16px;
|
||||
background: #fff8f8;
|
||||
}
|
||||
|
||||
.negative-feedback-title {
|
||||
color: var(--employee-text);
|
||||
font-size: 15px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.negative-reason-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.negative-reason {
|
||||
min-height: 40px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #f1d7d9;
|
||||
background: #ffffff;
|
||||
color: #5c626c;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.negative-reason.active {
|
||||
border-color: var(--employee-primary);
|
||||
background: var(--employee-primary-soft);
|
||||
color: var(--employee-primary);
|
||||
}
|
||||
|
||||
.negative-feedback-comment {
|
||||
min-height: 84px;
|
||||
border-color: #f1d7d9;
|
||||
}
|
||||
|
||||
.negative-feedback-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.feedback-status {
|
||||
display: block;
|
||||
padding: 10px 12px;
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="schedule-row" v-if="home?.review">
|
||||
<view class="schedule-row review-schedule-row" v-if="home?.review">
|
||||
<view class="schedule-icon-wrap review-icon">
|
||||
<uni-icons class="schedule-icon" type="calendar-filled" size="26" color="#d98200" />
|
||||
</view>
|
||||
@@ -917,9 +917,13 @@ onShow(() => {
|
||||
}
|
||||
|
||||
.schedule-desc {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
color: #8a8d93;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.schedule-action {
|
||||
@@ -942,6 +946,28 @@ onShow(() => {
|
||||
box-shadow: 0 8px 16px rgba(217, 130, 0, 0.16);
|
||||
}
|
||||
|
||||
.review-schedule-row {
|
||||
min-height: 62px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.review-schedule-row .schedule-icon-wrap {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
.review-schedule-row .schedule-meta,
|
||||
.review-schedule-row .schedule-desc {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.review-schedule-row .schedule-action {
|
||||
width: 70px;
|
||||
min-height: 40px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.map-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
Reference in New Issue
Block a user