+ {
+ (() => {
+ if (route.type === 'driving') {
+ let drivingChildren = [];
+ if (showSummary1) {
+ const [timeSummary, distanceSummary] = this.getTimeDistanceSummary(path);
+ drivingChildren.push(
{`${timeSummary} ${distanceSummary}`}
);
+ }
+ drivingChildren = drivingChildren.concat([
+
{path.steps.map((step) => step.road).filter((road) => !!road).join(" -> ")}
,
+
红绿灯{path.traffic_lights}个 {route.taxi_cost && `打车${parseFloat(route.taxi_cost).toFixed(1)}元`}
+ ]);
+ return drivingChildren;
+ } else if (route.type === 'bus') {
+ const busNames = [];
+ const transit = path;
+ if (transit.segments && transit.segments.length > 0) {
+ transit.segments.forEach((segment) => {
+ if(segment){
+ if (segment.bus && segment.bus.buslines && segment.bus.buslines.length > 0) {
+ segment.bus.buslines.forEach((busline) => {
+ if (busline && busline.busName) {
+ if (busNames.indexOf(busline.busName) < 0) {
+ busNames.push(busline.busName);
+ }
+ }
+ });
+ }
+ if(segment.railway){
+ let {
+ departure_stop,
+ arrival_stop
+ } = segment.railway;
+ if(departure_stop && departure_stop.name){
+ if(departure_stop.name.slice(-1) === '站'){
+ busNames.push(departure_stop.name);
+ }else{
+ busNames.push(`${departure_stop.name}站`);
+ }
+ }
+ if(arrival_stop && arrival_stop.name){
+ if(arrival_stop.name.slice(-1) === '站'){
+ busNames.push(arrival_stop.name);
+ }else{
+ busNames.push(`${arrival_stop.name}站`);
+ }
+ }
+ }
+ }
+ });
+ }
+ return [
+
{busNames.join(" -> ")}
,
+
花费{parseFloat(transit.cost).toFixed(1)}元 步行{this.getDistanceSummary(transit.walking_distance)}
+ ];
+ } else if (route.type === 'walking') {
+ const [timeSummary, distanceSummary] = this.getTimeDistanceSummary(path);
+ return [
+
{`${timeSummary} ${distanceSummary}`}
,
+
{path.steps.map((step) => step.road).filter((road) => typeof road === 'string' && !!road).join(" -> ")}
,
+
详情
+ ];
+ }
+ return false;
+ })()
+ }
+
+ );
+ }
+
+ getTimeDistanceSummary(path) {
+ let {
+ distance,
+ duration
+ } = path;
+
+ return [this.getTimeSummary(duration), this.getDistanceSummary(distance)];
+ }
+
+ getTimeSummary(seconds) {
+ seconds = parseFloat(seconds);
+ let allMinutes = Math.round(seconds / 60);
+ if(allMinutes < 60){
+ return `${allMinutes}分钟`;
+ }else{
+ let hours = Math.floor(allMinutes / 60);
+ let minutes = allMinutes - hours * 60;
+ if(minutes > 0){
+ return `${hours}小时${minutes}分钟`;
+ }else{
+ return `${hours}小时`;
+ }
+ }
+ }
+
+ getDistanceSummary(distance) {
+ distance = parseFloat(distance);
+ let distanceSummary = distance >= 1000 ? `${(distance / 1000).toFixed(1)}公里` : `${distance}米`;
+ return distanceSummary;
+ }
+};
\ No newline at end of file
diff --git a/src/webapp/routes/nav/Paths/index.scss b/src/webapp/routes/nav/Paths/index.scss
new file mode 100644
index 0000000..703d3f5
--- /dev/null
+++ b/src/webapp/routes/nav/Paths/index.scss
@@ -0,0 +1,139 @@
+@import "../../../css/variables";
+$header-height: 46px;
+
+$footer-single-path-detail-height: 77px;
+$footer-single-path-height: $footer-single-path-detail-height;
+
+$footer-multiple-path-tabs-height: 45px;
+$footer-multiple-path-detail-height: 60px;
+$footer-multiple-path-height: $footer-multiple-path-tabs-height + $footer-multiple-path-detail-height;
+
+
+.map-container{
+ position: absolute;
+ left: 0;
+ top: $header-height;
+ width: 100%;
+}
+
+.footer{
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+
+ .tabs{
+ box-sizing: border-box;
+ height: $footer-multiple-path-tabs-height;
+ text-align: center;
+ font-size: 12px;
+ display: flex;
+ flex-flow: row nowrap;
+
+ .tab{
+ box-sizing: border-box;
+ height: 100%;
+ flex: 1 1 auto;
+ background: #efefef;
+ border-left: 1px solid #dfdfdf;
+ border-top: 3px solid transparent;
+ &.selected{
+ background: white;
+ color: $blue-color;
+ border-top-color: $blue-color;
+ }
+
+ .time{
+ margin: 2px 0;
+ }
+ }
+ }
+
+ .path-details{
+ box-sizing: border-box;
+ position: relative;
+ overflow: hidden;
+
+ .path-detail{
+ box-sizing: border-box;
+ position: absolute;
+ left: 100%;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ padding: 0 9px;
+
+ &.selected{
+ left: 0;
+ }
+
+ .summary1{
+ font-weight: bold;
+ font-size: 14px;
+ color: #333;
+ margin: 4px 0;
+ }
+
+ .summary2{
+ font-size: 14px;
+ color: #333;
+ }
+
+ .summary3{
+ margin-top: 5px;
+ font-size: 13px;
+ color: #999;
+ }
+
+ .detail-btn{
+ color: $blue-color;
+ }
+ }
+ }
+}
+
+.no-path{
+ .map-container{
+ bottom: 0;
+ }
+
+ .footer{
+ height: 0;
+
+ .path-details{
+ height: 0;
+ }
+ }
+}
+
+.single-path{
+ .map-container{
+ bottom: $footer-single-path-height;
+ }
+
+ .footer{
+ height: $footer-single-path-height;
+
+ .path-details{
+ height: $footer-single-path-detail-height;
+ }
+ }
+}
+
+.multiple-path{
+ .map-container{
+ bottom: $footer-multiple-path-height;
+ }
+
+ .footer{
+ height: $footer-multiple-path-height;
+
+ .path-details{
+ height: $footer-multiple-path-detail-height;
+
+ .summary2{
+ margin-top: 9px;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/webapp/routes/nav/Paths/route.js b/src/webapp/routes/nav/Paths/route.js
new file mode 100644
index 0000000..a439185
--- /dev/null
+++ b/src/webapp/routes/nav/Paths/route.js
@@ -0,0 +1,7 @@
+const route = {
+ path: 'paths',
+ component: require('./index'),
+ childRoutes: []
+};
+
+export default route;
\ No newline at end of file
diff --git a/src/webapp/routes/nav/Search/index.jsx b/src/webapp/routes/nav/Search/index.jsx
new file mode 100644
index 0000000..492e7bd
--- /dev/null
+++ b/src/webapp/routes/nav/Search/index.jsx
@@ -0,0 +1,192 @@
+import React from 'react';
+import TrafficTypes from 'webapp/components/TrafficTypes';
+import RouteComponent from 'webapp/components/RouteComponent';
+import classNames from 'classnames';
+import styles from './index.scss';
+import Kernel from 'world/Kernel';
+import Service from 'world/Service';
+import { globe } from 'webapp/components/Map';
+
+export default class Nav extends RouteComponent {
+
+ constructor(props) {
+ super(props);
+ this.fromPoi = null;
+ this.toPoi = null;
+ this.pageCapacity = 10;
+ this.searchDistance = Kernel.REAL_EARTH_RADIUS;
+ this.isFromLastFocused = true;
+ this.state = {
+ type: 'driving',//bus,walking
+ fromPois: [],
+ toPois: [],
+ routes: []
+ };
+ }
+
+ onCancel() {
+ this.goBack();
+ }
+
+ onKeyPress(e) {
+ const isFrom = e.target === this.fromInput;
+ const keyword = e.target.value;
+ if (e.key === "Enter") {
+ if(keyword){
+ this.searchPois(isFrom, keyword);
+ }
+ }
+ }
+
+ onFromFocus() {
+ this.isFromLastFocused = true;
+ this.setState({
+ toPois: []
+ });
+ }
+
+ onToFocus() {
+ this.isFromLastFocused = false;
+ this.setState({
+ fromPois: []
+ });
+ }
+
+ onClickFromSearchIcon(){
+ this.fromInput.focus();
+ const keyword = this.fromInput.value;
+ if(keyword){
+ this.searchPois(true, keyword);
+ }
+ }
+
+ onClickToSearchIcon(){
+ this.toInput.focus();
+ const keyword = this.toInput.value;
+ if(keyword){
+ this.searchPois(false, keyword);
+ }
+ }
+
+ onClickSearchIcon(isFrom, keyword) {
+ if(keyword){
+ this.searchPois(isFrom, keyword);
+ }
+ }
+
+ searchPois(isFrom, keyword) {
+ if (!keyword) {
+ return;
+ }
+ const promise = Service.searchNearby(keyword, this.searchDistance, 'Auto', this.pageCapacity);
+ this.wrapPromise(promise).then((response) => {
+ let pois = null;
+ if (response.detail) {
+ pois = response.detail.pois;
+ }
+ if (!pois) {
+ pois = [];
+ }
+ if (isFrom) {
+ this.setState({
+ fromPois: pois
+ });
+ } else {
+ this.setState({
+ toPois: pois
+ });
+ }
+ });
+ }
+
+ onClickPoi(poi, isFromPoi) {
+ if (isFromPoi) {
+ this.fromPoi = poi;
+ this.fromInput.value = poi.name || "";
+ this.setState({
+ fromPois: []
+ });
+ if (this.toPoi) {
+ this.route(this.fromPoi, this.toPoi);
+ } else {
+ this.toInput.focus();
+ }
+ } else {
+ this.toPoi = poi;
+ this.toInput.value = poi.name || "";
+ this.setState({
+ toPois: []
+ })
+ if (this.fromPoi) {
+ this.route(this.fromPoi, this.toPoi);
+ } else {
+ this.fromInput.focus();
+ }
+ }
+ }
+
+ onTrafficTypeChange(trafficType) {
+ this.setState({
+ type: trafficType
+ });
+ if (this.fromPoi && this.toPoi) {
+ this.route(this.fromPoi, this.toPoi);
+ }
+ }
+
+ route(fromPoi, toPoi) {
+ if (fromPoi && toPoi) {
+ this.props.router.push({
+ pathname: '/nav/paths',
+ state: {
+ type: this.state.type,
+ fromPoi: fromPoi,
+ toPoi: toPoi
+ }
+ });
+ }
+ }
+
+ render() {
+ const fromClassName = classNames("icon-location", styles["from-icon"]);
+ const toClassName = classNames("icon-circle-empty", styles["to-icon"]);
+ // const exchangeArrowClassName = classNames(fontStyles.fa, fontStyles["fa-arrows-v"]);
+ const addressClassName = classNames(styles.address, "ellipsis");
+ const pois = this.isFromLastFocused ? this.state.fromPois : this.state.toPois;
+
+ return (
+