feat: 调整了字段的展示

This commit is contained in:
2026-07-27 11:42:53 +08:00
parent 59362e37e5
commit 6d629181ce
5 changed files with 46 additions and 64 deletions

View File

@@ -3,7 +3,7 @@
<view class="queue-ticket" :class="`queue-ticket--${ticketTone}`">
<view class="queue-ticket__topline">
<view>
<text class="queue-ticket__project">{{ ticket.project_name }}</text>
<text class="queue-ticket__project">{{ ticket.projectName }}</text>
<text class="queue-ticket__title">{{ ticketLabel }}</text>
</view>
<text class="queue-ticket__status-mark" />
@@ -11,24 +11,24 @@
<view class="queue-ticket__number">
<text>我的号码</text>
<text class="queue-ticket__number-value">{{ ticket.ticket_number }}</text>
<text class="queue-ticket__number-meta">本号 {{ ticket.party_size }} </text>
<text class="queue-ticket__number-value">{{ ticket.ticketNumber }}</text>
<text class="queue-ticket__number-meta">本号 {{ ticket.partySize }} </text>
</view>
<view class="queue-ticket__metrics">
<view class="queue-ticket__metric">
<text>当前最新到号</text>
<text class="queue-ticket__metric-value">{{ ticket.latest_called_number || "暂无" }}</text>
<text class="queue-ticket__metric-value">{{ ticket.latestCalledNumber || "暂无" }}</text>
</view>
<template v-if="ticketStatus === 'WAITING' && !projectPaused">
<view class="queue-ticket__metric">
<text>预计等待时间</text>
<text class="queue-ticket__metric-value">{{ formatEstimatedWait(ticket.estimated_wait) }}</text>
<text class="queue-ticket__metric-value">{{ formatEstimatedWait(ticket.estimatedWait) }}</text>
</view>
<view class="queue-ticket__metric queue-ticket__metric--wide">
<text>前方排队</text>
<text class="queue-ticket__metric-value">{{ ticket.tickets_ahead == null ? "未知" : ticket.tickets_ahead + " 个号码" }}</text>
<text class="queue-ticket__metric-meta">{{ ticket.people_ahead == null ? "人数未知" : "共 " + ticket.people_ahead + " 人" }}</text>
<text class="queue-ticket__metric-value">{{ ticket.ticketsAhead == null ? "未知" : ticket.ticketsAhead + " 个号码" }}</text>
<text class="queue-ticket__metric-meta">{{ ticket.peopleAhead == null ? "人数未知" : "共 " + ticket.peopleAhead + " 人" }}</text>
</view>
</template>
</view>
@@ -41,7 +41,7 @@
<view v-if="ticketStatus === 'CALLED'" class="queue-call-action">
<text>现在请前往</text>
<text class="queue-call-action__entrance">{{ ticket.entrance || "现场入口" }}</text>
<text class="queue-call-action__desc">请出示排队号码叫号时间 {{ formatDateTime(ticket.called_at) }}</text>
<text class="queue-call-action__desc">请出示排队号码叫号时间 {{ formatDateTime(ticket.calledAt) }}</text>
</view>
<text v-if="ticketStatus !== 'WAITING' && ticketStatus !== 'CALLED'" class="queue-ticket__guidance">
@@ -51,7 +51,7 @@
<view class="queue-ticket__metadata">
<text>手机号尾号 {{ phoneLast4 }}</text>
<text>更新于 {{ formatDateTime(lastUpdatedAt || ticket.last_updated_at) }}</text>
<text>更新于 {{ formatDateTime(lastUpdatedAt || ticket.lastUpdatedAt) }}</text>
</view>
</view>
@@ -78,13 +78,13 @@ const props = defineProps({
});
const ticketStatus = computed(() => String(props.ticket?.status || "").toUpperCase());
const statusMeta = computed(() => visitorStatusMeta(props.ticket.status, props.ticket.people_ahead));
const statusMeta = computed(() => visitorStatusMeta(props.ticket.status, props.ticket.peopleAhead));
const projectPaused = computed(() => ticketStatus.value === "WAITING" && String(props.ticket?.project?.status || "").toUpperCase() === "PAUSED");
const ticketTone = computed(() => projectPaused.value ? "warning" : statusMeta.value.tone);
const ticketLabel = computed(() => projectPaused.value ? "项目暂时暂停" : statusMeta.value.label);
const phoneLast4 = computed(() => String(props.ticket?.phone_last4 || "").replace(/\D/g, "").slice(-4) || "未知");
const phoneLast4 = computed(() => String(props.ticket?.phoneLast4 || "").replace(/\D/g, "").slice(-4) || "未知");
const visitorNotice = computed(() => {
if (props.ticket?.visitor_notice === null) return "";
return props.ticket?.visitor_notice?.trim() || "请您在景区附近等候,注意听从工作人员指引。";
if (props.ticket?.visitorNotice === null) return "";
return props.ticket?.visitorNotice?.trim() || "请您在景区附近等候,注意听从工作人员指引。";
});
</script>

