543 lines
14 KiB
Vue
543 lines
14 KiB
Vue
<template>
|
||
<div class="model-detail-page">
|
||
<header class="top-nav">
|
||
<button class="back-btn" type="button" @click="goBack">
|
||
<i class="el-icon-arrow-left"></i>
|
||
返回
|
||
</button>
|
||
<span class="nav-divider"></span>
|
||
<span class="token-market-link" @click="goTokenMarket">Token市集</span>
|
||
<div class="nav-actions">
|
||
<span>控制台</span>
|
||
<span>用户后台</span>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="detail-container">
|
||
<section class="model-hero-card">
|
||
<div class="model-logo">
|
||
<i class="el-icon-data-analysis"></i>
|
||
</div>
|
||
<div class="model-summary">
|
||
<div class="model-title-row">
|
||
<h1>{{ modelInfo.name }}</h1>
|
||
<el-tag size="mini" type="success">{{ modelInfo.tag }}</el-tag>
|
||
</div>
|
||
<div class="model-meta">
|
||
<span v-for="item in modelInfo.metaList" :key="item">{{ item }}</span>
|
||
</div>
|
||
<p>{{ modelInfo.description }}</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="version-card">
|
||
<div>
|
||
<h3>版本介绍</h3>
|
||
<p>
|
||
模型ID:{{ modelInfo.modelId }}
|
||
<i class="el-icon-copy-document"></i>
|
||
</p>
|
||
<p>{{ modelInfo.versionDescription }}</p>
|
||
</div>
|
||
<div class="version-actions">
|
||
<el-button size="small" @click="goApiDocument">API文档</el-button>
|
||
<el-button
|
||
size="small"
|
||
:type="canExperience ? 'primary' : 'info'"
|
||
:disabled="!canExperience"
|
||
@click="goExperience"
|
||
>
|
||
体验
|
||
</el-button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="two-column">
|
||
<div class="info-card">
|
||
<h3>模型能力</h3>
|
||
<div v-for="item in capabilityList" :key="item.label" class="info-row">
|
||
<span>{{ item.label }}</span>
|
||
<strong>{{ item.value }}</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-card">
|
||
<h3>模型限制</h3>
|
||
<div v-for="item in limitList" :key="item.label" class="info-row">
|
||
<span>{{ item.label }}</span>
|
||
<strong>{{ item.value }}</strong>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="price-card">
|
||
<h3>服务价格</h3>
|
||
<div class="price-list">
|
||
<div class="price-item input">
|
||
<span>模型输入</span>
|
||
<strong>{{ modelInfo.inputPrice }}</strong>
|
||
<em>{{ modelInfo.priceUnit }}</em>
|
||
</div>
|
||
<div class="price-item output">
|
||
<span>模型输出</span>
|
||
<strong>{{ modelInfo.outputPrice }}</strong>
|
||
<em>{{ modelInfo.priceUnit }}</em>
|
||
</div>
|
||
<div v-if="hasValue(modelInfo.cacheHitInputPrice)" class="price-item cache">
|
||
<span>缓存命中输入</span>
|
||
<strong>{{ modelInfo.cacheHitInputPrice }}</strong>
|
||
<em>{{ modelInfo.priceUnit }}</em>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="article-card">
|
||
<h3>1. 模型介绍</h3>
|
||
<p>{{ modelInfo.longDescription }}</p>
|
||
|
||
<h3>2. 模型亮点</h3>
|
||
<div
|
||
v-for="(item, index) in featureList"
|
||
:key="item.label"
|
||
class="feature-block"
|
||
:class="index % 2 === 0 ? 'blue' : 'purple'"
|
||
>
|
||
<i :class="index % 2 === 0 ? 'el-icon-cpu' : 'el-icon-magic-stick'"></i>
|
||
<div>
|
||
<strong>{{ item.label }}</strong>
|
||
<p>{{ item.value }}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer class="page-footer">© 2026 开元云科技 · 模型公共服务平台</footer>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'ModelDetail',
|
||
data() {
|
||
return {
|
||
modelInfo: {
|
||
name: 'MiniMax-M2.5',
|
||
modelId: 'abab7c72c278cfba',
|
||
description: 'MiniMax-M2.5 是面向复杂任务处理的通用语言模型,适用于知识问答、文案创作、工具调用和办公生产力场景。',
|
||
longDescription: 'MiniMax-M2.5 是 MiniMax 推出的新一代旗舰语言模型,致力于提升真实世界复杂任务中的表现。在推理、工具使用和搜索、办公生产力场景中均具备较好的任务完成能力。',
|
||
tag: '对话模型',
|
||
metaList: ['MiniMax', '对话模型', '192K上下文'],
|
||
versionDescription: '当前版本能力稳定,适合内容生成、知识问答、工具调用和复杂任务规划。',
|
||
inputPrice: '0.0021',
|
||
outputPrice: '0.0084',
|
||
cacheHitInputPrice: '',
|
||
priceUnit: '元/千Tokens',
|
||
experience: 1
|
||
},
|
||
capabilityList: [
|
||
{ label: '接口类型', value: '/v2/chat/completions' },
|
||
{ label: '接入ID', value: 'minimax-m2.5' },
|
||
{ label: '输入类型', value: '文本 / 图像 / 音频' },
|
||
{ label: '输出类型', value: '文本 / 结构化输出' },
|
||
{ label: 'Function Call', value: '支持' },
|
||
{ label: '联网搜索', value: '支持' },
|
||
{ label: '私有化部署', value: '不支持' }
|
||
],
|
||
limitList: [
|
||
{ label: '输入长度', value: '192K' },
|
||
{ label: '单轮输出', value: '32K' },
|
||
{ label: '输出长度', value: '128K' },
|
||
{ label: '服务速度限制', value: '60 RPM / 250000 TPM' }
|
||
],
|
||
featureList: [
|
||
{ label: '推理', value: '擅长处理数学、代码、逻辑分析和复杂任务拆解,适合作为业务助手和智能问答底座。' },
|
||
{ label: '模型调优', value: '提供稳定的上下文理解能力,便于后续结合业务数据进行知识增强和场景优化。' }
|
||
]
|
||
}
|
||
},
|
||
computed: {
|
||
canExperience() {
|
||
return Number(this.modelInfo.experience) === 1
|
||
}
|
||
},
|
||
created() {
|
||
this.loadModelDetail()
|
||
},
|
||
watch: {
|
||
'$route.query.id'() {
|
||
this.loadModelDetail()
|
||
}
|
||
},
|
||
methods: {
|
||
// TOKEN 市集卡片跳转时会缓存完整模型对象,详情页优先使用这份数据渲染。
|
||
loadModelDetail() {
|
||
const model = this.getCachedTokenMarketModel()
|
||
if (!model) return
|
||
|
||
const name = this.displayValue(model.display_name || model.model_name)
|
||
const modelId = this.displayValue(model.llmid || model.model_name || model.id)
|
||
const modelType = this.displayValue(model.model_type)
|
||
const provider = this.displayValue(model.provider)
|
||
const contextLength = this.displayValue(model.context_length)
|
||
const billingMethod = this.displayValue(model.billing_method)
|
||
const description = this.normalizeText(model.description)
|
||
|
||
this.modelInfo = {
|
||
name,
|
||
modelId,
|
||
description,
|
||
longDescription: description,
|
||
tag: modelType,
|
||
metaList: [provider, modelType, contextLength !== '-' ? `${contextLength}上下文` : '', billingMethod].filter(Boolean),
|
||
versionDescription: `${name} 当前版本由 ${provider} 提供,适合在 ${modelType} 场景中使用。`,
|
||
inputPrice: this.formatPrice(model.input_token_price),
|
||
outputPrice: this.formatPrice(model.output_token_price),
|
||
cacheHitInputPrice: this.formatPrice(model.cache_hit_input_price),
|
||
priceUnit: this.getPriceUnit(model.billing_unit),
|
||
experience: this.normalizeExperience(model.experience)
|
||
}
|
||
this.capabilityList = this.parseInfoList(model.capabilities, [
|
||
{ label: '接口类型', value: '-' },
|
||
{ label: '接入ID', value: modelId },
|
||
{ label: '模型类型', value: modelType },
|
||
{ label: '供应商', value: provider }
|
||
])
|
||
this.limitList = this.parseInfoList(model.limitations, [
|
||
{ label: '上下文长度', value: contextLength },
|
||
{ label: '计费方式', value: billingMethod },
|
||
{ label: '计费单位', value: this.displayValue(model.billing_unit) }
|
||
])
|
||
this.featureList = this.parseInfoList(model.highlights, [
|
||
{ label: '模型说明', value: description },
|
||
{ label: '适用场景', value: `${modelType}、内容生成、智能问答` }
|
||
])
|
||
},
|
||
getCachedTokenMarketModel() {
|
||
const cache = sessionStorage.getItem('tokenMarketSelectedModel')
|
||
if (!cache) return null
|
||
|
||
try {
|
||
const model = JSON.parse(cache)
|
||
const queryId = String(this.$route.query.id || this.$route.query.model_id || '')
|
||
if (!queryId || String(model.id) === queryId || String(model.llmid) === queryId || String(model.model_name) === queryId) {
|
||
return model
|
||
}
|
||
} catch (error) {
|
||
console.warn('[模型详情] 解析 TOKEN 市集模型缓存失败', error)
|
||
}
|
||
return null
|
||
},
|
||
parseInfoList(value, fallback = []) {
|
||
const parsed = this.safeParse(value)
|
||
let list = []
|
||
|
||
if (Array.isArray(parsed)) {
|
||
list = parsed.map((item, index) => ({
|
||
label: this.displayValue(item.name || item.label || item.key || `字段${index + 1}`),
|
||
value: this.displayValue(this.pickValue(item.value, item.text, item.content))
|
||
}))
|
||
} else if (parsed && typeof parsed === 'object') {
|
||
list = Object.keys(parsed).map(key => ({
|
||
label: key,
|
||
value: this.displayValue(parsed[key])
|
||
}))
|
||
} else if (typeof parsed === 'string' && parsed) {
|
||
list = [{ label: '说明', value: parsed }]
|
||
}
|
||
|
||
return list.length ? list : fallback
|
||
},
|
||
normalizeText(value) {
|
||
const parsed = this.safeParse(value)
|
||
if (Array.isArray(parsed)) {
|
||
return parsed.map(item => `${this.displayValue(item.name || item.label)}:${this.displayValue(item.value)}`).join(';')
|
||
}
|
||
if (parsed && typeof parsed === 'object') {
|
||
return Object.keys(parsed).map(key => `${key}:${this.displayValue(parsed[key])}`).join(';')
|
||
}
|
||
return this.displayValue(parsed)
|
||
},
|
||
safeParse(value) {
|
||
if (value === undefined || value === null || value === '') return ''
|
||
if (typeof value !== 'string') return value
|
||
try {
|
||
return JSON.parse(value)
|
||
} catch (error) {
|
||
return value
|
||
}
|
||
},
|
||
pickValue(...values) {
|
||
return values.find(value => value !== undefined && value !== null && value !== '')
|
||
},
|
||
formatPrice(value) {
|
||
if (!this.hasValue(value)) return '-'
|
||
const num = Number(value)
|
||
if (Number.isNaN(num)) return value
|
||
return num.toFixed(6).replace(/\.?0+$/, '')
|
||
},
|
||
getPriceUnit(unit) {
|
||
return unit ? `元/${unit}` : '元/千Tokens'
|
||
},
|
||
displayValue(value) {
|
||
if (value === undefined || value === null || value === '') return '-'
|
||
return value
|
||
},
|
||
hasValue(value) {
|
||
return value !== undefined && value !== null && value !== '' && value !== '-'
|
||
},
|
||
normalizeExperience(value) {
|
||
return Number(value) === 1 ? 1 : 0
|
||
},
|
||
goTokenMarket() {
|
||
if (this.$route.query.single === '1') {
|
||
this.$router.push({ path: '/tokenMarket', query: { category: 'TOKEN市集', single: '1' } })
|
||
return
|
||
}
|
||
this.$router.push({ path: '/product', query: { category: 'TOKEN市集' } })
|
||
},
|
||
goBack() {
|
||
this.$router.back()
|
||
},
|
||
buildModelQuery() {
|
||
return {
|
||
id: this.$route.query.id,
|
||
model_id: this.$route.query.model_id || this.$route.query.id,
|
||
from: 'tokenMarket',
|
||
category: 'TOKEN市集',
|
||
single: this.$route.query.single
|
||
}
|
||
},
|
||
goApiDocument() {
|
||
this.$router.push({ name: 'modelApiDocument', query: this.buildModelQuery() })
|
||
},
|
||
goExperience() {
|
||
if (!this.canExperience) return
|
||
this.$router.push({ name: 'modelExperience', query: this.buildModelQuery() })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.model-detail-page {
|
||
height: 100vh;
|
||
color: #1f2d3d;
|
||
background: #f6f8fb;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.top-nav {
|
||
display: flex;
|
||
align-items: center;
|
||
height: 48px;
|
||
padding: 0 28px;
|
||
color: #667085;
|
||
background: #ffffff;
|
||
border-bottom: 1px solid #edf1f7;
|
||
}
|
||
|
||
.back-btn {
|
||
padding: 0;
|
||
color: #667085;
|
||
cursor: pointer;
|
||
background: transparent;
|
||
border: none;
|
||
}
|
||
|
||
.nav-divider {
|
||
width: 1px;
|
||
height: 14px;
|
||
margin: 0 14px;
|
||
background: #d8dee9;
|
||
}
|
||
|
||
.nav-actions {
|
||
display: flex;
|
||
gap: 24px;
|
||
margin-left: auto;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.token-market-link {
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
color: #2f6bff;
|
||
}
|
||
}
|
||
|
||
.detail-container {
|
||
width: 1180px;
|
||
max-width: calc(100vw - 48px);
|
||
margin: 28px auto 0;
|
||
}
|
||
|
||
.model-hero-card,
|
||
.version-card,
|
||
.info-card,
|
||
.price-card,
|
||
.article-card {
|
||
background: #ffffff;
|
||
border: 1px solid #edf1f7;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.model-hero-card {
|
||
display: flex;
|
||
padding: 28px;
|
||
}
|
||
|
||
.model-logo {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 0 0 58px;
|
||
width: 58px;
|
||
height: 58px;
|
||
margin-right: 18px;
|
||
color: #ffffff;
|
||
font-size: 28px;
|
||
background: linear-gradient(135deg, #ff6b6b, #7c6cff);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.model-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
|
||
h1 {
|
||
margin: 0;
|
||
font-size: 26px;
|
||
}
|
||
}
|
||
|
||
.model-meta {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin: 12px 0;
|
||
color: #667085;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.model-summary p,
|
||
.version-card p,
|
||
.article-card p {
|
||
color: #667085;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.version-card {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 22px 28px;
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.two-column {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 18px;
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.info-card {
|
||
padding: 24px;
|
||
|
||
h3 {
|
||
margin: 0 0 18px;
|
||
padding-left: 10px;
|
||
border-left: 3px solid #2f6bff;
|
||
}
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 13px 0;
|
||
border-bottom: 1px solid #f0f2f5;
|
||
|
||
span {
|
||
color: #667085;
|
||
}
|
||
}
|
||
|
||
.price-card,
|
||
.article-card {
|
||
padding: 24px;
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.price-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||
gap: 18px;
|
||
}
|
||
|
||
.price-item {
|
||
padding: 20px;
|
||
border-radius: 12px;
|
||
|
||
span,
|
||
em {
|
||
display: block;
|
||
color: #667085;
|
||
font-style: normal;
|
||
}
|
||
|
||
strong {
|
||
display: block;
|
||
margin: 8px 0 4px;
|
||
color: #5356d8;
|
||
font-size: 28px;
|
||
}
|
||
|
||
&.input {
|
||
background: #eef4ff;
|
||
}
|
||
|
||
&.output {
|
||
background: #fff0fa;
|
||
}
|
||
|
||
&.cache {
|
||
background: #f0fdf4;
|
||
}
|
||
}
|
||
|
||
.feature-block {
|
||
display: flex;
|
||
gap: 14px;
|
||
padding: 18px;
|
||
margin-top: 14px;
|
||
border-radius: 12px;
|
||
|
||
i {
|
||
color: #2f6bff;
|
||
font-size: 20px;
|
||
}
|
||
|
||
strong {
|
||
display: block;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
&.blue {
|
||
background: #eef4ff;
|
||
}
|
||
|
||
&.purple {
|
||
background: #fff0fa;
|
||
}
|
||
}
|
||
|
||
.page-footer {
|
||
padding: 32px 0;
|
||
color: #98a2b3;
|
||
text-align: center;
|
||
font-size: 12px;
|
||
}
|
||
</style>
|