feat(quota): add downloadable traffic quota with local/OSS/share metering

This commit is contained in:
2026-02-17 16:52:26 +08:00
parent b0e89df5c4
commit 2629237f9e
5 changed files with 750 additions and 84 deletions

View File

@@ -3118,18 +3118,19 @@
<div class="card">
<h3 style="margin-bottom: 20px;">用户管理</h3>
<div class="admin-users-table-wrap" style="overflow-x: auto;">
<table class="admin-users-table" style="width: 100%; border-collapse: collapse; table-layout: fixed; min-width: 760px;">
<table class="admin-users-table" style="width: 100%; border-collapse: collapse; table-layout: fixed; min-width: 900px;">
<thead>
<tr style="background: rgba(255,255,255,0.05);">
<th style="padding: 10px; text-align: left; width: 4%;">ID</th>
<th style="padding: 10px; text-align: left; width: 10%;">用户名</th>
<th style="padding: 10px; text-align: center; width: 10%;">角色</th>
<th style="padding: 10px; text-align: left; width: 9%;">用户名</th>
<th style="padding: 10px; text-align: center; width: 9%;">角色</th>
<th style="padding: 10px; text-align: left; width: 14%;">邮箱</th>
<th style="padding: 10px; text-align: center; width: 9%;">存储权限</th>
<th style="padding: 10px; text-align: center; width: 9%;">当前存储</th>
<th style="padding: 10px; text-align: center; width: 12%;">配额使用</th>
<th style="padding: 10px; text-align: center; width: 8%;">状态</th>
<th style="padding: 10px; text-align: center; width: 24%;">操作</th>
<th style="padding: 10px; text-align: center; width: 8%;">存储权限</th>
<th style="padding: 10px; text-align: center; width: 8%;">当前存储</th>
<th style="padding: 10px; text-align: center; width: 11%;">存储配额</th>
<th style="padding: 10px; text-align: center; width: 11%;">下载流量</th>
<th style="padding: 10px; text-align: center; width: 7%;">状态</th>
<th style="padding: 10px; text-align: center; width: 19%;">操作</th>
</tr>
</thead>
<tbody>
@@ -3176,6 +3177,20 @@
</div>
</div>
</td>
<td style="padding: 10px; text-align: center; font-size: 12px;">
<div v-if="u.download_traffic_quota > 0">
<div>{{ formatBytes(u.download_traffic_used || 0) }} / {{ formatBytes(u.download_traffic_quota) }}</div>
<div style="font-size: 11px; color: var(--text-muted);">
{{ getAdminUserDownloadQuotaPercentage(u) }}%
</div>
</div>
<div v-else>
<div>{{ formatBytes(u.download_traffic_used || 0) }} / 不限</div>
<div style="font-size: 11px; color: var(--text-muted);">
不限
</div>
</div>
</td>
<td style="padding: 10px; text-align: center;">
<span v-if="u.is_banned" style="color: #ef4444; font-weight: 600;">已封禁</span>
<span v-else-if="!u.is_verified" style="color: #f59e0b; font-weight: 600;">未激活</span>
@@ -3517,13 +3532,43 @@
</small>
</div>
<div class="form-group">
<label class="form-label">下载流量配额</label>
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 8px;">
<label style="display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary);">
<input type="checkbox" v-model="editStorageForm.download_quota_unlimited">
不限流量
</label>
</div>
<div v-if="!editStorageForm.download_quota_unlimited" style="display: flex; gap: 10px;">
<input
type="number"
class="form-input"
v-model.number="editStorageForm.download_traffic_quota_value"
min="1"
max="10240"
step="1"
style="flex: 1;">
<select class="form-input" v-model="editStorageForm.download_quota_unit" style="width: 100px;">
<option value="MB">MB</option>
<option value="GB">GB</option>
<option value="TB">TB</option>
</select>
</div>
<small style="color: var(--text-secondary); font-size: 12px; margin-top: 5px; display: block;">
下载流量范围: 不限 或 1MB - 10TB按实际下载字节扣减
</small>
</div>
<div style="padding: 12px; background: rgba(255,255,255,0.03); border-radius: 6px; margin-bottom: 20px;">
<div style="font-size: 13px; color: var(--text-secondary); line-height: 1.6;">
<strong style="color: var(--text-primary);">配额说明:</strong><br>
• 本地默认配额: 1GB<br>
• 当前本地配额: {{ editStorageForm.local_storage_quota_value }} {{ editStorageForm.quota_unit }}
({{ editStorageForm.quota_unit === 'GB' ? (editStorageForm.local_storage_quota_value * 1024).toFixed(0) : editStorageForm.local_storage_quota_value }} MB)<br>
• 当前 OSS 配额: {{ editStorageForm.oss_storage_quota_value + ' ' + editStorageForm.oss_quota_unit }}
• 当前 OSS 配额: {{ editStorageForm.oss_storage_quota_value + ' ' + editStorageForm.oss_quota_unit }}<br>
• 已用下载流量: {{ formatBytes(editStorageForm.download_traffic_used || 0) }}<br>
• 当前下载流量配额: {{ editStorageForm.download_quota_unlimited ? '不限' : (editStorageForm.download_traffic_quota_value + ' ' + editStorageForm.download_quota_unit) }}
</div>
</div>
@@ -4884,6 +4929,11 @@
text-align: center !important;
}
body.enterprise-netdisk .file-list-table td.file-list-action-col {
overflow: visible;
text-overflow: clip;
}
body.enterprise-netdisk .file-list-action-col .btn {
margin: 0 auto;
}
@@ -5885,7 +5935,15 @@
display: inline-flex;
align-items: center;
gap: 6px;
flex-wrap: nowrap;
flex-wrap: wrap;
margin-left: auto;
justify-content: flex-end;
min-width: 0;
flex: 1 1 auto;
}
body.enterprise-netdisk .files-content-head-actions > * {
min-width: 0;
}
body.enterprise-netdisk .files-head-action-btn {
@@ -6242,6 +6300,177 @@
}
</style>
<script src="app.js?v=20260212007"></script>
<style>
/* ===== Files Header Action Layout v3 ===== */
@media (min-width: 992px) {
body.enterprise-netdisk .files-content-head.files-content-head-compact {
display: grid;
grid-template-columns: max-content minmax(220px, 1fr) minmax(420px, 560px);
align-items: center;
column-gap: 10px;
row-gap: 8px;
}
body.enterprise-netdisk .files-content-head-meta {
width: 100%;
margin-left: 0;
justify-content: flex-end;
}
body.enterprise-netdisk .files-content-head-actions {
width: 100%;
margin-left: 0;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
align-items: stretch;
}
body.enterprise-netdisk .files-head-action-btn,
body.enterprise-netdisk .files-head-folder-btn,
body.enterprise-netdisk .files-head-view-toggle {
width: 100%;
min-width: 0;
}
body.enterprise-netdisk .files-head-action-btn,
body.enterprise-netdisk .files-head-view-toggle .btn {
height: 36px;
padding: 0 10px;
font-size: 12px;
}
body.enterprise-netdisk .files-head-view-toggle {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px;
padding: 0;
border: none;
background: transparent;
}
body.enterprise-netdisk .files-head-view-toggle .btn {
min-width: 0;
}
}
@media (max-width: 991px) and (min-width: 769px) {
body.enterprise-netdisk .files-content-head-actions {
width: 100%;
margin-left: 0;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
body.enterprise-netdisk .files-head-action-btn,
body.enterprise-netdisk .files-head-folder-btn,
body.enterprise-netdisk .files-head-view-toggle {
width: 100%;
min-width: 0;
}
body.enterprise-netdisk .files-head-action-btn,
body.enterprise-netdisk .files-head-view-toggle .btn {
height: 34px;
padding: 0 8px;
}
body.enterprise-netdisk .files-head-view-toggle {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px;
padding: 0;
border: none;
background: transparent;
}
}
</style>
<style>
/* ===== Mobile Overflow Guard v1 ===== */
@media (max-width: 768px) {
body.enterprise-netdisk .card,
body.enterprise-netdisk .files-view-card,
body.enterprise-netdisk .files-content-shell,
body.enterprise-netdisk .files-content-head,
body.enterprise-netdisk .files-content-head-meta,
body.enterprise-netdisk .files-content-head-actions,
body.enterprise-netdisk .settings-section,
body.enterprise-netdisk .settings-panel,
body.enterprise-netdisk .settings-subpanel,
body.enterprise-netdisk .settings-inline-tip,
body.enterprise-netdisk .settings-storage-switch,
body.enterprise-netdisk .settings-storage-grid,
body.enterprise-netdisk .settings-storage-option {
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
}
body.enterprise-netdisk .settings-storage-grid {
grid-template-columns: 1fr !important;
gap: 10px !important;
}
body.enterprise-netdisk .settings-storage-head {
align-items: flex-start !important;
}
body.enterprise-netdisk .settings-storage-head > div {
width: 100%;
min-width: 0;
}
body.enterprise-netdisk .settings-storage-option [style*="display: flex"][style*="justify-content: space-between"] {
flex-wrap: wrap;
gap: 6px;
align-items: flex-start !important;
}
body.enterprise-netdisk .settings-storage-option button,
body.enterprise-netdisk .settings-oss-panel button,
body.enterprise-netdisk .settings-local-panel button {
max-width: 100%;
}
body.enterprise-netdisk .settings-page-subtitle,
body.enterprise-netdisk .settings-inline-tip,
body.enterprise-netdisk .files-content-title,
body.enterprise-netdisk .files-head-usage-progress-text {
overflow-wrap: anywhere;
word-break: break-word;
}
body.enterprise-netdisk .file-list {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
body.enterprise-netdisk .file-list-table {
width: 100%;
min-width: 0;
}
body.enterprise-netdisk .modal-content {
width: calc(100vw - 16px);
max-width: calc(100vw - 16px);
}
}
@media (max-width: 480px) {
body.enterprise-netdisk .settings-panel,
body.enterprise-netdisk .settings-subpanel {
padding: 10px !important;
}
body.enterprise-netdisk .settings-inline-tip {
font-size: 12px !important;
}
}
</style>
<script src="app.js?v=20260217003"></script>
</body>
</html>