334 lines
7.7 KiB
Vue
334 lines
7.7 KiB
Vue
<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>
|