Files
prop-ai-hr/mobile-uni/src/pages/candidate/study/index.vue
T

334 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page candidate-subpage study-page">
<view class="subpage-header">
<button class="subpage-back-button" aria-label="返回候选首页" title="返回候选首页" @click="goBack">←</button>
<view class="subpage-heading">
<text class="page-title">岗前预习</text>
<text class="page-subtitle">从正式知识库生成岗位要点</text>
</view>
</view>
<view v-if="message" class="status-message">{{ message }}</view>
<view v-if="!loggedIn" class="panel login-panel">
<view>
<text class="panel-title">需要登录</text>
<text class="muted block">登录后生成生活顾问岗前预习。</text>
</view>
<button class="primary-button login-button" @click="goLogin">去登录</button>
</view>
<template v-else>
<view class="panel query-panel">
<text class="query-label">预习主题</text>
<textarea
v-model="queryText"
class="textarea query-input"
maxlength="120"
placeholder="输入要预习的岗位问题"
/>
<view class="topic-row">
<button
v-for="topic in topics"
:key="topic"
class="topic-button"
:class="{ active: queryText === topic }"
:disabled="loading"
@click="queryText = topic"
>
{{ topic }}
</button>
</view>
<button class="primary-button generate-button" :disabled="loading || !queryText.trim()" @click="loadStudy">
{{ loading ? '生成中' : '生成预习' }}
</button>
</view>
<view v-if="answer" class="panel answer-panel">
<text class="panel-title">预习要点</text>
<text class="answer-text">{{ answer }}</text>
<view v-if="keyPoints.length" class="answer-section">
<text class="section-title">关键要点</text>
<view v-for="(item, index) in keyPoints" :key="`${index}-${item}`" class="point-row">
<text class="point-index">{{ index + 1 }}</text>
<text class="point-text">{{ item }}</text>
</view>
</view>
<view v-if="cautions.length" class="answer-section caution-section">
<text class="section-title">注意事项</text>
<text v-for="item in cautions" :key="item" class="caution-text">{{ item }}</text>
</view>
</view>
<view v-if="references.length" class="panel reference-panel">
<text class="panel-title">引用依据</text>
<view v-for="item in references" :key="item" class="reference-row">
<text class="reference-dot">•</text>
<text class="reference-title">{{ item }}</text>
</view>
</view>
<view v-if="!answer && !loading" class="empty-state">
<text class="empty-title">尚未生成预习</text>
<text class="empty-desc">选择主题后调用正式 SOP 知识库,不使用前端示例答案。</text>
</view>
</template>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { candidateRoutes } from '@/services/candidate-view';
import { searchKnowledge } from '@/services/knowledge';
import { resetPageScroll } from '@/services/navigation';
const currentPath = '/pages/candidate/study/index';
const topics = [
'生活顾问接到业主投诉时,应该如何安抚、记录、跟进和反馈?',
'生活顾问接到报事报修后,应该如何登记、派单和回访?',
'生活顾问与情绪激动的业主沟通时,有哪些原则和禁忌?'
];
const queryText = ref(topics[0]);
const answer = ref('');
const keyPoints = ref<string[]>([]);
const cautions = ref<string[]>([]);
const references = ref<string[]>([]);
const loggedIn = ref(false);
const loading = ref(false);
const message = ref('');
const refreshAuthState = () => {
loggedIn.value = isLoggedIn();
};
const clearResult = () => {
answer.value = '';
keyPoints.value = [];
cautions.value = [];
references.value = [];
};
const loadStudy = async () => {
if (loading.value) return;
refreshAuthState();
if (!loggedIn.value) {
goLogin();
return;
}
const query = queryText.value.trim();
if (!query) {
message.value = '请输入预习主题';
return;
}
loading.value = true;
message.value = '';
clearResult();
try {
const result = await searchKnowledge(query, '生活顾问');
answer.value = result.answer;
keyPoints.value = result.keyPoints || [];
cautions.value = result.cautions || [];
references.value = (result.docs || []).map((item) => item.title).filter(Boolean).slice(0, 5);
if (!answer.value) message.value = '正式知识库暂未返回可用答案';
} catch (error) {
refreshAuthState();
if (!loggedIn.value) {
clearResult();
message.value = '登录已过期,请重新登录';
} else {
message.value = error instanceof Error ? error.message : '岗前预习生成失败';
}
} finally {
loading.value = false;
}
};
const goLogin = () => {
rememberLoginRedirect(currentPath);
uni.redirectTo({ url: '/pages/auth/login/index' });
};
const goBack = () => {
if (getCurrentPages().length <= 1) {
uni.redirectTo({ url: candidateRoutes.index });
return;
}
uni.navigateBack({ delta: 1, fail: () => uni.redirectTo({ url: candidateRoutes.index }) });
};
onShow(() => {
resetPageScroll();
refreshAuthState();
if (!loggedIn.value) clearResult();
});
</script>
<style scoped>
.candidate-subpage {
background: #f4f7fb;
}
.candidate-subpage .panel,
.candidate-subpage button {
border-radius: 8px;
}
.login-panel {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.block {
display: block;
margin-top: 8px;
font-size: 14px;
line-height: 1.5;
}
.login-button {
min-width: 96px;
}
.query-panel {
border-left: 4px solid #e60012;
}
.query-label {
color: #172033;
font-size: 14px;
font-weight: 800;
}
.query-input {
min-height: 88px;
margin-top: 10px;
}
.topic-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.topic-button {
min-height: 34px;
padding: 0 10px;
border: 1px solid #cbd5e1;
background: #ffffff;
color: #475569;
font-size: 12px;
}
.topic-button.active {
border-color: #e60012;
background: #fff1f2;
color: #b91c1c;
}
.generate-button {
width: 100%;
margin-top: 16px;
}
.answer-text {
display: block;
margin-top: 12px;
color: #172033;
font-size: 15px;
line-height: 1.7;
white-space: pre-wrap;
}
.answer-section {
margin-top: 18px;
padding-top: 14px;
border-top: 1px solid #e2e8f0;
}
.section-title {
color: #172033;
font-size: 14px;
font-weight: 800;
}
.point-row {
display: flex;
gap: 10px;
margin-top: 10px;
align-items: flex-start;
}
.point-index {
display: flex;
width: 22px;
height: 22px;
flex: 0 0 22px;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f1f5f9;
color: #475569;
font-size: 11px;
font-weight: 800;
}
.point-text,
.caution-text {
color: #334155;
font-size: 14px;
line-height: 1.6;
}
.caution-text {
display: block;
margin-top: 8px;
}
.reference-row {
display: flex;
gap: 8px;
margin-top: 10px;
align-items: flex-start;
}
.reference-dot {
color: #b91c1c;
font-weight: 800;
}
.reference-title {
overflow-wrap: anywhere;
color: #475569;
font-size: 13px;
line-height: 1.5;
}
.empty-state {
padding: 56px 24px;
text-align: center;
}
.empty-title {
display: block;
color: #172033;
font-size: 17px;
font-weight: 800;
}
.empty-desc {
display: block;
margin-top: 8px;
color: #64748b;
font-size: 13px;
line-height: 1.55;
}
</style>