Simplify daily review UI and compact header cards
This commit is contained in:
1 parent
6d1a98743f
commit
64f4a0d3de
13 files changed
+185
-133
No files matched your search
+12
-68
@@ -522,11 +522,12 @@
|
||||
dateInput.disabled = locked;
|
||||
$("#arr-date-toggle").disabled = locked;
|
||||
$("#arr-date-toggle").setAttribute("aria-label", I18N.t("arr_download.choose_date"));
|
||||
$("#arr-download-help").textContent = I18N.t(locked ? "arr_download.submitting"
|
||||
const helpKey = locked ? "arr_download.submitting"
|
||||
: arrDownloadActive() ? "arr_download.date_next"
|
||||
: arrDownloadNeedsDataReview() || task?.status === "needs_review" ? "arr_download.date_independent"
|
||||
: reconciling ? "arr_download.date_resume" : "arr_download.help",
|
||||
{ date: state.arrDownloadIntent?.report_date });
|
||||
: arrDownloadNeedsDataReview() || task?.status === "needs_review" ? ""
|
||||
: reconciling ? "arr_download.date_resume" : "arr_download.help";
|
||||
$("#arr-download-help").textContent = helpKey ? I18N.t(helpKey, { date: state.arrDownloadIntent?.report_date }) : "";
|
||||
$("#arr-download-help").hidden = !helpKey;
|
||||
if (locked) closeARRCalendar();
|
||||
button.disabled = !state.arrDownloadLoaded || !state.arrDownloadReady || locked || arrDownloadActive() || state.arrDataReviewFinalizing
|
||||
|| (!reconciling && !arrDownloadCanResume() && !validARRDate(dateInput.value))
|
||||
@@ -555,8 +556,6 @@
|
||||
const taskDate = viewingAnotherDate ? dateInput.value : task?.report_date || state.arrDownloadIntent?.report_date;
|
||||
$("#arr-download-status").textContent = taskDate ? `${taskDate} · ${text}` : text;
|
||||
$("#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);
|
||||
@@ -815,25 +814,6 @@
|
||||
return Array.isArray(item.value) ? item.value.join("\n") : String(item.value ?? "");
|
||||
}
|
||||
|
||||
function arrDataReviewReasonKey(item) {
|
||||
if (item.confirmed) return "confirmed_value";
|
||||
const sourceState = String(item.source_state || "").toLowerCase();
|
||||
const reason = String(item.reason_code || "").toUpperCase();
|
||||
// Older review snapshots do not expose source_state. Recognize only the
|
||||
// collector's explicit failure codes; a missing optional field is not a
|
||||
// failed request and does not make the field required.
|
||||
const failedReasons = ["HTTP_FAILURE", "HTTP_PERMISSION_DENIED", "TRANSPORT_RETRY_EXHAUSTED",
|
||||
"REQUEST_LIMIT_EXCEEDED", "RESPONSE_TOO_LARGE", "OPERATION_MISMATCH", "HOTEL_MISMATCH",
|
||||
"INVALID_DATA_ENVELOPE", "MISSING_ORACLE_REQUEST_ID", "SECRET_IN_RESPONSE", "INVALID_JSON",
|
||||
"UPSTREAM_WARNING_OR_ERROR"];
|
||||
if (sourceState === "failed" || failedReasons.includes(reason)) return "fetch_failed";
|
||||
if (sourceState === "ambiguous" || /(?:^|_)(?:CONFLICT|AMBIGUOUS|MISMATCH|DISAGREEMENT|CHANGED)(?:_|$)/.test(reason)) return "conflict_value";
|
||||
if (/(?:^|_)(?:INVALID|MALFORMED)(?:_|$)/.test(reason) || ["XML_NEGATIVE_NIGHTS", "DATA_NEGATIVE_NIGHTS"].includes(reason)) return "invalid_value";
|
||||
if (item.field === "DISP_ROOM_NO" && !item.can_be_empty
|
||||
&& ["CALENDAR_MISSING_ROOMS", "DATA_ROOM_MISSING", "XML_ROOM_MISSING"].includes(reason)) return "room_missing_in_scope";
|
||||
return item.can_be_empty ? "source_unconfirmed" : "required_value_missing";
|
||||
}
|
||||
|
||||
function arrDataReviewCanFinalize(review = state.arrDataReview) {
|
||||
return Boolean(arrDownloadNeedsDataReview() && state.arrDownloadTask.request_id === state.arrDataReviewRequestId
|
||||
&& !state.arrDownloadBusy && review && review.status === "editing" && review.can_finalize && Number(review.pending_count) === 0
|
||||
@@ -879,16 +859,6 @@
|
||||
const editable = review?.status === "editing" && !busy && !state.arrDataReviewDisconnected && !state.arrDataReviewFinalizing;
|
||||
panel.setAttribute("aria-busy", String(busy));
|
||||
$("#arr-data-review-title").textContent = I18N.t("data_review.title");
|
||||
$("#arr-data-review-copy").textContent = I18N.t(state.arrDataReviewFinalizing || review?.status === "finalized" ? "data_review.finalizing_copy" : "data_review.copy");
|
||||
$("#arr-data-review-progress").textContent = review ? I18N.t("data_review.progress", {
|
||||
date: review.report_date, completed: Number(review.total_count) - Number(review.pending_count), total: review.total_count,
|
||||
}) : I18N.t("data_review.loading");
|
||||
if (Number.isInteger(review?.excluded_cancelled_count) && review.excluded_cancelled_count > 0) {
|
||||
$("#arr-data-review-progress").textContent += ` · ${I18N.t("data_review.excluded_cancelled", { count: review.excluded_cancelled_count })}`;
|
||||
}
|
||||
if (Number.isInteger(review?.excluded_pm_count) && review.excluded_pm_count > 0) {
|
||||
$("#arr-data-review-progress").textContent += ` · ${I18N.t("data_review.excluded_pm", { count: review.excluded_pm_count })}`;
|
||||
}
|
||||
$("#arr-data-review-refresh").textContent = I18N.t("data_review.refresh");
|
||||
$("#arr-data-review-refresh").disabled = busy;
|
||||
const finalize = $("#arr-data-review-finalize");
|
||||
@@ -901,8 +871,6 @@
|
||||
const labelKey = `data_review.field_${field.toLowerCase()}`;
|
||||
const translatedLabel = I18N.t(labelKey);
|
||||
const label = translatedLabel === labelKey ? (item.field_label || field) : translatedLabel;
|
||||
const reasonKey = arrDataReviewReasonKey(item);
|
||||
const ruleKey = item.can_be_empty ? "optional_field_rule" : "required_field_rule";
|
||||
const inputId = `arr-data-review-value-${index}`;
|
||||
const value = Object.prototype.hasOwnProperty.call(state.arrDataReviewDrafts, item.item_id)
|
||||
? state.arrDataReviewDrafts[item.item_id] : arrDataReviewInputValue(item);
|
||||
@@ -912,8 +880,8 @@
|
||||
? `<textarea id="${inputId}" data-arr-data-review-value rows="2" placeholder="${escapeHtml(placeholder)}" ${editable ? "" : "disabled"}>${escapeHtml(value)}</textarea>`
|
||||
: `<input id="${inputId}" data-arr-data-review-value type="text" inputmode="${inputMode}" autocomplete="off" value="${escapeHtml(value)}" placeholder="${escapeHtml(placeholder)}" ${editable ? "" : "disabled"} />`;
|
||||
return `<tr data-arr-data-review-item-id="${escapeHtml(item.item_id)}" class="${item.confirmed ? "is-complete" : ""}">
|
||||
<td data-label="${escapeHtml(I18N.t("data_review.reservation"))}"><strong>${escapeHtml(item.confirmation_no || "—")}</strong><small>${escapeHtml(I18N.t("data_review.source_row", { sequence: item.source_sequence }))} · ${escapeHtml(I18N.t("data_review.room", { room: item.room_no || "—" }))}</small><small>${escapeHtml(item.company_name || "—")} · ${escapeHtml(item.rate_code || "—")}</small></td>
|
||||
<td data-label="${escapeHtml(I18N.t("data_review.field"))}"><label for="${inputId}">${escapeHtml(label)}</label><small>${escapeHtml(I18N.t(`data_review.${ruleKey}`))}</small><small>${escapeHtml(I18N.t(`data_review.${reasonKey}`))}</small></td>
|
||||
<td data-label="${escapeHtml(I18N.t("data_review.reservation"))}"><strong>${escapeHtml(item.confirmation_no || "—")}</strong></td>
|
||||
<td data-label="${escapeHtml(I18N.t("data_review.field"))}"><label for="${inputId}">${escapeHtml(label)}</label></td>
|
||||
<td data-label="${escapeHtml(I18N.t("data_review.value"))}">${input}</td>
|
||||
<td class="arr-data-review-row-actions" data-label="${escapeHtml(I18N.t("common.operation"))}"><span class="arr-data-review-item-status">${escapeHtml(I18N.t(item.confirmed ? "data_review.confirmed" : "data_review.pending"))}</span><button type="button" data-arr-data-review-save ${editable ? "" : "disabled"}>${escapeHtml(I18N.t("data_review.save"))}</button>${item.can_be_empty ? `<button type="button" data-arr-data-review-empty ${editable ? "" : "disabled"}>${escapeHtml(I18N.t("data_review.confirm_empty"))}</button>` : ""}</td>
|
||||
</tr>`;
|
||||
@@ -1695,6 +1663,8 @@
|
||||
? `<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>` : "";
|
||||
const historyButton = Number(day.history_count) > 0
|
||||
? `<button class="daily-history-button" type="button" data-daily-history="${escapeHtml(date)}">${escapeHtml(I18N.t("daily.history_versions", { count: formatInteger(day.history_count) }))}</button>` : "";
|
||||
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>
|
||||
@@ -1702,7 +1672,7 @@
|
||||
<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>
|
||||
<td><div class="daily-row-actions">${dailyJobDownload(current)}${deleteButton}${historyButton}</div></td>
|
||||
</tr>`;
|
||||
}).join("");
|
||||
renderDailyOverview();
|
||||
@@ -1869,17 +1839,6 @@
|
||||
: String(value);
|
||||
}
|
||||
|
||||
function reviewCandidateMarkup(candidates) {
|
||||
if (!Array.isArray(candidates) || !candidates.length) {
|
||||
return `<span class="daily-price-review-none">${escapeHtml(I18N?.t("daily_review.no_candidates") || "暂无同公司 / Rate Code 固定价")}</span>`;
|
||||
}
|
||||
return candidates.map((candidate) => {
|
||||
const opera = reviewPrice(candidate?.effective_rate_amount);
|
||||
const price = reviewPrice(candidate?.real_price);
|
||||
return `<span class="daily-price-review-candidate">Opera ${escapeHtml(opera)} → ${escapeHtml(price)}</span>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function strictReviewPrice(value) {
|
||||
return /^(?:0|[1-9]\d{0,15})$/.test(String(value ?? "").trim());
|
||||
}
|
||||
@@ -1950,18 +1909,9 @@
|
||||
const editable = dailyPriceReviewEditable(review);
|
||||
const retryable = review.case_status === "generation_failed";
|
||||
const processing = review.case_status === "processing";
|
||||
const completed = Number(review.completed_items || 0);
|
||||
const total = Number(review.total_items || 0);
|
||||
panel.classList.toggle("is-busy", busy);
|
||||
panel.classList.toggle("is-retryable", retryable);
|
||||
panel.classList.toggle("is-processing", processing);
|
||||
$("#daily-price-review-completed").textContent = formatInteger(completed);
|
||||
$("#daily-price-review-pending").textContent = formatInteger(Math.max(0, total - completed));
|
||||
const reviewCopy = $("#daily-price-review-copy");
|
||||
reviewCopy.hidden = retryable;
|
||||
reviewCopy.textContent = processing
|
||||
? (I18N?.t("daily_review.processing_instruction") || "正在使用已冻结的价格清单重放原始 XML;此时无法修改。")
|
||||
: (I18N?.t("daily_review.default_instruction") || "请填写每晚处理价,可填 0。每项填写后点击“暂存”,全部暂存后生成日报。");
|
||||
const finalize = $("#daily-price-review-finalize");
|
||||
const cancel = $("#daily-price-review-cancel");
|
||||
finalize.disabled = busy || !dailyPriceReviewCanFinalize(review);
|
||||
@@ -1979,9 +1929,6 @@
|
||||
company: I18N?.t("common.company") || "公司",
|
||||
rateCode: I18N?.t("daily_review.rate_code") || "Rate Code",
|
||||
operaPrice: I18N?.t("daily_review.opera_price") || "Opera 价",
|
||||
candidates: I18N?.t("daily_review.fixed_candidates") || "固定价候选",
|
||||
affectedRecords: I18N?.t("daily_review.affected_records") || "影响记录",
|
||||
roomsNights: I18N?.t("daily_review.rooms_nights") || "房数 / 房晚",
|
||||
manualPrice: I18N?.t("daily_review.manual_price") || "人工价格",
|
||||
actions: I18N?.t("common.operation") || "操作",
|
||||
};
|
||||
@@ -1993,18 +1940,16 @@
|
||||
const inputLabel = I18N?.t("daily_review.manual_price_label", {
|
||||
company: priceKey,
|
||||
rateCode: item.rate_code || "",
|
||||
operaPrice: reviewPrice(item.effective_rate_amount),
|
||||
}) || `${priceKey} ${item.rate_code || ""} 的人工价格`;
|
||||
return `
|
||||
<tr class="daily-price-review-row${saved ? " is-complete" : ""}" data-daily-price-review-item-id="${Number(item.item_id)}">
|
||||
<td data-label="${escapeHtml(labels.company)}"><strong>${escapeHtml(item.company_key || "—")}</strong></td>
|
||||
<td data-label="${escapeHtml(labels.rateCode)}"><code>${escapeHtml(item.rate_code || "—")}</code></td>
|
||||
<td data-label="${escapeHtml(labels.operaPrice)}">${escapeHtml(reviewPrice(item.effective_rate_amount))}</td>
|
||||
<td class="daily-price-review-candidates" data-label="${escapeHtml(labels.candidates)}">${reviewCandidateMarkup(item.candidate_prices)}</td>
|
||||
<td data-label="${escapeHtml(labels.affectedRecords)}">${formatInteger(item.affected_records)}</td>
|
||||
<td data-label="${escapeHtml(labels.roomsNights)}">${formatInteger(item.affected_rooms)} / ${formatInteger(item.affected_room_nights)}</td>
|
||||
<td data-label="${escapeHtml(labels.manualPrice)}">
|
||||
<label class="sr-only" for="${inputId}">${escapeHtml(inputLabel)}</label>
|
||||
<input class="daily-price-review-input" id="${inputId}" data-daily-price-review-price value="${escapeHtml(reviewPriceInput(item.real_price))}" inputmode="numeric" autocomplete="off" pattern="(?:0|[1-9][0-9]{0,15})" placeholder="${escapeHtml(I18N?.t("daily_review.price_placeholder") || "请输入价格(可填 0)")}" aria-describedby="daily-price-review-copy" ${disabled ? "disabled" : ""} />
|
||||
<input class="daily-price-review-input" id="${inputId}" data-daily-price-review-price value="${escapeHtml(reviewPriceInput(item.real_price))}" inputmode="numeric" autocomplete="off" pattern="(?:0|[1-9][0-9]{0,15})" placeholder="${escapeHtml(I18N?.t("daily_review.price_placeholder") || "请输入价格(可填 0)")}" aria-describedby="daily-price-review-message" ${disabled ? "disabled" : ""} />
|
||||
</td>
|
||||
<td class="daily-price-review-row-actions" data-label="${escapeHtml(labels.actions)}">
|
||||
<span class="daily-price-review-status${saved ? " is-complete" : " is-pending"}">${escapeHtml(saved ? (I18N?.t("daily_review.saved") || "已暂存") : (I18N?.t("daily_review.awaiting_input") || "待填写"))}</span>
|
||||
@@ -4026,7 +3971,6 @@
|
||||
if (!jobId) return;
|
||||
try { await openDailyPriceReview(jobId, true); } catch (error) { showToast(error.message, true); }
|
||||
});
|
||||
$("#arr-download-data-review").addEventListener("click", () => loadARRDataReview(state.arrDownloadTask?.request_id, { scroll: true }));
|
||||
$("#arr-download-pending-list").addEventListener("click", (event) => {
|
||||
const button = event.target.closest("[data-arr-pending-review]");
|
||||
if (button) selectARRPendingReview(button.dataset.arrPendingReview);
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
"arr_download.date_resume": ["可先选择日期;请先确认或继续 {date} 的原任务。", "You can choose a date. First check or resume the original task for {date}.", "เลือกวันที่ได้ แต่โปรดตรวจสอบหรือดำเนินงานเดิมของวันที่ {date} ต่อก่อน"],
|
||||
"arr_download.help": ["选择一天,自动获取到店数据并生成报表。", "Choose one day to fetch arrivals and generate reports.", "เลือกหนึ่งวันเพื่อดาวน์โหลดและประมวลผลรายงานผู้เข้าพัก"],
|
||||
"arr_download.start": ["下载并处理", "Download & process", "ดาวน์โหลดและประมวลผล"],
|
||||
"arr_acquisition.label": ["取数状态", "Data retrieval", "สถานะการดึงข้อมูล"],
|
||||
"arr_acquisition.label": ["状态", "Status", "สถานะ"],
|
||||
"arr_acquisition.queued": ["等待取数", "Queued", "รอดึงข้อมูล"],
|
||||
"arr_acquisition.active": ["取数中", "Retrieving", "กำลังดึงข้อมูล"],
|
||||
"arr_acquisition.completed": ["已完成", "Completed", "เสร็จแล้ว"],
|
||||
@@ -140,7 +140,6 @@
|
||||
"arr_download.needs_review": ["等待人工价格复核", "Awaiting price review", "รอตรวจสอบราคาด้วยตนเอง"],
|
||||
"arr_download.needs_data_review": ["等待人工完善报表字段", "Awaiting source field review", "รอตรวจสอบข้อมูลรายงานด้วยตนเอง"],
|
||||
"arr_download.complete_data": ["完善报表字段", "Complete report fields", "กรอกข้อมูลรายงานให้ครบ"],
|
||||
"arr_download.date_independent": ["可选其他日期下载,当前待处理内容会保留。", "Choose another date to download; this review will be kept.", "เลือกวันอื่นเพื่อดาวน์โหลดได้ ข้อมูลที่รอตรวจสอบจะยังคงอยู่"],
|
||||
"arr_download.pending_dates": ["需处理日期", "Dates requiring attention", "วันที่ต้องดำเนินการ"],
|
||||
"arr_download.pending_date": ["待补字段", "Complete fields", "รอกรอกข้อมูล"],
|
||||
"arr_download.pending_price": ["待复核价格", "Review prices", "รอตรวจสอบราคา"],
|
||||
@@ -343,7 +342,7 @@
|
||||
"daily_review.save_before_switch": ["当前价格尚未暂存,请先暂存,再切换需处理日期。", "Save the current price edits before switching to another review.", "โปรดบันทึกราคาที่แก้ไขก่อนเปลี่ยนไปตรวจสอบวันที่อื่น"],
|
||||
"daily_review.no_candidates": ["暂无同公司 / Rate Code 固定价", "No fixed price for the same company / Rate Code", "ไม่มีราคาคงที่สำหรับบริษัท / Rate Code เดียวกัน"],
|
||||
"daily_review.price_key": ["价格键", "price key", "คีย์ราคา"],
|
||||
"daily_review.manual_price_label": ["{company} {rateCode} 的人工价格", "Manual price for {company} {rateCode}", "ราคาที่กำหนดด้วยตนเองสำหรับ {company} {rateCode}"],
|
||||
"daily_review.manual_price_label": ["{company} {rateCode}(Opera 价 {operaPrice})的人工价格", "Manual price for {company} {rateCode} (Opera {operaPrice})", "ราคาที่กำหนดด้วยตนเองสำหรับ {company} {rateCode} (Opera {operaPrice})"],
|
||||
"daily_review.saved": ["已暂存", "Saved", "บันทึกแล้ว"],
|
||||
"daily_review.awaiting_input": ["待填写", "Not entered", "รอกรอก"],
|
||||
"daily_review.save": ["暂存", "Save", "บันทึก"],
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
<div class="arr-acquisition-progress" id="arr-acquisition-progress" hidden>
|
||||
<div class="arr-acquisition-meta">
|
||||
<span><span id="arr-acquisition-label">取数状态</span><strong id="arr-acquisition-status" role="status" aria-live="polite"></strong></span>
|
||||
<span><span id="arr-acquisition-label">状态</span><strong id="arr-acquisition-status" role="status" aria-live="polite"></strong></span>
|
||||
<strong id="arr-acquisition-percent" hidden></strong>
|
||||
</div>
|
||||
<div class="arr-acquisition-track" id="arr-acquisition-track" role="progressbar" aria-labelledby="arr-acquisition-label" aria-describedby="arr-acquisition-detail" aria-valuemin="0" aria-valuemax="100">
|
||||
@@ -85,13 +85,13 @@
|
||||
<p id="arr-acquisition-detail"></p>
|
||||
</div>
|
||||
<div class="arr-download-actions">
|
||||
<button class="arr-download-review" id="arr-download-data-review" type="button" hidden>完善报表字段</button>
|
||||
<button class="arr-download-review" id="arr-download-review" type="button" hidden>查看价格复核</button>
|
||||
<button class="primary-button" id="arr-download-button" type="submit" disabled>下载并处理</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<section class="panel upload-panel" aria-labelledby="upload-title">
|
||||
<div class="upload-content">
|
||||
<div class="panel-heading upload-heading">
|
||||
<div>
|
||||
<h2 id="upload-title">上传 ARR.XML</h2>
|
||||
@@ -115,6 +115,7 @@
|
||||
<span id="upload-progress-bar"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-footer">
|
||||
<p class="service-hint" id="upload-hint" role="status"></p>
|
||||
<button class="primary-button" id="upload-button" type="button" disabled>开始处理</button>
|
||||
@@ -176,13 +177,7 @@
|
||||
<p id="manual-review-context" role="status" aria-live="polite"></p>
|
||||
</header>
|
||||
<section class="arr-data-review-panel" id="arr-data-review-panel" aria-labelledby="arr-data-review-title" tabindex="-1" hidden>
|
||||
<header class="arr-data-review-header">
|
||||
<div>
|
||||
<h3 class="sr-only" id="arr-data-review-title">完善报表字段</h3>
|
||||
<p id="arr-data-review-copy">请核实并逐项保存缺失或有冲突的字段。允许为空的字段也须明确确认,全部完成后才能生成日报。</p>
|
||||
</div>
|
||||
<p id="arr-data-review-progress" role="status" aria-live="polite"></p>
|
||||
</header>
|
||||
<h3 class="sr-only" id="arr-data-review-title">完善报表字段</h3>
|
||||
<div class="arr-data-review-toolbar">
|
||||
<p id="arr-data-review-message" role="status" aria-live="polite"></p>
|
||||
<div class="arr-data-review-actions">
|
||||
@@ -200,16 +195,7 @@
|
||||
</section>
|
||||
|
||||
<section class="daily-price-review-panel" id="daily-price-review-panel" aria-labelledby="daily-price-review-title" tabindex="-1" hidden>
|
||||
<header class="daily-price-review-header">
|
||||
<div class="daily-price-review-title-block">
|
||||
<h3 class="sr-only" id="daily-price-review-title">价格复核</h3>
|
||||
<p id="daily-price-review-copy" role="status">请为所有缺少固定价的价格键填写每晚价格。相同键只需填写一次。</p>
|
||||
</div>
|
||||
<dl class="daily-price-review-metrics" aria-label="人工价格复核进度">
|
||||
<div><dt>已填写</dt><dd id="daily-price-review-completed">0</dd></div>
|
||||
<div class="is-review"><dt>待处理</dt><dd id="daily-price-review-pending">0</dd></div>
|
||||
</dl>
|
||||
</header>
|
||||
<h3 class="sr-only" id="daily-price-review-title">价格复核</h3>
|
||||
<div class="daily-price-review-toolbar">
|
||||
<p class="daily-price-review-message" id="daily-price-review-message" role="status"></p>
|
||||
<div class="daily-price-review-actions">
|
||||
@@ -220,7 +206,7 @@
|
||||
<div class="daily-price-review-scroll" role="region" aria-label="待人工定价项" tabindex="0">
|
||||
<table class="daily-price-review-table">
|
||||
<thead>
|
||||
<tr><th scope="col">公司</th><th scope="col">Rate Code</th><th scope="col">Opera 价</th><th scope="col">固定价候选</th><th scope="col">影响记录</th><th scope="col">房数 / 房晚</th><th scope="col">人工价格</th><th scope="col">操作</th></tr>
|
||||
<tr><th scope="col">公司</th><th scope="col">Rate Code</th><th scope="col">Opera 价</th><th scope="col">人工价格</th><th scope="col">操作</th></tr>
|
||||
</thead>
|
||||
<tbody id="daily-price-review-body"></tbody>
|
||||
</table>
|
||||
|
||||
+26
-13
@@ -158,7 +158,7 @@ button { color: inherit; }
|
||||
.panel-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; padding: 22px 24px 0; }
|
||||
.panel-heading h2 { margin: 4px 0 0; font-size: 18px; line-height: 1.3; letter-spacing: -.025em; }
|
||||
|
||||
.daily-overview { display: grid; grid-template-columns: minmax(220px, 1.15fr) minmax(230px, 1.2fr) minmax(155px, .8fr) minmax(125px, .65fr) minmax(220px, 1.15fr); gap: 16px; align-items: stretch; margin-bottom: 20px; }
|
||||
.daily-overview { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.25fr) minmax(0, .85fr) minmax(0, .7fr) minmax(0, 1.2fr); gap: 12px; align-items: stretch; margin-bottom: 20px; }
|
||||
.daily-overview > * { min-width: 0; }
|
||||
.daily-overview > .upload-panel { display: flex; flex-direction: column; }
|
||||
.daily-overview .upload-footer { margin-top: auto; }
|
||||
@@ -273,7 +273,7 @@ button { color: inherit; }
|
||||
.daily-overview > .metric-card { align-self: stretch; display: flex; flex-direction: column; justify-content: center; height: auto; min-height: 0; padding: 24px 22px; }
|
||||
.daily-overview > .metric-card::after { right: -24px; bottom: -30px; width: 82px; height: 82px; border-width: 12px; }
|
||||
.daily-overview > .arrival-metric { padding-left: 16px; padding-right: 16px; }
|
||||
.daily-overview > .arrival-metric strong { font-size: clamp(20px, 1.5vw, 24px); }
|
||||
.daily-overview > .arrival-metric strong { font-size: clamp(18px, 1.3vw, 22px); white-space: nowrap; }
|
||||
.report-calendar { position: relative; display: flex; flex-direction: column; justify-content: center; padding: 22px; overflow: visible; }
|
||||
.report-calendar h2 { margin: 0 0 16px; font-size: 17px; line-height: 1.4; letter-spacing: -.02em; }
|
||||
.report-calendar button { min-height: 44px; border: 1px solid var(--line); border-radius: 8px; color: var(--ink); background: #fff; cursor: pointer; }
|
||||
@@ -514,7 +514,7 @@ tbody tr:hover td { background: #fafcff; }
|
||||
.daily-price-review-finalize:hover:not(:disabled) { border-color: #854507; background: #854507; }
|
||||
.daily-price-review-cancel:disabled, .daily-price-review-finalize:disabled { border-color: #d7dde7; color: #667085; background: #e9edf3; box-shadow: none; cursor: not-allowed; }
|
||||
.daily-price-review-scroll { max-width: 100%; overflow: auto; }
|
||||
.daily-price-review-table { width: 100%; min-width: 1080px; border-collapse: collapse; }
|
||||
.daily-price-review-table { width: 100%; min-width: 660px; border-collapse: collapse; }
|
||||
.daily-price-review-table th { padding: 10px 12px; color: #735d39; background: #fffaf0; font-size: 9px; font-weight: 800; letter-spacing: .04em; text-align: left; text-transform: uppercase; white-space: nowrap; }
|
||||
.daily-price-review-table td { padding: 10px 12px; border-top: 1px solid #f0f1f4; color: var(--ink); font-size: 10px; vertical-align: middle; }
|
||||
.daily-price-review-row:hover td { background: #fffdf8; }
|
||||
@@ -822,16 +822,18 @@ input:focus, select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgb
|
||||
}
|
||||
|
||||
@media (min-width: 701px) {
|
||||
.daily-overview .upload-heading { padding: 12px 18px 0; }
|
||||
.daily-overview .upload-heading h2 { font-size: 17px; }
|
||||
.daily-overview > .upload-panel { position: relative; justify-content: center; min-height: 260px; }
|
||||
.daily-overview .upload-content { padding-bottom: 0; }
|
||||
.daily-overview .upload-heading { padding: 0 18px; }
|
||||
.daily-overview .upload-heading h2 { margin: 0; font-size: 17px; line-height: 1.4; }
|
||||
.daily-overview .dropzone {
|
||||
min-height: 72px;
|
||||
margin: 8px 18px 0;
|
||||
padding: 10px 12px;
|
||||
margin: 16px 18px 0;
|
||||
padding: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: 28px minmax(0, 1fr);
|
||||
grid-template-columns: 20px minmax(0, 1fr);
|
||||
grid-template-rows: repeat(3, min-content);
|
||||
column-gap: 10px;
|
||||
column-gap: 8px;
|
||||
row-gap: 0;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
@@ -839,11 +841,11 @@ input:focus, select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgb
|
||||
}
|
||||
.daily-overview .dropzone .upload-glyph {
|
||||
grid-row: 1 / span 3;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 9px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.daily-overview .dropzone .upload-glyph svg { width: 16px; height: 16px; }
|
||||
.daily-overview .dropzone .upload-glyph svg { width: 14px; height: 14px; }
|
||||
.daily-overview .dropzone strong {
|
||||
margin-top: 0;
|
||||
font-size: 12px;
|
||||
@@ -868,6 +870,10 @@ input:focus, select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgb
|
||||
.daily-overview .upload-progress-meta strong { font-size: 10px; }
|
||||
.daily-overview .upload-progress-track { height: 5px; margin-top: 6px; }
|
||||
.daily-overview .upload-footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
min-height: 44px;
|
||||
margin: 0 18px;
|
||||
margin-top: auto;
|
||||
@@ -884,6 +890,13 @@ input:focus, select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgb
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.daily-overview { width: calc(100% * 2 / 3); }
|
||||
.daily-overview > .metric-card { padding-left: 14px; padding-right: 14px; }
|
||||
.daily-overview > .arrival-metric strong { font-size: 18px; }
|
||||
.report-calendar { padding-left: 18px; padding-right: 18px; }
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.shell { width: min(100% - 28px, 1400px); }
|
||||
.masthead { height: 64px; }
|
||||
|
||||
Reference in new issue
Block a user