Files
wecom_it_smart_desk/frontend-h5/src/components/chat/EvaluationDialog.vue
T

342 lines
7.9 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.
<!--
企微IT智能服务台 H5用户端满意度评价弹窗
说明会话结束后弹出的满意度评价对话框
功能
1. 5星评分必选
2. 表情选择满意/一般/不满意必选
3. 文字反馈输入框可选限200字
4. 提交评价
-->
<template>
<van-popup
v-model:show="visible"
round
:close-on-click-overlay="false"
class="evaluation-popup"
>
<div class="evaluation-dialog">
<div class="evaluation-dialog__header">
<h3 class="evaluation-dialog__title">请对本次服务进行评价</h3>
<p class="evaluation-dialog__subtitle">您的评价对我们非常重要</p>
</div>
<div class="evaluation-dialog__section">
<div class="evaluation-dialog__label">服务评分</div>
<div class="star-rating">
<van-rate
v-model="formData.star_rating"
:count="5"
size="32"
color="#FFD21E"
void-color="#E5E5E5"
@change="handleStarChange"
/>
<span class="star-label">{{ starLabel }}</span>
</div>
</div>
<div class="evaluation-dialog__section">
<div class="evaluation-dialog__label">整体感受</div>
<div class="emoji-selector">
<div class="emoji-item" :class="{ 'emoji-item--selected': formData.emoji === 'satisfied' }" @click="selectEmoji('satisfied')">
<span class="emoji-icon">😀</span>
<span class="emoji-text">满意</span>
</div>
<div class="emoji-item" :class="{ 'emoji-item--selected': formData.emoji === 'neutral' }" @click="selectEmoji('neutral')">
<span class="emoji-icon">😐</span>
<span class="emoji-text">一般</span>
</div>
<div class="emoji-item" :class="{ 'emoji-item--selected': formData.emoji === 'dissatisfied' }" @click="selectEmoji('dissatisfied')">
<span class="emoji-icon">😞</span>
<span class="emoji-text">不满意</span>
</div>
</div>
</div>
<div class="evaluation-dialog__section">
<div class="evaluation-dialog__label">
改进建议
<span class="evaluation-dialog__optional">选填</span>
</div>
<van-field
v-model="formData.feedback_text"
type="textarea"
placeholder="您对本次服务有什么建议?"
maxlength="200"
show-word-limit
rows="3"
class="evaluation-dialog__textarea"
/>
</div>
<div class="evaluation-dialog__actions">
<van-button
type="primary"
size="large"
:loading="submitting"
:disabled="!canSubmit"
@click="handleSubmit"
>
提交评价
</van-button>
</div>
<div class="evaluation-dialog__skip">
<van-button size="small" type="default" :disabled="submitting" @click="handleSkip">
暂不评价
</van-button>
</div>
</div>
</van-popup>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { showToast } from 'vant'
import { submitEvaluation, getEvaluation } from '@/api/conversation'
interface Props {
modelValue: boolean
conversationId: string
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
conversationId: '',
})
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'submitted'): void
}>()
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
})
const formData = ref({
star_rating: 0,
emoji: '',
feedback_text: '',
})
const submitting = ref(false)
const starLabel = computed(() => {
const labels: Record<number, string> = {
0: '',
1: '非常差',
2: '较差',
3: '一般',
4: '满意',
5: '非常满意',
}
return labels[formData.value.star_rating] || ''
})
const canSubmit = computed(() => {
return formData.value.star_rating > 0 && formData.value.emoji !== ''
})
watch(visible, async (val) => {
if (val && props.conversationId) {
try {
const evaluation = await getEvaluation(props.conversationId)
if (evaluation) {
visible.value = false
showToast('您已评价过该会话')
}
} catch (error) {
console.warn('[EvaluationDialog] 检查评价状态失败:', error)
}
}
})
function selectEmoji(emoji: string): void {
formData.value.emoji = emoji
}
function handleStarChange(value: number): void {
if (value >= 4) {
formData.value.emoji = 'satisfied'
} else if (value >= 2) {
formData.value.emoji = 'neutral'
} else {
formData.value.emoji = 'dissatisfied'
}
}
async function handleSubmit(): Promise<void> {
if (!canSubmit.value || submitting.value) return
submitting.value = true
try {
await submitEvaluation(props.conversationId, {
star_rating: formData.value.star_rating,
emoji: formData.value.emoji,
feedback_text: formData.value.feedback_text || undefined,
})
showToast('评价成功,感谢您的反馈!')
visible.value = false
emit('submitted')
} catch (error: any) {
console.error('[EvaluationDialog] 提交评价失败:', error)
// 修复:H5 拦截器已将错误对象格式化为 {code, message}
// 不再有 error.response.data.message 结构。
// 拦截器已在 showToast 中显示具体错误消息,此处不再重复。
} finally {
submitting.value = false
}
}
function handleSkip(): void {
visible.value = false
}
function resetForm(): void {
formData.value = {
star_rating: 0,
emoji: '',
feedback_text: '',
}
submitting.value = false
}
defineExpose({
resetForm,
})
</script>
<style scoped>
.evaluation-popup {
width: 90%;
max-width: 360px;
}
.evaluation-dialog {
padding: 24px 20px 20px;
}
.evaluation-dialog__header {
text-align: center;
margin-bottom: 20px;
}
.evaluation-dialog__title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary, #333);
margin: 0 0 6px;
}
.evaluation-dialog__subtitle {
font-size: 13px;
color: var(--text-tertiary, #999);
margin: 0;
}
.evaluation-dialog__section {
margin-bottom: 20px;
}
.evaluation-dialog__label {
font-size: 14px;
font-weight: 500;
color: var(--text-primary, #333);
margin-bottom: 10px;
display: flex;
align-items: center;
}
.evaluation-dialog__optional {
font-size: 12px;
font-weight: 400;
color: var(--text-tertiary, #999);
margin-left: 6px;
}
.star-rating {
display: flex;
align-items: center;
gap: 12px;
}
.star-label {
font-size: 14px;
color: var(--text-secondary, #666);
min-width: 48px;
}
.emoji-selector {
display: flex;
justify-content: space-between;
gap: 12px;
}
.emoji-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px;
background: var(--bg-tertiary, #f5f5f5);
border-radius: 12px;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.2s;
}
.emoji-item:hover {
background: var(--bg-secondary, #f0f0f0);
}
.emoji-item--selected {
background: var(--accent-soft, rgba(7, 193, 96, 0.1));
border-color: var(--accent, #07C160);
}
.emoji-icon {
font-size: 28px;
margin-bottom: 4px;
}
.emoji-text {
font-size: 12px;
color: var(--text-secondary, #666);
}
.emoji-item--selected .emoji-text {
color: var(--accent, #07C160);
font-weight: 500;
}
.evaluation-dialog__textarea {
background: var(--bg-tertiary, #f5f5f5);
border-radius: 8px;
padding: 12px;
}
.evaluation-dialog__actions {
margin-top: 24px;
}
.evaluation-dialog__actions .van-button--primary {
background: var(--accent, #07C160);
border-color: var(--accent, #07C160);
}
.evaluation-dialog__skip {
margin-top: 12px;
text-align: center;
}
.evaluation-dialog__skip .van-button--default {
color: var(--text-tertiary, #999);
border: none;
background: transparent;
}
</style>