feat(admin): add routed task evidence details
This commit is contained in:
@@ -58,3 +58,110 @@
|
||||
});
|
||||
refresh();
|
||||
})();
|
||||
|
||||
(() => {
|
||||
"use strict";
|
||||
const drawer = document.querySelector("[data-detail-drawer]");
|
||||
if (!drawer) return;
|
||||
const body = drawer.querySelector("[data-detail-body]");
|
||||
const closeButton = drawer.querySelector("[data-close-detail]");
|
||||
const rows = [...document.querySelectorAll("[data-task-row]")];
|
||||
const initialURL = window.location.pathname + window.location.search;
|
||||
let focusTrigger = null;
|
||||
let scrollPosition = window.scrollY;
|
||||
let activeRequest = null;
|
||||
|
||||
const isInteractive = (target) => Boolean(target && typeof target.closest === "function" && target.closest("a,button,input,select,textarea,label,[contenteditable=true]"));
|
||||
const showDrawer = () => {
|
||||
if (!drawer.open) drawer.showModal();
|
||||
};
|
||||
const restoreList = () => {
|
||||
if (activeRequest) {
|
||||
activeRequest.abort();
|
||||
activeRequest = null;
|
||||
}
|
||||
if (drawer.open) drawer.close();
|
||||
window.scrollTo({top: scrollPosition, behavior: "auto"});
|
||||
if (focusTrigger && document.contains(focusTrigger)) focusTrigger.focus({preventScroll: true});
|
||||
};
|
||||
const showError = (url, row, requestedFocus, pushHistory) => {
|
||||
body.replaceChildren();
|
||||
const message = document.createElement("p");
|
||||
message.className = "drawer-feedback";
|
||||
message.setAttribute("role", "alert");
|
||||
message.textContent = "任务详情加载失败。请重试,或在完整页打开。";
|
||||
const actions = document.createElement("p");
|
||||
const retry = document.createElement("button");
|
||||
retry.className = "button primary";
|
||||
retry.type = "button";
|
||||
retry.textContent = "重试";
|
||||
retry.addEventListener("click", () => loadDetail(url, row, requestedFocus, pushHistory));
|
||||
const fallback = document.createElement("a");
|
||||
fallback.className = "button";
|
||||
fallback.href = url;
|
||||
fallback.textContent = "在完整页打开";
|
||||
actions.className = "actions";
|
||||
actions.append(retry, fallback);
|
||||
body.append(message, actions);
|
||||
};
|
||||
const loadDetail = async (url, row, requestedFocus, pushHistory) => {
|
||||
if (activeRequest) activeRequest.abort();
|
||||
const requestController = new AbortController();
|
||||
activeRequest = requestController;
|
||||
focusTrigger = requestedFocus || focusTrigger;
|
||||
if (pushHistory) scrollPosition = window.scrollY;
|
||||
body.innerHTML = '<p class="drawer-feedback" role="status">正在加载任务详情…</p>';
|
||||
showDrawer();
|
||||
try {
|
||||
const response = await fetch(url, {headers: {"X-CMBuyer-View": "drawer", "Accept": "text/html"}, credentials: "same-origin", signal: requestController.signal});
|
||||
if (!response.ok || !String(response.headers.get("Content-Type") || "").toLowerCase().startsWith("text/html")) throw new Error("detail request rejected");
|
||||
const fragment = await response.text();
|
||||
if (!fragment.includes("data-task-detail-content")) throw new Error("detail fragment missing");
|
||||
body.innerHTML = fragment;
|
||||
if (pushHistory) window.history.pushState({cmbuyerDrawer: true, detailURL: url, focusTarget: requestedFocus === row ? "row" : "button"}, "", url);
|
||||
closeButton.focus();
|
||||
} catch (error) {
|
||||
if (error.name !== "AbortError") showError(url, row, requestedFocus, pushHistory);
|
||||
} finally {
|
||||
if (activeRequest === requestController) activeRequest = null;
|
||||
}
|
||||
};
|
||||
const requestClose = () => {
|
||||
if (window.history.state && window.history.state.cmbuyerDrawer) window.history.back();
|
||||
else restoreList();
|
||||
};
|
||||
|
||||
window.history.replaceState({cmbuyerList: true, listURL: initialURL}, "", initialURL);
|
||||
rows.forEach((row) => {
|
||||
const url = row.dataset.detailUrl;
|
||||
row.addEventListener("dblclick", (event) => {
|
||||
if (!isInteractive(event.target)) loadDetail(url, row, row, true);
|
||||
});
|
||||
row.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter" && event.target === row) {
|
||||
event.preventDefault();
|
||||
loadDetail(url, row, row, true);
|
||||
}
|
||||
});
|
||||
const button = row.querySelector("[data-open-detail]");
|
||||
if (button) button.addEventListener("click", () => loadDetail(url, row, button, true));
|
||||
});
|
||||
closeButton.addEventListener("click", requestClose);
|
||||
drawer.addEventListener("cancel", (event) => {
|
||||
event.preventDefault();
|
||||
requestClose();
|
||||
});
|
||||
window.addEventListener("popstate", (event) => {
|
||||
if (event.state && event.state.cmbuyerDrawer) {
|
||||
const row = rows.find((candidate) => candidate.dataset.detailUrl === event.state.detailURL);
|
||||
if (!row) {
|
||||
restoreList();
|
||||
return;
|
||||
}
|
||||
const requestedFocus = event.state.focusTarget === "button" ? row.querySelector("[data-open-detail]") || row : row;
|
||||
loadDetail(event.state.detailURL, row, requestedFocus, false);
|
||||
return;
|
||||
}
|
||||
restoreList();
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user