2026-05-23 14:52:06 +08:00

1570 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="product-service-page">
<!-- 产品分类导航 -->
<div class="category-nav">
<div v-for="category in panelData"
:key="category.firTitle"
class="nav-item"
:class="{ active: activeCategory === category.firTitle }"
@click="switchCategory(category)">
<span>{{ category.firTitle }}</span>
</div>
</div>
<!-- 产品内容区域 -->
<div class="product-content">
<!-- 左侧菜单区域只有有二级或三级菜单时才显示 -->
<div v-if="hasMenuSidebar" class="menu-sidebar">
<template v-if="isTokenMarketActive">
<div class="input">
<el-input
v-model="searchKeyword"
class="product-search"
clearable
prefix-icon="el-icon-search"
placeholder="输入关键词 搜索模型"
></el-input>
</div>
<div class="token-filter-section">
<h4>模型筛选</h4>
<div class="token-filter-grid">
<button
v-for="item in tokenModelTypeList"
:key="item"
class="token-filter-item"
:class="{ active: tokenActiveModelType === item }"
@click="toggleTokenModelType(item)"
>
{{ item }}
</button>
</div>
</div>
<div class="token-filter-section">
<h4>提供方</h4>
<div class="token-filter-grid">
<button
v-for="item in tokenProviderList"
:key="item"
class="token-filter-item"
:class="{ active: tokenActiveProvider === item }"
@click="toggleTokenProvider(item)"
>
{{ item }}
</button>
</div>
</div>
</template>
<template v-else>
<div class="input">
<el-input
v-model="searchKeyword"
class="product-search"
clearable
prefix-icon="el-icon-search"
placeholder="搜索产品名称、类型或描述"
></el-input>
</div>
<!-- 二级菜单 -->
<div v-if="hasSecondLevel" class="subcategory-section">
<div class="subcategory-list">
<div v-for="subItem in currentSubcategories"
:key="getSubItemKey(subItem)"
class="subcategory-item"
:class="{ active: activeSubId === getSubItemKey(subItem) }"
@click="switchSubcategory(subItem)">
<span class="subcategory-name">{{ subItem.secTitle }}</span>
<span v-if="getProductCount(subItem)" class="product-count">
{{ getProductCount(subItem) }}
</span>
</div>
</div>
</div>
<!-- 三级菜单如果有且不是算/网分类 -->
<div v-if="hasThirdLevel && !isSpecialCategory" class="third-level-section">
<div class="third-level-list">
<div v-for="thirdItem in thirdLevelData"
:key="getThirdItemKey(thirdItem)"
class="third-level-item"
:class="{ active: activeThirdId === getThirdItemKey(thirdItem) }"
@click="switchThirdLevel(thirdItem)">
{{ thirdItem.thrTitle }}
</div>
</div>
</div>
</template>
</div>
<!-- 主内容区 -->
<div class="main-content" :class="{ 'full-width': !hasMenuSidebar }">
<template v-if="isTokenMarketActive">
<!-- <div class="token-market-toolbar">
<span>排序:</span>
<button class="token-sort-btn active">综合排序</button>
<button class="token-sort-btn">体验</button>
</div> -->
<div class="token-market-grid">
<div
v-for="product in displayedTokenProducts"
:key="product.id"
class="token-market-card"
@click="goModelDetail(product)"
>
<div class="token-card-top">
<h3>{{ product.display_name || product.model_name }}</h3>
<!-- <span v-if="product.sort_order <= 10" class="token-new-badge">NEW</span> -->
<!-- <i class="el-icon-more token-more"></i> -->
</div>
<div class="token-tags">
<span>{{ product.model_type || '-' }}</span>
<span>{{ product.billing_method || '-' }}</span>
<span>{{ product.provider || '-' }}</span>
<span v-if="product.llmid">{{ product.llmid }}</span>
</div>
<div class="token-price-line">
<span>输入 ¥{{ formatTokenPrice(product.input_token_price) }}/千Token</span>
<span>输出 ¥{{ formatTokenPrice(product.output_token_price) }}/千Token</span>
</div>
<div class="token-meta">
<span class="token-provider-avatar">{{ getProviderInitial(product.provider) }}</span>
<span>{{ product.provider || '-' }}</span>
</div>
<div class="token-actions">
<button @click.stop="goModelApiDocument(product)">
<i class="el-icon-document"></i>
API文档
</button>
<button class="experience" @click.stop="goModelExperience(product)">
<i class="el-icon-video-play"></i>
体验
</button>
</div>
</div>
</div>
</template>
<!-- 产品网格 -->
<div v-else class="product-grid">
<div v-for="product in displayedProducts"
:key="product.id"
class="product-card"
@click="handleProductClick(product)">
<div class="product-header">
<div class="product-icon">
<i :class="getProductIcon(product)"></i>
</div>
<div class="product-title">
<h3 class="product-name">{{ product.name }}</h3>
<span class="product-type">{{ product.type }}</span>
</div>
<span v-if="product.discount" class="discount-badge">
{{ product.discount }}
</span>
</div>
<div class="product-desc">
{{ getProductDescription(product) }}
</div>
<div class="product-footer">
<span class="product-status">稳定服务</span>
<button class="detail-btn" @click.stop="handleProductClick(product)">
查看详情
<i class="el-icon-arrow-right"></i>
</button>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="isTokenMarketActive ? !displayedTokenProducts.length : !hasDisplayProducts" class="empty-state">
<div class="empty-icon">
<i class="el-icon-box"></i>
</div>
<p class="empty-text">{{ searchKeyword ? '没有匹配的产品' : '暂无产品数据' }}</p>
<span>可以切换分类或调整搜索关键词后再试</span>
</div>
</div>
</div>
</div>
</template>
<script>
import { reqNavList, reqNewHomeSync, reqNewHomeFestival } from "@/api/newHome";
import { gotoYuanJingAPI } from '@/api/gotoYuanJing'
export default {
name: "ProductServicePage",
data() {
return {
panelData: [],
activeCategory: '',
activeSubId: null,
activeThirdId: null,
searchKeyword: '',
currentProducts: [],
tokenList: [],
tokenModelTypeList: [],
tokenProviderList: [],
tokenActiveModelType: '',
tokenActiveProvider: ''
};
},
computed: {
isTokenMarketActive() {
return this.isTokenMarketCategory(this.activeCategory);
},
currentSubcategories() {
if (!this.activeCategory || !this.panelData.length) return [];
const category = this.panelData.find(item => item.firTitle === this.activeCategory);
return category ? category.secMenu : [];
},
hasSecondLevel() {
return this.currentSubcategories && this.currentSubcategories.length > 0;
},
hasThirdLevel() {
return this.thirdLevelData && this.thirdLevelData.length > 0;
},
hasProducts() {
return this.currentProducts && this.currentProducts.length > 0;
},
displayedProducts() {
const keyword = this.searchKeyword.trim().toLowerCase();
if (!keyword) {
return this.currentProducts;
}
return this.currentProducts.filter(product => {
const productText = [
product.name,
product.type,
this.getProductDescription(product)
].join(' ').toLowerCase();
return productText.includes(keyword);
});
},
hasDisplayProducts() {
return this.displayedProducts && this.displayedProducts.length > 0;
},
totalProductCount() {
return this.panelData.reduce((total, category) => {
return total + this.getAllProductsFromCategory(category).length;
}, 0);
},
activeCategorySummary() {
if (!this.activeCategory) {
return '选择分类后查看对应产品能力';
}
const productCount = this.currentProducts.length;
const subCount = this.currentSubcategories.length;
return `当前分类包含 ${subCount} 个子类,${productCount} 个可选产品`;
},
hasMenuSidebar() {
if (this.isTokenMarketActive) return true;
return this.hasSecondLevel || this.hasThirdLevel;
},
isSpecialCategory() {
return this.activeCategory === '算' || this.activeCategory === '网';
},
thirdLevelData() {
if (!this.activeSubId || !this.currentSubcategories.length) return [];
// 从activeSubId中提取真实的subItem id
const realSubId = this.activeSubId.split('_')[0];
const subItem = this.currentSubcategories.find(item =>
item.id === realSubId || item.id?.toString() === realSubId
);
return subItem && subItem.thrMenu ? subItem.thrMenu : [];
},
// 检查是否登录
loginState() {
const userId = sessionStorage.getItem('userId');
return userId !== null && userId !== 'null' && userId !== '';
},
displayedTokenProducts() {
const keyword = this.searchKeyword.trim().toLowerCase();
return this.tokenList.filter(item => {
const matchKeyword = !keyword || [
item.display_name,
item.model_name,
item.model_type,
item.provider,
item.description
].join(' ').toLowerCase().includes(keyword);
const matchType = !this.tokenActiveModelType || item.model_type === this.tokenActiveModelType;
const matchProvider = !this.tokenActiveProvider || item.provider === this.tokenActiveProvider;
return matchKeyword && matchType && matchProvider;
});
}
},
async mounted() {
await this.loadNavData();
this.initializeDefaultData();
},
methods: {
// 判断当前一级分类是否为 TOKEN 市集,后续用于切换独立渲染逻辑。
isTokenMarketCategory(title) {
return ['TOKEN市集', 'Token市集', 'token市集', 'Token Market'].includes(title);
},
// 从导航接口内嵌的 token_market 中取出模型列表和筛选项。
setTokenMarketData(category) {
const marketData = category && category.token_market && category.token_market.data
? category.token_market.data
: {};
this.tokenList = Array.isArray(marketData.model_list) ? marketData.model_list : [];
this.tokenModelTypeList = Array.isArray(marketData.model_type_list) ? marketData.model_type_list : [];
this.tokenProviderList = Array.isArray(marketData.provider_list) ? marketData.provider_list : [];
},
// 点击同一个模型类型时取消筛选,再点其他类型则切换筛选项。
toggleTokenModelType(type) {
this.tokenActiveModelType = this.tokenActiveModelType === type ? '' : type;
},
// 点击同一个供应商时取消筛选,再点其他供应商则切换筛选项。
toggleTokenProvider(provider) {
this.tokenActiveProvider = this.tokenActiveProvider === provider ? '' : provider;
},
// TOKEN 市集价格展示,去掉无意义的尾部 0。
formatTokenPrice(value) {
if (value === undefined || value === null || value === '') return '-';
const num = Number(value);
if (Number.isNaN(num)) return value;
return num.toFixed(4).replace(/\.?0+$/, '');
},
// 供应商头像取首字,避免没有 logo 时卡片显得空。
getProviderInitial(provider) {
return provider ? provider.slice(0, 1) : 'M';
},
// TOKEN 市集卡片点击:进入模型详情页。
goModelDetail(model) {
this.cacheTokenMarketModel(model);
this.$router.push({
name: 'modelDetail',
query: {
id: model.id,
model_id: model.id,
llmid: model.llmid || model.model_name || model.id,
from: 'tokenMarket',
category: 'TOKEN市集'
}
});
},
// TOKEN 市集 API 文档按钮:进入模型 API 文档页。
goModelApiDocument(model) {
this.cacheTokenMarketModel(model);
this.$router.push({
name: 'modelApiDocument',
query: {
id: model.id,
model_id: model.id,
from: 'tokenMarket',
category: 'TOKEN市集'
}
});
},
// TOKEN 市集体验按钮:进入模型体验页。
goModelExperience(model) {
this.cacheTokenMarketModel(model);
this.$router.push({
name: 'modelExperience',
query: {
id: model.id,
model_id: model.id,
from: 'tokenMarket',
category: 'TOKEN市集'
}
});
},
// 详情页需要完整模型字段,这里点击时缓存当前卡片数据,避免只带 id 过去后页面无法渲染。
cacheTokenMarketModel(model) {
if (!model) return;
sessionStorage.setItem('tokenMarketSelectedModel', JSON.stringify(model));
},
// 生成唯一的二级菜单项key
getSubItemKey(subItem) {
return `${subItem.id}_${this.activeCategory}_${subItem.secTitle}`;
},
// 生成唯一的三级菜单项key
getThirdItemKey(thirdItem) {
return `${thirdItem.id}_${this.activeSubId}_${thirdItem.thrTitle}`;
},
// 加载导航数据
async loadNavData() {
try {
const response = await reqNavList({ url_link: window.location.href });
if (response.status && response.data.product_service) {
this.panelData = this.processNavData(response.data.product_service);
}
} catch (error) {
console.error("加载产品数据失败:", error);
this.panelData = [];
}
},
// 处理导航数据 - 修复数据结构
processNavData(data) {
return data.map((category, categoryIndex) => {
if (this.isTokenMarketCategory(category.firTitle)) {
this.setTokenMarketData(category);
}
// 确保一级分类有唯一标识
if (!category.uniqueId) {
category.uniqueId = `category_${categoryIndex}_${category.firTitle}`;
}
if (category.secMenu) {
category.secMenu.forEach((secItem, secIndex) => {
// 确保二级菜单项有唯一标识
if (!secItem.id) {
secItem.id = `sec_${categoryIndex}_${secIndex}`;
}
if (secItem.thrMenu) {
secItem.thrMenu.forEach((thrItem, thrIndex) => {
// 确保三级菜单项有唯一标识
if (!thrItem.id) {
thrItem.id = `thr_${categoryIndex}_${secIndex}_${thrIndex}`;
}
if (thrItem.value) {
thrItem.value.forEach(product => {
product.type = secItem.secTitle;
// 确保产品有唯一ID
if (!product.id) {
product.id = `product_${categoryIndex}_${secIndex}_${thrIndex}_${Math.random().toString(36).substr(2, 9)}`;
}
});
}
});
}
});
}
return category;
});
},
// 初始化默认数据
initializeDefaultData() {
if (this.panelData.length > 0) {
const defaultCategory = this.getDefaultCategory();
this.activeCategory = defaultCategory.firTitle;
this.setDefaultSubcategory(defaultCategory);
}
},
// 进入 /product 时优先根据路由 query 打开指定分类;没有 query 时默认打开 TOKEN 市集。
getDefaultCategory() {
const queryCategory = this.$route.query.category || this.$route.query.tab;
const categoryFromQuery = queryCategory
? this.panelData.find(item => item.firTitle === queryCategory || this.isTokenMarketCategory(queryCategory) && this.isTokenMarketCategory(item.firTitle))
: null;
const tokenCategory = this.panelData.find(item => this.isTokenMarketCategory(item.firTitle));
return categoryFromQuery || tokenCategory || this.panelData[0];
},
// 根据分类设置默认二级菜单
setDefaultSubcategory(category) {
if (this.isTokenMarketCategory(category.firTitle)) {
this.activeSubId = null;
this.activeThirdId = null;
this.currentProducts = [];
return;
}
if (category.secMenu && category.secMenu.length > 0) {
let defaultSubItem = category.secMenu[0];
if (category.firTitle === '算') {
const zhishuanItem = category.secMenu.find(item =>
item.secTitle && item.secTitle.includes('智算')
);
if (zhishuanItem) defaultSubItem = zhishuanItem;
} else if (category.firTitle === '网') {
const networkItem = category.secMenu.find(item =>
item.secTitle && item.secTitle.includes('算力网络')
);
if (networkItem) defaultSubItem = networkItem;
}
this.activeSubId = this.getSubItemKey(defaultSubItem);
this.updateProductList(defaultSubItem);
} else {
this.activeSubId = null;
this.activeThirdId = null;
this.currentProducts = this.getAllProductsFromCategory(category);
}
},
// 获取分类下的所有产品
getAllProductsFromCategory(category) {
let allProducts = [];
if (category.secMenu) {
category.secMenu.forEach(secItem => {
if (secItem.thrMenu) {
secItem.thrMenu.forEach(thrItem => {
if (thrItem.value) {
allProducts = allProducts.concat(thrItem.value);
}
});
}
});
}
return allProducts;
},
// 切换分类
switchCategory(category) {
if (category && category.firTitle === '元境') {
this.goYuanjing()
return;
}
this.activeCategory = category.firTitle;
this.activeSubId = null;
this.activeThirdId = null;
if (this.isTokenMarketCategory(category.firTitle)) {
this.setTokenMarketData(category);
this.currentProducts = [];
return;
}
this.setDefaultSubcategory(category);
},
async goYuanjing() {
const userId = sessionStorage.getItem('userId')
if (!userId || userId === 'null' || userId === '') {
this.$message.warning('请先登录哦~')
return
}
const yuanJingWindow = window.open('', '_blank')
try {
const res = await gotoYuanJingAPI({ user_id: userId })
const deerer = this.getYuanJingAuthorization(res)
if (!deerer) {
if (yuanJingWindow) {
yuanJingWindow.close()
}
this.$message.error((res && res.msg) || '获取元境授权参数失败')
return
}
const loginUrl = `https://ai.opencomputing.cn/#/getCookie?deerer=${encodeURIComponent(deerer)}`
if (yuanJingWindow) {
yuanJingWindow.location.href = loginUrl
} else {
window.location.href = loginUrl
}
} catch (error) {
if (yuanJingWindow) {
yuanJingWindow.close()
}
this.$message.error('跳转元境失败,请稍后重试')
}
},
getYuanJingAuthorization(res) {
if (!res) {
return ''
}
if (typeof res === 'string') {
return res
}
const data = res.data || res
if (typeof data === 'string') {
return data
}
return data.Authorization || data.authorization || data.token || data.header || data.value || ''
},
// 切换二级分类
switchSubcategory(subItem) {
this.activeSubId = this.getSubItemKey(subItem);
this.activeThirdId = null;
this.updateProductList(subItem);
},
// 切换三级分类
switchThirdLevel(thirdItem) {
this.activeThirdId = this.getThirdItemKey(thirdItem);
this.currentProducts = thirdItem.value || [];
},
// 更新产品列表
updateProductList(subItem) {
if (this.isSpecialCategory) {
this.currentProducts = this.getAllProductsFromSubcategory(subItem);
return;
}
if (subItem.thrMenu && subItem.thrMenu.length > 0) {
this.activeThirdId = null;
this.currentProducts = this.getAllProductsFromSubcategory(subItem);
} else {
this.activeThirdId = null;
this.currentProducts = [];
}
},
// 获取二级分类下的所有产品
getAllProductsFromSubcategory(subItem) {
let allProducts = [];
if (subItem.thrMenu) {
subItem.thrMenu.forEach(thrItem => {
if (thrItem.value) {
allProducts = allProducts.concat(thrItem.value);
}
});
}
return allProducts;
},
// 获取产品数量
getProductCount(subItem) {
if (!subItem.thrMenu) return 0;
return subItem.thrMenu.reduce((total, thirdItem) => {
return total + (thirdItem.value ? thirdItem.value.length : 0);
}, 0);
},
// 获取产品描述
getProductDescription(product) {
const descriptions = {
'云服务器_GPU': '高性能GPU云服务器适合AI训练、科学计算等场景',
'云服务器_BCC': '平衡型云服务器,满足大多数业务需求',
'专属服务器': '独享物理服务器,提供更高性能和安全隔离',
'轻量应用服务器': '轻量级应用部署,简单易用',
'专线接入': '高速稳定的专线网络接入服务',
'文字识别': '高精度文字识别,支持多种场景和语言',
'AI能力引擎': '强大的AI能力引擎赋能各种智能应用',
'大数据平台': '全面的大数据处理和分析平台',
'云服务平台': '一站式云服务平台,便捷高效',
'智能内容科技': '智能内容生成与处理技术',
'SME企业服务': '中小企业专属服务解决方案',
'视频云平台': '专业的视频云处理与分发平台',
'智能短信': '智能化短信服务和营销解决方案'
};
return descriptions[product.name] || '专业的云服务产品,提供稳定可靠的服务';
},
// 根据产品类型展示不同图标,让卡片更容易区分。
getProductIcon(product) {
const iconMap = {
'百度云': 'el-icon-cloudy',
'阿里云': 'el-icon-cloudy-and-sunny',
'智算': 'el-icon-cpu',
'算力网络': 'el-icon-connection'
};
return iconMap[product.type] || 'el-icon-coin';
},
// 修复阿里云跳转逻辑
async handleAliyunProductClick(useid) {
// 第一步:同步请求
const syncResponse = await reqNewHomeSync();
if (!syncResponse.status) {
this.$message.warning(syncResponse.msg || '同步失败,请稍后重试');
return;
}
// 第二步:获取跳转链接
const festivalResponse = await reqNewHomeFestival();
console.log(festivalResponse);
if (festivalResponse.status && festivalResponse.data) {
window.open(festivalResponse.data);
} else {
this.$message.warning(festivalResponse.msg || '获取跳转链接失败');
}
},
// 点击产品
async handleProductClick(product) {
console.log('点击产品:', product);
const userId = sessionStorage.getItem('userId');
if (product.type === '百度云') {
if (userId) {
localStorage.setItem('redirectUrl', product.url);
localStorage.setItem('userRescourseUrl', product.listUrl);
this.$router.push({
name: 'baiduProductShow',
params: {
listUrl: product.listUrl,
url: product.url
}
});
} else {
this.$router.push({
path: "/login",
query: {
fromPath: 'productService',
type: 'bd',
listUrl: product.listUrl,
url: product.url
}
});
}
} else if (product.type === '阿里云') {
if (userId) {
await this.handleAliyunProductClick();
} else {
this.$router.push({
path: "/login",
query: {
fromPath: 'productService',
type: 'ali',
}
});
}
} else if (product.type === '智算' || product.type === '算力网络') {
if (product.name === '容器云') {
if (userId) {
this.$router.push('/product/productHome/k8s/createK8s');
} else {
this.$router.push({
path: "/login",
query: {
fromPath: 'productService',
type: 'rqy',
}
});
}
} else {
this.$router.push({
path: '/homePage/detail',
query: {
id: product.id,
}
});
}
} else if (product.name === '灵医智能体') {
this.$router.push('/homePage/hospital');
} else if (product.name === '客悦·智能客服') {
this.$router.push('/homePage/customerService');
}
}
}
};
</script>
<style lang="less" scoped>
.product-service-page {
margin: 0 auto;
padding: 24px;
background: #fff;
min-height: calc(100vh - 100px);
.category-nav {
display: flex;
justify-content: space-around;
margin-bottom: 32px;
border-bottom: 1px solid #f0f0f0;
.nav-item {
padding: 12px 24px;
font-size: 16px;
color: #666;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.3s;
&:hover {
color: #1E6FFF;
}
&.active {
color: #1E6FFF;
border-bottom-color: #1E6FFF;
font-weight: 600;
}
}
}
.product-content {
display: flex;
gap: 24px;
.menu-sidebar {
width: 280px;
height: calc(100vh - 100px);
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
flex-shrink: 0;
.section-title {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #e8e8e8;
}
.subcategory-section {
margin-bottom: 24px;
}
.subcategory-list,
.third-level-list {
.subcategory-item,
.third-level-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
cursor: pointer;
transition: all 0.3s;
border-radius: 6px;
margin-bottom: 4px;
&:hover {
background: rgba(30, 111, 255, 0.05);
color: #1E6FFF;
}
&.active {
background: rgba(30, 111, 255, 0.1);
color: #1E6FFF;
font-weight: 600;
}
.subcategory-name {
font-size: 14px;
}
.product-count {
background: #e8f4ff;
color: #1E6FFF;
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
min-width: 20px;
text-align: center;
}
}
}
.third-level-list {
.third-level-item {
padding-left: 20px;
font-size: 13px;
}
}
}
.main-content {
flex: 1;
&.full-width {
flex: 1;
margin-left: 0;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
.product-card {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 20px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #1E6FFF;
}
.product-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
.product-name {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
margin: 0;
flex: 1;
}
.discount-badge {
background: linear-gradient(135deg, #ff6b6b, #ff4757);
color: #fff;
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
font-weight: 600;
margin-left: 8px;
}
}
.product-desc {
font-size: 14px;
color: #666;
line-height: 1.5;
margin-bottom: 16px;
display: -webkit-box;
// -webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.product-footer {
display: flex;
justify-content: space-between;
align-items: center;
.product-type {
font-size: 12px;
color: #999;
background: #f8f9fa;
padding: 4px 8px;
border-radius: 4px;
}
.detail-btn {
background: #1E6FFF;
color: #fff;
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: background 0.3s;
&:hover {
background: #0d5ae0;
}
}
}
}
}
.empty-state {
text-align: center;
padding: 60px 20px;
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
}
.empty-text {
color: #999;
font-size: 14px;
}
}
}
}
}
@media (max-width: 768px) {
.product-service-page {
padding: 16px;
.product-content {
flex-direction: column;
.menu-sidebar {
width: 100%;
order: 2;
}
.main-content {
order: 1;
&.full-width {
margin-left: 0;
}
}
}
.product-grid {
grid-template-columns: 1fr !important;
}
}
}
.product-service-page {
background: linear-gradient(180deg, #f3f7ff 0%, #f7f9fc 38%, #ffffff 100%);
.page-hero {
display: flex;
justify-content: space-between;
align-items: stretch;
gap: 24px;
margin-bottom: 20px;
padding: 28px;
color: #ffffff;
background:
radial-gradient(circle at 88% 12%, rgba(255, 255, 255, 0.28) 0, rgba(255, 255, 255, 0) 32%),
linear-gradient(135deg, #1e6fff 0%, #5d8dff 52%, #7fb0ff 100%);
border-radius: 22px;
box-shadow: 0 18px 42px rgba(30, 111, 255, 0.18);
.hero-content {
max-width: 560px;
.hero-tag {
display: inline-flex;
align-items: center;
height: 26px;
padding: 0 12px;
margin-bottom: 14px;
font-size: 12px;
color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.22);
border-radius: 999px;
}
h2 {
margin: 0 0 10px;
font-size: 30px;
font-weight: 700;
letter-spacing: 1px;
}
p {
margin: 0;
color: rgba(255, 255, 255, 0.86);
font-size: 14px;
line-height: 1.8;
}
}
.hero-stats {
display: grid;
grid-template-columns: repeat(3, 116px);
gap: 12px;
align-items: center;
.stat-card {
padding: 16px 14px;
text-align: center;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
span {
display: block;
margin-bottom: 8px;
color: rgba(255, 255, 255, 0.78);
font-size: 12px;
}
strong {
font-size: 26px;
line-height: 1;
}
}
}
}
.product-search {
margin: 10px 0;
/deep/ .el-input__inner {
height: 40px;
line-height: 40px;
border-radius: 12px;
}
}
.page-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
margin-bottom: 16px;
padding: 18px 20px;
background: rgba(255, 255, 255, 0.92);
border: 1px solid #edf1f7;
border-radius: 18px;
box-shadow: 0 10px 28px rgba(31, 45, 61, 0.06);
h3 {
margin: 0 0 6px;
color: #1f2d3d;
font-size: 18px;
}
p {
margin: 0;
color: #8a94a6;
font-size: 13px;
}
}
.category-nav {
gap: 10px;
padding: 10px;
overflow-x: auto;
background: #ffffff;
border: 1px solid #edf1f7;
border-radius: 18px;
box-shadow: 0 10px 28px rgba(31, 45, 61, 0.05);
.nav-item {
flex: 1;
min-width: 96px;
padding: 12px 18px;
text-align: center;
border: 0;
border-radius: 12px;
&:hover {
background: #f4f8ff;
}
&.active {
color: #ffffff;
background: linear-gradient(135deg, #1e6fff, #5d8dff);
box-shadow: 0 10px 22px rgba(30, 111, 255, 0.18);
}
}
}
.product-content {
align-items: flex-start;
.menu-sidebar {
position: sticky;
top: 16px;
padding: 16px;
background: #ffffff;
border: 1px solid #edf1f7;
border-radius: 18px;
box-shadow: 0 12px 30px rgba(31, 45, 61, 0.06);
}
.token-filter-section {
margin-top: 18px;
h4 {
margin: 0 0 10px;
color: #98a2b3;
font-size: 13px;
font-weight: 600;
}
}
.token-filter-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px 12px;
}
.token-filter-item {
padding: 4px 0;
color: #344054;
text-align: left;
background: transparent;
border: 0;
cursor: pointer;
font-size: 14px;
&.active {
color: #1e6fff;
font-weight: 600;
}
}
}
.product-content .subcategory-list,
.product-content .third-level-list {
.subcategory-item,
.third-level-item {
padding: 12px 14px;
border-radius: 12px;
&.active {
background: linear-gradient(135deg, rgba(30, 111, 255, 0.12), rgba(93, 141, 255, 0.08));
box-shadow: inset 3px 0 0 #1e6fff;
}
}
}
.product-content .main-content {
min-width: 0;
.token-market-toolbar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 14px;
color: #667085;
font-size: 14px;
}
.token-sort-btn {
height: 28px;
padding: 0 12px;
color: #667085;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 8px;
cursor: pointer;
&.active {
color: #7c3aed;
background: #f5f3ff;
border-color: #c4b5fd;
}
}
.token-market-grid {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
.token-market-card {
flex: 0 0 calc((100% - 28px) / 3);
min-width: 0;
padding: 14px;
background: #ffffff;
border: 1px solid #edf1f7;
border-radius: 12px;
box-shadow: 0 10px 24px rgba(31, 45, 61, 0.05);
cursor: pointer;
transition: all 0.2s ease;
&:hover {
border-color: #d7e4f5;
transform: translateY(-2px);
box-shadow: 0 16px 32px rgba(31, 45, 61, 0.08);
}
}
.token-card-top {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
h3 {
flex: 1;
margin: 0;
color: #1f2d3d;
font-size: 14px;
font-weight: 700;
line-height: 1.35;
}
}
.token-new-badge {
padding: 1px 6px;
color: #ffffff;
font-size: 10px;
font-weight: 700;
background: #3b82f6;
border-radius: 5px;
}
.token-more {
color: #98a2b3;
transform: rotate(90deg);
}
.token-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px dashed #d8e0ee;
span {
padding: 2px 6px;
color: #667085;
font-size: 11px;
background: #f8fafc;
border: 1px solid #edf1f7;
border-radius: 6px;
}
}
.token-price-line {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 8px;
color: #1f2937;
font-size: 12px;
font-weight: 600;
}
.token-meta {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 10px;
color: #667085;
font-size: 12px;
}
.token-provider-avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
color: #ffffff;
font-size: 10px;
background: #1e6fff;
border-radius: 4px;
}
.token-actions {
display: flex;
gap: 6px;
button {
display: inline-flex;
align-items: center;
gap: 4px;
height: 28px;
padding: 0 10px;
color: #475467;
font-size: 12px;
background: #ffffff;
border: 1px solid #d8e0ee;
border-radius: 7px;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
color: #1e6fff;
border-color: #9ec5ff;
background: #f4f8ff;
}
}
.experience {
color: #4f46e5;
border-color: #c7d2fe;
&:hover {
color: #4338ca;
border-color: #a5b4fc;
background: #eef2ff;
}
}
}
.product-grid {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
.product-card {
position: relative;
min-height: 178px;
overflow: hidden;
border-color: #edf1f7;
border-radius: 18px;
box-shadow: 0 10px 26px rgba(31, 45, 61, 0.06);
&::after {
content: "";
position: absolute;
right: -36px;
top: -36px;
width: 96px;
height: 96px;
background: rgba(30, 111, 255, 0.08);
border-radius: 50%;
pointer-events: none;
}
&:hover {
box-shadow: 0 18px 38px rgba(30, 111, 255, 0.14);
}
.product-header {
position: relative;
z-index: 1;
align-items: flex-start;
gap: 12px;
.product-icon {
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 42px;
width: 42px;
height: 42px;
color: #1e6fff;
font-size: 22px;
background: #eef5ff;
border-radius: 14px;
}
.product-title {
flex: 1;
min-width: 0;
}
.product-name {
margin: 0 0 7px;
color: #1f2d3d;
line-height: 1.4;
}
.product-type {
display: inline-flex;
color: #7a8699;
font-size: 12px;
background: #f4f6fa;
padding: 4px 8px;
border-radius: 999px;
}
.discount-badge {
position: relative;
z-index: 1;
border-radius: 999px;
white-space: nowrap;
}
}
.product-desc {
position: relative;
z-index: 1;
min-height: 44px;
color: #667085;
line-height: 1.6;
line-clamp: 2;
-webkit-line-clamp: 2;
}
.product-footer {
position: relative;
z-index: 1;
display: flex;
justify-content: space-between;
align-items: center;
.product-status {
color: #12b76a;
font-size: 12px;
background: #ecfdf3;
padding: 5px 10px;
border-radius: 999px;
}
.detail-btn {
display: inline-flex;
align-items: center;
gap: 4px;
color: #ffffff;
background: #1E6FFF;
border: none;
padding: 8px 13px;
border-radius: 999px;
cursor: pointer;
transition: background 0.25s ease, transform 0.25s ease;
&:hover {
background: #0d5ae0;
transform: translateX(2px);
}
}
}
}
}
.empty-state {
padding: 76px 20px;
text-align: center;
background: #ffffff;
border: 1px dashed #d8e0ee;
border-radius: 18px;
.empty-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
margin-bottom: 16px;
color: #98a2b3;
font-size: 32px;
background: #f4f6fa;
border-radius: 18px;
}
.empty-text {
margin: 0 0 8px;
color: #475467;
font-size: 15px;
font-weight: 600;
}
span {
color: #98a2b3;
font-size: 13px;
}
}
}
@media (max-width: 1080px) {
.page-hero {
flex-direction: column;
.hero-stats {
grid-template-columns: repeat(3, 1fr);
}
}
.page-toolbar {
align-items: flex-start;
flex-direction: column;
.product-search {
width: 100%;
}
}
.product-content .main-content .token-market-card {
flex-basis: calc((100% - 14px) / 2);
}
}
@media (max-width: 768px) {
.product-content .main-content .token-market-card {
flex-basis: 100%;
}
}
}
</style>