2481 lines
58 KiB
Vue
2481 lines
58 KiB
Vue
<template>
|
||
<aside class="floating-sidebar">
|
||
<!-- IP类型选择 -->
|
||
<div class="form-section" >
|
||
<div class="expression-info">
|
||
<span class="expression-description">
|
||
{{ $t('iPandCardLeft.ipType') }}
|
||
</span>
|
||
</div>
|
||
<div class="ip-type-grid">
|
||
<div
|
||
class="ip-type-card"
|
||
:class="{ active: ipType === 1 }"
|
||
@click="handleIpTypeSelect(1)"
|
||
>
|
||
<div class="ip-type-label">{{ $t('iPandCardLeft.character') }}</div>
|
||
</div>
|
||
<div
|
||
class="ip-type-card"
|
||
:class="{ active: ipType === 2 }"
|
||
@click="handleIpTypeSelect(2)"
|
||
>
|
||
<div class="ip-type-label">{{ $t('iPandCardLeft.animal') }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 表情选择 -->
|
||
<div class="form-section" v-if="false">
|
||
<!-- <label class="section-label">表情选择</label> -->
|
||
<div class="expression-info">
|
||
<span class="expression-description">
|
||
选择一个表情来丰富您的角色形象
|
||
</span>
|
||
</div>
|
||
<div class="expression-grid">
|
||
<div
|
||
v-for="expression in expressions"
|
||
:key="expression.id"
|
||
class="expression-card"
|
||
:class="{ active: selectedExpression?.id === expression.id }"
|
||
@click="handleExpressionSelect(expression)"
|
||
>
|
||
<div class="expression-preview">
|
||
<img :src="expression.imageUrl" :alt="expression.name">
|
||
</div>
|
||
<div class="expression-name">{{ expression.name }}</div>
|
||
<div v-if="selectedExpression?.id === expression.id" class="expression-selected">✓</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 文本提示输入 -->
|
||
<div class="form-section">
|
||
<div class="expression-info">
|
||
<span class="expression-description">
|
||
{{ $t('iPandCardLeft.textPrompt') }}
|
||
</span>
|
||
</div>
|
||
<div class="prompt-input-container">
|
||
<div class="textarea-wrapper">
|
||
<textarea
|
||
class="prompt-input custom-scrollbar"
|
||
:placeholder="$t('iPandCardLeft.placeholder.characterDescription')"
|
||
v-model="formData.prompt"
|
||
@input="autoResizeTextarea"
|
||
ref="textareaRef"
|
||
:disabled="isOptimizing"
|
||
></textarea>
|
||
<!-- 扫描线动画效果 -->
|
||
<div v-if="isOptimizing" class="scan-overlay">
|
||
<div class="scan-line"></div>
|
||
</div>
|
||
<!-- <button
|
||
class="optimizer-btn"
|
||
@click="handleOptimizePrompt"
|
||
:disabled="isOptimizing || !prompt.trim()"
|
||
>
|
||
<span class="btn-icon">🪄</span>
|
||
</button> -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 参考图片上传 -->
|
||
<div class="form-section">
|
||
<div class="expression-info">
|
||
<span class="expression-description">
|
||
{{ $t('iPandCardLeft.addReferenceImage') }}
|
||
</span>
|
||
</div>
|
||
<div
|
||
class="image-upload-area"
|
||
@click="triggerFileUpload"
|
||
@dragover.prevent="handleDragOver"
|
||
@dragenter.prevent="handleDragEnter"
|
||
@dragleave.prevent="handleDragLeave"
|
||
@drop.prevent="handleDrop"
|
||
:class="{ 'drag-over': isDragOver, 'uploading': isUploading }"
|
||
>
|
||
<!-- 图片预览 -->
|
||
<div v-if="formData.previewImage && !isUploading" class="image-preview">
|
||
<img :src="formData.previewImage" alt="Selected image">
|
||
<button class="remove-image" @click.stop="removeImage">×</button>
|
||
</div>
|
||
<!-- 上传进度 -->
|
||
<div v-else-if="isUploading" class="upload-progress">
|
||
<div class="upload-spinner">
|
||
<div class="spinner-ring"></div>
|
||
<div class="spinner-ring"></div>
|
||
<div class="spinner-ring"></div>
|
||
</div>
|
||
<div class="upload-progress-text">loading...</div>
|
||
</div>
|
||
<!-- 上传提示 -->
|
||
<div v-else class="upload-prompt">
|
||
<div class="upload-icon">+</div>
|
||
<span class="upload-text">{{ $t('iPandCardLeft.uploadOrSelectImage') }}</span>
|
||
<span class="drag-text">{{ $t('iPandCardLeft.dragImageHere') }}</span>
|
||
</div>
|
||
<!-- 隐藏的文件输入 -->
|
||
<input
|
||
ref="fileInput"
|
||
type="file"
|
||
accept="image/*"
|
||
@change="handleFileChange"
|
||
class="hidden-file-input"
|
||
>
|
||
</div>
|
||
</div>
|
||
<!-- 材质选择 -->
|
||
<div class="form-section" v-if="false">
|
||
<label class="section-label">{{ $t('iPandCardLeft.material.title') }}</label>
|
||
<div class="material-selection">
|
||
<div class="material-info">
|
||
<span class="material-description">
|
||
{{ $t('iPandCardLeft.material.description') }}
|
||
</span>
|
||
</div>
|
||
<div class="material-grid">
|
||
<div
|
||
v-for="material in materials"
|
||
:key="material.id"
|
||
class="material-card"
|
||
:class="{ active: selectedMaterial?.id === material.id }"
|
||
@click="handleMaterialSelect(material)"
|
||
>
|
||
<div class="material-preview">
|
||
<img v-if="material.imageUrl" :src="material.imageUrl" :alt="material.name">
|
||
<div v-else class="material-placeholder">
|
||
<div class="placeholder-icon">{{ material.icon }}</div>
|
||
<div class="placeholder-text">{{ material.name }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="material-details">
|
||
<div class="material-name">{{ material.name }}</div>
|
||
<div class="material-type">{{ material.type }}</div>
|
||
</div>
|
||
<div v-if="selectedMaterial?.id === material.id" class="material-selected">✓</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 颜色选择 -->
|
||
<div class="form-section" v-if="selectedMaterial">
|
||
<label class="section-label">{{ $t('iPandCardLeft.color.title') }}</label>
|
||
<div class="color-selection">
|
||
<div class="color-info">
|
||
<span class="color-description">
|
||
{{ $t('iPandCardLeft.color.description', { material: selectedMaterial.name }) }}
|
||
</span>
|
||
</div>
|
||
<div class="color-grid">
|
||
<div
|
||
v-for="color in availableColors"
|
||
:key="color.id"
|
||
class="color-card"
|
||
:class="{ active: selectedColor?.id === color.id }"
|
||
@click="handleColorSelect(color)"
|
||
>
|
||
<div class="color-preview" :style="{ backgroundColor: color.hex }">
|
||
<div v-if="selectedColor?.id === color.id" class="color-selected">✓</div>
|
||
</div>
|
||
<div class="color-details">
|
||
<div class="color-name">{{ color.name }}</div>
|
||
<div class="color-hex">{{ color.hex }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 电子模块选择 -->
|
||
<div class="form-section" v-if="false">
|
||
<label class="section-label">{{ $t('iPandCardLeft.electronicModule') }}</label>
|
||
<div class="module-selection">
|
||
<div class="module-grid">
|
||
<div
|
||
v-for="module in electronicModules"
|
||
:key="module.id"
|
||
class="module-card"
|
||
:class="{ active: selectedModule?.id === module.id }"
|
||
@click="handleModuleSelect(module)"
|
||
>
|
||
<div class="module-icon">{{ module.icon }}</div>
|
||
<div class="module-info">
|
||
<div class="module-name">{{ module.name }}</div>
|
||
</div>
|
||
<div v-if="selectedModule?.id === module.id" class="module-selected">✓</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 草图选择系统 -->
|
||
<div class="form-section" v-if="selectedModule">
|
||
<label class="section-label">{{ $t('iPandCardLeft.sketch.title') }}</label>
|
||
<div class="sketch-selection">
|
||
<div class="sketch-info">
|
||
<span class="sketch-description">
|
||
{{ $t('iPandCardLeft.sketch.description', { module: selectedModule.name }) }}
|
||
</span>
|
||
</div>
|
||
<div class="sketch-grid">
|
||
<div
|
||
v-for="sketch in availableSketches"
|
||
:key="sketch.id"
|
||
class="sketch-card"
|
||
:class="{ active: selectedSketch?.id === sketch.id }"
|
||
@click="handleSketchSelect(sketch)"
|
||
>
|
||
<div class="sketch-preview">
|
||
<img v-if="sketch.imageUrl" :src="sketch.imageUrl" :alt="sketch.name">
|
||
</div>
|
||
<div class="sketch-details">
|
||
<div class="sketch-name">{{ sketch.name }}</div>
|
||
<div class="sketch-proportions">{{ sketch.proportions }}</div>
|
||
</div>
|
||
<div v-if="selectedSketch?.id === sketch.id" class="sketch-selected">✓</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 图片数量选择 -->
|
||
<div class="form-section" v-if="false">
|
||
<div class="expression-info">
|
||
<span class="expression-description">
|
||
选择图片数量
|
||
</span>
|
||
</div>
|
||
<div class="quantity-options">
|
||
<button
|
||
v-for="num in [1, 2, 3, 4]"
|
||
:key="num"
|
||
class="quantity-option"
|
||
:class="{ active: generateCount === num }"
|
||
@click="handleQuantitySelect(num)"
|
||
>
|
||
{{ num }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<!-- 生成按钮 -->
|
||
<div class="generate-section">
|
||
<el-button
|
||
type="primary"
|
||
class="generate-btn"
|
||
@click="handleGenerate"
|
||
:disabled="isGenerateButtonDisabled"
|
||
>
|
||
{{ $t('common.generate') }}
|
||
</el-button>
|
||
</div>
|
||
</aside>
|
||
</template>
|
||
|
||
<script setup>
|
||
// import lts from '../../assets/sketches/lts.png'
|
||
// import mk2dy from '../../assets/sketches/mk2dy.png'
|
||
import { ref, onMounted, watch, nextTick, computed, getCurrentInstance, reactive } from 'vue';
|
||
import { ElMessage } from 'element-plus';
|
||
// import cz1 from '../../assets/material/cz1.jpg'
|
||
import { FileServer } from '@deotaland/utils';
|
||
const filePlug = new FileServer();
|
||
// 定义事件
|
||
const emit = defineEmits(['image-generated', 'model-generated', 'generate-requested', 'import-character', 'navigate-back', 'updateProjectInfo']);
|
||
const props = defineProps({
|
||
Info: {
|
||
type: Object,
|
||
default: () => ({})
|
||
}
|
||
})
|
||
// 获取 i18n 实例
|
||
const { proxy } = getCurrentInstance();
|
||
const $t = proxy.$t;
|
||
|
||
const formData = ref({
|
||
prompt: '',
|
||
previewImage:'',
|
||
})
|
||
|
||
const textareaRef = ref(null);
|
||
const fileInput = ref(null); // 文件输入引用
|
||
const generateCount = ref(4); // 生成数量,默认为1
|
||
const isOptimizing = ref(false); // 优化状态
|
||
const isDragOver = ref(false); // 拖拽状态
|
||
const isUploading = ref(false); // 图片上传状态
|
||
// IP类型选择(人物/动物),默认人物,并保存到本地
|
||
const ipType = ref(1);
|
||
const handleIpTypeSelect = (type) => {
|
||
ipType.value = type;
|
||
// try {
|
||
// localStorage.setItem('ipType', type);
|
||
// } catch (e) {
|
||
// // 忽略本地存储异常
|
||
// }
|
||
};
|
||
// 新增:电子模块和草图选择相关状态
|
||
const selectedModule = ref(null); // 选中的电子模块
|
||
const selectedSketch = ref(null); // 选中的草图
|
||
const selectedMaterial = ref(null); // 选中的材质
|
||
const selectedColor = ref(null); // 选中的颜色
|
||
const selectedExpression = ref(null); // 选中的表情
|
||
const selectedHairColor = ref(null); // 选中的发色
|
||
const selectedSkinColor = ref(null); // 选中的肤色
|
||
|
||
// 表情数据:改为动态遍历 assets/email 目录
|
||
const expressions = ref([]);
|
||
|
||
const init = () => {
|
||
}
|
||
onMounted(() => {
|
||
init()
|
||
// loadExpressions();
|
||
});
|
||
|
||
// 颜色数据库 - 基于材质类型提供不同的颜色选项
|
||
const colorDatabase = ref({
|
||
metal_brushed: [
|
||
{ id: 'silver', name: 'Silver', hex: '#C0C0C0', description: 'Classic silver metallic' },
|
||
{ id: 'gold', name: 'Gold', hex: '#FFD700', description: 'Luxurious gold finish' },
|
||
{ id: 'copper', name: 'Copper', hex: '#B87333', description: 'Warm copper tone' },
|
||
{ id: 'titanium', name: 'Titanium', hex: '#878681', description: 'Modern titanium gray' },
|
||
{ id: 'bronze', name: 'Bronze', hex: '#CD7F32', description: 'Antique bronze' },
|
||
{ id: 'steel', name: 'Steel', hex: '#71797E', description: 'Industrial steel gray' }
|
||
],
|
||
plastic_matte: [
|
||
{ id: 'white', name: 'White', hex: '#FFFFFF', description: 'Pure white' },
|
||
{ id: 'black', name: 'Black', hex: '#2C2C2C', description: 'Deep black' },
|
||
{ id: 'red', name: 'Red', hex: '#E53E3E', description: 'Vibrant red' },
|
||
{ id: 'blue', name: 'Blue', hex: '#3182CE', description: 'Ocean blue' },
|
||
{ id: 'green', name: 'Green', hex: '#38A169', description: 'Forest green' },
|
||
{ id: 'yellow', name: 'Yellow', hex: '#D69E2E', description: 'Sunny yellow' },
|
||
{ id: 'purple', name: 'Purple', hex: '#805AD5', description: 'Royal purple' },
|
||
{ id: 'orange', name: 'Orange', hex: '#DD6B20', description: 'Bright orange' }
|
||
],
|
||
carbon_fiber: [
|
||
{ id: 'carbon_black', name: 'Carbon Black', hex: '#1A1A1A', description: 'Classic carbon black' },
|
||
{ id: 'carbon_gray', name: 'Carbon Gray', hex: '#4A4A4A', description: 'Lighter carbon gray' },
|
||
{ id: 'carbon_blue', name: 'Carbon Blue', hex: '#1E3A8A', description: 'Blue carbon weave' },
|
||
{ id: 'carbon_red', name: 'Carbon Red', hex: '#991B1B', description: 'Red carbon accent' }
|
||
],
|
||
wood_natural: [
|
||
{ id: 'oak', name: 'Oak', hex: '#DEB887', description: 'Natural oak wood' },
|
||
{ id: 'walnut', name: 'Walnut', hex: '#8B4513', description: 'Rich walnut brown' },
|
||
{ id: 'maple', name: 'Maple', hex: '#F5DEB3', description: 'Light maple wood' },
|
||
{ id: 'cherry', name: 'Cherry', hex: '#A0522D', description: 'Cherry wood red' },
|
||
{ id: 'mahogany', name: 'Mahogany', hex: '#C04000', description: 'Deep mahogany' },
|
||
{ id: 'pine', name: 'Pine', hex: '#FFEAA7', description: 'Light pine wood' }
|
||
],
|
||
ceramic_glossy: [
|
||
{ id: 'ceramic_white', name: 'Ceramic White', hex: '#FAFAFA', description: 'Pure ceramic white' },
|
||
{ id: 'ceramic_cream', name: 'Cream', hex: '#FFF8DC', description: 'Warm cream ceramic' },
|
||
{ id: 'ceramic_blue', name: 'Ceramic Blue', hex: '#4FC3F7', description: 'Sky blue ceramic' },
|
||
{ id: 'ceramic_green', name: 'Ceramic Green', hex: '#81C784', description: 'Mint green ceramic' },
|
||
{ id: 'ceramic_pink', name: 'Ceramic Pink', hex: '#F48FB1', description: 'Soft pink ceramic' }
|
||
],
|
||
fabric_soft: [
|
||
{ id: 'fabric_beige', name: 'Beige', hex: '#F5F5DC', description: 'Natural beige fabric' },
|
||
{ id: 'fabric_gray', name: 'Gray', hex: '#808080', description: 'Neutral gray fabric' },
|
||
{ id: 'fabric_navy', name: 'Navy', hex: '#000080', description: 'Deep navy fabric' },
|
||
{ id: 'fabric_burgundy', name: 'Burgundy', hex: '#800020', description: 'Rich burgundy fabric' },
|
||
{ id: 'fabric_olive', name: 'Olive', hex: '#808000', description: 'Olive green fabric' },
|
||
{ id: 'fabric_cream', name: 'Cream', hex: '#FFFDD0', description: 'Soft cream fabric' }
|
||
]
|
||
});
|
||
|
||
// 材质数据
|
||
// const materials = ref([
|
||
// {
|
||
// id: 'metal_brushed',
|
||
// name: '白毛绒',
|
||
// type: 'Metal',
|
||
// icon: '🔩',
|
||
// imageUrl: cz1, // 可以添加实际的材质图片URL
|
||
// description: 'Industrial brushed metal finish',
|
||
// properties: {
|
||
// reflectivity: 0.8,
|
||
// roughness: 0.3,
|
||
// metallic: 1.0
|
||
// }
|
||
// }
|
||
// // ,
|
||
// // {
|
||
// // id: 'plastic_matte',
|
||
// // name: '材质2',
|
||
// // type: 'Plastic',
|
||
// // icon: '🧱',
|
||
// // imageUrl: '',
|
||
// // description: 'Non-reflective plastic surface',
|
||
// // properties: {
|
||
// // reflectivity: 0.1,
|
||
// // roughness: 0.9,
|
||
// // metallic: 0.0
|
||
// // }
|
||
// // }
|
||
// ]);
|
||
|
||
// 电子模块数据
|
||
const electronicModules = ref([
|
||
{
|
||
id: 'arduino_uno',
|
||
name: 'D1 Mini',
|
||
icon: '🔌',
|
||
category: 'microcontroller',
|
||
compatibility: ['basic', 'intermediate', 'advanced']
|
||
},
|
||
{
|
||
id: 'raspberry_pi',
|
||
name: 'D1 Plus Pi',
|
||
icon: '🔌',
|
||
category: 'computer',
|
||
compatibility: ['intermediate', 'advanced']
|
||
},
|
||
]);
|
||
|
||
// 草图数据 - 基于选中的电子模块动态生成
|
||
// const sketchDatabase = ref({
|
||
// arduino_uno: [
|
||
// {
|
||
// id: 'humanoid_basic',
|
||
// name: 'Basic Humanoid',
|
||
// proportions: 'Head:Body = 1:1',
|
||
// pose: 'standing',
|
||
// imageUrl: lts,
|
||
// description: 'Classic humanoid proportions suitable for Arduino-based robots',
|
||
// compatibility: ['arduino_uno', 'servo_motor'],
|
||
// headToBodyRatio: '5:5'
|
||
// }
|
||
// ],
|
||
// raspberry_pi: [
|
||
// {
|
||
// id: 'advanced_humanoid',
|
||
// name: 'Advanced Humanoid',
|
||
// proportions: 'Head:Body = 1:2.5',
|
||
// pose: 'dynamic',
|
||
// imageUrl: mk2dy,
|
||
// description: 'Sophisticated design leveraging Raspberry Pi capabilities',
|
||
// compatibility: ['raspberry_pi', 'camera_module'],
|
||
// headToBodyRatio: '6:4'
|
||
// },
|
||
// ],
|
||
// esp32: [
|
||
// {
|
||
// id: 'iot_character',
|
||
// name: 'IoT Character',
|
||
// proportions: 'Head:Body = 1:6',
|
||
// pose: 'connected',
|
||
// imageUrl: '/src/assets/sketches/esp32_compact.svg',
|
||
// description: 'IoT-enabled character design for ESP32',
|
||
// compatibility: ['esp32']
|
||
// }
|
||
// ],
|
||
// servo_motor: [
|
||
// {
|
||
// id: 'articulated_figure',
|
||
// name: 'Articulated Figure',
|
||
// proportions: 'Head:Body = 1:7',
|
||
// pose: 'articulated',
|
||
// imageUrl: '/src/assets/sketches/arduino_standing.svg',
|
||
// description: 'Design emphasizing joint movement and servo integration',
|
||
// compatibility: ['servo_motor', 'arduino_uno']
|
||
// }
|
||
// ],
|
||
// led_matrix: [
|
||
// {
|
||
// id: 'display_character',
|
||
// name: 'Display Character',
|
||
// proportions: 'Head:Body = 1:6',
|
||
// pose: 'expressive',
|
||
// imageUrl: '/src/assets/sketches/arduino_sitting.svg',
|
||
// description: 'Character design featuring LED matrix display integration',
|
||
// compatibility: ['led_matrix', 'arduino_uno']
|
||
// }
|
||
// ],
|
||
// camera_module: [
|
||
// {
|
||
// id: 'vision_robot',
|
||
// name: 'Vision Robot',
|
||
// proportions: 'Head:Body = 1:6',
|
||
// pose: 'observing',
|
||
// imageUrl: '/src/assets/sketches/raspberry_action.svg',
|
||
// description: 'Design optimized for camera module and computer vision',
|
||
// compatibility: ['camera_module', 'raspberry_pi']
|
||
// }
|
||
// ]
|
||
// });
|
||
|
||
// 计算属性:基于选中模块获取可用草图
|
||
const availableSketches = computed(() => {
|
||
if (!selectedModule.value) return [];
|
||
return sketchDatabase.value[selectedModule.value.id] || [];
|
||
});
|
||
|
||
// 计算属性:基于选中材质获取可用颜色
|
||
const availableColors = computed(() => {
|
||
if (!selectedMaterial.value) return [];
|
||
return colorDatabase.value[selectedMaterial.value.id] || [];
|
||
});
|
||
|
||
const characterProfile = ref(null); // 字符档案
|
||
// 新增:材质选择处理函数
|
||
const handleMaterialSelect = (material) => {
|
||
// 如果点击的是已选中的材质,则取消选择
|
||
if (selectedMaterial.value?.id === material.id) {
|
||
selectedMaterial.value = null;
|
||
selectedColor.value = null; // 同时重置颜色选择
|
||
} else {
|
||
selectedMaterial.value = material;
|
||
// 重置颜色选择
|
||
selectedColor.value = null;
|
||
}
|
||
};
|
||
|
||
// 新增:颜色选择处理函数
|
||
const handleColorSelect = (color) => {
|
||
// 如果点击的是已选中的颜色,则取消选择
|
||
if (selectedColor.value?.id === color.id) {
|
||
selectedColor.value = null;
|
||
} else {
|
||
selectedColor.value = color;
|
||
}
|
||
};
|
||
|
||
// 新增:电子模块选择处理函数
|
||
const handleModuleSelect = (module) => {
|
||
// 如果点击的是已选中的模块,则取消选择
|
||
if (selectedModule.value?.id === module.id) {
|
||
selectedModule.value = null;
|
||
selectedSketch.value = null; // 同时重置草图选择
|
||
} else {
|
||
selectedModule.value = module;
|
||
// 重置草图选择
|
||
selectedSketch.value = null;
|
||
|
||
}
|
||
};
|
||
|
||
// 新增:表情选择处理函数
|
||
const handleExpressionSelect = (expression) => {
|
||
// 如果点击的是已选中的表情,则取消选择
|
||
if (selectedExpression.value?.id === expression.id) {
|
||
selectedExpression.value = null;
|
||
} else {
|
||
selectedExpression.value = expression;
|
||
}
|
||
};
|
||
|
||
|
||
|
||
// 新增:草图选择处理函数
|
||
const handleSketchSelect = (sketch) => {
|
||
// 如果点击的是已选中的草图,则取消选择
|
||
if (selectedSketch.value?.id === sketch.id) {
|
||
selectedSketch.value = null;
|
||
} else {
|
||
selectedSketch.value = sketch;
|
||
}
|
||
};
|
||
|
||
// 综合输入验证函数
|
||
const validateGenerationInputs = () => {
|
||
const errors = []
|
||
const warnings = []
|
||
// 验证参考图像 - 现在必须上传参考图片
|
||
if (!(formData.value.previewImage || formData.value.prompt)) {
|
||
errors.push($t('common.validation.referenceImageRequired'))
|
||
}
|
||
return { errors, warnings }
|
||
}
|
||
|
||
// 计算属性:判断生成按钮是否应该禁用
|
||
const isGenerateButtonDisabled = computed(() => {
|
||
const validation = validateGenerationInputs();
|
||
return validation.errors.length > 0;
|
||
});
|
||
|
||
// 错误处理函数
|
||
const handleValidationError = (errors) => {
|
||
if (errors.length > 0) {
|
||
// 显示第一个错误消息(已经通过 i18n 翻译)
|
||
const errorMessage = typeof errors[0] === 'string' ? errors[0] : String(errors[0])
|
||
ElMessage.error(errorMessage)
|
||
}
|
||
}
|
||
|
||
const handleProcessingError = (error, context = '') => {
|
||
console.error(`处理错误 ${context}:`, error)
|
||
|
||
let userMessage = '操作失败'
|
||
|
||
if (error.message) {
|
||
if (error.message.includes('network') || error.message.includes('fetch')) {
|
||
userMessage = '网络连接失败,请检查网络后重试'
|
||
} else if (error.message.includes('timeout')) {
|
||
userMessage = '请求超时,请稍后重试'
|
||
} else if (error.message.includes('quota') || error.message.includes('limit')) {
|
||
userMessage = '已达到使用限制,请稍后重试'
|
||
} else if (error.message.includes('format') || error.message.includes('invalid')) {
|
||
userMessage = '输入格式不正确,请检查后重试'
|
||
} else {
|
||
userMessage = `操作失败: ${error.message}`
|
||
}
|
||
}
|
||
|
||
ElMessage.error(userMessage)
|
||
|
||
// 记录详细错误信息用于调试
|
||
if (process.env.NODE_ENV === 'development') {
|
||
console.error('详细错误信息:', {
|
||
context,
|
||
error: error.stack || error,
|
||
timestamp: new Date().toISOString(),
|
||
userAgent: navigator.userAgent
|
||
})
|
||
}
|
||
}
|
||
|
||
|
||
// 处理数量选择
|
||
const handleQuantitySelect = (num) => {
|
||
generateCount.value = num;
|
||
};
|
||
|
||
// 处理生成按钮点击
|
||
const handleGenerate = async () => {
|
||
console.log('生成数量:', generateCount.value);
|
||
// 执行综合验证
|
||
const validation = validateGenerationInputs();
|
||
if (validation.errors.length > 0) {
|
||
handleValidationError(validation.errors);
|
||
return;
|
||
}
|
||
try {
|
||
await handleGenerateWithMultipleImages();
|
||
// // 检查是否选择了电子模块和草图(新功能)
|
||
// if (selectedModule.value && selectedSketch.value) {
|
||
// // 使用多图像生成模式
|
||
|
||
// } else {
|
||
// // 使用原有的单图像生成模式
|
||
// await handleGenerateWithSingleImage();
|
||
// }
|
||
} catch (error) {
|
||
console.error('生成过程出错:', error);
|
||
handleProcessingError(error, '图像生成');
|
||
}
|
||
};
|
||
|
||
// 新增:多图像生成处理函数(仅草图参考)
|
||
const handleGenerateWithMultipleImages = async () => {
|
||
try {
|
||
// 构建角色档案,只包含草图信息,不包含电子模块
|
||
const profile = {
|
||
appearance: formData.value.prompt,
|
||
};
|
||
// 传递参数给父组件,只包含草图信息
|
||
const params = {
|
||
profile: profile,
|
||
inspirationImage: formData.value.previewImage,
|
||
count: generateCount.value,
|
||
ipType:ipType.value,
|
||
}
|
||
emit('generate-requested', params);
|
||
} catch (error) {
|
||
console.error('创建多图像角色档案失败:', error);
|
||
handleProcessingError(error, '多图像角色档案创建');
|
||
}
|
||
};
|
||
// 自适应调整textarea高度
|
||
const autoResizeTextarea = () => {
|
||
if (textareaRef.value) {
|
||
// 重置高度以准确计算内容高度
|
||
textareaRef.value.style.height = 'auto';
|
||
|
||
// 设置高度限制
|
||
const minHeight = 40; // 最小高度,适合单行文本
|
||
const maxHeight = 300; // 最大高度,与CSS保持一致
|
||
const scrollHeight = textareaRef.value.scrollHeight;
|
||
|
||
// 计算实际需要的高度
|
||
const newHeight = Math.max(minHeight, Math.min(maxHeight, scrollHeight));
|
||
textareaRef.value.style.height = newHeight + 'px';
|
||
|
||
// 如果内容超过最大高度,确保滚动条可见
|
||
if (scrollHeight > maxHeight) {
|
||
textareaRef.value.style.overflowY = 'auto';
|
||
} else {
|
||
textareaRef.value.style.overflowY = 'hidden';
|
||
}
|
||
}
|
||
};
|
||
// 触发文件上传
|
||
const triggerFileUpload = () => {
|
||
fileInput.value?.click();
|
||
};
|
||
// 处理文件选择
|
||
const handleFileChange = async (event) => {
|
||
const file = event.target.files?.[0];
|
||
if (file) {
|
||
isUploading.value = true;
|
||
try {
|
||
const imgUrl = await filePlug.uploadFile(file);
|
||
formData.value.previewImage = imgUrl;
|
||
} catch (error) {
|
||
console.error('图片上传失败:', error);
|
||
ElMessage.error('图片上传失败,请重试');
|
||
} finally {
|
||
isUploading.value = false;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 移除已上传的图片
|
||
const removeImage = () => {
|
||
formData.value.previewImage = '';
|
||
if (fileInput.value) {
|
||
fileInput.value.value = ''; // 重置文件输入
|
||
}
|
||
};
|
||
|
||
// 拖拽事件处理函数
|
||
const handleDragOver = (event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
};
|
||
|
||
const handleDragEnter = (event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
isDragOver.value = true;
|
||
};
|
||
|
||
const handleDragLeave = (event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
// 只有当拖拽离开整个上传区域时才设置为false
|
||
if (event.target === event.currentTarget) {
|
||
isDragOver.value = false;
|
||
}
|
||
};
|
||
|
||
const handleDrop = async (event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
isDragOver.value = false;
|
||
|
||
const files = event.dataTransfer.files;
|
||
if (files && files.length > 0) {
|
||
const file = files[0];
|
||
// 检查文件类型是否为图片
|
||
if (file.type.startsWith('image/')) {
|
||
console.log('拖拽上传文件:', file);
|
||
isUploading.value = true;
|
||
try {
|
||
const imgUrl = await filePlug.uploadFile(file);
|
||
formData.value.previewImage = imgUrl;
|
||
} catch (error) {
|
||
console.error('拖拽上传失败:', error);
|
||
ElMessage.error('图片上传失败,请重试');
|
||
} finally {
|
||
isUploading.value = false;
|
||
}
|
||
} else {
|
||
ElMessage.error('请选择有效的图片文件');
|
||
}
|
||
}
|
||
};
|
||
|
||
// 监听 prompt 变化,自动调整 textarea 高度
|
||
watch(() => formData.value.prompt, () => {
|
||
nextTick(() => {
|
||
autoResizeTextarea();
|
||
});
|
||
});
|
||
|
||
// 组件挂载后初始化
|
||
onMounted(() => {
|
||
autoResizeTextarea();
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.import-btn {
|
||
width: 100%;
|
||
background-color: #381e16 !important;
|
||
border-color: #381e16 !important;
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.import-btn:hover,
|
||
.import-btn:focus {
|
||
filter: brightness(1.1);
|
||
}
|
||
.prompt-input-container {
|
||
position: relative;
|
||
width: 100%;
|
||
}
|
||
|
||
.textarea-wrapper {
|
||
position: relative;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 扫描线动画效果 */
|
||
.scan-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.1);
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
z-index: 5;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.scan-line {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: linear-gradient(90deg,
|
||
transparent 0%,
|
||
rgba(99, 102, 241, 0.8) 20%,
|
||
rgba(99, 102, 241, 1) 50%,
|
||
rgba(99, 102, 241, 0.8) 80%,
|
||
transparent 100%);
|
||
animation: scan 2s linear infinite;
|
||
box-shadow: 0 0 10px 2px rgba(99, 102, 241, 0.5);
|
||
}
|
||
|
||
@keyframes scan {
|
||
0% {
|
||
top: 0;
|
||
opacity: 0;
|
||
}
|
||
10% {
|
||
opacity: 1;
|
||
}
|
||
90% {
|
||
opacity: 1;
|
||
}
|
||
100% {
|
||
top: calc(100% - 2px);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
/* 浮动侧边栏样式 */
|
||
.floating-sidebar {
|
||
max-width: calc(100vw - 20px); /* 确保不超过视口宽度减去外边距 */
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
/* box-shadow: 0 4px 20px rgba(107, 70, 193, 0.3); */
|
||
border: 1px solid rgba(167, 139, 250, 0.2);
|
||
overflow-y: auto; /* 仅在侧边栏内容过多时允许垂直滚动 */
|
||
max-height: calc(100vh - 20px); /* 限制侧边栏最大高度 */
|
||
position: relative;
|
||
background-color: #293140;
|
||
min-width: 300px;
|
||
}
|
||
|
||
.floating-sidebar::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(45deg, rgba(167, 139, 250, 0.05) 0%, transparent 50%, rgba(107, 70, 193, 0.05) 100%);
|
||
pointer-events: none;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
/* 侧边栏头部样式 */
|
||
.sidebar-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
padding-bottom: 16px;
|
||
border-bottom: 1px solid rgba(167, 139, 250, 0.2);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 返回按钮样式 */
|
||
.back-button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 8px 12px;
|
||
background: rgba(167, 139, 250, 0.15);
|
||
border: 1px solid rgba(167, 139, 250, 0.3);
|
||
border-radius: 8px;
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
backdrop-filter: blur(10px);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.back-button:hover {
|
||
background: rgba(167, 139, 250, 0.25);
|
||
border-color: #A78BFA;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 12px rgba(167, 139, 250, 0.3);
|
||
}
|
||
|
||
.back-button:active {
|
||
transform: translateY(0);
|
||
transition: all 0.1s ease;
|
||
}
|
||
|
||
.back-button svg {
|
||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.back-button:hover svg {
|
||
transform: translateX(-2px);
|
||
}
|
||
|
||
.back-text {
|
||
font-weight: 500;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 768px) {
|
||
.back-button {
|
||
padding: 6px 10px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.back-button svg {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
.back-text {
|
||
display: none; /* 移动端隐藏文字,只显示图标 */
|
||
}
|
||
}
|
||
|
||
/* IP类型图片选择样式 */
|
||
.ip-type-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.ip-type-card {
|
||
position: relative;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
border: 1px solid var(--border-color, rgba(255, 255, 255, 0.1));
|
||
background-color: var(--bg-color, rgba(255, 255, 255, 0.05));
|
||
height: 34px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.ip-type-card:hover {
|
||
border-color: var(--border-hover-color, rgba(255, 255, 255, 0.2));
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.ip-type-card.active {
|
||
background-color: rgba(167, 139, 250, 0.2);
|
||
border-color: #A78BFA;
|
||
box-shadow: 0 0 0 2px rgba(167, 139, 250, 0.4);
|
||
}
|
||
|
||
.ip-type-label {
|
||
color: var(--text-color, #fff);
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 适应亮色主题 */
|
||
:root {
|
||
--border-color: rgba(0, 0, 0, 0.1);
|
||
--bg-color: rgba(0, 0, 0, 0.05);
|
||
--border-hover-color: rgba(0, 0, 0, 0.2);
|
||
--text-color: #333;
|
||
}
|
||
|
||
/* 深色主题特定样式 */
|
||
.dark-theme {
|
||
--border-color: rgba(255, 255, 255, 0.1);
|
||
--bg-color: rgba(255, 255, 255, 0.05);
|
||
--border-hover-color: rgba(255, 255, 255, 0.2);
|
||
--text-color: #fff;
|
||
}
|
||
|
||
/* 确保当前主题下的样式正确应用 */
|
||
.ip-type-label {
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
|
||
.ip-type-card {
|
||
border-color: var(--el-border-color-light);
|
||
background-color: var(--el-bg-color-light);
|
||
}
|
||
|
||
.ip-type-card:hover {
|
||
border-color: var(--el-border-color);
|
||
}
|
||
|
||
.ip-type-card.active:hover {
|
||
border-color: #A78BFA;
|
||
}
|
||
|
||
.logo-container {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.logo {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.logo-text {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.credits {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.credits-icon {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.credits-amount {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #ffcc00;
|
||
}
|
||
|
||
.credits-label {
|
||
font-size: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* 表单区域样式 */
|
||
.form-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.section-label {
|
||
display: block;
|
||
font-size: 14px;
|
||
margin-bottom: 8px;
|
||
margin-top: 16px;
|
||
padding: 6px 10px;
|
||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.1) 0%, rgba(107, 70, 193, 0.05) 100%);
|
||
border-radius: 6px;
|
||
border-left: 3px solid #A78BFA;
|
||
font-weight: 500;
|
||
color: #E9D5FF;
|
||
}
|
||
|
||
/* 选择框样式 */
|
||
.model-select {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 6px;
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
appearance: none;
|
||
position: relative;
|
||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-5 h-5'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19.5 8.25l-7.5 7.5-7.5-7.5'/%3E%3C/svg%3E");
|
||
background-repeat: no-repeat;
|
||
background-position: right 12px center;
|
||
background-size: 16px;
|
||
}
|
||
|
||
.model-select:focus {
|
||
outline: none;
|
||
border-color: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
/* 图片上传区域样式 */
|
||
.image-upload-area {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 180px;
|
||
border: 2px dashed rgba(221, 221, 221, 0.5);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
overflow: hidden;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.image-upload-area:hover {
|
||
border-color: #675b5c;
|
||
background-color: rgba(99, 102, 241, 0.05);
|
||
}
|
||
|
||
/* 拖拽状态样式 */
|
||
.image-upload-area.drag-over {
|
||
border-color: #6366f1;
|
||
background-color: rgba(99, 102, 241, 0.1);
|
||
transform: scale(1.02);
|
||
box-shadow: 0 0 15px rgba(99, 102, 241, 0.3);
|
||
}
|
||
|
||
/* 上传状态样式 */
|
||
.image-upload-area.uploading {
|
||
border-color: #6B46C1;
|
||
background-color: rgba(107, 70, 193, 0.05);
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 上传进度样式 */
|
||
.upload-progress {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
/* 旋转加载动画 */
|
||
.upload-spinner {
|
||
position: relative;
|
||
width: 48px;
|
||
height: 48px;
|
||
}
|
||
|
||
.spinner-ring {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 3px solid transparent;
|
||
border-top: 3px solid #6B46C1;
|
||
border-radius: 50%;
|
||
animation: spin 1.2s linear infinite;
|
||
}
|
||
|
||
.spinner-ring:nth-child(2) {
|
||
width: 36px;
|
||
height: 36px;
|
||
top: 6px;
|
||
left: 6px;
|
||
border-top-color: #A78BFA;
|
||
animation-duration: 1s;
|
||
animation-direction: reverse;
|
||
}
|
||
|
||
.spinner-ring:nth-child(3) {
|
||
width: 24px;
|
||
height: 24px;
|
||
top: 12px;
|
||
left: 12px;
|
||
border-top-color: #C4B5FD;
|
||
animation-duration: 0.8s;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.upload-progress-text {
|
||
font-size: 14px;
|
||
color: #6B46C1;
|
||
font-weight: 500;
|
||
animation: pulse 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 0.8; }
|
||
50% { opacity: 1; }
|
||
}
|
||
|
||
.upload-prompt {
|
||
text-align: center;
|
||
color: #666;
|
||
}
|
||
|
||
.upload-icon {
|
||
font-size: 32px;
|
||
color: #999;
|
||
margin-bottom: 8px;
|
||
display: block;
|
||
}
|
||
|
||
.upload-text {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.drag-text {
|
||
font-size: 14px;
|
||
color: #6366f1;
|
||
margin-top: 8px;
|
||
display: block;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* 隐藏文件输入 */
|
||
.hidden-file-input {
|
||
position: absolute;
|
||
width: 0;
|
||
height: 0;
|
||
opacity: 0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* 图片预览样式 */
|
||
.image-preview {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.image-preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
/* 移除图片按钮 */
|
||
.remove-image {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
background-color: rgba(0, 0, 0, 0.6);
|
||
color: white;
|
||
border: none;
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
|
||
.remove-image:hover {
|
||
background-color: rgba(0, 0, 0, 0.8);
|
||
}
|
||
|
||
.upload-icon {
|
||
font-size: 32px;
|
||
opacity: 0.6;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.upload-text {
|
||
font-size: 14px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* 按钮样式 */
|
||
.generate-worldview-btn {
|
||
width: 100%;
|
||
padding: 10px 16px;
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
border-radius: 6px;
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.generate-worldview-btn:hover {
|
||
background-color: rgba(255, 255, 255, 0.15);
|
||
border-color: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
/* 文本输入区域 */
|
||
.prompt-input {
|
||
width: 100%;
|
||
min-height: 40px; /* 最小高度,适合单行文本 */
|
||
max-height: 300px; /* 设置最大高度 */
|
||
padding: 12px 12px 40px 12px; /* 增加底部内边距,为按钮留出空间 */
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 6px;
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
resize: none; /* 禁用手动调整大小 */
|
||
overflow-y: hidden; /* 初始隐藏滚动条,内容多时通过JS控制 */
|
||
margin-bottom: 0; /* 移除底部边距,因为按钮现在是绝对定位 */
|
||
line-height: 1.5; /* 设置行高,便于计算高度 */
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
.prompt-input:disabled {
|
||
opacity: 0.7;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.prompt-input::placeholder {
|
||
color: rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.prompt-input:focus {
|
||
outline: none;
|
||
border-color: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.textarea-wrapper:has(.prompt-input:disabled) .prompt-input {
|
||
border-color: rgba(99, 102, 241, 0.5);
|
||
box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.3);
|
||
}
|
||
|
||
.optimizer-btn {
|
||
position: absolute;
|
||
bottom: 10px;
|
||
right: 8px;
|
||
padding: 6px 12px;
|
||
background-color: #6366f1;
|
||
border: none;
|
||
border-radius: 6px;
|
||
color: white;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
transition: background-color 0.2s;
|
||
z-index: 10;
|
||
}
|
||
|
||
.optimizer-btn:hover:not(:disabled) {
|
||
background-color: #4f46e5;
|
||
}
|
||
|
||
.optimizer-btn:disabled {
|
||
background-color: #9ca3af;
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* 风格选项 */
|
||
.style-options {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.style-option {
|
||
padding: 10px 12px;
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 6px;
|
||
color: #ffffff;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.style-option:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.style-option.active {
|
||
background-color: rgba(255, 204, 0, 0.2);
|
||
border-color: #ffcc00;
|
||
color: #ffcc00;
|
||
}
|
||
|
||
/* 数量选项 */
|
||
.quantity-options {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.quantity-option {
|
||
padding: 10px 12px;
|
||
background-color: rgba(167, 139, 250, 0.1);
|
||
border: 1px solid rgba(167, 139, 250, 0.2);
|
||
border-radius: 6px;
|
||
color: #A78BFA;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-align: center;
|
||
}
|
||
|
||
.quantity-option:hover {
|
||
background-color: rgba(167, 139, 250, 0.2);
|
||
border-color: rgba(167, 139, 250, 0.4);
|
||
}
|
||
|
||
.quantity-option.active {
|
||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.3) 0%, rgba(124, 58, 237, 0.3) 100%);
|
||
border-color: #A78BFA;
|
||
color: #A78BFA;
|
||
box-shadow: 0 0 0 2px rgba(167, 139, 250, 0.3);
|
||
}
|
||
|
||
/* 生成按钮区域 */
|
||
.generate-section {
|
||
margin-top: 32px;
|
||
}
|
||
|
||
/* 横向控制布局 */
|
||
.generate-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 生成按钮样式 */
|
||
.generate-btn {
|
||
width: 100%;
|
||
--el-button-bg-color: #7C3AED;
|
||
--el-button-hover-bg-color: #8B5CF6;
|
||
--el-button-active-bg-color: #6B46C1;
|
||
--el-button-font-weight: 500;
|
||
--el-button-font-size: 16px;
|
||
--el-button-border-radius: 8px;
|
||
--el-button-border-color: #7C3AED;
|
||
--el-button--base-line-height: 2.5;
|
||
height: 40px;
|
||
}
|
||
|
||
.generate-3d-btn {
|
||
flex: 1;
|
||
--el-button-bg-color: #10b981;
|
||
--el-button-hover-bg-color: #059669;
|
||
--el-button-active-bg-color: #047857;
|
||
--el-button-font-weight: 500;
|
||
--el-button-font-size: 16px;
|
||
--el-button-border-radius: 8px;
|
||
--el-button-border-color: #10b981;
|
||
--el-button--base-line-height: 2.5;
|
||
height: 40px;
|
||
}
|
||
|
||
|
||
/* 下拉菜单项样式优化 */
|
||
:deep(.el-dropdown-menu) {
|
||
--el-dropdown-menu-bg-color: #fff;
|
||
--el-dropdown-menu-border-color: #e0e0e0;
|
||
--el-dropdown-menu-item-hover-bg-color: #fff2f0;
|
||
--el-dropdown-menu-item-hover-color: #ff4d4f;
|
||
--el-dropdown-menu-item-active-color: #ff4d4f;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
:deep(.el-dropdown-menu__item) {
|
||
padding: 10px 20px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
:deep(.el-dropdown-menu__item:hover) {
|
||
transform: translateX(4px);
|
||
}
|
||
|
||
|
||
|
||
.btn-icon {
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 768px) {
|
||
.floating-sidebar {
|
||
width: 100%;
|
||
max-width: none;
|
||
margin: 0;
|
||
padding: 15px;
|
||
max-height: calc(100vh - 10px);
|
||
}
|
||
}
|
||
|
||
/* 表情选择样式 */
|
||
.expression-selection {
|
||
width: 100%;
|
||
}
|
||
|
||
.expression-info {
|
||
margin-bottom: 12px;
|
||
padding: 8px 12px;
|
||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.1) 0%, rgba(107, 70, 193, 0.05) 100%);
|
||
border-radius: 6px;
|
||
border-left: 3px solid #A78BFA;
|
||
}
|
||
|
||
.expression-description {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.expression-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.expression-card {
|
||
position: relative;
|
||
padding: 8px;
|
||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.08) 0%, rgba(107, 70, 193, 0.05) 100%);
|
||
border: 1px solid rgba(167, 139, 250, 0.2);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.expression-card:hover {
|
||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.15) 0%, rgba(107, 70, 193, 0.1) 100%);
|
||
border-color: rgba(167, 139, 250, 0.3);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(167, 139, 250, 0.2);
|
||
}
|
||
|
||
.expression-card.active {
|
||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.25) 0%, rgba(124, 58, 237, 0.2) 100%);
|
||
border-color: #A78BFA;
|
||
box-shadow: 0 0 0 2px rgba(167, 139, 250, 0.4);
|
||
}
|
||
|
||
.expression-preview {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.expression-preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.expression-name {
|
||
font-size: 11px;
|
||
color: #ffffff;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.expression-selected {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
width: 16px;
|
||
height: 16px;
|
||
background: linear-gradient(135deg, #A78BFA, #7C3AED);
|
||
color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
font-weight: bold;
|
||
box-shadow: 0 2px 4px rgba(167, 139, 250, 0.3);
|
||
}
|
||
|
||
/* 发色选择样式 */
|
||
.hair-color-info {
|
||
margin-bottom: 12px;
|
||
padding: 8px 12px;
|
||
background-color: rgba(59, 130, 246, 0.1);
|
||
border-radius: 6px;
|
||
border-left: 3px solid #3b82f6;
|
||
}
|
||
|
||
.hair-color-description {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.hair-color-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.hair-color-card {
|
||
position: relative;
|
||
padding: 8px;
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.hair-color-card:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.hair-color-card.active {
|
||
background-color: rgba(59, 130, 246, 0.2);
|
||
border-color: #3b82f6;
|
||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
|
||
}
|
||
|
||
.hair-color-preview {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
margin-bottom: 6px;
|
||
position: relative;
|
||
}
|
||
|
||
.hair-color-selected {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
background-color: rgba(255, 255, 255, 0.9);
|
||
color: #3b82f6;
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.hair-color-name {
|
||
font-size: 11px;
|
||
color: #ffffff;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
/* 肤色选择样式 */
|
||
.skin-color-info {
|
||
margin-bottom: 12px;
|
||
padding: 8px 12px;
|
||
background-color: rgba(236, 72, 153, 0.1);
|
||
border-radius: 6px;
|
||
border-left: 3px solid #ec4899;
|
||
}
|
||
|
||
.skin-color-description {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.skin-color-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.skin-color-card {
|
||
position: relative;
|
||
padding: 8px;
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.skin-color-card:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.skin-color-card.active {
|
||
background-color: rgba(236, 72, 153, 0.2);
|
||
border-color: #ec4899;
|
||
box-shadow: 0 0 0 2px rgba(236, 72, 153, 0.3);
|
||
}
|
||
|
||
.skin-color-preview {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
margin-bottom: 6px;
|
||
position: relative;
|
||
}
|
||
|
||
.skin-color-selected {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
background-color: rgba(255, 255, 255, 0.9);
|
||
color: #ec4899;
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.skin-color-name {
|
||
font-size: 11px;
|
||
color: #ffffff;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
/* 响应式调整 */
|
||
@media (max-width: 480px) {
|
||
.expression-grid, .hair-color-grid, .skin-color-grid {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
}
|
||
}
|
||
|
||
/* 材质选择样式 */
|
||
.material-selection {
|
||
width: 100%;
|
||
}
|
||
|
||
.material-info {
|
||
margin-bottom: 12px;
|
||
padding: 8px 12px;
|
||
background-color: rgba(168, 85, 247, 0.1);
|
||
border-radius: 6px;
|
||
border-left: 3px solid #a855f7;
|
||
}
|
||
|
||
.material-description {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.material-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 10px;
|
||
}
|
||
|
||
.material-card {
|
||
position: relative;
|
||
padding: 10px;
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
min-height: 100px;
|
||
}
|
||
|
||
.material-card:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.material-card.active {
|
||
background-color: rgba(168, 85, 247, 0.2);
|
||
border-color: #a855f7;
|
||
box-shadow: 0 0 0 2px rgba(168, 85, 247, 0.3);
|
||
}
|
||
|
||
.material-preview {
|
||
width: 40px;
|
||
height: 40px;
|
||
flex-shrink: 0;
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.material-preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.material-placeholder {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
height: 100%;
|
||
}
|
||
|
||
.material-placeholder .placeholder-icon {
|
||
font-size: 18px;
|
||
margin-bottom: 2px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.material-placeholder .placeholder-text {
|
||
font-size: 8px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.material-details {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.material-name {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: #ffffff;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.material-type {
|
||
font-size: 10px;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
}
|
||
|
||
.material-selected {
|
||
position: absolute;
|
||
top: 6px;
|
||
right: 6px;
|
||
width: 18px;
|
||
height: 18px;
|
||
background-color: #a855f7;
|
||
color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 11px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 颜色选择样式 */
|
||
.color-selection {
|
||
width: 100%;
|
||
}
|
||
|
||
.color-info {
|
||
margin-bottom: 12px;
|
||
padding: 8px 12px;
|
||
background-color: rgba(34, 197, 94, 0.1);
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.color-description {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.color-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.color-card {
|
||
position: relative;
|
||
padding: 8px;
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
min-width: 80px;
|
||
}
|
||
|
||
.color-card:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.color-card.active {
|
||
background-color: rgba(34, 197, 94, 0.2);
|
||
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
|
||
}
|
||
|
||
.color-preview {
|
||
position: relative;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
margin-bottom: 6px;
|
||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.color-selected {
|
||
color: white;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
text-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
|
||
}
|
||
|
||
.color-details {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.color-name {
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
color: #ffffff;
|
||
margin-bottom: 2px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.color-hex {
|
||
font-size: 9px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
/* 电子模块选择样式 */
|
||
.module-selection {
|
||
width: 100%;
|
||
}
|
||
|
||
.module-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 12px;
|
||
}
|
||
|
||
.module-card {
|
||
position: relative;
|
||
padding: 12px;
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.module-card:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.module-card.active {
|
||
background-color: rgba(99, 102, 241, 0.2);
|
||
border-color: #6366f1;
|
||
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3);
|
||
}
|
||
|
||
.module-icon {
|
||
font-size: 24px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.module-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.module-name {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #ffffff;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.module-size {
|
||
font-size: 11px;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
}
|
||
|
||
.module-selected {
|
||
position: absolute;
|
||
top: 6px;
|
||
right: 6px;
|
||
width: 18px;
|
||
height: 18px;
|
||
background-color: #6366f1;
|
||
color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 草图选择样式 */
|
||
.sketch-selection {
|
||
width: 100%;
|
||
}
|
||
|
||
.sketch-info {
|
||
margin-bottom: 12px;
|
||
padding: 8px 12px;
|
||
background-color: rgba(99, 102, 241, 0.1);
|
||
border-radius: 6px;
|
||
border-left: 3px solid #6366f1;
|
||
}
|
||
|
||
.sketch-description {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.sketch-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 10px;
|
||
}
|
||
|
||
.sketch-card {
|
||
position: relative;
|
||
padding: 12px;
|
||
background-color: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
.sketch-card:hover {
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.sketch-card.active {
|
||
background-color: rgba(34, 197, 94, 0.2);
|
||
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
|
||
}
|
||
|
||
.sketch-preview {
|
||
width: 60px;
|
||
height: 60px;
|
||
flex-shrink: 0;
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.sketch-preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.sketch-placeholder {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
height: 100%;
|
||
}
|
||
|
||
.placeholder-icon {
|
||
font-size: 20px;
|
||
margin-bottom: 4px;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.placeholder-text {
|
||
font-size: 10px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.sketch-details {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.sketch-name {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #ffffff;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.sketch-proportions {
|
||
font-size: 11px;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.sketch-selected {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
width: 20px;
|
||
height: 20px;
|
||
color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 响应式调整 */
|
||
@media (max-width: 480px) {
|
||
.module-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.module-card {
|
||
padding: 10px;
|
||
}
|
||
|
||
.sketch-card {
|
||
flex-direction: column;
|
||
text-align: center;
|
||
}
|
||
|
||
.sketch-preview {
|
||
width: 50px;
|
||
height: 50px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.color-grid {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
}
|
||
|
||
.color-card {
|
||
min-height: 70px;
|
||
padding: 6px;
|
||
}
|
||
|
||
.color-preview {
|
||
width: 28px;
|
||
height: 28px;
|
||
}
|
||
}
|
||
|
||
/* 亮色主题样式 */
|
||
:global(:root:not(.dark)) {
|
||
.floating-sidebar {
|
||
background-color: #FFFFFF;
|
||
border-right: 1px solid #E5E7EB;
|
||
color: #374151;
|
||
}
|
||
|
||
.sidebar-header {
|
||
background-color: #F9FAFB;
|
||
border-bottom: 1px solid #E5E7EB;
|
||
}
|
||
|
||
.back-button {
|
||
background-color: #FFFFFF;
|
||
border: 1px solid #E5E7EB;
|
||
color: #374151;
|
||
}
|
||
|
||
.back-button:hover {
|
||
background-color: #F3F4F6;
|
||
border-color: #D1D5DB;
|
||
}
|
||
|
||
.ip-type-card {
|
||
background-color: #FFFFFF;
|
||
border: 1px solid #E5E7EB;
|
||
color: #374151;
|
||
}
|
||
|
||
.ip-type-card:hover {
|
||
background-color: #F9FAFB;
|
||
border-color: #D1D5DB;
|
||
}
|
||
|
||
.ip-type-card.active {
|
||
background-color: rgba(99, 102, 241, 0.1);
|
||
border-color: #6366f1;
|
||
}
|
||
|
||
.image-upload-area {
|
||
background-color: #FFFFFF;
|
||
border: 2px dashed #D1D5DB;
|
||
color: #6B7280;
|
||
}
|
||
|
||
.image-upload-area:hover {
|
||
background-color: #F9FAFB;
|
||
border-color: #9CA3AF;
|
||
}
|
||
|
||
.image-upload-area.drag-over {
|
||
background-color: #F3F4F6;
|
||
border-color: #6366f1;
|
||
}
|
||
|
||
.remove-image {
|
||
background-color: rgba(0, 0, 0, 0.6);
|
||
color: white;
|
||
}
|
||
|
||
.remove-image:hover {
|
||
background-color: rgba(0, 0, 0, 0.8);
|
||
}
|
||
|
||
.prompt-input {
|
||
background-color: #FFFFFF;
|
||
border: 1px solid #D1D5DB;
|
||
color: #374151;
|
||
}
|
||
|
||
.prompt-input:focus {
|
||
border-color: #6366f1;
|
||
}
|
||
|
||
.prompt-input::placeholder {
|
||
color: #9CA3AF;
|
||
}
|
||
|
||
.optimizer-btn {
|
||
background-color: #6366f1;
|
||
color: white;
|
||
}
|
||
|
||
.optimizer-btn:hover:not(:disabled) {
|
||
background-color: #4f46e5;
|
||
}
|
||
|
||
.style-option {
|
||
background-color: #FFFFFF;
|
||
border: 1px solid #D1D5DB;
|
||
color: #374151;
|
||
}
|
||
|
||
.style-option:hover {
|
||
background-color: #F9FAFB;
|
||
border-color: #9CA3AF;
|
||
}
|
||
|
||
.style-option.active {
|
||
background-color: rgba(255, 204, 0, 0.1);
|
||
border-color: #ffcc00;
|
||
color: #b45309;
|
||
}
|
||
|
||
.quantity-option {
|
||
background-color: rgba(167, 139, 250, 0.05);
|
||
border: 1px solid rgba(167, 139, 250, 0.2);
|
||
color: #6B46C1;
|
||
}
|
||
|
||
.quantity-option:hover {
|
||
background-color: rgba(167, 139, 250, 0.1);
|
||
border-color: rgba(167, 139, 250, 0.3);
|
||
}
|
||
|
||
.quantity-option.active {
|
||
background: linear-gradient(135deg, rgba(167, 139, 250, 0.2) 0%, rgba(124, 58, 237, 0.2) 100%);
|
||
border-color: #6B46C1;
|
||
color: #6B46C1;
|
||
}
|
||
|
||
.generate-btn {
|
||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||
color: white;
|
||
}
|
||
|
||
.generate-btn:hover:not(:disabled) {
|
||
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
||
}
|
||
|
||
.generate-btn:disabled {
|
||
background-color: #9CA3AF;
|
||
}
|
||
|
||
.expression-card {
|
||
background-color: #FFFFFF;
|
||
border: 1px solid #E5E7EB;
|
||
color: #374151;
|
||
}
|
||
|
||
.expression-card:hover {
|
||
background-color: #F9FAFB;
|
||
border-color: #D1D5DB;
|
||
}
|
||
|
||
.expression-card.active {
|
||
background-color: rgba(34, 197, 94, 0.1);
|
||
}
|
||
|
||
.hair-color-card,
|
||
.skin-color-card,
|
||
.material-card,
|
||
.color-card,
|
||
.module-card,
|
||
.sketch-card {
|
||
background-color: #FFFFFF;
|
||
border: 1px solid #E5E7EB;
|
||
color: #374151;
|
||
}
|
||
|
||
.hair-color-card:hover,
|
||
.skin-color-card:hover,
|
||
.material-card:hover,
|
||
.color-card:hover,
|
||
.module-card:hover,
|
||
.sketch-card:hover {
|
||
background-color: #F9FAFB;
|
||
border-color: #D1D5DB;
|
||
}
|
||
|
||
.hair-color-card.active,
|
||
.skin-color-card.active,
|
||
.material-card.active,
|
||
.color-card.active,
|
||
.module-card.active,
|
||
.sketch-card.active {
|
||
background-color: rgba(34, 197, 94, 0.1);
|
||
}
|
||
|
||
.color-preview,
|
||
.hair-color-preview,
|
||
.skin-color-preview {
|
||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.material-preview,
|
||
.module-preview {
|
||
background-color: #F9FAFB;
|
||
}
|
||
|
||
.upload-icon,
|
||
.placeholder-icon {
|
||
opacity: 0.6;
|
||
color: #6B7280;
|
||
}
|
||
|
||
.upload-text,
|
||
.drag-text,
|
||
.expression-name,
|
||
.hair-color-name,
|
||
.skin-color-name,
|
||
.material-name,
|
||
.material-type,
|
||
.color-name,
|
||
.module-name,
|
||
.module-size,
|
||
.sketch-name,
|
||
.sketch-proportions,
|
||
.placeholder-text {
|
||
color: #374151;
|
||
}
|
||
|
||
.expression-description,
|
||
.hair-color-description,
|
||
.skin-color-description,
|
||
.material-description {
|
||
color: #6B7280;
|
||
}
|
||
|
||
.scan-overlay {
|
||
background-color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.scan-line {
|
||
background: linear-gradient(90deg, transparent, #6366f1, transparent);
|
||
}
|
||
|
||
.section-label,
|
||
.form-section label {
|
||
/* color: #374151; */
|
||
}
|
||
|
||
.generate-worldview-btn {
|
||
background-color: #FFFFFF;
|
||
border: 1px solid #D1D5DB;
|
||
color: #374151;
|
||
}
|
||
|
||
.generate-worldview-btn:hover {
|
||
background-color: #F9FAFB;
|
||
border-color: #9CA3AF;
|
||
}
|
||
|
||
.sketch-info {
|
||
background-color: rgba(99, 102, 241, 0.05);
|
||
border-left: 3px solid #6366f1;
|
||
}
|
||
|
||
.sketch-description {
|
||
color: #6B7280;
|
||
}
|
||
}
|
||
</style> |