View File

@@ -87,18 +87,18 @@
</picker>
</label>
<text v-if="selectedProject?.visitor_notice" class="queue-notice">{{ selectedProject.visitor_notice }}</text>
<text v-if="selectedProject?.visitorNotice" class="queue-notice">{{ selectedProject.visitorNotice }}</text>
<label class="queue-field">
<text>同行人数</text>
<input
type="number"
inputmode="numeric"
:value="ticketForm.party_size"
:value="ticketForm.partySize"
:disabled="createBusy"
:min="minPartySize"
:max="maxPartySize"
@input="handleTicketFormInput('party_size', $event)"
@input="handleTicketFormInput('partySize', $event)"
/>
<text class="queue-field__hint">本项目每个号码可绑定 {{ minPartySize }} {{ maxPartySize }} </text>
</label>
@@ -120,9 +120,9 @@
<label class="queue-field">
<text>姓氏选填</text>
<input
:value="ticketForm.last_name"
:value="ticketForm.lastName"
:disabled="createBusy"
@input="handleTicketFormInput('last_name', $event)"
@input="handleTicketFormInput('lastName', $event)"
/>
</label>
@@ -216,7 +216,7 @@
:show-retry="Boolean(currentStatusResource.error && currentStatusResource.data)"
@retry="refreshCurrentStatus"
/>
<TicketCard :ticket="tokenResource.data" :last-updated-at="tokenResource.data.last_updated_at || tokenResource.lastSuccessAt" />
<TicketCard :ticket="tokenResource.data" :last-updated-at="tokenResource.data.lastUpdatedAt || tokenResource.lastSuccessAt" />
<button class="queue-button queue-button--secondary queue-refresh" :disabled="tokenResource.refreshing" hover-class="none" @click="refreshCurrentStatus">
{{ tokenResource.refreshing ? "正在刷新" : "刷新状态" }}
</button>
@@ -284,9 +284,9 @@ const createIntent = ref(null);
const ticketForm = reactive({
phone: "",
last_name: "",
lastName: "",
honorific: "",
party_size: 1,
partySize: 1,
});
const projectsResource = reactive({
@@ -319,11 +319,11 @@ const statusRequestGate = createSingleFlight();
const selectedProject = computed(() => projectsResource.projects.find((project) => project.id === selectedProjectId.value) || null);
const projectNames = computed(() => projectsResource.projects.map((project) => project.name));
const selectedProjectIndex = computed(() => Math.max(0, projectsResource.projects.findIndex((project) => project.id === selectedProjectId.value)));
const minPartySize = computed(() => Number(selectedProject.value?.min_party_size) || 1);
const maxPartySize = computed(() => Number(selectedProject.value?.max_party_size) || minPartySize.value);
const minPartySize = computed(() => Number(selectedProject.value?.minPartySize) || 1);
const maxPartySize = computed(() => Number(selectedProject.value?.maxPartySize) || minPartySize.value);
const canSubmitTicket = computed(() => !createBusy.value && Boolean(selectedProjectId.value) && Boolean(projectsResource.projects.length));
const createdTicketTarget = computed(() => createdTicket.value ? resolveStatusTarget(createdTicket.value) : null);
const createdTicketProjectName = computed(() => selectedProject.value?.name || createdTicket.value?.ticket?.project_name || "当前项目");
const createdTicketProjectName = computed(() => selectedProject.value?.name || createdTicket.value?.ticket?.projectName || "当前项目");
const currentStatusResource = computed(() => tokenResource);
const currentStale = computed(() => Boolean(currentStatusResource.value.data) && isTimestampStale(currentStatusResource.value.lastSuccessAt, 30_000));
const headerTone = computed(() => {
@@ -356,7 +356,7 @@ watch(selectedProject, () => normalizePartySize());
onLoad((options = {}) => {
const sceneToken = extractVisitorToken(parseRouteValue(options.scene));
const token = parseRouteValue(options.token || options.public_token) || sceneToken;
const token = parseRouteValue(options.token || options.publicToken) || sceneToken;
const phone = parseRouteValue(options.phone);
const tab = parseRouteValue(options.tab);
@@ -504,9 +504,9 @@ const handleProjectChange = (event) => {
const normalizePartySize = () => {
if (!selectedProject.value) return;
const current = Number(ticketForm.party_size);
const current = Number(ticketForm.partySize);
if (!Number.isInteger(current) || current < minPartySize.value || current > maxPartySize.value) {
ticketForm.party_size = minPartySize.value;
ticketForm.partySize = minPartySize.value;
}
}
@@ -516,9 +516,9 @@ const handleTicketFormInput = (field, event) => {
const updateTicketForm = (field, value) => {
let nextValue = value;
if (field === "party_size") {
if (field === "partySize") {
const digits = digitsOnly(value);
ticketForm.party_size = digits === "" ? "" : Number(digits);
ticketForm.partySize = digits === "" ? "" : Number(digits);
nextValue = digits;
} else if (field === "phone") {
const phone = digitsOnly(value);
@@ -560,7 +560,7 @@ const submitTicket = async () => {
return;
}
const partySize = Number(ticketForm.party_size);
const partySize = Number(ticketForm.partySize);
if (!Number.isInteger(partySize) || partySize < minPartySize.value || partySize > maxPartySize.value) {
formError.value = `本项目每个号码可绑定 ${minPartySize.value}${maxPartySize.value}`;
return;
@@ -576,7 +576,7 @@ const submitTicket = async () => {
const payload = {
phone,
party_size: partySize,
last_name: ticketForm.last_name.trim(),
last_name: ticketForm.lastName.trim(),
honorific: ticketForm.honorific,
allow_duplicate: confirmDuplicatePhone.value,
};
@@ -612,9 +612,9 @@ const submitTicket = async () => {
const resetTicketForm = () => {
ticketForm.phone = "";
ticketForm.last_name = "";
ticketForm.lastName = "";
ticketForm.honorific = "";
ticketForm.party_size = minPartySize.value;
ticketForm.partySize = minPartySize.value;
phoneError.value = "";
formError.value = "";
}
@@ -700,7 +700,7 @@ const refreshTokenStatus = async ({ initial = false } = {}) => {
try {
const data = await getPublicStatus(currentToken.value);
if (requestId !== statusRequestId || viewMode.value !== VIEW_TOKEN) return;
applyStatusSuccess(tokenResource, data);
applyStatusSuccess(tokenResource, Array.isArray(data?.tickets) ? data.tickets[0] : data);
} catch (error) {
if (requestId !== statusRequestId || viewMode.value !== VIEW_TOKEN) return;
applyStatusError(tokenResource, error);

View File

@@ -1,4 +1,3 @@
import { normalizePublicStatus } from "../../utils/queueUtils.mjs";
import request from "../base/request";
export class QueueApiError extends Error {
@@ -74,22 +73,13 @@ export const createPublicTicket = (projectId, payload, idempotencyKey = newQueue
project_id: projectId,
idempotency_key: idempotencyKey,
...payload,
})).then((response) => ({
...response,
ticket: normalizePublicStatus(response.ticket),
}));
}
export const getPublicStatus = () => {
return normalizeQueueRequest(request.get("/hotelBiz/visitorQueue/status", {}))
.then((response) => normalizePublicStatus(response));
return normalizeQueueRequest(request.get("/hotelBiz/visitorQueue/status", {}));
}
export const searchPublicStatusByPhone = () => {
return normalizeQueueRequest(request.get("/hotelBiz/visitorQueue/status", {})).then((response) => {
if (Array.isArray(response.tickets)) {
return { tickets: response.tickets.map(normalizePublicStatus) };
}
return { tickets: [normalizePublicStatus(response)] };
});
return getPublicStatus();
}

View File

@@ -118,7 +118,6 @@ let requestId = 0;
const hasLookupTarget = computed(() => Boolean(phone.value || token.value));
const tickets = computed(() => {
if (token.value) return resource.data ? [resource.data] : [];
return resource.data?.tickets || [];
});
const selectedTicket = computed(() => {
@@ -153,7 +152,7 @@ watch(tickets, () => syncSelectedTicket(), { deep: true });
onLoad((options = {}) => {
phone.value = parseRouteValue(options.phone);
token.value = parseRouteValue(options.token || options.public_token);
token.value = parseRouteValue(options.token || options.publicToken);
});
onShow(() => {

View File

@@ -26,11 +26,13 @@ export const formatEstimatedWait = (value) => {
return Number.isFinite(numeric) ? `${Math.max(0, Math.round(numeric))} 分钟` : value;
}
if (typeof value === "object") {
const estimate = Number(value.estimate_minutes);
if (value.available === false && value.reason) return value.reason;
const estimate = Number(value.estimateMinutes);
if (Number.isFinite(estimate)) return `${Math.max(0, Math.round(estimate))} 分钟`;
const min = Number(value.min ?? value.min_minutes ?? value.lower ?? value.lower_minutes);
const max = Number(value.max ?? value.max_minutes ?? value.upper ?? value.upper_minutes);
const min = Number(value.min ?? value.minMinutes ?? value.lower ?? value.lowerMinutes);
const max = Number(value.max ?? value.maxMinutes ?? value.upper ?? value.upperMinutes);
if (Number.isFinite(min) && Number.isFinite(max)) {
return `预计 ${Math.max(0, Math.round(min))}-${Math.max(0, Math.round(max))} 分钟`;
}
@@ -78,23 +80,14 @@ export const visitorStatusMeta = (status, peopleAhead) => {
};
}
export const normalizePublicStatus = (ticket = {}) => {
const partySize = Number(ticket.party_size);
return {
...ticket,
party_size: Number.isFinite(partySize) && partySize > 0 ? partySize : 1,
ticket_number: ticket.ticket_number || ticket.display_number || "暂无",
};
}
export const buildTicketKey = (ticket = {}) => {
return `${ticket.project?.id ?? ticket.project_name}:${ticket.ticket_number}`;
return `${ticket.project?.id ?? ticket.projectName}:${ticket.ticketNumber}`;
}
export const resolveStatusTarget = (response = {}) => {
if (response.public_token) return { token: response.public_token };
if (response.publicToken) return { token: response.publicToken };
const url = response.public_url || response.status_path || "";
const url = response.publicUrl || response.statusPath || "";
const match = String(url).match(/\/visitor\/([^/?#]+)/);
if (match?.[1]) return { token: decodeURIComponent(match[1]) };
@@ -105,7 +98,7 @@ export const extractVisitorToken = (value) => {
const text = String(value ?? "").trim();
if (!text) return "";
const tokenMatch = text.match(/(?:^|[?&#])(?:token|public_token)=([^&#]+)/);
const tokenMatch = text.match(/(?:^|[?&#])(?:token|publicToken)=([^&#]+)/);
if (tokenMatch?.[1]) return decodeMaybe(tokenMatch[1]);
const visitorMatch = text.match(/\/visitor\/([^/?#]+)/);