|
|
@@ -0,0 +1,475 @@
|
|
|
+<template>
|
|
|
+ <el-dialog v-model="dialogVisible" title="选择模特" width="1000px" :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false" custom-class="model-generation-dialog" @close="handleClose">
|
|
|
+ <div class="model-generation-container">
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
+ <div class="main-content">
|
|
|
+
|
|
|
+ <!-- 左侧:女模特选择 -->
|
|
|
+ <div class="model-section">
|
|
|
+ <h2>女模特</h2>
|
|
|
+ <div class="model-display">
|
|
|
+ <el-image v-if="selectedFemaleModel" :src="selectedFemaleModel.image_url" :alt="selectedFemaleModel.name"
|
|
|
+ class="selected-model-image" lazy :preview-src-list="[selectedFemaleModel.image_url]" fit="cover" />
|
|
|
+ <div v-else class="placeholder-image">
|
|
|
+ <span>请在下方列表选择</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧:男模特选择 -->
|
|
|
+ <div class="model-section">
|
|
|
+ <h2>男模特</h2>
|
|
|
+ <div class="model-display">
|
|
|
+ <el-image v-if="selectedMaleModel" :src="selectedMaleModel.image_url" :alt="selectedMaleModel.name"
|
|
|
+ class="selected-model-image" lazy :preview-src-list="[selectedMaleModel.image_url]" fit="cover" />
|
|
|
+ <div v-else class="placeholder-image">
|
|
|
+ <span>请在下方列表选择</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部模特列表区域 -->
|
|
|
+ <div class="model-list-section">
|
|
|
+ <!-- 标签页切换 -->
|
|
|
+ <div class="tabs-container">
|
|
|
+ <div class="tab-item" :class="{ active: activeTab === 'female' }" @click="activeTab = 'female'">
|
|
|
+ 女模特
|
|
|
+ </div>
|
|
|
+ <div class="tab-item" :class="{ active: activeTab === 'male' }" @click="activeTab = 'male'">
|
|
|
+ 男模特
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 模特网格列表 -->
|
|
|
+ <div class="model-grid">
|
|
|
+ <div v-for="model in currentModelList" :key="model.id" class="model-item"
|
|
|
+ :class="{ selected: isModelSelected(model) }" @click="selectModel(model)">
|
|
|
+ <el-image :src="model.image_url" :alt="model.name" class="model-thumbnail" lazy fit="cover" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="handleCancel">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleConfirm" :disabled="!canConfirm">
|
|
|
+ 确认
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import { getShoesModelTemplateApi } from '@/apis/other'
|
|
|
+
|
|
|
+// 定义组件的 props
|
|
|
+interface Props {
|
|
|
+ modelValue: boolean
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps<Props>()
|
|
|
+
|
|
|
+// 定义组件的事件
|
|
|
+const emit = defineEmits<{
|
|
|
+ 'update:modelValue': [value: boolean]
|
|
|
+ confirm: [models: { female: any; male: any }]
|
|
|
+ cancel: []
|
|
|
+}>()
|
|
|
+
|
|
|
+// Dialog 显示状态
|
|
|
+const dialogVisible = computed({
|
|
|
+ get: () => props.modelValue,
|
|
|
+ set: (value) => emit('update:modelValue', value)
|
|
|
+})
|
|
|
+
|
|
|
+// 模特数据类型定义
|
|
|
+interface ModelData {
|
|
|
+ id: number
|
|
|
+ name: string
|
|
|
+ image_url: string
|
|
|
+ gender: 'male' | 'female'
|
|
|
+ keywords: string
|
|
|
+ status: number
|
|
|
+}
|
|
|
+
|
|
|
+// 当前激活的标签页
|
|
|
+const activeTab = ref<'female' | 'male'>('female')
|
|
|
+
|
|
|
+// 选中的女模特
|
|
|
+const selectedFemaleModel = ref<ModelData | null>(null)
|
|
|
+
|
|
|
+// 选中的男模特
|
|
|
+const selectedMaleModel = ref<ModelData | null>(null)
|
|
|
+
|
|
|
+// 女模特列表
|
|
|
+const femaleModels = ref<ModelData[]>([])
|
|
|
+
|
|
|
+// 男模特列表
|
|
|
+const maleModels = ref<ModelData[]>([])
|
|
|
+
|
|
|
+// 当前显示的模特列表
|
|
|
+const currentModelList = computed(() => {
|
|
|
+ return activeTab.value === 'female' ? femaleModels.value : maleModels.value
|
|
|
+})
|
|
|
+
|
|
|
+// 是否可以确认选择
|
|
|
+const canConfirm = computed(() => {
|
|
|
+ return selectedFemaleModel.value || selectedMaleModel.value
|
|
|
+})
|
|
|
+
|
|
|
+// 选择模特
|
|
|
+const selectModel = (model: ModelData) => {
|
|
|
+ if (model.keywords === '女性') {
|
|
|
+ selectedFemaleModel.value = model
|
|
|
+ } else {
|
|
|
+ selectedMaleModel.value = model
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 判断模特是否被选中
|
|
|
+const isModelSelected = (model: ModelData) => {
|
|
|
+ if (model.keywords === '女性') {
|
|
|
+ return selectedFemaleModel.value?.id === model.id
|
|
|
+ } else {
|
|
|
+ return selectedMaleModel.value?.id === model.id
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 确认选择
|
|
|
+const handleConfirm = () => {
|
|
|
+ // 只传递必要的数据字段,避免序列化问题
|
|
|
+ const selectedModels = {
|
|
|
+ female: selectedFemaleModel.value ? {
|
|
|
+ id: selectedFemaleModel.value.id,
|
|
|
+ name: selectedFemaleModel.value.name,
|
|
|
+ image_url: selectedFemaleModel.value.image_url,
|
|
|
+ gender: selectedFemaleModel.value.gender,
|
|
|
+ keywords: selectedFemaleModel.value.keywords,
|
|
|
+ status: selectedFemaleModel.value.status
|
|
|
+ } : null,
|
|
|
+ male: selectedMaleModel.value ? {
|
|
|
+ id: selectedMaleModel.value.id,
|
|
|
+ name: selectedMaleModel.value.name,
|
|
|
+ image_url: selectedMaleModel.value.image_url,
|
|
|
+ gender: selectedMaleModel.value.gender,
|
|
|
+ keywords: selectedMaleModel.value.keywords,
|
|
|
+ status: selectedMaleModel.value.status
|
|
|
+ } : null
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('选中的模特:', selectedModels)
|
|
|
+
|
|
|
+ // 通过事件将数据发送给父组件
|
|
|
+ emit('confirm', selectedModels)
|
|
|
+ dialogVisible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 取消选择
|
|
|
+const handleCancel = () => {
|
|
|
+ emit('cancel')
|
|
|
+ dialogVisible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭弹窗
|
|
|
+const handleClose = () => {
|
|
|
+ emit('cancel')
|
|
|
+}
|
|
|
+
|
|
|
+// 获取模特列表
|
|
|
+const fetchModelList = async () => {
|
|
|
+ try {
|
|
|
+ const response = await getShoesModelTemplateApi({ status: 2 })
|
|
|
+ console.log(response)
|
|
|
+ if (response && response.data) {
|
|
|
+ // 根据性别分类模特
|
|
|
+ femaleModels.value = response.data.filter((model: ModelData) => model.keywords === '女性')
|
|
|
+ maleModels.value = response.data.filter((model: ModelData) => model.keywords === '男性')
|
|
|
+
|
|
|
+ // 预加载前几个图片以提高性能
|
|
|
+ setTimeout(() => {
|
|
|
+ preloadImages(femaleModels.value.slice(0, 10))
|
|
|
+ preloadImages(maleModels.value.slice(0, 10))
|
|
|
+ }, 100)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取模特列表失败:', error)
|
|
|
+ ElMessage.error('获取模特列表失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 预加载图片
|
|
|
+const preloadImages = (models: ModelData[]) => {
|
|
|
+ models.forEach(model => {
|
|
|
+ if (model.image_url) {
|
|
|
+ const img = new Image()
|
|
|
+ img.src = model.image_url
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 组件挂载时的初始化
|
|
|
+onMounted(() => {
|
|
|
+ console.log('模特生成页面已加载')
|
|
|
+ fetchModelList()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.model-generation-container {
|
|
|
+ padding: 0;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+.page-header {
|
|
|
+ text-align: center;
|
|
|
+ padding: 20px 0;
|
|
|
+ background: #ffffff;
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+
|
|
|
+ h1 {
|
|
|
+ color: #303133;
|
|
|
+ margin: 0;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.main-content {
|
|
|
+ display: flex;
|
|
|
+ gap: 15px;
|
|
|
+ padding: 15px;
|
|
|
+ position: relative;
|
|
|
+ max-width: 50%;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.model-section {
|
|
|
+ flex: 1;
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ color: #303133;
|
|
|
+ margin: 0;
|
|
|
+ padding: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 550;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.model-display {
|
|
|
+ width: 100%;
|
|
|
+ aspect-ratio: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border: 2px solid #e4e7ed;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ .selected-model-image {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ :deep(.el-image__inner) {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .placeholder-image {
|
|
|
+ color: #c0c4cc;
|
|
|
+ font-size: 11px;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ span {
|
|
|
+ color: #c0c4cc;
|
|
|
+ display: block;
|
|
|
+ line-height: 1.2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+.model-list-section {
|
|
|
+ margin: 2px 0px 0px 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.tabs-container {
|
|
|
+ display: flex;
|
|
|
+ /* background: #f8f9fa; */
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-item {
|
|
|
+ padding: 8px 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 550;
|
|
|
+ color: #606266;
|
|
|
+ border-bottom: 2px solid transparent;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ background: transparent;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #409eff;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: #409eff;
|
|
|
+ border-bottom-color: #409eff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.model-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(5, 1fr);
|
|
|
+ gap: 6px;
|
|
|
+ padding: 12px;
|
|
|
+ max-height: 320px;
|
|
|
+ overflow-y: auto;
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.model-item {
|
|
|
+ aspect-ratio: 1;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 3px;
|
|
|
+ overflow: hidden;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ background: #f8f9fa;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ transform: scale(1.01);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.selected {
|
|
|
+ border-color: #409eff;
|
|
|
+ border-width: 2px;
|
|
|
+ box-shadow: 0 0 0 1px #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .model-thumbnail {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ :deep(.el-image__inner) {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-image__placeholder) {
|
|
|
+ background: #f8f9fa;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #c0c4cc;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-image__error) {
|
|
|
+ background: #fef0f0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #f56c6c;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 滚动条样式
|
|
|
+.model-grid::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.model-grid::-webkit-scrollbar-track {
|
|
|
+ background: #f1f1f1;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.model-grid::-webkit-scrollbar-thumb {
|
|
|
+ background: #c1c1c1;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.model-grid::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #a8a8a8;
|
|
|
+}
|
|
|
+
|
|
|
+// 响应式设计
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .main-content {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .model-grid {
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 10px;
|
|
|
+ padding: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .confirm-button-container {
|
|
|
+ position: static;
|
|
|
+ text-align: center;
|
|
|
+ margin-top: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .model-list-section {
|
|
|
+ margin: 0 20px 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.model-generation-dialog {
|
|
|
+ .el-dialog__body {
|
|
|
+ padding: 0;
|
|
|
+ background: #EAECED;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-dialog__footer {
|
|
|
+ padding: 0;
|
|
|
+ border-top: 1px solid #e4e7ed;
|
|
|
+ background: #fafafa;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-dialog {
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-dialog__header {
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-dialog__title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+}</style>
|