This commit is contained in:
parent
324b670ed5
commit
4c59a1349d
|
|
@ -128,6 +128,7 @@ window.setElLoading = (qp=false)=>{
|
||||||
}else{
|
}else{
|
||||||
loading.value = true
|
loading.value = true
|
||||||
}
|
}
|
||||||
|
window.closeMethods = closeMethods;
|
||||||
return closeMethods
|
return closeMethods
|
||||||
}
|
}
|
||||||
window.closeMethods = closeMethods;
|
window.closeMethods = closeMethods;
|
||||||
|
|
|
||||||
|
|
@ -797,7 +797,7 @@ export default {
|
||||||
animal: 'Animal',
|
animal: 'Animal',
|
||||||
person: 'Person',
|
person: 'Person',
|
||||||
general: 'General',
|
general: 'General',
|
||||||
O1: 'O1',
|
E1: 'E1',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
enterTitle: 'Please enter prompt title',
|
enterTitle: 'Please enter prompt title',
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
|
|
|
||||||
|
|
@ -791,7 +791,7 @@ orderManagement: {
|
||||||
animal: '动物',
|
animal: '动物',
|
||||||
person: '人物',
|
person: '人物',
|
||||||
general: '通用',
|
general: '通用',
|
||||||
O1: 'O1',
|
E1: 'E1',
|
||||||
title: '标题',
|
title: '标题',
|
||||||
enterTitle: '请输入提示词标题',
|
enterTitle: '请输入提示词标题',
|
||||||
content: '内容',
|
content: '内容',
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!-- 画布编辑器对话框 -->
|
<!-- 画布编辑器对话框 -->
|
||||||
<CanvasEditor
|
<DtCanvasEditor
|
||||||
v-model:visible="canvasEditorVisible"
|
v-model:visible="canvasEditorVisible"
|
||||||
:image-url="canvasEditorImageUrl"
|
:image-url="canvasEditorImageUrl"
|
||||||
@add-prompt-card="handleCanvasSave"
|
@add-prompt-card="handleCanvasSave"
|
||||||
|
|
@ -354,7 +354,6 @@ import {
|
||||||
import ModelViewer from '@/components/common/ModelViewer.vue'
|
import ModelViewer from '@/components/common/ModelViewer.vue'
|
||||||
import ModelCom from '@/components/modelCom/index.vue'
|
import ModelCom from '@/components/modelCom/index.vue'
|
||||||
import ImageWrapper from '@/components/disassembly/ImageWrapper.vue'
|
import ImageWrapper from '@/components/disassembly/ImageWrapper.vue'
|
||||||
import CanvasEditor from '@/components/common/CanvasEditor.vue'
|
|
||||||
import ModelUploadModal from '@/components/ModelUploadModal/index.vue'
|
import ModelUploadModal from '@/components/ModelUploadModal/index.vue'
|
||||||
import { AdminDisassemblyDetail } from './AdminDisassemblyDetail.js';
|
import { AdminDisassemblyDetail } from './AdminDisassemblyDetail.js';
|
||||||
import { MeshyServer,GiminiServer,FileServer } from '@deotaland/utils';
|
import { MeshyServer,GiminiServer,FileServer } from '@deotaland/utils';
|
||||||
|
|
@ -412,7 +411,6 @@ const handlePartialEdit = (imageUrl, index) => {
|
||||||
currentEditImageIndex.value = index;
|
currentEditImageIndex.value = index;
|
||||||
canvasEditorVisible.value = true;
|
canvasEditorVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCanvasSave = (editedImageUrl,editContent) => {
|
const handleCanvasSave = (editedImageUrl,editContent) => {
|
||||||
fileServer.uploadFile(editedImageUrl).then((url) => {
|
fileServer.uploadFile(editedImageUrl).then((url) => {
|
||||||
const newItem = {
|
const newItem = {
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
<el-option :label="t('admin.promptManagement.animal')" value="animal" />
|
<el-option :label="t('admin.promptManagement.animal')" value="animal" />
|
||||||
<el-option :label="t('admin.promptManagement.person')" value="person" />
|
<el-option :label="t('admin.promptManagement.person')" value="person" />
|
||||||
<el-option :label="t('admin.promptManagement.general')" value="general" />
|
<el-option :label="t('admin.promptManagement.general')" value="general" />
|
||||||
<el-option :label="t('admin.promptManagement.O1')" value="O1" />
|
<el-option :label="t('admin.promptManagement.E1')" value="E1" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -522,7 +522,6 @@ const formatDate = (dateString) => {
|
||||||
const beforeImageUpload = (file) => {
|
const beforeImageUpload = (file) => {
|
||||||
const isImage = file.type.startsWith('image/')
|
const isImage = file.type.startsWith('image/')
|
||||||
const isLt5M = file.size / 1024 / 1024 < 5
|
const isLt5M = file.size / 1024 / 1024 < 5
|
||||||
|
|
||||||
if (!isImage) {
|
if (!isImage) {
|
||||||
ElMessage.error(t('admin.productManagement.imageTypeError'))
|
ElMessage.error(t('admin.productManagement.imageTypeError'))
|
||||||
return false
|
return false
|
||||||
|
|
@ -539,12 +538,12 @@ const handleImageChange = (file) => {
|
||||||
if (!beforeImageUpload(file.raw)) {
|
if (!beforeImageUpload(file.raw)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const imageUrl = e.target.result
|
const imageUrl = e.target.result
|
||||||
fileServer.uploadFile(imageUrl).then((url) => {
|
fileServer.uploadFile(imageUrl).then((url) => {
|
||||||
formData.value.image = url
|
formData.value.image = url
|
||||||
|
window?.closeMethods?.close();
|
||||||
ElMessage.success(t('admin.productManagement.imageUploadSuccess'))
|
ElMessage.success(t('admin.productManagement.imageUploadSuccess'))
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error('图片上传失败:', error)
|
console.error('图片上传失败:', error)
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ export default defineConfig({
|
||||||
// 配置代理解决CORS问题
|
// 配置代理解决CORS问题
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'https://api.deotaland.ai',
|
// target: 'https://api.deotaland.ai',
|
||||||
// target: 'http://api.deotaland.local',
|
target: 'http://api.deotaland.local',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"@deotaland/utils": "workspace:*",
|
"@deotaland/utils": "workspace:*",
|
||||||
"@element-plus/icons-vue": "^2.3.2",
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"@google/genai": "^1.27.0",
|
"@google/genai": "^1.27.0",
|
||||||
|
"@splinetool/runtime": "^1.12.29",
|
||||||
"@twind/core": "^1.1.3",
|
"@twind/core": "^1.1.3",
|
||||||
"@twind/preset-autoprefix": "^1.0.7",
|
"@twind/preset-autoprefix": "^1.0.7",
|
||||||
"@twind/preset-tailwind": "^1.1.4",
|
"@twind/preset-tailwind": "^1.1.4",
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ window.setElLoading = (qp=false)=>{
|
||||||
}else{
|
}else{
|
||||||
loading.value = true
|
loading.value = true
|
||||||
}
|
}
|
||||||
|
window.closeMethods = closeMethods;
|
||||||
return closeMethods
|
return closeMethods
|
||||||
}
|
}
|
||||||
window.closeMethods = closeMethods;
|
window.closeMethods = closeMethods;
|
||||||
|
|
|
||||||
|
|
@ -1,559 +0,0 @@
|
||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
v-model="dialogVisible"
|
|
||||||
title="局部修改"
|
|
||||||
width="90%"
|
|
||||||
:fullscreen="isFullscreen"
|
|
||||||
append-to-body
|
|
||||||
destroy-on-close
|
|
||||||
@close="handleClose"
|
|
||||||
>
|
|
||||||
<div class="canvas-editor-container">
|
|
||||||
<div class="canvas-toolbar">
|
|
||||||
<div class="toolbar-section">
|
|
||||||
<span class="toolbar-label">画笔颜色:</span>
|
|
||||||
<div class="color-picker">
|
|
||||||
<div
|
|
||||||
v-for="color in colors"
|
|
||||||
:key="color"
|
|
||||||
:class="['color-option', { active: currentColor === color }]"
|
|
||||||
:style="{ backgroundColor: color }"
|
|
||||||
@click="selectColor(color)"
|
|
||||||
></div>
|
|
||||||
<el-color-picker
|
|
||||||
v-model="customColor"
|
|
||||||
size="small"
|
|
||||||
@change="selectCustomColor"
|
|
||||||
class="custom-color-picker"
|
|
||||||
></el-color-picker>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="toolbar-section">
|
|
||||||
<span class="toolbar-label">画笔大小:</span>
|
|
||||||
<el-slider
|
|
||||||
v-model="brushSize"
|
|
||||||
:min="1"
|
|
||||||
:max="50"
|
|
||||||
style="width: 120px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="toolbar-section">
|
|
||||||
<el-button
|
|
||||||
:type="isEraser ? 'primary' : 'default'"
|
|
||||||
size="small"
|
|
||||||
@click="toggleEraser"
|
|
||||||
>
|
|
||||||
<el-icon><Delete /></el-icon>
|
|
||||||
橡皮擦
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="warning"
|
|
||||||
size="small"
|
|
||||||
@click="clearCanvas"
|
|
||||||
>
|
|
||||||
<el-icon><RefreshLeft /></el-icon>
|
|
||||||
清空
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
size="small"
|
|
||||||
@click="handleSave"
|
|
||||||
>
|
|
||||||
<el-icon><Check /></el-icon>
|
|
||||||
确定
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="canvas-wrapper" ref="canvasWrapper">
|
|
||||||
<div v-if="imageLoading" class="loading-overlay">
|
|
||||||
<el-icon class="is-loading"><Loading /></el-icon>
|
|
||||||
<span>加载图片中...</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="imageLoadError" class="error-overlay">
|
|
||||||
<el-icon><Warning /></el-icon>
|
|
||||||
<p>图片加载失败</p>
|
|
||||||
<el-button type="primary" size="small" @click="retryLoadImage">重试</el-button>
|
|
||||||
<el-button size="small" @click="dialogVisible = false">关闭</el-button>
|
|
||||||
</div>
|
|
||||||
<canvas
|
|
||||||
v-show="!imageLoading && !imageLoadError"
|
|
||||||
ref="canvas"
|
|
||||||
@mousedown="startDrawing"
|
|
||||||
@mousemove="draw"
|
|
||||||
@mouseup="stopDrawing"
|
|
||||||
@mouseleave="stopDrawing"
|
|
||||||
@touchstart="handleTouchStart"
|
|
||||||
@touchmove="handleTouchMove"
|
|
||||||
@touchend="handleTouchEnd"
|
|
||||||
></canvas>
|
|
||||||
</div>
|
|
||||||
<div class="edit-textarea-container">
|
|
||||||
<el-input
|
|
||||||
v-model="editContent"
|
|
||||||
type="textarea"
|
|
||||||
:rows="3"
|
|
||||||
placeholder="请输入要修改的内容"
|
|
||||||
class="edit-textarea"
|
|
||||||
></el-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
|
||||||
import { Delete, RefreshLeft, Check, Loading, Warning } from '@element-plus/icons-vue'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
visible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
imageUrl: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:visible', 'add-prompt-card', 'close'])
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
const isFullscreen = ref(false)
|
|
||||||
const canvas = ref(null)
|
|
||||||
const canvasWrapper = ref(null)
|
|
||||||
const ctx = ref(null)
|
|
||||||
const isDrawing = ref(false)
|
|
||||||
const isEraser = ref(false)
|
|
||||||
const currentColor = ref('#6B46C1')
|
|
||||||
const customColor = ref('#6B46C1')
|
|
||||||
const brushSize = ref(5)
|
|
||||||
const imageLoading = ref(false)
|
|
||||||
const imageLoadError = ref(false)
|
|
||||||
const loadRetryCount = ref(0)
|
|
||||||
const maxRetryCount = 3
|
|
||||||
const editContent = ref('')
|
|
||||||
const colors = [
|
|
||||||
'#6B46C1',
|
|
||||||
'#EF4444',
|
|
||||||
'#F59E0B',
|
|
||||||
'#10B981',
|
|
||||||
'#3B82F6',
|
|
||||||
'#8B5CF6',
|
|
||||||
'#EC4899',
|
|
||||||
'#1F2937'
|
|
||||||
]
|
|
||||||
|
|
||||||
let lastX = 0
|
|
||||||
let lastY = 0
|
|
||||||
let backgroundImage = null
|
|
||||||
|
|
||||||
watch(() => props.visible, (val) => {
|
|
||||||
dialogVisible.value = val
|
|
||||||
if (val) {
|
|
||||||
nextTick(() => {
|
|
||||||
initCanvas()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(dialogVisible, (val) => {
|
|
||||||
emit('update:visible', val)
|
|
||||||
})
|
|
||||||
|
|
||||||
const initCanvas = async () => {
|
|
||||||
if (!canvas.value || !canvasWrapper.value) return
|
|
||||||
console.log('记载');
|
|
||||||
imageLoading.value = true
|
|
||||||
imageLoadError.value = false
|
|
||||||
|
|
||||||
try {
|
|
||||||
const img = new Image()
|
|
||||||
img.crossOrigin = 'anonymous'
|
|
||||||
const objectURL = await fetchImage(props.imageUrl)
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
img.onload = () => resolve()
|
|
||||||
img.onerror = () => reject(new Error('Failed to load image'))
|
|
||||||
img.src = objectURL
|
|
||||||
})
|
|
||||||
|
|
||||||
const canvasEl = canvas.value
|
|
||||||
|
|
||||||
canvasEl.width = img.width
|
|
||||||
canvasEl.height = img.height
|
|
||||||
|
|
||||||
ctx.value = canvasEl.getContext('2d')
|
|
||||||
|
|
||||||
backgroundImage = img
|
|
||||||
|
|
||||||
drawImage()
|
|
||||||
imageLoading.value = false
|
|
||||||
loadRetryCount.value = 0
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load image:', props.imageUrl, error)
|
|
||||||
imageLoading.value = false
|
|
||||||
imageLoadError.value = true
|
|
||||||
ElMessage.error('图片加载失败,请重试')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const retryLoadImage = () => {
|
|
||||||
if (loadRetryCount.value < maxRetryCount) {
|
|
||||||
loadRetryCount.value++
|
|
||||||
ElMessage.info(`正在重试加载图片 (${loadRetryCount.value}/${maxRetryCount})`)
|
|
||||||
initCanvas()
|
|
||||||
} else {
|
|
||||||
ElMessage.warning('已达到最大重试次数,请稍后再试')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const drawImage = () => {
|
|
||||||
if (!ctx.value || !backgroundImage || !canvas.value) return
|
|
||||||
|
|
||||||
const canvasEl = canvas.value
|
|
||||||
const img = backgroundImage
|
|
||||||
|
|
||||||
ctx.value.fillStyle = '#FFFFFF'
|
|
||||||
ctx.value.fillRect(0, 0, canvasEl.width, canvasEl.height)
|
|
||||||
|
|
||||||
ctx.value.drawImage(img, 0, 0, img.width, img.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectColor = (color) => {
|
|
||||||
currentColor.value = color
|
|
||||||
isEraser.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectCustomColor = (color) => {
|
|
||||||
if (color) {
|
|
||||||
currentColor.value = color
|
|
||||||
isEraser.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleEraser = () => {
|
|
||||||
isEraser.value = !isEraser.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearCanvas = () => {
|
|
||||||
if (!ctx.value || !canvas.value) return
|
|
||||||
|
|
||||||
ctx.value.fillStyle = '#FFFFFF'
|
|
||||||
ctx.value.fillRect(0, 0, canvas.value.width, canvas.value.height)
|
|
||||||
|
|
||||||
if (backgroundImage) {
|
|
||||||
ctx.value.drawImage(backgroundImage, 0, 0, backgroundImage.width, backgroundImage.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCanvasCoordinates = (clientX, clientY) => {
|
|
||||||
const rect = canvas.value.getBoundingClientRect()
|
|
||||||
const scaleX = canvas.value.width / rect.width
|
|
||||||
const scaleY = canvas.value.height / rect.height
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: (clientX - rect.left) * scaleX,
|
|
||||||
y: (clientY - rect.top) * scaleY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const startDrawing = (e) => {
|
|
||||||
isDrawing.value = true
|
|
||||||
const coords = getCanvasCoordinates(e.clientX, e.clientY)
|
|
||||||
lastX = coords.x
|
|
||||||
lastY = coords.y
|
|
||||||
}
|
|
||||||
|
|
||||||
const draw = (e) => {
|
|
||||||
if (!isDrawing.value || !ctx.value) return
|
|
||||||
|
|
||||||
const coords = getCanvasCoordinates(e.clientX, e.clientY)
|
|
||||||
|
|
||||||
ctx.value.beginPath()
|
|
||||||
ctx.value.moveTo(lastX, lastY)
|
|
||||||
ctx.value.lineTo(coords.x, coords.y)
|
|
||||||
ctx.value.strokeStyle = isEraser.value ? '#FFFFFF' : currentColor.value
|
|
||||||
ctx.value.lineWidth = brushSize.value
|
|
||||||
ctx.value.lineCap = 'round'
|
|
||||||
ctx.value.lineJoin = 'round'
|
|
||||||
ctx.value.stroke()
|
|
||||||
|
|
||||||
lastX = coords.x
|
|
||||||
lastY = coords.y
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopDrawing = () => {
|
|
||||||
isDrawing.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTouchStart = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
const touch = e.touches[0]
|
|
||||||
const coords = getCanvasCoordinates(touch.clientX, touch.clientY)
|
|
||||||
lastX = coords.x
|
|
||||||
lastY = coords.y
|
|
||||||
isDrawing.value = true
|
|
||||||
}
|
|
||||||
async function fetchImage(url) {
|
|
||||||
const cacheBusterUrl = url + '?v=1.0.0';
|
|
||||||
const response = await fetch(cacheBusterUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
mode: 'cors',
|
|
||||||
credentials: 'omit',
|
|
||||||
cache: 'no-cache'
|
|
||||||
});
|
|
||||||
const blob = await response.blob();
|
|
||||||
const imgurl = URL.createObjectURL(blob);
|
|
||||||
console.log(imgurl,'imgurlimgurlimgurl');
|
|
||||||
return imgurl;
|
|
||||||
}
|
|
||||||
const handleTouchMove = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (!isDrawing.value || !ctx.value) return
|
|
||||||
|
|
||||||
const touch = e.touches[0]
|
|
||||||
const coords = getCanvasCoordinates(touch.clientX, touch.clientY)
|
|
||||||
|
|
||||||
ctx.value.beginPath()
|
|
||||||
ctx.value.moveTo(lastX, lastY)
|
|
||||||
ctx.value.lineTo(coords.x, coords.y)
|
|
||||||
ctx.value.strokeStyle = isEraser.value ? '#FFFFFF' : currentColor.value
|
|
||||||
ctx.value.lineWidth = brushSize.value
|
|
||||||
ctx.value.lineCap = 'round'
|
|
||||||
ctx.value.lineJoin = 'round'
|
|
||||||
ctx.value.stroke()
|
|
||||||
|
|
||||||
lastX = coords.x
|
|
||||||
lastY = coords.y
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTouchEnd = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
isDrawing.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSave = () => {
|
|
||||||
const dataUrl = canvas.value.toDataURL('image/png')
|
|
||||||
emit('add-prompt-card', dataUrl,editContent.value)
|
|
||||||
dialogVisible.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
emit('close')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleResize = () => {
|
|
||||||
if (dialogVisible.value) {
|
|
||||||
nextTick(() => {
|
|
||||||
initCanvas()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
|
|
||||||
if (window.innerWidth < 768) {
|
|
||||||
isFullscreen.value = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('resize', handleResize)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.canvas-editor-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-toolbar {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px;
|
|
||||||
background: #f8fafc;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar-section {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar-label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1F2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-option {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-option:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-option.active {
|
|
||||||
border-color: #1F2937;
|
|
||||||
box-shadow: 0 0 0 2px rgba(107, 70, 193, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-color-picker {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-color-picker :deep(.el-color-picker__trigger) {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-color-picker :deep(.el-color-picker__color) {
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
height: 500px;
|
|
||||||
background: #f3f4f6;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-overlay,
|
|
||||||
.error-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 12px;
|
|
||||||
background: #f3f4f6;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-overlay .el-icon {
|
|
||||||
font-size: 32px;
|
|
||||||
color: #6B46C1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-overlay span {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-overlay .el-icon {
|
|
||||||
font-size: 48px;
|
|
||||||
color: #EF4444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-overlay p {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #1F2937;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
cursor: crosshair;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-textarea-container {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-textarea {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.canvas-toolbar {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar-section {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-wrapper {
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) and (max-width: 1024px) {
|
|
||||||
.canvas-wrapper {
|
|
||||||
height: 450px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .canvas-toolbar {
|
|
||||||
background: #1f1f1f;
|
|
||||||
border-color: #303030;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .toolbar-label {
|
|
||||||
color: #e5e7eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .canvas-wrapper {
|
|
||||||
background: #141414;
|
|
||||||
border-color: #303030;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .loading-overlay,
|
|
||||||
[data-theme="dark"] .error-overlay {
|
|
||||||
background: #141414;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .loading-overlay span,
|
|
||||||
[data-theme="dark"] .error-overlay p {
|
|
||||||
color: #e5e7eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .color-option.active {
|
|
||||||
border-color: #e5e7eb;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -37,8 +37,7 @@
|
||||||
<div class="guide-text-container">
|
<div class="guide-text-container">
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
<h2 class="guide-title">{{ step.title }}</h2>
|
<h2 class="guide-title">{{ step.title }}</h2>
|
||||||
<p class="guide-description">{{ step.description }}</p>
|
<p class="guide-description" v-html="step.description"></p>
|
||||||
|
|
||||||
<!-- 额外提示信息 -->
|
<!-- 额外提示信息 -->
|
||||||
<div v-if="step.tips" class="guide-tips">
|
<div v-if="step.tips" class="guide-tips">
|
||||||
<div class="tips-icon">💡</div>
|
<div class="tips-icon">💡</div>
|
||||||
|
|
@ -113,30 +112,30 @@ const guideSteps = computed(() => [
|
||||||
id: 1,
|
id: 1,
|
||||||
title: t('guideModal.step1.title'),
|
title: t('guideModal.step1.title'),
|
||||||
description: t('guideModal.step1.description'),
|
description: t('guideModal.step1.description'),
|
||||||
image: 'https://draft-user.s3.us-east-2.amazonaws.com/images/de7142df-ceb9-48f9-9367-af2a65e786a5.png',
|
image: 'https://draft-user.s3.us-east-2.amazonaws.com/images/f1cde4d7-bafd-41cb-8795-ad9ddcd521fb.png',
|
||||||
tips: t('guideModal.step1.tips')
|
tips: t('guideModal.step1.tips')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: t('guideModal.step2.title'),
|
title: t('guideModal.step2.title'),
|
||||||
description: t('guideModal.step2.description'),
|
description: t('guideModal.step2.description'),
|
||||||
image:'https://draft-user.s3.us-east-2.amazonaws.com/images/40773ee8-7f85-40c9-8c23-7ea1718e58a8.png',
|
image:'https://draft-user.s3.us-east-2.amazonaws.com/images/2ec4b422-3b42-4447-b049-6342184a5c50.png',
|
||||||
tips: t('guideModal.step2.tips')
|
tips: t('guideModal.step2.tips')
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
id: 3,
|
// id: 3,
|
||||||
title: t('guideModal.step3.title'),
|
// title: t('guideModal.step3.title'),
|
||||||
description: t('guideModal.step3.description'),
|
// description: t('guideModal.step3.description'),
|
||||||
image: 'https://draft-user.s3.us-east-2.amazonaws.com/images/47cdd95f-23fb-486b-bd19-5fca67c2ce45.png',
|
// image: 'https://draft-user.s3.us-east-2.amazonaws.com/images/47cdd95f-23fb-486b-bd19-5fca67c2ce45.png',
|
||||||
tips: t('guideModal.step3.tips')
|
// tips: t('guideModal.step3.tips')
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: 4,
|
// id: 4,
|
||||||
title: t('guideModal.step4.title'),
|
// title: t('guideModal.step4.title'),
|
||||||
description: t('guideModal.step4.description'),
|
// description: t('guideModal.step4.description'),
|
||||||
image: 'https://draft-user.s3.us-east-2.amazonaws.com/images/690f2da4-400e-4cb0-a815-246e614d79b1.png',
|
// image: 'https://draft-user.s3.us-east-2.amazonaws.com/images/690f2da4-400e-4cb0-a815-246e614d79b1.png',
|
||||||
tips: t('guideModal.step4.tips')
|
// tips: t('guideModal.step4.tips')
|
||||||
}
|
// }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 计算属性:当前步骤数据
|
// 计算属性:当前步骤数据
|
||||||
|
|
@ -312,7 +311,7 @@ const skipGuide = () => {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 350px;
|
max-height:350px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
|
@ -321,7 +320,7 @@ const skipGuide = () => {
|
||||||
.guide-image {
|
.guide-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: contain;
|
||||||
transition: transform 0.5s ease;
|
transition: transform 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@
|
||||||
<MagicStick />
|
<MagicStick />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span class="count-text">{{ t('header.remainingCredits') }}: {{ total_score }}</span>
|
<span class="count-text">{{ t('header.remainingCredits') }}: {{ total_score }}</span>
|
||||||
|
<button class="recharge-btn" @click="handleRecharge" :title="t('header.recharge')">
|
||||||
|
<span class="recharge-text">{{ t('header.recharge') }}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<LanguageToggle
|
<LanguageToggle
|
||||||
|
|
@ -64,6 +67,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { Picture, MagicStick, ArrowLeft, Edit, Check, Guide } from '@element-plus/icons-vue'
|
import { Picture, MagicStick, ArrowLeft, Edit, Check, Guide } from '@element-plus/icons-vue'
|
||||||
import { ElButton, ElIcon, ElInput } from 'element-plus'
|
import { ElButton, ElIcon, ElInput } from 'element-plus'
|
||||||
import ThemeToggle from '../ui/ThemeToggle.vue'
|
import ThemeToggle from '../ui/ThemeToggle.vue'
|
||||||
|
|
@ -79,14 +83,17 @@ const props = defineProps({
|
||||||
default: 'project'
|
default: 'project'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 编辑相关状态
|
const router = useRouter()
|
||||||
|
const { t, locale } = useI18n()
|
||||||
|
|
||||||
|
const handleRecharge = () => {
|
||||||
|
router.push('/points-recharge')
|
||||||
|
}
|
||||||
|
|
||||||
const isEditing = ref(false)
|
const isEditing = ref(false)
|
||||||
const editedProjectName = ref('')
|
const editedProjectName = ref('')
|
||||||
const editInput = ref(null)
|
const editInput = ref(null)
|
||||||
|
|
||||||
// 国际化支持
|
|
||||||
const { t, locale } = useI18n()
|
|
||||||
|
|
||||||
// 语言切换功能已由LanguageToggle组件内部处理
|
// 语言切换功能已由LanguageToggle组件内部处理
|
||||||
// 处理返回按钮点击
|
// 处理返回按钮点击
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
|
|
@ -412,6 +419,48 @@ html.dark .project-name-input :deep(.el-input__wrapper:focus) {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recharge-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: none;
|
||||||
|
background-color: #6B46C1;
|
||||||
|
color: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recharge-btn:hover {
|
||||||
|
background-color: #7C3AED;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(107, 70, 193, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recharge-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recharge-text {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .recharge-btn {
|
||||||
|
background-color: #8B5CF6;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .recharge-btn:hover {
|
||||||
|
background-color: #A78BFA;
|
||||||
|
box-shadow: 0 2px 8px rgba(139, 92, 246, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
/* 指南按钮样式 */
|
/* 指南按钮样式 */
|
||||||
.guide-btn {
|
.guide-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -508,6 +557,11 @@ html.dark .count-text {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recharge-btn {
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.count-icon {
|
.count-icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
ref="textInputRef"
|
ref="textInputRef"
|
||||||
v-model="textInputValue"
|
v-model="textInputValue"
|
||||||
class="text-input-area"
|
class="text-input-area"
|
||||||
placeholder="请输入文本内容..."
|
:placeholder="t('modelModal.textInputPlaceholder')"
|
||||||
rows="4"
|
rows="4"
|
||||||
@keydown.enter.ctrl="handleTextInputConfirm"
|
@keydown.enter.ctrl="handleTextInputConfirm"
|
||||||
@keydown.esc="handleTextInputCancel"
|
@keydown.esc="handleTextInputCancel"
|
||||||
|
|
@ -53,6 +53,9 @@
|
||||||
<!-- <el-skeleton-item variant="text" style="width:100%;height: 100%;" /> -->
|
<!-- <el-skeleton-item variant="text" style="width:100%;height: 100%;" /> -->
|
||||||
</template>
|
</template>
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
|
<button v-if="formData.internalImageUrl" class="customize-to-home-btn" @click="handleCustomizeToHome" @touchend.prevent="handleCustomizeToHome">
|
||||||
|
{{ t('modelModal.customizeToHome') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧控件区域 -->
|
<!-- 右侧控件区域 -->
|
||||||
|
|
@ -71,6 +74,10 @@
|
||||||
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="scene graph" @click="handleTextcjt" @touchend.prevent="handleTextcjt">
|
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="scene graph" @click="handleTextcjt" @touchend.prevent="handleTextcjt">
|
||||||
<el-icon class="btn-icon"><Grid /></el-icon>
|
<el-icon class="btn-icon"><Grid /></el-icon>
|
||||||
</button>
|
</button>
|
||||||
|
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="Picture board editor" @click="handlePartialEdit" @touchend.prevent="handlePartialEdit">
|
||||||
|
<el-icon class="btn-icon"><EditPen /></el-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,6 +87,7 @@
|
||||||
import {cjt} from './tsc.js'
|
import {cjt} from './tsc.js'
|
||||||
// import cjimg from '@/assets/sketches/cjt.png';
|
// import cjimg from '@/assets/sketches/cjt.png';
|
||||||
import { computed, ref, onMounted, watch, nextTick } from 'vue';
|
import { computed, ref, onMounted, watch, nextTick } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { GiminiServer } from '@deotaland/utils';
|
import { GiminiServer } from '@deotaland/utils';
|
||||||
// import humanTypeImg from '@/assets/sketches/tcww.png'
|
// import humanTypeImg from '@/assets/sketches/tcww.png'
|
||||||
// import humanTypeImg from '@/assets/sketches/tcww2.webp'
|
// import humanTypeImg from '@/assets/sketches/tcww2.webp'
|
||||||
|
|
@ -87,8 +95,9 @@ import { GiminiServer } from '@deotaland/utils';
|
||||||
// import anTypeImg from '@/assets/sketches/dwww2.png';
|
// import anTypeImg from '@/assets/sketches/dwww2.png';
|
||||||
// import cz2 from '@/assets/material/cz2.png';
|
// import cz2 from '@/assets/material/cz2.png';
|
||||||
// 引入Element Plus图标库和组件
|
// 引入Element Plus图标库和组件
|
||||||
import { Cpu, ChatDotRound, CloseBold,Grid,View } from '@element-plus/icons-vue'
|
import { Cpu, ChatDotRound, CloseBold,Grid,View,EditPen } from '@element-plus/icons-vue'
|
||||||
import { ElIcon,ElMessage,ElSkeleton,ElImage } from 'element-plus'
|
import { ElIcon,ElMessage,ElSkeleton,ElImage } from 'element-plus'
|
||||||
|
const { t } = useI18n();
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
internalImageUrl: '',//内部图片URL
|
internalImageUrl: '',//内部图片URL
|
||||||
status:'loading',//状态
|
status:'loading',//状态
|
||||||
|
|
@ -138,6 +147,12 @@ const handleTextcjt = ()=>{
|
||||||
cardData: props.cardData
|
cardData: props.cardData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const handleCustomizeToHome = () => {
|
||||||
|
emit('customize-to-home', {
|
||||||
|
imageUrl: formData.value.internalImageUrl,
|
||||||
|
cardData: props.cardData
|
||||||
|
});
|
||||||
|
}
|
||||||
// 处理文本输入确认
|
// 处理文本输入确认
|
||||||
const handleTextInputConfirm = () => {
|
const handleTextInputConfirm = () => {
|
||||||
// 触发创建新卡片事件,传递用户输入的文本内容
|
// 触发创建新卡片事件,传递用户输入的文本内容
|
||||||
|
|
@ -173,7 +188,7 @@ const handleTouchEnd = () => {
|
||||||
};
|
};
|
||||||
// 定义组件属性
|
// 定义组件属性
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
combinedPromptJson:{
|
combinedPromptJson:{//动态提示词
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
},
|
||||||
|
|
@ -210,8 +225,10 @@ const props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits(['generate-model-requested', 'create-new-card','create-prompt-card','delete','preview-image']);
|
const emit = defineEmits(['generate-model-requested', 'create-new-card','create-prompt-card','delete','preview-image','customize-to-home','handlePartialEdit']);
|
||||||
|
const handlePartialEdit = ()=>{
|
||||||
|
emit('handlePartialEdit', formData.value.internalImageUrl);
|
||||||
|
}
|
||||||
// 处理图片生成
|
// 处理图片生成
|
||||||
const handleGenerateImage = async () => {
|
const handleGenerateImage = async () => {
|
||||||
const iscjt = props?.cardData?.diyPromptText&&props?.cardData?.diyPromptText?.indexOf('[CJT_DEOTA]')!=-1;
|
const iscjt = props?.cardData?.diyPromptText&&props?.cardData?.diyPromptText?.indexOf('[CJT_DEOTA]')!=-1;
|
||||||
|
|
@ -222,72 +239,32 @@ const handleGenerateImage = async () => {
|
||||||
if (props?.cardData?.inspirationImage) {
|
if (props?.cardData?.inspirationImage) {
|
||||||
referenceImages.push(props.cardData.inspirationImage);
|
referenceImages.push(props.cardData.inspirationImage);
|
||||||
}
|
}
|
||||||
if(props?.cardData?.ipType){
|
|
||||||
if(props?.cardData?.ipType==1){
|
|
||||||
humanTypeImg&&referenceImages.push(humanTypeImg);
|
|
||||||
}else{
|
|
||||||
anTypeImg&&referenceImages.push(anTypeImg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(iscjt){
|
if(iscjt){
|
||||||
props.cardData.imgyt&&referenceImages.push(props.cardData.imgyt);
|
props.cardData.imgyt&&referenceImages.push(props.cardData.imgyt);
|
||||||
referenceImages.push(props.cardData.diyPromptImg);
|
referenceImages.push(props.cardData.diyPromptImg);
|
||||||
referenceImages.push(cjimg);
|
referenceImages.push(cjimg);
|
||||||
}
|
}
|
||||||
// if(props.cardData.diyPromptText){
|
if(props.cardData.diyPromptText){
|
||||||
// console.log(props.cardData.diyPromptImg,'diyPromptImgdiyPromptImgdiyPromptImg');
|
referenceImages.push(props.cardData.diyPromptImg);
|
||||||
// referenceImages.push(props.cardData.diyPromptImg);
|
}
|
||||||
// if(iscjt){
|
let dtprompt;
|
||||||
// props.cardData.imgyt&&referenceImages.push(props.cardData.imgyt);
|
if(props?.cardData?.ipType==1){
|
||||||
// referenceImages.push(cjimg);
|
dtprompt = props.combinedPromptJson.person.content;
|
||||||
// }
|
referenceImages.push(...props.combinedPromptJson.person.imgs);
|
||||||
// }else{
|
}else if(props?.cardData?.ipType==2){
|
||||||
// referenceImages.push(humanTypeImg);
|
dtprompt = props.combinedPromptJson.animal.content;
|
||||||
// referenceImages.push(anTypeImg);
|
referenceImages.push(...props.combinedPromptJson.animal.imgs);
|
||||||
// }
|
}
|
||||||
// referenceImages.push(cz2);
|
if(props.cardData.prompt){
|
||||||
// referenceImages.push(humanTypeImg);
|
dtprompt = `角色外观:${props.cardData.prompt}.${dtprompt}`
|
||||||
// if(props?.cardData?.selectedExpression){
|
}
|
||||||
// referenceImages.push(props.cardData.selectedExpression.imageUrl);
|
let prompt = props.cardData.diyPromptText|| dtprompt
|
||||||
// }
|
|
||||||
// 忽略第二张参考图
|
|
||||||
let prompt = props.cardData.diyPromptText|| `
|
|
||||||
首先保证生成的角色符合以下要求
|
|
||||||
角色肤色和衣服材质都为纯色一种颜色如下:
|
|
||||||
重点:保证角色所有的服饰衣服都为木头材质颜色,并且要带一些木头纹理,颜色为#e2cfb3
|
|
||||||
一个通体由单一纯色木材雕刻而成的角色,全身无布料、无皮肤、无金属,表面光滑,颜色均匀一致,无纹理变化,整体呈现木质雕塑或木偶风格,极简设计.
|
|
||||||
A full-body character portrait
|
|
||||||
角色特征:Q 版萌系造型,头身比例夸张(大头小身),神态纯真,服饰设计融合童话风与复古感(简化一下复杂衣服纹理,只保留特征).
|
|
||||||
Style:潮玩盲盒角色设计,采用 3D 立体建模渲染,呈现细腻的质感与精致的细节。
|
|
||||||
${props?.cardData?.prompt? `Appearance: ${props?.cardData?.prompt}.`:``}
|
|
||||||
Note: The image should not have white borders.
|
|
||||||
去除原图中复杂的背景,只保留人物角色的主体。
|
|
||||||
适配3D打印:请保持服装边缘、装饰等细节略微加厚、避免过细结构,以提高打印稳定性,手指头轮廓清晰,重点:保证角色全身包括衣服都为木头材质颜色,并且要带一些木头纹理,颜色为#e2cfb3。
|
|
||||||
【3D打印结构优化】
|
|
||||||
模型用于3D打印,必须保持结构厚实、稳定,无细小悬空部件或过薄结构。
|
|
||||||
不生成透明或复杂内构。
|
|
||||||
保持厚度和连贯性,适合打印。
|
|
||||||
【材质处理】
|
|
||||||
整体需光滑、稳固、边缘柔和,防止打印时断裂。
|
|
||||||
模型应呈现专业3D效果。
|
|
||||||
${props.cardData?.ipType==1?`
|
|
||||||
调整角色的发型,使其厚实、蓬松且结构坚固,轮廓清晰扎实,适合3D打印。
|
|
||||||
确保头发具备足够的厚度与结构完整性,避免在打印过程中出现脆弱断裂,同时保留原有的可爱美感。
|
|
||||||
头发纹理细节需针对3D制造进行优化——层次平滑且分明,兼顾视觉吸引力与可打印性,维持整体俏皮且高品质的盲盒角色风格。
|
|
||||||
`:`采用疯狂动物城的设计风格`}
|
|
||||||
调整背景为极简风格,换成中性纯白色,让图片中的人物呈现3D立体效果。
|
|
||||||
保证生成的图片一定要有眼睛,一定要有嘴巴,眼睛效果要可爱童真,Q版大眼睛。
|
|
||||||
角色肤色和衣服材质都为纯色一种颜色如下:
|
|
||||||
保证角色全身都为木头材质颜色,并且要带一些木头纹理,颜色为#e2cfb3。
|
|
||||||
衣服如果不适合做木制一定要简化衣服,不能用复杂的衣服设计,保留衣服特征即可,衣服一定要纯色木质材质。
|
|
||||||
保证角色所有的服饰衣服都为木头材质颜色,并且要带一些木头纹理,颜色为#e2cfb3。
|
|
||||||
`
|
|
||||||
;
|
|
||||||
// 角色姿势:${props.cardData.ipType==1?``:``}
|
// 角色姿势:${props.cardData.ipType==1?``:``}
|
||||||
|
|
||||||
if(props.cardData.prompt&&props.cardData.prompt.indexOf('nospec')!=-1){
|
if(props.cardData.prompt&&props.cardData.prompt.indexOf('nospec')!=-1){
|
||||||
prompt = '按原图生成'
|
prompt = '按原图生成'
|
||||||
referenceImages = [props.cardData.inspirationImage];
|
referenceImages = [props.cardData.inspirationImage];
|
||||||
|
formData.value.internalImageUrl = props?.cardData?.inspirationImage;
|
||||||
|
return
|
||||||
}
|
}
|
||||||
const taskResult = await giminiServer.handleGenerateImage(referenceImages, prompt,{
|
const taskResult = await giminiServer.handleGenerateImage(referenceImages, prompt,{
|
||||||
project_id: props.cardData.project_id,
|
project_id: props.cardData.project_id,
|
||||||
|
|
@ -301,6 +278,7 @@ const handleGenerateImage = async () => {
|
||||||
formData.value.status = 'success';
|
formData.value.status = 'success';
|
||||||
saveProject();
|
saveProject();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
emit('delete');
|
emit('delete');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -686,6 +664,77 @@ const handleImageLoad = (event) => {
|
||||||
border: 2px solid rgba(167, 139, 250, 0.2);
|
border: 2px solid rgba(167, 139, 250, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ip-card-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-to-home-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 16px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
min-width: 120px;
|
||||||
|
padding: 10px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.25);
|
||||||
|
background: rgba(107, 70, 193, 0.9);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
box-shadow: 0 4px 16px rgba(107, 70, 193, 0.3);
|
||||||
|
z-index: 5;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-card-container:hover .customize-to-home-btn,
|
||||||
|
.ip-card-container.controls-visible .customize-to-home-btn {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-to-home-btn:hover {
|
||||||
|
background: rgba(139, 92, 246, 1);
|
||||||
|
border-color: rgba(139, 92, 246, 0.5);
|
||||||
|
box-shadow: 0 6px 20px rgba(107, 70, 193, 0.4);
|
||||||
|
transform: translateX(-50%) translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .customize-to-home-btn {
|
||||||
|
background: rgba(139, 92, 246, 0.85);
|
||||||
|
border-color: rgba(167, 139, 250, 0.4);
|
||||||
|
box-shadow: 0 4px 16px rgba(139, 92, 246, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .customize-to-home-btn:hover {
|
||||||
|
background: rgba(167, 139, 250, 0.95);
|
||||||
|
border-color: rgba(167, 139, 250, 0.6);
|
||||||
|
box-shadow: 0 6px 20px rgba(139, 92, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) and (pointer: coarse) {
|
||||||
|
.ip-card-container:active .customize-to-home-btn,
|
||||||
|
.ip-card-container.controls-visible .customize-to-home-btn {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.customize-to-home-btn {
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 右侧控件容器 - 使用绝对定位避免布局重排 */
|
/* 右侧控件容器 - 使用绝对定位避免布局重排 */
|
||||||
.right-controls-container {
|
.right-controls-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
|
|
@ -53,31 +53,43 @@
|
||||||
<!-- <el-skeleton-item variant="text" style="width:100%;height: 100%;" /> -->
|
<!-- <el-skeleton-item variant="text" style="width:100%;height: 100%;" /> -->
|
||||||
</template>
|
</template>
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
<button v-if="formData.internalImageUrl" class="customize-to-home-btn" @click="handleCustomizeToHome" @touchend.prevent="handleCustomizeToHome">
|
<button v-if="formData.internalImageUrl&&!(props.cardData.diyPromptText==cjt)" class="customize-to-home-btn" @click="handleCustomizeToHome" @touchend.prevent="handleCustomizeToHome">
|
||||||
{{ t('modelModal.customizeToHome') }}
|
{{ t('modelModal.customizeToHome') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧控件区域 -->
|
<!-- 上侧控件区域 -->
|
||||||
<div class="right-controls-container" v-if="formData.internalImageUrl">
|
<div class="top-controls-container" v-if="formData.internalImageUrl">
|
||||||
<!-- 右侧圆形按钮控件 -->
|
<!-- 上侧按钮控件 -->
|
||||||
<div class="right-circular-controls">
|
<div class="top-controls">
|
||||||
<button class="control-button share-btn" title="Preview Image" @click="handleImageClick" @touchend.prevent="handleImageClick">
|
<button class="control-item" title="Preview Image" @click="handleImageClick" @touchend.prevent="handleImageClick">
|
||||||
<el-icon class="btn-icon"><View /></el-icon>
|
<span class="control-text">{{ t('modelModal.preview') }}</span>
|
||||||
|
<el-icon class="control-icon"><View /></el-icon>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="model" @click="handleGenerateModel" @touchend.prevent="handleGenerateModel">
|
<!-- <button v-if="!(props.cardData.imgyt)" class="control-item" title="model" @click="handleGenerateModel" @touchend.prevent="handleGenerateModel">
|
||||||
<el-icon class="btn-icon"><Cpu /></el-icon>
|
<span class="control-text">模型</span>
|
||||||
|
<el-icon class="control-icon"><Cpu /></el-icon>
|
||||||
|
</button> -->
|
||||||
|
<button v-if="!(props.cardData.diyPromptText==cjt)" class="control-item" title="textInput" @click="toggleTextInput" @touchend.prevent="toggleTextInput">
|
||||||
|
<span class="control-text">{{ t('modelModal.modify') }}</span>
|
||||||
|
<el-icon class="control-icon"><ChatDotRound /></el-icon>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="textInput" @click="toggleTextInput" @touchend.prevent="toggleTextInput">
|
<button v-if="!(props.cardData.diyPromptText==cjt)" class="control-item" title="Picture board editor" @click="handlePartialEdit" @touchend.prevent="handlePartialEdit">
|
||||||
<el-icon class="btn-icon"><ChatDotRound /></el-icon>
|
<span class="control-text">{{ t('modelModal.edit') }}</span>
|
||||||
|
<el-icon class="control-icon"><EditPen /></el-icon>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="scene graph" @click="handleTextcjt" @touchend.prevent="handleTextcjt">
|
<button v-if="!(props.cardData.diyPromptText==cjt)" class="control-item" title="scene graph" @click="handleTextcjt" @touchend.prevent="handleTextcjt">
|
||||||
<el-icon class="btn-icon"><Grid /></el-icon>
|
<span class="control-text">{{ t('modelModal.sceneGraph') }}</span>
|
||||||
|
<el-icon class="control-icon"><Grid /></el-icon>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="Picture board editor" @click="handlePartialEdit" @touchend.prevent="handlePartialEdit">
|
<button class="control-item" title="Download" @click="downloadImage" @touchend.prevent="downloadImage">
|
||||||
<el-icon class="btn-icon"><EditPen /></el-icon>
|
<span class="control-text">{{ t('modelModal.download') }}</span>
|
||||||
|
<el-icon class="control-icon"><Download /></el-icon>
|
||||||
|
</button>
|
||||||
|
<button class="control-item" title="Delete" @click="handleDeleteCard" @touchend.prevent="handleDeleteCard">
|
||||||
|
<span class="control-text">{{ t('common.close') }}</span>
|
||||||
|
<el-icon class="control-icon"><Delete /></el-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -95,7 +107,7 @@ import { GiminiServer } from '@deotaland/utils';
|
||||||
// import anTypeImg from '@/assets/sketches/dwww2.png';
|
// import anTypeImg from '@/assets/sketches/dwww2.png';
|
||||||
// import cz2 from '@/assets/material/cz2.png';
|
// import cz2 from '@/assets/material/cz2.png';
|
||||||
// 引入Element Plus图标库和组件
|
// 引入Element Plus图标库和组件
|
||||||
import { Cpu, ChatDotRound, CloseBold,Grid,View,EditPen } from '@element-plus/icons-vue'
|
import { Cpu, ChatDotRound, CloseBold,Grid,View,EditPen,Download,Delete } from '@element-plus/icons-vue'
|
||||||
import { ElIcon,ElMessage,ElSkeleton,ElImage } from 'element-plus'
|
import { ElIcon,ElMessage,ElSkeleton,ElImage } from 'element-plus'
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
|
|
@ -225,10 +237,17 @@ const props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits(['generate-model-requested', 'create-new-card','create-prompt-card','delete','preview-image','customize-to-home','handlePartialEdit']);
|
const emit = defineEmits(['generate-model-requested', 'create-new-card','create-prompt-card','delete','preview-image','customize-to-home','handlePartialEdit','download-image','delete-card']);
|
||||||
const handlePartialEdit = ()=>{
|
const handlePartialEdit = ()=>{
|
||||||
emit('handlePartialEdit', formData.value.internalImageUrl);
|
emit('handlePartialEdit', formData.value.internalImageUrl);
|
||||||
}
|
}
|
||||||
|
//删除
|
||||||
|
const handleDeleteCard = ()=>{
|
||||||
|
emit('delete-card');
|
||||||
|
}
|
||||||
|
const downloadImage = async ()=>{
|
||||||
|
emit('download-image', formData.value.internalImageUrl);
|
||||||
|
}
|
||||||
// 处理图片生成
|
// 处理图片生成
|
||||||
const handleGenerateImage = async () => {
|
const handleGenerateImage = async () => {
|
||||||
const iscjt = props?.cardData?.diyPromptText&&props?.cardData?.diyPromptText?.indexOf('[CJT_DEOTA]')!=-1;
|
const iscjt = props?.cardData?.diyPromptText&&props?.cardData?.diyPromptText?.indexOf('[CJT_DEOTA]')!=-1;
|
||||||
|
|
@ -735,90 +754,107 @@ const handleImageLoad = (event) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 右侧控件容器 - 使用绝对定位避免布局重排 */
|
/* 上侧控件容器 - 使用绝对定位避免布局重排 */
|
||||||
.right-controls-container {
|
.top-controls-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 100%;
|
top: 100%;
|
||||||
top: 50%;
|
left: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateX(-50%);
|
||||||
margin-left: 24px;
|
margin-top: 16px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 右侧圆形按钮控件 */
|
/* 上侧按钮控件 */
|
||||||
.right-circular-controls {
|
.top-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
align-items: flex-start;
|
gap: 12px;
|
||||||
gap: 16px;
|
padding: 12px 16px;
|
||||||
|
background: linear-gradient(135deg, rgba(107, 70, 193, 0.95) 0%, rgba(139, 92, 246, 0.9) 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(167, 139, 250, 0.3);
|
||||||
|
box-shadow: 0 8px 24px rgba(107, 70, 193, 0.3);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(-20px);
|
transform: translateY(-10px);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 当鼠标悬停在卡片容器上时显示控件 */
|
/* 当鼠标悬停在卡片容器上时显示控件 */
|
||||||
.ip-card-container:hover .right-circular-controls {
|
.ip-card-container:hover .top-controls {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0);
|
transform: translateY(0);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 在 iPad 及更大触控屏上,按下即显示右侧圆形控件 */
|
/* 在 iPad 及更大触控屏上,按下即显示上侧控件 */
|
||||||
@media (hover: none) and (pointer: coarse) {
|
@media (hover: none) and (pointer: coarse) {
|
||||||
.ip-card-container:active .right-circular-controls,
|
.ip-card-container:active .top-controls,
|
||||||
.ip-card-container.controls-visible .right-circular-controls {
|
.ip-card-container.controls-visible .top-controls {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0);
|
transform: translateY(0);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 非触控设备保持 hover 显示 */
|
/* 非触控设备保持 hover 显示 */
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.ip-card-container:hover .right-circular-controls {
|
.ip-card-container:hover .top-controls {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0);
|
transform: translateY(0);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移动端适配:点击卡片后显示功能按钮 */
|
/* 移动端适配:点击卡片后显示功能按钮 */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
/* 点击卡片容器时显示功能按钮 */
|
/* 点击卡片容器时显示功能按钮 */
|
||||||
.ip-card-container:active .right-circular-controls,
|
.ip-card-container:active .top-controls,
|
||||||
.ip-card-container.controls-visible .right-circular-controls {
|
.ip-card-container.controls-visible .top-controls {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0);
|
transform: translateY(0);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-button {
|
.control-item {
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.15) 0%, rgba(107, 70, 193, 0.1) 100%);
|
|
||||||
border: 1px solid rgba(167, 139, 250, 0.3);
|
|
||||||
color: #A78BFA;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
color: #ffffff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-button:hover {
|
.control-item:hover {
|
||||||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.25) 0%, rgba(107, 70, 193, 0.2) 100%);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
border-color: rgba(255, 255, 255, 0.4);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 12px rgba(167, 139, 250, 0.3);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-button:active {
|
.control-item:active {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon {
|
.control-text {
|
||||||
font-size: 20px;
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ip-card:hover {
|
.ip-card:hover {
|
||||||
|
|
@ -1016,21 +1052,32 @@ const handleImageLoad = (event) => {
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-controls-container {
|
.top-controls-container {
|
||||||
position: static;
|
position: static;
|
||||||
transform: none;
|
transform: none;
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-circular-controls {
|
.top-controls {
|
||||||
flex-direction: row;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: none;
|
transform: none;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-item {
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-icon {
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-actions-controls {
|
.right-actions-controls {
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,8 @@ onMounted(()=>{
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
/* background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%); */
|
||||||
|
background-color: #fdfdfd;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
||||||
|
|
@ -800,9 +800,11 @@ const handleFileChange = async (event) => {
|
||||||
try {
|
try {
|
||||||
const imgUrl = await filePlug.uploadFile(file);
|
const imgUrl = await filePlug.uploadFile(file);
|
||||||
formData.value.previewImage = imgUrl;
|
formData.value.previewImage = imgUrl;
|
||||||
|
window?.closeMethods.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('图片上传失败:', error);
|
console.error('图片上传失败:', error);
|
||||||
ElMessage.error('图片上传失败,请重试');
|
ElMessage.error('图片上传失败,请重试');
|
||||||
|
window?.closeMethods.close();
|
||||||
} finally {
|
} finally {
|
||||||
isUploading.value = false;
|
isUploading.value = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -643,7 +643,7 @@ onUnmounted(() => {
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
z-index: 1000;
|
z-index: 20;
|
||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
box-shadow: 4px 0 24px rgba(107, 70, 193, 0.2);
|
box-shadow: 4px 0 24px rgba(107, 70, 193, 0.2);
|
||||||
/* transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); */
|
/* transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); */
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ watch(() => window.location.pathname, () => {
|
||||||
top: 0;
|
top: 0;
|
||||||
/* left: -120px; */
|
/* left: -120px; */
|
||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
z-index: 999;
|
z-index: 99;
|
||||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -125,7 +125,12 @@ export default {
|
||||||
},
|
},
|
||||||
modelModal: {
|
modelModal: {
|
||||||
customizeToHome: '定制到家',
|
customizeToHome: '定制到家',
|
||||||
textInputPlaceholder: '请输入调整内容,例如:更改角色表情'
|
textInputPlaceholder: '请输入调整内容,例如:更改角色表情',
|
||||||
|
preview: '预览',
|
||||||
|
modify: '修改',
|
||||||
|
sceneGraph: '场景图',
|
||||||
|
edit: '编辑',
|
||||||
|
download: '下载'
|
||||||
},
|
},
|
||||||
modelCard: {
|
modelCard: {
|
||||||
generateModelButton: '生成模型',
|
generateModelButton: '生成模型',
|
||||||
|
|
@ -212,6 +217,7 @@ export default {
|
||||||
modelFreeCount: '模型免费',
|
modelFreeCount: '模型免费',
|
||||||
times: '次',
|
times: '次',
|
||||||
remainingCredits: '剩余积分',
|
remainingCredits: '剩余积分',
|
||||||
|
recharge: '充值',
|
||||||
guide: '使用指南',
|
guide: '使用指南',
|
||||||
back: '返回',
|
back: '返回',
|
||||||
skip: '跳过',
|
skip: '跳过',
|
||||||
|
|
@ -334,13 +340,16 @@ export default {
|
||||||
guideModal: {
|
guideModal: {
|
||||||
step1: {
|
step1: {
|
||||||
title: '参考图片',
|
title: '参考图片',
|
||||||
description: '选择您喜欢的图片作为创作参考',
|
description: '输入文字描述您的IP,或者选择您喜欢的图片作为创作参考',
|
||||||
tips: '点击生成按钮后,平台会根据您的选择生成相应的3D模型。'
|
tips: '点击生成按钮后,平台会根据您的选择生成相应的图片。'
|
||||||
},
|
},
|
||||||
step2: {
|
step2: {
|
||||||
title: '模型生成/文字优化',
|
title: '图片节点按钮控件',
|
||||||
description: '根据您的参考图片,平台会生成对应的3D模型。',
|
description: `
|
||||||
tips: '您也可以输入文字描述,平台会根据您的需求进行图片优化。'
|
①.<text style="font-weight: bold;">customizeToHome</text>:将您的IP角色定制为您的专属3D模型机器人。<br/>
|
||||||
|
②.<text style="font-weight: bold;">controls</text>:图片节点控件,支持修改预览和删除等操作<br/>
|
||||||
|
`,
|
||||||
|
tips: '您通过画布或者聊天框修改图片,平台会根据您的需求进行图片优化。'
|
||||||
},
|
},
|
||||||
step3: {
|
step3: {
|
||||||
title: '查看详情',
|
title: '查看详情',
|
||||||
|
|
@ -1088,6 +1097,7 @@ export default {
|
||||||
currentPoints: '当前积分',
|
currentPoints: '当前积分',
|
||||||
expiryDate: '积分到期时间',
|
expiryDate: '积分到期时间',
|
||||||
pointsList: '积分明细',
|
pointsList: '积分明细',
|
||||||
|
recharge: '充值',
|
||||||
consumptionRules: {
|
consumptionRules: {
|
||||||
title: '积分消耗规则',
|
title: '积分消耗规则',
|
||||||
behavior: '行为',
|
behavior: '行为',
|
||||||
|
|
@ -1166,10 +1176,10 @@ export default {
|
||||||
},
|
},
|
||||||
premium: {
|
premium: {
|
||||||
name: '高级套餐',
|
name: '高级套餐',
|
||||||
badge: '热门'
|
badge: '推荐'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
period: '年',
|
period: '天',
|
||||||
points: '积分',
|
points: '积分',
|
||||||
validity: '有效期',
|
validity: '有效期',
|
||||||
features: {
|
features: {
|
||||||
|
|
@ -1296,7 +1306,8 @@ export default {
|
||||||
goHome: '返回首页',
|
goHome: '返回首页',
|
||||||
submitInviteCode: '提交邀请码',
|
submitInviteCode: '提交邀请码',
|
||||||
hasInviteCode: '已有邀请码?',
|
hasInviteCode: '已有邀请码?',
|
||||||
inviteCodePlaceholder: '填写邀请码升级为免费会员'
|
inviteCodePlaceholder: '填写邀请码升级为免费会员',
|
||||||
|
logout: '退出登录'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
|
|
@ -1326,10 +1337,10 @@ export default {
|
||||||
},
|
},
|
||||||
premium: {
|
premium: {
|
||||||
name: 'Premium Plan',
|
name: 'Premium Plan',
|
||||||
badge: 'HOT'
|
badge: 'Picks'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
period: 'year',
|
period: 'day',
|
||||||
points: 'Points',
|
points: 'Points',
|
||||||
validity: 'Validity',
|
validity: 'Validity',
|
||||||
features: {
|
features: {
|
||||||
|
|
@ -1448,7 +1459,12 @@ export default {
|
||||||
},
|
},
|
||||||
modelModal: {
|
modelModal: {
|
||||||
customizeToHome: 'Customize to Home',
|
customizeToHome: 'Customize to Home',
|
||||||
textInputPlaceholder: 'Please enter adjustment content, e.g. change character expression'
|
textInputPlaceholder: 'Please enter adjustment content, e.g. change character expression',
|
||||||
|
preview: 'Preview',
|
||||||
|
modify: 'Modify',
|
||||||
|
sceneGraph: 'Scene Graph',
|
||||||
|
edit: 'Edit',
|
||||||
|
download: 'Download'
|
||||||
},
|
},
|
||||||
modelCard: {
|
modelCard: {
|
||||||
generateModelButton: 'Generate Model',
|
generateModelButton: 'Generate Model',
|
||||||
|
|
@ -1535,6 +1551,7 @@ export default {
|
||||||
modelFreeCount: 'Free Model',
|
modelFreeCount: 'Free Model',
|
||||||
times: 'times',
|
times: 'times',
|
||||||
remainingCredits: 'Remaining Credits',
|
remainingCredits: 'Remaining Credits',
|
||||||
|
recharge: 'Recharge',
|
||||||
guide: 'User Guide',
|
guide: 'User Guide',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
skip: 'Skip',
|
skip: 'Skip',
|
||||||
|
|
@ -1552,6 +1569,7 @@ export default {
|
||||||
currentPoints: 'Current Points',
|
currentPoints: 'Current Points',
|
||||||
expiryDate: 'Points Expiry Date',
|
expiryDate: 'Points Expiry Date',
|
||||||
pointsList: 'Points Details',
|
pointsList: 'Points Details',
|
||||||
|
recharge: 'Recharge',
|
||||||
consumptionRules: {
|
consumptionRules: {
|
||||||
title: 'Points Consumption Rules',
|
title: 'Points Consumption Rules',
|
||||||
behavior: 'Behavior',
|
behavior: 'Behavior',
|
||||||
|
|
@ -1732,9 +1750,9 @@ export default {
|
||||||
},
|
},
|
||||||
guideModal: {
|
guideModal: {
|
||||||
step1: {
|
step1: {
|
||||||
title: 'Reference Images',
|
title: 'Reference Image',
|
||||||
description: 'Select images you like as creative references',
|
description: 'Enter text to describe your IP, or select an image you like as a creative reference.',
|
||||||
tips: 'After clicking the generate button, the platform will generate corresponding 3D models based on your selection.'
|
tips: 'After clicking the generate button, the platform will generate the corresponding image based on your selection.'
|
||||||
},
|
},
|
||||||
step2: {
|
step2: {
|
||||||
title: 'Model Generation / Text Optimization',
|
title: 'Model Generation / Text Optimization',
|
||||||
|
|
@ -2585,7 +2603,8 @@ export default {
|
||||||
goHome: 'Go Home',
|
goHome: 'Go Home',
|
||||||
submitInviteCode: 'Submit Invite Code',
|
submitInviteCode: 'Submit Invite Code',
|
||||||
hasInviteCode: 'Already have an invite code?',
|
hasInviteCode: 'Already have an invite code?',
|
||||||
inviteCodePlaceholder: 'Enter invite code to upgrade to free membership'
|
inviteCodePlaceholder: 'Enter invite code to upgrade to free membership',
|
||||||
|
logout: 'Logout'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { createRouter, createWebHistory,createWebHashHistory} from 'vue-router'
|
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import NProgress from 'nprogress'
|
import NProgress from 'nprogress'
|
||||||
|
|
@ -17,21 +17,21 @@ const AddAgent = () => import('../views/AddAgent.vue')
|
||||||
const DeviceList = () => import('../views/DeviceList.vue')
|
const DeviceList = () => import('../views/DeviceList.vue')
|
||||||
const UiTest = () => import('../views/UiTest.vue')
|
const UiTest = () => import('../views/UiTest.vue')
|
||||||
const home = () => import('../views/home/index.vue')
|
const home = () => import('../views/home/index.vue')
|
||||||
const PointsRecharge = () => import('../views/PointsRecharge.vue')
|
const PointsRecharge = () => import('../views/PointsRecharge/PointsRecharge.vue')
|
||||||
const UserCenter = () => import('../views/user/index.vue')
|
const UserCenter = () => import('../views/user/index.vue')
|
||||||
const NotFound = () => import('../views/NotFound.vue')
|
const NotFound = () => import('../views/NotFound.vue')
|
||||||
const Waitlist = () => import('../views/Waitlist.vue')
|
const Waitlist = () => import('../views/Waitlist.vue')
|
||||||
NProgress.configure({
|
NProgress.configure({
|
||||||
showSpinner: false,
|
showSpinner: false,
|
||||||
})// 开启轻量模式(顶部细线)
|
})// 开启轻量模式(顶部细线)
|
||||||
// 路由配置
|
// 路由配置
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
component: home,
|
component: home,
|
||||||
meta: { fullScreen: false }
|
meta: { requiresAuth: false, keepAlive: false, fullScreen: true }
|
||||||
},{
|
}, {
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: Login,
|
component: Login,
|
||||||
|
|
@ -47,7 +47,7 @@ const routes = [
|
||||||
path: '/czhome',
|
path: '/czhome',
|
||||||
name: 'czhome',
|
name: 'czhome',
|
||||||
component: ModernHome,
|
component: ModernHome,
|
||||||
meta: { requiresAuth: false, keepAlive: false }
|
meta: { requiresAuth: true, keepAlive: false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/register',
|
path: '/register',
|
||||||
|
|
@ -61,6 +61,12 @@ const routes = [
|
||||||
component: ForgotPassword,
|
component: ForgotPassword,
|
||||||
meta: { requiresGuest: true, fullScreen: true }
|
meta: { requiresGuest: true, fullScreen: true }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/Waitlist',
|
||||||
|
name: 'Waitlist',
|
||||||
|
component: Waitlist, // 升级页
|
||||||
|
meta: { requiresAuth: true, keepAlive: false, fullScreen: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
name: 'NotFound',
|
name: 'NotFound',
|
||||||
|
|
@ -146,7 +152,7 @@ export const freeRoutes = [
|
||||||
path: '/points-recharge',
|
path: '/points-recharge',
|
||||||
name: 'points-recharge',
|
name: 'points-recharge',
|
||||||
component: PointsRecharge,
|
component: PointsRecharge,
|
||||||
meta: { requiresAuth: true,fullScreen: true }
|
meta: { requiresAuth: true, fullScreen: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/list',
|
path: '/list',
|
||||||
|
|
@ -163,7 +169,7 @@ export const freeRoutes = [
|
||||||
]
|
]
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
// history: createWebHistory(),
|
// history: createWebHistory(),
|
||||||
history:createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes,
|
routes,
|
||||||
})
|
})
|
||||||
// 路由守卫
|
// 路由守卫
|
||||||
|
|
@ -173,7 +179,7 @@ router.beforeEach(async (to, from, next) => {
|
||||||
// window.localStorage.setItem('token','123')
|
// window.localStorage.setItem('token','123')
|
||||||
// return next()
|
// return next()
|
||||||
// }
|
// }
|
||||||
if(to.path=='/login'||to.path=='/login/phone'||to.path=='/register'||to.path=='/forgot-password'){
|
if (to.path == '/login' || to.path == '/login/phone' || to.path == '/register' || to.path == '/forgot-password') {
|
||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
// 如果有 token,跳转到首页
|
// 如果有 token,跳转到首页
|
||||||
if (token) {
|
if (token) {
|
||||||
|
|
@ -181,7 +187,8 @@ router.beforeEach(async (to, from, next) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newto = freeRoutes.find(route => route.path == to.path)
|
const findRoutes = [...freeRoutes, ...routes]
|
||||||
|
let newto = findRoutes.find(route => route.path == to.path)
|
||||||
if (newto?.meta?.requiresAuth) {
|
if (newto?.meta?.requiresAuth) {
|
||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
// 如果没有 token,跳转到登录页
|
// 如果没有 token,跳转到登录页
|
||||||
|
|
@ -195,27 +202,24 @@ router.beforeEach(async (to, from, next) => {
|
||||||
// const info = await authStore.updateUserInfo();
|
// const info = await authStore.updateUserInfo();
|
||||||
// console.log(info,'infoinfo');
|
// console.log(info,'infoinfo');
|
||||||
const user_role = authStore.user?.userRole;
|
const user_role = authStore.user?.userRole;
|
||||||
if((user_role == 1||user_role == 2) && router.getRoutes().length == routes.length) {
|
if ((user_role == 1 || user_role == 2) && router.getRoutes().length == routes.length) {
|
||||||
// 添加动态路由
|
// 添加动态路由
|
||||||
addDynamicRoutes();
|
addDynamicRoutes();
|
||||||
if(isDynamicRoute(to.path)) {
|
if (isDynamicRoute(to.path)) {
|
||||||
next('/czhome')
|
next('/czhome')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.push(to.path)
|
router.push(to.path)
|
||||||
}, 20);
|
}, 20);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}else if(user_role == 0){
|
|
||||||
// 恢复默认路由
|
|
||||||
removeDynamicRoutes()
|
|
||||||
router.addRoute(
|
|
||||||
{
|
|
||||||
path: '/:pathMatch(.*)*',
|
|
||||||
name: 'NotFound',
|
|
||||||
component: Waitlist, // 显示404页面组件
|
|
||||||
meta: { requiresAuth: false, keepAlive: false, fullScreen: true }
|
|
||||||
}
|
}
|
||||||
)
|
// else if (user_role == 0) {
|
||||||
|
// // 恢复默认路由
|
||||||
|
// removeDynamicRoutes()
|
||||||
|
// }
|
||||||
|
if (user_role == 0&&to.path!='/Waitlist'&&to.path!='/') {
|
||||||
|
// 跳转升级页
|
||||||
|
return next('/Waitlist')
|
||||||
}
|
}
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
@ -228,7 +232,7 @@ function addDynamicRoutes() {
|
||||||
}
|
}
|
||||||
//恢复默认路由
|
//恢复默认路由
|
||||||
function removeDynamicRoutes() {
|
function removeDynamicRoutes() {
|
||||||
if(router.getRoutes().length == routes.length){
|
if (router.getRoutes().length == routes.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
router.getRoutes().forEach(route => {
|
router.getRoutes().forEach(route => {
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,18 @@ const handleFileSelect = (event) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const openProject = (project) => {
|
const openProject = (project) => {
|
||||||
router.push(`/project/${project.id}/${project.tags[0]}`)
|
let series = project.tags[0]
|
||||||
|
switch(series){
|
||||||
|
case 'Oone':
|
||||||
|
series = 'E1';
|
||||||
|
break;
|
||||||
|
case 'Done':
|
||||||
|
series = 'D1';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
router.push(`/project/${project.id}/${series}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewProject = (series) => {
|
const createNewProject = (series) => {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export class OrderManagement {
|
||||||
}
|
}
|
||||||
//获取订单列表
|
//获取订单列表
|
||||||
getOrderList(params){
|
getOrderList(params){
|
||||||
|
params.source_type = 0
|
||||||
return requestUtils.common(clientApi.default.getOrderList, params);
|
return requestUtils.common(clientApi.default.getOrderList, params);
|
||||||
}
|
}
|
||||||
//获取订单详情
|
//获取订单详情
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,11 @@
|
||||||
<button class="back-button" @click="goBack">
|
<button class="back-button" @click="goBack">
|
||||||
← {{ t('common.back') }}
|
← {{ t('common.back') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 语言切换组件 -->
|
<!-- 语言切换组件 -->
|
||||||
<div class="language-switcher">
|
<div class="language-switcher">
|
||||||
<LanguageToggle />
|
<LanguageToggle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 页面标题 -->
|
<!-- 页面标题 -->
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1 class="page-title">{{ t('pointsRecharge.title') }}</h1>
|
<h1 class="page-title">{{ t('pointsRecharge.title') }}</h1>
|
||||||
|
|
@ -21,68 +19,48 @@
|
||||||
|
|
||||||
<!-- 套餐选择 -->
|
<!-- 套餐选择 -->
|
||||||
<div class="plans-container">
|
<div class="plans-container">
|
||||||
<!-- 套餐卡片 1: 300积分 -->
|
<!-- 动态渲染套餐卡片 -->
|
||||||
<div class="plan-card" :class="{ active: selectedPlan === 'basic' }" @click="selectPlan('basic')">
|
<div
|
||||||
|
v-for="pkg in sortedPackages"
|
||||||
|
:key="pkg.id"
|
||||||
|
class="plan-card"
|
||||||
|
:class="{
|
||||||
|
premium: pkg.is_recommended === 1
|
||||||
|
}"
|
||||||
|
@click="selectPlan(pkg.id)"
|
||||||
|
>
|
||||||
<div class="plan-header">
|
<div class="plan-header">
|
||||||
<h3 class="plan-name">{{ t('pointsRecharge.plans.basic.name') }}</h3>
|
<h3 class="plan-name">{{ pkg.package_name }}</h3>
|
||||||
|
<div v-if="pkg.is_recommended === 1" class="plan-badge">{{ t('pointsRecharge.plans.premium.badge') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="plan-price">
|
<div class="plan-price">
|
||||||
<span class="price-amount">${{ plans.basic.price }}</span>
|
<span class="price-amount">${{ pkg.amount }}</span>
|
||||||
<span class="price-period">/ {{ t('pointsRecharge.period') }}</span>
|
<span class="price-period">/ {{ t('pointsRecharge.period') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="plan-points">
|
<div class="plan-points">
|
||||||
{{ plans.basic.points }} {{ t('pointsRecharge.points') }}
|
{{ pkg.credits }} {{ t('pointsRecharge.points') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="plan-validity">
|
<div class="plan-validity">
|
||||||
{{ t('pointsRecharge.validity') }}: {{ plans.basic.validity }} {{ t('pointsRecharge.period') }}
|
{{ t('pointsRecharge.validity') }}: {{ pkg.validity_days }} {{ t('pointsRecharge.period') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="plan-features">
|
<div class="plan-features">
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>{{ pkg.description }}</li>
|
||||||
<li>{{ t('pointsRecharge.features.unlimitedAccess') }}</li>
|
<li>{{ t('pointsRecharge.features.unlimitedAccess') }}</li>
|
||||||
<li>{{ t('pointsRecharge.features.prioritySupport') }}</li>
|
<li>{{ t('pointsRecharge.features.prioritySupport') }}</li>
|
||||||
<li>{{ t('pointsRecharge.features.securePayment') }}</li>
|
<li>{{ t('pointsRecharge.features.securePayment') }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="purchase-button" @click.stop="purchasePlan('basic')">
|
<button
|
||||||
{{ t('pointsRecharge.purchase') }}
|
class="purchase-button"
|
||||||
</button>
|
:class="{ 'premium-button': pkg.is_recommended === 1 }"
|
||||||
</div>
|
@click.stop="purchasePlan(pkg)"
|
||||||
|
>
|
||||||
<!-- 套餐卡片 2: 1000积分 -->
|
|
||||||
<div class="plan-card premium" :class="{ active: selectedPlan === 'premium' }" @click="selectPlan('premium')">
|
|
||||||
<div class="plan-header">
|
|
||||||
<h3 class="plan-name">{{ t('pointsRecharge.plans.premium.name') }}</h3>
|
|
||||||
<div class="plan-badge">{{ t('pointsRecharge.plans.premium.badge') }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="plan-price">
|
|
||||||
<span class="price-amount">${{ plans.premium.price }}</span>
|
|
||||||
<span class="price-period">/ {{ t('pointsRecharge.period') }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="plan-points">
|
|
||||||
{{ plans.premium.points }} {{ t('pointsRecharge.points') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="plan-validity">
|
|
||||||
{{ t('pointsRecharge.validity') }}: {{ plans.premium.validity }} {{ t('pointsRecharge.period') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="plan-features">
|
|
||||||
<ul>
|
|
||||||
<li>{{ t('pointsRecharge.features.unlimitedAccess') }}</li>
|
|
||||||
<li>{{ t('pointsRecharge.features.prioritySupport') }}</li>
|
|
||||||
<li>{{ t('pointsRecharge.features.securePayment') }}</li>
|
|
||||||
<li>{{ t('pointsRecharge.features.extraBenefits') }}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="purchase-button premium-button" @click.stop="purchasePlan('premium')">
|
|
||||||
{{ t('pointsRecharge.purchase') }}
|
{{ t('pointsRecharge.purchase') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -101,48 +79,51 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import LanguageToggle from '@/components/ui/LanguageToggle.vue'
|
import LanguageToggle from '@/components/ui/LanguageToggle.vue'
|
||||||
|
import { PointsRecharge } from './index.js'
|
||||||
|
const pointsRecharge = new PointsRecharge()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// 响应式状态
|
const selectedPlan = ref(null)
|
||||||
const selectedPlan = ref('basic')
|
const packagesList = ref([])
|
||||||
|
|
||||||
// 套餐数据
|
const sortedPackages = computed(() => {
|
||||||
const plans = ref({
|
return [...packagesList.value].sort((a, b) => a.sort_order - b.sort_order)
|
||||||
basic: {
|
|
||||||
name: 'basic',
|
|
||||||
points: 300,
|
|
||||||
price: 30,
|
|
||||||
validity: '1'
|
|
||||||
},
|
|
||||||
premium: {
|
|
||||||
name: 'premium',
|
|
||||||
points: 1000,
|
|
||||||
price: 80,
|
|
||||||
validity: '1'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
|
const history = router.options.history.state
|
||||||
|
console.log(history);
|
||||||
|
if(!history.back){
|
||||||
|
router.replace('/user-center')
|
||||||
|
}
|
||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择套餐
|
const selectPlan = (planId) => {
|
||||||
const selectPlan = (planName) => {
|
selectedPlan.value = planId
|
||||||
selectedPlan.value = planName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 购买套餐
|
const purchasePlan = (pkg) => {
|
||||||
const purchasePlan = (planName) => {
|
let parmas = {
|
||||||
console.log('购买套餐:', planName)
|
package_id: pkg.id,
|
||||||
// 这里可以添加购买逻辑
|
}
|
||||||
|
pointsRecharge.confirmPay(parmas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
let res = await pointsRecharge.getRechargePackageList()
|
||||||
|
const data = res.items || []
|
||||||
|
packagesList.value = data
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
@ -257,7 +238,8 @@ const purchasePlan = (planName) => {
|
||||||
|
|
||||||
.plan-card.active {
|
.plan-card.active {
|
||||||
border-color: #8B5CF6;
|
border-color: #8B5CF6;
|
||||||
box-shadow: 0 20px 40px rgba(139, 92, 246, 0.2);
|
border-width: 3px;
|
||||||
|
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.3), 0 20px 40px rgba(139, 92, 246, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-card.active::before {
|
.plan-card.active::before {
|
||||||
|
|
@ -267,7 +249,6 @@ const purchasePlan = (planName) => {
|
||||||
/* 高级套餐样式 */
|
/* 高级套餐样式 */
|
||||||
.plan-card.premium {
|
.plan-card.premium {
|
||||||
background: linear-gradient(135deg, #1F2937 0%, #374151 100%);
|
background: linear-gradient(135deg, #1F2937 0%, #374151 100%);
|
||||||
border-color: #8B5CF6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 套餐头部 */
|
/* 套餐头部 */
|
||||||
|
|
@ -276,6 +257,8 @@ const purchasePlan = (planName) => {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-name {
|
.plan-name {
|
||||||
|
|
@ -283,6 +266,11 @@ const purchasePlan = (planName) => {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #F9FAFB;
|
color: #F9FAFB;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-badge {
|
.plan-badge {
|
||||||
|
|
@ -292,6 +280,7 @@ const purchasePlan = (planName) => {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 价格样式 */
|
/* 价格样式 */
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import {clientApi,requestUtils,PayServer} from '@deotaland/utils';
|
||||||
|
export class PointsRecharge extends PayServer{
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
//获取充值包列表
|
||||||
|
async getRechargePackageList(){
|
||||||
|
let res = await requestUtils.common(clientApi.default.RECHARGE_PACKAGES);
|
||||||
|
if (res.code == 0) {
|
||||||
|
let data = res.data
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
throw new Error(res.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//确定支付
|
||||||
|
async confirmPay(orderInfo){
|
||||||
|
this.createPayorOrder(orderInfo,2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
>
|
>
|
||||||
<!-- @mouseleave="stopElementDrag" -->
|
<!-- @mouseleave="stopElementDrag" -->
|
||||||
<!-- 删除按钮 -->
|
<!-- 删除按钮 -->
|
||||||
<button
|
<!-- <button
|
||||||
v-if="(card.imageUrl&&card.type==='image')||(card.type==='model'&&card.modelUrl)"
|
v-if="(card.imageUrl&&card.type==='image')||(card.type==='model'&&card.modelUrl)"
|
||||||
class="delete-button"
|
class="delete-button"
|
||||||
@click.stop="handleDeleteCard(index)"
|
@click.stop="handleDeleteCard(index)"
|
||||||
|
|
@ -62,17 +62,18 @@
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button> -->
|
||||||
|
|
||||||
<!-- 根据卡片类型显示不同组件 -->
|
<!-- 根据卡片类型显示不同组件 -->
|
||||||
<IPCard
|
<IPCard
|
||||||
|
@delete-card="handleDeleteCard(index)"
|
||||||
:combinedPromptJson="combinedPromptJson"
|
:combinedPromptJson="combinedPromptJson"
|
||||||
@handlePartialEdit="(imageUrl) => handlePartialEdit(imageUrl, index)"
|
@handlePartialEdit="(imageUrl) => handlePartialEdit(imageUrl, index)"
|
||||||
@customize-to-home="handleCustomizeToHome(index)"
|
@customize-to-home="handleCustomizeToHome(index)"
|
||||||
@preview-image="handlePreviewImage"
|
@preview-image="handlePreviewImage"
|
||||||
@generate-smooth-white-model="(imageUrl)=>handleGenerateSmoothWhiteModel(index,imageUrl)"
|
@generate-smooth-white-model="(imageUrl)=>handleGenerateSmoothWhiteModel(index,imageUrl)"
|
||||||
@create-new-card="(data)=>handleCreateFourViewCard(index,data)"
|
@create-new-card="(data)=>handleCreateFourViewCard(index,data)"
|
||||||
@delete="handleDeleteCard(index)"
|
@download-image="downloadImage"
|
||||||
:projectId="projectId"
|
:projectId="projectId"
|
||||||
@create-prompt-card="(data)=>handleCreatePromptCard(index,data)"
|
@create-prompt-card="(data)=>handleCreatePromptCard(index,data)"
|
||||||
@generate-model-requested="(data)=>handleGenerateModelRequested(index,data)"
|
@generate-model-requested="(data)=>handleGenerateModelRequested(index,data)"
|
||||||
|
|
@ -123,7 +124,8 @@
|
||||||
:initialIndex="currentImageIndex"
|
:initialIndex="currentImageIndex"
|
||||||
@close="showImagePreview = false"
|
@close="showImagePreview = false"
|
||||||
/>
|
/>
|
||||||
<CanvasEditor
|
<DtCanvasEditor
|
||||||
|
:language="locale"
|
||||||
v-model:visible="canvasEditorVisible"
|
v-model:visible="canvasEditorVisible"
|
||||||
:image-url="canvasEditorImageUrl"
|
:image-url="canvasEditorImageUrl"
|
||||||
@add-prompt-card="handleCanvasSave"
|
@add-prompt-card="handleCanvasSave"
|
||||||
|
|
@ -167,6 +169,8 @@ import OrderProcessModal from '../../components/OrderProcessModal/index.vue';
|
||||||
import PurchaseModal from '../../components/PurchaseModal/index.vue';
|
import PurchaseModal from '../../components/PurchaseModal/index.vue';
|
||||||
import {Project} from './index';
|
import {Project} from './index';
|
||||||
import {ModernHome} from '../ModernHome/index.js'
|
import {ModernHome} from '../ModernHome/index.js'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
const { locale } = useI18n()
|
||||||
const fileServer = new FileServer();
|
const fileServer = new FileServer();
|
||||||
const modernHome = new ModernHome();
|
const modernHome = new ModernHome();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -197,6 +201,19 @@ const getGenerateCount = async ()=>{
|
||||||
// Limits.value.generateCount = data[0].model_count;
|
// Limits.value.generateCount = data[0].model_count;
|
||||||
// Limits.value.modelCount = data[1].model_count;
|
// Limits.value.modelCount = data[1].model_count;
|
||||||
}
|
}
|
||||||
|
//下载图片
|
||||||
|
const downloadImage = async (url) => {
|
||||||
|
try {
|
||||||
|
const blobUrl = await fileServer.fetchImage(url);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = blobUrl;
|
||||||
|
link.download = `image-${Date.now()}.jpg`;
|
||||||
|
link.click();
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('下载图片失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
const handlePartialEdit = (imageUrl, index) => {
|
const handlePartialEdit = (imageUrl, index) => {
|
||||||
canvasEditorImageUrl.value = imageUrl;
|
canvasEditorImageUrl.value = imageUrl;
|
||||||
canvasEditorVisible.value = true;
|
canvasEditorVisible.value = true;
|
||||||
|
|
@ -219,6 +236,14 @@ const handlePartialEdit = (imageUrl, index) => {
|
||||||
const cards = ref([
|
const cards = ref([
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
//判断引导弹窗的弹出逻辑,需要每日弹出一次
|
||||||
|
const showGuideModalLogic = ()=>{
|
||||||
|
const lastShowDate = localStorage.getItem('lastShowGuideModalDate');
|
||||||
|
const currentDate = new Date().toDateString();
|
||||||
|
if (!lastShowDate || lastShowDate !== currentDate) {
|
||||||
|
showGuideModal.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
const getMaxZIndexNum = ref(0);
|
const getMaxZIndexNum = ref(0);
|
||||||
//获取最大z-index+1
|
//获取最大z-index+1
|
||||||
const getMaxZIndex = (type)=>{
|
const getMaxZIndex = (type)=>{
|
||||||
|
|
@ -286,6 +311,7 @@ const handleSaveProject = (index,item,type='image')=>{
|
||||||
const createProject = async ()=>{
|
const createProject = async ()=>{
|
||||||
const {id} = await PluginProject.createProject();
|
const {id} = await PluginProject.createProject();
|
||||||
// 创建新项目后,将当前路由跳转到 project/项目id/系列
|
// 创建新项目后,将当前路由跳转到 project/项目id/系列
|
||||||
|
|
||||||
await router.replace(`/project/${id}/${series.value}`);
|
await router.replace(`/project/${id}/${series.value}`);
|
||||||
projectId.value = id;
|
projectId.value = id;
|
||||||
getProjectInfo(id);
|
getProjectInfo(id);
|
||||||
|
|
@ -341,11 +367,15 @@ const closeImportModal = () => {
|
||||||
// 关闭引导弹窗
|
// 关闭引导弹窗
|
||||||
const closeGuideModal = () => {
|
const closeGuideModal = () => {
|
||||||
showGuideModal.value = false;
|
showGuideModal.value = false;
|
||||||
|
const currentDate = new Date().toDateString();
|
||||||
|
localStorage.setItem('lastShowGuideModalDate', currentDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 完成引导
|
// 完成引导
|
||||||
const completeGuide = () => {
|
const completeGuide = () => {
|
||||||
showGuideModal.value = false;
|
showGuideModal.value = false;
|
||||||
|
const currentDate = new Date().toDateString();
|
||||||
|
localStorage.setItem('lastShowGuideModalDate', currentDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 定位计算器 ====================
|
// ==================== 定位计算器 ====================
|
||||||
|
|
@ -1027,7 +1057,6 @@ const init = ()=>{
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
projectId.value = route.params.id;
|
projectId.value = route.params.id;
|
||||||
series.value = route.params.series;
|
series.value = route.params.series;
|
||||||
console.log(series.value);
|
|
||||||
if(projectId.value === 'new'){
|
if(projectId.value === 'new'){
|
||||||
createProject();
|
createProject();
|
||||||
return
|
return
|
||||||
|
|
@ -1037,6 +1066,7 @@ const init = ()=>{
|
||||||
}
|
}
|
||||||
// 组件挂载时添加事件监听器
|
// 组件挂载时添加事件监听器
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
showGuideModalLogic();
|
||||||
MeshyServer.pollingEnabled = true;
|
MeshyServer.pollingEnabled = true;
|
||||||
GiminiServer.pollingEnabled = true;
|
GiminiServer.pollingEnabled = true;
|
||||||
// 每次进入都显示引导弹窗
|
// 每次进入都显示引导弹窗
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { clientApi, requestUtils } from '@deotaland/utils';
|
import { clientApi, requestUtils } from '@deotaland/utils';
|
||||||
export class Project{
|
export class Project {
|
||||||
static timer = null;
|
static timer = null;
|
||||||
//项目时常
|
//项目时常
|
||||||
duration_seconds = 0;
|
duration_seconds = 0;
|
||||||
|
|
@ -10,7 +10,7 @@ export class Project{
|
||||||
this.latestUpdateParams = null;
|
this.latestUpdateParams = null;
|
||||||
}
|
}
|
||||||
//创建项目
|
//创建项目
|
||||||
async createProject(name='project',) {
|
async createProject(name = 'project',) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
let params = {
|
let params = {
|
||||||
"thumbnail": "",//项目缩略图
|
"thumbnail": "",//项目缩略图
|
||||||
|
|
@ -18,7 +18,7 @@ export class Project{
|
||||||
"description": "init",//项目描述
|
"description": "init",//项目描述
|
||||||
"details": {
|
"details": {
|
||||||
"node_card": [],
|
"node_card": [],
|
||||||
"prompt":'',
|
"prompt": '',
|
||||||
"thumbnail": "", // 缩略图
|
"thumbnail": "", // 缩略图
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|
@ -26,16 +26,16 @@ export class Project{
|
||||||
],
|
],
|
||||||
"duration_seconds": 0,//项目时长(秒)
|
"duration_seconds": 0,//项目时长(秒)
|
||||||
}
|
}
|
||||||
let res = await requestUtils.common(clientApi.default.PROJECT_CREATE,params)
|
let res = await requestUtils.common(clientApi.default.PROJECT_CREATE, params)
|
||||||
if(res.code !== 0){
|
if (res.code !== 0) {
|
||||||
reject(res)
|
reject(res)
|
||||||
}else{
|
} else {
|
||||||
resolve(res.data)
|
resolve(res.data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
//更新项目(带防抖处理,三秒内只执行最后一次请求)
|
//更新项目(带防抖处理,三秒内只执行最后一次请求)
|
||||||
async updateProject(projectId,projectConfig) {
|
async updateProject(projectId, projectConfig) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
// 存储最新的请求参数
|
// 存储最新的请求参数
|
||||||
const params = {
|
const params = {
|
||||||
|
|
@ -52,9 +52,9 @@ export class Project{
|
||||||
this.updateTimer = setTimeout(async () => {
|
this.updateTimer = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await requestUtils.common(clientApi.default.PROJECT_UPDATE, this.latestUpdateParams);
|
const res = await requestUtils.common(clientApi.default.PROJECT_UPDATE, this.latestUpdateParams);
|
||||||
if(res.code !== 0){
|
if (res.code !== 0) {
|
||||||
reject(res)
|
reject(res)
|
||||||
}else{
|
} else {
|
||||||
resolve(res.data)
|
resolve(res.data)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -72,10 +72,10 @@ export class Project{
|
||||||
let params = {
|
let params = {
|
||||||
"id": projectId,//项目ID
|
"id": projectId,//项目ID
|
||||||
}
|
}
|
||||||
let res = await requestUtils.common(clientApi.default.PROJECT_DELETE,params)
|
let res = await requestUtils.common(clientApi.default.PROJECT_DELETE, params)
|
||||||
if(res.code !== 0){
|
if (res.code !== 0) {
|
||||||
reject(res)
|
reject(res)
|
||||||
}else{
|
} else {
|
||||||
resolve(res.data)
|
resolve(res.data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -86,10 +86,10 @@ export class Project{
|
||||||
let params = {
|
let params = {
|
||||||
"id": projectId,//项目ID
|
"id": projectId,//项目ID
|
||||||
}
|
}
|
||||||
let res = await requestUtils.common(clientApi.default.PROJECT_GET,params)
|
let res = await requestUtils.common(clientApi.default.PROJECT_GET, params)
|
||||||
if(res.code !== 0){
|
if (res.code !== 0) {
|
||||||
reject(res)
|
reject(res)
|
||||||
}else{
|
} else {
|
||||||
resolve(res.data)
|
resolve(res.data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -433,12 +433,12 @@ export class Project{
|
||||||
// {
|
// {
|
||||||
// "type": "model",
|
// "type": "model",
|
||||||
// "status": "success",
|
// "status": "success",
|
||||||
// "taskId": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8d3-959d-7432-ac15-b5bab5c75b74/output/model.glb?Expires=4917906081&Signature=W88kkM3xYsvvJkiroo1RV141Jfp03bSQCMATSoBQ2gvct87jfWADJTm80rJTDWuNCBmXcF7dt2kDgB2mGWXjQri1DhAwzY87gIp~bJsvtis6UreApFesKHPhU~BkZGfOW2culwT29NTIY~PkTL9Whg8WZ-Mu9xb6Og6iJHol6E4gIX-jGTrXN67~BWsh17kvgSHN5bmackn9IZ~GGwC2threl0BzeFzgpYEkFuP60fkXeCvFAiZBCd0shZR9tuXa4drdzeg7FJMTWpPhhSBPXEFNeDxOpxiLd8iCrQuyu5s1sxNJuGeDcMfTTDh864J0r4-ap11L0LAnWXyHE69-kw__&Key-Pair-Id=KL5I0C8H7HX83",
|
// "taskId": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8d3-959d-7432-ac15-b5bab5c75b74/output/model.glb?Expires=4917906081&Signature=W88kkM3xYsvvJkiroE1RV141Jfp03bSQCMATSoBQ2gvct87jfWADJTm80rJTDWuNCBmXcF7dt2kDgB2mGWXjQri1DhAwzY87gIp~bJsvtis6UreApFesKHPhU~BkZGfOW2culwT29NTIY~PkTL9Whg8WZ-Mu9xb6Og6iJHol6E4gIX-jGTrXN67~BWsh17kvgSHN5bmackn9IZ~GGwC2threl0BzeFzgpYEkFuP60fkXeCvFAiZBCd0shZR9tuXa4drdzeg7FJMTWpPhhSBPXEFNeDxOpxiLd8iCrQuyu5s1sxNJuGeDcMfTTDh864J0r4-ap11L0LAnWXyHE69-kw__&Key-Pair-Id=KL5I0C8H7HX83",
|
||||||
// "zIndex": 101,
|
// "zIndex": 101,
|
||||||
// "offsetX": "-263px",
|
// "offsetX": "-263px",
|
||||||
// "offsetY": "-388px",
|
// "offsetY": "-388px",
|
||||||
// "imageUrl": "https://api.deotaland.ai/upload/526afeec880e46688d6e2edd1893e778.png",
|
// "imageUrl": "https://api.deotaland.ai/upload/526afeec880e46688d6e2edd1893e778.png",
|
||||||
// "modelUrl": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8d3-959d-7432-ac15-b5bab5c75b74/output/model.glb?Expires=4917906081&Signature=W88kkM3xYsvvJkiroo1RV141Jfp03bSQCMATSoBQ2gvct87jfWADJTm80rJTDWuNCBmXcF7dt2kDgB2mGWXjQri1DhAwzY87gIp~bJsvtis6UreApFesKHPhU~BkZGfOW2culwT29NTIY~PkTL9Whg8WZ-Mu9xb6Og6iJHol6E4gIX-jGTrXN67~BWsh17kvgSHN5bmackn9IZ~GGwC2threl0BzeFzgpYEkFuP60fkXeCvFAiZBCd0shZR9tuXa4drdzeg7FJMTWpPhhSBPXEFNeDxOpxiLd8iCrQuyu5s1sxNJuGeDcMfTTDh864J0r4-ap11L0LAnWXyHE69-kw__&Key-Pair-Id=KL5I0C8H7HX83",
|
// "modelUrl": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8d3-959d-7432-ac15-b5bab5c75b74/output/model.glb?Expires=4917906081&Signature=W88kkM3xYsvvJkiroE1RV141Jfp03bSQCMATSoBQ2gvct87jfWADJTm80rJTDWuNCBmXcF7dt2kDgB2mGWXjQri1DhAwzY87gIp~bJsvtis6UreApFesKHPhU~BkZGfOW2culwT29NTIY~PkTL9Whg8WZ-Mu9xb6Og6iJHol6E4gIX-jGTrXN67~BWsh17kvgSHN5bmackn9IZ~GGwC2threl0BzeFzgpYEkFuP60fkXeCvFAiZBCd0shZR9tuXa4drdzeg7FJMTWpPhhSBPXEFNeDxOpxiLd8iCrQuyu5s1sxNJuGeDcMfTTDh864J0r4-ap11L0LAnWXyHE69-kw__&Key-Pair-Id=KL5I0C8H7HX83",
|
||||||
// "cardWidth": "250",
|
// "cardWidth": "250",
|
||||||
// "timestamp": "2025-11-28T04:51:05.620Z",
|
// "timestamp": "2025-11-28T04:51:05.620Z",
|
||||||
// "project_id": "11",
|
// "project_id": "11",
|
||||||
|
|
@ -709,11 +709,11 @@ export class Project{
|
||||||
// "total": 18
|
// "total": 18
|
||||||
// })
|
// })
|
||||||
// return
|
// return
|
||||||
let res = await requestUtils.common(clientApi.default.PROJECT_LIST,params)
|
let res = await requestUtils.common(clientApi.default.PROJECT_LIST, params)
|
||||||
if(res.code !== 0){
|
if (res.code !== 0) {
|
||||||
// reject(res)
|
// reject(res)
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
resolve(res.data)
|
resolve(res.data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -721,28 +721,28 @@ export class Project{
|
||||||
//叠加项目时常
|
//叠加项目时常
|
||||||
addProjectDuration = (time) => {
|
addProjectDuration = (time) => {
|
||||||
this.duration_seconds = time;
|
this.duration_seconds = time;
|
||||||
if(Project.timer){
|
if (Project.timer) {
|
||||||
clearInterval(Project.timer);
|
clearInterval(Project.timer);
|
||||||
}
|
}
|
||||||
Project.timer = setInterval(() => {
|
Project.timer = setInterval(() => {
|
||||||
this.duration_seconds +=1;
|
this.duration_seconds += 1;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
//获取动态提示词
|
//获取动态提示词
|
||||||
async getCombinedPrompt(series){//series:项目系列D1 O1
|
async getCombinedPrompt(series) {//series:项目系列D1 E1
|
||||||
try {
|
try {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const res = await requestUtils.common(clientApi.default.combined)
|
const res = await requestUtils.common(clientApi.default.combined)
|
||||||
if(res.code === 0){
|
if (res.code === 0) {
|
||||||
let data = res.data;
|
let data = res.data;
|
||||||
// 如果是Oone系列,过滤掉title中包含"动物坐姿"或"人物姿势"的提示词
|
// 如果是Oone系列,过滤掉title中包含"动物坐姿"或"人物姿势"的提示词
|
||||||
if (series === 'O1') {
|
if (series === 'E1') {
|
||||||
data = data.filter(item => {
|
data = data.filter(item => {
|
||||||
if (!item.title) return true;
|
if (!item.title) return true;
|
||||||
return !item.title.includes('动物坐姿') && !item.title.includes('人物姿势')&& item.type != 'D1';
|
return !item.title.includes('动物坐姿') && !item.title.includes('人物姿势') && item.type != 'D1';
|
||||||
});
|
});
|
||||||
}else if(series === 'D1'){// 如果是Done系列,过滤掉type为O1的提示词
|
} else if (series === 'D1') {// 如果是Done系列,过滤掉type为E1的提示词
|
||||||
data = data.filter(item => item.type !== 'O1');
|
data = data.filter(item => item.type !== 'E1');
|
||||||
}
|
}
|
||||||
// 初始化返回数据结构
|
// 初始化返回数据结构
|
||||||
const result = {
|
const result = {
|
||||||
|
|
@ -758,7 +758,7 @@ export class Project{
|
||||||
// 按sortOrder排序
|
// 按sortOrder排序
|
||||||
data.sort((a, b) => a.sortOrder - b.sortOrder);
|
data.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||||
// 处理person和general类型的数据
|
// 处理person和general类型的数据
|
||||||
const personAndGeneral = data.filter(item => item.type === 'person' || item.type === 'general'|| item.type === 'O1');
|
const personAndGeneral = data.filter(item => item.type === 'person' || item.type === 'general' || item.type === 'E1');
|
||||||
personAndGeneral.forEach(item => {
|
personAndGeneral.forEach(item => {
|
||||||
// 拼接content
|
// 拼接content
|
||||||
result.person.content += item.content;
|
result.person.content += item.content;
|
||||||
|
|
@ -775,7 +775,7 @@ export class Project{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 处理animal和general类型的数据
|
// 处理animal和general类型的数据
|
||||||
const animalAndGeneral = data.filter(item => item.type === 'animal' || item.type === 'general'|| item.type === 'O1');
|
const animalAndGeneral = data.filter(item => item.type === 'animal' || item.type === 'general' || item.type === 'E1');
|
||||||
animalAndGeneral.forEach(item => {
|
animalAndGeneral.forEach(item => {
|
||||||
// 拼接content
|
// 拼接content
|
||||||
result.animal.content += item.content;
|
result.animal.content += item.content;
|
||||||
|
|
@ -792,7 +792,7 @@ export class Project{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}else{
|
} else {
|
||||||
reject(res.msg)
|
reject(res.msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,16 @@
|
||||||
<button
|
<button
|
||||||
class="primary-button"
|
class="primary-button"
|
||||||
@click="submitInviteCode"
|
@click="submitInviteCode"
|
||||||
:loading="submitting"
|
:disabled="submitting"
|
||||||
>
|
>
|
||||||
{{ t('waitlist.submitInviteCode') }}
|
{{ t('waitlist.submitInviteCode') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="secondary-button" @click="goHome">
|
<button class="secondary-button" @click="goHome">
|
||||||
{{ t('waitlist.goHome') }}
|
{{ t('waitlist.goHome') }}
|
||||||
</button>
|
</button>
|
||||||
|
<button class="logout-button" @click="handleLogout">
|
||||||
|
{{ t('waitlist.logout') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="decoration">
|
<div class="decoration">
|
||||||
|
|
@ -43,7 +46,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
@ -51,38 +54,42 @@ import { ElMessage } from 'element-plus'
|
||||||
import { UserController } from './user/index.js'
|
import { UserController } from './user/index.js'
|
||||||
import { useAuthStore } from '@/stores/auth.js'
|
import { useAuthStore } from '@/stores/auth.js'
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n()
|
||||||
name: 'Waitlist',
|
const router = useRouter()
|
||||||
setup() {
|
const authStore = useAuthStore()
|
||||||
const { t } = useI18n()
|
const userController = new UserController()
|
||||||
const router = useRouter()
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
const userController = new UserController()
|
|
||||||
|
|
||||||
const inviteCode = ref('')
|
const inviteCode = ref('')
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
|
||||||
const goHome = () => {
|
const goHome = () => {
|
||||||
router.push('/')
|
router.replace('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await authStore.logout(() => {
|
||||||
|
ElMessage.success('退出登录成功')
|
||||||
|
router.replace('/login')
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('退出登录失败:', error)
|
||||||
|
ElMessage.error('退出登录失败,请稍后重试')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 提交邀请码升级
|
const submitInviteCode = async () => {
|
||||||
const submitInviteCode = async () => {
|
|
||||||
if (!inviteCode.value.trim()) {
|
if (!inviteCode.value.trim()) {
|
||||||
// ElMessage.warning('请输入邀请码')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
// 调用upgrade方法
|
|
||||||
const upgradeRes = await userController.upgrade({ inviteCode: inviteCode.value })
|
const upgradeRes = await userController.upgrade({ inviteCode: inviteCode.value })
|
||||||
if (upgradeRes.code === 0) {
|
if (upgradeRes.code === 0) {
|
||||||
// 升级成功,刷新用户信息
|
|
||||||
await authStore.updateUserInfo()
|
await authStore.updateUserInfo()
|
||||||
ElMessage.success('升级成功')
|
ElMessage.success('升级成功')
|
||||||
// 返回首页
|
router.replace('/czhome')
|
||||||
goHome()
|
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(upgradeRes.message || '升级失败')
|
ElMessage.error(upgradeRes.message || '升级失败')
|
||||||
}
|
}
|
||||||
|
|
@ -92,16 +99,6 @@ export default {
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
t,
|
|
||||||
goHome,
|
|
||||||
inviteCode,
|
|
||||||
submitting,
|
|
||||||
submitInviteCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -286,6 +283,34 @@ export default {
|
||||||
color: #6B46C1;
|
color: #6B46C1;
|
||||||
border: 1px solid #6B46C1;
|
border: 1px solid #6B46C1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logout-button {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: none;
|
||||||
|
background-color: #EF4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-button:hover {
|
||||||
|
background-color: #DC2626;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(239, 68, 68, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-button:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-button:disabled:hover {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.waitlist-icon svg {
|
.waitlist-icon svg {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,7 @@
|
||||||
>
|
>
|
||||||
{{ t('nav.land') }}
|
{{ t('nav.land') }}
|
||||||
</a> -->
|
</a> -->
|
||||||
<!-- <router-link
|
|
||||||
to="/points-recharge"
|
|
||||||
class="text-sm font-medium text-gray-300 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
pricing
|
|
||||||
</router-link> -->
|
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-sm font-medium text-gray-300 hover:text-white transition-colors"
|
class="text-sm font-medium text-gray-300 hover:text-white transition-colors"
|
||||||
|
|
@ -57,6 +52,12 @@
|
||||||
>
|
>
|
||||||
{{ t('nav.about') }}
|
{{ t('nav.about') }}
|
||||||
</a>
|
</a>
|
||||||
|
<router-link
|
||||||
|
to="/points-recharge"
|
||||||
|
class="text-sm font-medium text-gray-300 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
pricing
|
||||||
|
</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Right Action & Mobile Toggle -->
|
<!-- Right Action & Mobile Toggle -->
|
||||||
|
|
@ -547,7 +548,7 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import MotionCom from './motion.vue'
|
import MotionCom from './motion.vue'
|
||||||
// import spline from './spline.vue';
|
import spline from './spline.vue';
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||||
import Bg from './bg.vue'
|
import Bg from './bg.vue'
|
||||||
// import dog from '@/assets/home/dog.webp'
|
// import dog from '@/assets/home/dog.webp'
|
||||||
|
|
@ -641,8 +642,8 @@ const i18n = {
|
||||||
// land: '社区',
|
// land: '社区',
|
||||||
// pricing: '价格',
|
// pricing: '价格',
|
||||||
creator: 'Creator',
|
creator: 'Creator',
|
||||||
done: 'D one',
|
done: 'D1',
|
||||||
about: 'About us'
|
about: 'About'
|
||||||
},
|
},
|
||||||
hero: {
|
hero: {
|
||||||
title: '使用 Deotaland 创作',
|
title: '使用 Deotaland 创作',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<!-- <template>
|
<template>
|
||||||
<ParentSize
|
<ParentSize
|
||||||
:parent-size-styles="parentSizeStyles"
|
:parent-size-styles="parentSizeStyles"
|
||||||
:debounce-time="50"
|
:debounce-time="50"
|
||||||
|
|
@ -159,4 +159,4 @@ onUnmounted(() => {
|
||||||
splineApp.value = null;
|
splineApp.value = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script> -->
|
</script>
|
||||||
|
|
@ -12,30 +12,29 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 隐藏的文件上传输入 -->
|
<!-- 隐藏的文件上传输入 -->
|
||||||
<!-- <input
|
<input
|
||||||
ref="avatarInput"
|
ref="avatarInput"
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
class="avatar-input"
|
class="avatar-input"
|
||||||
@change="handleAvatarUpload"
|
@change="handleAvatarUpload"
|
||||||
hidden
|
hidden
|
||||||
/> -->
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- @click="toggleEditName" -->
|
|
||||||
<div class="user-details-section">
|
<div class="user-details-section">
|
||||||
<div class="user-name-row">
|
<div class="user-name-row">
|
||||||
<div class="editable-name-container">
|
<div class="editable-name-container">
|
||||||
<h1
|
<h1
|
||||||
|
@click="toggleEditName"
|
||||||
class="user-name"
|
class="user-name"
|
||||||
:class="{ 'editable': true }"
|
:class="{ 'editable': true }"
|
||||||
>{{ userData.nickname }}</h1>
|
>{{ userData.nickname }}</h1>
|
||||||
<div v-if="isEditingName" class="name-edit-form">
|
<div v-if="isEditingName" class="name-edit-form">
|
||||||
<!-- @blur="saveNickname"
|
|
||||||
@keyup.enter="saveNickname" -->
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="editNameValue"
|
v-model="editNameValue"
|
||||||
size="large"
|
size="large"
|
||||||
|
@blur="saveNickname"
|
||||||
|
@keyup.enter="saveNickname"
|
||||||
ref="nameInput"
|
ref="nameInput"
|
||||||
placeholder="Please enter a nickname"
|
placeholder="Please enter a nickname"
|
||||||
/>
|
/>
|
||||||
|
|
@ -48,7 +47,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 积分信息区域 - 免费会员可见 -->
|
<!-- 积分信息区域 - 免费会员可见 -->
|
||||||
<div class="points-section" v-if="false">
|
<div class="points-section" >
|
||||||
<h2>{{ $t('userCenter.points.title') }}</h2>
|
<h2>{{ $t('userCenter.points.title') }}</h2>
|
||||||
<!-- 积分明细和规则并排容器 -->
|
<!-- 积分明细和规则并排容器 -->
|
||||||
|
|
||||||
|
|
@ -57,7 +56,15 @@
|
||||||
<div class="total-points">
|
<div class="total-points">
|
||||||
<span class="total-icon">🪄</span>
|
<span class="total-icon">🪄</span>
|
||||||
<span class="total-label">{{ $t('userCenter.points.currentPoints') }}</span>
|
<span class="total-label">{{ $t('userCenter.points.currentPoints') }}</span>
|
||||||
<span class="points-value">{{ userData.currentPoints }}</span>
|
<span class="points-value">{{total_score}}</span>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="recharge-btn"
|
||||||
|
@click="router.push('/points-recharge')"
|
||||||
|
>
|
||||||
|
{{ $t('userCenter.points.recharge') }}
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 积分明细列表 -->
|
<!-- 积分明细列表 -->
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -127,6 +134,7 @@
|
||||||
<div class="invite-codes-section">
|
<div class="invite-codes-section">
|
||||||
<h3>{{ $t('userCenter.invitation.inviteCodes') }}</h3>
|
<h3>{{ $t('userCenter.invitation.inviteCodes') }}</h3>
|
||||||
<div class="invite-cards-container">
|
<div class="invite-cards-container">
|
||||||
|
<!-- v-for="(inviteCode, index) in (userData.inviteCodes.filter(item => item.codeType!='permanent'))" -->
|
||||||
<div
|
<div
|
||||||
v-for="(inviteCode, index) in (userData.inviteCodes.filter(item => item.codeType!='permanent'))"
|
v-for="(inviteCode, index) in (userData.inviteCodes.filter(item => item.codeType!='permanent'))"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
|
@ -267,11 +275,15 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, nextTick, computed, onMounted } from 'vue'
|
import { ref, nextTick, computed, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { Plus, Edit } from '@element-plus/icons-vue'
|
import { Plus, Edit } from '@element-plus/icons-vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { UserController } from './index.js'
|
import { UserController } from './index.js'
|
||||||
|
import {ModernHome} from '../ModernHome/index.js'
|
||||||
|
const router = useRouter()
|
||||||
|
const modernHome = new ModernHome()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const userController = new UserController()
|
const userController = new UserController()
|
||||||
|
|
@ -328,10 +340,16 @@ const fetchInviteCodes = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const total_score = ref(0);
|
||||||
|
//获取积分详情
|
||||||
|
const pointDetail = async () => {
|
||||||
|
const {data} = await modernHome.getModelLimits();
|
||||||
|
total_score.value = data.total_score
|
||||||
|
}
|
||||||
// 组件挂载时获取数据
|
// 组件挂载时获取数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchInviteCodes()
|
fetchInviteCodes();
|
||||||
|
pointDetail();
|
||||||
})
|
})
|
||||||
// 获取角色名称
|
// 获取角色名称
|
||||||
const getRoleName = () => {
|
const getRoleName = () => {
|
||||||
|
|
@ -903,6 +921,33 @@ html.dark .copy-btn:hover:not(:disabled) {
|
||||||
color: var(--text-secondary, #6b7280);
|
color: var(--text-secondary, #6b7280);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.points-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary, #1f2937);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recharge-btn {
|
||||||
|
margin-left: auto;
|
||||||
|
background: linear-gradient(135deg, #8B5CF6 0%, #6B46C1 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 8px rgba(107, 70, 193, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recharge-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(107, 70, 193, 0.4);
|
||||||
|
background: linear-gradient(135deg, #9D74FF 0%, #7B5BD9 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recharge-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
/* 积分列表样式 */
|
/* 积分列表样式 */
|
||||||
.points-list {
|
.points-list {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ export default defineConfig({
|
||||||
// 配置代理解决CORS问题
|
// 配置代理解决CORS问题
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'https://api.deotaland.ai',
|
// target: 'https://api.deotaland.ai',
|
||||||
// target: 'http://api.deotaland.local',
|
target: 'http://api.deotaland.local',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="局部修改"
|
:title="t.dialogTitle"
|
||||||
width="90%"
|
width="90%"
|
||||||
:fullscreen="isFullscreen"
|
:fullscreen="isFullscreen"
|
||||||
append-to-body
|
append-to-body
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<div class="canvas-editor-container">
|
<div class="canvas-editor-container">
|
||||||
<div class="canvas-toolbar">
|
<div class="canvas-toolbar">
|
||||||
<div class="toolbar-section">
|
<div class="toolbar-section">
|
||||||
<span class="toolbar-label">画笔颜色:</span>
|
<span class="toolbar-label">{{ t.brushColor }}</span>
|
||||||
<div class="color-picker">
|
<div class="color-picker">
|
||||||
<div
|
<div
|
||||||
v-for="color in colors"
|
v-for="color in colors"
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-section">
|
<div class="toolbar-section">
|
||||||
<span class="toolbar-label">画笔大小:</span>
|
<span class="toolbar-label">{{ t.brushSize }}</span>
|
||||||
<el-slider
|
<el-slider
|
||||||
v-model="brushSize"
|
v-model="brushSize"
|
||||||
:min="1"
|
:min="1"
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
@click="toggleEraser"
|
@click="toggleEraser"
|
||||||
>
|
>
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon><Delete /></el-icon>
|
||||||
橡皮擦
|
{{ t.eraser }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
@click="clearCanvas"
|
@click="clearCanvas"
|
||||||
>
|
>
|
||||||
<el-icon><RefreshLeft /></el-icon>
|
<el-icon><RefreshLeft /></el-icon>
|
||||||
清空
|
{{ t.clear }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="success"
|
type="success"
|
||||||
|
|
@ -60,20 +60,20 @@
|
||||||
@click="handleSave"
|
@click="handleSave"
|
||||||
>
|
>
|
||||||
<el-icon><Check /></el-icon>
|
<el-icon><Check /></el-icon>
|
||||||
确定
|
{{ t.confirm }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="canvas-wrapper" ref="canvasWrapper">
|
<div class="canvas-wrapper" ref="canvasWrapper">
|
||||||
<div v-if="imageLoading" class="loading-overlay">
|
<div v-if="imageLoading" class="loading-overlay">
|
||||||
<el-icon class="is-loading"><Loading /></el-icon>
|
<el-icon class="is-loading"><Loading /></el-icon>
|
||||||
<span>加载图片中...</span>
|
<span>{{ t.loadingImage }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="imageLoadError" class="error-overlay">
|
<div v-if="imageLoadError" class="error-overlay">
|
||||||
<el-icon><Warning /></el-icon>
|
<el-icon><Warning /></el-icon>
|
||||||
<p>图片加载失败</p>
|
<p>{{ t.imageLoadError }}</p>
|
||||||
<el-button type="primary" size="small" @click="retryLoadImage">重试</el-button>
|
<el-button type="primary" size="small" @click="retryLoadImage">{{ t.retry }}</el-button>
|
||||||
<el-button size="small" @click="dialogVisible = false">关闭</el-button>
|
<el-button size="small" @click="dialogVisible = false">{{ t.close }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
<canvas
|
<canvas
|
||||||
v-show="!imageLoading && !imageLoadError"
|
v-show="!imageLoading && !imageLoadError"
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
v-model="editContent"
|
v-model="editContent"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="3"
|
:rows="3"
|
||||||
placeholder="请输入要修改的内容"
|
:placeholder="t.editPlaceholder"
|
||||||
class="edit-textarea"
|
class="edit-textarea"
|
||||||
></el-input>
|
></el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, watch, nextTick, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||||
import { Delete, RefreshLeft, Check, Loading, Warning } from '@element-plus/icons-vue'
|
import { Delete, RefreshLeft, Check, Loading, Warning } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
|
@ -113,11 +113,52 @@ const props = defineProps({
|
||||||
imageUrl: {
|
imageUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
type: String,
|
||||||
|
default: 'cn'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:visible', 'add-prompt-card', 'close'])
|
const emit = defineEmits(['update:visible', 'add-prompt-card', 'close'])
|
||||||
|
|
||||||
|
const i18n = {
|
||||||
|
cn: {
|
||||||
|
dialogTitle: '局部修改',
|
||||||
|
brushColor: '画笔颜色:',
|
||||||
|
brushSize: '画笔大小:',
|
||||||
|
eraser: '橡皮擦',
|
||||||
|
clear: '清空',
|
||||||
|
confirm: '确定',
|
||||||
|
loadingImage: '加载图片中...',
|
||||||
|
imageLoadError: '图片加载失败',
|
||||||
|
retry: '重试',
|
||||||
|
close: '关闭',
|
||||||
|
editPlaceholder: '请输入要修改的内容',
|
||||||
|
imageLoadFailed: '图片加载失败,请重试',
|
||||||
|
retryingImage: '正在重试加载图片',
|
||||||
|
maxRetryReached: '已达到最大重试次数,请稍后再试'
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
dialogTitle: 'Local Edit',
|
||||||
|
brushColor: 'Brush Color:',
|
||||||
|
brushSize: 'Brush Size:',
|
||||||
|
eraser: 'Eraser',
|
||||||
|
clear: 'Clear',
|
||||||
|
confirm: 'Confirm',
|
||||||
|
loadingImage: 'Loading image...',
|
||||||
|
imageLoadError: 'Failed to load image',
|
||||||
|
retry: 'Retry',
|
||||||
|
close: 'Close',
|
||||||
|
editPlaceholder: 'Please enter the content to modify',
|
||||||
|
imageLoadFailed: 'Failed to load image, please retry',
|
||||||
|
retryingImage: 'Retrying to load image',
|
||||||
|
maxRetryReached: 'Maximum retry attempts reached, please try again later'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = computed(() => i18n[props.language] || i18n.cn)
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const isFullscreen = ref(false)
|
const isFullscreen = ref(false)
|
||||||
const canvas = ref(null)
|
const canvas = ref(null)
|
||||||
|
|
@ -194,17 +235,17 @@ const initCanvas = async () => {
|
||||||
console.error('Failed to load image:', props.imageUrl, error)
|
console.error('Failed to load image:', props.imageUrl, error)
|
||||||
imageLoading.value = false
|
imageLoading.value = false
|
||||||
imageLoadError.value = true
|
imageLoadError.value = true
|
||||||
ElMessage.error('图片加载失败,请重试')
|
ElMessage.error(t.value.imageLoadFailed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const retryLoadImage = () => {
|
const retryLoadImage = () => {
|
||||||
if (loadRetryCount.value < maxRetryCount) {
|
if (loadRetryCount.value < maxRetryCount) {
|
||||||
loadRetryCount.value++
|
loadRetryCount.value++
|
||||||
ElMessage.info(`正在重试加载图片 (${loadRetryCount.value}/${maxRetryCount})`)
|
ElMessage.info(`${t.value.retryingImage} (${loadRetryCount.value}/${maxRetryCount})`)
|
||||||
initCanvas()
|
initCanvas()
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning('已达到最大重试次数,请稍后再试')
|
ElMessage.warning(t.value.maxRetryReached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
// UI组件库入口文件
|
// UI组件库入口文件
|
||||||
import LoadingCom from './components/LoadingCom/index.vue'
|
import LoadingCom from './components/LoadingCom/index.vue'
|
||||||
|
import CanvasEditor from './components/CanvasEditor/CanvasEditor.vue'
|
||||||
|
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
|
||||||
// 创建带有Dt前缀的组件
|
// 创建带有Dt前缀的组件
|
||||||
|
|
@ -10,15 +13,24 @@ const DtLoadingCom = {
|
||||||
app.component('DtLoadingCom', DtLoadingCom)
|
app.component('DtLoadingCom', DtLoadingCom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const DtCanvasEditor = {
|
||||||
|
...CanvasEditor,
|
||||||
|
name: 'DtCanvasEditor',
|
||||||
|
install(app) {
|
||||||
|
app.component('DtCanvasEditor', DtCanvasEditor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 组件列表
|
// 组件列表
|
||||||
const components = [
|
const components = [
|
||||||
DtLoadingCom
|
DtLoadingCom,
|
||||||
|
DtCanvasEditor
|
||||||
]
|
]
|
||||||
|
|
||||||
// 导出组件
|
// 导出组件
|
||||||
export {
|
export {
|
||||||
DtLoadingCom
|
DtLoadingCom,
|
||||||
|
DtCanvasEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量注册组件的函数
|
// 批量注册组件的函数
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import order from './order.js';
|
||||||
import user from './user.js';
|
import user from './user.js';
|
||||||
import logistics from './logistics.js';
|
import logistics from './logistics.js';
|
||||||
import agent from './agent.js';
|
import agent from './agent.js';
|
||||||
|
import rechargeconfig from './rechargeconfig.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...meshy,
|
...meshy,
|
||||||
|
|
@ -18,4 +19,5 @@ export default {
|
||||||
...user,
|
...user,
|
||||||
...logistics,
|
...logistics,
|
||||||
...agent,
|
...agent,
|
||||||
|
...rechargeconfig,
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
const rechargeconfig = {
|
||||||
|
RECHARGE_PACKAGES:{url:'/api-core/front/order/recharge/packages',method:'POST',isLoading:true},//获取充值包列表
|
||||||
|
CREATE_RECHARGE_ORDER:{url:'/api-core/front/order/recharge/create',method:'POST',isLoading:true},//创建充值订单
|
||||||
|
}
|
||||||
|
export default rechargeconfig;
|
||||||
|
|
@ -11,7 +11,7 @@ const getPorjectType = () => {
|
||||||
}
|
}
|
||||||
// Node.js 环境
|
// Node.js 环境
|
||||||
if (typeof process !== 'undefined') {
|
if (typeof process !== 'undefined') {
|
||||||
return process.env.VITE_PROJECTTYPE ;
|
return process.env.VITE_PROJECTTYPE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export class FileServer {
|
export class FileServer {
|
||||||
|
|
@ -31,7 +31,7 @@ export class FileServer {
|
||||||
|
|
||||||
//文件拼接
|
//文件拼接
|
||||||
concatUrl(url) {
|
concatUrl(url) {
|
||||||
return urlRule.replace('IMGURL',url)
|
return urlRule.replace('IMGURL', url)
|
||||||
}
|
}
|
||||||
|
|
||||||
//生成唯一的缓存键
|
//生成唯一的缓存键
|
||||||
|
|
@ -285,8 +285,13 @@ export class FileServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//上传文件
|
//上传文件
|
||||||
async uploadFile(url,config={}) {
|
async uploadFile(url, config = {}) {
|
||||||
this.closeLoading = window.setElLoading();
|
this.closeLoading = window.setElLoading();
|
||||||
|
// 如果是网络路径直接返回
|
||||||
|
if (typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://'))) {
|
||||||
|
this.closeLoading.close();
|
||||||
|
return Promise.resolve(url);
|
||||||
|
}
|
||||||
let originalFileName = null;
|
let originalFileName = null;
|
||||||
// 判断参数是否为File对象,保存原始文件名
|
// 判断参数是否为File对象,保存原始文件名
|
||||||
if (url instanceof File) {
|
if (url instanceof File) {
|
||||||
|
|
@ -294,33 +299,27 @@ export class FileServer {
|
||||||
try {
|
try {
|
||||||
url = await this.fileToBase64FromFile(url);
|
url = await this.fileToBase64FromFile(url);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
console.error('File对象转base64失败:', error);
|
console.error('File对象转base64失败:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果是网络路径直接返回
|
|
||||||
if (typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://'))) {
|
|
||||||
return Promise.resolve(url);
|
|
||||||
}
|
|
||||||
const cacheKey = this.generateUniqueCacheKey(url);//生成唯一的缓存key
|
const cacheKey = this.generateUniqueCacheKey(url);//生成唯一的缓存key
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
if (FileServer.fileCacheMap.has(cacheKey) && FileServer.fileCacheMap.get(cacheKey) != 'loading') {
|
||||||
if(FileServer.fileCacheMap.has(cacheKey)&&FileServer.fileCacheMap.get(cacheKey)!='loading'){
|
|
||||||
// resolve(this.concatUrl(FileServer.fileCacheMap.get(cacheKey)));
|
// resolve(this.concatUrl(FileServer.fileCacheMap.get(cacheKey)));
|
||||||
resolve(FileServer.fileCacheMap.get(cacheKey));
|
resolve(FileServer.fileCacheMap.get(cacheKey));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let loadUrl = null;
|
let loadUrl = null;
|
||||||
if(FileServer.fileCacheMap.get(cacheKey)=='loading'){
|
if (FileServer.fileCacheMap.get(cacheKey) == 'loading') {
|
||||||
loadUrl = await this.pollFileCacheMap(cacheKey);
|
loadUrl = await this.pollFileCacheMap(cacheKey);
|
||||||
}
|
}
|
||||||
if(loadUrl!='loading'&&loadUrl!=null){
|
if (loadUrl != 'loading' && loadUrl != null) {
|
||||||
// resolve(this.concatUrl(loadUrl));
|
// resolve(this.concatUrl(loadUrl));
|
||||||
resolve(loadUrl);
|
resolve(loadUrl);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
FileServer.fileCacheMap.set(cacheKey,'loading');
|
FileServer.fileCacheMap.set(cacheKey, 'loading');
|
||||||
let file = await this.fileToBlob(url);//将文件或者base64文件转为blob对象
|
let file = await this.fileToBlob(url);//将文件或者base64文件转为blob对象
|
||||||
const fileName = originalFileName || this.extractFileName(url);
|
const fileName = originalFileName || this.extractFileName(url);
|
||||||
const isModelFile = this.isModelFile(fileName);
|
const isModelFile = this.isModelFile(fileName);
|
||||||
|
|
@ -346,17 +345,17 @@ export class FileServer {
|
||||||
try {
|
try {
|
||||||
// const response = await requestUtils.upload(clientApi.default.UPLOAD.url, formData);
|
// const response = await requestUtils.upload(clientApi.default.UPLOAD.url, formData);
|
||||||
let params = {
|
let params = {
|
||||||
filename:fileName,
|
filename: fileName,
|
||||||
file_type:file.type.split('/')[0],
|
file_type: file.type.split('/')[0],
|
||||||
prefix:isModelFile ? 'uploads' : 'images',
|
prefix: isModelFile ? 'uploads' : 'images',
|
||||||
...config
|
...config
|
||||||
}
|
}
|
||||||
const requestUrl = this.RULE=='admin'?adminApi.default.adminUPLOADS3:clientApi.default.UPLOADS3;
|
const requestUrl = this.RULE == 'admin' ? adminApi.default.adminUPLOADS3 : clientApi.default.UPLOADS3;
|
||||||
// const requestUrl = clientApi.default.UPLOADS3;
|
// const requestUrl = clientApi.default.UPLOADS3;
|
||||||
const response = await requestUtils.common(requestUrl, params);
|
const response = await requestUtils.common(requestUrl, params);
|
||||||
if(response.code==0){
|
if (response.code == 0) {
|
||||||
let data = response.data;
|
let data = response.data;
|
||||||
let {url,fields,file_key,file_url } = data;
|
let { url, fields, file_key, file_url } = data;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
for (const key in fields) {
|
for (const key in fields) {
|
||||||
formData.append(key, fields[key]);
|
formData.append(key, fields[key]);
|
||||||
|
|
@ -370,7 +369,7 @@ export class FileServer {
|
||||||
});
|
});
|
||||||
// 注意:S3可能返回204或303状态码表示成功
|
// 注意:S3可能返回204或303状态码表示成功
|
||||||
if (uploadResponse.status === 204 || uploadResponse.status === 303 || uploadResponse.ok) {
|
if (uploadResponse.status === 204 || uploadResponse.status === 303 || uploadResponse.ok) {
|
||||||
if(file_url){
|
if (file_url) {
|
||||||
// 截取后八位作为缓存 key
|
// 截取后八位作为缓存 key
|
||||||
FileServer.fileCacheMap.set(cacheKey, file_url);
|
FileServer.fileCacheMap.set(cacheKey, file_url);
|
||||||
// resolve(urlRule.replace('IMGURL',file_url));
|
// resolve(urlRule.replace('IMGURL',file_url));
|
||||||
|
|
@ -393,6 +392,19 @@ export class FileServer {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 从URL获取图片并返回Blob URL
|
||||||
|
async fetchImage(url) {
|
||||||
|
const cacheBusterUrl = url + '?v=1.0.0';
|
||||||
|
const response = await fetch(cacheBusterUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'cors',
|
||||||
|
credentials: 'omit',
|
||||||
|
cache: 'no-cache'
|
||||||
|
});
|
||||||
|
const blob = await response.blob();
|
||||||
|
const imgurl = URL.createObjectURL(blob);
|
||||||
|
return imgurl;
|
||||||
|
}
|
||||||
//文件文件或者base64文件转为blob对象
|
//文件文件或者base64文件转为blob对象
|
||||||
fileToBlob(fileOrBase64) {
|
fileToBlob(fileOrBase64) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
|
||||||
|
|
@ -145,28 +145,50 @@ export class PayServer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
//创建订单并且支付
|
//创建订单并且支付
|
||||||
async createPayorOrder(orderInfo) {
|
async createPayorOrder(orderInfo, type = 1) {//1 ip订单 2充值包订单
|
||||||
// let payReducerUrl = 'https://www.deotaland.ai/#/order-management'
|
let payReducerUrl = ''
|
||||||
let payReducerUrl = `${window.location.origin}/#/order-management`
|
|
||||||
// await this.init();
|
// await this.init();
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
let pamras = {
|
let pamras = {}
|
||||||
|
switch (type) {
|
||||||
|
case 1:
|
||||||
|
payReducerUrl = `${window.location.origin}/#/order-management`
|
||||||
|
pamras = {
|
||||||
product_id: orderInfo.product_id,
|
product_id: orderInfo.product_id,
|
||||||
"methods": [
|
"methods": [
|
||||||
"card"
|
"card"
|
||||||
],
|
],
|
||||||
"success_url":payReducerUrl,
|
"success_url": payReducerUrl,
|
||||||
"cancel_url":payReducerUrl,
|
"cancel_url": payReducerUrl,
|
||||||
"quantity":orderInfo.quantity,
|
"quantity": orderInfo.quantity,
|
||||||
"project_id": orderInfo.project_id,
|
"project_id": orderInfo.project_id,
|
||||||
"project_details": orderInfo.project_details,
|
"project_details": orderInfo.project_details,
|
||||||
"order_info": orderInfo.order_info
|
"order_info": orderInfo.order_info
|
||||||
}
|
}
|
||||||
let res = await requestUtils.common(clientApi.default.createPayorOrder, pamras);
|
break;
|
||||||
|
case 2:
|
||||||
|
payReducerUrl = `${window.location.origin}/#/points-recharge`
|
||||||
|
pamras = {
|
||||||
|
"package_id": orderInfo.package_id,
|
||||||
|
"methods": [
|
||||||
|
"card"
|
||||||
|
],
|
||||||
|
"success_url": payReducerUrl,
|
||||||
|
"cancel_url": payReducerUrl
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let requestUrl = {
|
||||||
|
1: clientApi.default.createPayorOrder,
|
||||||
|
2: clientApi.default.CREATE_RECHARGE_ORDER,
|
||||||
|
}[type]
|
||||||
|
let res = await requestUtils.common(requestUrl, pamras);
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
let data = res.data
|
let data = res.data
|
||||||
// return
|
// return
|
||||||
window.location.href = data.url
|
window.location.href = (data.url)||data.payment_url
|
||||||
resolve(data);
|
resolve(data);
|
||||||
} else {
|
} else {
|
||||||
reject(res.msg)
|
reject(res.msg)
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,9 @@ service.interceptors.request.use(
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
|
closeMethods?.close()
|
||||||
// 请求错误处理
|
// 请求错误处理
|
||||||
console.error('请求错误:', error);
|
console.log('请求错误:', error);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -84,6 +85,7 @@ service.interceptors.response.use(
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
|
closeMethods?.close()
|
||||||
// 响应错误处理
|
// 响应错误处理
|
||||||
let message = '网络请求失败';
|
let message = '网络请求失败';
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
|
|
@ -108,7 +110,6 @@ service.interceptors.response.use(
|
||||||
// 请求已发送但没有收到响应
|
// 请求已发送但没有收到响应
|
||||||
message = '网络连接失败,请检查网络';
|
message = '网络连接失败,请检查网络';
|
||||||
}
|
}
|
||||||
console.error(message);
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,9 @@ importers:
|
||||||
'@google/genai':
|
'@google/genai':
|
||||||
specifier: ^1.27.0
|
specifier: ^1.27.0
|
||||||
version: 1.27.0
|
version: 1.27.0
|
||||||
|
'@splinetool/runtime':
|
||||||
|
specifier: ^1.12.29
|
||||||
|
version: 1.12.29
|
||||||
'@twind/core':
|
'@twind/core':
|
||||||
specifier: ^1.1.3
|
specifier: ^1.1.3
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
|
|
@ -1088,6 +1091,13 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/@splinetool/runtime@1.12.29:
|
||||||
|
resolution: {integrity: sha512-uxqg497LrJdYy3bEE3fBVsnNJvy6xORq7+3kCrFkCsFYeQmffzcASRJr2giEcOgR7UKm+0KHVy/annXPBXBNfQ==}
|
||||||
|
dependencies:
|
||||||
|
on-change: 4.0.2
|
||||||
|
semver-compare: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@sxzz/popperjs-es@2.11.7:
|
/@sxzz/popperjs-es@2.11.7:
|
||||||
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
|
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
@ -3084,6 +3094,11 @@ packages:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/on-change@4.0.2:
|
||||||
|
resolution: {integrity: sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/once@1.4.0:
|
/once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -3408,6 +3423,10 @@ packages:
|
||||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/semver-compare@1.0.0:
|
||||||
|
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/semver@7.7.3:
|
/semver@7.7.3:
|
||||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue