Browse Source

修改:后视图生成

main
Linzm 3 weeks ago
parent
commit
9ede62f450
  1. 9
      components.d.ts
  2. 34
      src/api/badge.ts
  3. BIN
      src/assets/badge/1.png
  4. BIN
      src/assets/badge/2.png
  5. BIN
      src/assets/badge/3.png
  6. BIN
      src/assets/badge/4.png
  7. BIN
      src/assets/badge/fanzhuan.png
  8. BIN
      src/assets/badge/shili.png
  9. BIN
      src/assets/badge/wenjian.png
  10. 2
      src/config/cartoon.ts
  11. 45
      src/lang/data/en.ts
  12. 45
      src/lang/data/ko.ts
  13. 48
      src/lang/data/zh-CN.ts
  14. 45
      src/lang/data/zh-TW.ts
  15. 103
      src/views/cartoon/index.vue
  16. 664
      src/views/cartoon/previewOrder.vue

9
components.d.ts vendored

@ -13,11 +13,16 @@ declare module '@vue/runtime-core' { @@ -13,11 +13,16 @@ declare module '@vue/runtime-core' {
Backup: typeof import('./src/components/arFrame/backup.vue')['default']
Backup2: typeof import('./src/components/arFrame/backup2.vue')['default']
Brand: typeof import('./src/components/brand/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElButton: typeof import('element-plus/es')['ElButton']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
Gsplat: typeof import('./src/components/arFrame/gsplat.vue')['default']
Hint: typeof import('./src/components/hint/hint.vue')['default']
Loading: typeof import('./src/components/loading/index.vue')['default']
@ -27,11 +32,11 @@ declare module '@vue/runtime-core' { @@ -27,11 +32,11 @@ declare module '@vue/runtime-core' {
Tensorflow: typeof import('./src/components/arFrame/tensorflow.vue')['default']
VanActionSheet: typeof import('vant/es')['ActionSheet']
VanAddressEdit: typeof import('vant/es')['AddressEdit']
VanButton: typeof import('vant/es')['Button']
VanDivider: typeof import('vant/es')['Divider']
VanField: typeof import('vant/es')['Field']
VanIcon: typeof import('vant/es')['Icon']
VanList: typeof import('vant/es')['List']
VanLoading: typeof import('vant/es')['Loading']
VanOverlay: typeof import('vant/es')['Overlay']
VanPullRefresh: typeof import('vant/es')['PullRefresh']
VanStepper: typeof import('vant/es')['Stepper']
VanUploader: typeof import('vant/es')['Uploader']

34
src/api/badge.ts

@ -162,7 +162,39 @@ export const getMultiUrl = (params: any) => { @@ -162,7 +162,39 @@ export const getMultiUrl = (params: any) => {
// 获取坐骑列表
export const getMountsList = (params: any) => {
return request('products/mountsList', {
return request('cartoon/mountsList', {
method: 'GET',
params,
})
}
// 生成多视图数据
export const generateViews = (data: any) => {
return request('views/generateViews', {
method: 'POST',
data,
})
}
// 多视图列表
export const listViews = (params: any) => {
return request('views/listViews', {
method: 'GET',
params,
})
}
// 水平翻转图片
export const flipViews = (params: any) => {
return request('views/flipViews', {
method: 'GET',
params,
})
}
// 确认后视图
export const confirmViews = (params: any) => {
return request('views/confirmViews', {
method: 'GET',
params,
})

BIN
src/assets/badge/1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/badge/2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/badge/3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/badge/4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/badge/fanzhuan.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

BIN
src/assets/badge/shili.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/assets/badge/wenjian.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

2
src/config/cartoon.ts

@ -110,7 +110,7 @@ export const cartoonConfig = { @@ -110,7 +110,7 @@ export const cartoonConfig = {
},
spacing: {
pagePadding: '16px',
sectionMargin: '16px',
sectionMargin: '8px',
itemGap: '8px'
}
},

45
src/lang/data/en.ts

@ -158,5 +158,50 @@ const en = { @@ -158,5 +158,50 @@ const en = {
"请输入背面文字": "Please enter back text",
"请输入顶部文字": "Please enter top text",
"请输入底部文字": "Please enter bottom text",
"坐骑类型": "Ride Type",
"智能生成": "Smart Generation",
"自定义": "Custom",
"请选择坐骑类型": "Please select ride type",
"场景道具": "Scene Props",
"请输入场景道具": "Please enter scene props",
"手办摇摇乐": "Shake Toy",
"手办挂饰": "Handheld Pendant",
"场景手办": "Scene Figure",
"A款底座": "A Base",
"B款底座": "B Base",
"C款底座": "C Base",
"D款底座": "D Base",
"E款底座": "E Base",
"F款底座": "F Base",
"后视图": "Rear View",
"多主体手办下单前需先生成正确的后视图": "Multi-subject figures require generating the correct rear view before placing an order",
"请输入提示词,描述需要生成的后视图要求": "Please enter a prompt describing the requirements for generating the rear view",
"编辑": "Edit",
"生成后视图": "Generate Rear View",
"预设提示词": "Preset Prompt",
"请等待前一张后视图生成完成": "Please wait for the previous rear view to finish generating",
"订单ID不能为空": "Order ID cannot be empty",
"后视图生成提交成功": "Rear view generation submitted successfully",
"生成后视图提交失败,请重试": "Failed to submit rear view generation, please try again",
"镜像处理成功": "Mirror processing successful",
"镜像处理失败": "Mirror processing failed",
"请先选择后视图": "Please select a rear view first",
"后视图确认成功": "Rear view confirmed successfully",
"后视图确认失败": "Rear view confirmation failed",
"请输入后视图提示词": "Please enter rear view prompt",
"请输入后视图提示词,描述需要生成的后视图要求": "Please enter a rear view prompt describing the requirements for generating the rear view",
"选择预设提示词": "Select Preset Prompt",
"提示词": "Prompt",
"暂无预设提示词": "No preset prompts available",
"取消": "Cancel",
"后视图要求": "Rear View Requirements",
"1. 后视图应与前视图保持角色和动作的一致性": "1. The rear view should maintain character and action consistency with the front view",
"2. 模型需要水平旋转180度": "2. The model needs to be rotated 180 degrees horizontally",
"3. 确保后视图清晰可见,无明显遮挡": "3. Ensure the rear view is clear and visible with no obvious obstructions",
"4. 后视图应展示角色的背面特征": "4. The rear view should display the character's back features",
"镜像": "Mirror",
"选择": "Select",
"镜像处理": "Mirror Processing",
"确认选择后视图": "Confirm Rear View Selection",
}
export default en

45
src/lang/data/ko.ts

@ -158,5 +158,50 @@ const ko = { @@ -158,5 +158,50 @@ const ko = {
"请输入背面文字": "뒷면 텍스트를 입력하세요",
"请输入顶部文字": "상단 텍스트를 입력하세요",
"请输入底部文字": "하단 텍스트를 입력하세요",
"坐骑类型": "좌석 유형",
"智能生成": "스마트 생성",
"自定义": "사용자 정의",
"请选择坐骑类型": "좌석 유형을 선택하세요",
"场景道具": "장소 도구",
"请输入场景道具": "장소 도구를 입력하세요",
"手办摇摇乐": "피규어 흔들림",
"手办挂饰": "피규어 걸이",
"场景手办": "장소 피규어",
"A款底座": "A 베이스",
"B款底座": "B 베이스",
"C款底座": "C 베이스",
"D款底座": "D 베이스",
"E款底座": "E 베이스",
"F款底座": "F 베이스",
"后视图": "후면 뷰",
"多主体手办下单前需先生成正确的后视图": "다중 주체 피규어는 주문 전에 올바른 후면 뷰를 먼저 생성해야 합니다",
"请输入提示词,描述需要生成的后视图要求": "후면 뷰 생성 요구사항을 설명하는 프롬프트를 입력하세요",
"编辑": "편집",
"生成后视图": "후면 뷰 생성",
"预设提示词": "사전 설정 프롬프트",
"请等待前一张后视图生成完成": "이전 후면 뷰 생성이 완료될 때까지 기다려주세요",
"订单ID不能为空": "주문 ID는 비어 있을 수 없습니다",
"后视图生成提交成功": "후면 뷰 생성 제출 성공",
"生成后视图提交失败,请重试": "후면 뷰 생성 제출 실패, 다시 시도해주세요",
"镜像处理成功": "미러 처리 성공",
"镜像处理失败": "미러 처리 실패",
"请先选择后视图": "먼저 후면 뷰를 선택하세요",
"后视图确认成功": "후면 뷰 확인 성공",
"后视图确认失败": "후면 뷰 확인 실패",
"请输入后视图提示词": "후면 뷰 프롬프트를 입력하세요",
"请输入后视图提示词,描述需要生成的后视图要求": "생성할 후면 뷰 요구사항을 설명하는 후면 뷰 프롬프트를 입력하세요",
"选择预设提示词": "사전 설정 프롬프트 선택",
"提示词": "프롬프트",
"暂无预设提示词": "사전 설정 프롬프트 없음",
"取消": "취소",
"后视图要求": "후면 뷰 요구사항",
"1. 后视图应与前视图保持角色和动作的一致性": "1. 후면 뷰는 전면 뷰와 캐릭터 및 동작의 일관성을 유지해야 합니다",
"2. 模型需要水平旋转180度": "2. 모델은 수평으로 180도 회전해야 합니다",
"3. 确保后视图清晰可见,无明显遮挡": "3. 후면 뷰가 명확하고 보이며 명백한 가림이 없어야 합니다",
"4. 后视图应展示角色的背面特征": "4. 후면 뷰는 캐릭터의 뒷면 특징을 보여줘야 합니다",
"镜像": "미러",
"选择": "선택",
"镜像处理": "미러 처리",
"确认选择后视图": "후면 뷰 선택 확인",
}
export default ko

48
src/lang/data/zh-CN.ts

@ -158,5 +158,53 @@ const zhCN = { @@ -158,5 +158,53 @@ const zhCN = {
"请输入背面文字": "请输入背面文字",
"请输入顶部文字": "请输入顶部文字",
"请输入底部文字": "请输入底部文字",
"坐骑类型": "坐骑类型",
"智能生成": "智能生成",
"自定义": "自定义",
"请选择坐骑类型": "请选择坐骑类型",
"场景道具": "场景道具",
"请输入场景道具": "请输入场景道具",
"手办摇摇乐": "手办摇摇乐",
"手办挂饰": "手办挂饰",
"场景手办": "场景手办",
"A款底座": "A款底座",
"B款底座": "B款底座",
"C款底座": "C款底座",
"D款底座": "D款底座",
"E款底座": "E款底座",
"F款底座": "F款底座",
"G款底座": "G款底座",
"H款底座": "H款底座",
"I款底座": "I款底座",
"后视图": "后视图",
"多主体手办下单前需先生成正确的后视图": "多主体手办下单前需先生成正确的后视图",
"请输入提示词,描述需要生成的后视图要求": "请输入提示词,描述需要生成的后视图要求",
"编辑": "编辑",
"生成后视图": "生成后视图",
"预设提示词": "预设提示词",
"请等待前一张后视图生成完成": "请等待前一张后视图生成完成",
"订单ID不能为空": "订单ID不能为空",
"后视图生成提交成功": "后视图生成提交成功",
"生成后视图提交失败,请重试": "生成后视图提交失败,请重试",
"镜像处理成功": "镜像处理成功",
"镜像处理失败": "镜像处理失败",
"请先选择后视图": "请先选择后视图",
"后视图确认成功": "后视图确认成功",
"后视图确认失败": "后视图确认失败",
"请输入后视图提示词": "请输入后视图提示词",
"请输入后视图提示词,描述需要生成的后视图要求": "请输入后视图提示词,描述需要生成的后视图要求",
"选择预设提示词": "选择预设提示词",
"提示词": "提示词",
"暂无预设提示词": "暂无预设提示词",
"取消": "取消",
"后视图要求": "后视图要求",
"1. 后视图应与前视图保持角色和动作的一致性": "1. 后视图应与前视图保持角色和动作的一致性",
"2. 模型需要水平旋转180度": "2. 模型需要水平旋转180度",
"3. 确保后视图清晰可见,无明显遮挡": "3. 确保后视图清晰可见,无明显遮挡",
"4. 后视图应展示角色的背面特征": "4. 后视图应展示角色的背面特征",
"镜像": "镜像",
"选择": "选择",
"镜像处理": "镜像处理",
"确认选择后视图": "确认选择后视图",
}
export default zhCN

45
src/lang/data/zh-TW.ts

@ -158,5 +158,50 @@ const zhCT = { @@ -158,5 +158,50 @@ const zhCT = {
"请输入背面文字": "請輸入背面文字",
"请输入顶部文字": "請輸入頂部文字",
"请输入底部文字": "請輸入底部文字",
"坐骑类型": "坐騎類型",
"智能生成": "智能生成",
"自定义": "自定義",
"请选择坐骑类型": "請選擇坐騎類型",
"场景道具": "場景道具",
"请输入场景道具": "請輸入場景道具",
"手办摇摇乐": "手辦搖搖樂",
"手办挂饰": "手辦掛飾",
"场景手办": "場景手辦",
"A款底座": "A款底座",
"B款底座": "B款底座",
"C款底座": "C款底座",
"D款底座": "D款底座",
"E款底座": "E款底座",
"F款底座": "F款底座",
"后视图": "後視圖",
"多主体手办下单前需先生成正确的后视图": "多主體手辦下單前需先生成正確的後視圖",
"请输入提示词,描述需要生成的后视图要求": "請輸入提示詞,描述需要生成的後視圖要求",
"编辑": "編輯",
"生成后视图": "生成後視圖",
"预设提示词": "預設提示詞",
"请等待前一张后视图生成完成": "請等待前一張後視圖生成完成",
"订单ID不能为空": "訂單ID不能為空",
"后视图生成提交成功": "後視圖生成提交成功",
"生成后视图提交失败,请重试": "生成後視圖提交失敗,請重試",
"镜像处理成功": "鏡像處理成功",
"镜像处理失败": "鏡像處理失敗",
"请先选择后视图": "請先選擇後視圖",
"后视图确认成功": "後視圖確認成功",
"后视图确认失败": "後視圖確認失敗",
"请输入后视图提示词": "請輸入後視圖提示詞",
"请输入后视图提示词,描述需要生成的后视图要求": "請輸入後視圖提示詞,描述需要生成的後視圖要求",
"选择预设提示词": "選擇預設提示詞",
"提示词": "提示詞",
"暂无预设提示词": "暫無預設提示詞",
"取消": "取消",
"后视图要求": "後視圖要求",
"1. 后视图应与前视图保持角色和动作的一致性": "1. 後視圖應與前視圖保持角色和動作的一致性",
"2. 模型需要水平旋转180度": "2. 模型需要水平旋轉180度",
"3. 确保后视图清晰可见,无明显遮挡": "3. 確保後視圖清晰可見,無明顯遮擋",
"4. 后视图应展示角色的背面特征": "4. 後視圖應展示角色的背面特徵",
"镜像": "鏡像",
"选择": "選擇",
"镜像处理": "鏡像處理",
"确认选择后视图": "確認選擇後視圖",
}
export default zhCT

103
src/views/cartoon/index.vue

@ -88,53 +88,6 @@ @@ -88,53 +88,6 @@
</div>
</div>
</div>
<div class="step-line" v-if="shouldShowPhotoExample">
<div class="step-line-item">
<div class="step-line-item-title">
<span style="display: flex; align-items: center;justify-content: center;">
<span :style="`height: 1px; background: ${config.styles.colors.border};width: 50px;`"></span>
<span :style="`margin: 0 8px; color: ${config.styles.colors.textTertiary};`">{{ toValueWithout("照片示例") }}</span>
<span :style="`height: 1px; background: ${config.styles.colors.border};width: 50px;`"></span>
</span>
</div>
<div class="step-line-item-desc">
<div class="step-line-item-desc-item">
<img :src="example1" :style="`width: 20vw;height: 20vw;border-radius: 8px;`" alt="">
</div>
<div class="step-line-item-desc-item">
<img :src="example2" :style="`width: 20vw;height: 20vw;border-radius: 8px;`" alt="">
</div>
<div class="step-line-item-desc-item">
<img :src="example3" :style="`width: 20vw;height: 20vw;border-radius: 8px;`" alt="">
</div>
<div class="step-line-item-desc-item">
<img :src="example4" :style="`width: 20vw;height: 20vw;border-radius: 8px;`" alt="">
</div>
</div>
</div>
</div>
<div class="photo-upload-body">
<div v-if="!picture" class="photo-upload-box">
<div class="photo-upload-area">
<div class="photo-upload-plus">
+
</div>
<div class="photo-upload-text">
{{ toValueWithout("点击上传照片") }}
</div>
<div class="photo-upload-tip">
*{{ toValueWithout("上传照片时建议勾选[原图]") }}
</div>
</div>
<div class="photo-upload-footer">
{{ toValueWithout("请确定您对上传的照片拥有合法使用权利或已取得他人合法授权,且同意本平台分析图片信息以提供生成服务") }}
</div>
</div>
<img class="photo-upload-img" v-if="picture" :src="picture" alt="" srcset="">
<div class="photo-upload-box-1">
<h5-cropper :option="option" @getbase64Data="getbase64Data" @imgorigoinf="imgorigoinf"></h5-cropper>
</div>
</div>
<div v-if="shouldShowKindList">
<div :style="`font-size: 16px; color: ${config.styles.colors.textPrimary}; font-weight: bold; margin-top: ${config.styles.spacing.sectionMargin};padding: 0 ${config.styles.spacing.pagePadding};`">
{{ toValueWithout("风格类型") }}
@ -147,7 +100,7 @@ @@ -147,7 +100,7 @@
</div>
</div>
</div>
<div v-if="shouldShowMultiView">
<div v-if="isViews == 1">
<div :style="`font-size: 16px; color: ${config.styles.colors.textPrimary}; font-weight: bold; margin-top: ${config.styles.spacing.sectionMargin};padding: 0 ${config.styles.spacing.pagePadding};`">
{{ toValueWithout("多视角参考图") }}
</div>
@ -176,7 +129,7 @@ @@ -176,7 +129,7 @@
<div class="mounts-item-list">
<img
class="mounts-item-image"
:src="getImageUrl(item.cover_path)"
:src="item.image"
:alt="toValueWithout(item.name)"
/>
<van-icon
@ -241,6 +194,28 @@ @@ -241,6 +194,28 @@
</div>
</div>
</div>
<div class="photo-upload-body">
<div v-if="!picture" class="photo-upload-box">
<div class="photo-upload-area">
<div class="photo-upload-plus">
+
</div>
<div class="photo-upload-text">
{{ toValueWithout("点击上传照片") }}
</div>
<div class="photo-upload-tip">
*{{ toValueWithout("上传照片时建议勾选[原图]") }}
</div>
</div>
<div class="photo-upload-footer">
{{ toValueWithout("请确定您对上传的照片拥有合法使用权利或已取得他人合法授权,且同意本平台分析图片信息以提供生成服务") }}
</div>
</div>
<img class="photo-upload-img" v-if="picture" :src="picture" alt="" srcset="">
<div class="photo-upload-box-1">
<h5-cropper :option="option" @getbase64Data="getbase64Data" @imgorigoinf="imgorigoinf"></h5-cropper>
</div>
</div>
<div :style="`height: 120px;`"></div>
<div class="design-action-bar">
<div class="design-left">
@ -268,11 +243,6 @@ import { toValueWithout } from '@/lang/utils' @@ -268,11 +243,6 @@ import { toValueWithout } from '@/lang/utils'
import { cartoonConfig } from '@/config/cartoon'
import leafIcon from '@/assets/badge/leaf.png'
import arrowIcon from '@/assets/badge/arrow.png'
import example1 from '@/assets/badge/1.png'
import example2 from '@/assets/badge/2.png'
import example3 from '@/assets/badge/3.png'
import example4 from '@/assets/badge/4.png'
// Element Plus 使
//
const { t } = useTranslation();
@ -332,6 +302,7 @@ const typeId = ref(0) @@ -332,6 +302,7 @@ const typeId = ref(0)
const subjectId = ref(0)
const typeName = ref('')
const subjectName = ref('')
const isViews = ref(0)
const getOrderStat = () => {
badgeApi.getOrderStat({}).then((res: any) => {
orderStat.value = res
@ -341,9 +312,9 @@ const getOrderStat = () => { @@ -341,9 +312,9 @@ const getOrderStat = () => {
typeName.value = res.type_name
subjectId.value = res.subject_id
subjectName.value = res.subject_name
if (res.type_id === 4) {
getKindList()
} else if (res.type_id === 8) {
isViews.value = res.is_views
getKindList()
if (res.type_id === 8) {
mountsId.value = 0
getmountsList()
} else if (res.type_id === 9) {
@ -361,12 +332,7 @@ const shouldShowPhotoExample = computed(() => { @@ -361,12 +332,7 @@ const shouldShowPhotoExample = computed(() => {
//
const shouldShowKindList = computed(() => {
return typeId.value === 4 && kindList.value.length > 0
})
//
const shouldShowMultiView = computed(() => {
return typeId.value === 4 && config.productLimits.multiViewSupported.includes(prodId.value)
return kindList.value.length > 0
})
const kindList = ref([])
@ -403,7 +369,10 @@ const getmountsList = () => { @@ -403,7 +369,10 @@ const getmountsList = () => {
}
badgeApi.getMountsList({
prod_id: prodId.value,
type_id: typeId.value
type_id: typeId.value,
status: 1,
page: 1,
size: 1000
}).then((res: any) => {
if (res && res.list && res.list.length > 0) {
mountsList.value = res.list
@ -645,7 +614,7 @@ const getPid = async () => { @@ -645,7 +614,7 @@ const getPid = async () => {
sendToOss(imgurl.value, res.url),
]
await Promise.all(uploadTasks)
if (shouldShowMultiView.value && referPicture.value.length > 0) {
if (isViews.value == 1 && referPicture.value.length > 0) {
const multiUrlTasks = referPicture.value.map((item) => {
return badgeApi.getMultiUrl({
pid: pid.value,
@ -1064,7 +1033,7 @@ onMounted(() => { @@ -1064,7 +1033,7 @@ onMounted(() => {
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
padding: 16px 36px;
padding: 16px;
}
.kind-box-item {
margin-right: 10px;
@ -1114,7 +1083,7 @@ onMounted(() => { @@ -1114,7 +1083,7 @@ onMounted(() => {
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-fit: contain;
border-radius: 8px;
transition: transform 0.2s;
}

664
src/views/cartoon/previewOrder.vue

@ -103,9 +103,149 @@ @@ -103,9 +103,149 @@
</div>
</div>
<!-- 后视图 -->
<div>
<div v-if="isViews === 0" style="padding: 0 16px; margin-top: 16px;">
<div :style="`font-size: 16px; color: ${config.styles.colors.textPrimary}; font-weight: bold; margin-bottom: 12px;`">
{{ toValueWithout("后视图") }}
</div>
<div :style="`font-size: 14px; color: ${config.styles.colors.textSecondary}; margin-bottom: 12px; line-height: 1.6;`">
<p>{{ toValueWithout("多主体手办下单前需先生成正确的后视图") }}</p>
</div>
<!-- 提示词输入框 -->
<div style="margin-bottom: 12px; position: relative;">
<el-input
v-model="rearViewPromptText"
type="textarea"
:rows="4"
:placeholder="toValueWithout('请输入提示词,描述需要生成的后视图要求')"
:disabled="isUsingPresetPrompt"
class="rear-view-textarea"
@input="handlePromptTextInput"
/>
<el-button
v-if="isUsingPresetPrompt"
type="text"
size="small"
style="position: absolute; bottom: 8px; right: 8px;"
@click="enableEditPrompt"
>
{{ toValueWithout("编辑") }}
</el-button>
</div>
<!-- 操作按钮 -->
<div style="display: flex; gap: 12px; margin-bottom: 16px;">
<el-button
type="success"
@click="handleGenerateRearView"
:loading="generatingRearView"
:disabled="hasGeneratingRearView || generatingRearView"
>
{{ toValueWithout("生成后视图") }}
</el-button>
<el-button type="default" @click="handlePresetRearViewPrompt">
{{ toValueWithout("预设提示词") }}
</el-button>
</div>
<!-- 后视图记录 -->
<div class="rear-view-record-section">
<el-tabs v-model="rearViewActiveTab">
<el-tab-pane :label="toValueWithout('后视图记录')" name="record">
<div class="rear-view-image-grid">
<!-- 生成中的占位 -->
<div v-if="generatingRearView && rearViewList.length === 0" class="rear-view-image-item loading">
<div class="loading-placeholder">
{{ toValueWithout("生成中...") }}
</div>
</div>
<!-- 已生成的后视图 -->
<div
v-for="(item, index) in rearViewList"
:key="item.unique_no || item.id || index"
class="rear-view-image-item"
:class="{ active: selectedRearViewIndex === index }"
@click="selectRearViewImage(index)"
>
<!-- 图片为空时显示生成中 -->
<div v-if="item.status == 0" class="loading-placeholder">
<van-loading color="#1989fa" />
<span>{{ toValueWithout("生成中,请稍后...") }}</span>
</div>
<el-image
v-else
:src="getRearViewImageUrl(item.url)"
fit="cover"
class="rear-view-image"
/>
<div v-if="selectedRearViewIndex === index" class="rear-view-image-actions">
<el-button
size="small"
:disabled="item.status == 0"
@click.stop="handleMirrorRearView(index)"
>
{{ toValueWithout("镜像") }}
</el-button>
<el-button
type="success"
size="small"
:disabled="item.status == 0"
@click.stop="confirmSelectRearView(index)"
>
{{ toValueWithout("选择") }}
</el-button>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane :label="toValueWithout('后视图要求')" name="requirements">
<div class="rear-view-requirements-content">
<el-alert
type="info"
:closable="false"
show-icon
>
<template #title>
<div class="requirements-text">
<p>{{ toValueWithout("1. 后视图应与前视图保持角色和动作的一致性") }}</p>
<p>{{ toValueWithout("2. 模型需要水平旋转180度") }}</p>
<p>{{ toValueWithout("3. 确保后视图清晰可见,无明显遮挡") }}</p>
<p>{{ toValueWithout("4. 后视图应展示角色的背面特征") }}</p>
</div>
</template>
</el-alert>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
<!-- 预设提示词选择弹窗 -->
<el-dialog
:title="toValueWithout('选择预设提示词')"
v-model="presetRearViewPromptDialogVisible"
width="600px"
:close-on-click-modal="false"
>
<div class="preset-prompt-list">
<div
v-for="(tip, index) in rearViewTipsList"
:key="tip.id || index"
class="preset-prompt-item"
@click="selectPresetRearViewPrompt(tip)"
>
<div class="prompt-title">{{ tip.title || `${toValueWithout('提示词')} ${String(index + 1)}` }}</div>
<div class="prompt-content">{{ tip.content }}</div>
</div>
<div v-if="rearViewTipsList.length === 0" class="empty-tips">
{{ toValueWithout("暂无预设提示词") }}
</div>
</div>
<template #footer>
<el-button @click="presetRearViewPromptDialogVisible = false">{{ toValueWithout("取消") }}</el-button>
</template>
</el-dialog>
<div style="padding: 0 16px;">
<van-divider />
</div>
@ -179,9 +319,9 @@ @@ -179,9 +319,9 @@
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, onUnmounted, nextTick } from 'vue';
import { onMounted, ref, onUnmounted, nextTick, watch, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { showSuccessToast, showToast, showConfirmDialog } from 'vant';
import { showSuccessToast, showToast, showConfirmDialog, Loading } from 'vant';
import { localStorage } from '@/utils/local-storage'
import * as badgeApi from '@/api/badge'
import { areaList } from '@/utils/area'
@ -966,6 +1106,7 @@ const orderStat = ref({}) @@ -966,6 +1106,7 @@ const orderStat = ref({})
const typeId = ref(0)
const subjectId = ref(0)
const typeName = ref('')
const isViews = ref(0)
const getOrderStat = () => {
badgeApi.getOrderStat({}).then((res: any) => {
console.log('getOrderStat', res)
@ -974,15 +1115,373 @@ const getOrderStat = () => { @@ -974,15 +1115,373 @@ const getOrderStat = () => {
subjectId.value = res.subject_id
typeName.value = res.type_name
badgeTypeId.value = res.type_id
isViews.value = res.is_views || 0
getSizeList()
//
if (isViews.value === 1) {
loadRearViewList()
}
})
}
//
const rearViewActiveTab = ref('record')
const generatingRearView = ref(false)
const rearViewPromptText = ref('')
const isUsingPresetPrompt = ref(false) // 使
const rearViewList = ref<any[]>([])
const selectedRearViewIndex = ref<number | null>(null)
const selectedRearView = ref<any>(null)
const rearViewTipsList = ref<any[]>([])
const presetRearViewPromptDialogVisible = ref(false)
const rearViewPollingTimer = ref<NodeJS.Timeout | null>(null)
const isRearViewPolling = ref(false)
const isLoadingRearViewList = ref(false)
//
const hasGeneratingRearView = computed(() => {
return rearViewList.value.some((item: any) => item.status === 0)
})
// URL
function getRearViewImageUrl(url: string) {
if (!url) return ''
if (url.indexOf('http') === 0) {
return url
}
return `https://3dview.suwa3d.com/${url}?t=${Date.now()}`
}
//
function handlePresetRearViewPrompt() {
if (rearViewTipsList.value.length === 0) {
showToast({
message: toValueWithout('暂无预设提示词'),
duration: 2000,
})
return
}
//
if (rearViewTipsList.value.length === 1) {
rearViewPromptText.value = rearViewTipsList.value[0].content || ''
isUsingPresetPrompt.value = true // 使
return
}
//
presetRearViewPromptDialogVisible.value = true
}
//
function selectPresetRearViewPrompt(tip: any) {
rearViewPromptText.value = tip.content || ''
isUsingPresetPrompt.value = true // 使
presetRearViewPromptDialogVisible.value = false
}
//
function handlePromptTextInput() {
//
isUsingPresetPrompt.value = false
}
//
function enableEditPrompt() {
isUsingPresetPrompt.value = false
}
//
async function handleGenerateRearView() {
//
if (hasGeneratingRearView.value) {
showToast({
message: toValueWithout('请等待前一张后视图生成完成'),
duration: 2000,
})
return
}
if (!pid.value) {
showToast({
message: toValueWithout('订单ID不能为空'),
duration: 2000,
})
return
}
if (!rearViewPromptText.value || rearViewPromptText.value.trim() === '') {
showToast({
message: toValueWithout('请输入后视图提示词'),
duration: 2000,
})
return
}
try {
generatingRearView.value = true
//
await badgeApi.generateViews({
pid: pid.value,
pos: 3,
size_type: 2,
prompt: rearViewPromptText.value || ''
})
showSuccessToast({
message: toValueWithout('后视图生成提交成功'),
duration: 2000,
})
//
await loadRearViewList()
//
startRearViewPolling()
} catch (err: any) {
console.error('生成后视图提交失败:', err)
showToast({
message: err.message || toValueWithout('生成后视图提交失败,请重试'),
duration: 2000,
})
} finally {
generatingRearView.value = false
}
}
//
function selectRearViewImage(index: number) {
selectedRearViewIndex.value = index
selectedRearView.value = rearViewList.value[index]
}
//
async function handleMirrorRearView(index: number) {
if (rearViewList.value[index]) {
await badgeApi.flipViews({
unique_no: rearViewList.value[index].unique_no,
}).then((res: any) => {
showSuccessToast({
message: toValueWithout('镜像处理成功'),
duration: 2000,
})
//
loadRearViewList(false)
}).catch((err: any) => {
showToast({
message: toValueWithout('镜像处理失败'),
duration: 2000,
})
})
}
}
//
function confirmSelectRearView(index: number) {
selectRearViewImage(index)
handleConfirmRearView()
}
//
function handleConfirmRearView() {
if (!selectedRearView.value) {
showToast({
message: toValueWithout('请先选择后视图'),
duration: 2000,
})
return
}
badgeApi.confirmViews({
unique_no: selectedRearView.value.unique_no,
}).then((res: any) => {
showSuccessToast({
message: toValueWithout('后视图确认成功'),
duration: 2000,
})
}).catch((err: any) => {
showToast({
message: err.message && err.message.indexOf('文件不存在') > -1 ? toValueWithout('图片不存在') : toValueWithout('后视图确认失败'),
duration: 2000,
})
})
}
//
// isPollingRequest: tips
async function loadRearViewList(isPollingRequest: boolean = false) {
if (!pid.value) return
//
if (isLoadingRearViewList.value) {
return
}
try {
isLoadingRearViewList.value = true
const res: any = await badgeApi.listViews({
pid: pid.value,
})
console.log('后视图列表===》', res)
// tips
if (!isPollingRequest) {
if (res && Array.isArray(res.tips)) {
rearViewTipsList.value = res.tips
} else {
rearViewTipsList.value = []
}
}
if (res && Array.isArray(res.list)) {
const newList = res.list.map((item: any) => ({
url: item.path,
id: item.id,
unique_no: item.unique_no,
status: item.status,
}))
//
await updateRearViewList(newList)
// status=1
const hasGenerating = newList.some((item: any) => item.status === 0)
if (hasGenerating && isPollingRequest) {
//
// startRearViewPolling
} else if (!hasGenerating) {
//
stopRearViewPolling()
}
}
} catch (err) {
console.error('加载后视图列表失败:', err)
//
if (isPollingRequest) {
stopRearViewPolling()
}
} finally {
isLoadingRearViewList.value = false
}
}
//
async function updateRearViewList(newList: any[]) {
// 使 nextTick DOM
await nextTick()
//
if (rearViewList.value.length === 0) {
rearViewList.value = newList
return
}
// unique_no key 便
const existingMap = new Map<number | string, any>()
rearViewList.value.forEach((item: any, index: number) => {
if (item.unique_no) {
existingMap.set(item.unique_no, { item, index })
}
})
//
const toRemove: number[] = []
const newUniqueNos = new Set(newList.map((item: any) => item.unique_no).filter(Boolean))
//
rearViewList.value.forEach((item: any, index: number) => {
if (item.unique_no && !newUniqueNos.has(item.unique_no)) {
toRemove.push(index)
}
})
//
toRemove.reverse().forEach((index: number) => {
rearViewList.value.splice(index, 1)
})
//
newList.forEach((newItem: any) => {
if (newItem.unique_no) {
const existing = existingMap.get(newItem.unique_no)
if (existing) {
// 使 Object.assign
const existingItem = existing.item
const hasStatusChange = existingItem.status !== newItem.status
const hasUrlChange = existingItem.url !== newItem.url
if (hasStatusChange || hasUrlChange) {
//
if (hasStatusChange) {
existingItem.status = newItem.status
}
if (hasUrlChange) {
existingItem.url = newItem.url
}
}
} else {
//
rearViewList.value.push(newItem)
}
}
})
//
await nextTick()
if (selectedRearView.value && selectedRearView.value.unique_no) {
const foundIndex = rearViewList.value.findIndex(
(item: any) => item.unique_no === selectedRearView.value?.unique_no
)
if (foundIndex !== -1) {
selectedRearViewIndex.value = foundIndex
selectedRearView.value = rearViewList.value[foundIndex]
} else {
selectedRearViewIndex.value = null
selectedRearView.value = null
}
} else if (selectedRearViewIndex.value !== null && rearViewList.value[selectedRearViewIndex.value]) {
selectedRearView.value = rearViewList.value[selectedRearViewIndex.value]
}
}
//
function startRearViewPolling() {
//
stopRearViewPolling()
//
isRearViewPolling.value = true
// 10
rearViewPollingTimer.value = setInterval(async () => {
// true
if (isRearViewPolling.value && !isLoadingRearViewList.value) {
// true
await loadRearViewList(true)
//
const hasGenerating = rearViewList.value.some((item: any) => item.status === 0)
if (!hasGenerating) {
stopRearViewPolling()
}
}
}, 10000)
}
//
function stopRearViewPolling() {
isRearViewPolling.value = false
if (rearViewPollingTimer.value) {
clearInterval(rearViewPollingTimer.value)
rearViewPollingTimer.value = null
}
}
function handleBeforeUnload(_event: BeforeUnloadEvent) {
// code main.ts
//
clearInterval(timer.value)
clearInterval(progressTimer.value)
stopRearViewPolling()
// userId main.ts
}
@ -1018,11 +1517,31 @@ onMounted(() => { @@ -1018,11 +1517,31 @@ onMounted(() => {
onUnmounted(() => {
clearInterval(timer.value)
clearInterval(progressTimer.value)
stopRearViewPolling()
})
const searchResult = ref([])
// isViews 1
watch(() => isViews.value, (val) => {
if (val === 1 && pid.value) {
loadRearViewList()
} else {
stopRearViewPolling()
}
})
</script>
<style scoped>
/* 后视图标签页样式 */
.rear-view-record-section .el-tabs__item.is-active,
.rear-view-record-section .el-tabs__item:hover {
color: #07c160!important;
}
.rear-view-record-section .el-tabs__active-bar {
background-color: #07c160!important;
}
.preview-container {
min-height: 100vh;
font-family: 'PingFang SC', Arial, sans-serif;
@ -1481,4 +2000,143 @@ const searchResult = ref([]) @@ -1481,4 +2000,143 @@ const searchResult = ref([])
.van-address-edit {
padding: 0!important;
}
/* 后视图相关样式 */
.rear-view-textarea {
width: 100%;
}
.rear-view-record-section {
margin-top: 16px;
}
.rear-view-image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
padding: 10px 0;
max-height: 435px; /* 两行图片高度: 200px * 2 + 15px (gap) + 20px (padding) */
overflow-y: auto;
overflow-x: hidden;
}
.rear-view-image-item {
position: relative;
width: 100%;
height: 200px;
border: 2px solid transparent;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s;
}
.rear-view-image-item:hover {
border-color: #07c160;
}
.rear-view-image-item.active {
border-color: #67c23a;
box-shadow: 0 0 10px rgba(103, 194, 58, 0.3);
}
.rear-view-image-item.loading {
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f7fa;
border: 2px dashed #dcdfe6;
}
.rear-view-image-item .loading-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
height: 100%;
background-color: #f5f7fa;
color: #909399;
font-size: 14px;
}
.rear-view-image-item .loading-placeholder .el-icon {
font-size: 24px;
}
.rear-view-image {
width: 100%;
height: 100%;
}
.rear-view-image-actions {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
gap: 5px;
padding: 10px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
opacity: 0;
transition: opacity 0.3s;
}
.rear-view-image-item:hover .rear-view-image-actions,
.rear-view-image-item.active .rear-view-image-actions {
opacity: 1;
}
.rear-view-requirements-content {
padding: 20px 0;
}
.rear-view-requirements-content .requirements-text {
line-height: 1.6;
}
.rear-view-requirements-content .requirements-text p {
margin: 10px 0;
}
.preset-prompt-list {
max-height: 400px;
overflow-y: auto;
}
.preset-prompt-item {
padding: 15px;
margin-bottom: 10px;
border: 1px solid #dcdfe6;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
.preset-prompt-item:hover {
border-color: #07c160;
background-color: #f5f7fa;
}
.preset-prompt-item .prompt-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
}
.preset-prompt-item .prompt-content {
font-size: 13px;
color: #606266;
line-height: 1.6;
white-space: pre-wrap;
}
.empty-tips {
text-align: center;
padding: 40px 0;
color: #909399;
font-size: 14px;
}
</style>
Loading…
Cancel
Save