fix: 模型库优化
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npx vue-tsc --noEmit)"
|
||||
"Bash(npx vue-tsc --noEmit)",
|
||||
"Bash(timeout 60 npx vue-tsc:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -12,14 +12,14 @@ export function getModelLibraryList(params?: ModelLibraryGetListInput) {
|
||||
if (params?.searchKey) {
|
||||
queryParams.append('SearchKey', params.searchKey);
|
||||
}
|
||||
if (params?.providerName) {
|
||||
queryParams.append('ProviderName', params.providerName);
|
||||
if (params?.providerNames && params.providerNames.length > 0) {
|
||||
params.providerNames.forEach(name => queryParams.append('ProviderNames', name));
|
||||
}
|
||||
if (params?.modelType !== undefined) {
|
||||
queryParams.append('ModelType', params.modelType.toString());
|
||||
if (params?.modelTypes && params.modelTypes.length > 0) {
|
||||
params.modelTypes.forEach(type => queryParams.append('ModelTypes', type.toString()));
|
||||
}
|
||||
if (params?.modelApiType !== undefined) {
|
||||
queryParams.append('ModelApiType', params.modelApiType.toString());
|
||||
if (params?.modelApiTypes && params.modelApiTypes.length > 0) {
|
||||
params.modelApiTypes.forEach(type => queryParams.append('ModelApiTypes', type.toString()));
|
||||
}
|
||||
if (params?.isPremiumOnly !== undefined) {
|
||||
queryParams.append('IsPremiumOnly', params.isPremiumOnly.toString());
|
||||
|
||||
@@ -46,9 +46,9 @@ export interface ModelLibraryDto {
|
||||
// 获取模型库列表查询参数
|
||||
export interface ModelLibraryGetListInput {
|
||||
searchKey?: string;
|
||||
providerName?: string;
|
||||
modelType?: ModelTypeEnum;
|
||||
modelApiType?: ModelApiTypeEnum;
|
||||
providerNames?: string[];
|
||||
modelTypes?: ModelTypeEnum[];
|
||||
modelApiTypes?: ModelApiTypeEnum[];
|
||||
isPremiumOnly?: boolean;
|
||||
skipCount?: number;
|
||||
maxResultCount?: number;
|
||||
|
||||
98
Yi.Ai.Vue3/src/pages/modelLibrary/element.text
Normal file
98
Yi.Ai.Vue3/src/pages/modelLibrary/element.text
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<el-tag v-for="tag in tags" :key="tag.name" closable :type="tag.type">
|
||||
{{ tag.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
import type { TagProps } from 'element-plus'
|
||||
|
||||
interface TagsItem {
|
||||
name: string
|
||||
type: TagProps['type']
|
||||
}
|
||||
|
||||
const tags = ref<TagsItem[]>([
|
||||
{ name: 'Tag 1', type: 'primary' },
|
||||
{ name: 'Tag 2', type: 'success' },
|
||||
{ name: 'Tag 3', type: 'info' },
|
||||
{ name: 'Tag 4', type: 'warning' },
|
||||
{ name: 'Tag 5', type: 'danger' },
|
||||
])
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<el-check-tag checked>Checked</el-check-tag>
|
||||
<el-check-tag :checked="checked" @change="onChange">Toggle me</el-check-tag>
|
||||
<el-check-tag disabled>Disabled</el-check-tag>
|
||||
</div>
|
||||
<div class="flex gap-2 mt-4">
|
||||
<el-check-tag :checked="checked1" type="primary" @change="onChange1">
|
||||
Tag 1
|
||||
</el-check-tag>
|
||||
<el-check-tag :checked="checked2" type="success" @change="onChange2">
|
||||
Tag 2
|
||||
</el-check-tag>
|
||||
<el-check-tag :checked="checked3" type="info" @change="onChange3">
|
||||
Tag 3
|
||||
</el-check-tag>
|
||||
<el-check-tag :checked="checked4" type="warning" @change="onChange4">
|
||||
Tag 4
|
||||
</el-check-tag>
|
||||
<el-check-tag :checked="checked5" type="danger" @change="onChange5">
|
||||
Tag 5
|
||||
</el-check-tag>
|
||||
<el-check-tag
|
||||
:checked="checked6"
|
||||
disabled
|
||||
type="success"
|
||||
@change="onChange6"
|
||||
>
|
||||
Tag 6
|
||||
</el-check-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const checked = ref(false)
|
||||
const checked1 = ref(true)
|
||||
const checked2 = ref(true)
|
||||
const checked3 = ref(true)
|
||||
const checked4 = ref(true)
|
||||
const checked5 = ref(true)
|
||||
const checked6 = ref(true)
|
||||
|
||||
const onChange = (status: boolean) => {
|
||||
checked.value = status
|
||||
}
|
||||
|
||||
const onChange1 = (status: boolean) => {
|
||||
checked1.value = status
|
||||
}
|
||||
|
||||
const onChange2 = (status: boolean) => {
|
||||
checked2.value = status
|
||||
}
|
||||
|
||||
const onChange3 = (status: boolean) => {
|
||||
checked3.value = status
|
||||
}
|
||||
|
||||
const onChange4 = (status: boolean) => {
|
||||
checked4.value = status
|
||||
}
|
||||
|
||||
const onChange5 = (status: boolean) => {
|
||||
checked5.value = status
|
||||
}
|
||||
|
||||
const onChange6 = (status: boolean) => {
|
||||
checked6.value = status
|
||||
}
|
||||
</script>
|
||||
@@ -1,10 +1,10 @@
|
||||
<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 { ElMessage } from 'element-plus';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getApiTypeOptions, getModelLibraryList, getModelTypeOptions, getProviderList } from '@/api/model';
|
||||
import type { ModelApiTypeOption, ModelLibraryDto, ModelTypeOption } from '@/api/model/types';
|
||||
import { Search, CopyDocument, HomeFilled } from '@element-plus/icons-vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -16,32 +16,28 @@ const pageSize = ref(10);
|
||||
|
||||
// 筛选条件
|
||||
const searchKey = ref('');
|
||||
const selectedProvider = ref<string>('');
|
||||
const selectedModelType = ref<number | undefined>(undefined);
|
||||
const selectedApiType = ref<number | undefined>(undefined);
|
||||
const selectedProviders = ref<string[]>([]);
|
||||
const selectedModelTypes = ref<number[]>([]);
|
||||
const selectedApiTypes = ref<number[]>([]);
|
||||
const isPremiumOnly = ref(false);
|
||||
|
||||
// 供应商列表
|
||||
const providerList = ref<string[]>(['全部供应商']);
|
||||
const providerList = ref<string[]>([]);
|
||||
|
||||
// 模型类型选项
|
||||
const modelTypeOptions = ref<ModelTypeOption[]>([
|
||||
{ label: '全部类型', value: undefined as any },
|
||||
]);
|
||||
const modelTypeOptions = ref<ModelTypeOption[]>([]);
|
||||
|
||||
// API类型选项
|
||||
const apiTypeOptions = ref<ModelApiTypeOption[]>([
|
||||
{ label: '全部API类型', value: undefined as any },
|
||||
]);
|
||||
const apiTypeOptions = ref<ModelApiTypeOption[]>([]);
|
||||
|
||||
async function fetchModelList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
searchKey: searchKey.value || undefined,
|
||||
providerName: selectedProvider.value && selectedProvider.value !== '全部供应商' ? selectedProvider.value : undefined,
|
||||
modelType: selectedModelType.value,
|
||||
modelApiType: selectedApiType.value,
|
||||
providerNames: selectedProviders.value.length > 0 ? selectedProviders.value : undefined,
|
||||
modelTypes: selectedModelTypes.value.length > 0 ? selectedModelTypes.value : undefined,
|
||||
modelApiTypes: selectedApiTypes.value.length > 0 ? selectedApiTypes.value : undefined,
|
||||
isPremiumOnly: isPremiumOnly.value || undefined,
|
||||
skipCount: currentPage.value,
|
||||
maxResultCount: pageSize.value,
|
||||
@@ -66,7 +62,7 @@ async function fetchModelList() {
|
||||
async function fetchProviderList() {
|
||||
try {
|
||||
const response = await getProviderList();
|
||||
providerList.value = ['全部供应商', ...response.data.sort()];
|
||||
providerList.value = response.data.sort();
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取供应商列表失败:', error);
|
||||
@@ -77,10 +73,7 @@ async function fetchProviderList() {
|
||||
async function fetchModelTypeOptions() {
|
||||
try {
|
||||
const response = await getModelTypeOptions();
|
||||
modelTypeOptions.value = [
|
||||
{ label: '全部类型', value: undefined as any },
|
||||
...response.data,
|
||||
];
|
||||
modelTypeOptions.value = response.data;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取模型类型选项失败:', error);
|
||||
@@ -91,10 +84,7 @@ async function fetchModelTypeOptions() {
|
||||
async function fetchApiTypeOptions() {
|
||||
try {
|
||||
const response = await getApiTypeOptions();
|
||||
apiTypeOptions.value = [
|
||||
{ label: '全部API类型', value: undefined as any },
|
||||
...response.data,
|
||||
];
|
||||
apiTypeOptions.value = response.data;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取API类型选项失败:', error);
|
||||
@@ -109,15 +99,72 @@ function copyModelId(modelId: string) {
|
||||
|
||||
function resetFilters() {
|
||||
searchKey.value = '';
|
||||
selectedProvider.value = '';
|
||||
selectedModelType.value = undefined;
|
||||
selectedApiType.value = undefined;
|
||||
selectedProviders.value = [];
|
||||
selectedModelTypes.value = [];
|
||||
selectedApiTypes.value = [];
|
||||
isPremiumOnly.value = false;
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
function selectProvider(provider: string) {
|
||||
selectedProvider.value = provider === selectedProvider.value ? '' : provider;
|
||||
function toggleProvider(provider: string | null) {
|
||||
if (provider === null) {
|
||||
// 点击"全部",清空所有选择
|
||||
selectedProviders.value = [];
|
||||
}
|
||||
else {
|
||||
const index = selectedProviders.value.indexOf(provider);
|
||||
if (index > -1) {
|
||||
selectedProviders.value.splice(index, 1);
|
||||
}
|
||||
else {
|
||||
selectedProviders.value.push(provider);
|
||||
}
|
||||
}
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
function toggleModelType(type: number | null) {
|
||||
if (type === null) {
|
||||
// 点击"全部",清空所有选择
|
||||
selectedModelTypes.value = [];
|
||||
}
|
||||
else {
|
||||
const index = selectedModelTypes.value.indexOf(type);
|
||||
if (index > -1) {
|
||||
selectedModelTypes.value.splice(index, 1);
|
||||
}
|
||||
else {
|
||||
selectedModelTypes.value.push(type);
|
||||
}
|
||||
}
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
function toggleApiType(type: number | null) {
|
||||
if (type === null) {
|
||||
// 点击"全部",清空所有选择
|
||||
selectedApiTypes.value = [];
|
||||
}
|
||||
else {
|
||||
const index = selectedApiTypes.value.indexOf(type);
|
||||
if (index > -1) {
|
||||
selectedApiTypes.value.splice(index, 1);
|
||||
}
|
||||
else {
|
||||
selectedApiTypes.value.push(type);
|
||||
}
|
||||
}
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
function togglePremiumOnly(isPremium: boolean | null) {
|
||||
if (isPremium === null) {
|
||||
// 点击"全部",设置为false
|
||||
isPremiumOnly.value = false;
|
||||
}
|
||||
else {
|
||||
isPremiumOnly.value = isPremium;
|
||||
}
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
@@ -149,7 +196,7 @@ watch(searchKey, () => {
|
||||
}, 500);
|
||||
});
|
||||
|
||||
watch([selectedProvider, selectedModelType, selectedApiType, isPremiumOnly], () => {
|
||||
watch([selectedProviders, selectedModelTypes, selectedApiTypes, isPremiumOnly], () => {
|
||||
currentPage.value = 1;
|
||||
fetchModelList();
|
||||
}, { deep: true });
|
||||
@@ -169,42 +216,42 @@ onMounted(() => {
|
||||
<div class="banner-content">
|
||||
<div class="banner-header">
|
||||
<div class="banner-left">
|
||||
<h1 class="banner-title">AI模型广场</h1>
|
||||
<p class="banner-subtitle">
|
||||
探索并接入全球顶尖AI模型,覆盖文本生成、图像理解、代码辅助等多个领域
|
||||
</p>
|
||||
</div>
|
||||
<div class="banner-right">
|
||||
<div class="banner-text-section">
|
||||
<h1 class="banner-title">AI模型广场</h1>
|
||||
<p class="banner-subtitle">
|
||||
探索并接入全球顶尖AI模型,覆盖文本生成、图像理解、代码辅助等多个领域
|
||||
</p>
|
||||
</div>
|
||||
<!-- 统计信息卡片 -->
|
||||
<div class="stats-cards">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<el-icon><i-ep-box /></el-icon>
|
||||
<el-icon><Box /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ totalCount }}</div>
|
||||
<div class="stat-label">模型</div>
|
||||
<div class="stat-label">可用模型</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<el-icon><i-ep-office-building /></el-icon>
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ providerList.length - 1 }}</div>
|
||||
<div class="stat-label">供应商</div>
|
||||
<div class="stat-label">支持供应商</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
:icon="HomeFilled"
|
||||
class="home-btn"
|
||||
round
|
||||
@click="goToHome"
|
||||
>
|
||||
返回首页
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button
|
||||
:icon="HomeFilled"
|
||||
class="home-btn"
|
||||
round
|
||||
@click="goToHome"
|
||||
>
|
||||
返回首页
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,85 +288,99 @@ onMounted(() => {
|
||||
|
||||
<!-- 供应商 -->
|
||||
<div class="filter-group">
|
||||
<h4 class="filter-group-title">供应商</h4>
|
||||
<div class="filter-options">
|
||||
<div
|
||||
v-for="provider in providerList.filter(p => p !== '全部供应商')"
|
||||
:key="provider"
|
||||
class="filter-option"
|
||||
:class="{ active: selectedProvider === provider }"
|
||||
@click="selectedProvider = selectedProvider === provider ? '' : provider; currentPage = 1"
|
||||
<h4 class="filter-group-title">
|
||||
供应商
|
||||
</h4>
|
||||
<div class="filter-tags">
|
||||
<el-check-tag
|
||||
:checked="selectedProviders.length === 0"
|
||||
class="filter-tag"
|
||||
@change="toggleProvider(null)"
|
||||
>
|
||||
<span class="option-label">{{ provider }}</span>
|
||||
<span v-if="selectedProvider === provider" class="option-check">
|
||||
<el-icon><i-ep-check /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
全部供应商
|
||||
</el-check-tag>
|
||||
<el-check-tag
|
||||
v-for="provider in providerList"
|
||||
:key="provider"
|
||||
:checked="selectedProviders.includes(provider)"
|
||||
class="filter-tag"
|
||||
@change="toggleProvider(provider)"
|
||||
>
|
||||
{{ provider }}
|
||||
</el-check-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型类型 -->
|
||||
<div class="filter-group">
|
||||
<h4 class="filter-group-title">模型类型</h4>
|
||||
<div class="filter-options">
|
||||
<div
|
||||
v-for="option in modelTypeOptions.filter(o => o.value !== undefined)"
|
||||
:key="option.value"
|
||||
class="filter-option"
|
||||
:class="{ active: selectedModelType === option.value }"
|
||||
@click="selectedModelType = option.value"
|
||||
<h4 class="filter-group-title">
|
||||
模型类型
|
||||
</h4>
|
||||
<div class="filter-tags">
|
||||
<el-check-tag
|
||||
:checked="selectedModelTypes.length === 0"
|
||||
class="filter-tag"
|
||||
@change="toggleModelType(null)"
|
||||
>
|
||||
<span class="option-label">{{ option.label }}</span>
|
||||
<span v-if="selectedModelType === option.value" class="option-check">
|
||||
<el-icon><i-ep-check /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
全部类型
|
||||
</el-check-tag>
|
||||
<el-check-tag
|
||||
v-for="option in modelTypeOptions"
|
||||
:key="option.value"
|
||||
:checked="selectedModelTypes.includes(option.value)"
|
||||
class="filter-tag"
|
||||
@change="toggleModelType(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</el-check-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API类型 -->
|
||||
<div class="filter-group">
|
||||
<h4 class="filter-group-title">API类型</h4>
|
||||
<div class="filter-options">
|
||||
<div
|
||||
v-for="option in apiTypeOptions.filter(o => o.value !== undefined)"
|
||||
:key="option.value"
|
||||
class="filter-option"
|
||||
:class="{ active: selectedApiType === option.value }"
|
||||
@click="selectedApiType = option.value"
|
||||
<h4 class="filter-group-title">
|
||||
API类型
|
||||
</h4>
|
||||
<div class="filter-tags">
|
||||
<el-check-tag
|
||||
:checked="selectedApiTypes.length === 0"
|
||||
class="filter-tag"
|
||||
@change="toggleApiType(null)"
|
||||
>
|
||||
<span class="option-label">{{ option.label }}</span>
|
||||
<span v-if="selectedApiType === option.value" class="option-check">
|
||||
<el-icon><i-ep-check /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
全部API类型
|
||||
</el-check-tag>
|
||||
<el-check-tag
|
||||
v-for="option in apiTypeOptions"
|
||||
:key="option.value"
|
||||
:checked="selectedApiTypes.includes(option.value)"
|
||||
class="filter-tag"
|
||||
@change="toggleApiType(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</el-check-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 计费类型 -->
|
||||
<div class="filter-group">
|
||||
<h4 class="filter-group-title">计费类型</h4>
|
||||
<div class="filter-options">
|
||||
<div
|
||||
class="filter-option"
|
||||
:class="{ active: !isPremiumOnly }"
|
||||
@click="isPremiumOnly = false"
|
||||
<h4 class="filter-group-title">
|
||||
计费类型
|
||||
</h4>
|
||||
<div class="filter-tags">
|
||||
<el-check-tag
|
||||
:checked="!isPremiumOnly"
|
||||
class="filter-tag"
|
||||
@change="togglePremiumOnly(null)"
|
||||
>
|
||||
<span class="option-label">全部模型</span>
|
||||
<span v-if="!isPremiumOnly" class="option-check">
|
||||
<el-icon><i-ep-check /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="filter-option"
|
||||
:class="{ active: isPremiumOnly }"
|
||||
@click="isPremiumOnly = true"
|
||||
全部模型
|
||||
</el-check-tag>
|
||||
<el-check-tag
|
||||
:checked="isPremiumOnly"
|
||||
class="filter-tag"
|
||||
@change="togglePremiumOnly(true)"
|
||||
>
|
||||
<span class="option-label">仅尊享模型</span>
|
||||
<span v-if="isPremiumOnly" class="option-check">
|
||||
<el-icon><i-ep-check /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
仅尊享模型
|
||||
</el-check-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -347,6 +408,7 @@ onMounted(() => {
|
||||
v-for="model in modelList"
|
||||
:key="model.modelId"
|
||||
class="model-card"
|
||||
:class="{ 'premium-card': model.isPremium }"
|
||||
>
|
||||
<div class="model-card-header">
|
||||
<div class="model-icon">
|
||||
@@ -386,26 +448,16 @@ onMounted(() => {
|
||||
|
||||
<div class="model-footer">
|
||||
<div class="model-tags">
|
||||
<el-tag v-if="model.isPremium" size="small" type="warning" effect="dark">
|
||||
尊享
|
||||
</el-tag>
|
||||
<el-tag size="small" effect="plain">
|
||||
<el-tag size="small">
|
||||
{{ model.modelTypeName }}
|
||||
</el-tag>
|
||||
<el-tag size="small" effect="plain">
|
||||
<el-tag size="small">
|
||||
{{ model.modelApiTypeName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="model-pricing">
|
||||
<div class="price-item">
|
||||
<span class="price-label">输入</span>
|
||||
<span class="price-value">¥{{ model.multiplierShow }}</span>
|
||||
</div>
|
||||
<div class="price-divider">|</div>
|
||||
<div class="price-item">
|
||||
<span class="price-label">输出</span>
|
||||
<span class="price-value">¥{{ (model.multiplierShow * 2).toFixed(4) }}</span>
|
||||
</div>
|
||||
<span class="pricing-label">计费倍率</span>
|
||||
<span class="pricing-value">{{ model.multiplierShow }}×</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -476,49 +528,52 @@ onMounted(() => {
|
||||
.banner-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 32px;
|
||||
|
||||
.banner-left {
|
||||
flex: 1;
|
||||
|
||||
.banner-title {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: white;
|
||||
margin: 0 0 12px 0;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.banner-subtitle {
|
||||
font-size: 15px;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
.banner-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
gap: 32px;
|
||||
|
||||
.banner-text-section {
|
||||
flex-shrink: 0;
|
||||
|
||||
.banner-title {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: white;
|
||||
margin: 0 0 12px 0;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.banner-subtitle {
|
||||
font-size: 15px;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
// 统计卡片
|
||||
.stats-cards {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.stat-card {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 12px;
|
||||
padding: 12px 16px;
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
transition: all 0.3s;
|
||||
min-width: 160px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
@@ -526,48 +581,50 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-btn {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
color: white;
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 10px 24px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
.home-btn {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
color: white;
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 10px 24px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -576,11 +633,12 @@ onMounted(() => {
|
||||
|
||||
// 主内容区
|
||||
.main-content {
|
||||
padding: 32px 24px;
|
||||
padding: 32px 16px;
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 1400px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
|
||||
@@ -632,6 +690,23 @@ onMounted(() => {
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.filter-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.filter-tag {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -724,6 +799,30 @@ onMounted(() => {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
// 尊享模型流光溢彩效果
|
||||
&.premium-card {
|
||||
border: 2px solid transparent;
|
||||
background-image:
|
||||
linear-gradient(white, white),
|
||||
linear-gradient(45deg, #ff0000, #ff8000, #ffff00, #00ff00, #00ffff, #0000ff, #8000ff, #ff0080);
|
||||
background-origin: border-box;
|
||||
background-clip: padding-box, border-box;
|
||||
animation: gradientFlow 3s ease infinite;
|
||||
background-size: 400% 400%;
|
||||
|
||||
&::before {
|
||||
background: linear-gradient(90deg, #ff0000, #ff8000, #ffff00, #00ff00, #00ffff, #0000ff, #8000ff, #ff0080);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientFlow 3s ease infinite;
|
||||
opacity: 1;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 12px 32px rgba(255, 0, 128, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 32px rgba(102, 126, 234, 0.15);
|
||||
@@ -779,7 +878,7 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 6px 0;
|
||||
overflow: hidden;
|
||||
//overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -806,7 +905,7 @@ onMounted(() => {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
//overflow: hidden;
|
||||
min-height: 48px;
|
||||
|
||||
&.placeholder {
|
||||
@@ -828,35 +927,27 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.model-pricing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
padding: 6px 12px;
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%);
|
||||
border-radius: 8px;
|
||||
white-space: nowrap;
|
||||
|
||||
.price-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
.price-label {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.pricing-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.price-divider {
|
||||
color: #dcdfe6;
|
||||
.pricing-value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -925,33 +1016,56 @@ onMounted(() => {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.banner-left .banner-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.banner-left .banner-subtitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.banner-right {
|
||||
.banner-left {
|
||||
width: 100%;
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
|
||||
.stats-cards {
|
||||
.banner-text-section {
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
.banner-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.banner-subtitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.home-btn {
|
||||
.stats-cards {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 12px 16px;
|
||||
|
||||
.stat-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -983,10 +1097,7 @@ onMounted(() => {
|
||||
|
||||
.model-pricing {
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
padding: 12px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1022,4 +1133,17 @@ onMounted(() => {
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
// 流光溢彩动画
|
||||
@keyframes gradientFlow {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
1
Yi.Ai.Vue3/types/components.d.ts
vendored
1
Yi.Ai.Vue3/types/components.d.ts
vendored
@@ -19,6 +19,7 @@ declare module 'vue' {
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckTag: typeof import('element-plus/es')['ElCheckTag']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
|
||||
Reference in New Issue
Block a user