Files
huihuiSquare/digital-avatar-app/src/views/QaPairEdit.vue
T

309 lines
6.2 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>
<div class="qa-edit-page">
<header class="page-header">
<div class="header-left">
<button class="back-btn" @click="goBack">‹</button>
<h1 class="page-title">{{ isEdit ? '编辑问答对' : '添加问答对' }}</h1>
</div>
</header>
<section class="form-section">
<label class="field-label">问题</label>
<textarea
v-model="form.question"
class="field-input"
rows="3"
placeholder="例如:你们的退款政策是什么?"
></textarea>
<label class="field-label">标准答案</label>
<textarea
v-model="form.answer"
class="field-input"
rows="5"
placeholder="输入该问题的标准回答"
></textarea>
<div class="switch-row">
<div class="switch-text">
<span class="switch-title">启用</span>
<span class="switch-desc">关闭后该问答对不参与分身作答</span>
</div>
<label class="switch">
<input type="checkbox" v-model="form.enabled" />
<span class="slider"></span>
</label>
</div>
<p v-if="error" class="error-text">{{ error }}</p>
<div class="actions">
<button class="btn-cancel" @click="goBack">取消</button>
<button class="btn-save" :disabled="saving" @click="save">保存</button>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAvatarStore } from '@/store/avatar'
import { pickScopedAvatarId, unwrapListData } from '@/utils/avatar-page-data.js'
import { getQAPairs, createQAPair, updateQAPair } from '@/api'
const router = useRouter()
const route = useRoute()
const store = useAvatarStore()
const avatarId = computed(() => pickScopedAvatarId(route.params.avatarId, store.currentAvatarId, store.avatars))
const qaId = computed(() => (route.params.qaId as string) || null)
const isEdit = computed(() => !!qaId.value)
const form = reactive({ question: '', answer: '', enabled: true })
const saving = ref(false)
const error = ref('')
const goBack = () => router.back()
const loadForEdit = async () => {
if (!avatarId.value || !qaId.value) return
try {
const res: any = await getQAPairs(avatarId.value)
const list: any[] = unwrapListData(res)
const target = list.find((q) => q.id === qaId.value)
if (target) {
form.question = target.question
form.answer = target.answer
form.enabled = target.enabled !== false
}
} catch (e) {
console.error(e)
}
}
const save = async () => {
error.value = ''
if (!avatarId.value) {
error.value = '请先创建数字分身'
return
}
if (!form.question.trim() || !form.answer.trim()) {
error.value = '请填写问题和答案'
return
}
saving.value = true
try {
const payload = {
question: form.question.trim(),
answer: form.answer.trim(),
enabled: form.enabled
}
if (isEdit.value && qaId.value) {
await updateQAPair(avatarId.value, qaId.value, payload)
} else {
await createQAPair(avatarId.value, payload)
}
if (route.params.avatarId) {
router.replace({ name: 'AvatarKnowledgeManage', params: { avatarId: avatarId.value } })
} else {
router.replace('/knowledge')
}
} catch (e: any) {
error.value = e?.message || '保存失败'
} finally {
saving.value = false
}
}
onMounted(async () => {
if (!store.avatars.length) {
await store.loadAvatars()
}
if (avatarId.value) store.currentAvatarId = avatarId.value
if (isEdit.value) {
await loadForEdit()
}
})
</script>
<style scoped>
.qa-edit-page {
min-height: 100vh;
background: #F8F9FA;
padding-bottom: 40px;
}
.page-header {
display: flex;
align-items: center;
padding: 16px 20px;
background: linear-gradient(135deg, #F97316 0%, #FB923C 100%);
color: white;
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.back-btn {
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
padding: 4px;
}
.page-title {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.form-section {
padding: 20px;
display: flex;
flex-direction: column;
gap: 8px;
}
.field-label {
font-size: 14px;
font-weight: 600;
color: #18191C;
margin-top: 8px;
}
.field-input {
width: 100%;
border: 1px solid #E5E7EB;
border-radius: 8px;
padding: 10px 12px;
font-size: 14px;
color: #18191C;
resize: vertical;
font-family: inherit;
box-sizing: border-box;
}
.field-input:focus {
outline: none;
border-color: #F97316;
}
.switch-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 12px;
padding: 12px 14px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.switch-text {
display: flex;
flex-direction: column;
gap: 2px;
}
.switch-title {
font-size: 14px;
font-weight: 600;
color: #18191C;
}
.switch-desc {
font-size: 12px;
color: #9398AE;
}
/* 开关 */
.switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
flex-shrink: 0;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
inset: 0;
background: #E5E7EB;
border-radius: 24px;
transition: background 0.2s;
}
.slider::before {
content: "";
position: absolute;
height: 18px;
width: 18px;
left: 3px;
top: 3px;
background: white;
border-radius: 50%;
transition: transform 0.2s;
}
.switch input:checked + .slider {
background: #F97316;
}
.switch input:checked + .slider::before {
transform: translateX(20px);
}
.error-text {
font-size: 13px;
color: #EF4444;
margin: 4px 0 0;
}
.actions {
display: flex;
gap: 12px;
margin-top: 20px;
}
.btn-cancel {
flex: 1;
padding: 12px;
background: #F3F4F6;
color: #6B7280;
border: none;
border-radius: 10px;
font-size: 15px;
cursor: pointer;
}
.btn-save {
flex: 1;
padding: 12px;
background: #F97316;
color: white;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
}
.btn-save:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>