fix:修改仪表盘页面和授权管理列表页面
No files matched your search
@@ -1,5 +1,16 @@
|
|||||||
import Axios from './https'
|
import Axios from './https'
|
||||||
|
|
||||||
|
// 授权统计信息类型
|
||||||
|
export interface AuthorizationStats {
|
||||||
|
active_licenses?: number; // 已激活许可证数量
|
||||||
|
expired_auth_codes?: number; // 已过期授权码数量
|
||||||
|
expired_licenses?: number; // 已过期许可证数量
|
||||||
|
expiring_soon_auth_codes?: number; // 30日内即将到期授权码数量
|
||||||
|
inactive_licenses?: number; // 未激活许可证数量
|
||||||
|
total_auth_codes?: number; // 总授权码数量
|
||||||
|
total_licenses?: number; // 总许可证数量
|
||||||
|
}
|
||||||
|
|
||||||
// 客户接口类型定义 - 完全使用接口提供的字段名称
|
// 客户接口类型定义 - 完全使用接口提供的字段名称
|
||||||
export interface Customer {
|
export interface Customer {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -23,6 +34,7 @@ export interface Customer {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
|
authorization_stats?: AuthorizationStats; // 授权统计信息
|
||||||
}
|
}
|
||||||
|
|
||||||
// 客户查询请求参数
|
// 客户查询请求参数
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 416 B After Width: | Height: | Size: 479 B |
|
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 481 B |
|
Before Width: | Height: | Size: 487 B After Width: | Height: | Size: 484 B |
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 493 B After Width: | Height: | Size: 492 B |
|
Before Width: | Height: | Size: 389 B After Width: | Height: | Size: 444 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 418 B |
|
After Width: | Height: | Size: 89 KiB |
@@ -136,7 +136,7 @@
|
|||||||
"table": {
|
"table": {
|
||||||
"code": "License Code",
|
"code": "License Code",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"deploymentType": "Deployment Type",
|
"activationProgress": "Activation Progress",
|
||||||
"endDate": "Expiry Time",
|
"endDate": "Expiry Time",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"operation": "Operation",
|
"operation": "Operation",
|
||||||
@@ -562,13 +562,13 @@
|
|||||||
"createTime": "Create Time",
|
"createTime": "Create Time",
|
||||||
"updater": "Updater",
|
"updater": "Updater",
|
||||||
"updateTime": "Update Time",
|
"updateTime": "Update Time",
|
||||||
"totalLicenses": "Total Licenses",
|
"totalLicenses": "Total Authorization Codes",
|
||||||
"activeLicenses": "Expired Licenses",
|
"activeLicenses": "Expired Authorization Codes",
|
||||||
"recentLicense": "Licenses Expiring Within 30 Days",
|
"recentLicense": "Authorization Codes Expiring Within 30 Days",
|
||||||
"expiringSoon": "Total License Count",
|
"expiringSoon": "Total Licenses",
|
||||||
"expiringSoonTime": "Activated License Count",
|
"expiringSoonTime": "Activated Licenses",
|
||||||
"expiredLicenses": "Unactivated License Count",
|
"expiredLicenses": "Expired Licenses",
|
||||||
"expiredTime": "Expired License Count",
|
"expiredTime": "Inactivated Licenses",
|
||||||
"noData": "--",
|
"noData": "--",
|
||||||
"onlineLicense": "Online License",
|
"onlineLicense": "Online License",
|
||||||
"customerNotExist": "Customer data does not exist"
|
"customerNotExist": "Customer data does not exist"
|
||||||
|
|||||||
@@ -136,7 +136,7 @@
|
|||||||
"table": {
|
"table": {
|
||||||
"code": "ライセンスコード",
|
"code": "ライセンスコード",
|
||||||
"status": "ステータス",
|
"status": "ステータス",
|
||||||
"deploymentType": "デプロイメントタイプ",
|
"activationProgress": "アクティベーション進捗",
|
||||||
"endDate": "有効期限",
|
"endDate": "有効期限",
|
||||||
"description": "説明",
|
"description": "説明",
|
||||||
"operation": "操作",
|
"operation": "操作",
|
||||||
@@ -562,13 +562,13 @@
|
|||||||
"createTime": "作成日時",
|
"createTime": "作成日時",
|
||||||
"updater": "更新者",
|
"updater": "更新者",
|
||||||
"updateTime": "更新日時",
|
"updateTime": "更新日時",
|
||||||
"totalLicenses": "総ライセンス数",
|
"totalLicenses": "総認証コード数",
|
||||||
"activeLicenses": "期限切れライセンス",
|
"activeLicenses": "期限切れ認証コード",
|
||||||
"recentLicense": "30日以内に期限切れするライセンスコード数量",
|
"recentLicense": "30日以内に期限切れする認証コード数量",
|
||||||
"expiringSoon": "総ライセンス数量",
|
"expiringSoon": "総ライセンス数量",
|
||||||
"expiringSoonTime": "アクティベート済みライセンス数量",
|
"expiringSoonTime": "アクティベート済みライセンス数量",
|
||||||
"expiredLicenses": "未アクティベートライセンス数量",
|
"expiredLicenses": "期限切れライセンス数量",
|
||||||
"expiredTime": "期限切れライセンス数量",
|
"expiredTime": "未アクティベートライセンス数量",
|
||||||
"noData": "--",
|
"noData": "--",
|
||||||
"onlineLicense": "オンラインライセンス",
|
"onlineLicense": "オンラインライセンス",
|
||||||
"customerNotExist": "顧客データが存在しません"
|
"customerNotExist": "顧客データが存在しません"
|
||||||
|
|||||||
@@ -26,13 +26,13 @@
|
|||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "仪表盘",
|
"title": "仪表盘",
|
||||||
"stats": {
|
"stats": {
|
||||||
"totalCustomers": "客户总数",
|
"totalCustomers": "总授权码",
|
||||||
"totalLicenses": "总授权",
|
"totalLicenses": "活跃许可证",
|
||||||
"activeLicenses": "有效授权",
|
"activeLicenses": "即将过期",
|
||||||
"expired": "过期授权",
|
"expired": "异常警告",
|
||||||
"onlineClients": "在线客户端数量",
|
"onlineClients": "增长率",
|
||||||
"expiringSoon": "到期提醒",
|
"expiringSoon": "授权码增长率",
|
||||||
"authSuccessRate24h": "近24小时授权验证成功率"
|
"authSuccessRate24h": "许可证增长率"
|
||||||
},
|
},
|
||||||
"recentLicenses": {
|
"recentLicenses": {
|
||||||
"title": "最近授权",
|
"title": "最近授权",
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"table": {
|
"table": {
|
||||||
"code": "授权码",
|
"code": "授权码",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"deploymentType": "部署类型",
|
"activationProgress": "激活进度",
|
||||||
"endDate": "到期时间",
|
"endDate": "到期时间",
|
||||||
"description": "备注",
|
"description": "备注",
|
||||||
"operation": "操作",
|
"operation": "操作",
|
||||||
@@ -567,8 +567,8 @@
|
|||||||
"recentLicense": "30日内即将到期授权码数量 ",
|
"recentLicense": "30日内即将到期授权码数量 ",
|
||||||
"expiringSoon": "总许可证数量",
|
"expiringSoon": "总许可证数量",
|
||||||
"expiringSoonTime": "已激活许可证数量",
|
"expiringSoonTime": "已激活许可证数量",
|
||||||
"expiredLicenses": "未激活许可证数量",
|
"expiredLicenses": "已过期许可证数量",
|
||||||
"expiredTime": "已过期许可证数量",
|
"expiredTime": "未激活许可证数量",
|
||||||
"noData": "--",
|
"noData": "--",
|
||||||
"onlineLicense": "在线授权",
|
"onlineLicense": "在线授权",
|
||||||
"customerNotExist": "客户数据不存在"
|
"customerNotExist": "客户数据不存在"
|
||||||
|
|||||||
@@ -2,147 +2,157 @@
|
|||||||
* @Author: 13895237362 2205451508@qq.com
|
* @Author: 13895237362 2205451508@qq.com
|
||||||
* @Date: 2025-08-20 16:00:00
|
* @Date: 2025-08-20 16:00:00
|
||||||
* @LastEditors: 13895237362 2205451508@qq.com
|
* @LastEditors: 13895237362 2205451508@qq.com
|
||||||
* @LastEditTime: 2025-09-03 11:41:35
|
* @LastEditTime: 2025-11-03 15:44:00
|
||||||
* @FilePath: /frontend/src/views/Customers/CustomerView.vue
|
* @FilePath: /frontend/src/views/Customers/CustomerView.vue
|
||||||
* @Description: 客户查看组件
|
* @Description: 客户查看组件
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<!-- 顶部横向区域 - 面包屑和返回按钮 -->
|
<div class="customer-view-container">
|
||||||
<div class="top-section">
|
<!-- 顶部横向区域 - 面包屑和返回按钮 -->
|
||||||
<!-- 面包屑导航 -->
|
<div class="top-section">
|
||||||
<div class="breadcrumb">
|
<!-- 面包屑导航 -->
|
||||||
<span class="breadcrumb-item">{{ t('customers.breadcrumb.customerManagement') }}</span>
|
<div class="breadcrumb">
|
||||||
<span class="breadcrumb-separator">></span>
|
<span class="breadcrumb-item">{{ t('customers.breadcrumb.customerManagement') }}</span>
|
||||||
<span class="breadcrumb-current">{{ t('customers.viewCustomer') }}</span>
|
<span class="breadcrumb-separator">></span>
|
||||||
</div>
|
<span class="breadcrumb-current">{{ t('customers.viewCustomer') }}</span>
|
||||||
|
</div>
|
||||||
<!-- 返回按钮 -->
|
|
||||||
<div class="form-actions">
|
|
||||||
<el-button @click="handleBack">{{ t('customers.view.back') }}</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 主内容区域 -->
|
<!-- 返回按钮 -->
|
||||||
<div class="customer-view" v-loading="loading">
|
<div class="form-actions">
|
||||||
<!-- 基本信息卡片 -->
|
<el-button @click="handleBack">{{ t('customers.view.back') }}</el-button>
|
||||||
<div class="info-card basic-info">
|
|
||||||
<h3 class="section-title">{{ t('customers.form.basicInfo') }}</h3>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.form.customerName') }}:</span>
|
|
||||||
<span class="value">{{ customerData.name || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.form.customerType') }}:</span>
|
|
||||||
<span class="value">{{ getCustomerTypeLabel(customerData.type) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.form.customerLevel') }}:</span>
|
|
||||||
<span class="value">{{ getCustomerLevelLabel(customerData.level) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.form.contactPerson') }}:</span>
|
|
||||||
<span class="value">{{ customerData.contact || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.form.email') }}:</span>
|
|
||||||
<span class="value">{{ customerData.email || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.form.status') }}:</span>
|
|
||||||
<span class="value">{{ getStatusLabel(customerData.status) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.form.phone') }}:</span>
|
|
||||||
<span class="value">{{ customerData.phone || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item full-width">
|
|
||||||
<span class="label">{{ t('customers.form.address') }}:</span>
|
|
||||||
<span class="value">{{ customerData.address || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 商业信息卡片 -->
|
<!-- 主内容区域 -->
|
||||||
<div class="info-card business-info">
|
<div class="customer-view" v-loading="loading">
|
||||||
<h3 class="section-title">{{ t('customers.form.businessInfo') }}</h3>
|
<!-- 基本信息卡片 -->
|
||||||
<div class="info-grid">
|
<div class="info-card basic-info">
|
||||||
<div class="info-item">
|
<h3 class="section-title">{{ t('customers.form.basicInfo') }}</h3>
|
||||||
<span class="label">{{ t('customers.form.companySize') }}:</span>
|
<div class="info-grid">
|
||||||
<span class="value">{{ enumLabels.companySize || t('customers.view.noData') }}</span>
|
<div class="info-item">
|
||||||
</div>
|
<span class="label">{{ t('customers.form.customerName') }}:</span>
|
||||||
<div class="info-item full-width">
|
<span class="value">{{ customerData.name || t('customers.view.noData') }}</span>
|
||||||
<span class="label">{{ t('customers.form.preferredLicense') }}:</span>
|
|
||||||
<span class="value">{{ customerData.preferredLicense || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item full-width">
|
|
||||||
<span class="label">{{ t('customers.form.description') }}:</span>
|
|
||||||
<span class="value description">{{ customerData.description || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 状态信息卡片 -->
|
|
||||||
<div class="info-card status-info">
|
|
||||||
<h3 class="section-title">{{ t('customers.view.statusInfo') }}</h3>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-item inline-group full-width">
|
|
||||||
<div class="inline-pair">
|
|
||||||
<span class="label">{{ t('customers.view.creator') }}:</span>
|
|
||||||
<span class="value">{{ customerData.statusRecords?.[0]?.user || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-pair">
|
<div class="info-item">
|
||||||
<span class="label">{{ t('customers.view.createTime') }}:</span>
|
<span class="label">{{ t('customers.form.customerType') }}:</span>
|
||||||
<span class="value">{{ formatDate(customerData.statusRecords?.[0]?.time) }}</span>
|
<span class="value">{{ getCustomerTypeLabel(customerData.type) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="info-item">
|
||||||
<div class="info-item inline-group full-width">
|
<span class="label">{{ t('customers.form.customerLevel') }}:</span>
|
||||||
<div class="inline-pair">
|
<span class="value">{{ getCustomerLevelLabel(customerData.level) }}</span>
|
||||||
<span class="label">{{ t('customers.view.updater') }}:</span>
|
|
||||||
<span class="value">{{ customerData.statusRecords?.[1]?.user || t('customers.view.noData') }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-pair">
|
<div class="info-item">
|
||||||
<span class="label">{{ t('customers.view.updateTime') }}:</span>
|
<span class="label">{{ t('customers.form.contactPerson') }}:</span>
|
||||||
<span class="value">{{ formatDate(customerData.statusRecords?.[1]?.time) }}</span>
|
<span class="value">{{ customerData.contact || t('customers.view.noData') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('customers.form.email') }}:</span>
|
||||||
|
<span class="value">{{ customerData.email || t('customers.view.noData') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('customers.form.status') }}:</span>
|
||||||
|
<span class="value">{{ getStatusLabel(customerData.status) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('customers.form.phone') }}:</span>
|
||||||
|
<span class="value">{{ customerData.phone || t('customers.view.noData') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item full-width">
|
||||||
|
<span class="label">{{ t('customers.form.address') }}:</span>
|
||||||
|
<span class="value">{{ customerData.address || t('customers.view.noData') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 授权统计卡片 -->
|
<!-- 商业信息卡片 -->
|
||||||
<div class="info-card license-stats">
|
<div class="info-card business-info">
|
||||||
<h3 class="section-title">{{ t('customers.view.licenseStats') }}</h3>
|
<h3 class="section-title">{{ t('customers.form.businessInfo') }}</h3>
|
||||||
<div class="info-grid">
|
<div class="info-grid">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="label">{{ t('customers.view.totalLicenses') }}:</span>
|
<span class="label">{{ t('customers.form.companySize') }}:</span>
|
||||||
<span class="value">{{ customerData.totalLicenses || 0 }}</span>
|
<span class="value">{{ enumLabels.companySize || t('customers.view.noData') }}</span>
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.view.activeLicenses') }}:</span>
|
|
||||||
<span class="value">{{ customerData.activeLicenses || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">{{ t('customers.view.recentLicense') }}:</span>
|
|
||||||
<span class="value">{{ formatDate(customerData.latestLicenseTime) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item inline-group full-width">
|
|
||||||
<div class="inline-pair">
|
|
||||||
<span class="label">{{ t('customers.view.expiringSoon') }}:</span>
|
|
||||||
<span class="value">{{ customerData.expiringSoonLicenses || 0 }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-pair">
|
<div class="info-item full-width">
|
||||||
<span class="label">{{ t('customers.view.expiringSoonTime') }}:</span>
|
<span class="label">{{ t('customers.form.preferredLicense') }}:</span>
|
||||||
<span class="value">{{ formatDate(customerData.expiringSoonTime) }}</span>
|
<span class="value">{{
|
||||||
|
customerData.preferredLicense || t('customers.view.noData')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item full-width">
|
||||||
|
<span class="label">{{ t('customers.form.description') }}:</span>
|
||||||
|
<span class="value description">{{
|
||||||
|
customerData.description || t('customers.view.noData')
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item inline-group full-width">
|
</div>
|
||||||
<div class="inline-pair">
|
|
||||||
<span class="label">{{ t('customers.view.expiredLicenses') }}:</span>
|
<!-- 状态信息卡片 -->
|
||||||
<span class="value">{{ customerData.expiredLicenses || 0 }}</span>
|
<div class="info-card status-info">
|
||||||
|
<h3 class="section-title">{{ t('customers.view.statusInfo') }}</h3>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item inline-group full-width">
|
||||||
|
<div class="inline-pair">
|
||||||
|
<span class="label">{{ t('customers.view.creator') }}:</span>
|
||||||
|
<span class="value">{{
|
||||||
|
customerData.statusRecords?.[0]?.user || t('customers.view.noData')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-pair">
|
||||||
|
<span class="label">{{ t('customers.view.createTime') }}:</span>
|
||||||
|
<span class="value">{{ formatDate(customerData.statusRecords?.[0]?.time) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-pair">
|
<div class="info-item inline-group full-width">
|
||||||
<span class="label">{{ t('customers.view.expiredTime') }}:</span>
|
<div class="inline-pair">
|
||||||
<span class="value">{{ formatDate(customerData.expiredTime) }}</span>
|
<span class="label">{{ t('customers.view.updater') }}:</span>
|
||||||
|
<span class="value">{{
|
||||||
|
customerData.statusRecords?.[1]?.user || t('customers.view.noData')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-pair">
|
||||||
|
<span class="label">{{ t('customers.view.updateTime') }}:</span>
|
||||||
|
<span class="value">{{ formatDate(customerData.statusRecords?.[1]?.time) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 授权统计卡片 -->
|
||||||
|
<div class="info-card license-stats">
|
||||||
|
<h3 class="section-title">{{ t('customers.view.licenseStats') }}</h3>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('customers.view.totalLicenses') }}:</span>
|
||||||
|
<span class="value">{{ customerData.total_auth_codes || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('customers.view.activeLicenses') }}:</span>
|
||||||
|
<span class="value">{{ customerData.expired_auth_codes || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('customers.view.recentLicense') }}:</span>
|
||||||
|
<span class="value">{{ customerData.expiring_soon_auth_codes || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item inline-group full-width">
|
||||||
|
<div class="inline-pair">
|
||||||
|
<span class="label">{{ t('customers.view.expiringSoon') }}:</span>
|
||||||
|
<span class="value">{{ customerData.total_licenses || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-pair">
|
||||||
|
<span class="label">{{ t('customers.view.expiringSoonTime') }}:</span>
|
||||||
|
<span class="value">{{ customerData.active_licenses || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item inline-group full-width">
|
||||||
|
<div class="inline-pair">
|
||||||
|
<span class="label">{{ t('customers.view.expiredLicenses') }}:</span>
|
||||||
|
<span class="value">{{ customerData.expired_licenses || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-pair">
|
||||||
|
<span class="label">{{ t('customers.view.expiredTime') }}:</span>
|
||||||
|
<span class="value">{{ customerData.inactive_licenses }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,13 +186,13 @@ interface CustomerViewData {
|
|||||||
preferredLicense: string
|
preferredLicense: string
|
||||||
description: string
|
description: string
|
||||||
statusRecords?: StatusRecord[]
|
statusRecords?: StatusRecord[]
|
||||||
totalLicenses?: number
|
active_licenses: number
|
||||||
activeLicenses?: number
|
expired_auth_codes: number
|
||||||
expiredLicenses?: number
|
expired_licenses: number
|
||||||
expiringSoonLicenses?: number
|
expiring_soon_auth_codes: number
|
||||||
latestLicenseTime?: string
|
inactive_licenses: number
|
||||||
expiringSoonTime?: string
|
total_auth_codes: number
|
||||||
expiredTime?: string
|
total_licenses: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -208,18 +218,18 @@ const enumLabels = ref({
|
|||||||
// 获取客户详情数据
|
// 获取客户详情数据
|
||||||
const fetchCustomerDetail = async () => {
|
const fetchCustomerDetail = async () => {
|
||||||
if (!props.customerId) return
|
if (!props.customerId) return
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 调用真实API接口
|
// 调用真实API接口
|
||||||
const response = await getCustomerDetail(props.customerId)
|
const response = await getCustomerDetail(props.customerId)
|
||||||
const customer = response.data
|
const customer = response.data
|
||||||
|
|
||||||
if (!customer) {
|
if (!customer) {
|
||||||
ElMessage.error(t('customers.view.customerNotExist'))
|
ElMessage.error(t('customers.view.customerNotExist'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用原始枚举值作为显示值 (移除映射功能)
|
// 使用原始枚举值作为显示值 (移除映射功能)
|
||||||
enumLabels.value = {
|
enumLabels.value = {
|
||||||
customerType: customer.customer_type_display || customer.customer_type,
|
customerType: customer.customer_type_display || customer.customer_type,
|
||||||
@@ -227,7 +237,7 @@ const fetchCustomerDetail = async () => {
|
|||||||
status: customer.status_display || customer.status,
|
status: customer.status_display || customer.status,
|
||||||
companySize: customer.company_size_display || customer.company_size || ''
|
companySize: customer.company_size_display || customer.company_size || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换API数据为组件需要的格式
|
// 转换API数据为组件需要的格式
|
||||||
customerData.value = {
|
customerData.value = {
|
||||||
name: customer.customer_name,
|
name: customer.customer_name,
|
||||||
@@ -253,14 +263,14 @@ const fetchCustomerDetail = async () => {
|
|||||||
time: customer.updated_at
|
time: customer.updated_at
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
// 授权统计数据等待后端接口
|
// 授权统计数据
|
||||||
totalLicenses: 0,
|
total_auth_codes: customer.authorization_stats?.total_auth_codes || 0, // 总授权码数量
|
||||||
activeLicenses: 0,
|
expired_auth_codes: customer.authorization_stats?.expired_auth_codes || 0, // 已过期授权码数量
|
||||||
expiredLicenses: 0,
|
expiring_soon_auth_codes: customer.authorization_stats?.expiring_soon_auth_codes || 0, // 30日内即将到期授权码数量
|
||||||
expiringSoonLicenses: 0,
|
total_licenses: customer.authorization_stats?.total_licenses || 0, // 总许可证数量
|
||||||
latestLicenseTime: undefined,
|
active_licenses: customer.authorization_stats?.active_licenses || 0, // 已激活许可证数量
|
||||||
expiringSoonTime: undefined,
|
expired_licenses: customer.authorization_stats?.expired_licenses || 0, // 已过期许可证数量
|
||||||
expiredTime: undefined
|
inactive_licenses: customer.authorization_stats?.inactive_licenses || 0 // 未激活许可证数量
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Get customer detail failed:', error)
|
console.error('Get customer detail failed:', error)
|
||||||
@@ -297,8 +307,6 @@ const getStatusLabel = (status: string | undefined) => {
|
|||||||
return enumLabels.value.status || t('customers.view.noData')
|
return enumLabels.value.status || t('customers.view.noData')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 组件挂载时获取数据
|
// 组件挂载时获取数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchCustomerDetail()
|
fetchCustomerDetail()
|
||||||
@@ -306,6 +314,12 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 容器 */
|
||||||
|
.customer-view-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
/* 顶部横向区域 */
|
/* 顶部横向区域 */
|
||||||
.top-section {
|
.top-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -439,7 +453,6 @@ onMounted(() => {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Element Plus 按钮样式 */
|
/* Element Plus 按钮样式 */
|
||||||
:deep(.el-button) {
|
:deep(.el-button) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -460,17 +473,17 @@ onMounted(() => {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-grid {
|
.info-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-item {
|
.info-item {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
@@ -481,9 +494,9 @@ onMounted(() => {
|
|||||||
.info-card {
|
.info-card {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customer-view {
|
.customer-view {
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: 13895237362 2205451508@qq.com
|
* @Author: 13895237362 2205451508@qq.com
|
||||||
* @Date: 2025-08-12 00:00:00
|
* @Date: 2025-08-12 00:00:00
|
||||||
* @LastEditors: 13895237362 2205451508@qq.com
|
* @LastEditors: 13895237362 2205451508@qq.com
|
||||||
* @LastEditTime: 2025-10-30 14:45:23
|
* @LastEditTime: 2025-11-03 11:19:50
|
||||||
* @FilePath: /frontend/src/views/Customers/index.vue
|
* @FilePath: /frontend/src/views/Customers/index.vue
|
||||||
* @Description: 客户管理页面
|
* @Description: 客户管理页面
|
||||||
-->
|
-->
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
{{ formatDateShort(scope.row.created_at) }}
|
{{ formatDateShort(scope.row.created_at) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="t('customers.table.operation')" fixed="right" class-name="action-column" align="center">
|
<el-table-column :label="t('customers.table.operation')" fixed="right" class-name="action-column" align="center" :width="320">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button class="action-btn primary" @click="handleViewLicense(scope.row)">{{ t('customers.actions.viewLicense') }}</button>
|
<button class="action-btn primary" @click="handleViewLicense(scope.row)">{{ t('customers.actions.viewLicense') }}</button>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: 13895237362 2205451508@qq.com
|
* @Author: 13895237362 2205451508@qq.com
|
||||||
* @Date: 2025-08-01 09:32:42
|
* @Date: 2025-08-01 09:32:42
|
||||||
* @LastEditors: 13895237362 2205451508@qq.com
|
* @LastEditors: 13895237362 2205451508@qq.com
|
||||||
* @LastEditTime: 2025-10-28 17:28:18
|
* @LastEditTime: 2025-11-04 15:19:44
|
||||||
* @FilePath: /frontend/src/views/Dashboard.vue
|
* @FilePath: /frontend/src/views/Dashboard.vue
|
||||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||||
-->
|
-->
|
||||||
@@ -27,10 +27,6 @@
|
|||||||
<div class="stat-label">{{ stat.label }}</div>
|
<div class="stat-label">{{ stat.label }}</div>
|
||||||
<div class="stat-value-row">
|
<div class="stat-value-row">
|
||||||
<div class="stat-value">{{ stat.value }}</div>
|
<div class="stat-value">{{ stat.value }}</div>
|
||||||
<div class="stat-trend" v-if="stat.trend">
|
|
||||||
<img :src="upIcon" alt="trend up" class="trend-icon" width="16" height="16" />
|
|
||||||
<span class="trend-value">{{ stat.trend }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,11 +110,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Layout from '@/components/common/layout/Layout.vue'
|
import Layout from '@/components/common/layout/Layout.vue'
|
||||||
import LicenseTrendChart from '@/components/charts/LicenseTrendChart.vue'
|
import LicenseTrendChart from '@/components/charts/LicenseTrendChart.vue'
|
||||||
import { getRecentAuthorizations, type RecentAuthorizationItem } from '@/api/dashboard'
|
import { getRecentAuthorizations, type RecentAuthorizationItem, getOverviewStats} from '@/api/dashboard'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { formatDate } from '@/utils/date'
|
import { formatDate } from '@/utils/date'
|
||||||
|
|
||||||
@@ -130,62 +126,20 @@ import icon4 from '@/assets/icons/dashboard/icon4.png'
|
|||||||
import icon5 from '@/assets/icons/dashboard/icon5.png'
|
import icon5 from '@/assets/icons/dashboard/icon5.png'
|
||||||
import icon6 from '@/assets/icons/dashboard/icon6.png'
|
import icon6 from '@/assets/icons/dashboard/icon6.png'
|
||||||
import icon7 from '@/assets/icons/dashboard/icon7.png'
|
import icon7 from '@/assets/icons/dashboard/icon7.png'
|
||||||
import upIcon from '@/assets/icons/up-icon.svg'
|
|
||||||
|
|
||||||
// 使用国际化
|
// 使用国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 统计卡片数据
|
// 统计卡片数据(接口动态获取 value,其余静态配置)
|
||||||
const statsData = computed(() => [
|
const statsData = ref([
|
||||||
{
|
|
||||||
id: 1,
|
{ id: 1, key: 'total_auth_codes', value: '', label: t('dashboard.stats.totalCustomers'), icon: icon1 },//总授权码
|
||||||
value: '100',
|
{ id: 2, key: 'active_licenses', value: '', label: t('dashboard.stats.totalLicenses'), icon: icon2 },//活跃许可证
|
||||||
label: t('dashboard.stats.totalCustomers'),
|
{ id: 3, key: 'expiring_soon', value: '', label: t('dashboard.stats.activeLicenses'), icon: icon3 },//即将过期
|
||||||
trend: '+1.5%',
|
{ id: 4, key: 'abnormal_alerts', value: '', label: t('dashboard.stats.expired'), icon: icon4 },//异常警告
|
||||||
icon: icon1
|
{ id: 5, key: 'growth_rate', value: '', label: t('dashboard.stats.onlineClients'), icon: icon5 },//增长率
|
||||||
},
|
{ id: 6, key: 'auth_codes', value: '', label: t('dashboard.stats.expiringSoon'), icon: icon6 },//授权码增长率
|
||||||
{
|
{ id: 7, key: 'licenses', value: '', label: t('dashboard.stats.authSuccessRate24h'), icon: icon7 }//许可证增长率
|
||||||
id: 2,
|
|
||||||
value: '100',
|
|
||||||
label: t('dashboard.stats.totalLicenses'),
|
|
||||||
trend: '+8.5%',
|
|
||||||
icon: icon2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
value: '98',
|
|
||||||
label: t('dashboard.stats.activeLicenses'),
|
|
||||||
trend: '+15.6%',
|
|
||||||
icon: icon3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
value: '2',
|
|
||||||
label: t('dashboard.stats.expired'),
|
|
||||||
trend: '+8.6%',
|
|
||||||
icon: icon4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
value: '600',
|
|
||||||
label: t('dashboard.stats.onlineClients'),
|
|
||||||
trend: '',
|
|
||||||
icon: icon5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
value: '50',
|
|
||||||
label: t('dashboard.stats.expiringSoon'),
|
|
||||||
trend: '',
|
|
||||||
icon: icon6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
value: '98%',
|
|
||||||
label: t('dashboard.stats.authSuccessRate24h'),
|
|
||||||
trend: '',
|
|
||||||
icon: icon7
|
|
||||||
}
|
|
||||||
])
|
])
|
||||||
|
|
||||||
// 最近授权数据 - 响应式数据
|
// 最近授权数据 - 响应式数据
|
||||||
@@ -221,9 +175,30 @@ const fetchRecentAuthorizations = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取仪表盘统计数据
|
||||||
|
const fetchDashboardStats = async () => {
|
||||||
|
try {
|
||||||
|
const resp = await getOverviewStats()
|
||||||
|
const data = resp.data
|
||||||
|
statsData.value.forEach(item => {
|
||||||
|
// 处理直接映射的字段
|
||||||
|
if (data[item.key] !== undefined && typeof data[item.key] !== 'object') {
|
||||||
|
item.value = data[item.key]
|
||||||
|
}
|
||||||
|
// 处理嵌套在 growth_rate 中的字段
|
||||||
|
if (data.growth_rate && data.growth_rate[item.key] !== undefined) {
|
||||||
|
item.value = data.growth_rate[item.key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('获取仪表盘统计数据失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 组件挂载时获取数据
|
// 组件挂载时获取数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('Dashboard 组件已挂载')
|
console.log('Dashboard 组件已挂载')
|
||||||
|
fetchDashboardStats()
|
||||||
fetchRecentAuthorizations()
|
fetchRecentAuthorizations()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -282,7 +257,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.stats-card {
|
.stats-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
gap: 0.83vw; /* 16px/1920 = 0.83vw */
|
gap: 0.83vw; /* 16px/1920 = 0.83vw */
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -306,9 +281,13 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stat-icon {
|
.stat-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.icon-circle {
|
.icon-circle {
|
||||||
width: 2.08vw; /* 40px/1920 = 2.08vw */
|
height: 100%;
|
||||||
height: 2.08vw; /* 40px/1920 = 2.08vw */
|
aspect-ratio: 1; /* 保持圆形 */
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -316,8 +295,8 @@ onMounted(() => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.stat-icon-img {
|
.stat-icon-img {
|
||||||
width: 0.83vw; /* 16px/1920 = 0.83vw */
|
width: 50%; /* icon-circle 的一半 */
|
||||||
height: 0.83vw; /* 16px/1920 = 0.83vw */
|
height: 50%; /* icon-circle 的一半 */
|
||||||
filter: brightness(0) invert(1) opacity(0.9); /* 将图标转为白色半透明 */
|
filter: brightness(0) invert(1) opacity(0.9); /* 将图标转为白色半透明 */
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
@@ -327,6 +306,10 @@ onMounted(() => {
|
|||||||
.stat-content {
|
.stat-content {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
min-width: 4.17vw; /* 80px/1920 = 4.17vw */
|
min-width: 4.17vw; /* 80px/1920 = 4.17vw */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 0.83vw; /* 16px/1920 = 0.83vw */
|
font-size: 0.83vw; /* 16px/1920 = 0.83vw */
|
||||||
@@ -349,35 +332,14 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-trend {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.21vw; /* 4px/1920 = 0.21vw */
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
|
|
||||||
.trend-icon {
|
|
||||||
filter: brightness(0) invert(1) opacity(0.8);
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trend-value {
|
|
||||||
font-size: 0.73vw; /* 14px/1920 = 0.73vw */
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 紧凑型卡片样式(专门针对到期提醒)
|
// 紧凑型卡片样式(专门针对到期提醒)
|
||||||
.stats-card.compact-card {
|
.stats-card.compact-card {
|
||||||
.stat-content {
|
min-width: 10vw !important; /* 48px/1920 = 2.5vw */
|
||||||
min-width: 2.5vw !important; /* 48px/1920 = 2.5vw */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 大型卡片样式(专门针对近24小时授权验证成功率)
|
// 大型卡片样式(专门针对近24小时授权验证成功率)
|
||||||
.stats-card.large-card {
|
.stats-card.large-card {
|
||||||
.stat-content {
|
min-width: 10vw !important; /* 115px/1920 = 6vw */
|
||||||
min-width: 6vw !important; /* 115px/1920 = 6vw */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内容区域 - 默认上下布局,充满屏幕宽度和高度
|
// 内容区域 - 默认上下布局,充满屏幕宽度和高度
|
||||||
@@ -534,13 +496,19 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.stat-icon .icon-circle {
|
.stat-icon .icon-circle {
|
||||||
width: 36px; /* 移动端固定大小 */
|
height: 100%;
|
||||||
height: 36px;
|
aspect-ratio: 1; /* 保持圆形 */
|
||||||
|
min-width: 36px; /* 移动端最小大小 */
|
||||||
|
min-height: 36px;
|
||||||
|
|
||||||
.stat-icon-img {
|
.stat-icon-img {
|
||||||
width: 14px;
|
width: 50%; /* icon-circle 的一半 */
|
||||||
height: 14px;
|
height: 50%; /* icon-circle 的一半 */
|
||||||
filter: brightness(0) invert(1) opacity(0.9); /* 将图标转为白色半透明 */
|
filter: brightness(0) invert(1) opacity(0.9); /* 将图标转为白色半透明 */
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
@@ -563,10 +531,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-trend .trend-value {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-section {
|
.content-section {
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<div class="license-form">
|
<div class="license-form">
|
||||||
<h3 class="section-title">{{ t('pages.licenses.form.sections.basicInfo') }}</h3>
|
<h3 class="section-title">{{ t('pages.licenses.form.sections.basicInfo') }}</h3>
|
||||||
|
|
||||||
<!-- 第一行:关联客户名称,客户ID,创建人 -->
|
<!-- 第一行:关联客户名称,客户ID -->
|
||||||
<div class="fields-row">
|
<div class="fields-row">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
:label="t('pages.licenses.form.fields.customerName')"
|
:label="t('pages.licenses.form.fields.customerName')"
|
||||||
@@ -69,19 +69,6 @@
|
|||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item
|
|
||||||
:label="t('pages.licenses.form.fields.createdBy')"
|
|
||||||
prop="created_by"
|
|
||||||
required
|
|
||||||
class="field-item"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-model="formData.created_by"
|
|
||||||
:placeholder="t('pages.licenses.form.placeholders.currentUser')"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第二行:备注单独一行 -->
|
<!-- 第二行:备注单独一行 -->
|
||||||
@@ -94,6 +81,8 @@
|
|||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="formData.description"
|
v-model="formData.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
:placeholder="t('pages.licenses.form.placeholders.enterDescription')"
|
:placeholder="t('pages.licenses.form.placeholders.enterDescription')"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -240,11 +229,9 @@ import {
|
|||||||
} from '@/api/license'
|
} from '@/api/license'
|
||||||
import { getCustomers, type Customer } from '@/api/customer'
|
import { getCustomers, type Customer } from '@/api/customer'
|
||||||
import { getEnumOptions, type RawEnumItem } from '@/api/enum'
|
import { getEnumOptions, type RawEnumItem } from '@/api/enum'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const userStore = useUserStore()
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 表单引用
|
// 表单引用
|
||||||
@@ -263,13 +250,11 @@ const encryptionTypeOptions = ref<RawEnumItem[]>([])
|
|||||||
const formData = reactive<
|
const formData = reactive<
|
||||||
AuthorizationCodeCreateRequest & {
|
AuthorizationCodeCreateRequest & {
|
||||||
customer_code: string
|
customer_code: string
|
||||||
created_by: string
|
|
||||||
validity_type: 'permanent' | 'limited'
|
validity_type: 'permanent' | 'limited'
|
||||||
}
|
}
|
||||||
>({
|
>({
|
||||||
customer_id: '',
|
customer_id: '',
|
||||||
customer_code: '',
|
customer_code: '',
|
||||||
created_by: '',
|
|
||||||
validity_type: 'limited',
|
validity_type: 'limited',
|
||||||
description: '',
|
description: '',
|
||||||
validity_days: 365,
|
validity_days: 365,
|
||||||
@@ -511,12 +496,6 @@ const loadCustomerInfo = () => {
|
|||||||
} as Customer]
|
} as Customer]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置创建人(从用户store获取当前登录用户)
|
|
||||||
if (userStore.userInfo) {
|
|
||||||
formData.created_by = userStore.userInfo.username
|
|
||||||
} else {
|
|
||||||
formData.created_by = t('pages.licenses.form.placeholders.currentUser')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理授权期限类型变化
|
// 处理授权期限类型变化
|
||||||
@@ -570,12 +549,6 @@ const loadLicenseDetail = async () => {
|
|||||||
|
|
||||||
// 设置表单字段
|
// 设置表单字段
|
||||||
formData.customer_code = (data as any).customer_code || data.customer_id || ''
|
formData.customer_code = (data as any).customer_code || data.customer_id || ''
|
||||||
// 编辑模式下显示原始创建人,如果没有则显示当前用户
|
|
||||||
formData.created_by =
|
|
||||||
(data as any).created_by ||
|
|
||||||
(userStore.userInfo
|
|
||||||
? userStore.userInfo.username
|
|
||||||
: t('pages.licenses.form.placeholders.currentUser'))
|
|
||||||
|
|
||||||
// 计算有效期天数和设置日期范围
|
// 计算有效期天数和设置日期范围
|
||||||
if (data.start_date && data.end_date) {
|
if (data.start_date && data.end_date) {
|
||||||
|
|||||||
@@ -62,11 +62,25 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="deployment_type_display"
|
:label="t('pages.licenses.list.table.activationProgress')"
|
||||||
:label="t('pages.licenses.list.table.deploymentType')"
|
:width="220"
|
||||||
:width="140"
|
|
||||||
align="center"
|
align="center"
|
||||||
/>
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<div
|
||||||
|
class="progress-container"
|
||||||
|
:class="{ 'zero-activated': (scope.row.activated_licenses_count || 0) === 0 }"
|
||||||
|
>
|
||||||
|
<el-progress
|
||||||
|
:percentage="getActivationProgress(scope.row)"
|
||||||
|
:stroke-width="8"
|
||||||
|
:show-text="true"
|
||||||
|
:text-inside="true"
|
||||||
|
:format="() => `${scope.row.activated_licenses_count || 0}/${scope.row.max_activations}`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="end_date" :label="t('pages.licenses.list.table.endDate')" :width="180" align="center">
|
<el-table-column prop="end_date" :label="t('pages.licenses.list.table.endDate')" :width="180" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatDate(scope.row.end_date) }}
|
{{ formatDate(scope.row.end_date) }}
|
||||||
@@ -171,6 +185,15 @@ const getStatusClass = (status: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getActivationProgress = (row: AuthorizationCode) => {
|
||||||
|
if (!row.max_activations || row.max_activations === 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const activated = row.activated_licenses_count || 0
|
||||||
|
return Math.round((activated / row.max_activations) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'licenses-create',
|
name: 'licenses-create',
|
||||||
@@ -517,6 +540,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.status-tag {
|
.status-tag {
|
||||||
padding: $spacing-extra-small $spacing-small;
|
padding: $spacing-extra-small $spacing-small;
|
||||||
|
margin: 0 10px;
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
font-weight: $font-weight-secondary;
|
font-weight: $font-weight-secondary;
|
||||||
@@ -546,6 +570,64 @@ onMounted(async () => {
|
|||||||
line-height: 24px; // 改为24px符合8px栅格
|
line-height: 24px; // 改为24px符合8px栅格
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
:deep(.el-progress) {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-progress-bar__outer) {
|
||||||
|
background: #4763FF1A !important;
|
||||||
|
min-height: 24px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-progress-bar__inner) {
|
||||||
|
background: #4763FF !important;
|
||||||
|
min-height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-progress-bar__innerText) {
|
||||||
|
color: #FFFFFF !important;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-progress__text) {
|
||||||
|
color: #FFFFFF !important;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当 activated_licenses_count 为 0 时,文字显示为红色
|
||||||
|
&.zero-activated {
|
||||||
|
:deep(.el-progress-bar__innerText),
|
||||||
|
:deep(.el-progress__text) {
|
||||||
|
color: #F56C6C !important;
|
||||||
|
margin-left: 50px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当百分比为0时,确保外部文字可见且为红色
|
||||||
|
:deep(.el-progress-bar__outer) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 操作按钮组,严格复制 customers 页 action-btn/action-buttons 样式
|
// 操作按钮组,严格复制 customers 页 action-btn/action-buttons 样式
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
gap: $spacing-small;
|
gap: $spacing-small;
|
||||||
|
|||||||