From 08bce4cab1a4518312d2c03f9f8a7142cd6b9a4f Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Wed, 5 Aug 2026 13:16:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=91=98=E5=B7=A5?= =?UTF-8?q?=E7=AB=AF=E6=89=B9=E9=87=8F=E5=8F=AB=E5=8F=B7=E6=8C=89=E6=95=B0?= =?UTF-8?q?=E9=87=8F=E8=BE=93=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:批量叫号被误实现为输入目标号码,人数输入框清空时会立即回填 1。 修复:恢复按号码数量叫号,并允许号码数量和目标人数先清空后重新输入;补充回归测试。 --- web/src/pages/StaffPage.test.tsx | 43 +++++++++++-------------- web/src/pages/StaffPage.tsx | 54 +++++--------------------------- 2 files changed, 26 insertions(+), 71 deletions(-) diff --git a/web/src/pages/StaffPage.test.tsx b/web/src/pages/StaffPage.test.tsx index f5c200d..1a54fbc 100644 --- a/web/src/pages/StaffPage.test.tsx +++ b/web/src/pages/StaffPage.test.tsx @@ -239,16 +239,16 @@ describe("StaffPage scene-focused H5", () => { } }); - it("can call through a full target number beyond the 0001x range", async () => { + it("calls a requested number of tickets", async () => { const callNext = vi.spyOn(api, "callNext").mockResolvedValueOnce({ batch: { id: "batch-2", batch_number: 5, status: "CALLED", called_at: "2026-07-10T09:03:00Z", - ticket_count: 13, - people_count: 28, - tickets: [{ id: "called-25", ticket_number: "00025", status: "CALLED", party_size: 2 }], + ticket_count: 10, + people_count: 20, + tickets: [{ id: "called-22", ticket_number: "00022", status: "CALLED", party_size: 2 }], }, revision: 9, device_results: [{ channel: "SIMULATOR", status: "SUCCESS" }], @@ -256,36 +256,29 @@ describe("StaffPage scene-focused H5", () => { try { renderScene("/staff"); - const targetInput = screen.getByRole("textbox", { name: "叫到号码" }); - expect(targetInput).toHaveValue("00014"); + const countInput = screen.getByRole("spinbutton", { name: "按号码叫号数量" }); + expect(countInput).toHaveValue(2); - fireEvent.change(targetInput, { target: { value: "00025" } }); + fireEvent.change(countInput, { target: { value: "" } }); + expect(countInput).toHaveValue(null); + fireEvent.change(countInput, { target: { value: "10" } }); fireEvent.click(screen.getByRole("button", { name: "批量叫号" })); - await waitFor(() => expect(callNext).toHaveBeenCalledWith("project-1", 8, "TICKET", 13, expect.any(String))); + await waitFor(() => expect(callNext).toHaveBeenCalledWith("project-1", 8, "TICKET", 10, expect.any(String))); } finally { callNext.mockRestore(); } }); - it("rejects a target outside the waiting queue or beyond the configured batch limit", () => { - const callNext = vi.spyOn(api, "callNext"); - try { - renderScene("/staff"); - const targetInput = screen.getByRole("textbox", { name: "叫到号码" }); + it("allows the people count to be cleared before entering a new value", () => { + renderScene("/staff"); + const countInput = screen.getByRole("spinbutton", { name: "按人数叫号数量" }); + expect(countInput).toHaveValue(5); - fireEvent.change(targetInput, { target: { value: "00040" } }); - fireEvent.click(screen.getByRole("button", { name: "批量叫号" })); - expect(callNext).not.toHaveBeenCalled(); - expect(screen.getByRole("alert")).toHaveTextContent("请输入当前待叫队列中的完整目标号码"); - - fireEvent.change(targetInput, { target: { value: "00033" } }); - fireEvent.click(screen.getByRole("button", { name: "批量叫号" })); - expect(callNext).not.toHaveBeenCalled(); - expect(screen.getByRole("alert")).toHaveTextContent("从 00013 叫到 00033 共 21 个号码,超过单次上限 20 个"); - } finally { - callNext.mockRestore(); - } + fireEvent.change(countInput, { target: { value: "" } }); + expect(countInput).toHaveValue(null); + fireEvent.change(countInput, { target: { value: "10" } }); + expect(countInput).toHaveValue(10); }); it("updates number indicators after creation without showing a success card", async () => { diff --git a/web/src/pages/StaffPage.tsx b/web/src/pages/StaffPage.tsx index 23c01d0..74456fb 100644 --- a/web/src/pages/StaffPage.tsx +++ b/web/src/pages/StaffPage.tsx @@ -90,8 +90,8 @@ export function StaffPage() { const [formError, setFormError] = useState(null); const [notice, setNotice] = useState<{ scene: "call" | "tickets"; tone: "success" | "warning" | "danger"; title: string; detail?: string; url?: string } | null>(null); const [busyAction, setBusyAction] = useState(null); - const [ticketCallTarget, setTicketCallTarget] = useState(""); - const [peopleCallCount, setPeopleCallCount] = useState(1); + const [ticketCallCount, setTicketCallCount] = useState(1); + const [peopleCallCount, setPeopleCallCount] = useState(1); const [visibleWaitingTicketCount, setVisibleWaitingTicketCount] = useState(WAITING_TICKET_PAGE_SIZE); const [confirmDuplicatePhone, setConfirmDuplicatePhone] = useState(false); const [phoneError, setPhoneError] = useState(null); @@ -148,8 +148,6 @@ export function StaffPage() { const waitingTicketCount = Math.max(0, Number(queue?.metrics.waiting_ticket_count ?? queue?.metrics.waiting_count) || 0); const waitingPeopleCount = Math.max(0, Number(queue?.metrics.waiting_people_count) || 0); const waitingTickets = queue?.waiting ?? []; - const defaultTicketCallCount = selectedProject?.default_call_ticket_count ?? selectedProject?.call_batch_size ?? selectedProject?.batch_size ?? 1; - const defaultTicketCallTarget = waitingTickets[Math.min(Math.max(defaultTicketCallCount, 1), waitingTickets.length) - 1]?.ticket_number ?? ""; const visibleWaitingTickets = waitingTickets.slice(0, visibleWaitingTicketCount); const canShowMoreWaitingTickets = visibleWaitingTickets.length < waitingTickets.length; const canCollapseWaitingTickets = visibleWaitingTickets.length > WAITING_TICKET_PAGE_SIZE; @@ -235,6 +233,7 @@ export function StaffPage() { useEffect(() => { if (!selectedProject) return; + setTicketCallCount(selectedProject.default_call_ticket_count ?? selectedProject.call_batch_size ?? selectedProject.batch_size ?? 1); setPeopleCallCount(selectedProject.default_call_people_count ?? 1); const minPartySize = selectedProject.min_party_size ?? 1; const maxPartySize = selectedProject.max_party_size ?? minPartySize; @@ -246,14 +245,6 @@ export function StaffPage() { })); }, [selectedProject?.id]); - useEffect(() => { - setTicketCallTarget((current) => ( - waitingTickets.some((ticket) => ticket.ticket_number === current) - ? current - : defaultTicketCallTarget - )); - }, [defaultTicketCallTarget, queue?.revision, selectedProjectId]); - useGSAP(() => { if (!showProjectSwitcher) return; const mm = gsap.matchMedia(); @@ -287,7 +278,6 @@ export function StaffPage() { setFormError(null); setConfirmDuplicatePhone(false); setPhoneError(null); - setTicketCallTarget(""); createIntentRef.current = null; callIntentRef.current = null; }; @@ -347,12 +337,12 @@ export function StaffPage() { } }; - const callNext = async (mode: Exclude, count: number) => { + const callNext = async (mode: Exclude, count: number | "") => { if (!queue || waitingTicketCount === 0) return; const limit = mode === "PEOPLE" ? selectedProject?.max_call_people_count ?? 100 : selectedProject?.max_call_ticket_count ?? 100; - if (!Number.isInteger(count) || count < 1 || count > limit) { + if (typeof count !== "number" || !Number.isInteger(count) || count < 1 || count > limit) { setNotice({ scene: "call", tone: "danger", @@ -393,34 +383,6 @@ export function StaffPage() { } }; - const callThroughTicket = () => { - const target = ticketCallTarget.trim(); - const targetIndex = waitingTickets.findIndex((ticket) => ticket.ticket_number === target); - if (targetIndex < 0) { - setNotice({ - scene: "call", - tone: "danger", - title: "叫号未提交", - detail: "请输入当前待叫队列中的完整目标号码", - }); - return; - } - - const count = targetIndex + 1; - const limit = selectedProject?.max_call_ticket_count ?? 100; - if (count > limit) { - setNotice({ - scene: "call", - tone: "danger", - title: "叫号未提交", - detail: `从 ${waitingTickets[0].ticket_number} 叫到 ${target} 共 ${count} 个号码,超过单次上限 ${limit} 个`, - }); - return; - } - - void callNext("TICKET", count); - }; - const noticeBanner = notice && notice.scene === scene ? (
- - + +
: null} {supportsPeopleCall ?
- +
: null}