forked from natuka/web.puabadge.com
修改:京东demo页面编辑效果图流程调整
This commit is contained in:
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 {
|
||||
|
||||
@@ -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
|
||||
// 先记下接口值;等会话历史回来后再按已生成次数扣除
|
||||
|
||||
Reference in new issue
Block a user