🔥 移除旧密码重置审核系统 & ✨ 优化存储管理UI
后端改进: - 移除需要管理员审核的密码重置请求功能 - 简化密码重置流程,直接使用邮件重置 - 删除 password_reset_requests 表及相关代码 前端优化: - 重新设计存储管理界面,采用现代化渐变风格 - 改进存储方式切换交互,添加动画效果 - 优化视觉层次和信息展示 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1111,45 +1111,120 @@
|
||||
<i class="fas fa-database"></i> 存储管理
|
||||
</h3>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px;">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<span style="font-weight: 600; color: #333;">当前存储方式: </span>
|
||||
<span style="color: #667eea; font-weight: 600;">{{ storageTypeText }}</span>
|
||||
<div style="background: linear-gradient(135deg, #f3f5ff 0%, #eef7ff 100%); padding: 22px; border-radius: 14px; box-shadow: 0 10px 30px rgba(102,126,234,0.12); border: 1px solid #e3e9ff;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap;">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<span style="font-weight: 700; color: #334155;">当前模式</span>
|
||||
<span :style="{
|
||||
padding: '6px 12px',
|
||||
borderRadius: '999px',
|
||||
background: storageType === 'local' ? 'rgba(40,167,69,0.12)' : 'rgba(102,126,234,0.12)',
|
||||
color: storageType === 'local' ? '#1c7c3d' : '#4b5fc9',
|
||||
fontWeight: 700,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px'
|
||||
}">
|
||||
<i :class="storageType === 'local' ? 'fas fa-hard-drive' : 'fas fa-server'"></i>
|
||||
{{ storageTypeText }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="storageSwitching" style="color: #4b5fc9; font-weight: 600; display: inline-flex; align-items: center; gap: 8px;">
|
||||
<i class="fas fa-sync-alt fa-spin"></i>
|
||||
正在切换到 {{ storageSwitchTarget === 'sftp' ? 'SFTP 存储' : '本地存储' }}...
|
||||
</div>
|
||||
<div v-else style="color: #666; font-size: 13px;">本地存储适合快速读写,SFTP 适合独立服务器空间</div>
|
||||
</div>
|
||||
|
||||
<div v-if="storageType === 'local'" style="margin-bottom: 15px;">
|
||||
<span style="font-weight: 600; color: #333;">配额使用: </span>
|
||||
<span>{{ localUsedFormatted }} / {{ localQuotaFormatted }} ({{ quotaPercentage }}%)</span>
|
||||
<div style="margin-top: 8px; width: 100%; height: 18px; background: #e0e0e0; border-radius: 9px; overflow: hidden;">
|
||||
<div :style="{
|
||||
width: quotaPercentage + '%',
|
||||
height: '100%',
|
||||
background: quotaPercentage > 90 ? '#dc3545' : quotaPercentage > 75 ? '#ffc107' : '#28a745',
|
||||
transition: 'width 0.3s'
|
||||
}"></div>
|
||||
<div style="margin-top: 16px; background: white; border-radius: 12px; padding: 12px; border: 1px solid #e2e8f0;">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; align-items: center;">
|
||||
<button
|
||||
class="btn"
|
||||
:class="storageType === 'local' ? 'btn-primary' : 'btn-secondary'"
|
||||
style="width: 100%; border-radius: 10px; padding: 12px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; transition: all .3s;"
|
||||
:disabled="storageType === 'local' || storageSwitching"
|
||||
@click="switchStorage('local')">
|
||||
<i class="fas fa-hard-drive"></i>
|
||||
切换到本地
|
||||
</button>
|
||||
<div style="height: 4px; background: #e2e8f0; border-radius: 999px; position: relative; overflow: hidden;">
|
||||
<div :style="{
|
||||
position: 'absolute',
|
||||
left: storageType === 'local' ? '6%' : '52%',
|
||||
width: '42%',
|
||||
height: '100%',
|
||||
background: 'linear-gradient(90deg,#667eea,#764ba2)',
|
||||
borderRadius: '999px',
|
||||
transition: 'left .35s ease'
|
||||
}"></div>
|
||||
</div>
|
||||
<button
|
||||
class="btn"
|
||||
:class="storageType === 'sftp' ? 'btn-primary' : 'btn-secondary'"
|
||||
style="width: 100%; border-radius: 10px; padding: 12px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; transition: all .3s;"
|
||||
:disabled="storageType === 'sftp' || storageSwitching"
|
||||
@click="switchStorage('sftp')">
|
||||
<i class="fas fa-server"></i>
|
||||
切换到 SFTP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 15px; flex-wrap: wrap;">
|
||||
<button
|
||||
class="btn"
|
||||
:class="storageType === 'local' ? 'btn-primary' : 'btn-secondary'"
|
||||
@click="switchStorage('local')"
|
||||
:disabled="storageType === 'local'">
|
||||
<i class="fas fa-hard-drive"></i> 本地存储
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
:class="storageType === 'sftp' ? 'btn-primary' : 'btn-secondary'"
|
||||
@click="switchStorage('sftp')"
|
||||
:disabled="storageType === 'sftp'">
|
||||
<i class="fas fa-server"></i> SFTP存储
|
||||
</button>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; margin-top: 14px;">
|
||||
<div style="background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 16px; box-shadow: 0 6px 20px rgba(0,0,0,0.04);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div style="font-weight: 700; color: #0f172a; display: flex; gap: 8px; align-items: center;">
|
||||
<i class="fas fa-hard-drive"></i> 本地存储
|
||||
</div>
|
||||
<span v-if="storageType === 'local'" style="font-size: 12px; color: #28a745; background: rgba(40,167,69,0.12); padding: 4px 8px; border-radius: 999px;">当前</span>
|
||||
</div>
|
||||
<div style="color: #475569; font-size: 13px; margin-bottom: 10px;">更快的读写,适合日常上传下载。</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div style="font-size: 12px; color: #64748b; margin-bottom: 6px;">配额使用</div>
|
||||
<div style="font-weight: 600; color: #0f172a;">{{ localUsedFormatted }} / {{ localQuotaFormatted }}</div>
|
||||
<div style="margin-top: 6px; width: 100%; height: 10px; background: #e2e8f0; border-radius: 5px; overflow: hidden;">
|
||||
<div :style="{
|
||||
width: quotaPercentage + '%',
|
||||
height: '100%',
|
||||
background: quotaPercentage > 90 ? '#dc3545' : quotaPercentage > 75 ? '#ffc107' : '#28a745',
|
||||
transition: 'width 0.35s ease'
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" style="width: 100%; border-radius: 10px;" :disabled="storageType === 'local' || storageSwitching" @click="switchStorage('local')">
|
||||
<i class="fas fa-bolt"></i> 用本地存储
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 16px; box-shadow: 0 6px 20px rgba(0,0,0,0.04);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div style="font-weight: 700; color: #0f172a; display: flex; gap: 8px; align-items: center;">
|
||||
<i class="fas fa-server"></i> SFTP 存储
|
||||
</div>
|
||||
<span v-if="storageType === 'sftp'" style="font-size: 12px; color: #4b5fc9; background: rgba(102,126,234,0.15); padding: 4px 8px; border-radius: 999px;">当前</span>
|
||||
</div>
|
||||
<div style="color: #475569; font-size: 13px; margin-bottom: 10px;">使用你自己的服务器空间,独立存储更灵活。</div>
|
||||
<div v-if="user?.has_ftp_config" style="font-size: 13px; color: #0f172a; margin-bottom: 10px;">
|
||||
已配置: {{ user.ftp_host }}:{{ user.ftp_port }}
|
||||
</div>
|
||||
<div v-else style="font-size: 13px; color: #b45309; background: #fff7ed; border: 1px dashed #fcd34d; padding: 10px; border-radius: 8px; margin-bottom: 10px;">
|
||||
<i class="fas fa-exclamation-circle"></i> 先填写 SFTP 连接信息再切换
|
||||
</div>
|
||||
<button
|
||||
class="btn"
|
||||
:class="user?.has_ftp_config ? 'btn-primary' : 'btn-secondary'"
|
||||
style="width: 100%; border-radius: 10px;"
|
||||
:disabled="storageType === 'sftp' || storageSwitching"
|
||||
@click="switchStorage('sftp')">
|
||||
<i class="fas fa-random"></i>
|
||||
{{ user?.has_ftp_config ? '切到 SFTP 存储' : '去配置 SFTP' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 15px; padding: 10px; background: #fff3cd; border-radius: 6px; font-size: 13px; color: #856404;">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>提示:</strong> 本地存储速度快但有配额限制;SFTP存储需先配置服务器信息
|
||||
<div style="margin-top: 12px; padding: 10px 12px; background: #f1f5f9; border-radius: 10px; font-size: 13px; color: #475569;">
|
||||
<i class="fas fa-info-circle" style="color: #4b5fc9;"></i>
|
||||
本地存储速度快但受配额限制;SFTP 需先配置连接,切换过程中可继续查看文件列表。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1641,39 +1716,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 密码重置审核区域 -->
|
||||
<div class="card" style="margin-top: 30px;">
|
||||
<h3 style="margin-bottom: 20px;">密码重置审核</h3>
|
||||
<div v-if="passwordResetRequests.length === 0" class="alert alert-info">
|
||||
暂无待审核的密码重置请求
|
||||
</div>
|
||||
<table v-else style="width: 100%; border-collapse: collapse; table-layout: fixed;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid #ddd;">
|
||||
<th style="padding: 10px; text-align: left;">用户名</th>
|
||||
<th style="padding: 10px; text-align: left;">邮箱</th>
|
||||
<th style="padding: 10px; text-align: left;">提交时间</th>
|
||||
<th style="padding: 10px; text-align: center;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="req in passwordResetRequests" :key="req.id" style="border-bottom: 1px solid #eee;">
|
||||
<td style="padding: 10px;">{{ req.username }}</td>
|
||||
<td style="padding: 10px;">{{ req.email }}</td>
|
||||
<td style="padding: 10px;">{{ formatDate(req.created_at) }}</td>
|
||||
<td style="padding: 10px; text-align: center;">
|
||||
<button class="btn" style="background: #28a745; color: white; margin: 2px;" @click="reviewPasswordReset(req.id, true)">
|
||||
<i class="fas fa-check"></i> 批准
|
||||
</button>
|
||||
<button class="btn" style="background: #dc3545; color: white; margin: 2px;" @click="reviewPasswordReset(req.id, false)">
|
||||
<i class="fas fa-times"></i> 拒绝
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 上传工具管理区域 -->
|
||||
<div class="card" style="margin-top: 30px;">
|
||||
<h3 style="margin-bottom: 20px;">
|
||||
|
||||
@@ -113,9 +113,6 @@ createApp({
|
||||
resetPwdUser: {},
|
||||
newPassword: '',
|
||||
|
||||
// 密码重置审核
|
||||
passwordResetRequests: [],
|
||||
|
||||
// 文件审查
|
||||
showFileInspectionModal: false,
|
||||
inspectionUser: null,
|
||||
@@ -213,7 +210,11 @@ createApp({
|
||||
uploadingTool: false, // 是否正在上传工具
|
||||
|
||||
// 强制显示SFTP配置(用于本地存储模式下临时显示SFTP配置)
|
||||
forceSftpConfigVisible: false
|
||||
forceSftpConfigVisible: false,
|
||||
|
||||
// 存储切换状态
|
||||
storageSwitching: false,
|
||||
storageSwitchTarget: null
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1692,44 +1693,6 @@ handleDragLeave(e) {
|
||||
}
|
||||
},
|
||||
|
||||
// ===== 管理员:密码重置审核 =====
|
||||
|
||||
async loadPasswordResetRequests() {
|
||||
try {
|
||||
const response = await axios.get(`${this.apiBase}/api/admin/password-reset/pending`, {
|
||||
headers: { Authorization: `Bearer ${this.token}` }
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
this.passwordResetRequests = response.data.requests;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载密码重置请求失败:', error);
|
||||
this.showToast('error', '错误', '加载密码重置请求失败');
|
||||
}
|
||||
},
|
||||
|
||||
async reviewPasswordReset(requestId, approved) {
|
||||
const action = approved ? '批准' : '拒绝';
|
||||
if (!confirm(`确定要${action}这个密码重置请求吗?`)) return;
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${this.apiBase}/api/admin/password-reset/${requestId}/review`,
|
||||
{ approved },
|
||||
{ headers: { Authorization: `Bearer ${this.token}` } }
|
||||
);
|
||||
|
||||
if (response.data.success) {
|
||||
this.showToast('success', '成功', response.data.message);
|
||||
this.loadPasswordResetRequests();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审核失败:', error);
|
||||
this.showToast('error', '错误', error.response?.data?.message || '审核失败');
|
||||
}
|
||||
},
|
||||
|
||||
// ===== 管理员:文件审查功能 =====
|
||||
|
||||
async openFileInspection(user) {
|
||||
@@ -1876,10 +1839,30 @@ handleDragLeave(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`确定要切换到${type === 'local' ? '本地存储' : 'SFTP存储'}吗?`)) {
|
||||
if (this.storageSwitching || type === this.storageType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 切到SFTP但还未配置,引导去配置
|
||||
if (type === 'sftp' && (!this.user?.has_ftp_config)) {
|
||||
this.showToast('info', '需要配置SFTP', '请先填写SFTP信息再切换');
|
||||
this.currentView = 'settings';
|
||||
this.forceSftpConfigVisible = true;
|
||||
if (this.user && !this.user.is_admin) {
|
||||
this.loadFtpConfig();
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
const sftpSection = document.getElementById('sftp-config-section');
|
||||
if (sftpSection) {
|
||||
sftpSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.storageSwitching = true;
|
||||
this.storageSwitchTarget = type;
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${this.apiBase}/api/user/switch-storage`,
|
||||
@@ -1899,6 +1882,9 @@ handleDragLeave(e) {
|
||||
} catch (error) {
|
||||
console.error('切换存储失败:', error);
|
||||
this.showToast('error', '错误', error.response?.data?.message || '切换存储失败');
|
||||
} finally {
|
||||
this.storageSwitching = false;
|
||||
this.storageSwitchTarget = null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2308,7 +2294,6 @@ handleDragLeave(e) {
|
||||
} else if (newView === 'admin' && this.user?.is_admin) {
|
||||
this.loadUsers();
|
||||
this.loadSystemSettings();
|
||||
this.loadPasswordResetRequests();
|
||||
this.loadServerStorageStats();
|
||||
} else if (newView === 'settings' && this.user && !this.user.is_admin) {
|
||||
// 普通用户进入设置页面时加载SFTP配置
|
||||
|
||||
Reference in New Issue
Block a user