| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- <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="switchTab('female')">
- 女模特
- </div>
- <div class="tab-item" :class="{ active: activeTab === 'male' }" @click="switchTab('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, nextTick } 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 switchTab = (tab: 'female' | 'male') => {
- activeTab.value = tab
- // 使用 nextTick 确保 DOM 更新后再滚动
- nextTick(() => {
- const modelGrid = document.querySelector('.model-grid') as HTMLElement
- if (modelGrid) {
- modelGrid.scrollTop = 0
- }
- })
- }
- // 确认选择
- 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>
|