feat: 调整了字段的展示
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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\/([^/?#]+)/);
|
||||
|
||||
Reference in New Issue
Block a user