Simplify daily review UI and compact header cards

This commit is contained in:
Wyndham ARR committed 2026-10-09 14:30:41 +08:00
1 parent 6d1a98743f
commit 64f4a0d3de
13 files changed
+185 -133

No files matched your search

+4 -2
View File
@@ -14,6 +14,8 @@
任务结果待确认时,先检查或继续按钮上标明日期的原任务,避免重复提交。
某一天等待人工完善不阻塞其他日期:另选日期后可点击“下载并处理”,此前字段或价格任务仍保存在“需处理日期”中。
点击需处理日期可返回该日的“人工核对”,刷新后列表仍保留;同一天的未完成任务沿用原编号。
人工核对页面只展示预订编号、字段名、输入和确认操作;价格核对保留公司、费率代码、Opera价、人工价格和保存操作。页面不展示来源/规则解释或取消、PM排除数量,相关原始数据和审计仍保留。字段完善在下载卡片仅保留蓝色主按钮,必填/留空限制、保存校验和生成门槛不变。
切换字段任务保留各自草稿,未保存的价格会阻止切换核对任务;刷新或切换语言前仍应保存输入,已保存决定长期保留。
完整采集中的缺字段、多个候选或不满足原规则的字段先进入“待完善数据”(`needs_data_review`)。
全部阻塞字段通过并确认后,原任务自动继续处理;随后只有缺少pureprice时进入原有价格复核(`needs_review`)。
@@ -35,11 +37,11 @@
点击列表中的日期,可查看该日概览;上方日期与房数始终对应当前查看日期。待处理新版本会单独提示,尚无有效日报时显示空值,不借用其他日期的成功结果。
顶部概览提供“自动下载 ARR、上传 ARR.XML、ARRIVAL DATE、NO. OF ROOM、本月报表情况”五个区域,顶部不再展示处理耗时。“本月报表情况”默认收起,显示月份和已生成天数;点击带日历图标的月份按钮展开月历,支持切换月份。每一天按是否存在当前有效日报标为“已生成”或“未生成”,已有报表的日期显示为绿色。历史版本、已删除版本和仅待核对的任务不算已生成;已有有效日报、另有待核对新版本时,仍标为已生成。读取中或读取失败会单独显示,不把未知状态当作未生成。
顶部概览提供“自动下载 ARR、上传 ARR.XML、ARRIVAL DATE、NO. OF ROOM、本月报表情况”五个区域,顶部不再展示处理耗时。宽屏卡片行宽度约为原来的2/3,上传与日历标题、内容对齐,小屏按可用宽度自动排列。“本月报表情况”默认收起,显示月份和已生成天数;点击带日历图标的月份按钮展开月历,支持切换月份。每一天按是否存在当前有效日报标为“已生成”或“未生成”,已有报表的日期显示为绿色。历史版本、已删除版本和仅待核对的任务不算已生成;已有有效日报、另有待核对新版本时,仍标为已生成。读取中或读取失败会单独显示,不把未知状态当作未生成。
点击日历日期只切换查看日期和日报列表月份,不会获取Oracle数据、生成报表或提交人工核对值。需要下载新数据时,仍须在自动下载入口主动提交;未保存的人工价格继续受切换保护。日历月份与列表分页独立,显示整月状态;生成或删除后的刷新会更新标记。
“历史记录”保留同一天的旧版本和未完成版本。删除前页面会说明影响:
存在历史版本时显示“历史记录”入口,保留同一天的旧版本和未完成版本;数量为0时不显示该按钮。删除前页面会说明影响:
- 删除当前有效日报:该日从月报中移除,系统自动生成剩余日期的月报;整月已无有效日报时,撤下该月报。旧日报不会自动恢复。
- 删除历史版本或待价格核对版本:保留当前有效日报,月报不变。运行中的版本须等任务停止后再删除。
+2
View File
@@ -4,6 +4,8 @@
顶部“本月报表情况”提供可展开的日历按钮,有当前有效日报的日期显示为绿色,可切换月份和选择日期查看;顶部处理耗时已移除。日历只读取ARR已保存状态,不查询Oracle或自动创建报表。
日报界面已精简:下载卡片显示“状态”,字段完善保留一个主按钮;人工核对只展示定位和填写所需信息及操作,不显示来源说明或排除统计。宽屏顶部卡片行约占原宽度的2/3,上传与日历内容对齐,小屏保持自适应。日报仅在存在历史版本时提供历史记录入口。
2026-09-18:[直接数据入口](DIRECT_DATA_ENTRY.md)已接通日期按钮、日报、复核和月报;独立本机模拟8875,未查询实际Oracle。
2026-09-17:[本机接口模拟全流程及按钮联调](LOCAL_API_SIMULATION.md)已在独立8874验证;[原文件重放](LOCAL_XML_REPLAY.md)8873保留。两个入口均明确未连接Oracle,不改变默认运行配置。
+12 -68
View File
@@ -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);
+2 -3
View File
@@ -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", "บันทึก"],
+6 -20
View File
@@ -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
View File
@@ -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; }