feat: add force refresh button to source/collection cards

- Backend: preview handlers accept forceRefresh in request body
- Frontend: Sources & Collections cards get a refresh button that
  bypasses cache and re-fetches from remote
This commit is contained in:
2026-07-28 21:42:12 +08:00
parent feafb19714
commit 55ab656606
8 changed files with 95 additions and 40 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sub-Store</title>
<script src="/config.js"></script>
<script type="module" crossorigin src="/assets/index-CgvGHl8V.js"></script>
<script type="module" crossorigin src="/assets/index-Du03v-Eh.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-s7owp4VX.css">
</head>
<body class="bg-gray-50 text-gray-900">
+4 -2
View File
@@ -71,8 +71,10 @@ export const deleteRecycleEntry = (id) => api.delete(`/recycle-bin/${id}`)
export const restoreRecycleEntry = (id) => api.post(`/recycle-bin/${id}/restore`)
// Preview
export const previewSource = (data) => api.post('/preview/source', data)
export const previewCollection = (data) => api.post('/preview/collection', data)
export const previewSource = (data, forceRefresh = false) =>
api.post('/preview/source', forceRefresh ? { ...data, forceRefresh: true } : data)
export const previewCollection = (data, forceRefresh = false) =>
api.post('/preview/collection', forceRefresh ? { ...data, forceRefresh: true } : data)
// Download links
export const getLinkSource = (name, target) => api.get(`/link/source/${name}`, { params: target ? { target } : {} })
+22
View File
@@ -26,6 +26,10 @@
</div>
</div>
<div class="flex gap-1 ml-3">
<button class="px-2.5 py-1 text-xs rounded hover:bg-blue-50 text-blue-600 whitespace-nowrap"
:disabled="!!egressLoadingId || refreshingId === col.id" @click.stop="refreshCollection(col)">
{{ refreshingId === col.id ? '刷新中...' : '刷新' }}
</button>
<button class="px-2.5 py-1 text-xs rounded hover:bg-blue-50 text-blue-600 whitespace-nowrap"
:disabled="!!egressLoadingId" @click.stop="probeCollectionEgress(col)">
{{ egressLoadingId === col.id ? `探测 ${egressProgress}` : '探测出口' }}
@@ -168,6 +172,7 @@ const expandedLoading = ref(false)
const expandedData = ref(null)
const egressLoadingId = ref('')
const egressProgress = ref('')
const refreshingId = ref('')
const toast = ref(null)
const nodeCounts = reactive({})
const confirmDialog = ref({ show: false, title: '', message: '', danger: false, action: null })
@@ -325,6 +330,23 @@ async function copyNodeLink(node) {
}
}
async function refreshCollection(col) {
if (refreshingId.value) return
refreshingId.value = col.id
try {
const res = await previewCollection(col, true)
nodeCounts[col.id] = countsFromPreview(res.data)
if (expandedCollectionId.value === col.id) {
expandedData.value = res.data
}
showToast('已刷新')
} catch (e) {
showToast(e.message, 'error')
} finally {
refreshingId.value = ''
}
}
async function probeCollectionEgress(col) {
if (egressLoadingId.value) return
egressLoadingId.value = col.id
+22
View File
@@ -30,6 +30,10 @@
<p v-if="flowSummary(src)" class="text-xs text-gray-500 mt-1 truncate">{{ flowSummary(src) }}</p>
</div>
<div class="flex gap-1 ml-3">
<button class="px-2.5 py-1 text-xs rounded hover:bg-blue-50 text-blue-600 whitespace-nowrap"
:disabled="!!egressLoadingId || refreshingId === src.id" @click.stop="refreshSource(src)">
{{ refreshingId === src.id ? '刷新中...' : '刷新' }}
</button>
<button class="px-2.5 py-1 text-xs rounded hover:bg-blue-50 text-blue-600 whitespace-nowrap"
:disabled="!!egressLoadingId" @click.stop="probeSourceEgress(src)">
{{ egressLoadingId === src.id ? `探测 ${egressProgress}` : '探测出口' }}
@@ -203,6 +207,7 @@ const expandedLoading = ref(false)
const expandedData = ref(null)
const egressLoadingId = ref('')
const egressProgress = ref('')
const refreshingId = ref('')
const nodeCounts = reactive({})
const flowInfos = reactive({})
const toast = ref(null)
@@ -428,6 +433,23 @@ async function copyNodeLink(node) {
}
}
async function refreshSource(src) {
if (refreshingId.value) return
refreshingId.value = src.id
try {
const res = await previewSource(sourcePreviewPayload(src), true)
nodeCounts[src.id] = countsFromPreview(res.data)
if (expandedSourceId.value === src.id) {
expandedData.value = res.data
}
showToast('已刷新')
} catch (e) {
showToast(e.message, 'error')
} finally {
refreshingId.value = ''
}
}
async function probeSourceEgress(src) {
if (egressLoadingId.value) return
egressLoadingId.value = src.id
+7
View File
@@ -620,6 +620,13 @@ func getMapValue(v any) map[string]any {
return map[string]any{}
}
func getBoolValue(v any) bool {
if b, ok := v.(bool); ok {
return b
}
return false
}
func getStringArray(v any) []string {
if arr, ok := v.([]any); ok {
result := make([]string, 0, len(arr))
+2
View File
@@ -183,6 +183,7 @@ func (d *Deps) HandlePreviewSource(c fiber.Ctx) error {
Target: "json",
Settings: settings,
RequestUserAgent: c.Get("User-Agent"),
ForceRefresh: getBoolValue(input["forceRefresh"]),
CacheRepo: d.CacheRepo,
ProxyURL: d.Cfg.Fetcher.ProxyURL,
})
@@ -212,6 +213,7 @@ func (d *Deps) HandlePreviewCollection(c fiber.Ctx) error {
TemplateConfig: d.getTemplateConfig(rec.TemplateId),
Settings: settings,
RequestUserAgent: c.Get("User-Agent"),
ForceRefresh: getBoolValue(input["forceRefresh"]),
CacheRepo: d.CacheRepo,
ProxyURL: d.Cfg.Fetcher.ProxyURL,
})