fix: 模型库优化
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(npx vue-tsc --noEmit)"
|
"Bash(npx vue-tsc --noEmit)",
|
||||||
|
"Bash(timeout 60 npx vue-tsc:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ export function getModelLibraryList(params?: ModelLibraryGetListInput) {
|
|||||||
if (params?.searchKey) {
|
if (params?.searchKey) {
|
||||||
queryParams.append('SearchKey', params.searchKey);
|
queryParams.append('SearchKey', params.searchKey);
|
||||||
}
|
}
|
||||||
if (params?.providerName) {
|
if (params?.providerNames && params.providerNames.length > 0) {
|
||||||
queryParams.append('ProviderName', params.providerName);
|
params.providerNames.forEach(name => queryParams.append('ProviderNames', name));
|
||||||
}
|
}
|
||||||
if (params?.modelType !== undefined) {
|
if (params?.modelTypes && params.modelTypes.length > 0) {
|
||||||
queryParams.append('ModelType', params.modelType.toString());
|
params.modelTypes.forEach(type => queryParams.append('ModelTypes', type.toString()));
|
||||||
}
|
}
|
||||||
if (params?.modelApiType !== undefined) {
|
if (params?.modelApiTypes && params.modelApiTypes.length > 0) {
|
||||||
queryParams.append('ModelApiType', params.modelApiType.toString());
|
params.modelApiTypes.forEach(type => queryParams.append('ModelApiTypes', type.toString()));
|
||||||
}
|
}
|
||||||
if (params?.isPremiumOnly !== undefined) {
|
if (params?.isPremiumOnly !== undefined) {
|
||||||
queryParams.append('IsPremiumOnly', params.isPremiumOnly.toString());
|
queryParams.append('IsPremiumOnly', params.isPremiumOnly.toString());
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ export interface ModelLibraryDto {
|
|||||||
// 获取模型库列表查询参数
|
// 获取模型库列表查询参数
|
||||||
export interface ModelLibraryGetListInput {
|
export interface ModelLibraryGetListInput {
|
||||||
searchKey?: string;
|
searchKey?: string;
|
||||||
providerName?: string;
|
providerNames?: string[];
|
||||||
modelType?: ModelTypeEnum;
|
modelTypes?: ModelTypeEnum[];
|
||||||
modelApiType?: ModelApiTypeEnum;
|
modelApiTypes?: ModelApiTypeEnum[];
|
||||||
isPremiumOnly?: boolean;
|
isPremiumOnly?: boolean;
|
||||||
skipCount?: number;
|
skipCount?: number;
|
||||||
maxResultCount?: 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">
|
<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 { onMounted, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
import { getApiTypeOptions, getModelLibraryList, getModelTypeOptions, getProviderList } from '@/api/model';
|
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();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -16,32 +16,28 @@ const pageSize = ref(10);
|
|||||||
|
|
||||||
// 筛选条件
|
// 筛选条件
|
||||||
const searchKey = ref('');
|
const searchKey = ref('');
|
||||||
const selectedProvider = ref<string>('');
|
const selectedProviders = ref<string[]>([]);
|
||||||
const selectedModelType = ref<number | undefined>(undefined);
|
const selectedModelTypes = ref<number[]>([]);
|
||||||
const selectedApiType = ref<number | undefined>(undefined);
|
const selectedApiTypes = ref<number[]>([]);
|
||||||
const isPremiumOnly = ref(false);
|
const isPremiumOnly = ref(false);
|
||||||
|
|
||||||
// 供应商列表
|
// 供应商列表
|
||||||
const providerList = ref<string[]>(['全部供应商']);
|
const providerList = ref<string[]>([]);
|
||||||
|
|
||||||
// 模型类型选项
|
// 模型类型选项
|
||||||
const modelTypeOptions = ref<ModelTypeOption[]>([
|
const modelTypeOptions = ref<ModelTypeOption[]>([]);
|
||||||
{ label: '全部类型', value: undefined as any },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// API类型选项
|
// API类型选项
|
||||||
const apiTypeOptions = ref<ModelApiTypeOption[]>([
|
const apiTypeOptions = ref<ModelApiTypeOption[]>([]);
|
||||||
{ label: '全部API类型', value: undefined as any },
|
|
||||||
]);
|
|
||||||
|
|
||||||
async function fetchModelList() {
|
async function fetchModelList() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
searchKey: searchKey.value || undefined,
|
searchKey: searchKey.value || undefined,
|
||||||
providerName: selectedProvider.value && selectedProvider.value !== '全部供应商' ? selectedProvider.value : undefined,
|
providerNames: selectedProviders.value.length > 0 ? selectedProviders.value : undefined,
|
||||||
modelType: selectedModelType.value,
|
modelTypes: selectedModelTypes.value.length > 0 ? selectedModelTypes.value : undefined,
|
||||||
modelApiType: selectedApiType.value,
|
modelApiTypes: selectedApiTypes.value.length > 0 ? selectedApiTypes.value : undefined,
|
||||||
isPremiumOnly: isPremiumOnly.value || undefined,
|
isPremiumOnly: isPremiumOnly.value || undefined,
|
||||||
skipCount: currentPage.value,
|
skipCount: currentPage.value,
|
||||||
maxResultCount: pageSize.value,
|
maxResultCount: pageSize.value,
|
||||||
@@ -66,7 +62,7 @@ async function fetchModelList() {
|
|||||||
async function fetchProviderList() {
|
async function fetchProviderList() {
|
||||||
try {
|
try {
|
||||||
const response = await getProviderList();
|
const response = await getProviderList();
|
||||||
providerList.value = ['全部供应商', ...response.data.sort()];
|
providerList.value = response.data.sort();
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('获取供应商列表失败:', error);
|
console.error('获取供应商列表失败:', error);
|
||||||
@@ -77,10 +73,7 @@ async function fetchProviderList() {
|
|||||||
async function fetchModelTypeOptions() {
|
async function fetchModelTypeOptions() {
|
||||||
try {
|
try {
|
||||||
const response = await getModelTypeOptions();
|
const response = await getModelTypeOptions();
|
||||||
modelTypeOptions.value = [
|
modelTypeOptions.value = response.data;
|
||||||
{ label: '全部类型', value: undefined as any },
|
|
||||||
...response.data,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('获取模型类型选项失败:', error);
|
console.error('获取模型类型选项失败:', error);
|
||||||
@@ -91,10 +84,7 @@ async function fetchModelTypeOptions() {
|
|||||||
async function fetchApiTypeOptions() {
|
async function fetchApiTypeOptions() {
|
||||||
try {
|
try {
|
||||||
const response = await getApiTypeOptions();
|
const response = await getApiTypeOptions();
|
||||||
apiTypeOptions.value = [
|
apiTypeOptions.value = response.data;
|
||||||
{ label: '全部API类型', value: undefined as any },
|
|
||||||
...response.data,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('获取API类型选项失败:', error);
|
console.error('获取API类型选项失败:', error);
|
||||||
@@ -109,15 +99,72 @@ function copyModelId(modelId: string) {
|
|||||||
|
|
||||||
function resetFilters() {
|
function resetFilters() {
|
||||||
searchKey.value = '';
|
searchKey.value = '';
|
||||||
selectedProvider.value = '';
|
selectedProviders.value = [];
|
||||||
selectedModelType.value = undefined;
|
selectedModelTypes.value = [];
|
||||||
selectedApiType.value = undefined;
|
selectedApiTypes.value = [];
|
||||||
isPremiumOnly.value = false;
|
isPremiumOnly.value = false;
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectProvider(provider: string) {
|
function toggleProvider(provider: string | null) {
|
||||||
selectedProvider.value = provider === selectedProvider.value ? '' : provider;
|
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;
|
currentPage.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +196,7 @@ watch(searchKey, () => {
|
|||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch([selectedProvider, selectedModelType, selectedApiType, isPremiumOnly], () => {
|
watch([selectedProviders, selectedModelTypes, selectedApiTypes, isPremiumOnly], () => {
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
fetchModelList();
|
fetchModelList();
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
@@ -169,42 +216,42 @@ onMounted(() => {
|
|||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-header">
|
<div class="banner-header">
|
||||||
<div class="banner-left">
|
<div class="banner-left">
|
||||||
<h1 class="banner-title">AI模型广场</h1>
|
<div class="banner-text-section">
|
||||||
<p class="banner-subtitle">
|
<h1 class="banner-title">AI模型广场</h1>
|
||||||
探索并接入全球顶尖AI模型,覆盖文本生成、图像理解、代码辅助等多个领域
|
<p class="banner-subtitle">
|
||||||
</p>
|
探索并接入全球顶尖AI模型,覆盖文本生成、图像理解、代码辅助等多个领域
|
||||||
</div>
|
</p>
|
||||||
<div class="banner-right">
|
</div>
|
||||||
<!-- 统计信息卡片 -->
|
<!-- 统计信息卡片 -->
|
||||||
<div class="stats-cards">
|
<div class="stats-cards">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<el-icon><i-ep-box /></el-icon>
|
<el-icon><Box /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">{{ totalCount }}</div>
|
<div class="stat-value">{{ totalCount }}</div>
|
||||||
<div class="stat-label">模型</div>
|
<div class="stat-label">可用模型</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<el-icon><i-ep-office-building /></el-icon>
|
<el-icon><OfficeBuilding /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<div class="stat-value">{{ providerList.length - 1 }}</div>
|
<div class="stat-value">{{ providerList.length - 1 }}</div>
|
||||||
<div class="stat-label">供应商</div>
|
<div class="stat-label">支持供应商</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-button
|
|
||||||
:icon="HomeFilled"
|
|
||||||
class="home-btn"
|
|
||||||
round
|
|
||||||
@click="goToHome"
|
|
||||||
>
|
|
||||||
返回首页
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<el-button
|
||||||
|
:icon="HomeFilled"
|
||||||
|
class="home-btn"
|
||||||
|
round
|
||||||
|
@click="goToHome"
|
||||||
|
>
|
||||||
|
返回首页
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -241,85 +288,99 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 供应商 -->
|
<!-- 供应商 -->
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<h4 class="filter-group-title">供应商</h4>
|
<h4 class="filter-group-title">
|
||||||
<div class="filter-options">
|
供应商
|
||||||
<div
|
</h4>
|
||||||
v-for="provider in providerList.filter(p => p !== '全部供应商')"
|
<div class="filter-tags">
|
||||||
:key="provider"
|
<el-check-tag
|
||||||
class="filter-option"
|
:checked="selectedProviders.length === 0"
|
||||||
:class="{ active: selectedProvider === provider }"
|
class="filter-tag"
|
||||||
@click="selectedProvider = selectedProvider === provider ? '' : provider; currentPage = 1"
|
@change="toggleProvider(null)"
|
||||||
>
|
>
|
||||||
<span class="option-label">{{ provider }}</span>
|
全部供应商
|
||||||
<span v-if="selectedProvider === provider" class="option-check">
|
</el-check-tag>
|
||||||
<el-icon><i-ep-check /></el-icon>
|
<el-check-tag
|
||||||
</span>
|
v-for="provider in providerList"
|
||||||
</div>
|
:key="provider"
|
||||||
|
:checked="selectedProviders.includes(provider)"
|
||||||
|
class="filter-tag"
|
||||||
|
@change="toggleProvider(provider)"
|
||||||
|
>
|
||||||
|
{{ provider }}
|
||||||
|
</el-check-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 模型类型 -->
|
<!-- 模型类型 -->
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<h4 class="filter-group-title">模型类型</h4>
|
<h4 class="filter-group-title">
|
||||||
<div class="filter-options">
|
模型类型
|
||||||
<div
|
</h4>
|
||||||
v-for="option in modelTypeOptions.filter(o => o.value !== undefined)"
|
<div class="filter-tags">
|
||||||
:key="option.value"
|
<el-check-tag
|
||||||
class="filter-option"
|
:checked="selectedModelTypes.length === 0"
|
||||||
:class="{ active: selectedModelType === option.value }"
|
class="filter-tag"
|
||||||
@click="selectedModelType = option.value"
|
@change="toggleModelType(null)"
|
||||||
>
|
>
|
||||||
<span class="option-label">{{ option.label }}</span>
|
全部类型
|
||||||
<span v-if="selectedModelType === option.value" class="option-check">
|
</el-check-tag>
|
||||||
<el-icon><i-ep-check /></el-icon>
|
<el-check-tag
|
||||||
</span>
|
v-for="option in modelTypeOptions"
|
||||||
</div>
|
:key="option.value"
|
||||||
|
:checked="selectedModelTypes.includes(option.value)"
|
||||||
|
class="filter-tag"
|
||||||
|
@change="toggleModelType(option.value)"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</el-check-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- API类型 -->
|
<!-- API类型 -->
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<h4 class="filter-group-title">API类型</h4>
|
<h4 class="filter-group-title">
|
||||||
<div class="filter-options">
|
API类型
|
||||||
<div
|
</h4>
|
||||||
v-for="option in apiTypeOptions.filter(o => o.value !== undefined)"
|
<div class="filter-tags">
|
||||||
:key="option.value"
|
<el-check-tag
|
||||||
class="filter-option"
|
:checked="selectedApiTypes.length === 0"
|
||||||
:class="{ active: selectedApiType === option.value }"
|
class="filter-tag"
|
||||||
@click="selectedApiType = option.value"
|
@change="toggleApiType(null)"
|
||||||
>
|
>
|
||||||
<span class="option-label">{{ option.label }}</span>
|
全部API类型
|
||||||
<span v-if="selectedApiType === option.value" class="option-check">
|
</el-check-tag>
|
||||||
<el-icon><i-ep-check /></el-icon>
|
<el-check-tag
|
||||||
</span>
|
v-for="option in apiTypeOptions"
|
||||||
</div>
|
:key="option.value"
|
||||||
|
:checked="selectedApiTypes.includes(option.value)"
|
||||||
|
class="filter-tag"
|
||||||
|
@change="toggleApiType(option.value)"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</el-check-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 计费类型 -->
|
<!-- 计费类型 -->
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<h4 class="filter-group-title">计费类型</h4>
|
<h4 class="filter-group-title">
|
||||||
<div class="filter-options">
|
计费类型
|
||||||
<div
|
</h4>
|
||||||
class="filter-option"
|
<div class="filter-tags">
|
||||||
:class="{ active: !isPremiumOnly }"
|
<el-check-tag
|
||||||
@click="isPremiumOnly = false"
|
:checked="!isPremiumOnly"
|
||||||
|
class="filter-tag"
|
||||||
|
@change="togglePremiumOnly(null)"
|
||||||
>
|
>
|
||||||
<span class="option-label">全部模型</span>
|
全部模型
|
||||||
<span v-if="!isPremiumOnly" class="option-check">
|
</el-check-tag>
|
||||||
<el-icon><i-ep-check /></el-icon>
|
<el-check-tag
|
||||||
</span>
|
:checked="isPremiumOnly"
|
||||||
</div>
|
class="filter-tag"
|
||||||
<div
|
@change="togglePremiumOnly(true)"
|
||||||
class="filter-option"
|
|
||||||
:class="{ active: isPremiumOnly }"
|
|
||||||
@click="isPremiumOnly = true"
|
|
||||||
>
|
>
|
||||||
<span class="option-label">仅尊享模型</span>
|
仅尊享模型
|
||||||
<span v-if="isPremiumOnly" class="option-check">
|
</el-check-tag>
|
||||||
<el-icon><i-ep-check /></el-icon>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -347,6 +408,7 @@ onMounted(() => {
|
|||||||
v-for="model in modelList"
|
v-for="model in modelList"
|
||||||
:key="model.modelId"
|
:key="model.modelId"
|
||||||
class="model-card"
|
class="model-card"
|
||||||
|
:class="{ 'premium-card': model.isPremium }"
|
||||||
>
|
>
|
||||||
<div class="model-card-header">
|
<div class="model-card-header">
|
||||||
<div class="model-icon">
|
<div class="model-icon">
|
||||||
@@ -386,26 +448,16 @@ onMounted(() => {
|
|||||||
|
|
||||||
<div class="model-footer">
|
<div class="model-footer">
|
||||||
<div class="model-tags">
|
<div class="model-tags">
|
||||||
<el-tag v-if="model.isPremium" size="small" type="warning" effect="dark">
|
<el-tag size="small">
|
||||||
尊享
|
|
||||||
</el-tag>
|
|
||||||
<el-tag size="small" effect="plain">
|
|
||||||
{{ model.modelTypeName }}
|
{{ model.modelTypeName }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<el-tag size="small" effect="plain">
|
<el-tag size="small">
|
||||||
{{ model.modelApiTypeName }}
|
{{ model.modelApiTypeName }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-pricing">
|
<div class="model-pricing">
|
||||||
<div class="price-item">
|
<span class="pricing-label">计费倍率</span>
|
||||||
<span class="price-label">输入</span>
|
<span class="pricing-value">{{ model.multiplierShow }}×</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -476,49 +528,52 @@ onMounted(() => {
|
|||||||
.banner-header {
|
.banner-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 32px;
|
gap: 32px;
|
||||||
|
|
||||||
.banner-left {
|
.banner-left {
|
||||||
flex: 1;
|
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;
|
display: flex;
|
||||||
align-items: center;
|
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 {
|
.stats-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 12px 16px;
|
padding: 16px 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
min-width: 160px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
@@ -526,48 +581,50 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stat-icon {
|
.stat-icon {
|
||||||
width: 40px;
|
width: 48px;
|
||||||
height: 40px;
|
height: 48px;
|
||||||
background: rgba(255, 255, 255, 0.25);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 20px;
|
font-size: 24px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-info {
|
.stat-info {
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 24px;
|
font-size: 28px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: white;
|
color: white;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.home-btn {
|
.home-btn {
|
||||||
background: rgba(255, 255, 255, 0.25);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||||
color: white;
|
color: white;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
padding: 10px 24px;
|
padding: 10px 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.35);
|
background: rgba(255, 255, 255, 0.35);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,11 +633,12 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 主内容区
|
// 主内容区
|
||||||
.main-content {
|
.main-content {
|
||||||
padding: 32px 24px;
|
padding: 32px 16px;
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
max-width: 1400px;
|
max-width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 0 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
|
|
||||||
@@ -632,6 +690,23 @@ onMounted(() => {
|
|||||||
letter-spacing: 0.5px;
|
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 {
|
.filter-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -724,6 +799,30 @@ onMounted(() => {
|
|||||||
transition: opacity 0.3s;
|
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 {
|
&:hover {
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
box-shadow: 0 12px 32px rgba(102, 126, 234, 0.15);
|
box-shadow: 0 12px 32px rgba(102, 126, 234, 0.15);
|
||||||
@@ -779,7 +878,7 @@ onMounted(() => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin: 0 0 6px 0;
|
margin: 0 0 6px 0;
|
||||||
overflow: hidden;
|
//overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -806,7 +905,7 @@ onMounted(() => {
|
|||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
//overflow: hidden;
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
|
|
||||||
&.placeholder {
|
&.placeholder {
|
||||||
@@ -828,35 +927,27 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-pricing {
|
.model-pricing {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
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;
|
white-space: nowrap;
|
||||||
|
|
||||||
.price-item {
|
.pricing-label {
|
||||||
display: flex;
|
font-size: 12px;
|
||||||
flex-direction: column;
|
color: #909399;
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
|
|
||||||
.price-label {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price-value {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-divider {
|
.pricing-value {
|
||||||
color: #dcdfe6;
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #667eea;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -925,33 +1016,56 @@ onMounted(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
.banner-left .banner-title {
|
.banner-left {
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-left .banner-subtitle {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-right {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
.stats-cards {
|
.banner-text-section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
.stat-card {
|
.banner-title {
|
||||||
flex: 1;
|
font-size: 28px;
|
||||||
justify-content: center;
|
}
|
||||||
|
|
||||||
|
.banner-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-btn {
|
.stats-cards {
|
||||||
width: 100%;
|
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 {
|
.model-pricing {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-around;
|
justify-content: center;
|
||||||
padding: 12px;
|
|
||||||
background: #f5f7fa;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1022,4 +1133,17 @@ onMounted(() => {
|
|||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
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>
|
</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']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
|
ElCheckTag: typeof import('element-plus/es')['ElCheckTag']
|
||||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
|
|||||||
Reference in New Issue
Block a user