修改:京东demo页面编辑效果图流程调整
continuous-integration/drone/push Build was killed
continuous-integration/drone/promote/prod Build is passing

This commit is contained in:
Linzm committed 2026-11-09 15:53:14 +08:00
1 parent e1da41c2f8
commit 0199b924ac
3 files changed
+95 -135

No files matched your search

@@ -95,13 +95,13 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { Close, EditPen, Loading, Picture, Present, Star, Top, User } from '@element-plus/icons-vue'
import { Close, EditPen, Loading, Lock, Picture, Top } from '@element-plus/icons-vue'
import type { ProductTitleSpec } from '@/utils/jingdong-product-title'
export type ComposerTag = {
key: string
label: string
icon?: 'user' | 'star' | 'present'
icon?: 'lock'
}
const props = withDefaults(
@@ -162,9 +162,7 @@ const titleInputId = 'jd-composer-product-title'
const productTitleLength = computed(() => String(props.productTitle || '').length)
const iconMap = {
user: User,
star: Star,
present: Present,
lock: Lock,
}
const tagItems = computed(() =>
@@ -173,7 +171,7 @@ const tagItems = computed(() =>
.map((tag) => ({
key: tag.key || tag.label,
label: String(tag.label).trim(),
icon: iconMap[tag.icon || 'present'] || Present,
icon: iconMap[tag.icon || 'lock'] || Lock,
})),
)
@@ -504,7 +502,7 @@ defineExpose({ focus, getPreviewEl, getFlyTargetEl })
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-overflow: clip;
}
.composer__send {
+73 -94
View File
@@ -252,14 +252,15 @@ const messages = ref<ChatMessage[]>(createBaseMessages())
const composerTags = computed<ComposerTag[]>(() => {
const tags: ComposerTag[] = []
// 顺序:主体 → 产品 → 风格;图标统一锁
if (subjectName.value) {
tags.push({ key: 'subject', label: subjectName.value, icon: 'user' })
}
if (styleName.value) {
tags.push({ key: 'style', label: styleName.value, icon: 'star' })
tags.push({ key: 'subject', label: subjectName.value, icon: 'lock' })
}
if (typeName.value) {
tags.push({ key: 'type', label: typeName.value, icon: 'present' })
tags.push({ key: 'type', label: typeName.value, icon: 'lock' })
}
if (styleName.value) {
tags.push({ key: 'style', label: styleName.value, icon: 'lock' })
}
return tags
})
@@ -353,14 +354,13 @@ const canSend = computed(() => {
if (isBusy.value || uploading.value || inspecting.value) return false
if (remainCount.value <= 0) return false
if (!hasRequiredTitle.value) return false
// 上传未完成时不可发(避免缺少 upload_id)
if (pendingPreview.value && !pendingUploadId.value) return false
if (isEditing.value) {
// 编辑:需有飞入的效果图 + 调整文案
// 编辑:效果图预览 + 调整文案(不走 upload,用结果卡 image_id)
return !!pendingPreview.value && draft.value.trim().length > 0
}
// 导入生图:上传未完成时不可发(避免缺少 upload_id)
if (pendingPreview.value && !pendingUploadId.value) return false
// 主体不一致只提示,不拦截发送
// 首轮生图需上传照片(对齐 agent:upload → submit → poll)
return !!pendingPreview.value
})
@@ -569,15 +569,6 @@ function onClearPreview() {
}
}
async function fileFromImageUrl(url: string, filename = 'effect.jpg') {
const res = await fetch(url)
if (!res.ok) throw new Error('效果图加载失败')
const blob = await res.blob()
const type = blob.type || 'image/jpeg'
const name = filename || (type.includes('png') ? 'effect.png' : 'effect.jpg')
return new File([blob], name, { type })
}
function onFileChange(e: Event) {
const input = e.target as HTMLInputElement
const file = input.files?.[0]
@@ -648,25 +639,21 @@ async function enterEdit(resultId: string, sourceRect?: FlyRect | null) {
const msg = messages.value.find((m) => m.id === resultId && m.type === 'result')
if (!msg || msg.type !== 'result' || !msg.image) return
// 编辑直接用效果图 image_id 调 submit,不再走 upload/url + confirm
const imageId = String(msg.imageId || '').trim()
if (!imageId) {
showToast(config.messages.effectPreparing)
return
}
editingResultId.value = resultId
// 保留用户已输入的提示词,不清空 draft
clearPendingPreview()
// 先放入预览(飞入过程中隐藏),并准备重新上传用的文件
pendingPreview.value = msg.image
subjectMatched.value = true
pendingUploadId.value = ''
void fileFromImageUrl(msg.image)
.then((file) => {
if (editingResultId.value !== resultId) return
pendingFile.value = file
void startPendingUploadAndClassify(file)
})
.catch((error) => {
if (editingResultId.value === resultId) {
showToast(friendlyError(error, 'common', '效果图加载失败,请重试'))
}
})
pendingFile.value = null
await nextTick()
const thumb = composerRef.value?.getPreviewEl()
@@ -698,17 +685,6 @@ function resetComposerAfterSubmit(options?: { keepObjectUrl?: boolean }) {
clearPendingPreview(options)
}
function resolveProductId() {
// submit 的 product_id 取自 orders/stat 的 type_id
const id = Number(orderStat.value?.type_id || 0)
return Number.isFinite(id) && id > 0 ? id : 0
}
function resolveStyleId() {
const id = Number(orderStat.value?.style_id || 0)
return Number.isFinite(id) && id > 0 ? id : 0
}
function patchGeneratingResult(generatingId: string, resultUrl: string) {
const idx = messages.value.findIndex((m) => m.id === generatingId && m.type === 'generating')
if (idx < 0) return
@@ -786,8 +762,10 @@ async function startGenerate(opts: {
userText: string
userImage?: string
mode: 'generate' | 'edit'
/** 导入阶段已拿到的 upload_id;提交只 confirm,不再调 upload/url */
uploadId: string
/** 导入生图:confirm + submit 用 upload_id */
uploadId?: string
/** 编辑效果图:直接 submit,传 image_id,不走 url/confirm */
imageId?: string
sceneProp?: string
}) {
if (remainCount.value <= 0) {
@@ -801,17 +779,16 @@ async function startGenerate(opts: {
return
}
const productId = resolveProductId()
if (!productId) {
showToast(friendlyError('缺少产品信息,请刷新后重试', 'common'))
return
}
const isEdit = opts.mode === 'edit'
const uploadId = String(opts.uploadId || '').trim()
if (!uploadId) {
showToast(
opts.mode === 'edit' ? config.messages.effectPreparing : config.messages.needPhoto,
)
const editImageId = String(opts.imageId || '').trim()
if (isEdit) {
if (!editImageId) {
showToast(config.messages.effectPreparing)
return
}
} else if (!uploadId) {
showToast(config.messages.needPhoto)
return
}
@@ -841,31 +818,25 @@ async function startGenerate(opts: {
isBusy.value = true
pendingFinish.value = null
lastUploadId.value = uploadId
if (uploadId) lastUploadId.value = uploadId
try {
// 提交阶段:仅 confirm → imagegen/submit(upload/url 已在导入时完成)
await jingdongApi.confirmUpload({
session_id: sid,
upload_id: uploadId,
})
// 导入生图:confirm → submit(upload_id);编辑:直接 submit(image_id)
if (!isEdit) {
await jingdongApi.confirmUpload({
session_id: sid,
upload_id: uploadId,
})
}
const styleId = resolveStyleId()
const sceneProp = String(opts.sceneProp || '').trim()
const task = await jingdongApi.submitImageGen({
session_id: sid,
upload_id: uploadId,
product_id: productId,
product_name: String(orderStat.value?.prod_name || orderStat.value?.product_name || ''),
style_id: styleId,
...(isEdit ? { image_id: editImageId } : { upload_id: uploadId }),
prompt: opts.userText,
n: 1,
mode: opts.mode,
scene_prop: sceneProp,
// 主体数量:沿用 orders/stat.subject_name
subject_name:
subjectNameFromStat.value ||
String(orderStat.value?.subject_name || '').trim(),
custom_text: '',
})
let taskId = task.task_id
if (!taskId) throw new Error('提交生成失败')
@@ -881,7 +852,7 @@ async function startGenerate(opts: {
resultUrl,
taskId,
imageId,
uploadId,
uploadId: uploadId || '',
startedAt,
}
patchGeneratingResult(generatingId, resultUrl)
@@ -894,8 +865,8 @@ async function startGenerate(opts: {
isBusy.value = false
const tip = friendlyError(
error,
opts.mode === 'edit' ? 'edit' : 'generate',
opts.mode === 'edit' ? config.messages.editFail : config.messages.generateFail,
isEdit ? 'edit' : 'generate',
isEdit ? config.messages.editFail : config.messages.generateFail,
)
showToast({ message: tip, duration: 2800 })
// 对话里留下可读失败提示,避免用户错过 Toast
@@ -914,18 +885,6 @@ function onSend() {
showToast(config.messages.generatingTip)
return
}
if (uploading.value) {
showToast(config.messages.uploadingTip)
return
}
if (inspecting.value) {
showToast(config.messages.inspectingTip)
return
}
if (pendingPreview.value && !pendingUploadId.value) {
showToast(config.messages.uploadingTip)
return
}
if (remainCount.value <= 0) {
showToast(config.messages.noRemain)
@@ -942,13 +901,6 @@ function onSend() {
const text = draft.value.trim()
const sceneProp = resolveScenePropFromOrder(orderStat.value, productTitle.value)
// 必须在 resetComposer 之前取出:reset 会清空 pendingUploadId
const uploadId = String(pendingUploadId.value || '').trim()
if (!uploadId) {
showToast(config.messages.uploadingTip)
return
}
if (isEditing.value) {
if (!text) {
showToast(config.messages.needEditText)
@@ -959,17 +911,43 @@ function onSend() {
showToast(config.messages.effectPreparing)
return
}
const editingMsg = messages.value.find(
(m) => m.id === editingResultId.value && m.type === 'result',
)
const imageId =
editingMsg && editingMsg.type === 'result'
? String(editingMsg.imageId || '').trim()
: ''
if (!imageId) {
showToast(config.messages.effectPreparing)
return
}
resetComposerAfterSubmit()
// 编辑提交:右侧气泡只显示提示词(与 session/detail 一致)
// 编辑:不走 url/confirm,submit 只传 image_id
void startGenerate({
userText: text,
mode: 'edit',
uploadId,
imageId,
sceneProp,
})
return
}
if (uploading.value) {
showToast(config.messages.uploadingTip)
return
}
if (inspecting.value) {
showToast(config.messages.inspectingTip)
return
}
// 必须在 resetComposer 之前取出:reset 会清空 pendingUploadId
const uploadId = String(pendingUploadId.value || '').trim()
if (!uploadId) {
showToast(config.messages.uploadingTip)
return
}
const image = pendingPreview.value
if (!image) {
showToast(config.messages.needPhoto)
@@ -1067,7 +1045,8 @@ function fetchOrderStat() {
subjectNameFromStat.value = String(res?.subject_name || '').trim()
subjectName.value = subjectNameFromStat.value
styleName.value = String(res?.style_name || '').trim()
typeName.value = String(res?.type_name || '').trim()
// 产品名称展示取 cate_name
typeName.value = String(res?.cate_name || '').trim()
sessionId.value = String(res?.session_id || '').trim()
subjectMatched.value = true
// 先记下接口值;等会话历史回来后再按已生成次数扣除