feat(ui): 全局弹窗替代原生 alert/confirm,添加防重复提交与搜索功能
- 新增 ConfirmModal / ResultModal 全局组件,替代原生 alert/confirm/prompt - 资产/平台/账号保存添加防重复提交锁(assetSaving / providerSaving) - 添加 Esc 键盘快捷键关闭模态框 - 平台/监控视图添加搜索过滤功能 - 修复监控中心 SSL TAB 下新增按钮的显示逻辑 - 调整 toast 位置避免遮挡底部导航 - 统一各弹窗 z-index 层级
This commit is contained in:
@@ -50,13 +50,14 @@ const MonitorView = {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 类别 TAB -->
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<!-- 类别 TAB + 搜索 -->
|
||||
<div class="flex flex-wrap gap-1.5 items-center">
|
||||
<button v-for="t in tabs" :key="t.key" @click="switchTab(t.key)" :title="t.tip"
|
||||
class="text-sm px-3 py-1.5 rounded-full transition"
|
||||
:class="store.monitorTab===t.key ? 'bg-blue-600 text-white' : 'bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800'">
|
||||
{{ t.label }}
|
||||
</button>
|
||||
<input v-model="search" placeholder="搜索名称/平台…" class="ml-auto text-sm px-3 py-1.5 rounded-lg border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-900 w-32 md:w-40">
|
||||
</div>
|
||||
|
||||
<!-- 资产表格(非 SSL TAB) -->
|
||||
@@ -199,21 +200,26 @@ const MonitorView = {
|
||||
else if (t) list = list.filter(a => a.asset_type === t);
|
||||
return list;
|
||||
});
|
||||
const search = Vue.ref('');
|
||||
const items = Vue.computed(() => {
|
||||
let list = tabScopedAssets.value;
|
||||
const ef = store.monitorExpiryFilter;
|
||||
if (ef === 'soon30') list = list.filter(a => a.days_to_expiry >= 0 && a.days_to_expiry <= 30);
|
||||
else if (ef === 'soon7') list = list.filter(a => a.days_to_expiry >= 0 && a.days_to_expiry <= 7);
|
||||
else if (ef === 'expired') list = list.filter(a => a.days_to_expiry < 0);
|
||||
const kw = search.value.trim().toLowerCase();
|
||||
if (kw) list = list.filter(a => a.name.toLowerCase().includes(kw) || (a.provider_name || a.provider || '').toLowerCase().includes(kw));
|
||||
return [...list].sort((x, y) => (x.days_to_expiry ?? 9999) - (y.days_to_expiry ?? 9999));
|
||||
});
|
||||
// SSL 证书:按剩余天数倒计时排序,可叠加状态子筛选
|
||||
// SSL 证书:按剩余天数倒计时排序,可叠加状态子筛选与搜索
|
||||
const sslList = Vue.computed(() => {
|
||||
let list = store.siteCerts;
|
||||
const f = store.monitorSslStatusFilter;
|
||||
if (f === 'valid') list = list.filter(c => c.status === 'valid');
|
||||
else if (f === 'expiring') list = list.filter(c => c.status === 'expiring');
|
||||
else if (f === 'expired_error') list = list.filter(c => c.status === 'expired' || c.status === 'error');
|
||||
const kw = search.value.trim().toLowerCase();
|
||||
if (kw) list = list.filter(c => c.hostname.toLowerCase().includes(kw) || (c.asset_name || '').toLowerCase().includes(kw));
|
||||
return [...list].sort((x, y) => (x.days_to_expiry ?? 9999) - (y.days_to_expiry ?? 9999));
|
||||
});
|
||||
const sslStats = Vue.computed(() => {
|
||||
@@ -223,12 +229,12 @@ const MonitorView = {
|
||||
});
|
||||
async function recheck(c) {
|
||||
try { await Api.post('/site-certs/' + c.id + '/check', {}); await loadSiteCerts(); }
|
||||
catch (e) { alert('重测失败:' + e.message); }
|
||||
catch (e) { showResult('重测失败', e.message, true); }
|
||||
}
|
||||
async function delCert(c) {
|
||||
if (!confirm('删除证书监控「' + c.hostname + '」?')) return;
|
||||
if (!await showConfirm('删除证书监控', '删除证书监控「' + c.hostname + '」?', true)) return;
|
||||
try { await Api.del('/site-certs/' + c.id); await loadSiteCerts(); }
|
||||
catch (e) { alert('删除失败:' + e.message); }
|
||||
catch (e) { showResult('删除失败', e.message, true); }
|
||||
}
|
||||
const total = Vue.computed(() => tabScopedAssets.value.length);
|
||||
const stats = Vue.computed(() => {
|
||||
@@ -265,6 +271,6 @@ const MonitorView = {
|
||||
if (d <= 90) return 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400';
|
||||
return 'bg-slate-500/10 text-slate-500';
|
||||
}
|
||||
return { store, Fmt, tabs, items, total, stats, sslList, sslStats, emptyText, sslEmptyText, switchTab, toggleExpiryFilter, clearExpiryFilter, toggleSslStatusFilter, clearSslStatusFilter, countdownText, countdownBadge, recheck, delCert, edit: openAssetEdit, del: deleteAsset };
|
||||
return { store, Fmt, tabs, items, total, stats, sslList, sslStats, emptyText, sslEmptyText, switchTab, toggleExpiryFilter, clearExpiryFilter, toggleSslStatusFilter, clearSslStatusFilter, countdownText, countdownBadge, recheck, delCert, edit: openAssetEdit, del: deleteAsset, search };
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user