Manage daily reports by date and refresh monthly reports after deletion
This commit is contained in:
1 parent
976a4fa7b2
commit
2a54838d59
41 files changed
+2757
-89
No files matched your search
+389
-42
@@ -54,6 +54,18 @@
|
||||
biPollLoading: false,
|
||||
toastTimer: null,
|
||||
jobs: [],
|
||||
dailyReports: [],
|
||||
dailyOverviewDate: "",
|
||||
dailyOverview: null,
|
||||
dailyOverviewTimer: null,
|
||||
dailyOverviewRequest: 0,
|
||||
dailyHistoryDate: "",
|
||||
dailyHistoryItems: [],
|
||||
dailyHistoryTotal: 0,
|
||||
dailyHistoryOffset: 0,
|
||||
dailyHistoryLoading: false,
|
||||
dailyDeleteRequest: null,
|
||||
dailyDeleting: false,
|
||||
selectedJobId: "",
|
||||
jobTrace: null,
|
||||
jobTraceLoading: false,
|
||||
@@ -119,6 +131,7 @@
|
||||
rejected: ["status.validation_failed", "failed"],
|
||||
failed: ["status.failed", "failed"],
|
||||
cancelled: ["status.cancelled", "failed"],
|
||||
deleted: ["daily.deleted", ""],
|
||||
};
|
||||
const reportStatus = {
|
||||
reserved: ["status.generating", "running"],
|
||||
@@ -522,16 +535,18 @@
|
||||
feedback.className = `arr-download-feedback${arrDownloadActive() ? " is-running" : status === "succeeded" ? " is-success" : ["needs_review", "needs_data_review"].includes(status) ? " is-review" : ["failed", "interrupted", "reconnecting", "service_reconnecting", "date_unavailable"].includes(status) || rejected ? " is-error" : ""}`;
|
||||
const taskError = !task?.job_id && status === "failed" && task.error_code === "ARR_SOURCE_FETCH_FAILED" ? "source_failed"
|
||||
: !task?.job_id && status === "interrupted" && task.error_code === "ARR_DOWNLOAD_INTERRUPTED" ? "source_interrupted" : status;
|
||||
const text = rejected
|
||||
const viewingAnotherDate = task && task.report_date !== dateInput.value && !arrDownloadActive() && !reconciling;
|
||||
const text = viewingAnotherDate ? I18N.t("arr_download.ready") : rejected
|
||||
? I18N.t("arr_download.submission_rejected", { reason: task.submission_error })
|
||||
: I18N.t(`arr_download.${taskError}`);
|
||||
const taskDate = task?.report_date || state.arrDownloadIntent?.report_date;
|
||||
const taskDate = viewingAnotherDate ? dateInput.value : task?.report_date || state.arrDownloadIntent?.report_date;
|
||||
$("#arr-download-status").textContent = taskDate ? `${taskDate} · ${text}` : text;
|
||||
$("#arr-download-review").hidden = task?.status !== "needs_review" || !task.job_id;
|
||||
$("#arr-download-data-review").hidden = !arrDownloadNeedsDataReview();
|
||||
$("#arr-download-review").hidden = viewingAnotherDate || task?.status !== "needs_review" || !task.job_id;
|
||||
$("#arr-download-data-review").hidden = !selectedARRDateNeedsDataReview();
|
||||
$("#arr-download-data-review").disabled = locked;
|
||||
renderARRAcquisitionProgress(task);
|
||||
renderARRPendingReviews();
|
||||
syncDailyOverviewDate(dateInput.value);
|
||||
}
|
||||
|
||||
function clearARRDownloadPoll() {
|
||||
@@ -560,7 +575,12 @@
|
||||
state.arrDownloadTask = task;
|
||||
rememberARRPendingReview(task, { allowAdd: allowPendingAdd });
|
||||
state.arrDownloadDisconnected = false;
|
||||
rememberARRIntent({ request_id: task.request_id, report_date: task.report_date });
|
||||
rememberARRIntent(task.status === "deleted" ? null : { request_id: task.request_id, report_date: task.report_date });
|
||||
if (task.status === "deleted" && state.dailyPriceReviewJobId === task.job_id) {
|
||||
state.dailyPriceReview = null;
|
||||
state.dailyPriceReviewJobId = "";
|
||||
renderDailyPriceReview(null);
|
||||
}
|
||||
if (!state.arrDownloadDateEdited) $("#arr-download-date").value = task.report_date;
|
||||
renderARRDownload();
|
||||
if (arrDownloadNeedsDataReview()) {
|
||||
@@ -576,7 +596,7 @@
|
||||
state.jobsOffset = 0;
|
||||
renderHistoryMonthControl("jobs");
|
||||
await loadJobs(false, false);
|
||||
await selectJob(task.job_id);
|
||||
await selectJob(task.job_id, { syncDate: !state.arrDownloadDateEdited });
|
||||
state.arrDownloadSynced = signature;
|
||||
if (task.status === "succeeded" && $("#panel-monthly").classList.contains("is-active")) await loadMonthly(false, true);
|
||||
}
|
||||
@@ -627,6 +647,7 @@
|
||||
}
|
||||
const storageKey = `arr:last-download:v2:${encodeURIComponent(config.context_id)}:${encodeURIComponent(state.arrDownloadUsername)}`;
|
||||
if (state.arrDownloadStorageKey !== storageKey) {
|
||||
const contextChanged = state.arrDownloadContextId && state.arrDownloadContextId !== config.context_id;
|
||||
state.arrDownloadStorageKey = storageKey;
|
||||
state.arrDownloadContextId = config.context_id;
|
||||
state.arrDownloadRestored = false;
|
||||
@@ -636,6 +657,19 @@
|
||||
state.arrDownloadDisconnected = false;
|
||||
state.arrDownloadSynced = "";
|
||||
state.arrDownloadPendingReviews = [];
|
||||
state.dailyOverview = null;
|
||||
state.dailyOverviewDate = "";
|
||||
state.dailyOverviewRequest += 1;
|
||||
window.clearTimeout(state.dailyOverviewTimer);
|
||||
if (contextChanged) {
|
||||
state.dailyReports = [];
|
||||
state.jobs = [];
|
||||
state.dailyHistoryItems = [];
|
||||
state.dailyHistoryTotal = 0;
|
||||
state.dailyHistoryDate = "";
|
||||
renderJobs();
|
||||
renderDailyHistory();
|
||||
}
|
||||
state.manualReviewMode = "";
|
||||
state.dailyPriceReview = null;
|
||||
state.dailyPriceReviewJobId = "";
|
||||
@@ -1285,40 +1319,280 @@
|
||||
updateCompanyReportControls();
|
||||
}
|
||||
|
||||
function renderJobs(jobs) {
|
||||
const body = $("#jobs-body");
|
||||
if (!jobs.length) {
|
||||
body.innerHTML = `<tr><td class="empty-cell" colspan="7">${historyEmptyMarkup("jobs", "history.daily_empty", "暂无 Daily Report 记录")}</td></tr>`;
|
||||
$("#metric-arrival").textContent = "—";
|
||||
$("#metric-duration").textContent = "—";
|
||||
$("#metric-rooms").textContent = "—";
|
||||
function dailyJobSource(job) {
|
||||
return job?.filename === "ARR.json" ? I18N.t("arr_download.source_label") : job?.filename || "—";
|
||||
}
|
||||
|
||||
function dailyJobStatus(job) {
|
||||
if (!job) return `<span class="cell-subtle">${escapeHtml(I18N.t("daily.no_current"))}</span>`;
|
||||
const completed = formatInteger(job.review_completed_items || 0);
|
||||
const total = formatInteger(job.review_total_items || 0);
|
||||
return job.status === "needs_review" && job.review_case_id
|
||||
? `<button class="job-review-link" type="button" data-open-price-review="${escapeHtml(job.job_id)}" aria-label="${escapeHtml(I18N.t("daily_review.open", { completed, total }))}">${chip(job.status, jobStatus)}<span class="job-review-progress">${escapeHtml(I18N.t("daily_review.progress_short", { completed, total }))}</span></button>`
|
||||
: chip(job.status, jobStatus);
|
||||
}
|
||||
|
||||
function dailyJobDownload(job) {
|
||||
return state.health.download_ready && job?.status === "succeeded" && job.daily_report_sha256 && !job.is_retired
|
||||
? `<a class="download-link" href="/api/download/daily?job_id=${encodeURIComponent(job.job_id)}">${escapeHtml(I18N.t("common.download"))}</a>`
|
||||
: '<span class="cell-subtle">—</span>';
|
||||
}
|
||||
|
||||
function dailyOverviewRow() {
|
||||
return (state.dailyOverview?.business_date === state.dailyOverviewDate ? state.dailyOverview : null)
|
||||
|| state.dailyReports.find((day) => day.business_date === state.dailyOverviewDate);
|
||||
}
|
||||
|
||||
function renderDailyOverview() {
|
||||
const date = state.dailyOverviewDate;
|
||||
const day = dailyOverviewRow();
|
||||
const task = state.arrDownloadTask?.report_date === date ? state.arrDownloadTask : null;
|
||||
const latest = day?.latest_job;
|
||||
const current = day?.current_job;
|
||||
const taskPending = task && ["queued", "downloading", "processing", "needs_data_review", "needs_review", "interrupted"].includes(task.status);
|
||||
const replacement = latest && latest.job_id !== current?.job_id && !latest.is_retired;
|
||||
const jobPending = replacement && !["succeeded", "accepted", "cancelled", "rejected", "failed"].includes(latest.status);
|
||||
const failed = !taskPending && !jobPending && (task?.status === "failed" || (replacement && ["failed", "rejected"].includes(latest.status)));
|
||||
const pending = Boolean(taskPending || jobPending);
|
||||
const status = taskPending ? task.status : jobPending ? latest.status : "";
|
||||
const review = ["needs_data_review", "needs_review"].includes(status);
|
||||
let hint = I18N.t("daily.no_current");
|
||||
if (pending) hint = I18N.t(review ? "daily.overview_review" : ["queued", "downloading"].includes(status) ? "daily.overview_fetching" : "daily.overview_processing");
|
||||
else if (current) hint = I18N.t("daily.current");
|
||||
else if (failed) hint = I18N.t("status.failed");
|
||||
$("#metric-arrival").textContent = validARRDate(date) ? formatDate(date) : "—";
|
||||
$("#metric-arrival-note").textContent = hint;
|
||||
$("#metric-current-version").hidden = !current || (!pending && !failed);
|
||||
$("#metric-current-version").textContent = I18N.t("daily.current_preserved");
|
||||
const showResult = Boolean(current && !pending);
|
||||
$("#metric-duration").textContent = showResult ? duration(current) : "—";
|
||||
$("#metric-rooms").textContent = showResult ? formatInteger(current.no_of_rooms) : "—";
|
||||
$("#metric-duration-note").textContent = I18N.t(showResult ? "metric.duration_from_submit" : "daily.after_generation");
|
||||
$("#metric-rooms-note").textContent = I18N.t(showResult ? "metric.rooms" : "daily.after_generation");
|
||||
}
|
||||
|
||||
function syncDailyOverviewDate(date) {
|
||||
if (!validARRDate(date)) {
|
||||
state.dailyOverviewDate = "";
|
||||
state.dailyOverview = null;
|
||||
state.dailyOverviewRequest += 1;
|
||||
window.clearTimeout(state.dailyOverviewTimer);
|
||||
renderDailyOverview();
|
||||
return;
|
||||
}
|
||||
body.innerHTML = jobs.map((job) => {
|
||||
const sourceLabel = job.filename === "ARR.json"
|
||||
? (I18N?.t("arr_download.source_label") || "接口获取")
|
||||
: (job.filename || "—");
|
||||
const completedItems = formatInteger(job.review_completed_items || 0);
|
||||
const totalItems = formatInteger(job.review_total_items || 0);
|
||||
const statusMarkup = job.status === "needs_review" && job.review_case_id
|
||||
? `<button class="job-review-link" type="button" data-open-price-review="${escapeHtml(job.job_id)}" aria-label="${escapeHtml(I18N?.t("daily_review.open", { completed: completedItems, total: totalItems }) || `打开人工价格复核,已填写 ${completedItems} / ${totalItems}`)}">${chip(job.status, jobStatus)}<span class="job-review-progress">${escapeHtml(I18N?.t("daily_review.progress_short", { completed: completedItems, total: totalItems }) || `复核 ${completedItems} / ${totalItems}`)}</span></button>`
|
||||
: chip(job.status, jobStatus);
|
||||
const canDownload = state.health.download_ready && job.status === "succeeded" && job.daily_report_sha256;
|
||||
return `
|
||||
<tr>
|
||||
<td><span class="filename" title="${escapeHtml(sourceLabel)}">${escapeHtml(sourceLabel)}</span></td>
|
||||
<td>${statusMarkup}${job.failure_code ? `<code class="job-failure-code" title="${escapeHtml(job.failure_code)}">${escapeHtml(job.failure_code)}</code>` : ""}</td>
|
||||
<td>${escapeHtml(job.arrival_date ? formatDate(job.arrival_date) : "—")}</td>
|
||||
<td>${formatInteger(job.no_of_rooms)}</td>
|
||||
<td class="cell-subtle">${duration(job)}</td>
|
||||
<td class="cell-subtle">${formatDate(job.created_at, true)}</td>
|
||||
<td>${canDownload ? `<a class="download-link" href="/api/download/daily?job_id=${encodeURIComponent(job.job_id)}">${escapeHtml(I18N?.t("common.download") || "下载")}</a>` : '<span class="cell-subtle">—</span>'}</td>
|
||||
if (state.dailyOverviewDate !== date) {
|
||||
state.dailyOverviewDate = date;
|
||||
state.dailyOverview = null;
|
||||
state.dailyOverviewRequest += 1;
|
||||
window.clearTimeout(state.dailyOverviewTimer);
|
||||
if (!state.dailyReports.some((day) => day.business_date === date)) {
|
||||
state.dailyOverviewTimer = window.setTimeout(() => loadDailyOverview(date), 250);
|
||||
}
|
||||
renderJobs();
|
||||
return;
|
||||
}
|
||||
renderDailyOverview();
|
||||
}
|
||||
|
||||
async function loadDailyOverview(date = state.dailyOverviewDate) {
|
||||
if (!validARRDate(date)) return;
|
||||
const sequence = ++state.dailyOverviewRequest;
|
||||
const context = state.arrDownloadContextId;
|
||||
try {
|
||||
const day = await api(`/api/daily-reports/${encodeURIComponent(date)}`);
|
||||
if (state.dailyOverviewDate !== date || state.dailyOverviewRequest !== sequence || state.arrDownloadContextId !== context) return;
|
||||
if (day?.business_date === date) state.dailyOverview = day;
|
||||
renderDailyOverview();
|
||||
} catch (_) { /* Do not substitute a different date's result when this day cannot be read. */ }
|
||||
}
|
||||
|
||||
async function selectDailyReportDate(date) {
|
||||
if (!validARRDate(date) || state.dailyDeleting || state.arrDownloadBusy || dailyPriceReviewHasUnsavedChanges()) {
|
||||
if (dailyPriceReviewHasUnsavedChanges()) showToast(I18N.t("daily_review.save_before_switch"), true);
|
||||
return;
|
||||
}
|
||||
$("#arr-download-date").value = date;
|
||||
state.arrDownloadDateEdited = true;
|
||||
renderARRDownload();
|
||||
if (!dailyOverviewRow()) await loadDailyOverview(date);
|
||||
}
|
||||
|
||||
function renderJobs() {
|
||||
const body = $("#jobs-body");
|
||||
if (!state.dailyReports.length) {
|
||||
body.innerHTML = `<tr><td class="empty-cell" colspan="7">${historyEmptyMarkup("jobs", "history.daily_empty", "暂无 Daily Report 记录")}</td></tr>`;
|
||||
renderDailyOverview();
|
||||
return;
|
||||
}
|
||||
body.innerHTML = state.dailyReports.map((day) => {
|
||||
const current = day.current_job;
|
||||
const latest = day.latest_job;
|
||||
const replacement = latest && latest.job_id !== current?.job_id && !latest.is_retired ? latest : null;
|
||||
const primary = current || replacement;
|
||||
const historicalOnly = !current && latest && ["succeeded", "accepted"].includes(latest.status);
|
||||
const date = day.business_date;
|
||||
const deleteButton = primary?.can_delete
|
||||
? `<button class="daily-delete-button" type="button" data-daily-delete="${escapeHtml(primary.job_id)}" data-daily-date="${escapeHtml(date)}" data-daily-current="${Boolean(current)}" ${state.dailyDeleting ? "disabled" : ""}>${escapeHtml(I18N.t("common.delete"))}</button>` : "";
|
||||
const pendingMarkup = current && replacement
|
||||
? `<div class="daily-replacement"><span>${escapeHtml(I18N.t("daily.replacement"))}</span>${dailyJobStatus(replacement)}</div>` : "";
|
||||
return `<tr>
|
||||
<td><span class="filename" title="${escapeHtml(dailyJobSource(primary))}">${escapeHtml(dailyJobSource(primary))}</span>${current || historicalOnly ? `<small class="daily-version-label">${escapeHtml(I18N.t(current ? "daily.current" : "daily.history_only"))}</small>` : ""}</td>
|
||||
<td>${historicalOnly ? `<span class="cell-subtle">${escapeHtml(I18N.t("daily.no_current"))}</span>` : dailyJobStatus(primary)}${pendingMarkup}</td>
|
||||
<td><button class="daily-date-button" type="button" data-daily-date-select="${escapeHtml(date)}" aria-pressed="${date === state.dailyOverviewDate}">${escapeHtml(formatDate(date))}</button></td>
|
||||
<td>${current ? formatInteger(current.no_of_rooms) : "—"}</td>
|
||||
<td class="cell-subtle">${current ? duration(current) : "—"}</td>
|
||||
<td class="cell-subtle">${primary?.created_at ? formatDate(primary.created_at, true) : "—"}</td>
|
||||
<td><div class="daily-row-actions">${dailyJobDownload(current)}${deleteButton}<button class="daily-history-button" type="button" data-daily-history="${escapeHtml(date)}">${escapeHtml(I18N.t("daily.history_versions", { count: formatInteger(day.history_count || 0) }))}</button></div></td>
|
||||
</tr>`;
|
||||
}).join("");
|
||||
const latest = jobs.find((job) => job.status === "succeeded");
|
||||
$("#metric-arrival").textContent = latest?.arrival_date ? formatDate(latest.arrival_date) : "—";
|
||||
$("#metric-duration").textContent = latest ? duration(latest) : "—";
|
||||
$("#metric-rooms").textContent = latest ? formatInteger(latest.no_of_rooms) : "—";
|
||||
renderDailyOverview();
|
||||
}
|
||||
|
||||
function renderDailyHistory() {
|
||||
$("#daily-history-description").textContent = I18N.t("daily.history_date", { date: state.dailyHistoryDate });
|
||||
const items = state.dailyHistoryItems;
|
||||
$("#daily-history-body").innerHTML = items.length ? items.map((job) => {
|
||||
const label = I18N.t(job.is_retired ? "daily.deleted" : job.is_current ? "daily.current" : "daily.historical");
|
||||
const remove = job.can_delete && !job.is_retired
|
||||
? `<button class="daily-delete-button" type="button" data-daily-delete="${escapeHtml(job.job_id)}" data-daily-date="${escapeHtml(state.dailyHistoryDate)}" data-daily-current="${Boolean(job.is_current)}" ${state.dailyDeleting ? "disabled" : ""}>${escapeHtml(I18N.t("common.delete"))}</button>` : "";
|
||||
return `<tr><td><span class="filename">${escapeHtml(dailyJobSource(job))}</span><small class="daily-version-label">${escapeHtml(label)}</small></td><td>${job.is_retired ? `<span class="cell-subtle">${escapeHtml(label)}</span>` : dailyJobStatus(job)}</td><td>${job.status === "succeeded" ? formatInteger(job.no_of_rooms) : "—"}</td><td class="cell-subtle">${formatDate(job.created_at, true)}</td><td><div class="daily-row-actions">${dailyJobDownload(job)}${remove}<button class="daily-history-button" type="button" data-daily-job-log="${escapeHtml(job.job_id)}">${escapeHtml(I18N.t("daily.view_log"))}</button></div></td></tr>`;
|
||||
}).join("") : `<tr><td colspan="5" class="empty-cell">${escapeHtml(I18N.t(state.dailyHistoryLoading ? "daily.loading" : "daily.history_empty"))}</td></tr>`;
|
||||
const total = state.dailyHistoryTotal;
|
||||
const offset = state.dailyHistoryOffset;
|
||||
$("#daily-history-page-summary").textContent = I18N.t("pagination.summary", { total: formatInteger(total), start: formatInteger(total ? offset + 1 : 0), end: formatInteger(Math.min(total, offset + HISTORY_PAGE_SIZE)) });
|
||||
$("#daily-history-prev").disabled = state.dailyHistoryLoading || state.dailyDeleting || offset === 0;
|
||||
$("#daily-history-next").disabled = state.dailyHistoryLoading || state.dailyDeleting || offset + HISTORY_PAGE_SIZE >= total;
|
||||
$("#daily-history-close").disabled = state.dailyDeleting;
|
||||
}
|
||||
|
||||
async function loadDailyHistory(date = state.dailyHistoryDate) {
|
||||
if (!validARRDate(date) || state.dailyHistoryLoading) return;
|
||||
state.dailyHistoryDate = date;
|
||||
state.dailyHistoryLoading = true;
|
||||
renderDailyHistory();
|
||||
const context = state.arrDownloadContextId;
|
||||
const offset = state.dailyHistoryOffset;
|
||||
try {
|
||||
const page = readPage(await api(`/api/daily-reports/${encodeURIComponent(date)}/history?limit=${HISTORY_PAGE_SIZE}&offset=${offset}`, { returnEnvelope: true }));
|
||||
if (state.dailyHistoryDate !== date || state.arrDownloadContextId !== context) return;
|
||||
state.dailyHistoryItems = page.items;
|
||||
state.dailyHistoryTotal = page.total;
|
||||
$("#daily-history-error").hidden = true;
|
||||
} catch (error) {
|
||||
if (state.dailyHistoryDate !== date || state.arrDownloadContextId !== context) return;
|
||||
$("#daily-history-error").textContent = error.message;
|
||||
$("#daily-history-error").hidden = false;
|
||||
} finally {
|
||||
state.dailyHistoryLoading = false;
|
||||
renderDailyHistory();
|
||||
}
|
||||
}
|
||||
|
||||
async function openDailyHistory(date, trigger) {
|
||||
if (!validARRDate(date) || state.dailyDeleting || state.dailyHistoryLoading) return;
|
||||
state.dailyHistoryDate = date;
|
||||
state.dailyHistoryItems = [];
|
||||
state.dailyHistoryOffset = 0;
|
||||
state.dailyHistoryTotal = 0;
|
||||
$("#daily-history-error").hidden = true;
|
||||
const dialog = $("#daily-history-dialog");
|
||||
dialog._returnFocus = trigger;
|
||||
renderDailyHistory();
|
||||
if (!dialog.open) {
|
||||
if (typeof dialog.showModal === "function") dialog.showModal();
|
||||
else dialog.setAttribute("open", "");
|
||||
}
|
||||
$("#daily-history-close").focus();
|
||||
await loadDailyHistory(date);
|
||||
}
|
||||
|
||||
function closeDailyHistory() {
|
||||
if (state.dailyDeleting) return;
|
||||
const dialog = $("#daily-history-dialog");
|
||||
if (typeof dialog.close === "function") dialog.close();
|
||||
else dialog.removeAttribute("open");
|
||||
if (dialog._returnFocus?.isConnected) dialog._returnFocus.focus();
|
||||
}
|
||||
|
||||
function openDailyDelete(button) {
|
||||
if (dailyPriceReviewHasUnsavedChanges()) {
|
||||
showToast(I18N.t("daily_review.save_before_switch"), true);
|
||||
return;
|
||||
}
|
||||
if (state.dailyDeleting || state.dailyPriceReviewMutating || state.arrDataReviewMutating) return;
|
||||
const jobId = button.dataset.dailyDelete;
|
||||
const date = button.dataset.dailyDate;
|
||||
const current = button.dataset.dailyCurrent === "true";
|
||||
if (!jobId || !validARRDate(date)) return;
|
||||
state.dailyDeleteRequest = { jobId, date, current, trigger: button };
|
||||
$("#daily-delete-description").textContent = I18N.t(current ? "daily.delete_current_description" : "daily.delete_history_description", { date });
|
||||
$("#daily-delete-note").textContent = I18N.t("daily.delete_note");
|
||||
$("#daily-delete-error").hidden = true;
|
||||
const dialog = $("#daily-delete-dialog");
|
||||
if (typeof dialog.showModal === "function") dialog.showModal();
|
||||
else dialog.setAttribute("open", "");
|
||||
$("#daily-delete-cancel").focus();
|
||||
}
|
||||
|
||||
function closeDailyDelete(force = false) {
|
||||
if (state.dailyDeleting && !force) return;
|
||||
const request = state.dailyDeleteRequest;
|
||||
state.dailyDeleteRequest = null;
|
||||
const dialog = $("#daily-delete-dialog");
|
||||
if (typeof dialog.close === "function") dialog.close();
|
||||
else dialog.removeAttribute("open");
|
||||
if (request?.trigger?.isConnected) request.trigger.focus();
|
||||
}
|
||||
|
||||
async function confirmDailyDelete() {
|
||||
const request = state.dailyDeleteRequest;
|
||||
if (!request || state.dailyDeleting) return;
|
||||
state.dailyDeleting = true;
|
||||
$("#daily-delete-dialog").setAttribute("aria-busy", "true");
|
||||
$("#daily-delete-cancel").disabled = true;
|
||||
$("#daily-delete-submit").disabled = true;
|
||||
$("#daily-delete-error").hidden = true;
|
||||
renderJobs();
|
||||
renderDailyHistory();
|
||||
let deleted = false;
|
||||
try {
|
||||
const result = await api(`/api/jobs/${encodeURIComponent(request.jobId)}`, {
|
||||
method: "DELETE", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ business_date: request.date }),
|
||||
});
|
||||
deleted = true;
|
||||
// Once deletion succeeds, never present a later refresh failure as a failed deletion.
|
||||
if (state.dailyPriceReviewJobId === request.jobId) {
|
||||
state.dailyPriceReview = null;
|
||||
state.dailyPriceReviewJobId = "";
|
||||
renderDailyPriceReview(null);
|
||||
}
|
||||
state.arrDownloadPendingReviews = state.arrDownloadPendingReviews.filter((task) => task.job_id !== request.jobId);
|
||||
if (state.arrDownloadTask?.job_id === request.jobId) {
|
||||
await acceptARRDownloadTask({ ...state.arrDownloadTask, status: "deleted", can_retry: false, is_retired: true }, { sync: false });
|
||||
}
|
||||
closeDailyDelete(true);
|
||||
showToast(I18N.t(result.current_removed ? "daily.deleted_current" : "daily.deleted_history"));
|
||||
await Promise.allSettled([loadJobs(false, false), loadDailyOverview(state.dailyOverviewDate), loadHistoryMonths()]);
|
||||
if ($("#daily-history-dialog").open) await loadDailyHistory(request.date);
|
||||
if ($("#panel-monthly").classList.contains("is-active")) await loadMonthly(false, true);
|
||||
if ($("#panel-bi").classList.contains("is-active")) await loadMonthsAndAnalytics();
|
||||
} catch (error) {
|
||||
$("#daily-delete-error").textContent = error.message;
|
||||
$("#daily-delete-error").hidden = false;
|
||||
} finally {
|
||||
state.dailyDeleting = false;
|
||||
$("#daily-delete-dialog").setAttribute("aria-busy", "false");
|
||||
$("#daily-delete-cancel").disabled = false;
|
||||
$("#daily-delete-submit").disabled = false;
|
||||
renderJobs();
|
||||
renderDailyHistory();
|
||||
if (deleted) {
|
||||
const focusTarget = $("#daily-history-dialog").open ? $("#daily-history-close")
|
||||
: $(`[data-daily-date-select="${request.date}"]`) || $("#refresh-jobs");
|
||||
focusTarget.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reviewPrice(value) {
|
||||
@@ -1525,6 +1799,11 @@
|
||||
}
|
||||
setDailyPriceReviewMessage();
|
||||
await loadDailyPriceReview(jobId, { reset: jobId !== state.dailyPriceReviewJobId, scroll });
|
||||
if (state.dailyPriceReviewJobId === jobId && validARRDate(state.dailyPriceReview?.business_date)) {
|
||||
$("#arr-download-date").value = state.dailyPriceReview.business_date;
|
||||
state.arrDownloadDateEdited = true;
|
||||
renderARRDownload();
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDailyPriceReviewItem(button) {
|
||||
@@ -1924,7 +2203,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function selectJob(jobId) {
|
||||
async function selectJob(jobId, { syncDate = true } = {}) {
|
||||
if (!jobId) return;
|
||||
clearTracePoll();
|
||||
const changed = state.selectedJobId !== jobId;
|
||||
@@ -1932,30 +2211,47 @@
|
||||
if (changed) state.jobTrace = null;
|
||||
renderJobs(state.jobs);
|
||||
await loadJobTrace(true, !changed);
|
||||
const date = state.jobs.find((job) => job.job_id === jobId)?.arrival_date
|
||||
|| (state.jobTrace?.job?.job_id === jobId ? state.jobTrace.job.business_date : "");
|
||||
if (syncDate && state.selectedJobId === jobId && validARRDate(date)) {
|
||||
$("#arr-download-date").value = date;
|
||||
renderARRDownload();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadJobs(showErrors = false, loadTrace = true) {
|
||||
if (state.jobsLoading) return;
|
||||
state.jobsLoading = true;
|
||||
const requestedMonth = historyMonth("jobs");
|
||||
const context = state.arrDownloadContextId;
|
||||
let requestedOffset = state.jobsOffset;
|
||||
const isCurrent = () => historyMonth("jobs") === requestedMonth
|
||||
&& state.arrDownloadContextId === context && state.jobsOffset === requestedOffset;
|
||||
renderPagination("jobs", state.jobsTotal, state.jobsOffset, true);
|
||||
try {
|
||||
const month = encodeURIComponent(historyMonth("jobs"));
|
||||
const month = encodeURIComponent(requestedMonth);
|
||||
let page = readPage(await api(
|
||||
`/api/jobs?month=${month}&limit=${HISTORY_PAGE_SIZE}&offset=${state.jobsOffset}`,
|
||||
`/api/daily-reports?month=${month}&limit=${HISTORY_PAGE_SIZE}&offset=${requestedOffset}`,
|
||||
{ returnEnvelope: true },
|
||||
));
|
||||
if (!isCurrent()) return;
|
||||
if (!page.items.length && page.total > 0 && state.jobsOffset >= page.total) {
|
||||
state.jobsOffset = pageOffset(page.total, state.jobsOffset, -1);
|
||||
requestedOffset = state.jobsOffset;
|
||||
page = readPage(await api(
|
||||
`/api/jobs?month=${month}&limit=${HISTORY_PAGE_SIZE}&offset=${state.jobsOffset}`,
|
||||
`/api/daily-reports?month=${month}&limit=${HISTORY_PAGE_SIZE}&offset=${requestedOffset}`,
|
||||
{ returnEnvelope: true },
|
||||
));
|
||||
if (!isCurrent()) return;
|
||||
}
|
||||
state.jobs = page.items;
|
||||
state.dailyReports = page.items;
|
||||
if (page.items.some((day) => day.business_date === state.dailyOverviewDate)) state.dailyOverview = null;
|
||||
state.jobs = [...new Map(page.items.flatMap((day) => [day.current_job, day.latest_job])
|
||||
.filter(Boolean).map((job) => [job.job_id, job])).values()];
|
||||
state.jobsTotal = page.total;
|
||||
renderJobs(state.jobs);
|
||||
if (!state.selectedJobId && state.jobs.length) {
|
||||
await selectJob(state.jobs[0].job_id);
|
||||
await selectJob(state.jobs[0].job_id, { syncDate: false });
|
||||
} else if (loadTrace && state.selectedJobId) {
|
||||
await loadJobTrace(showErrors, Boolean(state.jobTrace));
|
||||
} else if (!state.jobs.length && !state.selectedJobId) {
|
||||
@@ -1966,11 +2262,13 @@
|
||||
setTraceLiveState(I18N?.t("task.live_unselected") || "未选择任务");
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isCurrent()) return;
|
||||
$("#jobs-body").innerHTML = `<tr><td class="empty-cell" colspan="7">${escapeHtml(I18N?.t("daily.unavailable") || "日报记录暂时无法读取")}</td></tr>`;
|
||||
if (showErrors) showToast(error.message, true);
|
||||
} finally {
|
||||
state.jobsLoading = false;
|
||||
renderPagination("jobs", state.jobsTotal, state.jobsOffset);
|
||||
if (!isCurrent()) await loadJobs(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3341,6 +3639,11 @@
|
||||
renderARRDataReview();
|
||||
renderARRCalendar();
|
||||
renderJobs(state.jobs);
|
||||
renderDailyHistory();
|
||||
if (state.dailyDeleteRequest) {
|
||||
$("#daily-delete-description").textContent = I18N.t(state.dailyDeleteRequest.current ? "daily.delete_current_description" : "daily.delete_history_description", { date: state.dailyDeleteRequest.date });
|
||||
$("#daily-delete-note").textContent = I18N.t("daily.delete_note");
|
||||
}
|
||||
renderPagination("jobs", state.jobsTotal, state.jobsOffset, state.jobsLoading);
|
||||
renderMonthly(state.monthlyRuns);
|
||||
renderPagination("monthly", state.monthlyTotal, state.monthlyOffset, state.monthlyLoading);
|
||||
@@ -3553,12 +3856,56 @@
|
||||
changeHistoryPage("companyReportsTotal", "companyReportsOffset", 1, () => loadCompanyReportHistory(false))
|
||||
);
|
||||
$("#jobs-body").addEventListener("click", (event) => {
|
||||
const dateTrigger = event.target.closest("[data-daily-date-select]");
|
||||
if (dateTrigger) { selectDailyReportDate(dateTrigger.dataset.dailyDateSelect); return; }
|
||||
const historyTrigger = event.target.closest("[data-daily-history]");
|
||||
if (historyTrigger) { openDailyHistory(historyTrigger.dataset.dailyHistory, historyTrigger); return; }
|
||||
const deleteTrigger = event.target.closest("[data-daily-delete]");
|
||||
if (deleteTrigger) { openDailyDelete(deleteTrigger); return; }
|
||||
const reviewTrigger = event.target.closest("[data-open-price-review]");
|
||||
if (reviewTrigger) {
|
||||
event.preventDefault();
|
||||
openDailyPriceReview(reviewTrigger.dataset.openPriceReview, true);
|
||||
}
|
||||
});
|
||||
$("#daily-history-body").addEventListener("click", async (event) => {
|
||||
const deleteTrigger = event.target.closest("[data-daily-delete]");
|
||||
if (deleteTrigger) { openDailyDelete(deleteTrigger); return; }
|
||||
const reviewTrigger = event.target.closest("[data-open-price-review]");
|
||||
if (reviewTrigger) {
|
||||
closeDailyHistory();
|
||||
await selectDailyReportDate(state.dailyHistoryDate);
|
||||
await openDailyPriceReview(reviewTrigger.dataset.openPriceReview, true);
|
||||
return;
|
||||
}
|
||||
const logTrigger = event.target.closest("[data-daily-job-log]");
|
||||
if (logTrigger) {
|
||||
closeDailyHistory();
|
||||
await selectJob(logTrigger.dataset.dailyJobLog);
|
||||
openTaskLog();
|
||||
}
|
||||
});
|
||||
$("#daily-history-close").addEventListener("click", closeDailyHistory);
|
||||
$("#daily-history-dialog").addEventListener("cancel", (event) => {
|
||||
event.preventDefault();
|
||||
closeDailyHistory();
|
||||
});
|
||||
$("#daily-history-prev").addEventListener("click", async () => {
|
||||
if (state.dailyHistoryLoading || state.dailyDeleting) return;
|
||||
state.dailyHistoryOffset = Math.max(0, state.dailyHistoryOffset - HISTORY_PAGE_SIZE);
|
||||
await loadDailyHistory();
|
||||
});
|
||||
$("#daily-history-next").addEventListener("click", async () => {
|
||||
if (state.dailyHistoryLoading || state.dailyDeleting) return;
|
||||
state.dailyHistoryOffset += HISTORY_PAGE_SIZE;
|
||||
await loadDailyHistory();
|
||||
});
|
||||
$("#daily-delete-cancel").addEventListener("click", () => closeDailyDelete());
|
||||
$("#daily-delete-submit").addEventListener("click", confirmDailyDelete);
|
||||
$("#daily-delete-dialog").addEventListener("cancel", (event) => {
|
||||
event.preventDefault();
|
||||
closeDailyDelete();
|
||||
});
|
||||
$("#daily-price-review-body").addEventListener("click", (event) => {
|
||||
const save = event.target.closest("[data-daily-price-review-save]");
|
||||
if (save) saveDailyPriceReviewItem(save);
|
||||
|
||||
+39
-1
@@ -250,10 +250,41 @@
|
||||
"upload.stage_processor": ["正在运行固定处理器", "Running the fixed processor", "กำลังเรียกใช้ตัวประมวลผล"],
|
||||
"upload.stage_validate": ["正在独立验收结果", "Independently validating the result", "กำลังตรวจสอบผลอย่างอิสระ"],
|
||||
"upload.stage_commit": ["正在提交数据库", "Committing to the database", "กำลังบันทึกลงฐานข้อมูล"],
|
||||
"metric.arrival_date": ["最近成功处理日期", "Latest successful processing date", "วันที่ประมวลผลสำเร็จล่าสุด"],
|
||||
"metric.arrival_date": ["当前查看日期", "Selected report date", "วันที่รายงานที่เลือก"],
|
||||
"metric.duration": ["处理耗时", "Processing time", "เวลาประมวลผล"],
|
||||
"metric.duration_from_submit": ["从提交至完成", "From submission to completion", "ตั้งแต่ส่งจนเสร็จ"],
|
||||
"metric.rooms": ["保留记录的售出房数", "Sold rooms in retained records", "จำนวนห้องที่ขายในรายการที่เก็บไว้"],
|
||||
"daily.current": ["当前有效日报", "Current daily report", "รายงานรายวันที่ใช้อยู่"],
|
||||
"daily.no_current": ["无当前有效日报", "No current daily report", "ไม่มีรายงานรายวันที่ใช้อยู่"],
|
||||
"daily.history_only": ["仅保留历史版本", "Historical versions only", "มีเฉพาะรุ่นในประวัติ"],
|
||||
"daily.current_preserved": ["当前有效日报仍保留", "The current daily report remains valid", "รายงานรายวันปัจจุบันยังคงใช้ได้"],
|
||||
"daily.overview_review": ["待人工核对", "Awaiting manual review", "รอการตรวจสอบด้วยตนเอง"],
|
||||
"daily.overview_fetching": ["正在获取该日数据", "Fetching data for this date", "กำลังดึงข้อมูลของวันที่นี้"],
|
||||
"daily.overview_processing": ["正在生成该日日报", "Generating this daily report", "กำลังสร้างรายงานรายวันของวันที่นี้"],
|
||||
"daily.after_generation": ["待新日报生成后显示", "Shown after the new report is generated", "แสดงเมื่อสร้างรายงานใหม่แล้ว"],
|
||||
"daily.replacement": ["新版本", "New version", "รุ่นใหม่"],
|
||||
"daily.history_versions": ["历史记录({count})", "History ({count})", "ประวัติ ({count})"],
|
||||
"daily.historical": ["历史版本", "Historical version", "รุ่นในประวัติ"],
|
||||
"daily.deleted": ["已删除", "Deleted", "ลบแล้ว"],
|
||||
"daily.history_title": ["日报历史记录", "Daily report history", "ประวัติรายงานรายวัน"],
|
||||
"daily.history_date": ["{date} · 全部处理记录", "{date} · All processing records", "{date} · ประวัติการประมวลผลทั้งหมด"],
|
||||
"daily.history_empty": ["暂无历史记录", "No history records", "ยังไม่มีประวัติ"],
|
||||
"daily.history_note": ["每天仅展示当前有效日报;新版本处理成功后替换,月报同步更新。", "One current report per date. A successfully processed version replaces it and updates the monthly report.", "แสดงรายงานที่ใช้อยู่หนึ่งฉบับต่อวัน รุ่นใหม่จะแทนที่เมื่อประมวลผลสำเร็จและอัปเดตรายงานประจำเดือน"],
|
||||
"daily.history_dialog_note": ["旧版本仅供追溯,不计入月报。已删除记录保留操作痕迹,不会自动恢复。", "Old versions are kept for reference and excluded from the monthly report. Deleted records retain their audit history and are not restored automatically.", "เก็บรุ่นเก่าไว้ตรวจสอบย้อนหลังและไม่นับในรายงานประจำเดือน รายการที่ลบยังมีประวัติการดำเนินการและจะไม่กู้คืนอัตโนมัติ"],
|
||||
"daily.history_region": ["日报历史版本", "Daily report versions", "รุ่นของรายงานรายวัน"],
|
||||
"daily.history_pagination": ["日报历史分页", "Daily report history pages", "การแบ่งหน้าประวัติรายงานรายวัน"],
|
||||
"daily.view_log": ["查看日志", "View log", "ดูบันทึก"],
|
||||
"daily.delete_title": ["删除日报", "Delete daily report", "ลบรายงานรายวัน"],
|
||||
"daily.delete_current_description": ["删除 {date} 当前有效日报?该日数据将从月报中移除。", "Delete the current report for {date}? This date will be removed from the monthly report.", "ลบรายงานที่ใช้อยู่ของวันที่ {date} หรือไม่ ข้อมูลวันนี้จะถูกลบออกจากรายงานประจำเดือน"],
|
||||
"daily.delete_history_description": ["删除 {date} 的这条处理记录?当前有效日报和月报不变。", "Delete this processing record for {date}? The current daily and monthly reports will not change.", "ลบประวัติการประมวลผลนี้ของวันที่ {date} หรือไม่ รายงานรายวันปัจจุบันและรายงานประจำเดือนจะไม่เปลี่ยน"],
|
||||
"daily.delete_note": ["原始数据和操作记录保留;删除后不会自动恢复旧版本。", "Original data and audit history are retained. Deleting a report will not reactivate an older version.", "เก็บข้อมูลต้นฉบับและประวัติการดำเนินการไว้ การลบจะไม่เปิดใช้รุ่นเก่าโดยอัตโนมัติ"],
|
||||
"daily.confirm_delete": ["确认删除", "Confirm delete", "ยืนยันการลบ"],
|
||||
"daily.deleted_current": ["该日日报已移除,月报正在同步更新", "Daily report removed; the monthly report is updating", "ลบรายงานรายวันแล้ว กำลังอัปเดตรายงานประจำเดือน"],
|
||||
"daily.deleted_history": ["处理记录已删除,当前日报和月报保持不变", "Record deleted; the current daily and monthly reports are unchanged", "ลบประวัติแล้ว รายงานรายวันปัจจุบันและรายงานประจำเดือนไม่เปลี่ยน"],
|
||||
"arr_download.deleted": ["报表已删除,可重新获取该日数据", "Report deleted; you may fetch this date again", "ลบรายงานแล้ว สามารถดึงข้อมูลวันนี้ใหม่ได้"],
|
||||
"error.daily_delete_invalid": ["删除请求无效,请刷新日报列表后重试", "Invalid delete request. Refresh the daily list and try again.", "คำขอลบไม่ถูกต้อง โปรดรีเฟรชรายการรายวันแล้วลองใหม่"],
|
||||
"error.daily_delete_busy": ["这份报表正在处理,请完成后再删除", "This report is being processed. Try deleting it after processing finishes.", "รายงานนี้กำลังประมวลผล โปรดลองลบเมื่อประมวลผลเสร็จ"],
|
||||
"error.daily_delete_unavailable": ["日报删除服务暂不可用,请稍后重试", "Report deletion is temporarily unavailable. Try again later.", "บริการลบรายงานไม่พร้อมใช้งานชั่วคราว โปรดลองภายหลัง"],
|
||||
"daily.history": ["Daily Report", "Daily Report", "Daily Report"],
|
||||
"daily.refresh": ["刷新", "Refresh", "รีเฟรช"],
|
||||
"daily.download": ["日报", "Daily report", "รายงานรายวัน"],
|
||||
@@ -678,6 +709,13 @@
|
||||
};
|
||||
|
||||
const ERROR_KEYS = {
|
||||
DAILY_DELETE_REQUEST_INVALID: "error.daily_delete_invalid",
|
||||
DAILY_DATE_CONFLICT: "error.daily_delete_invalid",
|
||||
DAILY_JOB_BUSY: "error.daily_delete_busy",
|
||||
DAILY_RETIREMENT_UNAVAILABLE: "error.daily_delete_unavailable",
|
||||
DAILY_RETIREMENT_CONFLICT: "error.daily_delete_invalid",
|
||||
DAILY_JOB_NOT_FOUND: "error.not_found",
|
||||
JOB_RETIRED: "arr_download.deleted",
|
||||
ARR_DOWNLOAD_UNAVAILABLE: "arr_download.unavailable",
|
||||
ARR_DOWNLOAD_DATE_INVALID: "arr_download.date_invalid",
|
||||
ARR_DOWNLOAD_REQUEST_INVALID: "arr_download.request_invalid",
|
||||
|
||||
@@ -124,17 +124,18 @@
|
||||
<article class="metric-card">
|
||||
<span>ARRIVAL DATE</span>
|
||||
<strong id="metric-arrival">—</strong>
|
||||
<small>最近成功处理日期</small>
|
||||
<small id="metric-arrival-note">当前查看日期</small>
|
||||
<small id="metric-current-version" hidden>当前有效日报仍保留</small>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<span>处理耗时</span>
|
||||
<strong id="metric-duration">—</strong>
|
||||
<small>从提交至完成</small>
|
||||
<small id="metric-duration-note">从提交至完成</small>
|
||||
</article>
|
||||
<article class="metric-card accent-card">
|
||||
<span>NO. OF ROOM</span>
|
||||
<strong id="metric-rooms">—</strong>
|
||||
<small>保留记录的售出房数</small>
|
||||
<small id="metric-rooms-note">保留记录的售出房数</small>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
@@ -214,6 +215,7 @@
|
||||
<div class="panel-heading table-heading">
|
||||
<div>
|
||||
<h2 id="daily-history-title">Daily Report</h2>
|
||||
<p class="daily-history-note">每天仅展示当前有效日报;新版本处理成功后替换,月报同步更新。</p>
|
||||
</div>
|
||||
<div class="history-heading-actions">
|
||||
<div class="history-month-control" data-history-scope="jobs">
|
||||
@@ -228,7 +230,7 @@
|
||||
</div>
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead><tr><th>文件</th><th>状态</th><th>ARRIVAL DATE</th><th>NO. OF ROOM</th><th>处理耗时</th><th>提交时间</th><th>日报</th></tr></thead>
|
||||
<thead><tr><th>文件</th><th>状态</th><th>ARRIVAL DATE</th><th>NO. OF ROOM</th><th>处理耗时</th><th>提交时间</th><th>操作</th></tr></thead>
|
||||
<tbody id="jobs-body"><tr><td class="empty-cell" colspan="7">正在读取本月记录…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -510,6 +512,29 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<dialog class="daily-history-dialog" id="daily-history-dialog" aria-labelledby="daily-history-dialog-title" aria-describedby="daily-history-description">
|
||||
<div class="daily-history-dialog-heading">
|
||||
<div><h2 id="daily-history-dialog-title">日报历史记录</h2><p id="daily-history-description"></p></div>
|
||||
<button id="daily-history-close" type="button">关闭</button>
|
||||
</div>
|
||||
<p class="daily-history-dialog-note">旧版本仅供追溯,不计入月报。已删除记录保留操作痕迹,不会自动恢复。</p>
|
||||
<p id="daily-history-error" class="daily-delete-error" role="alert" hidden></p>
|
||||
<div class="table-scroll" tabindex="0" role="region" aria-label="日报历史版本">
|
||||
<table><thead><tr><th scope="col">文件</th><th scope="col">状态</th><th scope="col">NO. OF ROOM</th><th scope="col">提交时间</th><th scope="col">操作</th></tr></thead><tbody id="daily-history-body"></tbody></table>
|
||||
</div>
|
||||
<nav class="pagination-bar" aria-label="日报历史分页"><span id="daily-history-page-summary"></span><div class="pagination-actions"><button class="pagination-button" id="daily-history-prev" type="button">上一页</button><button class="pagination-button" id="daily-history-next" type="button">下一页</button></div></nav>
|
||||
</dialog>
|
||||
|
||||
<dialog class="daily-delete-dialog" id="daily-delete-dialog" aria-labelledby="daily-delete-title" aria-describedby="daily-delete-description daily-delete-note">
|
||||
<div class="daily-delete-card">
|
||||
<h2 id="daily-delete-title">删除日报</h2>
|
||||
<p id="daily-delete-description"></p>
|
||||
<p id="daily-delete-note">原始数据和操作记录保留;删除后不会自动恢复旧版本。</p>
|
||||
<p id="daily-delete-error" class="daily-delete-error" role="alert" hidden></p>
|
||||
<div class="daily-delete-actions"><button id="daily-delete-cancel" type="button">取消</button><button id="daily-delete-submit" type="button">确认删除</button></div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="task-log-dialog" id="task-log-dialog" aria-labelledby="process-title" aria-describedby="task-log-description">
|
||||
<section class="panel process-panel task-log-panel">
|
||||
<p class="sr-only" id="task-log-description">显示当前选中的日报处理任务从上传到下游通知的处理日志。</p>
|
||||
|
||||
@@ -390,6 +390,37 @@ button { color: inherit; }
|
||||
.text-button:hover { color: var(--blue); border-color: #a9c1f5; }
|
||||
.download-link { color: var(--blue); font-size: 12px; font-weight: 700; text-decoration: none; }
|
||||
.download-link:hover { text-decoration: underline; text-underline-offset: 3px; }
|
||||
.daily-history-note { max-width: 58ch; margin: 7px 0 0; color: var(--subtle); font-size: 12px; line-height: 1.6; }
|
||||
.daily-version-label { display: block; margin-top: 5px; color: var(--subtle); font-size: 11px; }
|
||||
.daily-row-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; }
|
||||
.daily-date-button, .daily-history-button, .daily-delete-button { padding: 5px 0; border: 0; border-radius: 4px; background: transparent; color: var(--blue); font: inherit; font-size: 12px; cursor: pointer; }
|
||||
.daily-date-button { font-size: 13px; font-variant-numeric: tabular-nums; }
|
||||
.daily-date-button[aria-pressed="true"] { font-weight: 750; text-decoration: underline; text-underline-offset: 4px; }
|
||||
.daily-date-button:hover, .daily-history-button:hover, .daily-delete-button:hover { text-decoration: underline; text-underline-offset: 3px; }
|
||||
.daily-delete-button { color: #b42318; }
|
||||
.daily-row-actions button:disabled { opacity: .45; cursor: wait; }
|
||||
.daily-date-button:focus-visible, .daily-history-button:focus-visible, .daily-delete-button:focus-visible { outline: 3px solid rgba(37, 99, 235, .2); outline-offset: 3px; }
|
||||
.daily-replacement { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 5px 9px; margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--line); }
|
||||
.daily-replacement > span { padding-top: 4px; color: var(--subtle); font-size: 11px; }
|
||||
#metric-current-version { color: #9a4b0a; line-height: 1.5; }
|
||||
.daily-history-dialog { width: min(1100px, calc(100% - 32px)); max-width: none; max-height: calc(100vh - 48px); padding: 0; overflow: auto; border: 1px solid var(--line); border-radius: 16px; color: var(--ink); background: var(--surface); box-shadow: 0 28px 80px rgba(16, 24, 40, .3); }
|
||||
.daily-history-dialog::backdrop, .daily-delete-dialog::backdrop { background: rgba(15, 23, 42, .48); }
|
||||
.daily-history-dialog-heading { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; padding: 24px 26px 12px; }
|
||||
.daily-history-dialog h2, .daily-delete-dialog h2 { margin: 0; font-size: 20px; }
|
||||
.daily-history-dialog-heading p { margin: 8px 0 0; color: var(--blue); font-size: 13px; }
|
||||
.daily-history-dialog-heading > button, .daily-delete-actions button { min-height: 40px; padding: 9px 16px; border: 1px solid var(--line); border-radius: 8px; background: white; color: var(--ink); font: inherit; font-size: 13px; cursor: pointer; }
|
||||
.daily-history-dialog-note { margin: 0; padding: 0 26px 20px; color: var(--subtle); font-size: 12px; line-height: 1.6; }
|
||||
.daily-history-dialog table { min-width: 650px; }
|
||||
.daily-delete-dialog { width: min(460px, calc(100% - 32px)); max-width: none; padding: 0; border: 1px solid var(--line); border-radius: 16px; color: var(--ink); background: var(--surface); box-shadow: 0 28px 80px rgba(16, 24, 40, .3); }
|
||||
.daily-delete-card { padding: 26px; }
|
||||
.daily-delete-card > p { margin: 16px 0 0; font-size: 14px; line-height: 1.7; }
|
||||
#daily-delete-note { color: var(--subtle); font-size: 12px; }
|
||||
.daily-delete-error { margin: 0; padding: 12px 26px; color: #b42318; font-size: 13px; line-height: 1.6; }
|
||||
.daily-delete-card .daily-delete-error { padding: 0; }
|
||||
.daily-delete-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 24px; }
|
||||
#daily-delete-submit { border-color: #b42318; color: white; background: #b42318; }
|
||||
.daily-delete-actions button:disabled { opacity: .5; cursor: wait; }
|
||||
.daily-delete-actions button:focus-visible, .daily-history-dialog-heading > button:focus-visible { outline: 3px solid rgba(37, 99, 235, .2); outline-offset: 3px; }
|
||||
.table-scroll { overflow: auto; border-top: 1px solid var(--line); }
|
||||
.pagination-bar { min-height: 58px; padding: 11px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-top: 1px solid var(--line); background: #fbfcfe; }
|
||||
.pagination-summary, .pagination-page { color: var(--muted); font-size: 11px; font-weight: 650; font-variant-numeric: tabular-nums; }
|
||||
|
||||
Reference in new issue
Block a user