diff --git a/public/js/admin.js b/public/js/admin.js
index 7da6f4a..9642fac 100644
--- a/public/js/admin.js
+++ b/public/js/admin.js
@@ -546,6 +546,36 @@
});
}
+ function toastSuccess(text) {
+ try {
+ var container = qs('[data-role="toast-container"]');
+ if (!container) {
+ return false;
+ }
+
+ var t = document.createElement('div');
+ t.className = 'toast toast-success';
+ t.setAttribute('role', 'status');
+
+ var c = document.createElement('div');
+ c.className = 'toast-content';
+ c.textContent = String(text || '');
+
+ t.appendChild(c);
+ container.appendChild(t);
+
+ setTimeout(function () {
+ try {
+ container.removeChild(t);
+ } catch (e) {}
+ }, 2500);
+
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
// 批次页:一键复制 run_id(渐进增强)
(function(){
var btn = qs('[data-action="copy-run-id"][data-run-id]');
@@ -554,25 +584,9 @@
btn.addEventListener('click', function(){
var runId = btn.getAttribute('data-run-id') || '';
copyToClipboard(runId).then(function(){
- // 复用 toast 容器(若不存在则降级 alert)
- try {
- var container = qs('[data-role="toast-container"]');
- if(container){
- var t = document.createElement('div');
- t.className = 'toast toast-success';
- t.setAttribute('role','status');
- var c = document.createElement('div');
- c.className = 'toast-content';
- c.textContent = '已复制 run_id:' + runId;
- t.appendChild(c);
- container.appendChild(t);
- setTimeout(function(){
- try{ container.removeChild(t);}catch(e){}
- }, 2500);
- return;
- }
- } catch(e) {}
-
+ if (toastSuccess('已复制 run_id:' + runId)) {
+ return;
+ }
try { window.alert('已复制 run_id:' + runId); } catch (e) {}
}).catch(function(){
try { window.alert('复制失败,请手动复制 run_id:' + runId); } catch (e) {}
@@ -580,6 +594,26 @@
});
})();
+ // 批次页:一键复制治理集合链接(例如:本批次失败 / 按Top原因)
+ (function(){
+ var btns = document.querySelectorAll('[data-action="copy-link"][data-href]');
+ if(!btns || btns.length === 0){return;}
+
+ btns.forEach(function(btn){
+ btn.addEventListener('click', function(){
+ var href = btn.getAttribute('data-href') || '';
+ copyToClipboard(href).then(function(){
+ if (toastSuccess('已复制链接')) {
+ return;
+ }
+ try { window.alert('已复制链接'); } catch (e) {}
+ }).catch(function(){
+ try { window.alert('复制失败,请手动复制链接'); } catch (e) {}
+ });
+ });
+ });
+ })();
+
// 续费缺订阅治理:订单详情页“绑定订阅ID”输入框,小交互增强:
// - 输入后按 Enter 直接提交
// - 自动聚焦,减少点击
diff --git a/resources/views/admin/platform_batches/show.blade.php b/resources/views/admin/platform_batches/show.blade.php
index 773806f..4ba2e6d 100644
--- a/resources/views/admin/platform_batches/show.blade.php
+++ b/resources/views/admin/platform_batches/show.blade.php
@@ -124,10 +124,12 @@
@if(($governanceLinks['failed'] ?? '') !== '')
|
本批次失败
+
@endif
@if(($governanceLinks['top_reason'] ?? '') !== '')
|
按Top原因
+
@endif
@if(($governanceLinks['retry_syncable'] ?? '') !== '')
|
diff --git a/tests/Feature/AdminPlatformBatchShowPageCopyGovernanceLinksButtonsShouldRenderTest.php b/tests/Feature/AdminPlatformBatchShowPageCopyGovernanceLinksButtonsShouldRenderTest.php
new file mode 100644
index 0000000..077eb1b
--- /dev/null
+++ b/tests/Feature/AdminPlatformBatchShowPageCopyGovernanceLinksButtonsShouldRenderTest.php
@@ -0,0 +1,38 @@
+seed();
+
+ $this->post('/admin/login', [
+ 'email' => 'platform.admin@demo.local',
+ 'password' => 'Platform@123456',
+ ])->assertRedirect('/admin');
+ }
+
+ public function test_batch_show_page_should_render_copy_buttons_for_governance_links_when_present(): void
+ {
+ $this->loginAsPlatformAdmin();
+
+ // 不依赖 DB:该页面即使 summary 缺失也会渲染治理入口(本批次失败/按Top原因)是否存在取决于 controller summary。
+ // 这里用 BAS + 任意 run_id,只校验按钮结构存在;治理链接内容在其它测试已覆盖。
+ $runId = 'BAS_COPY_LINKS_0001';
+
+ // 该 run_id 在空库下 summary 为空,controller 仍会生成 failed/all/retry 链接,因此“本批次失败”应存在。
+ $html = $this->get('/admin/platform-batches/show?type=bas&run_id=' . $runId)
+ ->assertOk()
+ ->getContent();
+
+ $this->assertStringContainsString('data-action="copy-link"', $html);
+ $this->assertStringContainsString('data-role="copy-failed-link"', $html);
+ }
+}