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