From 3f65e1cb0c7888db7f13f059ec4231a0f9d2ff13 Mon Sep 17 00:00:00 2001 From: hrx <18603305412@163.com> Date: Fri, 29 May 2026 17:57:44 +0800 Subject: [PATCH] updata --- .../FinancialOverview/FinancialOverview.js | 17 + f/web-kboss/src/api/model/model.js | 14 +- f/web-kboss/src/router/index.js | 50 +- f/web-kboss/src/store/modules/permission.js | 16 + .../views/finance/billingStatistics/index.vue | 669 ++++++++++++++++ .../views/finance/financialOverview/index.vue | 736 ++++++++++++++++++ .../modelInfoConfig/ModelInfoEditDialog.vue | 120 ++- .../views/operation/modelInfoConfig/index.vue | 58 +- .../src/views/product/allProduct/index.vue | 412 +++++++--- f/web-kboss/src/views/tokenUsage/index.vue | 1 + 10 files changed, 1938 insertions(+), 155 deletions(-) create mode 100644 f/web-kboss/src/api/FinancialOverview/FinancialOverview.js create mode 100644 f/web-kboss/src/views/finance/billingStatistics/index.vue create mode 100644 f/web-kboss/src/views/finance/financialOverview/index.vue diff --git a/f/web-kboss/src/api/FinancialOverview/FinancialOverview.js b/f/web-kboss/src/api/FinancialOverview/FinancialOverview.js new file mode 100644 index 0000000..9e4c7aa --- /dev/null +++ b/f/web-kboss/src/api/FinancialOverview/FinancialOverview.js @@ -0,0 +1,17 @@ +import request from "@/utils/request"; +// 获取财务概览 +export const reqFinancialOverview = (params = {}) => { + return request({ + url: '/bill/finance_order_report_overview.dspy', + method: 'get', + params + }) +} +// 计费统计 +export const reqBillingStatistics = (params = {}) => { + return request({ + url: '/bill/finance_order_report.dspy', + method: 'get', + params + }) +} \ No newline at end of file diff --git a/f/web-kboss/src/api/model/model.js b/f/web-kboss/src/api/model/model.js index 226e457..07fb52c 100644 --- a/f/web-kboss/src/api/model/model.js +++ b/f/web-kboss/src/api/model/model.js @@ -126,19 +126,25 @@ export const reqTokenUsage = (params = {}) => { // 模型信息配置添加 export const reqModelInfoConfig = (params = {}) => { + const isFormData = params instanceof FormData return request({ url: '/cntoai/model_management_add.dspy', - method: 'get', - params + method: isFormData ? 'post' : 'get', + params: isFormData ? undefined : params, + data: isFormData ? params : undefined, + headers: isFormData ? { 'Content-Type': 'multipart/form-data' } : undefined }) } // 模型信息配置编辑(编辑时需要额外传 id) export const reqModelInfoConfigEdit = (params = {}) => { + const isFormData = params instanceof FormData return request({ url: '/cntoai/model_management_update.dspy', - method: 'get', - params + method: isFormData ? 'post' : 'get', + params: isFormData ? undefined : params, + data: isFormData ? params : undefined, + headers: isFormData ? { 'Content-Type': 'multipart/form-data' } : undefined }) } diff --git a/f/web-kboss/src/router/index.js b/f/web-kboss/src/router/index.js index dafc74d..0828d53 100644 --- a/f/web-kboss/src/router/index.js +++ b/f/web-kboss/src/router/index.js @@ -436,6 +436,44 @@ export const constantRoutes = [ * 需要根据用户角色动态加载的路由 */ export const asyncRoutes = [ + // 财务——财务概览 + { + path: "/financialOverview", + component: Layout, + meta: { + title: "财务概览", + fullPath: "/financialOverview", + noCache: true, + icon: "el-icon-data-analysis", + roles: ["财务"] + }, + children: [ + { + path: "", + component: () => import('@/views/finance/financialOverview/index.vue'), + name: 'FinancialOverview', + meta: { + title: "财务概览", + fullPath: "/financialOverview", + noCache: true, + roles: ["财务"] + } + }, + // 计费统计 + { + path: "billingStatistics", + component: () => import('@/views/finance/billingStatistics/index.vue'), + name: 'BillingStatistics', + meta: { + title: "计费统计", + fullPath: "/financialOverview/billingStatistics", + noCache: false, + roles: ["财务"] + } + } + ] + }, + // 运营——模型管理 { path: "/modelManagement", @@ -2049,9 +2087,19 @@ export const asyncRoutes = [ }, { - path: "/finance", component: Layout, redirect: "/finance", meta: { + path: "/finance", component: Layout, redirect: "/finance/financialOverview", meta: { title: "财务", icon: "el-icon-s-data", noCache: true, fullPath: "/finance", }, children: [{ + path: "financialOverview", + component: () => import("@/views/finance/financialOverview"), + name: "FinancialOverview", + meta: { + title: "财务概览", + fullPath: "/finance/financialOverview", + icon: "el-icon-data-analysis", + roles: ["财务"] + }, + }, { path: "supplierSettlement", hidden: true, component: () => import( diff --git a/f/web-kboss/src/store/modules/permission.js b/f/web-kboss/src/store/modules/permission.js index 719ba05..1b2d5bb 100644 --- a/f/web-kboss/src/store/modules/permission.js +++ b/f/web-kboss/src/store/modules/permission.js @@ -6,6 +6,7 @@ const MOBILE_UA_REGEXP = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Ope // 项目里用到的固定角色名,集中放这里,避免代码里到处写字符串。 const CUSTOMER_ROLE = '客户'; const OPERATION_ROLE = '运营'; +const FINANCE_ROLE = '财务'; // 这个用户能看到订单管理里的特殊子菜单,比如历史订单和订单详情。 const SPECIAL_ORDER_USER = 'ZhipuHZ'; @@ -19,6 +20,9 @@ const COMMON_ROUTE_PATHS = ['/product', '/tokenManagement', '/tokenUsage', '/mod // 运营角色需要额外补出来的菜单。 const OPERATION_EXTRA_ROUTE_PATHS = ['/modelManagement', '/modelInfoConfig', '/operationReport']; +// 财务角色需要额外补出来的菜单。 +const FINANCE_EXTRA_ROUTE_PATHS = ['/financialOverview']; + // 普通客户账号默认要补出来的基础菜单。 const BASE_USER_ROUTE_PATHS = ['/orderManagement', '/resourceManagement']; @@ -336,6 +340,15 @@ function addOperationRoutes(accessedRoutes, routes, userRoles = [], deviceType = return appendMissingRoutes(accessedRoutes, getRoutesByPath(routes, OPERATION_EXTRA_ROUTE_PATHS)); } +// 财务角色额外补财务菜单,目前只在 PC 端展示。 +function addFinanceRoutes(accessedRoutes, routes, userRoles = [], deviceType = 'pc') { + if (!userRoles.includes(FINANCE_ROLE) || deviceType !== 'pc') { + return accessedRoutes; + } + + return appendMissingRoutes(accessedRoutes, getRoutesByPath(routes, FINANCE_EXTRA_ROUTE_PATHS)); +} + // token市集是公共菜单,所有登录用户都要能看到。 function addCommonRoutes(accessedRoutes, routes, deviceType = 'pc') { const commonRoutes = getRoutesByPath(routes, COMMON_ROUTE_PATHS) @@ -499,6 +512,9 @@ const actions = { // 6. 运营角色额外补模型管理。 accessedRoutes = addOperationRoutes(accessedRoutes, asyncRoutes, userRoles, deviceType); + // 6.1 财务角色额外补财务菜单。 + accessedRoutes = addFinanceRoutes(accessedRoutes, asyncRoutes, userRoles, deviceType); + // 7. 最后处理订单管理里的特殊子菜单权限。 accessedRoutes = filterOrderChildrenByUser(accessedRoutes, username); diff --git a/f/web-kboss/src/views/finance/billingStatistics/index.vue b/f/web-kboss/src/views/finance/billingStatistics/index.vue new file mode 100644 index 0000000..8275f74 --- /dev/null +++ b/f/web-kboss/src/views/finance/billingStatistics/index.vue @@ -0,0 +1,669 @@ + + + + + diff --git a/f/web-kboss/src/views/finance/financialOverview/index.vue b/f/web-kboss/src/views/finance/financialOverview/index.vue new file mode 100644 index 0000000..3d0f4b6 --- /dev/null +++ b/f/web-kboss/src/views/finance/financialOverview/index.vue @@ -0,0 +1,736 @@ + + + + + diff --git a/f/web-kboss/src/views/operation/modelInfoConfig/ModelInfoEditDialog.vue b/f/web-kboss/src/views/operation/modelInfoConfig/ModelInfoEditDialog.vue index 90823b8..ee2e91e 100644 --- a/f/web-kboss/src/views/operation/modelInfoConfig/ModelInfoEditDialog.vue +++ b/f/web-kboss/src/views/operation/modelInfoConfig/ModelInfoEditDialog.vue @@ -44,10 +44,23 @@ - +
- 选择文件 - {{ editForm.logo || '未选择任何文件' }} + +
+ model logo +
+
+ 选择文件 + 移除 + {{ editForm.logo || (editForm.modelLogoPreview ? '已上传图片' : '未选择任何文件') }} +
@@ -260,6 +273,9 @@ const DEFAULT_EDIT_FORM = { type: '', provider: '', logo: '', + modelLogo: '', + modelLogoFile: null, + modelLogoPreview: '', apiUrl: '', description: '', contextLength: '', @@ -321,6 +337,7 @@ export default { const apiUrl = this.toFormValue(apiDoc.api_url || row.api_url || '') const curlCode = this.toTextareaValue(apiDoc.curl_code || row.curl_code || '') const pythonCode = this.toTextareaValue(apiDoc.python_code || row.python_code || '') + const modelLogo = row.model_logo || row.modelLogo || row.provider_logo || '' this.editForm = { ...defaultForm, @@ -330,7 +347,10 @@ export default { displayName: row.display_name || row.displayName || row.model_name || row.name || '', type: row.model_type || row.type || '', provider: row.provider || '', - logo: row.provider_logo || row.logo || '', + logo: row.logo || '', + modelLogo, + modelLogoFile: null, + modelLogoPreview: modelLogo, apiUrl, requestUrl: apiUrl, description: row.description || '', @@ -376,7 +396,7 @@ export default { this.submitLoading = true try { const res = isEdit - ? await reqModelInfoConfigEdit({ ...params, id: this.editForm.id }) + ? await reqModelInfoConfigEdit(this.appendEditId(params)) : await reqModelInfoConfig(params) if (res && res.status) { this.$message.success(res.msg || (isEdit ? '模型信息编辑成功' : '模型信息添加成功')) @@ -392,7 +412,7 @@ export default { } }, buildSubmitParams() { - return { + const params = { provider: this.editForm.provider, model_name: this.editForm.modelName, display_name: this.editForm.displayName, @@ -408,10 +428,68 @@ export default { highlights: JSON.stringify(this.filterNameValueList(this.editForm.highlights)), description: this.editForm.description, experience: this.editForm.experience, + model_logo: this.editForm.modelLogo, api_url: this.editForm.apiUrl || this.editForm.requestUrl, curl_code: this.editForm.curlCode, python_code: this.editForm.pythonCode } + return this.editForm.modelLogoFile ? this.buildFormData(params) : params + }, + buildFormData(params) { + const formData = new FormData() + Object.keys(params).forEach(key => { + if (params[key] !== undefined && params[key] !== null) { + formData.append(key, params[key]) + } + }) + formData.set('model_logo', this.editForm.modelLogoFile) + return formData + }, + appendEditId(params) { + if (params instanceof FormData) { + params.append('id', this.editForm.id) + return params + } + return { ...params, id: this.editForm.id } + }, + triggerLogoUpload() { + if (this.$refs.modelLogoInput) { + this.$refs.modelLogoInput.click() + } + }, + handleLogoFileChange(event) { + const file = event.target.files && event.target.files[0] + if (!file) return + if (file.size > 5 * 1024 * 1024) { + this.$message.error('图片大小不能超过5MB') + event.target.value = '' + return + } + if (!file.type || !file.type.startsWith('image/')) { + this.$message.error('请选择图片文件') + event.target.value = '' + return + } + if (this.editForm.modelLogoPreview && this.editForm.modelLogoPreview.startsWith('blob:')) { + URL.revokeObjectURL(this.editForm.modelLogoPreview) + } + this.editForm.modelLogoFile = file + this.editForm.modelLogo = '' + this.editForm.modelLogoPreview = URL.createObjectURL(file) + this.editForm.logo = file.name + event.target.value = '' + }, + clearModelLogo() { + if (this.editForm.modelLogoPreview && this.editForm.modelLogoPreview.startsWith('blob:')) { + URL.revokeObjectURL(this.editForm.modelLogoPreview) + } + this.editForm.modelLogo = '' + this.editForm.modelLogoFile = null + this.editForm.modelLogoPreview = '' + this.editForm.logo = '' + if (this.$refs.modelLogoInput) { + this.$refs.modelLogoInput.value = '' + } }, buildCapabilities() { return [ @@ -775,6 +853,36 @@ export default { align-items: center; gap: 10px; min-height: 32px; +} + +.logo-file-input { + display: none; +} + +.logo-preview { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 42px; + width: 42px; + height: 42px; + overflow: hidden; + background: #f6f8fb; + border: 1px solid #edf1f7; + border-radius: 10px; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.logo-actions { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; span { color: #98a2b3; diff --git a/f/web-kboss/src/views/operation/modelInfoConfig/index.vue b/f/web-kboss/src/views/operation/modelInfoConfig/index.vue index faa41f0..5fde449 100644 --- a/f/web-kboss/src/views/operation/modelInfoConfig/index.vue +++ b/f/web-kboss/src/views/operation/modelInfoConfig/index.vue @@ -100,63 +100,7 @@ export default { modelType: '', provider: '' }, - tableData: [ - { - id: 'M006', - name: 'GPT-3.5-Turbo', - type: '文本生成', - provider: 'OpenAI', - logo: '-', - apiUrl: 'https://api.openai.com/v1/chat/completions', - description: 'GPT-3.5-Turbo高效对话模型,适合文本生成和问答。', - status: '待上架', - updatedAt: '2026-04-15' - }, - { - id: 'M007', - name: 'ViT-B/16', - type: '图像生成', - provider: 'Google', - logo: '-', - apiUrl: 'https://api.kaiyuan.cloud/v1/images', - description: 'ViT-B/16视觉Transformer模型,适合图像理解。', - status: '待上架', - updatedAt: '2026-04-14' - }, - { - id: 'M008', - name: 'Bark', - type: '语音合成', - provider: '开元云', - logo: '-', - apiUrl: 'https://api.kaiyuan.cloud/v1/audio', - description: 'Bark多语言语音合成模型,支持自然语音生成。', - status: '待上架', - updatedAt: '2026-04-13' - }, - { - id: 'M009', - name: 'CLIP', - type: '多模态', - provider: 'OpenAI', - logo: '-', - apiUrl: 'https://api.kaiyuan.cloud/v1/clip', - description: 'CLIP多模态模型,支持图文检索和理解。', - status: '待上架', - updatedAt: '2026-04-12' - }, - { - id: 'M011', - name: 'Falcon-180B', - type: '文本生成', - provider: 'Meta', - logo: '-', - apiUrl: 'https://api.kaiyuan.cloud/v1/chat', - description: 'Falcon-180B开源大语言模型,适合复杂文本任务。', - status: '待上架', - updatedAt: '2026-04-27' - } - ] + tableData: [] } }, computed: { diff --git a/f/web-kboss/src/views/product/allProduct/index.vue b/f/web-kboss/src/views/product/allProduct/index.vue index 675af6b..ae2749e 100644 --- a/f/web-kboss/src/views/product/allProduct/index.vue +++ b/f/web-kboss/src/views/product/allProduct/index.vue @@ -116,37 +116,65 @@ @click="goModelDetail(product)" >
-

{{ product.display_name || product.model_name }}

- - + + + {{ getProviderInitial(product.provider || product.display_name || product.model_name) }} + +
+

{{ product.display_name || product.model_name }}

+

{{ product.provider || product.model_name || '-' }}

+
-
- {{ product.model_type || '-' }} - {{ product.billing_method || '-' }} - {{ product.provider || '-' }} - {{ product.llmid }} + +
+
+ 输入 + ¥{{ formatTokenPrice(product.input_token_price) }} + 元/百万 tokens +
+
+ 输出 + ¥{{ formatTokenPrice(product.output_token_price) }} + 元/百万 tokens +
+
+ 缓存读 + ¥{{ formatTokenPrice(product.cache_hit_input_price) }} + 元/百万 tokens +
+
+ 缓存创建 + ¥{{ formatTokenPrice(product.input_token_price) }} + 元/百万 tokens +
-
- 输入 ¥{{ formatTokenPrice(product.input_token_price) }}/千Token - 输出 ¥{{ formatTokenPrice(product.output_token_price) }}/千Token + + + + -
- {{ getProviderInitial(product.provider) }} - {{ product.provider || '-' }} -
-
- + +
+
@@ -202,6 +230,20 @@ import { reqNavList, reqNewHomeSync, reqNewHomeFestival } from "@/api/newHome"; import { gotoYuanJingAPI } from '@/api/gotoYuanJing' import TopBox from '@/views/homePage/components/topBox/index.vue' +const getImageUrlPrefix = () => { + const origin = window.location.origin + if (origin.includes('localhost') || origin.includes('dev.opencomputing.cn')) { + return 'https://dev.opencomputing.cn/idfile?path=' + } + if (origin.includes('www.opencomputing.cn')) { + return 'https://www.opencomputing.cn/idfile?path=' + } + if (origin.includes('www.ncmatch.cn')) { + return 'https://www.ncmatch.cn/idfile?path=' + } + return `${origin}/idfile?path=` +} + export default { name: "ProductServicePage", components: { @@ -219,7 +261,8 @@ export default { tokenModelTypeList: [], tokenProviderList: [], tokenActiveModelType: '', - tokenActiveProvider: '' + tokenActiveProvider: "", + IMG_URL: getImageUrlPrefix(), }; }, computed: { @@ -359,6 +402,61 @@ export default { return provider ? provider.slice(0, 1) : 'M'; }, + getModelLogoUrl(modelLogo) { + if (!modelLogo) return ''; + if (/^https?:\/\//.test(modelLogo) || modelLogo.startsWith('data:') || modelLogo.startsWith('blob:')) { + return modelLogo; + } + return `${this.IMG_URL}${modelLogo}`; + }, + + normalizeTokenConfigList(value) { + if (!value) return []; + let parsedValue = value; + if (typeof value === 'string') { + try { + parsedValue = JSON.parse(value); + } catch (error) { + return []; + } + } + if (Array.isArray(parsedValue)) { + return parsedValue.map(item => ({ + name: item.name || item.label || item.key || '', + value: item.value || item.text || item.content || '' + })).filter(item => item.name || item.value); + } + if (parsedValue && typeof parsedValue === 'object') { + return Object.keys(parsedValue).map(key => ({ name: key, value: parsedValue[key] })); + } + return []; + }, + + getTokenFeatureTags(model) { + const capabilities = this.normalizeTokenConfigList(model.capabilities); + const highlights = this.normalizeTokenConfigList(model.highlights); + const inputType = capabilities.find(item => item.name === '输入类型'); + const outputType = capabilities.find(item => item.name === '输出类型'); + const highlight = highlights[0]; + return [ + { + text: inputType && inputType.value ? inputType.value : (model.model_type || '模型能力'), + icon: 'el-icon-view', + type: 'green' + }, + { + text: outputType && outputType.value ? outputType.value : (highlight && (highlight.value || highlight.name)) || '智能生成', + icon: 'el-icon-magic-stick', + type: 'purple' + }, + { + text: model.billing_method || '按量计费', + icon: 'el-icon-coin', + type: 'blue' + } + ].filter(item => item.text); + }, + // experience 为 1 才允许模型体验,0 时按钮置灰禁用。 isModelExperienceEnabled(model) { return Number(model && model.experience) === 1; @@ -1293,42 +1391,86 @@ export default { } .token-market-grid { - display: flex; - flex-wrap: wrap; - gap: 14px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 12px; + align-items: stretch; } .token-market-card { - flex: 0 0 calc((100% - 28px) / 3); + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; min-width: 0; - padding: 14px; + min-height: 224px; + padding: 12px; background: #ffffff; - border: 1px solid #edf1f7; - border-radius: 12px; - box-shadow: 0 10px 24px rgba(31, 45, 61, 0.05); + border: 1px solid #dfe7f3; + border-radius: 10px; + box-shadow: 0 8px 20px rgba(31, 45, 61, 0.08); cursor: pointer; transition: all 0.2s ease; &:hover { - border-color: #d7e4f5; + border-color: #8bbcff; transform: translateY(-2px); - box-shadow: 0 16px 32px rgba(31, 45, 61, 0.08); + box-shadow: 0 0 0 3px rgba(30, 111, 255, 0.1), 0 18px 38px rgba(30, 111, 255, 0.18); + + &:before { + opacity: 1; + } + + .token-hover-actions { + opacity: 1; + transform: translateY(0); + } + } + + &:before { + position: absolute; + inset: 0; + content: ''; + pointer-events: none; + opacity: 0; + background: radial-gradient(420px circle at 45% 0%, rgba(51, 133, 255, 0.16), transparent 42%); + transition: opacity 0.35s ease; } } .token-card-top { + position: relative; + z-index: 1; display: flex; align-items: center; - gap: 6px; - margin-bottom: 8px; + gap: 10px; + min-height: 38px; + margin-bottom: 10px; + + .token-title-group { + flex: 1; + min-width: 0; + } h3 { - flex: 1; margin: 0; - color: #1f2d3d; - font-size: 14px; + color: #111827; + font-size: 15px; font-weight: 700; - line-height: 1.35; + line-height: 1.25; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + p { + margin: 3px 0 0; + color: #6b7890; + font-size: 12px; + line-height: 1.2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } } @@ -1346,70 +1488,164 @@ export default { transform: rotate(90deg); } - .token-tags { - display: flex; - flex-wrap: wrap; - gap: 6px; - padding-bottom: 10px; + .token-price-grid { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px 12px; margin-bottom: 10px; - border-bottom: 1px dashed #d8e0ee; + } + + .token-price-item { + span, + em { + display: block; + color: #6b7890; + font-style: normal; + } span { - padding: 2px 6px; - color: #667085; + margin-bottom: 2px; font-size: 11px; - background: #f8fafc; - border: 1px solid #edf1f7; - border-radius: 6px; + } + + strong { + display: block; + color: #111827; + font-size: 15px; + font-weight: 800; + line-height: 1.1; + } + + em { + margin-top: 0; + font-size: 10px; } } - .token-price-line { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 8px; - color: #1f2937; - font-size: 12px; - font-weight: 600; + .token-description { + position: relative; + z-index: 1; + min-height: 58px; + margin-bottom: 12px; + color: #728096; + font-size: 13px; + line-height: 1.5; + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + line-clamp: 3; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; } - .token-meta { + .token-card-footer { + position: relative; + z-index: 1; display: flex; align-items: center; - gap: 6px; - margin-bottom: 10px; - color: #667085; - font-size: 12px; + justify-content: space-between; + gap: 8px; + min-width: 0; + margin-top: auto; + padding-top: 0; + } + + .token-feature-tags { + display: flex; + align-items: center; + flex-wrap: wrap; + flex: 1; + min-width: 0; + gap: 5px; } .token-provider-avatar { display: inline-flex; align-items: center; justify-content: center; - width: 16px; - height: 16px; + flex: 0 0 36px; + width: 34px; + height: 34px; color: #ffffff; - font-size: 10px; - background: #1e6fff; - border-radius: 4px; + font-size: 16px; + font-weight: 700; + // background: #7c3aed; + // border-radius: 8px; + overflow: hidden; + // box-shadow: 0 6px 16px rgba(124, 58, 237, 0.18); + + img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + } } - .token-actions { + .token-feature-tags span { + display: inline-flex; + align-items: center; + gap: 4px; + max-width: 104px; + height: 20px; + padding: 0 7px; + font-size: 11px; + border-radius: 999px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &.green { + color: #047857; + background: #dcfce7; + border: 1px solid #86efac; + } + + &.purple { + color: #4338ca; + background: #eef2ff; + border: 1px solid #c7d2fe; + } + + &.blue { + color: #1d4ed8; + background: #eff6ff; + border: 1px solid #bfdbfe; + } + } + + .token-hover-actions { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; display: flex; gap: 6px; + padding: 6px; + opacity: 0; + background: rgba(255, 255, 255, 0.94); + border-top: 1px solid #edf1f7; + backdrop-filter: blur(10px); + transform: translateY(100%); + transition: all 0.25s ease; button { display: inline-flex; align-items: center; + justify-content: center; + flex: 1; gap: 4px; height: 28px; - padding: 0 10px; + padding: 0 8px; color: #475467; font-size: 12px; + font-weight: 600; background: #ffffff; border: 1px solid #d8e0ee; - border-radius: 7px; + border-radius: 8px; cursor: pointer; transition: all 0.2s ease; @@ -1417,30 +1653,32 @@ export default { color: #1e6fff; border-color: #9ec5ff; background: #f4f8ff; + box-shadow: 0 0 0 3px rgba(30, 111, 255, 0.08); } } - .experience { - color: #4f46e5; - border-color: #c7d2fe; + .primary-action { + color: #ffffff; + background: #1e6fff; + border-color: #1e6fff; &:hover { - color: #4338ca; - border-color: #a5b4fc; - background: #eef2ff; + color: #ffffff; + background: #155eef; + border-color: #155eef; } &.disabled, &:disabled { - color: #98a2b3; + color: #ffffff; cursor: not-allowed; - background: #f3f4f6; - border-color: #e5e7eb; + background: #cbd5e1; + border-color: #cbd5e1; &:hover { - color: #98a2b3; - background: #f3f4f6; - border-color: #e5e7eb; + color: #ffffff; + background: #cbd5e1; + border-color: #cbd5e1; } } } @@ -1618,14 +1856,14 @@ export default { } } - .product-content .main-content .token-market-card { - flex-basis: calc((100% - 14px) / 2); + .product-content .main-content .token-market-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 768px) { - .product-content .main-content .token-market-card { - flex-basis: 100%; + .product-content .main-content .token-market-grid { + grid-template-columns: 1fr; } } } diff --git a/f/web-kboss/src/views/tokenUsage/index.vue b/f/web-kboss/src/views/tokenUsage/index.vue index e4dcfe9..4ca7265 100644 --- a/f/web-kboss/src/views/tokenUsage/index.vue +++ b/f/web-kboss/src/views/tokenUsage/index.vue @@ -17,6 +17,7 @@
--> +