/* * Copyright (c) Baidu, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @file transport-ticket 组件 */ import {Component} from 'san'; import type {TransportTicketData} from './interface'; import Image from '@cosui/cosmic/image'; export default class TransportTicket extends Component { static trimWhitespace = 'all'; static template = `
{{departTime}}
{{from}}
{{duration}}
{{number}}
{{transfer.station}}
{{transfer.waitTime}}
{{arriveTime}}
+{{crossDays}}
{{to}}
¥ {{price}}
{{level}}{{discount}}
{{seat.type}} {{seat.remaining}}
{{item}}
{{index + 1}}
{{segment.number}}
{{seat.type}} {{seat.remaining}}
{{segment.operator}} {{segment.number}}
{{service}}
`; static components = { 'cos-image': Image }; static computed = { _descArray(this: TransportTicket) { const operator = this.data.get('operator'); const number = this.data.get('number'); const model = this.data.get('model'); return [operator, number, model].filter(item => item); }, _seats(this: TransportTicket) { const seats = this.data.get('seats'); if (!seats || !seats.length) { return; } const paddedSeats = seats.map(seat => { let remaining = seat?.remaining || ''; const remainingNum = remaining.match(/\d+/)?.[0]; if (remainingNum) { remaining = `余${remainingNum}`; } return { ...seat, remaining, disabled: seat.remaining === '无票', show: true }; }); return paddedSeats; } }; private resizeObserver: ResizeObserver | null; initData(): TransportTicketData { return { type: 'train', from: '', to: '', departTime: '', arriveTime: '', price: 0, duration: '', operator: '', number: '', level: '', discount: '', seats: [], linkInfo: {}, model: '', service: '', crossDays: 0 }; } inited() { this.resizeObserver = null; } attached() { this.nextTick(() => { this.updateMaskVisibility(); this.setupResizeObserver(); }); } detached() { this.disconnectResizeObserver(); } /** * 设置 ResizeObserver 监听组件容器宽度变化 */ setupResizeObserver() { const rootElement = this.el as HTMLElement; if (!rootElement) { return; } // 增强兼容性判断 if (typeof window === 'undefined' || typeof window.ResizeObserver === 'undefined') { return; } try { this.resizeObserver = new window.ResizeObserver(() => { this.updateMaskVisibility(); }); this.resizeObserver.observe(rootElement); } catch (error) { // nothing to do } } /** * 断开 ResizeObserver */ disconnectResizeObserver() { if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } } /** * 更新所有滚动容器的遮罩显示状态 */ updateMaskVisibility() { this.updateScrollMask('seats', '_seatsLeftMask', '_seatsRightMask'); this.updateScrollMask('transfer', '_transferLeftMask', '_transferRightMask'); } /** * 更新指定滚动容器的左右遮罩显示状态 * @param refName - ref 引用名称 * @param leftMaskKey - 左侧遮罩的 data key * @param rightMaskKey - 右侧遮罩的 data key */ updateScrollMask( refName: string, leftMaskKey: string, rightMaskKey: string ) { const scrollContainer = this.ref(refName) as unknown as HTMLElement; if (!scrollContainer) { return; } // 检测是否能向左滚动(不在最左端) const canScrollLeft = scrollContainer.scrollLeft > 0; // 检测是否能向右滚动(不在最右端) const canScrollRight = scrollContainer.scrollWidth > scrollContainer.clientWidth + scrollContainer.scrollLeft + 1; this.data.set(leftMaskKey, canScrollLeft); this.data.set(rightMaskKey, canScrollRight); } handleTouch(e: Event, isStart: boolean) { if (!isStart) { this.data.set('_isActive', false); return; } const touchTarget = e?.target as HTMLElement; const scrollableSelectors = [ '.cosd-transport-ticket-seat-scrollable', '.cosd-transport-ticket-transfer-scrollable' ]; const isScrollable = scrollableSelectors.some(selector => touchTarget?.closest(selector) ); if (!isScrollable) { this.data.set('_isActive', true); } } }