fix(sync): 恢复「拉取最新版本」自定义弹窗 + 修复 sync-btn 样式回归
CI / Backend tests (push) Canceled after 0s
CI / Frontend build (push) Canceled after 0s

问题:commit 765d9f5 意外覆盖了 ac1e36e 中的自定义弹窗代码
修复内容:
1. 恢复自定义确认弹窗(sync-confirm):毛玻璃遮罩 + 圆角卡片 + pld-btn 按钮
2. 恢复自定义结果弹窗(sync-result):成功/失败变色 + 滚动详情区域
3. 恢复 confirmSyncLatest / closeSyncConfirm / closeSyncResult 等函数
4. 恢复弹窗 CSS 样式(.sync-mask/.sync-confirm/.sync-result/.pld-btn)
5. 修复 sync-btn 样式回归:运行态透明背景 + 无阴影,旋转动画移到内部 span
This commit is contained in:
yinjun.chen1
2026-09-21 23:08:56 +08:00
parent 72699ae422
commit da924492ce
+104 -21
View File
@@ -241,18 +241,20 @@ async function onCleanConfirmed(res) {
// —— 一键拉取存量包最新版本 ——
const summaryShown = ref(false)
async function doSyncLatest() {
const sp = state.syncProgress
if (sp.running) return
try {
await ElMessageBox.confirm(
'将查询上游 npm registry,为存量包拉取每个包的最新版本(已缓存最新版会自动跳过)。',
'拉取最新版本',
{ type: 'info', confirmButtonText: '开始拉取', cancelButtonText: '取消', closeOnClickModal: false },
)
} catch {
return // 用户取消
const syncConfirmOpen = ref(false)
const syncResultOpen = ref(false)
const syncResultDetail = ref('')
const syncResultFailed = ref(false)
function openSyncConfirm() {
if (state.syncProgress.running) return
syncConfirmOpen.value = true
}
function closeSyncConfirm() {
syncConfirmOpen.value = false
}
async function confirmSyncLatest() {
closeSyncConfirm()
summaryShown.value = false
const res = await syncLatest()
if (!res.ok) return
@@ -263,6 +265,10 @@ async function doSyncLatest() {
// 任务异步执行,完成后由下方 watch 弹出汇总
}
function closeSyncResult() {
syncResultOpen.value = false
}
watch(
() => state.syncProgress.running,
async (running, prev) => {
@@ -274,18 +280,17 @@ watch(
detail.push(`❌ 拉取失败 ${sp.failed} 个`)
if (sp.failedList.length) detail.push(`失败明细:\n${sp.failedList.map(f => `· ${f}`).join('\n')}`)
}
try {
await ElMessageBox.alert(detail.join('\n'), sp.failed ? '拉取完成(部分失败)' : '拉取完成', {
confirmButtonText: '知道了',
type: sp.failed ? 'warning' : 'success',
})
} catch {
/* 用户关闭弹窗无需处理 */
}
syncResultDetail.value = detail.join('\n')
syncResultFailed.value = !!sp.failed
syncResultOpen.value = true
}
},
)
async function doSyncLatest() {
openSyncConfirm()
}
// —— npm 包清单:查看/编辑入口 + 按清单手动拉取(后台执行,失败自动重试 3 次) ——
const pkgListOpen = ref(false)
const pullLoading = ref(null)
@@ -424,7 +429,9 @@ const pendingCount = computed(() => state.pkgList.items.filter(i => i.status ===
:class="{ on: state.syncProgress.running }"
:title="state.syncProgress.running ? `正在拉取… ${state.syncProgress.processed}/${state.syncProgress.total}` : '一键拉取存量包最新版本'"
@click.stop="doSyncLatest"
>{{ state.syncProgress.running ? '⏳' : '📥' }}</button>
>
<span :class="state.syncProgress.running ? 'sync-spin' : ''">{{ state.syncProgress.running ? '⏳' : '📥' }}</span>
</button>
<div class="dd">
<button class="dd-btn" :class="{ on: filterOpen }" title="快捷筛选" @click.stop="toggleFilter">🎚️</button>
<Transition name="dd">
@@ -525,6 +532,33 @@ const pendingCount = computed(() => state.pkgList.items.filter(i => i.status ===
@confirmed="onCleanConfirmed"
/>
<PkgListDrawer v-model:visible="pkgListOpen" />
<!-- 一键拉取确认弹窗(统一风格) -->
<Transition name="dd">
<div v-if="syncConfirmOpen" class="sync-mask" @click.self="closeSyncConfirm">
<div class="sync-confirm">
<div class="sync-confirm-title">拉取最新版本</div>
<div class="sync-confirm-body">将查询上游 npm registry,为存量包拉取每个包的最新版本(已缓存最新版会自动跳过)。</div>
<div class="sync-confirm-ops">
<button class="pld-btn" @click="closeSyncConfirm">取消</button>
<button class="pld-btn primary" @click="confirmSyncLatest">开始拉取</button>
</div>
</div>
</div>
</Transition>
<!-- 拉取结果弹窗 -->
<Transition name="dd">
<div v-if="syncResultOpen" class="sync-mask" @click.self="closeSyncResult">
<div class="sync-result" :class="{ warn: syncResultFailed }">
<div class="sync-result-title">{{ syncResultFailed ? '拉取完成(部分失败)' : '拉取完成' }}</div>
<pre class="sync-result-body">{{ syncResultDetail }}</pre>
<div class="sync-result-ops">
<button class="pld-btn primary" @click="closeSyncResult">知道了</button>
</div>
</div>
</div>
</Transition>
</template>
<style scoped>
@@ -669,11 +703,12 @@ const pendingCount = computed(() => state.pkgList.items.filter(i => i.status ===
.dd-btn:hover { border-color: color-mix(in srgb, var(--orange) 60%, transparent); }
.dd-btn.on { background: var(--green); border-color: var(--green); box-shadow: 0 6px 14px -4px rgba(50, 205, 50, .5); }
.sync-btn { font-size: 17px; }
.sync-btn.on { animation: sync-spin 1.1s linear infinite; }
.sync-btn.on { background: transparent; border-color: var(--line); box-shadow: none; }
@keyframes sync-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.sync-spin { animation: sync-spin 1.1s linear infinite; display: inline-block; }
/* npm 包清单入口角标 + 拉取按钮(含失败角标) */
.pkglist-btn { position: relative; font-size: 16px; }
.pkg-badge {
@@ -781,6 +816,54 @@ const pendingCount = computed(() => state.pkgList.items.filter(i => i.status ===
.empty-tip { padding: 40px 0; text-align: center; color: var(--ink-faint); font-size: 12.5px; }
.loading-tip { text-align: center; padding: 40px 0; color: var(--ink-faint); font-size: 13px; }
/* —— 一键拉取弹窗(统一风格) —— */
.sync-mask {
position: fixed; inset: 0; z-index: 500;
display: grid; place-items: center;
background: rgba(18, 16, 14, .38);
backdrop-filter: blur(14px) saturate(1.3);
}
html[data-theme="bright"] .sync-mask {
background: rgba(244, 242, 239, .45);
backdrop-filter: blur(14px) saturate(1.3);
}
.sync-confirm, .sync-result {
width: min(420px, 92vw);
background: var(--card);
border: 1.5px solid var(--line);
border-radius: 18px;
box-shadow: 0 30px 70px -20px rgba(0, 0, 0, .5);
padding: 22px 24px;
display: flex; flex-direction: column; gap: 14px;
}
html[data-theme="bright"] .sync-confirm,
html[data-theme="bright"] .sync-result {
box-shadow: 0 30px 70px -20px rgba(0, 0, 0, .22);
}
.sync-confirm-title, .sync-result-title { font-size: 15px; font-weight: 800; }
.sync-confirm-body { font-size: 13px; color: var(--ink-dim); line-height: 1.6; }
.sync-result-body {
font-size: 12.5px; color: var(--ink-dim); line-height: 1.7;
white-space: pre-wrap; max-height: 50vh; overflow: auto;
padding: 10px 12px; border-radius: 10px; background: var(--panel-soft);
}
.sync-confirm-ops, .sync-result-ops {
display: flex; justify-content: flex-end; gap: 10px;
}
.sync-result.warn .sync-result-title { color: var(--red); }
/* —— 弹窗按钮(与 PkgListDrawer 统一风格) —— */
.pld-btn {
display: inline-flex; align-items: center; gap: 6px;
height: 36px; padding: 0 16px; border-radius: 12px;
border: 1.5px solid var(--line); background: var(--card);
color: var(--ink-dim); font-size: 12.5px; font-weight: 600;
cursor: pointer; transition: .15s;
}
.pld-btn:hover { border-color: color-mix(in srgb, var(--orange) 60%, transparent); color: var(--orange); }
.pld-btn.primary { background: var(--grad-main); border-color: transparent; color: #fff; box-shadow: 0 8px 18px -6px rgba(255, 140, 0, .55); }
.pld-btn.primary:hover { transform: translateY(-1px); }
@media (max-width: 767px) {
.search-box { height: 48px; }
.search-box .s-go { display: none; }