forked from natuka/web.puabadge.com
修改:京东demo页面编辑效果图流程调整
This commit is contained in:
1 parent
e1da41c2f8
commit
0199b924ac
3 files changed
+87
-127
No files matched your search
+14
-31
@@ -98,25 +98,15 @@ export interface UploadTicket {
|
|||||||
|
|
||||||
export interface ImageGenSubmitInput {
|
export interface ImageGenSubmitInput {
|
||||||
session_id: string
|
session_id: string
|
||||||
upload_id: string
|
/** 导入生图传 upload_id;编辑效果图不传 */
|
||||||
product_id: number
|
upload_id?: string
|
||||||
product_name?: string
|
/** 编辑效果图传 image_id;导入生图不传 */
|
||||||
style_id?: number
|
image_id?: string
|
||||||
prompt?: string
|
prompt?: string
|
||||||
n?: number
|
|
||||||
mounts_id?: number
|
|
||||||
scene_prop?: string
|
scene_prop?: string
|
||||||
custom_text?: string
|
custom_text?: string
|
||||||
base_type?: number
|
|
||||||
frame_type?: number
|
|
||||||
mount_id?: number
|
|
||||||
pid?: number
|
|
||||||
proportion_id?: number
|
|
||||||
pose_id?: number
|
|
||||||
/** 效果图编辑 edit;原图编辑 / 普通立即生成 generate */
|
/** 效果图编辑 edit;原图编辑 / 普通立即生成 generate */
|
||||||
mode?: 'generate' | 'edit'
|
mode?: 'generate' | 'edit'
|
||||||
/** 主体数量文案,来自 orders/stat.subject_name,如「1人」「2人+1宠物」 */
|
|
||||||
subject_name?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImageGenImage {
|
export interface ImageGenImage {
|
||||||
@@ -773,28 +763,21 @@ export const classifySubject = (input: {
|
|||||||
|
|
||||||
/** POST /jd/imagegen/submit */
|
/** POST /jd/imagegen/submit */
|
||||||
export const submitImageGen = (input: ImageGenSubmitInput) => {
|
export const submitImageGen = (input: ImageGenSubmitInput) => {
|
||||||
return request(`${PREFIX}/imagegen/submit`, {
|
const data: Record<string, string> = {
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
session_id: input.session_id,
|
session_id: input.session_id,
|
||||||
upload_id: input.upload_id,
|
|
||||||
product_id: input.product_id,
|
|
||||||
product_name: input.product_name || '',
|
|
||||||
style_id: input.style_id ?? 0,
|
|
||||||
prompt: input.prompt || '',
|
prompt: input.prompt || '',
|
||||||
n: input.n ?? 1,
|
|
||||||
mounts_id: input.mounts_id ?? 0,
|
|
||||||
scene_prop: input.scene_prop || '',
|
scene_prop: input.scene_prop || '',
|
||||||
custom_text: input.custom_text || '',
|
custom_text: input.custom_text || '',
|
||||||
base_type: input.base_type ?? 0,
|
|
||||||
frame_type: input.frame_type ?? 0,
|
|
||||||
mount_id: input.mount_id ?? 0,
|
|
||||||
pid: input.pid ?? 0,
|
|
||||||
proportion_id: input.proportion_id ?? 0,
|
|
||||||
pose_id: input.pose_id ?? 0,
|
|
||||||
mode: input.mode || 'generate',
|
mode: input.mode || 'generate',
|
||||||
subject_name: input.subject_name || '',
|
}
|
||||||
},
|
const uploadId = String(input.upload_id || '').trim()
|
||||||
|
const imageId = String(input.image_id || '').trim()
|
||||||
|
// 导入生图:upload_id;编辑效果图:image_id(互斥)
|
||||||
|
if (uploadId) data.upload_id = uploadId
|
||||||
|
if (imageId) data.image_id = imageId
|
||||||
|
return request(`${PREFIX}/imagegen/submit`, {
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
}) as Promise<ImageGenTask>
|
}) as Promise<ImageGenTask>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,13 +95,13 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
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'
|
import type { ProductTitleSpec } from '@/utils/jingdong-product-title'
|
||||||
|
|
||||||
export type ComposerTag = {
|
export type ComposerTag = {
|
||||||
key: string
|
key: string
|
||||||
label: string
|
label: string
|
||||||
icon?: 'user' | 'star' | 'present'
|
icon?: 'lock'
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -162,9 +162,7 @@ const titleInputId = 'jd-composer-product-title'
|
|||||||
const productTitleLength = computed(() => String(props.productTitle || '').length)
|
const productTitleLength = computed(() => String(props.productTitle || '').length)
|
||||||
|
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
user: User,
|
lock: Lock,
|
||||||
star: Star,
|
|
||||||
present: Present,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagItems = computed(() =>
|
const tagItems = computed(() =>
|
||||||
@@ -173,7 +171,7 @@ const tagItems = computed(() =>
|
|||||||
.map((tag) => ({
|
.map((tag) => ({
|
||||||
key: tag.key || tag.label,
|
key: tag.key || tag.label,
|
||||||
label: String(tag.label).trim(),
|
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;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer__send {
|
.composer__send {
|
||||||
|
|||||||
@@ -252,14 +252,15 @@ const messages = ref<ChatMessage[]>(createBaseMessages())
|
|||||||
|
|
||||||
const composerTags = computed<ComposerTag[]>(() => {
|
const composerTags = computed<ComposerTag[]>(() => {
|
||||||
const tags: ComposerTag[] = []
|
const tags: ComposerTag[] = []
|
||||||
|
// 顺序:主体 → 产品 → 风格;图标统一锁
|
||||||
if (subjectName.value) {
|
if (subjectName.value) {
|
||||||
tags.push({ key: 'subject', label: subjectName.value, icon: 'user' })
|
tags.push({ key: 'subject', label: subjectName.value, icon: 'lock' })
|
||||||
}
|
|
||||||
if (styleName.value) {
|
|
||||||
tags.push({ key: 'style', label: styleName.value, icon: 'star' })
|
|
||||||
}
|
}
|
||||||
if (typeName.value) {
|
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
|
return tags
|
||||||
})
|
})
|
||||||
@@ -353,14 +354,13 @@ const canSend = computed(() => {
|
|||||||
if (isBusy.value || uploading.value || inspecting.value) return false
|
if (isBusy.value || uploading.value || inspecting.value) return false
|
||||||
if (remainCount.value <= 0) return false
|
if (remainCount.value <= 0) return false
|
||||||
if (!hasRequiredTitle.value) return false
|
if (!hasRequiredTitle.value) return false
|
||||||
// 上传未完成时不可发(避免缺少 upload_id)
|
|
||||||
if (pendingPreview.value && !pendingUploadId.value) return false
|
|
||||||
if (isEditing.value) {
|
if (isEditing.value) {
|
||||||
// 编辑:需有飞入的效果图 + 调整文案
|
// 编辑:效果图预览 + 调整文案(不走 upload,用结果卡 image_id)
|
||||||
return !!pendingPreview.value && draft.value.trim().length > 0
|
return !!pendingPreview.value && draft.value.trim().length > 0
|
||||||
}
|
}
|
||||||
|
// 导入生图:上传未完成时不可发(避免缺少 upload_id)
|
||||||
|
if (pendingPreview.value && !pendingUploadId.value) return false
|
||||||
// 主体不一致只提示,不拦截发送
|
// 主体不一致只提示,不拦截发送
|
||||||
// 首轮生图需上传照片(对齐 agent:upload → submit → poll)
|
|
||||||
return !!pendingPreview.value
|
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) {
|
function onFileChange(e: Event) {
|
||||||
const input = e.target as HTMLInputElement
|
const input = e.target as HTMLInputElement
|
||||||
const file = input.files?.[0]
|
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')
|
const msg = messages.value.find((m) => m.id === resultId && m.type === 'result')
|
||||||
if (!msg || msg.type !== 'result' || !msg.image) return
|
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
|
editingResultId.value = resultId
|
||||||
// 保留用户已输入的提示词,不清空 draft
|
// 保留用户已输入的提示词,不清空 draft
|
||||||
clearPendingPreview()
|
clearPendingPreview()
|
||||||
|
|
||||||
// 先放入预览(飞入过程中隐藏),并准备重新上传用的文件
|
|
||||||
pendingPreview.value = msg.image
|
pendingPreview.value = msg.image
|
||||||
subjectMatched.value = true
|
subjectMatched.value = true
|
||||||
pendingUploadId.value = ''
|
pendingUploadId.value = ''
|
||||||
void fileFromImageUrl(msg.image)
|
pendingFile.value = null
|
||||||
.then((file) => {
|
|
||||||
if (editingResultId.value !== resultId) return
|
|
||||||
pendingFile.value = file
|
|
||||||
void startPendingUploadAndClassify(file)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
if (editingResultId.value === resultId) {
|
|
||||||
showToast(friendlyError(error, 'common', '效果图加载失败,请重试'))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
const thumb = composerRef.value?.getPreviewEl()
|
const thumb = composerRef.value?.getPreviewEl()
|
||||||
@@ -698,17 +685,6 @@ function resetComposerAfterSubmit(options?: { keepObjectUrl?: boolean }) {
|
|||||||
clearPendingPreview(options)
|
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) {
|
function patchGeneratingResult(generatingId: string, resultUrl: string) {
|
||||||
const idx = messages.value.findIndex((m) => m.id === generatingId && m.type === 'generating')
|
const idx = messages.value.findIndex((m) => m.id === generatingId && m.type === 'generating')
|
||||||
if (idx < 0) return
|
if (idx < 0) return
|
||||||
@@ -786,8 +762,10 @@ async function startGenerate(opts: {
|
|||||||
userText: string
|
userText: string
|
||||||
userImage?: string
|
userImage?: string
|
||||||
mode: 'generate' | 'edit'
|
mode: 'generate' | 'edit'
|
||||||
/** 导入阶段已拿到的 upload_id;提交只 confirm,不再调 upload/url */
|
/** 导入生图:confirm + submit 用 upload_id */
|
||||||
uploadId: string
|
uploadId?: string
|
||||||
|
/** 编辑效果图:直接 submit,传 image_id,不走 url/confirm */
|
||||||
|
imageId?: string
|
||||||
sceneProp?: string
|
sceneProp?: string
|
||||||
}) {
|
}) {
|
||||||
if (remainCount.value <= 0) {
|
if (remainCount.value <= 0) {
|
||||||
@@ -801,17 +779,16 @@ async function startGenerate(opts: {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const productId = resolveProductId()
|
const isEdit = opts.mode === 'edit'
|
||||||
if (!productId) {
|
const uploadId = String(opts.uploadId || '').trim()
|
||||||
showToast(friendlyError('缺少产品信息,请刷新后重试', 'common'))
|
const editImageId = String(opts.imageId || '').trim()
|
||||||
|
if (isEdit) {
|
||||||
|
if (!editImageId) {
|
||||||
|
showToast(config.messages.effectPreparing)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else if (!uploadId) {
|
||||||
const uploadId = String(opts.uploadId || '').trim()
|
showToast(config.messages.needPhoto)
|
||||||
if (!uploadId) {
|
|
||||||
showToast(
|
|
||||||
opts.mode === 'edit' ? config.messages.effectPreparing : config.messages.needPhoto,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,31 +818,25 @@ async function startGenerate(opts: {
|
|||||||
|
|
||||||
isBusy.value = true
|
isBusy.value = true
|
||||||
pendingFinish.value = null
|
pendingFinish.value = null
|
||||||
lastUploadId.value = uploadId
|
if (uploadId) lastUploadId.value = uploadId
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 提交阶段:仅 confirm → imagegen/submit(upload/url 已在导入时完成)
|
// 导入生图:confirm → submit(upload_id);编辑:直接 submit(image_id)
|
||||||
|
if (!isEdit) {
|
||||||
await jingdongApi.confirmUpload({
|
await jingdongApi.confirmUpload({
|
||||||
session_id: sid,
|
session_id: sid,
|
||||||
upload_id: uploadId,
|
upload_id: uploadId,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const styleId = resolveStyleId()
|
|
||||||
const sceneProp = String(opts.sceneProp || '').trim()
|
const sceneProp = String(opts.sceneProp || '').trim()
|
||||||
const task = await jingdongApi.submitImageGen({
|
const task = await jingdongApi.submitImageGen({
|
||||||
session_id: sid,
|
session_id: sid,
|
||||||
upload_id: uploadId,
|
...(isEdit ? { image_id: editImageId } : { upload_id: uploadId }),
|
||||||
product_id: productId,
|
|
||||||
product_name: String(orderStat.value?.prod_name || orderStat.value?.product_name || ''),
|
|
||||||
style_id: styleId,
|
|
||||||
prompt: opts.userText,
|
prompt: opts.userText,
|
||||||
n: 1,
|
|
||||||
mode: opts.mode,
|
mode: opts.mode,
|
||||||
scene_prop: sceneProp,
|
scene_prop: sceneProp,
|
||||||
// 主体数量:沿用 orders/stat.subject_name
|
custom_text: '',
|
||||||
subject_name:
|
|
||||||
subjectNameFromStat.value ||
|
|
||||||
String(orderStat.value?.subject_name || '').trim(),
|
|
||||||
})
|
})
|
||||||
let taskId = task.task_id
|
let taskId = task.task_id
|
||||||
if (!taskId) throw new Error('提交生成失败')
|
if (!taskId) throw new Error('提交生成失败')
|
||||||
@@ -881,7 +852,7 @@ async function startGenerate(opts: {
|
|||||||
resultUrl,
|
resultUrl,
|
||||||
taskId,
|
taskId,
|
||||||
imageId,
|
imageId,
|
||||||
uploadId,
|
uploadId: uploadId || '',
|
||||||
startedAt,
|
startedAt,
|
||||||
}
|
}
|
||||||
patchGeneratingResult(generatingId, resultUrl)
|
patchGeneratingResult(generatingId, resultUrl)
|
||||||
@@ -894,8 +865,8 @@ async function startGenerate(opts: {
|
|||||||
isBusy.value = false
|
isBusy.value = false
|
||||||
const tip = friendlyError(
|
const tip = friendlyError(
|
||||||
error,
|
error,
|
||||||
opts.mode === 'edit' ? 'edit' : 'generate',
|
isEdit ? 'edit' : 'generate',
|
||||||
opts.mode === 'edit' ? config.messages.editFail : config.messages.generateFail,
|
isEdit ? config.messages.editFail : config.messages.generateFail,
|
||||||
)
|
)
|
||||||
showToast({ message: tip, duration: 2800 })
|
showToast({ message: tip, duration: 2800 })
|
||||||
// 对话里留下可读失败提示,避免用户错过 Toast
|
// 对话里留下可读失败提示,避免用户错过 Toast
|
||||||
@@ -914,18 +885,6 @@ function onSend() {
|
|||||||
showToast(config.messages.generatingTip)
|
showToast(config.messages.generatingTip)
|
||||||
return
|
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) {
|
if (remainCount.value <= 0) {
|
||||||
showToast(config.messages.noRemain)
|
showToast(config.messages.noRemain)
|
||||||
@@ -942,13 +901,6 @@ function onSend() {
|
|||||||
const text = draft.value.trim()
|
const text = draft.value.trim()
|
||||||
const sceneProp = resolveScenePropFromOrder(orderStat.value, productTitle.value)
|
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 (isEditing.value) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
showToast(config.messages.needEditText)
|
showToast(config.messages.needEditText)
|
||||||
@@ -959,17 +911,43 @@ function onSend() {
|
|||||||
showToast(config.messages.effectPreparing)
|
showToast(config.messages.effectPreparing)
|
||||||
return
|
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()
|
resetComposerAfterSubmit()
|
||||||
// 编辑提交:右侧气泡只显示提示词(与 session/detail 一致)
|
// 编辑:不走 url/confirm,submit 只传 image_id
|
||||||
void startGenerate({
|
void startGenerate({
|
||||||
userText: text,
|
userText: text,
|
||||||
mode: 'edit',
|
mode: 'edit',
|
||||||
uploadId,
|
imageId,
|
||||||
sceneProp,
|
sceneProp,
|
||||||
})
|
})
|
||||||
return
|
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
|
const image = pendingPreview.value
|
||||||
if (!image) {
|
if (!image) {
|
||||||
showToast(config.messages.needPhoto)
|
showToast(config.messages.needPhoto)
|
||||||
@@ -1067,7 +1045,8 @@ function fetchOrderStat() {
|
|||||||
subjectNameFromStat.value = String(res?.subject_name || '').trim()
|
subjectNameFromStat.value = String(res?.subject_name || '').trim()
|
||||||
subjectName.value = subjectNameFromStat.value
|
subjectName.value = subjectNameFromStat.value
|
||||||
styleName.value = String(res?.style_name || '').trim()
|
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()
|
sessionId.value = String(res?.session_id || '').trim()
|
||||||
subjectMatched.value = true
|
subjectMatched.value = true
|
||||||
// 先记下接口值;等会话历史回来后再按已生成次数扣除
|
// 先记下接口值;等会话历史回来后再按已生成次数扣除
|
||||||
|
|||||||
Reference in new issue
Block a user