refactor: 全局字体、样式、间距统一优化

This commit is contained in:
jie committed 2026-02-08 03:41:38 +08:00
1 parent 33881795c5
commit 601658c992
18 files changed
+3847 -53

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
# 字体版权审计报告
生成日期: 2026-02-08
## 🔴 紧急处理项
### 1. Swis721 BlkCn BT - **必须移除**
- **风险等级**: 🔴 高危
- **当前状态**: 未授权商用
- **文件位置**: `src/assets/fonts/Swis721-BlkCn-BT-Black.ttf`
- **使用位置**: `src/views/Login.vue`
- **版权方**: Bitstream Inc.
- **商用费用**: ~$35-99 USD/字重
#### 替代方案:
```css
/* 推荐替换为开源字体 */
font-family: 'Roboto Condensed', 'Noto Sans SC', sans-serif;
/* 或 */
font-family: 'Barlow Condensed', 'Noto Sans SC', sans-serif;
```
---
### 2. PangMenZhengDao (庞门正道标题体) - **需确认授权**
- **风险等级**: 🟡 中危
- **当前状态**: 免费版(疑似仅限个人使用)
- **文件位置**: `src/assets/fonts/PangMenZhengDao.ttf`
- **使用位置**: `src/views/Licenses/LicenseSearch.vue`
- **版权方**: 庞门正道字体工作室
- **免费版限制**: 通常仅限个人非商业使用
#### 处理建议:
1. **联系作者确认商用授权**
- 官网: https://www.zcool.com.cn/u/14777796
- 或购买商用授权(通常 ¥199-999 RMB)
2. **或替换为开源字体**:
```css
/* 推荐中文标题字体 */
font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
/* 或使用粗体 Noto Sans */
font-family: 'Noto Sans SC', sans-serif;
font-weight: 700;
```
---
## ✅ 安全字体
### Noto Sans SC - 完全合规
- **协议**: SIL Open Font License 1.1
- **版权方**: Google Fonts
- **商用**: ✅ 完全免费
- **当前状态**: ✅ 已正确引入
- **文件**: `src/assets/fonts/noto-sans-sc-*.woff2`
---
## 📋 建议行动计划
### 立即执行(24小时内)
1. ✅ 移除 `Swis721-BlkCn-BT-Black.ttf`
2. ✅ 移除 fonts.css 中的 Swis721 声明
3. ✅ 更新 Login.vue 使用开源字体
### 短期执行(1周内)
1. 🔍 联系庞门正道确认 PangMenZhengDao 授权
2. 📝 如无法获得授权,替换为开源字体
3. 📄 添加字体授权文档到项目
### 长期优化
1. 📋 建立字体使用规范文档
2. 🔍 定期审计第三方资源版权
3. ✅ 优先使用 Google Fonts 等开源字体
---
## 🔧 技术实施清单
### 移除商业字体
```bash
# 删除文件
rm frontend/src/assets/fonts/Swis721-BlkCn-BT-Black.ttf
rm frontend/src/assets/fonts/PangMenZhengDao.ttf # 如未获授权
# 更新 fonts.css(移除相关 @font-face 声明)
# 更新组件样式(替换 font-family)
```
### 推荐开源替代字体
#### 英文/数字字体:
- **Roboto Condensed** - 类似 Swis721 风格
- **Barlow Condensed** - 现代无衬线浓缩字体
- **Oswald** - 窄体标题字体
#### 中文字体:
- **Noto Sans SC** (已使用) - 黑体,适合正文
- **Noto Serif SC** - 宋体,适合标题
- **Source Han Sans CN** - 思源黑体(Adobe)
#### 获取方式:
```bash
# 通过 Google Fonts CDN
https://fonts.google.com/
```
---
## 📚 相关法律条款
### GPL-3.0 项目字体使用
- ✅ 可使用 SIL OFL 协议字体
- ✅ 可使用 Apache License 字体
- ⚠️ 商业字体需额外购买授权
- ❌ 不可使用未授权商业字体
### 字体授权检查清单
- [ ] 确认字体协议类型
- [ ] 验证商用授权范围
- [ ] 保存授权证明文件
- [ ] 添加版权声明到项目
---
## 🔗 相关资源
- [Google Fonts](https://fonts.google.com/) - 免费开源字体库
- [SIL Open Font License](https://scripts.sil.org/OFL)
- [Adobe Fonts](https://fonts.adobe.com/) - 部分免费字体
- [中文字体版权指南](https://www.zcool.com.cn/article/ZMTQzNjM2.html)
---
**审计人**: GitHub Copilot
**项目**: License Manager Frontend
**版本**: 1.0.0
**协议**: GPL-3.0
+3589
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+42
View File
@@ -34,3 +34,45 @@
url('../fonts/noto-sans-sc-bold.woff') format('woff'); url('../fonts/noto-sans-sc-bold.woff') format('woff');
} }
/* Swis721 BlkCn BT 字体 */
@font-face {
font-family: 'Swis721 BlkCn BT';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('../fonts/Swis721-BlkCn-BT-Black.ttf') format('truetype');
}
/* PangMenZhengDao 字体 */
@font-face {
font-family: 'PangMenZhengDao';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('../fonts/PangMenZhengDao.ttf') format('truetype');
}
/* Source Han Sans CN (思源黑体) */
@font-face {
font-family: 'Source Han Sans CN';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('../fonts/SourceHanSansSC-Regular.otf') format('opentype');
}
@font-face {
font-family: 'Source Han Sans CN';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('../fonts/SourceHanSansSC-Medium.otf') format('opentype');
}
@font-face {
font-family: 'Source Han Sans CN';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('../fonts/SourceHanSansSC-Bold.otf') format('opentype');
}
+1 -1
View File
@@ -62,7 +62,7 @@ body {
/* Light Theme Variables */ /* Light Theme Variables */
--app-bg-color: #F5F7FA; --app-bg-color: #F5F7FA;
--app-content-bg: #FFFFFF; --app-content-bg: #FFFFFF;
--app-text-primary: #303133; --app-text-primary: #1D1D1D;
--app-text-regular: #606266; --app-text-regular: #606266;
--app-text-secondary: #909399; --app-text-secondary: #909399;
--app-border-color: #DCDFE6; --app-border-color: #DCDFE6;
+1 -1
View File
@@ -6,7 +6,7 @@ $danger-color: #F56C6C; // 危险色
$info-color: #909399; // 信息色 $info-color: #909399; // 信息色
// 中性色 // 中性色
$text-color-primary: #303133; // 主要文字 $text-color-primary: #1D1D1D; // 主要文字(第一层级黑色)
$text-color-regular: #606266; // 常规文字 $text-color-regular: #606266; // 常规文字
$text-color-secondary: #909399; // 次要文字 $text-color-secondary: #909399; // 次要文字
$text-color-placeholder: #C0C4CC; // 占位符文字 $text-color-placeholder: #C0C4CC; // 占位符文字
@@ -167,10 +167,11 @@ const handleNavClick = (item: NavItem, event: Event) => {
.logo-text { .logo-text {
font-family: 'Swis721 BlkCn BT', sans-serif; font-family: 'Swis721 BlkCn BT', sans-serif;
font-size: 30px; font-size: 30px;
font-style: normal;
font-weight: 400; font-weight: 400;
color: var(--app-text-primary); color: #333;
@include text-ellipsis; @include text-ellipsis;
line-height: 1.2; line-height: normal;
@include mobile { @include mobile {
font-size: 24px; font-size: 24px;
@@ -258,7 +259,7 @@ const handleNavClick = (item: NavItem, event: Event) => {
text-decoration: none; text-decoration: none;
border-radius: 24px; border-radius: 24px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 14px; font-size: 16px;
font-weight: 400; font-weight: 400;
line-height: 1; line-height: 1;
+3 -3
View File
@@ -97,12 +97,12 @@
:row-style="getRowStyle" :row-style="getRowStyle"
:max-height="'calc(100vh - 265px)'" :max-height="'calc(100vh - 265px)'"
> >
<el-table-column prop="customer_code" :label="t('customers.table.customerCode')" :width="170" show-overflow-tooltip align="left"> <el-table-column prop="customer_code" :label="t('customers.table.customerCode')" :width="140" show-overflow-tooltip align="left">
<template #default="scope"> <template #default="scope">
<span class="customer-code customer-code-link" @click="handleViewCustomerDetail(scope.row)" style="cursor:pointer; color:var(--el-color-primary)">{{ scope.row.customer_code }}</span> <span class="customer-code customer-code-link" @click="handleViewCustomerDetail(scope.row)" style="cursor:pointer; color:var(--el-color-primary)">{{ scope.row.customer_code }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="customer_name" :label="t('customers.table.customerName')" :width="180" show-overflow-tooltip align="center"> <el-table-column prop="customer_name" :label="t('customers.table.customerName')" :width="200" show-overflow-tooltip align="center">
<template #default="scope"> <template #default="scope">
<span class="ellipsis-text">{{ scope.row.customer_name }}</span> <span class="ellipsis-text">{{ scope.row.customer_name }}</span>
</template> </template>
@@ -110,7 +110,7 @@
<el-table-column prop="customer_type_display" :label="t('customers.table.customerType')" :width="130" align="center"> <el-table-column prop="customer_type_display" :label="t('customers.table.customerType')" :width="130" align="center">
</el-table-column> </el-table-column>
<el-table-column prop="contact_person" :label="t('customers.table.contactPerson')" :width="140" align="center" /> <el-table-column prop="contact_person" :label="t('customers.table.contactPerson')" :width="140" align="center" />
<el-table-column prop="email" :label="t('customers.table.email')" :width="160" show-overflow-tooltip align="center"> <el-table-column prop="email" :label="t('customers.table.email')" :width="200" show-overflow-tooltip align="center">
<template #default="scope"> <template #default="scope">
<span class="ellipsis-text">{{ scope.row.email }}</span> <span class="ellipsis-text">{{ scope.row.email }}</span>
</template> </template>
+29 -29
View File
@@ -137,7 +137,7 @@ const statsData = ref([
{ id: 2, key: 'active_licenses', value: '', label: t('dashboard.stats.totalLicenses'), icon: icon2 },//活跃许可证 { id: 2, key: 'active_licenses', value: '', label: t('dashboard.stats.totalLicenses'), icon: icon2 },//活跃许可证
{ id: 3, key: 'expiring_soon', value: '', label: t('dashboard.stats.activeLicenses'), icon: icon3 },//即将过期 { id: 3, key: 'expiring_soon', value: '', label: t('dashboard.stats.activeLicenses'), icon: icon3 },//即将过期
{ id: 4, key: 'abnormal_alerts', value: '', label: t('dashboard.stats.expired'), icon: icon4 },//异常警告 { id: 4, key: 'abnormal_alerts', value: '', label: t('dashboard.stats.expired'), icon: icon4 },//异常警告
{ id: 5, key: 'growth_rate', value: '', label: t('dashboard.stats.onlineClients'), icon: icon5 },//增长率 // { 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: 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: 7, key: 'licenses', value: '', label: t('dashboard.stats.authSuccessRate24h'), icon: icon7 }//许可证增长率
]) ])
@@ -229,10 +229,9 @@ onMounted(() => {
// 统计卡片区域 - 基于1920*1080设计的vw适配 // 统计卡片区域 - 基于1920*1080设计的vw适配
.stats-section { .stats-section {
display: grid; display: flex;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 0.8fr 1.5fr; /* 前5个正常,第6个缩小,第7个放大 */ gap: 2.08vw; /* 卡片之间的间距 40px/1920 = 2.08vw */
gap: 1.04vw; /* 20px/1920 = 1.04vw */ margin-bottom: 1.25vw; /* 24px/1920 = 1.25vw */
margin-bottom: 2.08vw; /* 40px/1920 = 2.08vw */
padding: 1.25vw; /* 24px/1920 = 1.25vw */ padding: 1.25vw; /* 24px/1920 = 1.25vw */
background: linear-gradient(135deg, #019C7C 0%, #1db584 100%); background: linear-gradient(135deg, #019C7C 0%, #1db584 100%);
border-radius: 0.42vw; /* 8px/1920 = 0.42vw */ border-radius: 0.42vw; /* 8px/1920 = 0.42vw */
@@ -279,8 +278,10 @@ onMounted(() => {
} }
.stats-card { .stats-card {
flex: 1; /* 等宽分布 */
display: flex; display: flex;
align-items: stretch; align-items: center;
justify-content: center; /* 内容居中 */
gap: 0.83vw; /* 16px/1920 = 0.83vw */ gap: 0.83vw; /* 16px/1920 = 0.83vw */
position: relative; position: relative;
z-index: 2; /* 确保内容显示在背景图片和网格纹理之上 */ z-index: 2; /* 确保内容显示在背景图片和网格纹理之上 */
@@ -289,9 +290,9 @@ onMounted(() => {
&:not(:last-child)::after { &:not(:last-child)::after {
content: ''; content: '';
position: absolute; position: absolute;
right: -0.52vw; /* -10px/1920 = -0.52vw */ left: 100%; /* 从卡片右边缘开始 */
top: 50%; top: 50%;
transform: translateY(-50%); transform: translate(calc(2.08vw / 2), -50%); /* 向右移动gap的一半,垂直居中 */
width: 1px; width: 1px;
height: 3.33vw; /* 64px/1920 = 3.33vw */ height: 3.33vw; /* 64px/1920 = 3.33vw */
background: linear-gradient( background: linear-gradient(
@@ -355,16 +356,6 @@ onMounted(() => {
} }
} }
// 紧凑型卡片样式(专门针对到期提醒)
.stats-card.compact-card {
min-width: 10vw !important; /* 48px/1920 = 2.5vw */
}
// 大型卡片样式(专门针对近24小时授权验证成功率)
.stats-card.large-card {
min-width: 10vw !important; /* 115px/1920 = 6vw */
}
// 内容区域 - 默认上下布局,充满屏幕宽度和高度 // 内容区域 - 默认上下布局,充满屏幕宽度和高度
.content-section { .content-section {
display: flex; display: flex;
@@ -395,20 +386,21 @@ onMounted(() => {
} }
.card-header { .card-header {
padding: 1.25vw 1.25vw 0; /* 24px/1920 = 1.25vw */ padding: 24px 24px 0; /* 调整为24px符合4px倍数 */
.card-title { .card-title {
font-size: 1.04vw; /* 20px/1920 = 1.04vw */ font-size: 1.04vw; /* 20px/1920 = 1.04vw */
font-weight: 400; font-weight: 500; /* 调整为中等粗细 */
color: var(--app-text-primary); color: var(--app-text-primary);
margin: 0; margin: 0;
line-height: 1.3; line-height: 1.3;
font-family: 'Source Han Sans CN', sans-serif;
} }
} }
// 表格卡片 // 表格卡片
.table-container { .table-container {
padding: 1.35vw 1.25vw 1.25vw; /* 26px 24px 24px → 1.35vw 1.25vw 1.25vw */ padding: 24px; /* 使用24px统一padding,符合4px倍数 */
flex: 1; /* 占据剩余高度 */ flex: 1; /* 占据剩余高度 */
overflow: auto; /* 如果内容过多,允许滚动 */ overflow: auto; /* 如果内容过多,允许滚动 */
display: flex; display: flex;
@@ -425,13 +417,12 @@ onMounted(() => {
.table-header th { .table-header th {
background-color: var(--app-bg-color) !important; background-color: var(--app-bg-color) !important;
color: var(--app-text-primary); color: var(--app-text-primary);
font-weight: 500; font-weight: 500;
font-size: 16px; font-size: 16px;
font-family: 'Source Han Sans CN', sans-serif; font-family: 'Source Han Sans CN', sans-serif;
height: 48px; height: 48px;
padding: 13px 20px; padding: 12px 20px; /* 调整为12px符合4px倍数 */
border-bottom: none; border-bottom: none;
} }
@@ -439,11 +430,11 @@ onMounted(() => {
height: 48px; height: 48px;
td { td {
padding: 13px 20px; padding: 12px 20px; /* 调整为12px符合4px倍数 */
border-bottom: 1px solid var(--app-border-light); border-bottom: 1px solid var(--app-border-light);
font-size: 14px; font-size: 14px;
font-family: 'Source Han Sans CN', sans-serif; font-family: 'Source Han Sans CN', sans-serif;
font-weight: 350; font-weight: 400; /* 调整为正常粗细 */
color: var(--app-text-primary); color: var(--app-text-primary);
} }
@@ -456,17 +447,25 @@ onMounted(() => {
} }
} }
// 序号列和状态列居中 // 序号列居中
.el-table__body tr td:nth-child(1), .el-table__body tr td:nth-child(1) {
.el-table__body tr td:nth-child(4) {
text-align: center; text-align: center;
} }
// 状态列防止文本溢出 // 状态列居中
.el-table__body tr td:nth-child(4) { .el-table__body tr td:nth-child(4) {
text-align: center;
overflow: visible; overflow: visible;
text-overflow: initial; text-overflow: initial;
} }
// 其他列左对齐(客户名称、描述、时间等)
.el-table__body tr td:nth-child(2),
.el-table__body tr td:nth-child(3),
.el-table__body tr td:nth-child(5),
.el-table__body tr td:nth-child(6) {
text-align: left;
}
} }
} }
@@ -502,6 +501,7 @@ onMounted(() => {
// 响应式设计 - 移动端切换回px单位确保可读性 // 响应式设计 - 移动端切换回px单位确保可读性
@media (max-width: 768px) { @media (max-width: 768px) {
.stats-section { .stats-section {
display: grid; /* 在移动端切换回 grid 布局 */
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 16px; /* 移动端使用固定像素 */ gap: 16px; /* 移动端使用固定像素 */
padding: 16px; padding: 16px;
@@ -15,6 +15,7 @@
filterable filterable
size="large" size="large"
class="customer-select" class="customer-select"
style="font-size: 20px !important;"
@change="handleCustomerChange" @change="handleCustomerChange"
> >
<el-option <el-option
@@ -165,14 +166,15 @@ onMounted(() => {
} }
.platform-title { .platform-title {
font-family: 'PangMenZhengDao', 'Source Han Sans CN', sans-serif; font-family: 'PangMenZhengDao';
font-size: 56px; font-size: 56px;
font-style: normal;
font-weight: 400; font-weight: 400;
color: #1C1C28; color: #1C1C28;
text-align: center; text-align: center;
margin: 0; margin: 0;
letter-spacing: 0.06em; letter-spacing: 3.36px;
line-height: 1.2; line-height: normal;
} }
.action-section { .action-section {
@@ -222,10 +224,10 @@ onMounted(() => {
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
height: 60px !important; height: 60px !important;
font-size: 20px; font-size: 20px !important;
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
border: 1px solid #E3E3E3; border: 1px solid #E3E3E3;
border-radius: 8px; border-radius: 8px !important;
padding: 0 48px 0 16px; padding: 0 48px 0 16px;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
@@ -798,8 +798,11 @@ const submitAddLicense = async () => {
} }
.empty-state { .empty-state {
display: flex;
align-items: center;
justify-content: center;
min-height: 500px;
padding: 40px; padding: 40px;
text-align: center;
} }
// 响应式布局 // 响应式布局
@@ -809,9 +809,9 @@ onMounted(() => {
.sidebar-item { .sidebar-item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
gap: 12px; gap: 12px;
padding: 16px 0; padding: 16px 24px;
margin: 8px 16px; margin: 8px 16px;
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.3s;
+1 -4
View File
@@ -42,7 +42,6 @@
<el-table-column <el-table-column
prop="code" prop="code"
:label="t('pages.licenses.list.table.code')" :label="t('pages.licenses.list.table.code')"
:width="220"
show-overflow-tooltip show-overflow-tooltip
align="center" align="center"
> >
@@ -54,7 +53,7 @@
> >
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="status" :label="t('pages.licenses.list.table.status')" :width="100" align="center"> <el-table-column prop="status" :label="t('pages.licenses.list.table.status')" :width="150" align="center">
<template #default="scope"> <template #default="scope">
<div class="status-tag" :class="getStatusClass(scope.row.status)"> <div class="status-tag" :class="getStatusClass(scope.row.status)">
{{ scope.row.status_display }} {{ scope.row.status_display }}
@@ -63,7 +62,6 @@
</el-table-column> </el-table-column>
<el-table-column <el-table-column
:label="t('pages.licenses.list.table.activationProgress')" :label="t('pages.licenses.list.table.activationProgress')"
:width="220"
align="center" align="center"
> >
<template #default="scope"> <template #default="scope">
@@ -89,7 +87,6 @@
<el-table-column <el-table-column
prop="description" prop="description"
:label="t('pages.licenses.list.table.description')" :label="t('pages.licenses.list.table.description')"
:min-width="200"
show-overflow-tooltip show-overflow-tooltip
align="center" align="center"
> >
+25 -4
View File
@@ -7,9 +7,13 @@
<span class="title-bar"></span> <span class="title-bar"></span>
<span>{{ t('packages.table.title') }}</span> <span>{{ t('packages.table.title') }}</span>
</div> </div>
<el-button class="refresh-btn" :icon="Refresh" text @click="fetchData"> <el-button
{{ t('enterpriseLeads.actions.refresh') }} class="refresh-btn"
</el-button> :icon="Refresh"
circle
@click="fetchData"
:loading="loading"
/>
</div> </div>
<el-table <el-table
@@ -23,7 +27,7 @@
height: '50px' height: '50px'
}" }"
> >
<el-table-column prop="id" :label="t('packages.table.id')" width="100" /> <!-- <el-table-column prop="id" :label="t('packages.table.id')" width="100" /> -->
<el-table-column prop="name" :label="t('packages.table.name')" min-width="150" /> <el-table-column prop="name" :label="t('packages.table.name')" min-width="150" />
<el-table-column prop="price" :label="t('packages.table.price')" min-width="120"> <el-table-column prop="price" :label="t('packages.table.price')" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
@@ -192,6 +196,9 @@ const handleUpdate = async (updatedData: any) => {
} }
.list-header { .list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
} }
@@ -204,6 +211,20 @@ const handleUpdate = async (updatedData: any) => {
color: #333; color: #333;
} }
.refresh-btn {
border-color: #E6F7F3;
background-color: #E6F7F3 !important;
color: #00a870 !important;
transition: all 0.3s;
&:hover {
border-color: #00a870;
background-color: #00a870 !important;
color: #fff !important;
transform: rotate(180deg);
}
}
.title-bar { .title-bar {
width: 4px; width: 4px;
height: 18px; height: 18px;