You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
16 KiB
559 lines
16 KiB
import React, { useState, useEffect, useReducer, useRef } from "react";
|
|
import Qmmap from "qmmap";
|
|
import { useHistory, useLocation } from "react-router-dom";
|
|
import { getMallInfo } from "../../js/helpers/data-helper";
|
|
import "./Index.scss";
|
|
import Floors from "../../components/Floors/Floors";
|
|
import Popup from "../../components/Popup/Popup";
|
|
import pos from "./pos.png";
|
|
import offline from "./offline.png";
|
|
import compass from "./compass.png";
|
|
import Shops from "../Shops/Shops";
|
|
import NavBottom from "../../components/NavBottom/NavBottom";
|
|
import Modal from "react-modal";
|
|
import { QrReader } from "react-qr-reader";
|
|
// import vconsole from "vconsole";
|
|
// const vConsole = new vconsole();
|
|
export const MallCode = React.createContext(null);
|
|
|
|
let focusdDevice;
|
|
|
|
const Index = () => {
|
|
const history = useHistory();
|
|
const [mapId] = useState("id" + new Date().getTime());
|
|
const [shop, setShop] = useState(null);
|
|
const [mallInfo, setMallInfo] = useState(null);
|
|
const mall = mallInfo ? mallInfo.mall : null;
|
|
const config = mallInfo ? mallInfo.config : null;
|
|
const images = mallInfo ? mallInfo.images : null;
|
|
const [map, setMap] = useState(null);
|
|
const [sceneIndex, setSceneIndex] = useState(null);
|
|
const params = new URLSearchParams(useLocation().search);
|
|
const startParams = params.get("s");
|
|
let endId = params.get("e");
|
|
const mallCode = params.get("code");
|
|
const [navigation, setNavigation] = useState(false);
|
|
const [inAnimation, setInAnimation] = useState(false);
|
|
const [showShops, setShowShops] = useState(false);
|
|
const [routeSearchText, setRouteSearchText] = useState("");
|
|
const [percent, setPercent] = useState(0);
|
|
const [elevations, setElevations] = useState([]);
|
|
const [paused, setPaused] = useState(false);
|
|
const [online, setOnline] = useState(true);
|
|
const [azimuthAngle, setAzimuthAngle] = useState(0);
|
|
const [follow, setFollow] = useState(false);
|
|
const [start, _setStart] = useState(null);
|
|
const [end, _setEnd] = useState(null);
|
|
const [doFocus, _setDoFocus] = useState(0);
|
|
const [focusTextClicked, setFocusTextClicked] = useState(false);
|
|
const [showQrcodeModal, setShowQrcodeModal] = useState(false);
|
|
const [qrcodeModalOpened, setQrcodeModalOpened] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!map) return;
|
|
let start;
|
|
let end;
|
|
if (startParams) {
|
|
const startParamList = startParams.split("_");
|
|
if (startParamList.length === 3) {
|
|
let [sfloororder, spoint, sname] = startParamList;
|
|
sfloororder = Number(sfloororder);
|
|
spoint = Number(spoint);
|
|
start = {
|
|
isDevice: true,
|
|
name: sname,
|
|
navPoint: spoint,
|
|
yaxis: spoint,
|
|
floorOrder: sfloororder,
|
|
};
|
|
setStart(start);
|
|
}
|
|
}
|
|
if (!endId) {
|
|
if (start) map.changeFloor(start.floorOrder);
|
|
return;
|
|
}
|
|
|
|
if (mall.mcShopIDHouseNumMap && mall.mcShopIDHouseNumMap[endId])
|
|
endId = mall.mcShopIDHouseNumMap[endId];
|
|
const shop = map.getShopByHouseNum(endId);
|
|
const p = map.getPByHouseNum(endId) ? map.getPByHouseNum(endId).data : null;
|
|
const fac = map.getFacilityById(endId) ? map.getFacilityById(endId) : null;
|
|
const endParamList = endId.split("_");
|
|
let endPoint = null;
|
|
if (endParamList.length === 3) {
|
|
let [efloororder, epoint, ename] = endParamList;
|
|
efloororder = Number(efloororder);
|
|
epoint = Number(epoint);
|
|
endPoint = {
|
|
isDevice: true,
|
|
name: ename,
|
|
navPoint: epoint,
|
|
yaxis: epoint,
|
|
floorOrder: efloororder,
|
|
};
|
|
}
|
|
end = shop ? shop : p ? p : fac ? fac : endPoint;
|
|
if (!start) {
|
|
setDoFocus(2);
|
|
shop
|
|
? map.focusShopByHouseNum(endId)
|
|
: p
|
|
? map.focusPByHouseNum(endId)
|
|
: fac
|
|
? map.focusFacilityById(endId)
|
|
: map.addDeviceAndFocus(end).then((device) => {
|
|
focusdDevice = device;
|
|
setShop(end);
|
|
});
|
|
return;
|
|
}
|
|
if (start && end) {
|
|
setEnd(end);
|
|
map.startNavigate({
|
|
start,
|
|
end,
|
|
});
|
|
}
|
|
}, [map]);
|
|
|
|
useEffect(() => {
|
|
map && map.setStart(start);
|
|
}, [start]);
|
|
|
|
useEffect(() => {
|
|
map && map.setEnd(end);
|
|
}, [end]);
|
|
|
|
const doFocusRef = useRef(doFocus);
|
|
const setDoFocus = (data) => {
|
|
doFocusRef.current = data;
|
|
_setDoFocus(data);
|
|
};
|
|
|
|
const startRef = useRef(start);
|
|
const setStart = (data) => {
|
|
startRef.current = data;
|
|
_setStart(data);
|
|
};
|
|
|
|
const endRef = useRef(end);
|
|
const setEnd = (data) => {
|
|
endRef.current = data;
|
|
_setEnd(data);
|
|
};
|
|
|
|
useEffect(() => {
|
|
let offlineListener = () => setOnline(false);
|
|
let onlineListener = () => setOnline(true);
|
|
window.addEventListener("offline", offlineListener);
|
|
window.addEventListener("online", onlineListener);
|
|
return () => {
|
|
window.removeEventListener("online", onlineListener);
|
|
window.removeEventListener("offline", offlineListener);
|
|
};
|
|
});
|
|
|
|
useEffect(() => {
|
|
const end = endRef.current;
|
|
if (!inAnimation && map && end) {
|
|
setShop(end);
|
|
const timeout = setTimeout(() => {
|
|
map.focusShopByHouseNum(end.houseNum);
|
|
}, 500);
|
|
exitFromNav();
|
|
return () => clearTimeout(timeout);
|
|
}
|
|
}, [inAnimation]);
|
|
|
|
const [_, dispatchLS] = useReducer(
|
|
({ lastSearch }, { type, data }) => {
|
|
switch (type) {
|
|
case "set":
|
|
return { lastSearch: data };
|
|
case "addLine":
|
|
lastSearch &&
|
|
lastSearch.forEach(({ floorOrder: floorOrder, addLine }) => {
|
|
Number(floorOrder) === data && addLine();
|
|
});
|
|
return { lastSearch };
|
|
default:
|
|
throw new Error();
|
|
}
|
|
},
|
|
{
|
|
lastSearch: null,
|
|
}
|
|
);
|
|
|
|
const handleFocus = ({ data, preventDefault }) => {
|
|
const start = startRef.current;
|
|
const end = endRef.current;
|
|
const doFocus = doFocusRef.current;
|
|
if (doFocus === 2) {
|
|
setDoFocus(1);
|
|
setShop(data);
|
|
return;
|
|
}
|
|
if (start && end) {
|
|
preventDefault();
|
|
} else {
|
|
if (follow) {
|
|
map.setFollow(false);
|
|
setFollow(false);
|
|
}
|
|
setShop(data);
|
|
}
|
|
setDoFocus(0);
|
|
};
|
|
|
|
const handleBlur = () => {
|
|
if (focusdDevice) {
|
|
focusdDevice.blur();
|
|
focusdDevice = null;
|
|
}
|
|
const doFocus = doFocusRef.current;
|
|
if (doFocus === 1) return;
|
|
history.replace(`/`);
|
|
setShop(null);
|
|
};
|
|
|
|
const loadMap = () => {
|
|
let img = new Image(100, 200);
|
|
img.src = offline;
|
|
const { scale, needSpotLight } = mall;
|
|
new Qmmap({
|
|
container: document.querySelector(`#${mapId}`),
|
|
config: {
|
|
...(config ? config : {}),
|
|
focusStyle: ["scale", "showText"],
|
|
focusColor: 0xff0000,
|
|
controller: "MapControls",
|
|
routeSearchAnimationType: 2,
|
|
routeSearchZoom: 2,
|
|
maxZoom: 4,
|
|
playAudio: false,
|
|
needSpotLight,
|
|
scale,
|
|
floorHeights: {
|
|
0: 10,
|
|
1: 10,
|
|
2: 10,
|
|
3: 10,
|
|
4: 10,
|
|
5: 10,
|
|
6: 10,
|
|
7: 10,
|
|
8: 10,
|
|
9: 10,
|
|
10: 10,
|
|
11: 10,
|
|
},
|
|
backgroundColor: null,
|
|
navIconTopColor: 0xffffff,
|
|
navIconMiddleColor: 0x437af7,
|
|
navIconBottomColor: 0xffffff,
|
|
pathMainColor: 0x437af7,
|
|
pathBorderColor: 0x5ea4f9,
|
|
pathArrowColor: 0xffffff,
|
|
addtionalFacilityCodeMap: mall.addtionalFacilityCodeMap,
|
|
autoRotate: false,
|
|
customTitleFacilityTypeMap: { 116: true },
|
|
wrapperWindowRatio: 1,
|
|
},
|
|
mall,
|
|
onFocusShop: (data, stop) => {
|
|
handleFocus({ data, preventDefault: stop });
|
|
},
|
|
onBlurShop: handleBlur,
|
|
onFocusFacility: (data, stop) => {
|
|
handleFocus({ data, preventDefault: stop });
|
|
},
|
|
onBlurFacility: handleBlur,
|
|
onTapP(data, stop) {
|
|
handleFocus({ data, preventDefault: stop });
|
|
},
|
|
onBlurP: handleBlur,
|
|
onSearchStart: (navigation) => {
|
|
const { pathsByFloor } = navigation;
|
|
console.log(navigation);
|
|
if (pathsByFloor.length === 1) setElevations([]);
|
|
else {
|
|
let result = [];
|
|
let acc = 0;
|
|
for (let i = 0; i < pathsByFloor.length - 1; i++) {
|
|
let { floorOrder: floorOrder1, distanceSum } = pathsByFloor[i];
|
|
let { floorOrder: floorOrder2 } = pathsByFloor[i + 1];
|
|
|
|
acc += distanceSum;
|
|
result.push({
|
|
isUp: Number(floorOrder2) > Number(floorOrder1),
|
|
floorOrder: floorOrder2,
|
|
percent: acc / navigation.totalDistance,
|
|
name: mall.floors[floorOrder2][1],
|
|
});
|
|
}
|
|
setElevations(result);
|
|
}
|
|
navigation.handleText = setRouteSearchText;
|
|
navigation.onPercentChange = setPercent;
|
|
navigation.onPausedChanged = setPaused;
|
|
setNavigation(navigation);
|
|
setInAnimation(true);
|
|
dispatchLS({ type: "set", data: pathsByFloor });
|
|
},
|
|
onSearchComplete: () => {
|
|
setInAnimation(false);
|
|
},
|
|
images,
|
|
onFloorChange: (floorOrder) => {
|
|
setDoFocus(0);
|
|
setSceneIndex(floorOrder);
|
|
dispatchLS({ type: "addLine", data: floorOrder });
|
|
},
|
|
onLoad: (map) => {
|
|
setMap(map);
|
|
},
|
|
onAzimuthAngleChange: (angle) =>
|
|
setAzimuthAngle(Math.floor((angle / Math.PI) * 180)),
|
|
onFocusTextClick: () => setFocusTextClicked(true),
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (mallCode) {
|
|
getMallInfo(mallCode).then((result) => {
|
|
setMallInfo(result);
|
|
});
|
|
}
|
|
}, [mallCode]);
|
|
|
|
const showNav = inAnimation && start && end;
|
|
|
|
const exitFromNav = () => {
|
|
map.setEnd(null);
|
|
map.recycle();
|
|
navigation && navigation.stop && navigation.stop();
|
|
dispatchLS({ type: "set", data: null });
|
|
setEnd(null);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (mallInfo) {
|
|
loadMap();
|
|
}
|
|
}, [mallInfo]);
|
|
|
|
useEffect(() => {
|
|
if (focusTextClicked) {
|
|
if (shop) handleEndSet(shop);
|
|
setFocusTextClicked(false);
|
|
}
|
|
}, [focusTextClicked]);
|
|
|
|
const handleEndSet = (end) => {
|
|
if (!start) return setShowQrcodeModal(true);
|
|
setEnd(end);
|
|
map.startNavigate({
|
|
start,
|
|
end,
|
|
});
|
|
};
|
|
const handleQrcodeResult = (code) => {
|
|
try {
|
|
code = decodeURIComponent(code);
|
|
const kvs = code
|
|
.split("?")
|
|
.pop()
|
|
.split("&")
|
|
.map((kv) => kv.split("="));
|
|
const s = kvs.find(([k]) => k === "s")
|
|
? kvs.find(([k]) => k === "s")[1]
|
|
: "";
|
|
const startParamList = s.split("_");
|
|
if (startParamList.length === 3) {
|
|
let [sfloororder, spoint, sname] = startParamList;
|
|
sfloororder = Number(sfloororder);
|
|
spoint = Number(spoint);
|
|
const nxt = {
|
|
isDevice: true,
|
|
name: sname,
|
|
navPoint: spoint,
|
|
yaxis: spoint,
|
|
floorOrder: sfloororder,
|
|
};
|
|
setStart(nxt);
|
|
setEnd(shop);
|
|
map.startNavigate({
|
|
start: nxt,
|
|
end: shop,
|
|
});
|
|
try {
|
|
setQrcodeModalOpened(false);
|
|
setShowQrcodeModal(false);
|
|
} catch (error) {}
|
|
}
|
|
} catch (error) {
|
|
window.weui.toast("请扫大屏二维码", {
|
|
className: "toast",
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<MallCode.Provider value={mallCode}>
|
|
<div className="index">
|
|
{showQrcodeModal && (
|
|
<Modal
|
|
onAfterOpen={() => setQrcodeModalOpened(true)}
|
|
isOpen
|
|
ariaHideApp={false}
|
|
className="ScanModal"
|
|
>
|
|
<div className="border">
|
|
<div className="scaner"></div>
|
|
<QrReader
|
|
constraints={{ facingMode: "environment" }}
|
|
className="container"
|
|
onResult={(result, error) => {
|
|
if (!!result) {
|
|
handleQrcodeResult(result?.text);
|
|
}
|
|
|
|
if (!!error) {
|
|
console.info(error);
|
|
}
|
|
}}
|
|
videoStyle={{ objectFit: "cover" }}
|
|
></QrReader>
|
|
</div>
|
|
<div className="qrcodeTip">
|
|
<div
|
|
className="back"
|
|
onClick={() => {
|
|
setQrcodeModalOpened(false);
|
|
setShowQrcodeModal(false);
|
|
}}
|
|
>
|
|
取消
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
)}
|
|
{!online && (
|
|
<Modal
|
|
isOpen={!online}
|
|
style={{ overlay: { zIndex: 10001, background: "#fff" } }}
|
|
ariaHideApp={false}
|
|
className="offline"
|
|
>
|
|
<img className="img" src={offline}></img>
|
|
</Modal>
|
|
)}
|
|
{mall && (
|
|
<div className="hud">
|
|
{!(start && end) && (
|
|
<>
|
|
<div className="top-left">
|
|
<img alt="图左" src={pos} className="left"></img>
|
|
<span style={{ color: "#7A7E8D" }}>{mall && mall.city}</span>
|
|
<div className="border"></div>
|
|
<span>{mall && mall.name}</span>
|
|
</div>
|
|
<div
|
|
className="top-right"
|
|
onClick={() => setShowShops(true)}
|
|
></div>
|
|
</>
|
|
)}
|
|
|
|
<div
|
|
className={["compass-wrapper", showNav ? "inNav" : ""].join(" ")}
|
|
>
|
|
<img
|
|
className="compass"
|
|
src={compass}
|
|
style={{
|
|
transform: `rotate(${
|
|
azimuthAngle + (mall ? mall.offsetToNorth : 0)
|
|
}deg)`,
|
|
}}
|
|
onClick={() =>
|
|
map && !showNav && map.changeFloor(sceneIndex, true)
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="qmmap" id={mapId}></div>
|
|
|
|
<Shops
|
|
mall={mall}
|
|
isOpen={showShops}
|
|
onRequestClose={() => setShowShops(false)}
|
|
onClick={({ houseNum }) => {
|
|
setShowShops(false);
|
|
setDoFocus(2);
|
|
map.focusShopByHouseNum(houseNum);
|
|
}}
|
|
onClose={() => setShowShops(false)}
|
|
></Shops>
|
|
{mall && !showNav && (
|
|
<Popup
|
|
shop={shop}
|
|
sceneIndex={sceneIndex}
|
|
mall={mall}
|
|
onClick={({ houseNum }) => {
|
|
setShowShops(false);
|
|
setDoFocus(2);
|
|
map.focusShopByHouseNum(houseNum);
|
|
}}
|
|
floors={
|
|
mall &&
|
|
sceneIndex !== null && (
|
|
<Floors
|
|
start={start}
|
|
end={end}
|
|
clickable={map && !showNav}
|
|
showNav={showNav}
|
|
onClickFloor={map && map.changeFloor}
|
|
floors={mall.floors}
|
|
sceneIndex={sceneIndex}
|
|
setSceneIndex={setSceneIndex}
|
|
mall={mall}
|
|
></Floors>
|
|
)
|
|
}
|
|
onClickActive={handleEndSet}
|
|
></Popup>
|
|
)}
|
|
{showNav && (
|
|
<NavBottom
|
|
routeSearchAnimationType={map.routeSearchAnimationType()}
|
|
switchType={() =>
|
|
map.changeRouteSearchAnimationType(
|
|
map.routeSearchAnimationType() === 1 ? 2 : 1
|
|
)
|
|
}
|
|
onExit={exitFromNav}
|
|
end={end}
|
|
meters={Math.floor(navigation.totalDistanceInMeter * (1 - percent))}
|
|
minutes={Math.floor(
|
|
(navigation.totalDistanceInMeter * (1 - percent)) / 1.4 / 60
|
|
)}
|
|
puaseOrResume={() => {
|
|
if (paused) navigation.resume();
|
|
else navigation.pause();
|
|
}}
|
|
paused={paused}
|
|
startText={mall.floors[start.floorOrder][1]}
|
|
routeSearchText={routeSearchText}
|
|
percent={percent}
|
|
elevations={elevations}
|
|
></NavBottom>
|
|
)}
|
|
</div>
|
|
</MallCode.Provider>
|
|
);
|
|
};
|
|
export default Index;
|
|
|