修改:京东下单页面样式与引导图、编辑购买交互优化
continuous-integration/drone/push Build was killed
continuous-integration/drone/promote/prod Build is passing

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
LinzmandCursor committed 2026-11-10 17:51:23 +08:00
1 parent 789d4f106c
commit 2f9d443a6a
6 files changed
+371 -256

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

+12 -5
View File
@@ -1,6 +1,7 @@
import logoAvatar from '@/assets/jingdong/logo-mark.png'
import logoIcon from '@/assets/jingdong/logo_1.png'
import userAvatar from '@/assets/jingdong/jingdong.png'
import guideImage from '@/assets/jingdong/yindao.png'
import caseOriginal from '@/assets/jingdong/原图.png'
import caseEffect from '@/assets/jingdong/效果图.png'
import caseScene from '@/assets/jingdong/场景图.png'
@@ -17,7 +18,7 @@ export const jingdongConfig = {
brandName: '速哇手办制作Agent',
primaryColor: '#3A8E4D',
primarySoft: '#E8F5E9',
background: '#F5F5F5',
background: '#FFFFFF',
images: {
/** 顶栏 / Agent 对话头像 */
@@ -27,6 +28,8 @@ export const jingdongConfig = {
userAvatar,
/** 浏览器标签 favicon */
logoIcon,
/** 对话引导图(照片要求) */
guide: guideImage,
caseOriginal,
caseEffect,
caseScene,
@@ -43,18 +46,22 @@ export const jingdongConfig = {
pollTimeoutMs: 5 * 60 * 1000,
messages: {
welcome: '你好~先上传一张照片,我来帮你做成专属手办效果图。',
caseIntro: '先看看效果案例:原图 → 效果图 → 场景应用',
welcome: '你好,上传一张照片,或直接说说想做成什么样的手办。',
caseIntro: '上传照片前,先看下照片要求',
caseLabels: ['原图', '效果图', '场景应用'] as const,
placeholder: '描述你想要的手办,例如:橘猫,戴黄色围巾...',
placeholderNeedPhoto: '请先点击左侧按钮上传照片',
placeholderNeedPhoto: '描述你想要的手办,例如:橘猫,戴黄色围巾...',
placeholderEditing: '说说想怎么改,例如:换成蓝色围巾、表情更开心…',
customTextLabel: '自定义文字',
customTextPlaceholder: '输入自定义文字...',
customTextMax: 18,
defaultPrompt: '按所选产品、风格生成手办效果图~',
editingHint: '正在编辑效果图 · 描述想调整的地方后发送',
editingHint: '正在编辑效果图 · 输入文字描述想调整的地方',
exitEdit: '退出编辑',
view: '查看',
edit: '编辑',
buyNow: '立即购买',
buyDisabledTip: '编辑调整中,暂不可购买',
buyToast: '购买功能即将开放',
noRemain: '生图次数已用完,暂时无法继续生成',
remainTip: (n: number) => (n > 0 ? `还可以生成 ${n} 次效果图` : '生图次数已用完'),
+179 -119
View File
@@ -2,8 +2,8 @@
<div class="composer">
<div v-if="editing" class="composer__edit-bar">
<span class="composer__edit-left">
<el-icon :size="14"><EditPen /></el-icon>
<span>{{ editingHint }}</span>
<el-icon class="composer__edit-icon" :size="14"><EditPen /></el-icon>
<span class="composer__edit-text">{{ editingHint }}</span>
</span>
<button class="composer__exit" type="button" @click="emit('exit-edit')">
<el-icon :size="12"><Close /></el-icon>
@@ -13,7 +13,36 @@
<p v-if="statusTip" class="composer__tip" :class="{ 'is-warn': tipWarn }">{{ statusTip }}</p>
<div ref="boxRef" class="composer__box" :class="{ 'is-editing': editing, 'is-disabled': disabled }">
<div
ref="boxRef"
class="composer__box"
:class="{ 'is-editing': editing, 'is-disabled': disabled, 'has-custom': !!titleSpec }"
>
<template v-if="titleSpec">
<div class="composer__custom" :class="{ 'is-invalid': titleInvalid }">
<label class="composer__custom-label" :for="titleInputId">
<el-icon class="composer__custom-icon" :size="14"><EditPen /></el-icon>
<span>{{ customLabel }}</span>
</label>
<input
:id="titleInputId"
class="composer__custom-input"
type="text"
:value="productTitle"
:maxlength="titleInputMaxlength"
:placeholder="customPlaceholder"
:disabled="disabled"
:aria-invalid="titleInvalid"
@input="onTitleInput"
/>
<span class="composer__custom-count">{{ titleCount }}/{{ customMax }}</span>
</div>
<p v-if="titleSpec.hint" class="composer__custom-hint">{{ titleSpec.hint }}</p>
<p v-if="titleInvalid" class="composer__custom-error" role="alert">
请填写{{ customLabel }}
</p>
</template>
<div v-if="previewUrl" ref="previewRef" class="composer__preview">
<img class="composer__preview-img" :src="previewUrl" alt="已选图片" />
<span v-if="inspecting" class="composer__preview-mask">识别中</span>
@@ -28,28 +57,6 @@
</button>
</div>
<div v-if="titleSpec" class="composer__title" :class="{ 'is-invalid': titleInvalid }">
<label class="composer__title-label" :for="titleInputId">{{ titleSpec.label }}</label>
<div class="composer__title-field">
<input
:id="titleInputId"
class="composer__title-input"
type="text"
:value="productTitle"
:maxlength="titleInputMaxlength"
:placeholder="titleSpec.placeholder"
:disabled="disabled"
:aria-invalid="titleInvalid"
@input="onTitleInput"
/>
<span class="composer__title-count">{{ titleCount }} / {{ titleSpec.max }}</span>
</div>
<p v-if="titleSpec.hint" class="composer__title-hint">{{ titleSpec.hint }}</p>
<p v-if="titleInvalid" class="composer__title-error" role="alert">
请填写{{ titleSpec.label }}
</p>
</div>
<textarea
ref="textareaRef"
class="composer__input"
@@ -69,7 +76,7 @@
:disabled="disabled || editing || inspecting"
@click="emit('upload')"
>
<el-icon v-if="inspecting" class="is-loading" :size="26"><Loading /></el-icon>
<el-icon v-if="inspecting" class="is-loading" :size="28"><Loading /></el-icon>
<el-icon v-else :size="28"><Picture /></el-icon>
</button>
@@ -115,6 +122,9 @@ const props = withDefaults(
placeholder?: string
placeholderNeedPhoto?: string
placeholderEditing?: string
customTextLabel?: string
customTextPlaceholder?: string
customTextMax?: number
previewUrl?: string
tags?: ComposerTag[]
editing?: boolean
@@ -127,7 +137,7 @@ const props = withDefaults(
/** 底部状态提示 */
statusTip?: string
tipWarn?: boolean
/** 产品标题规则(有值时展示标题输入) */
/** 产品标题规则(有值时参与校验 / 场景道具模式) */
titleSpec?: ProductTitleSpec | null
productTitle?: string
/** 标题未填时高亮错误 */
@@ -136,8 +146,11 @@ const props = withDefaults(
{
modelValue: '',
placeholder: '',
placeholderNeedPhoto: '请先点击左侧按钮上传照片',
placeholderNeedPhoto: '描述你想要的手办,例如:橘猫,戴黄色围巾...',
placeholderEditing: '说说想怎么改…',
customTextLabel: '自定义文字',
customTextPlaceholder: '输入自定义文字...',
customTextMax: 18,
previewUrl: '',
tags: () => [],
editing: false,
@@ -166,11 +179,23 @@ const emit = defineEmits<{
const titleInputId = 'jd-composer-product-title'
const isPropsTitle = computed(() => props.titleSpec?.mode === 'props')
/** 按产品显示:场景道具 / 飘带标题 / 相框标题;无规则时回退自定义文字 */
const customLabel = computed(
() => props.titleSpec?.label || props.customTextLabel || '自定义文字',
)
const customPlaceholder = computed(() => {
if (props.titleSpec?.placeholder) return props.titleSpec.placeholder
return props.customTextPlaceholder || '输入自定义文字...'
})
const customMax = computed(() => {
if (props.titleSpec) return props.titleSpec.max
return props.customTextMax || 18
})
/** 道具模式不按字符截断;字符模式仍用 max */
const titleInputMaxlength = computed(() => {
if (!props.titleSpec) return undefined
if (props.titleSpec.mode === 'props') return undefined
return props.titleSpec.max
if (props.titleSpec?.mode === 'props') return undefined
return customMax.value
})
const titleCount = computed(() => {
@@ -273,33 +298,57 @@ defineExpose({ focus, getPreviewEl, getFlyTargetEl })
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
gap: 10px;
margin-bottom: 8px;
padding: 8px 12px;
border-radius: 12px;
background: #eaf6ec;
color: #3a8e4d;
font-size: 12px;
padding: 8px 8px 8px 12px;
border-radius: 999px;
background: #f2f2f2;
box-sizing: border-box;
}
.composer__edit-left {
display: inline-flex;
align-items: center;
align-items: flex-start;
gap: 6px;
min-width: 0;
flex: 1;
}
.composer__edit-icon {
flex-shrink: 0;
margin-top: 1px;
color: #3a8e4d;
}
.composer__edit-text {
min-width: 0;
color: #3a8e4d;
font-size: 12px;
font-weight: 500;
line-height: 1.4;
}
.composer__exit {
display: inline-flex;
align-items: center;
gap: 3px;
justify-content: center;
gap: 4px;
flex-shrink: 0;
height: 28px;
padding: 0 10px;
border: 0;
background: transparent;
color: #3a8e4d;
border-radius: 999px;
background: #ffffff;
color: #1a1a1a;
font-size: 12px;
font-weight: 500;
line-height: 1;
cursor: pointer;
padding: 0;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
&:active {
opacity: 0.88;
}
}
.composer__box {
@@ -308,6 +357,7 @@ defineExpose({ focus, getPreviewEl, getFlyTargetEl })
background: #ffffff;
border: 1px solid #ebebeb;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
box-sizing: border-box;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
&.is-editing {
@@ -320,6 +370,90 @@ defineExpose({ focus, getPreviewEl, getFlyTargetEl })
}
}
.composer__custom {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
margin: 0 0 10px;
padding: 0 0 10px;
border-bottom: 1px solid #f0f0f0;
&.is-invalid .composer__custom-input {
border-color: rgba(196, 92, 38, 0.55);
background: #fff8f4;
}
}
.composer__custom-label {
display: inline-flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
color: #3a8e4d;
font-size: 13px;
font-weight: 600;
line-height: 1;
white-space: nowrap;
cursor: pointer;
}
.composer__custom-icon {
color: #3a8e4d;
}
.composer__custom-input {
flex: 1;
min-width: 0;
height: 32px;
padding: 0 10px;
border: 1px solid #e6e6e6;
border-radius: 8px;
outline: none;
background: #fafafa;
font-size: 13px;
color: #1a1a1a;
font-family: inherit;
box-sizing: border-box;
&::placeholder {
color: #b0b0b0;
}
&:disabled {
opacity: 0.6;
}
&:focus {
border-color: rgba(58, 142, 77, 0.55);
background: #ffffff;
}
}
.composer__custom-count {
flex-shrink: 0;
min-width: 32px;
font-size: 12px;
color: #999;
text-align: right;
white-space: nowrap;
line-height: 1;
}
.composer__custom-hint {
margin: -4px 0 10px;
font-size: 11px;
line-height: 1.4;
color: #888;
}
.composer__custom-error {
margin: -4px 0 10px;
font-size: 12px;
line-height: 1.3;
color: #c45c26;
}
.composer__preview {
position: relative;
width: 56px;
@@ -365,80 +499,6 @@ defineExpose({ focus, getPreviewEl, getFlyTargetEl })
padding: 0;
}
.composer__title {
margin: 0 0 10px;
padding: 8px 10px;
border-radius: 12px;
background: #f7f8f7;
border: 1px solid transparent;
&.is-invalid {
border-color: rgba(196, 92, 38, 0.45);
background: #fff8f4;
}
}
.composer__title-label {
display: block;
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: #3a8e4d;
}
.composer__title-field {
display: flex;
align-items: center;
gap: 8px;
}
.composer__title-input {
flex: 1;
min-width: 0;
height: 32px;
padding: 0 10px;
border: 1px solid #e6e6e6;
border-radius: 8px;
outline: none;
background: #fff;
font-size: 13px;
color: #1a1a1a;
font-family: inherit;
&::placeholder {
color: #b0b0b0;
}
&:disabled {
opacity: 0.6;
}
&:focus {
border-color: rgba(58, 142, 77, 0.55);
}
}
.composer__title-count {
flex-shrink: 0;
font-size: 11px;
color: #999;
white-space: nowrap;
}
.composer__title-hint {
margin: 6px 0 0;
font-size: 11px;
line-height: 1.4;
color: #888;
}
.composer__title-error {
margin: 6px 0 0;
font-size: 12px;
line-height: 1.3;
color: #c45c26;
}
.composer__input {
display: block;
width: 100%;
@@ -474,9 +534,9 @@ defineExpose({ focus, getPreviewEl, getFlyTargetEl })
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
margin: -6px 0 -6px -6px;
width: 40px;
height: 40px;
margin: -2px 0 -2px -4px;
padding: 0;
border: 0;
background: transparent;
@@ -234,7 +234,7 @@ onUnmounted(() => {
<style scoped lang="scss">
.jd-stage {
width: min(280px, 72vw);
width: 100%;
max-width: 100%;
}
@@ -242,10 +242,9 @@ onUnmounted(() => {
position: relative;
width: 100%;
aspect-ratio: 1 / 1;
max-width: 360px;
overflow: hidden;
border-radius: 12px;
background: #f0f4f9;
background: #f7f7f7;
}
.jd-stage__gif,
@@ -254,8 +253,8 @@ onUnmounted(() => {
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
object-fit: contain;
object-position: center top;
pointer-events: none;
will-change: opacity, transform;
backface-visibility: hidden;
@@ -263,13 +262,14 @@ onUnmounted(() => {
.jd-stage__gif {
z-index: 1;
object-fit: cover;
object-position: center;
}
.jd-stage__result {
z-index: 3;
/* 与结果卡片一致,避免切换时构图跳变 */
object-position: center top;
background: #f0f4f9;
/* 与结果卡片一致:完整显示效果图 */
background: #f7f7f7;
}
.jd-stage__status {
+104 -50
View File
@@ -1,13 +1,22 @@
<template>
<div class="result-card" :class="{ 'is-editing': editing }">
<div class="result-card__media">
<img ref="imgRef" class="result-card__img" :src="image" alt="效果图" />
<button class="result-card__view" type="button" @click="emit('view')">
<span>{{ viewLabel }}</span>
<el-icon :size="12"><FullScreen /></el-icon>
</button>
<img
ref="imgRef"
class="result-card__img"
:src="image"
alt="效果图"
draggable="false"
@click="onImageClick"
@pointerdown="onPointerDown"
@pointerup="onPointerUp"
@pointerleave="onPointerUp"
@pointercancel="onPointerUp"
@contextmenu.prevent
/>
<button
class="result-card__edit"
:class="{ 'is-exit': editing }"
type="button"
@click="onEditClick"
>
@@ -20,21 +29,25 @@
<span>{{ editLabel }}</span>
</template>
</button>
<button class="result-card__download" type="button" aria-label="下载" @click="emit('download')">
<el-icon :size="16"><Download /></el-icon>
</button>
</div>
<div class="result-card__meta">{{ meta }}</div>
<button v-if="showBuy" class="result-card__buy" type="button" @click="emit('buy')">
<button
class="result-card__buy"
type="button"
:disabled="editing"
:aria-disabled="editing"
@click="onBuyClick"
>
<el-icon :size="16"><ShoppingCart /></el-icon>
<span>{{ buyLabel }}</span>
</button>
<p v-if="editing" class="result-card__buy-tip">{{ buyDisabledTip }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Close, Download, EditPen, FullScreen, ShoppingCart } from '@element-plus/icons-vue'
import { onBeforeUnmount, ref } from 'vue'
import { Close, EditPen, ShoppingCart } from '@element-plus/icons-vue'
import { rectFromElement, type FlyRect } from '@/utils/fly-image'
const props = withDefaults(
@@ -42,19 +55,17 @@ const props = withDefaults(
image: string
meta: string
editing?: boolean
showBuy?: boolean
viewLabel?: string
editLabel?: string
exitEditLabel?: string
buyLabel?: string
buyDisabledTip?: string
}>(),
{
editing: false,
showBuy: true,
viewLabel: '查看',
editLabel: '编辑',
exitEditLabel: '退出编辑',
buyLabel: '立即购买',
buyDisabledTip: '编辑调整中,暂不可购买',
},
)
@@ -67,6 +78,39 @@ const emit = defineEmits<{
}>()
const imgRef = ref<HTMLImageElement | null>(null)
const LONG_PRESS_MS = 520
let pressTimer: number | null = null
let longPressed = false
function clearPressTimer() {
if (pressTimer != null) {
window.clearTimeout(pressTimer)
pressTimer = null
}
}
function onPointerDown() {
longPressed = false
clearPressTimer()
pressTimer = window.setTimeout(() => {
longPressed = true
pressTimer = null
emit('download')
}, LONG_PRESS_MS)
}
function onPointerUp() {
clearPressTimer()
}
function onImageClick() {
// 长按保存后不再触发查看大图
if (longPressed) {
longPressed = false
return
}
emit('view')
}
function onEditClick() {
if (props.editing) {
@@ -75,6 +119,15 @@ function onEditClick() {
}
emit('edit', { sourceRect: rectFromElement(imgRef.value) })
}
function onBuyClick() {
if (props.editing) return
emit('buy')
}
onBeforeUnmount(() => {
clearPressTimer()
})
</script>
<style scoped lang="scss">
@@ -86,7 +139,7 @@ function onEditClick() {
position: relative;
overflow: hidden;
border-radius: 14px;
background: #f0f0f0;
background: #f7f7f7;
border: 1.5px solid transparent;
}
@@ -109,51 +162,39 @@ function onEditClick() {
.result-card__img {
display: block;
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
height: auto;
object-fit: contain;
object-position: center top;
cursor: pointer;
-webkit-touch-callout: none;
user-select: none;
vertical-align: top;
}
.result-card__view,
.result-card__edit,
.result-card__download {
.result-card__edit {
position: absolute;
left: 10px;
bottom: 10px;
display: inline-flex;
align-items: center;
gap: 4px;
height: 26px;
padding: 0 8px;
border: 0;
border-radius: 8px;
cursor: pointer;
color: #fff;
background: rgba(0, 0, 0, 0.45);
backdrop-filter: blur(4px);
font-size: 12px;
animation: jd-result-chrome-in 0.55s ease both;
}
.result-card__view {
top: 10px;
right: 10px;
height: 26px;
padding: 0 8px;
border-radius: 8px;
font-size: 12px;
}
.result-card__edit {
left: 10px;
bottom: 10px;
height: 26px;
padding: 0 8px;
border-radius: 8px;
font-size: 12px;
}
.result-card__download {
right: 10px;
bottom: 10px;
width: 30px;
height: 30px;
justify-content: center;
border-radius: 8px;
&.is-exit {
color: #1a1a1a;
background: #ffffff;
border-radius: 999px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}
}
.result-card__meta {
@@ -179,12 +220,25 @@ function onEditClick() {
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.15s ease, transform 0.15s ease;
transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;
animation: jd-result-chrome-in 0.55s ease 0.12s both;
&:active {
&:active:not(:disabled) {
opacity: 0.9;
transform: scale(0.98);
}
&:disabled {
background: #ececec;
color: #b0b0b0;
cursor: not-allowed;
}
}
.result-card__buy-tip {
margin: 8px 0 0;
text-align: center;
font-size: 12px;
line-height: 1.4;
color: #b0b0b0;
}
</style>
+68 -74
View File
@@ -27,19 +27,14 @@
</div>
<div v-else-if="msg.type === 'case'" class="msg__bubble msg__bubble--case">
<p class="msg__case-intro">{{ config.messages.caseIntro }}</p>
<div class="msg__case-grid">
<figure
v-for="(item, idx) in caseItems"
:key="item.label"
class="msg__case-item"
>
<div class="msg__case-thumb">
<img :src="item.src" :alt="item.label" @click="previewImages(caseItems.map((c) => c.src), idx)" />
</div>
<figcaption>{{ item.label }}</figcaption>
</figure>
</div>
<button
class="msg__guide"
type="button"
:aria-label="config.messages.caseIntro"
@click="previewImages([config.images.guide], 0)"
>
<img class="msg__guide-img" :src="config.images.guide" :alt="config.messages.caseIntro" />
</button>
</div>
<div
@@ -65,11 +60,10 @@
:image="msg.image"
:meta="msg.meta"
:editing="editingResultId === msg.id"
:show-buy="editingResultId !== msg.id"
:view-label="config.messages.view"
:edit-label="config.messages.edit"
:exit-edit-label="config.messages.exitEdit"
:buy-label="config.messages.buyNow"
:buy-disabled-tip="config.messages.buyDisabledTip"
@view="previewImages([msg.image], 0)"
@edit="(payload) => enterEdit(msg.id, payload?.sourceRect)"
@exit-edit="exitEdit"
@@ -91,6 +85,9 @@
:placeholder="config.messages.placeholder"
:placeholder-need-photo="config.messages.placeholderNeedPhoto"
:placeholder-editing="config.messages.placeholderEditing"
:custom-text-label="titleSpec?.label || config.messages.customTextLabel"
:custom-text-placeholder="titleSpec?.placeholder || config.messages.customTextPlaceholder"
:custom-text-max="titleSpec?.max || config.messages.customTextMax"
:preview-url="pendingPreview"
:tags="composerTags"
:editing="isEditing"
@@ -189,12 +186,6 @@ function formatTime(date = new Date()) {
return `${h}:${m}`
}
const caseItems = [
{ label: config.messages.caseLabels[0], src: config.images.caseOriginal },
{ label: config.messages.caseLabels[1], src: config.images.caseEffect },
{ label: config.messages.caseLabels[2], src: config.images.caseScene },
]
/**
* 剩余次数:
* - 首屏用接口 remain_count 打底
@@ -814,6 +805,7 @@ async function startGenerate(opts: {
/** 编辑效果图:直接 submit,传 image_id,不走 url/confirm */
imageId?: string
sceneProp?: string
customText?: string
}) {
if (remainCount.value <= 0) {
showToast(config.messages.noRemain)
@@ -877,13 +869,14 @@ async function startGenerate(opts: {
}
const sceneProp = String(opts.sceneProp || '').trim()
const customText = String(opts.customText || '').trim()
const task = await jingdongApi.submitImageGen({
session_id: sid,
...(isEdit ? { image_id: editImageId } : { upload_id: uploadId }),
prompt: opts.userText,
mode: opts.mode,
scene_prop: sceneProp,
custom_text: '',
custom_text: customText,
})
let taskId = task.task_id
if (!taskId) throw new Error('提交生成失败')
@@ -940,13 +933,23 @@ function onSend() {
if (titleSpec.value && !productTitle.value.trim()) {
titleInvalid.value = true
showToast(config.messages.needProductTitle(titleSpec.value.label))
showToast(
config.messages.needProductTitle(
titleSpec.value.label || config.messages.customTextLabel,
),
)
return
}
titleInvalid.value = false
const text = draft.value.trim()
const sceneProp = resolveScenePropFromOrder(orderStat.value, productTitle.value)
// 必须在 reset 前取出:场景道具走 scene_prop,其余走 custom_text
const titleValue = productTitle.value.trim()
const isPropsTitle = titleSpec.value?.mode === 'props'
const sceneProp = isPropsTitle
? resolveScenePropFromOrder(orderStat.value, titleValue)
: ''
const customText = isPropsTitle ? '' : titleValue
if (isEditing.value) {
if (!text) {
@@ -976,6 +979,7 @@ function onSend() {
mode: 'edit',
imageId,
sceneProp,
customText,
})
return
}
@@ -1018,6 +1022,7 @@ function onSend() {
mode: 'generate',
uploadId,
sceneProp,
customText,
})
}
@@ -1158,7 +1163,10 @@ onBeforeUnmount(() => {
<style scoped lang="scss">
.jd-page {
--jd-primary: #3a8e4d;
--jd-bg: #f5f5f5;
--jd-bg: #ffffff;
--jd-avatar: 36px;
--jd-avatar-gap: 10px;
--jd-avatar-slot: calc(var(--jd-avatar) + var(--jd-avatar-gap));
display: flex;
flex-direction: column;
height: 100vh;
@@ -1198,8 +1206,8 @@ onBeforeUnmount(() => {
}
.msg__avatar {
width: 36px;
height: 36px;
width: var(--jd-avatar);
height: var(--jd-avatar);
flex-shrink: 0;
border-radius: 8px;
overflow: hidden;
@@ -1227,7 +1235,8 @@ onBeforeUnmount(() => {
}
.msg__body {
max-width: calc(100% - 52px);
/* 左侧头像占位后,右侧再留一个头像宽度,与用户侧对称 */
max-width: calc(100% - var(--jd-avatar-slot) - var(--jd-avatar));
min-width: 0;
}
@@ -1235,11 +1244,11 @@ onBeforeUnmount(() => {
display: flex;
flex-direction: column;
align-items: flex-end;
max-width: min(268px, calc(100% - 52px));
max-width: min(268px, calc(100% - var(--jd-avatar-slot)));
}
.msg.is-case .msg__body {
max-width: calc(100% - 46px);
max-width: calc(100% - var(--jd-avatar-slot) - var(--jd-avatar));
}
.msg__bubble {
@@ -1259,6 +1268,8 @@ onBeforeUnmount(() => {
.msg:not(.is-user) .msg__bubble {
border-radius: 4px 16px 16px 16px;
border: 1px solid #e6e6e6;
box-sizing: border-box;
}
.msg__bubble--user {
@@ -1303,62 +1314,45 @@ onBeforeUnmount(() => {
}
.msg__bubble--case {
padding: 12px;
}
.msg__case-intro {
margin: 0 0 10px;
font-size: 14px;
line-height: 1.5;
color: #222;
}
.msg__case-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.msg__case-item {
margin: 0;
min-width: 0;
figcaption {
margin-top: 6px;
text-align: center;
font-size: 12px;
color: #999;
line-height: 1.2;
}
}
.msg__case-thumb {
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 10px;
padding: 0;
overflow: hidden;
background: #f0f0f0;
background: #ffffff;
box-shadow: none;
}
img {
display: block;
width: 100%;
height: 200%;
object-fit: contain;
object-position: center top;
cursor: pointer;
}
.msg__guide {
display: block;
width: 100%;
margin: 0;
padding: 0;
border: 0;
background: transparent;
cursor: pointer;
border-radius: 16px;
overflow: hidden;
}
.msg__guide-img {
display: block;
width: 100%;
height: auto;
vertical-align: top;
}
.msg__bubble--generating {
padding: 0;
background: transparent;
background: #ffffff;
box-shadow: none;
overflow: hidden;
width: 60vw;
max-width: 100%;
box-sizing: border-box;
}
.msg__bubble--result {
padding: 10px;
width: min(280px, 72vw);
/* 效果图最大占整屏宽度 60% */
width: 60vw;
max-width: 100%;
box-sizing: border-box;
}