fix: 模型库页面移动端适配
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { ModelApiTypeOption, ModelLibraryDto, ModelTypeOption } from '@/api/model/types';
|
||||
import { Box, CopyDocument, HomeFilled, OfficeBuilding, Search } from '@element-plus/icons-vue';
|
||||
import { Box, Close, CopyDocument, HomeFilled, OfficeBuilding, Search } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getApiTypeOptions, getModelLibraryList, getModelTypeOptions, getProviderList } from '@/api/model';
|
||||
import { useScreenStore } from '@/hooks/useScreen';
|
||||
|
||||
const router = useRouter();
|
||||
const { isMobile } = useScreenStore();
|
||||
|
||||
const showMobileFilter = ref(false);
|
||||
const loading = ref(false);
|
||||
const modelList = ref<ModelLibraryDto[]>([]);
|
||||
const totalCount = ref(0);
|
||||
@@ -201,6 +204,14 @@ watch([selectedProviders, selectedModelTypes, selectedApiTypes, isPremiumOnly],
|
||||
fetchModelList();
|
||||
}, { deep: true });
|
||||
|
||||
function openMobileFilter() {
|
||||
showMobileFilter.value = true;
|
||||
}
|
||||
|
||||
function closeMobileFilter() {
|
||||
showMobileFilter.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchProviderList();
|
||||
fetchModelTypeOptions();
|
||||
@@ -270,16 +281,24 @@ onMounted(() => {
|
||||
<div class="main-content">
|
||||
<div class="content-wrapper">
|
||||
<!-- 左侧筛选栏 -->
|
||||
<aside class="filter-sidebar">
|
||||
<aside class="filter-sidebar" :class="{ 'mobile-active': showMobileFilter }">
|
||||
<!-- 移动端遮罩 -->
|
||||
<div v-if="isMobile && showMobileFilter" class="mobile-overlay" @click="closeMobileFilter" />
|
||||
|
||||
<div class="filter-section">
|
||||
<div class="filter-header">
|
||||
<h3 class="filter-title">
|
||||
<el-icon><i-ep-filter /></el-icon>
|
||||
筛选条件
|
||||
</h3>
|
||||
<el-button link type="primary" size="small" @click="resetFilters">
|
||||
重置
|
||||
</el-button>
|
||||
<div class="filter-header-actions">
|
||||
<el-button link type="primary" size="small" @click="resetFilters">
|
||||
重置
|
||||
</el-button>
|
||||
<el-icon v-if="isMobile" class="close-btn" @click="closeMobileFilter">
|
||||
<Close />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
@@ -399,6 +418,13 @@ onMounted(() => {
|
||||
|
||||
<!-- 右侧模型列表 -->
|
||||
<main class="model-list-section">
|
||||
<!-- 移动端筛选按钮 -->
|
||||
<div v-if="isMobile" class="mobile-filter-bar">
|
||||
<el-button type="primary" :icon="Search" plain class="w-full" @click="openMobileFilter">
|
||||
筛选与搜索
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-wrapper">
|
||||
<el-skeleton :rows="8" animated />
|
||||
@@ -494,7 +520,7 @@ onMounted(() => {
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[12, 24, 48, 96]"
|
||||
:total="totalCount"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
|
||||
prev-text="上一页"
|
||||
next-text="下一页"
|
||||
background
|
||||
@@ -883,6 +909,7 @@ onMounted(() => {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
align-items: flex-start;
|
||||
|
||||
.model-icon {
|
||||
width: 56px;
|
||||
@@ -914,15 +941,19 @@ onMounted(() => {
|
||||
.model-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.model-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 6px 0;
|
||||
//overflow: hidden;
|
||||
margin: 0 0 4px 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding-right: 24px; // 防止文字遮挡复制按钮
|
||||
}
|
||||
|
||||
.model-provider {
|
||||
@@ -948,7 +979,6 @@ onMounted(() => {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
font-weight: 600;
|
||||
//text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
@@ -1102,4 +1132,251 @@ onMounted(() => {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.model-library-container {
|
||||
.banner-section {
|
||||
padding: 24px 16px;
|
||||
|
||||
.banner-content {
|
||||
.banner-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 24px;
|
||||
|
||||
.banner-left {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
|
||||
.stats-cards {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 16px 12px;
|
||||
|
||||
.content-wrapper {
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
|
||||
.filter-sidebar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&.mobile-active {
|
||||
width: auto;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
|
||||
.mobile-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1999;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 80%;
|
||||
max-width: 320px;
|
||||
z-index: 2000;
|
||||
border-radius: 0 16px 16px 0;
|
||||
overflow-y: auto;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
margin: 0;
|
||||
|
||||
.filter-header {
|
||||
.filter-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.close-btn {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
color: #909399;
|
||||
|
||||
&:hover {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-list-section {
|
||||
.mobile-filter-bar {
|
||||
margin-bottom: 16px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: rgba(245, 247, 250, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 12px 4px;
|
||||
margin: -16px -4px 16px -4px;
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
:deep(.el-pagination) {
|
||||
.el-pagination__jump {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-grid {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.model-card {
|
||||
padding: 20px; // 增加内边距
|
||||
margin-bottom: 16px; // 增加卡片间距
|
||||
border-radius: 16px;
|
||||
|
||||
.copy-btn-corner {
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.model-card-header {
|
||||
margin-bottom: 16px;
|
||||
gap: 16px;
|
||||
|
||||
.model-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
.icon-placeholder {
|
||||
font-size: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.icon-img {
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-info {
|
||||
.model-name {
|
||||
font-size: 17px;
|
||||
margin-bottom: 4px;
|
||||
padding-right: 36px; // 移动端预留更多空间给复制按钮
|
||||
}
|
||||
|
||||
.model-provider {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-id {
|
||||
padding: 6px 10px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.model-id-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.model-id-value {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-description {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
min-height: auto;
|
||||
-webkit-line-clamp: 2;
|
||||
max-height: 3em;
|
||||
|
||||
&:hover {
|
||||
/* 移动端取消悬停展开,或者改为点击展开(这里简单处理为取消悬停效果以免遮挡) */
|
||||
-webkit-line-clamp: 2;
|
||||
max-height: 3em;
|
||||
overflow: hidden;
|
||||
position: static;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.model-footer {
|
||||
padding-top: 12px;
|
||||
gap: 8px;
|
||||
|
||||
.model-tags {
|
||||
gap: 4px;
|
||||
|
||||
:deep(.el-tag) {
|
||||
height: 20px;
|
||||
padding: 0 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-pricing {
|
||||
padding: 4px 8px;
|
||||
|
||||
.pricing-label {
|
||||
display: none; // 移动端隐藏"计费倍率"文字,只显示数字
|
||||
}
|
||||
|
||||
.pricing-value {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
@@ -7,6 +7,7 @@ interface ImportMetaEnv {
|
||||
readonly VITE_WEB_BASE_API: string;
|
||||
readonly VITE_API_URL: string;
|
||||
readonly VITE_FILE_UPLOAD_API: string;
|
||||
readonly VITE_BUILD_COMPRESS: string;
|
||||
readonly VITE_SSO_SEVER_URL: string;
|
||||
readonly VITE_APP_VERSION: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user