- 输入侧:编辑消息不再静默销毁草稿(备份/回填)、补全浮层滚随、贴图 loading 态+并发上限复核、mention 失焦收口、富文本图文同粘、上传失败提示 - 消息流:图片点击 lightbox 放大、复制钮 focus 可见、复制为图片降级文案据实、JSON 块恢复可选、bucketSig 改哈希防长会话卡顿、user 长消息折叠、工具卡流式滚尾 - 会话框架:后台任务面板入点外/Esc 收起编排、恢复任务行级 busy+失败 toast、菜单底部翻转、搜索态统一清、归档禁改名对齐、listbox 语义+方向键、状态点 title、窄侧栏容器查询 - 跨端:删除目标不再抹其余目标完成态(前后端 GoalEntry 全量传递+失败回滚) - 边界条:MESSAGE_CAP 滚动淘汰不再静默,头部一键补齐完整会话,历史定位文案据实区分
76 lines
2.3 KiB
Vue
76 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
//! 图片输入预览 — 待发送图片缩略图列表,从 ChatInput.vue 提取
|
|
//!
|
|
//! 职责:显示待发送图片缩略图、移除按钮。不含图片读取/粘贴/拖拽逻辑。
|
|
//! 2026-08-06:支持 url 模式(文件服务上传)与 dataUrl 模式(base64)双预览。
|
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
const { t } = useI18n()
|
|
|
|
defineProps<{
|
|
/** 待发送图片列表(url 模式用 url 预览,base64 模式用 dataUrl;uploading=上传中占位 chip) */
|
|
pendingImages: { id: string; dataUrl?: string; url?: string; mediaType: string; uploading?: boolean }[]
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'remove', id: string): void
|
|
}>()
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="pendingImages.length" class="ai-img-preview-row">
|
|
<div v-for="img in pendingImages" :key="img.id" class="ai-img-preview">
|
|
<!-- 走查:上传中占位 chip——url/dataUrl 未定时渲染 loading 态,不渲染空 src <img> -->
|
|
<div
|
|
v-if="img.uploading && !img.url && !img.dataUrl"
|
|
class="ai-img-preview-thumb ai-img-thumb--uploading"
|
|
>
|
|
<span class="ai-thumb-spinner" aria-hidden="true"></span>
|
|
<span class="ai-thumb-uploading-text">{{ t('aiChat.imageUploading') }}</span>
|
|
</div>
|
|
<img
|
|
v-else
|
|
:src="img.url || img.dataUrl"
|
|
:alt="t('aiChat.imagePreviewAlt')"
|
|
class="ai-img-preview-thumb"
|
|
loading="lazy"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="ai-img-preview-x"
|
|
:title="t('aiChat.removeImage')"
|
|
:aria-label="t('aiChat.removeImage')"
|
|
@click="emit('remove', img.id)"
|
|
>×</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* 走查:上传中占位态(与缩略图同尺寸盒,转圈+文案)。仅本组件消费的私有态样式,不入公共库 */
|
|
.ai-img-thumb--uploading {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 3px;
|
|
background: var(--df-bg-card-hover);
|
|
}
|
|
.ai-thumb-spinner {
|
|
width: 12px;
|
|
height: 12px;
|
|
border: 1.5px solid var(--df-border);
|
|
border-top-color: var(--df-accent);
|
|
border-radius: 50%;
|
|
animation: ai-thumb-spin 0.8s linear infinite;
|
|
}
|
|
@keyframes ai-thumb-spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
.ai-thumb-uploading-text {
|
|
font-size: 9px;
|
|
color: var(--df-text-dim);
|
|
}
|
|
</style>
|