feat: complete grounded digital avatar chat experience

This commit is contained in:
stefanfeng
2026-08-19 14:21:56 +08:00
parent 07d4a21379
commit 4a2d788e85
16 changed files with 1013 additions and 193 deletions
+62 -12
View File
@@ -3,8 +3,8 @@
<!-- 顶部导航 -->
<header class="page-header">
<button class="back-btn" @click="goBack">‹</button>
<h1 class="page-title">形象微调编辑</h1>
<button class="save-btn" :disabled="loading || saving" @click="saveChanges">
<h1 class="page-title">分身微调</h1>
<button class="save-btn" :disabled="loading || saving || uploadingPhoto" @click="saveChanges">
{{ saving ? '保存中...' : '保存' }}
</button>
</header>
@@ -14,13 +14,15 @@
<!-- 头像预览 -->
<section class="photo-section">
<div class="photo-container">
<label class="photo-container" for="avatar-photo-input">
<div class="photo-preview">
<img v-if="formData.photoUrl" :src="formData.photoUrl" alt="" class="photo-image" referrerpolicy="no-referrer" />
<div v-else class="photo-placeholder">🤖</div>
<span class="photo-edit-mark">更换</span>
</div>
<span class="photo-hint">可直接修改下方头像链接</span>
</div>
<span class="photo-hint">{{ uploadingPhoto ? '头像上传中...' : '点击头像上传新图片' }}</span>
</label>
<input id="avatar-photo-input" class="photo-input" type="file" accept="image/jpeg,image/png,image/webp,image/gif" :disabled="uploadingPhoto" @change="selectPhoto" />
</section>
<!-- 基本信息表单 -->
@@ -56,12 +58,23 @@
</div>
<div class="form-item">
<label class="form-label">头像链接</label>
<input
v-model="formData.photoUrl"
class="form-input"
placeholder="请输入头像图片 URL"
/>
<label class="form-label">职业</label>
<input v-model="formData.profession" class="form-input" placeholder="例如:医生、律师、产品经理" />
</div>
<div class="form-item">
<label class="form-label">职位</label>
<input v-model="formData.position" class="form-input" placeholder="例如:主任医师、部门负责人" />
</div>
<div class="form-item">
<label class="form-label">单位</label>
<input v-model="formData.organization" class="form-input" placeholder="请输入所在单位" />
</div>
<div class="form-item">
<label class="form-label">单位地址</label>
<input v-model="formData.organizationAddress" class="form-input" placeholder="请输入单位详细地址" />
</div>
<div class="form-item">
@@ -152,7 +165,7 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { deleteAvatar as apiDeleteAvatar, getAvatarDetail, updateAvatar } from '@/api'
import { deleteAvatar as apiDeleteAvatar, getAvatarDetail, updateAvatar, uploadAvatarPhoto } from '@/api'
import { useAvatarStore } from '@/store/avatar'
import { buildAvatarUpdatePayload, normalizeAvatarEditForm } from '@/utils/avatar-page-data.js'
@@ -174,6 +187,10 @@ const formData = reactive({
humor: 30,
responseLength: 'medium',
systemPrompt: '',
profession: '',
position: '',
organization: '',
organizationAddress: '',
autoReply: true
})
@@ -185,6 +202,7 @@ const responseLengths = [
const loading = ref(true)
const saving = ref(false)
const uploadingPhoto = ref(false)
const deleting = ref(false)
const errorMsg = ref('')
@@ -201,6 +219,23 @@ const loadAvatar = async () => {
}
}
const selectPhoto = async (event: Event) => {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
input.value = ''
if (!file || uploadingPhoto.value) return
uploadingPhoto.value = true
errorMsg.value = ''
try {
const result = await uploadAvatarPhoto(avatarId, file)
formData.photoUrl = result.photoUrl
} catch (e: any) {
errorMsg.value = e?.message || '头像上传失败,请重试'
} finally {
uploadingPhoto.value = false
}
}
// 保存修改
const saveChanges = async () => {
if (loading.value || saving.value) return
@@ -323,6 +358,7 @@ onMounted(async () => {
}
.photo-preview {
position: relative;
width: 100px;
height: 100px;
border-radius: 50%;
@@ -331,6 +367,7 @@ onMounted(async () => {
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.3);
overflow: hidden;
}
.photo-image {
@@ -349,6 +386,19 @@ onMounted(async () => {
font-weight: 500;
}
.photo-input { display: none; }
.photo-edit-mark {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 5px 0 7px;
color: white;
background: rgba(47, 26, 16, .68);
font-size: 12px;
text-align: center;
}
/* 表单区域 */
.form-section {
padding: 20px;