卢春龙
2026-04-20 13c960a2b6bb54797bdbd03aa3184af507d94a9b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import Taro from "@tarojs/taro";
import { View, Image } from "@tarojs/components";
import { useState, useRef, useEffect } from "react";
import { unlockVehicle, queryVehicleTrunkOpenStatus } from '../../../../api/begin-route';
 
type Props = {
    tripStatus: number;
    orderId?: string;
    orderTrunkStatus?: boolean;
    handleOpenOverRoutePopup?: () => void;
    handleOpenAnnouncementsPopup?: () => void;
};
 
export default function DoorTrunkControl({
    tripStatus,
    orderId,
    orderTrunkStatus,
    handleOpenOverRoutePopup,
    handleOpenAnnouncementsPopup
}: Props) {
    const [trunkStatus, setTrunkStatus] = useState(orderTrunkStatus); // 后备箱开关状态
    const [lockStatus, setLockStatus] = useState(false); // 车门解锁状态
    const trunkPollTimerRef = useRef<any>(null); // 后备箱状态轮询定时器
    const hasShownAnnouncementsRef = useRef(false); // 安全须知是否已展示
 
    // 外部后备箱状态变化时同步本地状态
    useEffect(() => {
        setTrunkStatus(orderTrunkStatus);
    }, [orderTrunkStatus]);
 
    // 组件卸载时清理轮询定时器
    useEffect(() => {
        return () => {
            if (trunkPollTimerRef.current) {
                clearTimeout(trunkPollTimerRef.current);
            }
        };
    }, []);
 
    // 获取解锁请求所需定位参数,失败时回退到缓存上车点
    const getUnlockLocationPayload = async () => {
        try {
            Taro.showLoading({ title: '正在获取位置...', mask: true });
            const location = await Taro.getLocation({ type: 'gcj02', isHighAccuracy: true });
            Taro.hideLoading();
            const longitude = Number(location?.longitude);
            const latitude = Number(location?.latitude);
            if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
                return { longitude, latitude };
            }
        } catch (_) {
            Taro.hideLoading();
        }
        const startSite = Taro.getStorageSync('startSite') || {};
        const longitude = Number(startSite?.longitude);
        const latitude = Number(startSite?.latitude);
        if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
            return { longitude, latitude };
        }
        return {};
    };
 
    // 解锁车门/后备箱,后备箱场景会轮询确认结果
    const handleUnlock = async (doorType: 'door' | 'trunk', onSuccess?: () => void) => {
        const targetOrderId = orderId || Taro.getStorageSync('orderId');
        if (!targetOrderId) return;
        
        const locationPayload = await getUnlockLocationPayload();
        const isCloseAction = doorType === 'trunk' ? !!trunkStatus : false;
        const actionLoadingText = doorType === 'trunk'
            ? (isCloseAction ? '正在关闭后备箱' : '正在开启后备箱')
            : '正在解锁车门';
        Taro.showLoading({ title: actionLoadingText, mask: true });
        unlockVehicle({
            orderId: targetOrderId,
            doorType,
            ...locationPayload
        }).then((res: any) => {
            console.log('解锁结果:', res);
            
            if (res?.code !== 200 || !res?.data) {
                Taro.hideLoading();
                Taro.showToast({ title: res?.msg || '解锁失败,请重试或联系客服', icon: 'none' });
                return;
            }
 
            if (doorType === 'trunk') {
                let pollCount = 0;
                const maxPolls = 5;
                const pollInterval = 2000;
                const expectedTrunkState = isCloseAction ? 0 : 1;
                const successToastText = isCloseAction ? '关闭后备箱成功' : '打开后备箱成功';
                const failToastText = isCloseAction ? '关闭后备箱失败' : '开启后备箱失败';
 
                const stopPolling = () => {
                    if (trunkPollTimerRef.current) {
                        clearTimeout(trunkPollTimerRef.current);
                        trunkPollTimerRef.current = null;
                    }
                };
 
                const handlePollFail = (reason: string, actualValue?: any) => {
                    pollCount++;
                    if (pollCount < maxPolls) {
                        stopPolling();
                        trunkPollTimerRef.current = setTimeout(doPoll, pollInterval);
                    } else {
                        stopPolling();
                        Taro.hideLoading();
                        Taro.showToast({
                            title: failToastText,
                            icon: 'none'
                        });
                    }
                };
 
                const doPoll = () => {
                    queryVehicleTrunkOpenStatus({ orderId: targetOrderId }).then((pollRes: any) => {
                        console.log('第', pollCount, '次查询后备箱状态:', pollRes);
                        
                        const rawValue = typeof pollRes === 'number'
                            ? pollRes
                            : (
                                pollRes?.data ??
                                pollRes?.stateMap?.trunkAjarCloseSt?.value ??
                                pollRes?.value
                            );
                        if (rawValue === undefined || rawValue === null || rawValue === '') {
                            handlePollFail('empty_data_or_value', rawValue);
                            return;
                        }
                        const value = Number(rawValue);
                        if (Number.isNaN(value)) {
                            handlePollFail('invalid_value', rawValue);
                            return;
                        }
                        if (value === expectedTrunkState) {
                            stopPolling();
                            const isOpen = !isCloseAction;
                            setTrunkStatus(isOpen);
                            Taro.hideLoading();
                            Taro.showToast({
                                title: successToastText,
                                icon: 'none'
                            });
                            onSuccess?.();
                        } else {
                            handlePollFail('state_mismatch', value);
                        }
                    }).catch((err) => {
                        handlePollFail('api_error', err);
                    });
                };
                trunkPollTimerRef.current = setTimeout(doPoll, pollInterval);
            }
            
            if (doorType === 'door') {
                Taro.hideLoading();
                Taro.showToast({ title: '右侧车门已解锁', icon: 'none' });
                if (!hasShownAnnouncementsRef.current) {
                    handleOpenAnnouncementsPopup?.();
                    hasShownAnnouncementsRef.current = true;
                }
            }
        }).catch(() => {
            Taro.hideLoading();
            Taro.showToast({ title: '解锁失败,请重试或联系客服', icon: 'none' });
        });
    };
 
    if (tripStatus === 5) {
        return (
            <View className="car-card-arrive">
                <View className={`car-card-arrive-item ${trunkStatus && 'close-btn-item'}`} onClick={() => handleUnlock('trunk', () => setTrunkStatus(!trunkStatus))}>
                    <Image className="car-card-arrive-item-img" src={trunkStatus ? require('../../../../assets/trunk-open.png') : require('../../../../assets/trunk-close.png')} />
                    <View className="car-card-arrive-item-text">{trunkStatus ? '关闭后备箱' : '打开后备箱'}</View>
                </View>
                <View className="car-card-arrive-item" onClick={() => handleUnlock('door', () => setLockStatus(true))}>
                    <Image className="car-card-arrive-item-img" src={require('../../../../assets/lock-close.png')} />
                    <View className="car-card-arrive-item-text">解锁车门</View>
                </View>
            </View>
        );
    }
 
    if (tripStatus === 7) {
        return (
            <View className="car-card-reach">
                <View className="car-card-reach-text">点击按钮开车门</View>
                <Image className='car-card-reach-img' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/door-anim.webp' />
                <View className="car-card-reach-btn">
                    <View className={`car-card-reach-btn-item ${trunkStatus && 'close-btn-item'}`} onClick={() => handleUnlock('trunk', () => setTrunkStatus(!trunkStatus))}>
                        <Image className="car-card-reach-btn-item-img" src={trunkStatus ? require('../../../../assets/trunk-open.png') : require('../../../../assets/trunk-close.png')} />
                        <View className="car-card-reach-btn-item-text">{trunkStatus ? '关闭后备箱' : '打开后备箱'}</View>
                    </View>
                    <View className="car-card-reach-btn-item" onClick={handleOpenOverRoutePopup}>
                        <Image className="car-card-reach-btn-item-img" src={require('../../../../assets/over-gps-icon.png')} />
                        <View className="car-card-reach-btn-item-text">结束行程</View>
                    </View>
                </View>
            </View>
        );
    }
 
    return null;
}