Browse Source

first commit

dev
jiannibang 3 years ago
commit
a484d95952
  1. 25
      .gitignore
  2. 70
      README.md
  3. 55
      package.json
  4. 650
      public/aes.js
  5. 1
      public/es7.js
  6. BIN
      public/favicon.ico
  7. 42
      public/index.html
  8. BIN
      public/logo192.png
  9. BIN
      public/logo512.png
  10. 25
      public/manifest.json
  11. 3
      public/polyfill.js
  12. 3
      public/robots.txt
  13. 14
      src/App.js
  14. 17
      src/App.scss
  15. 8
      src/App.test.js
  16. 231
      src/components/DefaultPopup/DefaultPopup.js
  17. BIN
      src/components/DefaultPopup/arrow.png
  18. BIN
      src/components/DefaultPopup/close.png
  19. BIN
      src/components/DefaultPopup/fac.png
  20. BIN
      src/components/DefaultPopup/fac_active.png
  21. BIN
      src/components/DefaultPopup/go.png
  22. BIN
      src/components/DefaultPopup/poi.png
  23. BIN
      src/components/DefaultPopup/poi_active.png
  24. BIN
      src/components/DefaultPopup/search.png
  25. 268
      src/components/DefaultPopup/style.scss
  26. 74
      src/components/Floors/Floors.js
  27. 88
      src/components/Floors/Floors.scss
  28. BIN
      src/components/Floors/loc.png
  29. BIN
      src/components/Floors/up.png
  30. 195
      src/components/HeadBar/HeadBar.js
  31. 310
      src/components/HeadBar/HeadBar.scss
  32. BIN
      src/components/HeadBar/arrow.png
  33. BIN
      src/components/HeadBar/back.png
  34. BIN
      src/components/HeadBar/end.png
  35. BIN
      src/components/HeadBar/start.png
  36. BIN
      src/components/HeadBar/switch.png
  37. 260
      src/components/Malls/Malls.js
  38. 242
      src/components/Malls/Malls.scss
  39. BIN
      src/components/Malls/close_white.png
  40. BIN
      src/components/Malls/pos.png
  41. BIN
      src/components/Malls/search.png
  42. BIN
      src/components/Malls/up.png
  43. BIN
      src/components/More/2d.png
  44. BIN
      src/components/More/3d.png
  45. 53
      src/components/More/More.js
  46. 53
      src/components/More/More.scss
  47. 179
      src/components/NavBottom/NavBottom.js
  48. 333
      src/components/NavBottom/NavBottom.scss
  49. BIN
      src/components/NavBottom/ar.png
  50. BIN
      src/components/NavBottom/back.png
  51. BIN
      src/components/NavBottom/bubble.png
  52. BIN
      src/components/NavBottom/close.png
  53. BIN
      src/components/NavBottom/des.png
  54. BIN
      src/components/NavBottom/des_active.png
  55. BIN
      src/components/NavBottom/followDirection.png
  56. BIN
      src/components/NavBottom/left.png
  57. BIN
      src/components/NavBottom/nav.png
  58. BIN
      src/components/NavBottom/nav_active.png
  59. BIN
      src/components/NavBottom/notFollowDirection.png
  60. BIN
      src/components/NavBottom/pause.png
  61. BIN
      src/components/NavBottom/play.png
  62. BIN
      src/components/NavBottom/right.png
  63. BIN
      src/components/NavBottom/straight.png
  64. BIN
      src/components/Options/2d.png
  65. BIN
      src/components/Options/3d.png
  66. 137
      src/components/Options/Options.js
  67. 106
      src/components/Options/Options.scss
  68. BIN
      src/components/Options/ft.png
  69. BIN
      src/components/Options/ft1.png
  70. BIN
      src/components/Options/ftb.png
  71. BIN
      src/components/Options/ftb1.png
  72. BIN
      src/components/Options/mute.png
  73. BIN
      src/components/Options/on.png
  74. BIN
      src/components/Options/shortest.png
  75. BIN
      src/components/Options/shortest1.png
  76. BIN
      src/components/Options/shortestb.png
  77. BIN
      src/components/Options/shortestb1.png
  78. BIN
      src/components/Options/zt.png
  79. BIN
      src/components/Options/zt1.png
  80. BIN
      src/components/Options/ztb.png
  81. BIN
      src/components/Options/ztb1.png
  82. 50
      src/components/Popup/Popup.js
  83. 185
      src/components/Popup/Popup.scss
  84. BIN
      src/components/Popup/ar.png
  85. BIN
      src/components/Popup/close.png
  86. BIN
      src/components/Popup/desc.png
  87. BIN
      src/components/Popup/nav.png
  88. 39
      src/components/Shop/Shop.js
  89. 92
      src/components/Shop/Shop.scss
  90. BIN
      src/components/Shop/desc.png
  91. 58
      src/components/ShopList/ShopList.js
  92. 70
      src/components/ShopList/ShopList.scss
  93. 24
      src/components/ShopTabs/ShopTabs.js
  94. 32
      src/components/ShopTabs/ShopTabs.scss
  95. BIN
      src/components/ShopTabs/floor.png
  96. BIN
      src/components/ShopTabs/floor_active.png
  97. BIN
      src/components/ShopTabs/format.png
  98. BIN
      src/components/ShopTabs/format_active.png
  99. 83
      src/components/ShopsWithFilter/ShopsWithFilter.js
  100. 100
      src/components/ShopsWithFilter/ShopsWithFilter.scss

25
.gitignore

@ -0,0 +1,25 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.vscode

70
README.md

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `yarn build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

55
package.json

@ -0,0 +1,55 @@
{
"name": "hospital",
"version": "0.1.0",
"private": true,
"homepage": "./",
"dependencies": {
"@apollo/client": "^3.3.11",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"antd-mobile": "^2.3.4",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.10",
"apollo-link-http": "^1.5.17",
"axios": "^0.21.1",
"graphql": "^15.5.0",
"node-sass": "^5.0.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-infinite-scroller": "^1.2.4",
"react-lazy-load-image-component": "^1.5.1",
"react-modal": "^3.12.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"vconsole": "^3.4.0",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build && coscmd config -a AKIDjS6at7fjTAVgggDNCrogRiZTSL304DPR -s FPPGCXr4xgZRLKDC8tZGp7HTZXlqx0gU -b lg-cjdqwkbo-1256266248 -r ap-shanghai && coscmd delete hospital -f && coscmd upload -r build hospital",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"resolutions": {
"react-error-overlay": "6.0.9"
}
}

650
public/aes.js

@ -0,0 +1,650 @@
! function (t, n) {
"object" == typeof exports ? module.exports = exports = n() : "function" == typeof define && define.amd ? define([], n) : t.CryptoJS = n()
}(this, function () {
var t = t || function (t, n) {
var i = Object.create || function () {
function t() {}
return function (n) {
var i;
return t.prototype = n, i = new t, t.prototype = null, i
}
}(),
e = {},
r = e.lib = {},
o = r.Base = function () {
return {
extend: function (t) {
var n = i(this);
return t && n.mixIn(t), n.hasOwnProperty("init") && this.init !== n.init || (n.init = function () {
n.$super.init.apply(this, arguments)
}), n.init.prototype = n, n.$super = this, n
},
create: function () {
var t = this.extend();
return t.init.apply(t, arguments), t
},
init: function () {},
mixIn: function (t) {
for (var n in t) t.hasOwnProperty(n) && (this[n] = t[n]);
t.hasOwnProperty("toString") && (this.toString = t.toString)
},
clone: function () {
return this.init.prototype.extend(this)
}
}
}(),
s = r.WordArray = o.extend({
init: function (t, i) {
t = this.words = t || [], i != n ? this.sigBytes = i : this.sigBytes = 4 * t.length
},
toString: function (t) {
return (t || c).stringify(this)
},
concat: function (t) {
var n = this.words,
i = t.words,
e = this.sigBytes,
r = t.sigBytes;
if (this.clamp(), e % 4)
for (var o = 0; o < r; o++) {
var s = i[o >>> 2] >>> 24 - o % 4 * 8 & 255;
n[e + o >>> 2] |= s << 24 - (e + o) % 4 * 8
} else
for (var o = 0; o < r; o += 4) n[e + o >>> 2] = i[o >>> 2];
return this.sigBytes += r, this
},
clamp: function () {
var n = this.words,
i = this.sigBytes;
n[i >>> 2] &= 4294967295 << 32 - i % 4 * 8, n.length = t.ceil(i / 4)
},
clone: function () {
var t = o.clone.call(this);
return t.words = this.words.slice(0), t
},
random: function (n) {
for (var i, e = [], r = function (n) {
var n = n,
i = 987654321,
e = 4294967295;
return function () {
i = 36969 * (65535 & i) + (i >> 16) & e, n = 18e3 * (65535 & n) + (n >> 16) & e;
var r = (i << 16) + n & e;
return r /= 4294967296, r += .5, r * (t.random() > .5 ? 1 : -1)
}
}, o = 0; o < n; o += 4) {
var a = r(4294967296 * (i || t.random()));
i = 987654071 * a(), e.push(4294967296 * a() | 0)
}
return new s.init(e, n)
}
}),
a = e.enc = {},
c = a.Hex = {
stringify: function (t) {
for (var n = t.words, i = t.sigBytes, e = [], r = 0; r < i; r++) {
var o = n[r >>> 2] >>> 24 - r % 4 * 8 & 255;
e.push((o >>> 4).toString(16)), e.push((15 & o).toString(16))
}
return e.join("")
},
parse: function (t) {
for (var n = t.length, i = [], e = 0; e < n; e += 2) i[e >>> 3] |= parseInt(t.substr(e, 2), 16) << 24 - e % 8 * 4;
return new s.init(i, n / 2)
}
},
u = a.Latin1 = {
stringify: function (t) {
for (var n = t.words, i = t.sigBytes, e = [], r = 0; r < i; r++) {
var o = n[r >>> 2] >>> 24 - r % 4 * 8 & 255;
e.push(String.fromCharCode(o))
}
return e.join("")
},
parse: function (t) {
for (var n = t.length, i = [], e = 0; e < n; e++) i[e >>> 2] |= (255 & t.charCodeAt(e)) << 24 - e % 4 * 8;
return new s.init(i, n)
}
},
f = a.Utf8 = {
stringify: function (t) {
try {
return decodeURIComponent(escape(u.stringify(t)))
} catch (t) {
throw new Error("Malformed UTF-8 data")
}
},
parse: function (t) {
return u.parse(unescape(encodeURIComponent(t)))
}
},
h = r.BufferedBlockAlgorithm = o.extend({
reset: function () {
this._data = new s.init, this._nDataBytes = 0
},
_append: function (t) {
"string" == typeof t && (t = f.parse(t)), this._data.concat(t), this._nDataBytes += t.sigBytes
},
_process: function (n) {
var i = this._data,
e = i.words,
r = i.sigBytes,
o = this.blockSize,
a = 4 * o,
c = r / a;
c = n ? t.ceil(c) : t.max((0 | c) - this._minBufferSize, 0);
var u = c * o,
f = t.min(4 * u, r);
if (u) {
for (var h = 0; h < u; h += o) this._doProcessBlock(e, h);
var p = e.splice(0, u);
i.sigBytes -= f
}
return new s.init(p, f)
},
clone: function () {
var t = o.clone.call(this);
return t._data = this._data.clone(), t
},
_minBufferSize: 0
}),
p = (r.Hasher = h.extend({
cfg: o.extend(),
init: function (t) {
this.cfg = this.cfg.extend(t), this.reset()
},
reset: function () {
h.reset.call(this), this._doReset()
},
update: function (t) {
return this._append(t), this._process(), this
},
finalize: function (t) {
t && this._append(t);
var n = this._doFinalize();
return n
},
blockSize: 16,
_createHelper: function (t) {
return function (n, i) {
return new t.init(i).finalize(n)
}
},
_createHmacHelper: function (t) {
return function (n, i) {
return new p.HMAC.init(t, i).finalize(n)
}
}
}), e.algo = {});
return e
}(Math);
return t
});
//# sourceMappingURL=core.min.js.map
! function (e, t, i) {
"object" == typeof exports ? module.exports = exports = t(require("./core.min"), require("./sha1.min"), require("./hmac.min")) : "function" == typeof define && define.amd ? define(["./core.min", "./sha1.min", "./hmac.min"], t) : t(e.CryptoJS)
}(this, function (e) {
return function () {
var t = e,
i = t.lib,
r = i.Base,
n = i.WordArray,
o = t.algo,
a = o.MD5,
c = o.EvpKDF = r.extend({
cfg: r.extend({
keySize: 4,
hasher: a,
iterations: 1
}),
init: function (e) {
this.cfg = this.cfg.extend(e)
},
compute: function (e, t) {
for (var i = this.cfg, r = i.hasher.create(), o = n.create(), a = o.words, c = i.keySize, f = i.iterations; a.length < c;) {
s && r.update(s);
var s = r.update(e).finalize(t);
r.reset();
for (var u = 1; u < f; u++) s = r.finalize(s), r.reset();
o.concat(s)
}
return o.sigBytes = 4 * c, o
}
});
t.EvpKDF = function (e, t, i) {
return c.create(i).compute(e, t)
}
}(), e.EvpKDF
});
//# sourceMappingURL=evpkdf.min.js.map
! function (r, e) {
"object" == typeof exports ? module.exports = exports = e(require("./core.min")) : "function" == typeof define && define.amd ? define(["./core.min"], e) : e(r.CryptoJS)
}(this, function (r) {
return function () {
function e(r, e, t) {
for (var n = [], i = 0, o = 0; o < e; o++)
if (o % 4) {
var f = t[r.charCodeAt(o - 1)] << o % 4 * 2,
c = t[r.charCodeAt(o)] >>> 6 - o % 4 * 2;
n[i >>> 2] |= (f | c) << 24 - i % 4 * 8, i++
} return a.create(n, i)
}
var t = r,
n = t.lib,
a = n.WordArray,
i = t.enc;
i.Base64 = {
stringify: function (r) {
var e = r.words,
t = r.sigBytes,
n = this._map;
r.clamp();
for (var a = [], i = 0; i < t; i += 3)
for (var o = e[i >>> 2] >>> 24 - i % 4 * 8 & 255, f = e[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 255, c = e[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 255, s = o << 16 | f << 8 | c, h = 0; h < 4 && i + .75 * h < t; h++) a.push(n.charAt(s >>> 6 * (3 - h) & 63));
var p = n.charAt(64);
if (p)
for (; a.length % 4;) a.push(p);
return a.join("")
},
parse: function (r) {
var t = r.length,
n = this._map,
a = this._reverseMap;
if (!a) {
a = this._reverseMap = [];
for (var i = 0; i < n.length; i++) a[n.charCodeAt(i)] = i
}
var o = n.charAt(64);
if (o) {
var f = r.indexOf(o);
f !== -1 && (t = f)
}
return e(r, t, a)
},
_map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
}
}(), r.enc.Base64
});
//# sourceMappingURL=enc-base64.min.js.map
! function (e, t, r) {
"object" == typeof exports ? module.exports = exports = t(require("./core.min"), require("./evpkdf.min")) : "function" == typeof define && define.amd ? define(["./core.min", "./evpkdf.min"], t) : t(e.CryptoJS)
}(this, function (e) {
e.lib.Cipher || function (t) {
var r = e,
i = r.lib,
n = i.Base,
c = i.WordArray,
o = i.BufferedBlockAlgorithm,
s = r.enc,
a = (s.Utf8, s.Base64),
f = r.algo,
p = f.EvpKDF,
d = i.Cipher = o.extend({
cfg: n.extend(),
createEncryptor: function (e, t) {
return this.create(this._ENC_XFORM_MODE, e, t)
},
createDecryptor: function (e, t) {
return this.create(this._DEC_XFORM_MODE, e, t)
},
init: function (e, t, r) {
this.cfg = this.cfg.extend(r), this._xformMode = e, this._key = t, this.reset()
},
reset: function () {
o.reset.call(this), this._doReset()
},
process: function (e) {
return this._append(e), this._process()
},
finalize: function (e) {
e && this._append(e);
var t = this._doFinalize();
return t
},
keySize: 4,
ivSize: 4,
_ENC_XFORM_MODE: 1,
_DEC_XFORM_MODE: 2,
_createHelper: function () {
function e(e) {
return "string" == typeof e ? B : x
}
return function (t) {
return {
encrypt: function (r, i, n) {
return e(i).encrypt(t, r, i, n)
},
decrypt: function (r, i, n) {
return e(i).decrypt(t, r, i, n)
}
}
}
}()
}),
h = (i.StreamCipher = d.extend({
_doFinalize: function () {
var e = this._process(!0);
return e
},
blockSize: 1
}), r.mode = {}),
u = i.BlockCipherMode = n.extend({
createEncryptor: function (e, t) {
return this.Encryptor.create(e, t)
},
createDecryptor: function (e, t) {
return this.Decryptor.create(e, t)
},
init: function (e, t) {
this._cipher = e, this._iv = t
}
}),
l = h.CBC = function () {
function e(e, r, i) {
var n = this._iv;
if (n) {
var c = n;
this._iv = t
} else var c = this._prevBlock;
for (var o = 0; o < i; o++) e[r + o] ^= c[o]
}
var r = u.extend();
return r.Encryptor = r.extend({
processBlock: function (t, r) {
var i = this._cipher,
n = i.blockSize;
e.call(this, t, r, n), i.encryptBlock(t, r), this._prevBlock = t.slice(r, r + n)
}
}), r.Decryptor = r.extend({
processBlock: function (t, r) {
var i = this._cipher,
n = i.blockSize,
c = t.slice(r, r + n);
i.decryptBlock(t, r), e.call(this, t, r, n), this._prevBlock = c
}
}), r
}(),
_ = r.pad = {},
v = _.Pkcs7 = {
pad: function (e, t) {
for (var r = 4 * t, i = r - e.sigBytes % r, n = i << 24 | i << 16 | i << 8 | i, o = [], s = 0; s < i; s += 4) o.push(n);
var a = c.create(o, i);
e.concat(a)
},
unpad: function (e) {
var t = 255 & e.words[e.sigBytes - 1 >>> 2];
e.sigBytes -= t
}
},
y = (i.BlockCipher = d.extend({
cfg: d.cfg.extend({
mode: l,
padding: v
}),
reset: function () {
d.reset.call(this);
var e = this.cfg,
t = e.iv,
r = e.mode;
if (this._xformMode == this._ENC_XFORM_MODE) var i = r.createEncryptor;
else {
var i = r.createDecryptor;
this._minBufferSize = 1
}
this._mode && this._mode.__creator == i ? this._mode.init(this, t && t.words) : (this._mode = i.call(r, this, t && t.words), this._mode.__creator = i)
},
_doProcessBlock: function (e, t) {
this._mode.processBlock(e, t)
},
_doFinalize: function () {
var e = this.cfg.padding;
if (this._xformMode == this._ENC_XFORM_MODE) {
e.pad(this._data, this.blockSize);
var t = this._process(!0)
} else {
var t = this._process(!0);
e.unpad(t)
}
return t
},
blockSize: 4
}), i.CipherParams = n.extend({
init: function (e) {
this.mixIn(e)
},
toString: function (e) {
return (e || this.formatter).stringify(this)
}
})),
m = r.format = {},
k = m.OpenSSL = {
stringify: function (e) {
var t = e.ciphertext,
r = e.salt;
if (r) var i = c.create([1398893684, 1701076831]).concat(r).concat(t);
else var i = t;
return i.toString(a)
},
parse: function (e) {
var t = a.parse(e),
r = t.words;
if (1398893684 == r[0] && 1701076831 == r[1]) {
var i = c.create(r.slice(2, 4));
r.splice(0, 4), t.sigBytes -= 16
}
return y.create({
ciphertext: t,
salt: i
})
}
},
x = i.SerializableCipher = n.extend({
cfg: n.extend({
format: k
}),
encrypt: function (e, t, r, i) {
i = this.cfg.extend(i);
var n = e.createEncryptor(r, i),
c = n.finalize(t),
o = n.cfg;
return y.create({
ciphertext: c,
key: r,
iv: o.iv,
algorithm: e,
mode: o.mode,
padding: o.padding,
blockSize: e.blockSize,
formatter: i.format
})
},
decrypt: function (e, t, r, i) {
i = this.cfg.extend(i), t = this._parse(t, i.format);
var n = e.createDecryptor(r, i).finalize(t.ciphertext);
return n
},
_parse: function (e, t) {
return "string" == typeof e ? t.parse(e, this) : e
}
}),
g = r.kdf = {},
S = g.OpenSSL = {
execute: function (e, t, r, i) {
i || (i = c.random(8));
var n = p.create({
keySize: t + r
}).compute(e, i),
o = c.create(n.words.slice(t), 4 * r);
return n.sigBytes = 4 * t, y.create({
key: n,
iv: o,
salt: i
})
}
},
B = i.PasswordBasedCipher = x.extend({
cfg: x.cfg.extend({
kdf: S
}),
encrypt: function (e, t, r, i) {
i = this.cfg.extend(i);
var n = i.kdf.execute(r, e.keySize, e.ivSize);
i.iv = n.iv;
var c = x.encrypt.call(this, e, t, n.key, i);
return c.mixIn(n), c
},
decrypt: function (e, t, r, i) {
i = this.cfg.extend(i), t = this._parse(t, i.format);
var n = i.kdf.execute(r, e.keySize, e.ivSize, t.salt);
i.iv = n.iv;
var c = x.decrypt.call(this, e, t, n.key, i);
return c
}
})
}()
});
//# sourceMappingURL=cipher-core.min.js.map
! function (e, i) {
"object" == typeof exports ? module.exports = exports = i(require("./core.min")) : "function" == typeof define && define.amd ? define(["./core.min"], i) : i(e.CryptoJS)
}(this, function (e) {
! function () {
var i = e,
t = i.lib,
n = t.Base,
s = i.enc,
r = s.Utf8,
o = i.algo;
o.HMAC = n.extend({
init: function (e, i) {
e = this._hasher = new e.init, "string" == typeof i && (i = r.parse(i));
var t = e.blockSize,
n = 4 * t;
i.sigBytes > n && (i = e.finalize(i)), i.clamp();
for (var s = this._oKey = i.clone(), o = this._iKey = i.clone(), a = s.words, f = o.words, c = 0; c < t; c++) a[c] ^= 1549556828, f[c] ^= 909522486;
s.sigBytes = o.sigBytes = n, this.reset()
},
reset: function () {
var e = this._hasher;
e.reset(), e.update(this._iKey)
},
update: function (e) {
return this._hasher.update(e), this
},
finalize: function (e) {
var i = this._hasher,
t = i.finalize(e);
i.reset();
var n = i.finalize(this._oKey.clone().concat(t));
return n
}
})
}()
});
//# sourceMappingURL=hmac.min.js.map
! function (e, o, r) {
"object" == typeof exports ? module.exports = exports = o(require("./core.min"), require("./cipher-core.min")) : "function" == typeof define && define.amd ? define(["./core.min", "./cipher-core.min"], o) : o(e.CryptoJS)
}(this, function (e) {
return e.mode.ECB = function () {
var o = e.lib.BlockCipherMode.extend();
return o.Encryptor = o.extend({
processBlock: function (e, o) {
this._cipher.encryptBlock(e, o)
}
}), o.Decryptor = o.extend({
processBlock: function (e, o) {
this._cipher.decryptBlock(e, o)
}
}), o
}(), e.mode.ECB
});
//# sourceMappingURL=mode-ecb.min.js.map
! function (e, r, i) {
"object" == typeof exports ? module.exports = exports = r(require("./core.min"), require("./cipher-core.min")) : "function" == typeof define && define.amd ? define(["./core.min", "./cipher-core.min"], r) : r(e.CryptoJS)
}(this, function (e) {
return e.pad.Pkcs7
});
//# sourceMappingURL=pad-pkcs7.min.js.map
! function (e, r, i) {
"object" == typeof exports ? module.exports = exports = r(require("./core.min"), require("./enc-base64.min"), require("./md5.min"), require("./evpkdf.min"), require("./cipher-core.min")) : "function" == typeof define && define.amd ? define(["./core.min", "./enc-base64.min", "./md5.min", "./evpkdf.min", "./cipher-core.min"], r) : r(e.CryptoJS)
}(this, function (e) {
return function () {
var r = e,
i = r.lib,
n = i.BlockCipher,
o = r.algo,
t = [],
c = [],
s = [],
f = [],
a = [],
d = [],
u = [],
v = [],
h = [],
y = [];
! function () {
for (var e = [], r = 0; r < 256; r++) r < 128 ? e[r] = r << 1 : e[r] = r << 1 ^ 283;
for (var i = 0, n = 0, r = 0; r < 256; r++) {
var o = n ^ n << 1 ^ n << 2 ^ n << 3 ^ n << 4;
o = o >>> 8 ^ 255 & o ^ 99, t[i] = o, c[o] = i;
var p = e[i],
l = e[p],
_ = e[l],
k = 257 * e[o] ^ 16843008 * o;
s[i] = k << 24 | k >>> 8, f[i] = k << 16 | k >>> 16, a[i] = k << 8 | k >>> 24, d[i] = k;
var k = 16843009 * _ ^ 65537 * l ^ 257 * p ^ 16843008 * i;
u[o] = k << 24 | k >>> 8, v[o] = k << 16 | k >>> 16, h[o] = k << 8 | k >>> 24, y[o] = k, i ? (i = p ^ e[e[e[_ ^ p]]], n ^= e[e[n]]) : i = n = 1
}
}();
var p = [0, 1, 2, 4, 8, 16, 32, 64, 128, 27, 54],
l = o.AES = n.extend({
_doReset: function () {
if (!this._nRounds || this._keyPriorReset !== this._key) {
for (var e = this._keyPriorReset = this._key, r = e.words, i = e.sigBytes / 4, n = this._nRounds = i + 6, o = 4 * (n + 1), c = this._keySchedule = [], s = 0; s < o; s++)
if (s < i) c[s] = r[s];
else {
var f = c[s - 1];
s % i ? i > 6 && s % i == 4 && (f = t[f >>> 24] << 24 | t[f >>> 16 & 255] << 16 | t[f >>> 8 & 255] << 8 | t[255 & f]) : (f = f << 8 | f >>> 24, f = t[f >>> 24] << 24 | t[f >>> 16 & 255] << 16 | t[f >>> 8 & 255] << 8 | t[255 & f], f ^= p[s / i | 0] << 24), c[s] = c[s - i] ^ f
} for (var a = this._invKeySchedule = [], d = 0; d < o; d++) {
var s = o - d;
if (d % 4) var f = c[s];
else var f = c[s - 4];
d < 4 || s <= 4 ? a[d] = f : a[d] = u[t[f >>> 24]] ^ v[t[f >>> 16 & 255]] ^ h[t[f >>> 8 & 255]] ^ y[t[255 & f]]
}
}
},
encryptBlock: function (e, r) {
this._doCryptBlock(e, r, this._keySchedule, s, f, a, d, t)
},
decryptBlock: function (e, r) {
var i = e[r + 1];
e[r + 1] = e[r + 3], e[r + 3] = i, this._doCryptBlock(e, r, this._invKeySchedule, u, v, h, y, c);
var i = e[r + 1];
e[r + 1] = e[r + 3], e[r + 3] = i
},
_doCryptBlock: function (e, r, i, n, o, t, c, s) {
for (var f = this._nRounds, a = e[r] ^ i[0], d = e[r + 1] ^ i[1], u = e[r + 2] ^ i[2], v = e[r + 3] ^ i[3], h = 4, y = 1; y < f; y++) {
var p = n[a >>> 24] ^ o[d >>> 16 & 255] ^ t[u >>> 8 & 255] ^ c[255 & v] ^ i[h++],
l = n[d >>> 24] ^ o[u >>> 16 & 255] ^ t[v >>> 8 & 255] ^ c[255 & a] ^ i[h++],
_ = n[u >>> 24] ^ o[v >>> 16 & 255] ^ t[a >>> 8 & 255] ^ c[255 & d] ^ i[h++],
k = n[v >>> 24] ^ o[a >>> 16 & 255] ^ t[d >>> 8 & 255] ^ c[255 & u] ^ i[h++];
a = p, d = l, u = _, v = k
}
var p = (s[a >>> 24] << 24 | s[d >>> 16 & 255] << 16 | s[u >>> 8 & 255] << 8 | s[255 & v]) ^ i[h++],
l = (s[d >>> 24] << 24 | s[u >>> 16 & 255] << 16 | s[v >>> 8 & 255] << 8 | s[255 & a]) ^ i[h++],
_ = (s[u >>> 24] << 24 | s[v >>> 16 & 255] << 16 | s[a >>> 8 & 255] << 8 | s[255 & d]) ^ i[h++],
k = (s[v >>> 24] << 24 | s[a >>> 16 & 255] << 16 | s[d >>> 8 & 255] << 8 | s[255 & u]) ^ i[h++];
e[r] = p, e[r + 1] = l, e[r + 2] = _, e[r + 3] = k
},
keySize: 8
});
r.AES = n._createHelper(l)
}(), e.AES
});
//# sourceMappingURL=aes.min.js.map
! function (e, n) {
"object" == typeof exports ? module.exports = exports = n(require("./core.min")) : "function" == typeof define && define.amd ? define(["./core.min"], n) : n(e.CryptoJS)
}(this, function (e) {
return e.enc.Utf8
});
//# sourceMappingURL=enc-utf8.min.js.map

1
public/es7.js

File diff suppressed because one or more lines are too long

BIN
public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

42
public/index.html

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,user-scalable=no"
/>
<meta name="theme-color" content="#000000" />
<meta name="description" content="上海市东方医院手机地图导览" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>上海市东方医院</title>
<script src="%PUBLIC_URL%/polyfill.js"></script>
<script src="%PUBLIC_URL%/es7.js"></script>
<script src="%PUBLIC_URL%/aes.js"></script>
<script
type="text/javascript"
src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"
></script>
<link
rel="stylesheet"
href="https://res.wx.qq.com/open/libs/weui/2.0.1/weui.min.css"
/>
<script
type="text/javascript"
src="https://res.wx.qq.com/t/wx_fed/cdn_libs/res/weui/1.2.3/weui.min.js"
></script>
</head>
<body>
<noscript>上海市东方医院手机地图导览</noscript>
<style>
#root {
position: fixed;
width: 100vw;
height: 100vh;
}
</style>
<div id="root"></div>
</body>
</html>

BIN
public/logo192.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
public/manifest.json

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/polyfill.js

File diff suppressed because one or more lines are too long

3
public/robots.txt

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

14
src/App.js

@ -0,0 +1,14 @@
import { HashRouter, Switch, Route } from "react-router-dom";
import Index from "./pages/Index/Index";
import "./App.scss";
function App() {
return (
<HashRouter basename="">
<Switch>
<Route path="/" component={Index} />
</Switch>
</HashRouter>
);
}
export default App;

17
src/App.scss

@ -0,0 +1,17 @@
.toast {
.weui-icon_toast {
display: none;
}
.weui-toast {
display: inline-block;
width: auto;
min-height: auto;
margin: auto;
padding: 16px;
transform-origin: center;
transform: translateX(-50%);
.weui-toast__content {
margin: 0;
}
}
}

8
src/App.test.js

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

231
src/components/DefaultPopup/DefaultPopup.js

@ -0,0 +1,231 @@
import "./style.scss";
import React, { useState, useEffect } from "react";
import search from "./search.png";
import close from "./close.png";
import go from "./go.png";
import arrow from "./arrow.png";
import ShopsWithFilter from "../ShopsWithFilter/ShopsWithFilter";
import facIcon from "./fac.png";
import facActiveIcon from "./fac_active.png";
import poiIcon from "./poi.png";
import poiActiveIcon from "./poi_active.png";
const STATES = {
init: 0,
moreFac: 1,
facList: 2,
};
export const DefaultPopupStates = STATES;
const initMarginBottom = 47;
const DefaultPopup = ({
state,
setState,
onSearch,
facilities,
onClickFac,
mall,
onClick,
setEnd,
blurMap = () => {},
}) => {
const tabs = [
{ name: "便民服务", icon: facIcon, activeIcon: facActiveIcon },
{ name: "科室列表", icon: poiIcon, activeIcon: poiActiveIcon },
];
const heightStateMap = { 0: 224, 1: window.innerHeight - 24 };
const baseHeight = heightStateMap[state];
const [facList, setFacList] = useState(null);
const [focused, setFocused] = useState(null);
const [start, setStart] = useState(null);
const [height, setHeight] = useState(null);
const [doTransition, setdoTransition] = useState(false);
const [tabIndex, setTabIndex] = useState(0);
useEffect(() => {
setdoTransition(true);
setHeight(baseHeight);
setTimeout(() => {
setdoTransition(false);
}, 500);
}, [state]);
const handleTouchStart = (e) => {
if (start) return;
setStart({
identifier: e.changedTouches[0].identifier,
y: e.changedTouches[0].clientY,
});
};
const handleTouchMove = (e) => {
if (!start) return;
const touch = Array.from(e.changedTouches).find(
({ identifier }) => identifier === start.identifier
);
if (!touch) return;
const delta = touch.clientY - start.y;
setHeight(baseHeight - delta);
};
const handleTouchEnd = (e) => {
if (!start) return;
const touch = Array.from(e.changedTouches).find(
({ identifier }) => identifier === start.identifier
);
if (!touch) return;
setStart(null);
const delta = touch.clientY - start.y;
const newState =
state === STATES.init && delta < -100
? STATES.moreFac
: state === STATES.moreFac && delta > 100
? STATES.init
: state;
if (state === newState) {
setdoTransition(true);
setHeight(baseHeight);
setTimeout(() => {
setdoTransition(false);
}, 500);
} else setState(newState);
};
return (
<>
{state === STATES.init || state === STATES.moreFac ? (
<div
className={"dp" + (state === STATES.moreFac ? " more-fac" : "")}
style={{
height: height + "px",
transition: !doTransition ? "none" : "height 0.5s ease",
}}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchCancel={handleTouchEnd}
onTouchEnd={handleTouchEnd}
>
<div
className="ts"
onClick={() =>
setState(state === STATES.moreFac ? STATES.init : STATES.moreFac)
}
>
<img className="t1" src={arrow} />
</div>
<div className="search" onClick={onSearch}>
<img className="icon" src={search}></img>
点击可查找科室
</div>
{tabIndex === 0 ? (
<div className="facs">
{Object.entries(facilities).map(([name, list]) => (
<div
className="fac"
key={name}
onClick={() => {
list.sort((a, b) => a.floorOrder - b.floorOrder);
setFacList(list);
setState(STATES.facList);
}}
>
<img src={list[0].url}></img>
{name}
</div>
))}
</div>
) : (
<div
className="sf"
onClick={() => {
setState(STATES.moreFac);
}}
>
<ShopsWithFilter
mall={mall}
onClick={(e) => {
setState(STATES.init);
onClick(e);
}}
wingHeight="calc(100vh - 270px)"
></ShopsWithFilter>
</div>
)}
{state === STATES.moreFac && (
<div className="tabs">
{tabs.map(({ name, icon, activeIcon }, i) => {
const active = tabIndex === i;
return (
<div
onClick={() => setTabIndex(i)}
key={name}
className={"tab" + (active ? " active" : "")}
>
<img className="icon" src={active ? activeIcon : icon} />
{name}
</div>
);
})}
</div>
)}
</div>
) : (
<>
<img
src={close}
className="close"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setState(STATES.init);
setFacList(null);
blurMap();
}}
></img>
<div className="dp1">
{facList &&
facList.map((fac, i) => {
const showRow1 =
i === 0 || fac.floorName !== facList[i - 1].floorName;
const isActive = focused === fac.id;
return (
<div key={fac.id}>
{showRow1 && (
<div className={"row1" + (i === 0 ? "" : " has-border")}>
{fac.floorName}
</div>
)}
<div
className={[
"row2",
isActive ? "active" : "",
!showRow1 ? "has-margin" : "",
].join(" ")}
onClick={() => {
setFocused(fac.id);
onClickFac(fac.id);
}}
>
{fac.name}
<span className="meta">{fac.floorName}</span>
{isActive && (
<div
className="go"
src={go}
onClick={(e) => {
e.stopPropagation();
setState(STATES.init);
onClickFac(fac.id);
}}
>
GO
</div>
)}
</div>
</div>
);
})}
</div>
</>
)}
</>
);
};
export default DefaultPopup;

BIN
src/components/DefaultPopup/arrow.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

BIN
src/components/DefaultPopup/close.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

BIN
src/components/DefaultPopup/fac.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/components/DefaultPopup/fac_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/components/DefaultPopup/go.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/components/DefaultPopup/poi.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

BIN
src/components/DefaultPopup/poi_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

BIN
src/components/DefaultPopup/search.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

268
src/components/DefaultPopup/style.scss

@ -0,0 +1,268 @@
.dp {
position: relative;
width: calc(100vw - 20px);
margin-left: 10px;
text-align: center;
position: relative;
padding: 14px 0 0 0;
background: #ffffff;
box-shadow: 0px 12px 16px rgba(104, 110, 127, 0.08);
border-radius: 16px 16px 0px 0px;
@keyframes fadeInTabs {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.tabs {
position: absolute;
bottom: 38px;
left: 0;
right: 0;
width: 327px;
height: 56px;
background: #f2f3f5;
border-radius: 8px;
margin: auto;
animation: fadeInTabs 1s;
display: flex;
padding: 4px;
.tab {
flex: 1;
display: flex;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 22px;
color: #333333;
align-content: center;
justify-content: center;
align-items: center;
.icon {
width: 20px;
height: 20px;
margin-right: 8px;
}
&.active {
background: #ffffff;
border-radius: 8px;
}
}
}
&::before {
position: absolute;
content: "";
top: 0;
left: -10px;
right: -10px;
height: 236px;
background: linear-gradient(
180deg,
rgba(243, 244, 248, 0) 0%,
#f3f4f8 41.53%,
#f3f4f8 100%
);
z-index: -1;
}
&::after {
position: absolute;
content: "";
left: -10px;
right: -10px;
top: 236px;
height: calc(100vh - 52px - 236px);
background: #f3f4f8;
z-index: -1;
}
&.more-fac {
.t1,
.t2 {
transform: rotate(180deg);
}
}
.ts {
position: absolute;
margin: auto;
left: 0;
right: 0;
top: 0;
width: 24px;
height: 24px;
.t1 {
position: absolute;
margin: auto;
left: 0;
right: 0;
top: 3px;
width: 33px;
height: 7px;
border-radius: 5px;
}
}
.search {
position: relative;
display: flex;
align-items: center;
margin: 0 14px;
height: 48px;
font-family: PingFang SC;
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #999999;
text-align: left;
background: #f3f4f8;
border: 1px solid #edeff3;
box-sizing: border-box;
border-radius: 10px;
padding-left: 14px;
.icon {
width: 20px;
height: 20px;
margin-right: 12px;
}
.sep {
width: 1px;
height: 16px;
background: #c9cbd1;
margin: 0 14px;
}
}
.facs {
display: grid;
gap: 24px 0;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
width: 100%;
margin-top: 20px;
height: 75px;
padding: 0 14px;
.fac {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 65px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 11px;
line-height: 15px;
text-align: center;
color: #474a56;
align-items: center;
align-content: center;
img {
display: block;
width: 44px;
height: 44px;
margin-bottom: 6px;
}
}
}
}
.close {
position: absolute;
top: 16px;
right: 26px;
width: 24px;
height: 24px;
z-index: 10;
}
.dp1 {
position: relative;
margin: 0 10px 26px 10px;
height: 326px;
border-radius: 18px;
background: #ffffff;
box-shadow: 0px 12px 16px rgba(104, 110, 127, 0.08);
text-align: center;
padding: 14px;
overflow-x: hidden;
overflow-y: auto;
.row1,
.row2 {
text-align: left;
line-height: 48px;
height: 48px;
}
.row1 {
font-family: DINPro;
font-style: normal;
font-weight: bold;
font-size: 24px;
line-height: 48px;
height: 48px;
color: #323337;
padding: 0 16px;
&.has-border {
position: relative;
padding-top: 24px;
height: 72px;
&::after {
position: absolute;
content: "";
top: 12px;
left: 0;
right: 0;
border-top: 1px dashed #edeff3;
}
}
}
.row2 {
display: flex;
position: relative;
padding-left: 16px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 16px;
color: #323337;
background: #f9f9fb;
border-radius: 12px;
&.has-margin {
margin-top: 4px;
}
&.active {
background: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%);
color: #ffffff;
border-radius: 12px;
.meta {
color: #ffffff;
}
}
.meta {
margin-left: 16px;
font-family: DINPro;
font-style: normal;
font-weight: normal;
font-size: 17px;
color: #a1a5b3;
}
.go {
position: absolute;
top: 8px;
right: 8px;
width: 96px;
height: 32px;
background: #ffffff;
box-shadow: 0px 2px 6px rgba(93, 172, 249, 0.2);
border-radius: 8px;
font-family: PingFang SC;
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 32px;
text-align: center;
color: #437af7;
}
}
}
.sf {
width: 100%;
}

74
src/components/Floors/Floors.js

@ -0,0 +1,74 @@
import React, { useState, useEffect } from "react";
import "./Floors.scss";
import up from "./up.png";
import loc from "./loc.png";
export const upUrl = up;
const Floors = ({
clickable,
floors,
sceneIndex,
setSceneIndex,
onClickFloor,
popupHeight,
showNav,
isNavEnd,
end,
}) => {
const [list, setList] = useState([]);
useEffect(() => {
setList(
floors
.map(([modelUrl, floor], i) => ({
name: floor,
index: i,
modelUrl,
ref: React.createRef(),
}))
.filter(({ modelUrl }) => modelUrl !== null)
.reverse()
);
}, [floors]);
useEffect(() => {
const selected = list.find(({ index }) => sceneIndex === index);
if (selected && selected.ref.current)
selected.ref.current.scrollIntoView({
block: "center",
});
});
return !showNav ? (
<div
className={[
"floor-list",
showNav ? " show-nav" : "",
isNavEnd ? " nav-end" : "",
].join(" ")}
style={{ bottom: popupHeight + 66 + "px" }}
>
<ul>
{list.map(({ name, index, ref }) => (
<li
key={index}
ref={ref}
className={sceneIndex === index ? "active" : ""}
onClick={() => {
if (!clickable) return false;
setSceneIndex(index);
onClickFloor(index);
}}
>
{name}
</li>
))}
</ul>
</div>
) : (
<div className="floor-sub">
<img className="loc" src={loc}></img> <div className="sep"></div>
<div className="value">{floors[sceneIndex][1]}</div>
</div>
);
};
export default Floors;

88
src/components/Floors/Floors.scss

@ -0,0 +1,88 @@
.floor-list {
position: absolute;
top: -172px;
left: 10px;
height: 160px;
width: 40px;
padding: 4px 0;
text-align: center;
box-sizing: border-box;
z-index: 1000;
background: #ffffff;
box-shadow: 0px 8px 8px rgba(104, 110, 127, 0.04);
border-radius: 12px;
.show-nav {
top: -442px;
}
&.nav-end {
top: -212px;
}
ul {
overflow-x: hidden;
overflow-y: auto;
height: 152px;
}
li {
display: inline-block;
line-height: 38px;
font-family: DINPro;
font-style: normal;
font-weight: normal;
font-size: 14px;
text-align: center;
color: #333333;
width: 32px;
height: 38px;
&.active {
background: #53c79d;
border-radius: 6px;
color: #fff;
}
}
img {
position: absolute;
width: 8px;
height: 3px;
margin: auto;
left: 0;
right: 0;
&.top {
top: 7px;
}
&.bottom {
bottom: 7px;
transform-origin: center;
transform: rotate(180deg);
}
}
}
.floor-sub {
position: absolute;
bottom: calc(100vh - 42px);
left: 10px;
display: inline-flex;
z-index: 1000;
height: 32px;
padding: 0 12px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #323337;
background: #fff;
border-radius: 20px;
align-content: center;
justify-content: center;
align-items: center;
.loc {
width: 16px;
height: 16px;
}
.sep {
height: 10px;
width: 1px;
margin: 0 8px;
background: #c9cbd1;
}
}

BIN
src/components/Floors/loc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

BIN
src/components/Floors/up.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

195
src/components/HeadBar/HeadBar.js

@ -0,0 +1,195 @@
import React, { useState, useEffect } from "react";
import "./HeadBar.scss";
import backpng from "./back.png";
const HeadBar = ({
mall,
exit,
start,
end,
floors,
onSwap,
shop,
onSetStart,
onSetEnd,
isPick,
setIsPick,
blurMap,
onIsTypingChange,
}) => {
const [isTypingStart, setIsTypingStart] = useState(false);
const [isTypingEnd, setIsTypingEnd] = useState(false);
const isTyping = isTypingStart || isTypingEnd;
const [q, setQ] = useState(null);
const onlyShowStart = isTypingStart || (!start && isPick);
const onlyShowEnd = isTypingEnd || (!end && isPick);
const showStart = !onlyShowEnd;
const showEnd = !onlyShowStart;
const showBoth = !(isPick || isTyping);
const hasBoth = start && end;
const startValue =
q !== null ? q : isPick ? (shop ? shop.name : "") : start ? start.name : "";
const endValue =
q !== null ? q : isPick ? (shop ? shop.name : "") : end ? end.name : "";
const shops =
mall && mall.shopInfo
? mall.shopInfo.reduce((acc, { shopList }) => acc.concat(shopList), [])
: [];
const filteredShops = shops
.filter(({ yaxis }) => yaxis)
.filter(({ name }) => name.includes(q));
useEffect(() => {
onIsTypingChange(isTyping);
}, [isTyping]);
return (
<div
className={"head-bar" + (showBoth ? " double" : "")}
onClick={() => blurMap()}
>
<div
className="back"
onClick={() => {
if (isPick) setIsPick(false);
else if (isTypingStart) {
setQ(null);
setIsTypingStart(false);
} else if (isTypingEnd) {
setQ(null);
setIsTypingEnd(false);
} else exit();
}}
>
<img src={backpng}></img>
</div>
<div className="content">
{showBoth && <div className="dots"></div>}
{showStart && (
<div className={"row start" + (onlyShowStart ? " single" : "")}>
<div className="text">
<input
value={startValue}
onFocus={() => setIsTypingStart(true)}
onChange={(e) => setQ(e.target.value)}
readOnly={isPick}
disabled={isPick}
placeholder={isPick ? "请点击地图选择起点" : "请输入起点"}
></input>
{(start || (isPick && shop)) && (
<span className="label">
{floors[(start ? start : shop).floorOrder][1]}
</span>
)}
<div style={{ flex: 1 }}></div>
{start === null && isPick && (
<div
className="right"
onClick={() => {
setIsPick(false);
onSetStart(shop);
}}
>
开始导航
</div>
)}
{start === null && !isPick && !isTypingStart && (
<div
className="right"
onClick={() => {
setQ(null);
setIsPick(true);
setIsTypingStart(false);
setIsTypingEnd(false);
}}
>
地图选点
</div>
)}
</div>
</div>
)}
{showEnd && (
<div className={"row end" + (isPick ? " single" : "")}>
<div className={"text " + (isTypingEnd ? "" : "has-border")}>
<input
value={endValue}
onFocus={() => setIsTypingEnd(true)}
onChange={(e) => setQ(e.target.value)}
readOnly={isPick}
disabled={isPick}
placeholder={isPick ? "请点击地图选择终点" : "请输入终点"}
></input>
{((isPick && shop) || end) && (
<span className="label">
{floors[(end ? end : shop).floorOrder][1]}
</span>
)}
<div style={{ flex: 1 }}></div>
{end === null && isPick && (
<div
className={shop ? "right green" : "right grey"}
onClick={() => {
setIsPick(false);
onSetEnd(shop);
}}
>
开始导航
</div>
)}
{end === null && !isPick && !isTypingEnd && (
<div
className="right"
onClick={() => {
setQ(null);
setIsPick(true);
setIsTypingEnd(false);
}}
>
地图选点
</div>
)}
</div>
</div>
)}
{showBoth && <div className="switch" onClick={() => onSwap()}></div>}
</div>
{showBoth && !hasBoth && (
<div className={"banner " + (start === null ? "start" : "end")}>
请在顶部选择{!start ? "起" : "终"}
</div>
)}
{isTyping && q && (
<div className="shop-list-wrapper">
<div className="search-list">
{filteredShops.map((shop) => (
<div
className="search-item"
key={shop.houseNum}
onClick={() => {
if (isTypingStart) {
onSetStart(shop);
setQ(null);
setIsTypingStart(false);
}
if (isTypingEnd) {
onSetEnd(shop);
setQ(null);
setIsTypingEnd(false);
}
}}
>
<div className="floorname">{shop.floorName}</div>
{shop.name}
<div className="format">{shop.shopFormat}</div>
</div>
))}
</div>
</div>
)}
</div>
);
};
export default HeadBar;

310
src/components/HeadBar/HeadBar.scss

@ -0,0 +1,310 @@
.head-bar {
display: flex;
box-sizing: border-box;
width: calc(100vw - 20px);
margin-left: 10px;
margin-top: 10px;
font-size: 16px;
line-height: 44px;
position: relative;
z-index: 1;
height: 64px;
position: relative;
border-radius: 12px;
align-items: center;
pointer-events: auto;
&.double {
height: 114px;
.back {
height: 114px;
}
.content {
height: 114px;
}
}
.search-list {
display: inline-flex;
flex-direction: column;
width: calc(100vw - 20px);
margin: 10px;
margin-bottom: 0;
.search-item {
display: flex;
position: relative;
background: #ffffff;
flex: 0 0 56px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.08);
border-radius: 8px;
align-items: center;
.floorname {
width: 46px;
font-family: DINPro;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 22px;
text-align: center;
color: #333333;
}
.format {
position: absolute;
right: 17px;
font-family: PingFang SC;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 18px;
color: #666666;
}
}
.search-item + .search-item {
margin-top: 16px;
}
}
.back {
display: flex;
justify-content: center;
align-items: center;
position: relative;
background: #ffffff;
flex: 0 0 50px;
margin-right: 8px;
height: 64px;
border-radius: 12px;
img {
width: 32px;
height: 32px;
}
}
.content {
display: flex;
flex-direction: column;
justify-content: center;
background: #ffffff;
height: 64px;
z-index: 2;
flex: 1;
position: relative;
border-radius: 12px;
.dots {
position: absolute;
top: 0;
bottom: 0;
left: 23px;
width: 2px;
height: 2px;
border-radius: 50%;
background: #c9cbd1;
margin: auto 0;
&::before {
content: "";
display: block;
position: absolute;
top: -6px;
width: 2px;
height: 2px;
border-radius: 50%;
background: #c9cbd1;
left: 0;
right: 0;
margin: 0 auto;
}
&::after {
content: "";
display: block;
position: absolute;
bottom: -6px;
width: 2px;
height: 2px;
border-radius: 50%;
background: #c9cbd1;
left: 0;
right: 0;
margin: 0 auto;
}
}
.row {
position: relative;
line-height: 57px;
height: 57px;
padding-left: 48px;
padding-right: 49px;
&.start {
background: 16px center/16px 16px no-repeat url("./start.png");
}
&.end {
background: 16px center/16px 16px no-repeat url("./end.png");
}
&.single {
padding-right: 10px;
}
.text {
display: flex;
align-items: center;
input {
border: none;
background: transparent;
display: inline-block;
line-height: 57px;
outline: none;
width: 100%;
font-family: PingFang SC;
font-style: normal;
font-weight: 600;
font-size: 18px;
color: #323337;
}
::placeholder {
font-size: 16px;
font-weight: 400;
color: #c9cbd1;
}
&.has-border {
border-top: 1px solid rgba(238, 238, 238, 0.7);
}
.label {
display: inline-block;
flex: 0 0 20px;
text-align: center;
margin-left: 8px;
font-style: normal;
font-weight: bold;
font-size: 12px;
color: #b3aea7;
}
.right {
background: #f3f4f8;
border: 1px solid #edeff3;
box-sizing: border-box;
border-radius: 8px;
font-family: PingFang SC;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 33px;
color: #53c79d;
flex: 0 0 80px;
text-align: center;
display: inline-block;
white-space: nowrap;
margin-left: 8px;
}
}
}
.switch {
position: absolute;
width: 49px;
height: 32px;
background-image: url("./switch.png");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
top: 0;
bottom: 0;
right: 0;
margin: auto 0;
}
}
.banner {
position: absolute;
display: flex;
background: rgba(0, 0, 0, 0.6);
width: 237px;
line-height: 40px;
height: 40px;
text-align: center;
bottom: -48px;
left: 0;
right: 0;
margin: auto;
font-family: PingFang SC;
font-style: normal;
font-weight: normal;
font-size: 14px;
color: #ffffff;
border-radius: 20px;
justify-content: center;
align-items: center;
&.start::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
margin-right: 8px;
vertical-align: middle;
background-image: url("./start.png");
background-size: contain;
}
&.end::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
margin-right: 8px;
vertical-align: middle;
background-image: url("./end.png");
background-size: contain;
}
&::after {
content: "";
display: inline-block;
margin-left: 8px;
width: 16px;
height: 16px;
vertical-align: middle;
background-image: url("./arrow.png");
background-size: contain;
}
}
.shop-list-wrapper {
position: absolute;
top: 64px;
left: -10px;
width: 100vw;
height: calc(100vh - 74px);
padding-bottom: 10px;
overflow-x: hidden;
overflow-y: auto;
}
.search-type {
position: absolute;
top: 100px;
width: 100vw;
height: 40px;
line-height: 40px;
background: #fff;
display: flex;
color: #404040;
left: 0;
font-size: 12px;
.el {
flex: 1 1 33%;
text-align: center;
span {
position: relative;
display: inline-block;
img {
width: 18px;
height: 18px;
vertical-align: middle;
margin-right: 8px;
}
&.active {
color: #0074ed;
}
&.active {
&::after {
content: "";
position: absolute;
width: 100%;
height: 4px;
background: #0074ed;
bottom: 0;
left: 0;
}
}
}
}
}
}

BIN
src/components/HeadBar/arrow.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

BIN
src/components/HeadBar/back.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

BIN
src/components/HeadBar/end.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/components/HeadBar/start.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/components/HeadBar/switch.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

260
src/components/Malls/Malls.js

@ -0,0 +1,260 @@
import React, { useState, useEffect, useContext } from "react";
import { ListView } from "antd-mobile";
import Modal from "react-modal";
import "./Malls.scss";
import search from "./search.png";
import pos from "./pos.png";
import close_white from "./close_white.png";
import up from "./up.png";
import {
Mall,
cityMallsGetter,
CityMall,
} from "../../js/helpers/data-helper.js";
import { MallCode } from "../../pages/Index/Index";
const getSectionData = (dataBlob, sectionID) => dataBlob[sectionID];
const getRowData = (dataBlob, sectionID, rowID) => dataBlob[rowID];
function genData(ds, cityMalls) {
const dataBlob = {};
const sectionIDs = [];
const rowIDs = [];
const groupByIndex = (list) =>
list.reduce((acc, nxt) => {
acc[nxt.index] = acc[nxt.index] ? [...acc[nxt.index], nxt] : [nxt];
return acc;
}, {});
const data = groupByIndex(cityMalls);
Object.keys(data).forEach((item, index) => {
sectionIDs.push(item);
dataBlob[item] = item;
rowIDs[index] = [];
data[item].forEach((city) => {
rowIDs[index].push(city.name);
dataBlob[city.name] = city;
});
});
return ds.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs);
}
const Malls = ({ isOpen, onRequestClose, setMallCode }) => {
const [q, setQ] = useState("");
const [showList, setShowList] = useState(false);
const [cities, setCities] = useState([]);
const [currentMall, setCurrentMall] = useState(null);
const [currentCity, setCurrentCity] = useState(null);
const [currentMalls, setCurrentMalls] = useState([]);
const [isMallExpand, setIsMallExpand] = useState(false);
const [dataSource, setDataSource] = useState(
new ListView.DataSource({
getRowData,
getSectionHeaderData: getSectionData,
rowHasChanged: (row1, row2) => row1 !== row2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
})
);
const mallCode = useContext(MallCode);
const setDefaultCityMall = (list) => {
setCurrentCity(list[0]);
setCurrentMall(list[0].malls[0]);
setCurrentMalls(list[0].malls);
};
useEffect(() => {
document.title = "城市商场选择";
cityMallsGetter().then((cityMalls) => {
setCities(cityMalls);
setDataSource(genData(dataSource, cityMalls));
if (cityMalls.length) {
const city = cityMalls.find(({ malls }) =>
malls.find(({ code }) => mallCode === code)
);
if (city) {
setCurrentCity(city);
const mall = city.malls.find(({ code }) => mallCode === code);
if (mall) setCurrentMall(mall);
setCurrentMalls(city.malls);
} else setDefaultCityMall(cityMalls);
}
});
}, [mallCode]);
return (
<Modal
isOpen={isOpen}
style={{ overlay: { zIndex: 10000, background: "#fff" } }}
ariaHideApp={false}
className="malls"
onRequestClose={() => onRequestClose()}
>
<div className="malls" onClick={() => setIsMallExpand(false)}>
<div className="input-wrapper">
<input
value={q}
className="input"
placeholder="输入城市进行搜索"
onChange={(e) => setQ(e.target.value)}
onFocus={() => setShowList(true)}
onBlur={() => !q && setShowList(false)}
></input>
</div>
<img className="search-icon" src={search}></img>
{q && (
<img className="close" src={close_white} onClick={() => setQ("")} />
)}
{showList && (
<div className="list">
{cities
.filter(({ name }) => name.includes(q))
.map((city) => (
<div
className="item"
key={city.name}
onClick={() => {
setCurrentCity(city);
setCurrentMall(city.malls[0]);
setCurrentMalls(city.malls);
setShowList(false);
}}
>
{city.name}
</div>
))}
</div>
)}
{!showList && (
<div className="main">
<div className="r1">
<div className="left">
<img className="pos" src={pos}></img>
{currentCity && <span>{currentCity.name}</span>}
&nbsp;&nbsp;
{currentMall && <span>{currentMall.name}</span>}
<img className="up" src={up}></img>
</div>
<span className="right">当前定位城市</span>
</div>
<div className="r2">切换商场</div>
<div
className="malls-wrapper"
onClick={() => {
setIsMallExpand(false);
}}
>
<div
className={"malls1" + (isMallExpand ? " expand" : "")}
onClick={(e) => {
e.stopPropagation();
}}
>
{currentMalls.map((mall) => (
<span
key={mall.code}
className="tag"
onClick={() => {
console.log("setMallCode", mall.code);
setCurrentMall(mall);
setIsMallExpand(false);
setMallCode(mall.code);
onRequestClose();
// Taro.reLaunch({
// url: `/pages/index/index?mallId=${mall.id}`
// });
}}
>
{mall.name}
</span>
))}
{!isMallExpand && (
<div
className="more"
onClick={(e) => {
e.stopPropagation();
setIsMallExpand(true);
}}
>
更多
</div>
)}
{isMallExpand && (
<img
className="fold"
src={up}
onClick={(e) => {
e.stopPropagation();
setIsMallExpand(false);
}}
></img>
)}
</div>
<ListView.IndexedList
dataSource={dataSource}
className="am-list sticky-list"
useBodyScroll
renderSectionHeader={(sectionData) => (
<div>
<div
className="sticky"
style={{
zIndex: 3,
}}
>
{sectionData}
</div>
</div>
)}
renderHeader={() => (
<div>
<div className="meta">切换城市</div>
<div className="city-buttons">
{cities.map((city) => (
<div
key={city.name}
className="city-button"
onClick={() => {
setCurrentCity(city);
setCurrentMall(city.malls[0]);
setCurrentMalls(city.malls);
}}
>
{city.name}
</div>
))}
</div>
</div>
)}
renderRow={(rowData) => (
<div
key={rowData.name}
className="city-button"
onClick={() => {
setCurrentCity(rowData);
setCurrentMalls(rowData.malls);
setCurrentMall(rowData.malls[0]);
}}
>
{rowData.name}
</div>
)}
quickSearchBarStyle={{
position: "absolute",
top: 25,
}}
delayTime={10}
delayActivityIndicator={
<div style={{ padding: 25, textAlign: "center" }}>
rendering...
</div>
}
/>
</div>
</div>
)}
</div>
</Modal>
);
};
export default Malls;

242
src/components/Malls/Malls.scss

@ -0,0 +1,242 @@
/*postcss-pxtransform disable*/
.malls {
position: relative;
width: 100vw;
height: 100vh;
box-sizing: border-box;
color: #5a5a5a;
font-family: SourceHanSansCN-Medium, SourceHanSansCN;
background: #fff;
box-sizing: border-box;
.search-icon {
position: absolute;
width: 13px;
height: 13px;
top: 21px;
left: 26px;
z-index: 1;
}
.close {
position: absolute;
border-radius: 50%;
background: #8d8d8d;
width: 20px;
height: 20px;
top: 17px;
right: 19px;
}
.input-wrapper {
padding: 12px 14px 0 14px;
.input {
padding: 5px 30px;
background: #ececec;
border-radius: 100px;
height: 30px;
line-height: 20px;
font-size: 11px;
box-sizing: border-box;
border: none;
width: 100%;
outline: none;
}
::placeholder {
color: #a9a9a9;
}
}
.list {
padding: 0 50px 0 15px;
line-height: 36px;
height: calc(100vh - 42px);
font-size: 12px;
overflow: scroll;
.item {
border-bottom: 1px solid #f4f4f4;
}
}
.main {
.r1 {
padding: 18px 15px;
line-height: 30px;
.left {
position: relative;
display: inline-block;
font-size: 14px;
background: rgba(244, 244, 244, 1);
border-radius: 8px;
border: 1px solid rgba(236, 236, 236, 1);
padding: 0 30px;
.pos {
position: absolute;
width: 14px;
height: 16px;
top: 7px;
left: 9px;
}
.up {
position: absolute;
right: 7.5px;
top: 12.5px;
width: 8px;
height: 5px;
transform-origin: center;
transform: rotate(90deg);
}
Text + Text {
margin-left: 22px;
}
}
.right {
margin-left: 15px;
font-size: 11px;
font-weight: 400;
color: rgba(169, 169, 169, 1);
}
}
.r2 {
padding-left: 15px;
color: #a9a9a9;
font-size: 11px;
line-height: 11px;
font-weight: 400;
}
.malls-wrapper {
height: calc(100vh - 119px);
.malls1 {
position: relative;
margin-top: 11px;
padding-left: 15px;
padding-right: 60px;
height: 32px;
overflow: hidden;
background: #fff;
z-index: 10;
.more {
position: absolute;
line-height: 30px;
color: #a9a9a9;
right: 14px;
font-size: 11px;
top: 0;
}
&.expand {
box-shadow: 0px 15px 12px 0px rgba(0, 0, 0, 0.22);
padding-right: 15px;
padding-bottom: 17px;
overflow: auto;
height: auto;
}
.tag {
color: #878787;
font-size: 12px;
padding: 0 11px;
border-radius: 15px;
border: 1px solid rgba(236, 236, 236, 1);
line-height: 30px;
display: inline-block;
margin-bottom: 12px;
margin-right: 10px;
}
.fold {
position: absolute;
bottom: 0;
width: 8px;
height: 5px;
padding: 8px 6.5px;
left: 0;
right: 0;
margin: auto;
}
.fold::after {
content: "";
position: absolute;
left: -5px;
right: -5px;
top: -5px;
bottom: -5px;
}
}
}
.am-indexed-list-container {
position: absolute;
top: 178px;
border-top: 1px solid #f4f4f4;
padding: 0 50px 0 15px;
height: calc(100vh - 178px);
width: 100vw;
box-sizing: border-box;
.am-indexed-list-quick-search-bar :first-child {
display: none !important;
}
.am-indexed-list-quick-search-bar {
top: 204px;
font-size: 11px;
transform: none;
right: 15px;
z-index: 1;
text-align: center;
li {
display: block;
padding: 0;
font-size: 11px;
color: #a9a9a9;
line-height: 15px;
height: 15px;
width: 15px;
}
li:active {
background: #0091ff;
color: white;
border-radius: 50%;
}
}
.at-list::after {
content: none;
}
.am-indexed-list-section-body {
line-height: 36px;
color: #878787;
font-size: 12px;
font-weight: 400;
border-bottom: 1px solid #f4f4f4;
background: none;
padding: 0;
}
.am-indexed-list-section-header {
font-size: 12px;
font-weight: 400;
border-bottom: 1px solid #f4f4f4;
padding: 0;
}
.am-list-body {
color: #5a5a5a;
line-height: 36px;
}
}
.meta {
font-size: 11px;
font-weight: 400;
margin-top: 7px;
padding: 11px 0;
color: #a9a9a9;
}
.city-buttons {
display: grid;
grid-column-gap: 10px;
grid-row-gap: 11px;
grid-auto-rows: 30px;
grid-auto-columns: 70px;
grid-template-columns: 1fr 1fr 1fr 1fr;
.city-button {
display: inline-block;
color: #878787;
font-size: 12px;
line-height: 30px;
background: rgba(244, 244, 244, 1);
border-radius: 4px;
text-align: center;
}
}
}
}

BIN
src/components/Malls/close_white.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

BIN
src/components/Malls/pos.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 B

BIN
src/components/Malls/search.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

BIN
src/components/Malls/up.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

BIN
src/components/More/2d.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

BIN
src/components/More/3d.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

53
src/components/More/More.js

@ -0,0 +1,53 @@
import React from "react";
import { searchTypes, audioOptions } from "../Options/Options";
import "./More.scss";
import flat from "./2d.png";
import thrD from "./3d.png";
const displayModes = [
{
id: 0,
name: "2D",
bg: flat,
},
{
id: 1,
name: "3D",
bg: thrD,
},
];
const More = ({
showHeadBar,
displayMode,
searchType,
onClickDisplayMode,
onClickSearchType,
}) => {
return (
<div className={"more " + (showHeadBar ? "has-header-top" : "")}>
<div className="types">
{searchTypes.map(({ id, name, bg, bgb }) => (
<div
key={id}
className={"btn " + (searchType === id ? "active" : "")}
onClick={() => {
onClickSearchType(id);
}}
>
<img src={searchType === id ? bgb : bg} />
<div>{name}</div>
</div>
))}
</div>
<div
className="btn big"
onClick={() => onClickDisplayMode(displayMode == 0 ? 1 : 0)}
>
<img src={displayMode == 0 ? flat : thrD}></img>
{displayMode == 0 ? "2D" : "3D"}
</div>
</div>
);
};
export default More;

53
src/components/More/More.scss

@ -0,0 +1,53 @@
.more {
position: absolute;
top: 10px;
right: 10px;
display: inline-flex;
flex-direction: column;
pointer-events: auto;
&.has-header-top {
top: 138px;
}
.types {
display: flex;
flex-direction: column;
width: 40px;
height: 160px;
background: #ffffff;
box-shadow: 0px 8px 8px rgba(104, 110, 127, 0.04);
border-radius: 12px;
justify-content: space-evenly;
align-items: center;
}
.btn {
display: inline-flex;
flex-direction: column;
width: 32px;
height: 48px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 10px;
line-height: 14px;
text-align: center;
color: #474a56;
background: #ffffff;
border-radius: 12px;
justify-content: center;
align-items: center;
img {
width: 24px;
height: 24px;
}
&.active {
background: #53c79d;
color: #fff;
}
&.big {
width: 40px;
height: 56px;
margin-top: 8px;
box-shadow: 0px 8px 8px rgba(104, 110, 127, 0.04);
}
}
}

179
src/components/NavBottom/NavBottom.js

@ -0,0 +1,179 @@
import React, { useState, useEffect } from "react";
import "./NavBottom.scss";
import followDirection from "./followDirection.png";
import notFollowDirection from "./notFollowDirection.png";
import play from "./play.png";
import pause from "./pause.png";
import closeButton from "./close.png";
import left from "./left.png";
import right from "./right.png";
import straight from "./straight.png";
import back from "./back.png";
import bubble from "./bubble.png";
import arpng from "./ar.png";
import nav from "./nav.png";
import navActive from "./nav_active.png";
import des from "./des.png";
import desActive from "./des_active.png";
const getImgByRouteSearchText = (txt) =>
txt.includes("左前")
? left
: txt.includes("右前")
? right
: txt.includes("左后")
? left
: txt.includes("右后")
? right
: txt.includes("左转")
? left
: txt.includes("右转")
? right
: txt.includes("上")
? straight
: txt.includes("下")
? back
: straight;
const NavBottom = ({
routeSearchAnimationType,
switchType,
onExit,
end,
meters,
minutes,
paused,
puaseOrResume,
hidePause,
hideSwitch,
routeSearchText,
percent,
handleAR,
}) => {
const [showText, setShowText] = useState(false);
const [showNav, setShowNav] = useState(true);
useEffect(() => {
setShowText(true);
let timeout = setTimeout(() => {
clearTimeout(timeout);
setShowText(false);
}, 3000);
return () => {
clearTimeout(timeout);
setShowText(false);
};
}, [routeSearchAnimationType]);
let progressNum = parseInt(percent * 100);
if (isNaN(progressNum)) return;
if (progressNum < 0) progressNum = 0;
if (progressNum > 100) progressNum = 100;
const progressText = progressNum + "%";
return (
<div className={"nav-bottom" + (hidePause ? " hide-pause" : "")}>
<div className="tabs">
<div
className={["tab", showNav ? "active" : ""].join(" ")}
onClick={() => setShowNav(true)}
>
<img src={showNav ? navActive : nav}></img>
导航
</div>
<div
className={["tab", !showNav ? "active" : ""].join(" ")}
onClick={() => setShowNav(false)}
>
<img src={showNav ? des : desActive}></img>
目的地
</div>
</div>
{showText && !hideSwitch && (
<div className="switch">
{routeSearchAnimationType === 1
? "已切转地图视角"
: "已切转方向标视角"}
</div>
)}
{!hideSwitch && (
<img
className="switch-pic"
src={
routeSearchAnimationType === 1
? notFollowDirection
: followDirection
}
onClick={() => {
switchType();
}}
/>
)}
<div
className="ar-btn"
onClick={() => {
!paused && puaseOrResume();
handleAR(end);
}}
>
<img src={arpng} />
AR导航
</div>
{showNav ? (
<div className="nav">
<img
className="dir"
src={getImgByRouteSearchText(routeSearchText)}
alt={routeSearchText}
/>
<div className="t1">{routeSearchText}</div>
<div className="t2">
剩余<span className="val">{meters}</span> &nbsp;{" "}
<span className="val">{minutes}</span>
</div>
<img
alt="关闭"
className="close-icon"
src={closeButton}
onClick={() => onExit()}
></img>
<div className="progress"></div>
<div
className="bar"
style={{ width: `calc((100vw - 56px) * ${progressNum / 100})` }}
></div>
<div
className="bubble"
style={{
left: `calc(((100vw - 56px) * ${progressNum / 100}) + 20px)`,
}}
>
{progressText}
</div>
{!hidePause && (
<img
src={paused ? play : pause}
className="pause"
onClick={() => {
puaseOrResume();
}}
/>
)}
</div>
) : (
<div className="destination">
<img
alt="关闭"
className="close-icon"
src={closeButton}
onClick={() => onExit()}
></img>
<div className="name">{end.name}</div>
<div className="meta">
<div> {end.shopFormat}</div> <div>{end.floorName}</div>
</div>
</div>
)}
</div>
);
};
export default NavBottom;

333
src/components/NavBottom/NavBottom.scss

@ -0,0 +1,333 @@
.nav-bottom {
position: absolute;
width: calc(100vw - 20px);
left: 8px;
bottom: 26px;
border-radius: 18px;
height: 200px;
padding: 14px 0 0 14px;
background: #fff;
z-index: 30;
.tabs {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
bottom: 14px;
left: 14px;
right: 14px;
height: 56px;
background: #f3f4f8;
border-radius: 10px;
.tab {
display: flex;
width: calc((100vw - 20px - 28px - 12px) / 2);
height: 48px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 22px;
color: #323337;
justify-content: center;
align-items: center;
img {
width: 20px;
height: 20px;
margin-right: 8px;
}
&.active {
background: #53c79d;
box-shadow: 0px 6px 12px rgba(93, 249, 192, 0.2);
border-radius: 10px;
color: #fff;
}
}
}
.nav {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 82px;
.dir {
position: absolute;
top: 14px;
left: 14px;
width: 80px;
height: 80px;
}
.t1 {
position: absolute;
top: 24px;
left: 108px;
font-family: PingFang SC;
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 34px;
color: #323337;
}
.t2 {
position: absolute;
display: flex;
top: 65px;
left: 108px;
right: 10px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #a1a5b3;
align-items: center;
.val {
font-family: DINPro;
font-style: normal;
font-weight: normal;
font-size: 18px;
line-height: 23px;
color: #7a7e8d;
margin: 0 4px;
}
}
.exit {
position: absolute;
top: 20px;
right: 12px;
width: 48px;
height: 48px;
text-align: center;
border: 1px solid #e2e1df;
box-sizing: border-box;
border-radius: 12px;
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 48px;
color: #6a6665;
}
.progress {
position: absolute;
left: 20px;
right: 20px;
top: 108px;
height: 4px;
background: #edeff3;
border-radius: 5px;
z-index: 1;
}
.bar {
position: absolute;
left: 20px;
right: 20px;
top: 108px;
height: 4px;
background: #53c79d;
border-radius: 5px;
z-index: 2;
}
.bubble {
display: flex;
position: absolute;
z-index: 3;
width: 33px;
height: 14px;
top: 103.5px;
background: #ffffff;
text-align: center;
border: 2px solid #53c79d;
border-radius: 12px;
font-family: DINPro;
font-style: normal;
font-weight: bold;
font-size: 10px;
color: #53c79d;
justify-content: center;
align-items: center;
transform: translate(-50%, 0);
}
.close-icon {
position: absolute;
top: 16px;
right: 16px;
width: 24px;
height: 24px;
z-index: 10;
}
.avatar {
width: 64px;
height: 64px;
background: rgba(255, 255, 255, 1);
filter: drop-shadow(0px 0px 10px rgba(220, 183, 123, 0.3));
border-radius: 8px;
margin-right: 13px;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
.popupname {
font-style: normal;
font-weight: 900;
font-size: 24px;
line-height: 34px;
margin-top: 4px;
letter-spacing: 1px;
color: #b38f65;
margin-bottom: 2px;
}
.popupdesc {
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #6a6665;
}
.popuptag {
border: 1px solid #e1e1e1;
display: inline-block;
line-height: 18px;
font-size: 11px;
padding: 0 8px;
border-radius: 10px;
color: #a9a9a9;
margin-right: 10px;
}
}
.destination {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 82px;
&::after {
content: "";
display: block;
position: absolute;
left: 20px;
right: 20px;
top: 110px;
border-bottom: 1px dashed #ebedf0;
}
.close-icon {
position: absolute;
top: 16px;
right: 16px;
width: 24px;
height: 24px;
z-index: 10;
}
.name {
position: absolute;
top: 24px;
left: 20px;
font-family: PingFang SC;
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 34px;
color: #323337;
right: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.meta {
display: flex;
position: absolute;
top: 68px;
left: 20px;
right: 20px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #a1a5b3;
align-items: center;
justify-content: space-between;
}
}
.switch {
position: absolute;
top: -52px;
right: 0;
height: 40px;
line-height: 40px;
color: #fff;
border-radius: 85px;
font-size: 12px;
font-family: SourceHanSansCN-Regular, SourceHanSansCN;
font-weight: 400;
background: rgba(0, 0, 0, 0.2);
padding-left: 12px;
padding-right: 42px;
animation: 3s ease-out fadeout;
@keyframes fadeout {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
}
.switch-pic {
position: absolute;
top: -52px;
right: 0;
width: 40px;
height: 40px;
vertical-align: middle;
box-shadow: 0px 8px 8px rgba(104, 110, 127, 0.04);
}
.ar-btn {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
position: absolute;
width: 111px;
height: 40px;
left: 0;
top: -52px;
background: #ffffff;
border-radius: 20px;
font-family: PingFang SC;
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 40px;
color: #474a56;
img {
width: 24px;
height: 24px;
margin-right: 8px;
}
}
.br {
margin-bottom: 8px;
}
.gold {
font-weight: 400;
color: #b48764;
}
.big {
font-size: 20px;
font-family: DINPro-Bold, DINPro;
font-weight: bold;
margin: 0 5px;
}
.pause {
position: absolute;
width: 40px;
height: 40px;
top: -100px;
right: 0;
box-shadow: 0px 8px 8px rgba(104, 110, 127, 0.04);
}
}

BIN
src/components/NavBottom/ar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/components/NavBottom/back.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
src/components/NavBottom/bubble.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

BIN
src/components/NavBottom/close.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

BIN
src/components/NavBottom/des.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

BIN
src/components/NavBottom/des_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

BIN
src/components/NavBottom/followDirection.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
src/components/NavBottom/left.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
src/components/NavBottom/nav.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

BIN
src/components/NavBottom/nav_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

BIN
src/components/NavBottom/notFollowDirection.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/components/NavBottom/pause.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

BIN
src/components/NavBottom/play.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/components/NavBottom/right.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
src/components/NavBottom/straight.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
src/components/Options/2d.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/components/Options/3d.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

137
src/components/Options/Options.js

@ -0,0 +1,137 @@
import React from "react";
import Modal from "react-modal";
import flat from "./2d.png";
import thrD from "./3d.png";
import on from "./on.png";
import mute from "./mute.png";
import ft from "./ft.png";
import ftb from "./ftb.png";
import ztb from "./ztb.png";
import zt from "./zt.png";
import shortest from "./shortest.png";
import shortestb from "./shortestb.png";
import ft1 from "./ft1.png";
import ftb1 from "./ftb1.png";
import ztb1 from "./ztb1.png";
import zt1 from "./zt1.png";
import shortest1 from "./shortest1.png";
import shortestb1 from "./shortestb1.png";
import "antd-mobile/lib/switch/style/css";
import "./Options.scss";
export const displayModes = [
{
id: 0,
name: "2D",
bg: flat,
},
{
id: 1,
name: "3D",
bg: thrD,
},
];
export const searchTypes = [
{
id: 0,
name: "最佳",
bg: shortest,
bgb: shortestb,
bg1: shortest1,
bgb1: shortestb1,
},
{
id: 1,
name: "扶梯",
bg: ft,
bgb: ftb,
bg1: ft1,
bgb1: ftb1,
},
{
id: 2,
name: "直梯",
bg: zt,
bgb: ztb,
bg1: zt1,
bgb1: ztb1,
},
];
export const audioOptions = [
{
id: 0,
name: "语音",
bg: on,
},
{
id: 1,
name: "语音",
bg: mute,
},
];
const Options = ({
show,
hide,
onClickDisplayMode,
onClickSearchType,
onClickPlayAudioMode,
showHeadBar,
}) => (
<Modal
isOpen={show}
style={{
overlay: {
zIndex: 10000,
background: "rgba(0,0,0,0)",
},
}}
ariaHideApp={false}
className={"modal" + (showHeadBar ? " has-header-bar" : "")}
onRequestClose={() => hide()}
>
<div className="row">
{displayModes.map(({ id, name, bg }) => (
<div
key={id}
className={"col"}
onClick={() => {
onClickDisplayMode(id);
hide();
}}
>
<div
className="up"
style={{
background: `center/22px 22px no-repeat url(${bg})`,
}}
></div>
<div className="down">{name}</div>
</div>
))}
<div className="col"></div>
</div>
<div className="row">
{searchTypes.map(({ id, name, bg }) => (
<div
key={id}
className="col"
onClick={() => {
onClickSearchType(id);
hide();
}}
>
<div
className="up"
style={{
background: `center/22px 22px no-repeat url(${bg})`,
}}
></div>
<div className="down">{name}</div>
</div>
))}
</div>
</Modal>
);
export default Options;

106
src/components/Options/Options.scss

@ -0,0 +1,106 @@
.modal {
position: absolute;
width: 168px;
height: 136px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.08);
outline: none;
right: 59px;
top: 12px;
overflow: hidden;
padding-top: 16px;
&.has-header-bar {
top: 112px;
}
.title {
background: #f4f4f4;
padding: 0 19px;
color: #878787;
font-family: SourceHanSansCN-Regular, SourceHanSansCN;
font-weight: 400;
line-height: 40px;
font-size: 11px;
}
.row {
display: flex;
flex: 1;
flex-direction: row;
margin-bottom: 18px;
.col {
flex: 1;
text-align: center;
.up {
width: 22px;
height: 22px;
margin: auto;
}
.down {
font-size: 12px;
font-family: SourceHanSansCN, SourceHanSansCN-Regular;
font-weight: 400;
text-align: center;
color: #696969;
line-height: 20px;
}
&.active {
.down {
color: #0074ed;
}
}
}
}
.bottom {
position: absolute;
bottom: 0;
width: 100%;
line-height: 52px;
padding: 0 20px;
background: #f4f4f4;
color: #404040;
font-size: 12px;
font-family: SourceHanSansCN-Regular, SourceHanSansCN;
font-weight: 400;
.am-switch {
position: absolute;
top: 16px;
right: 20px;
input[type="checkbox"]:checked + .checkbox:after {
transform: translateX(22px);
}
.checkbox {
width: 42px;
height: 20px;
border-radius: 20px;
&:before {
content: " ";
position: absolute;
left: 1.5px;
top: 1.5px;
width: 39px;
height: 17px;
border-radius: 17px;
box-sizing: border-box;
background: #fff;
z-index: 1;
transition: all 200ms;
transform: scale(1);
}
&:after {
content: " ";
height: 17px;
width: 17px;
border-radius: 17px;
background: #fff;
position: absolute;
z-index: 2;
left: 1.5px;
top: 1.5px;
transform: translateX(0);
transition: all 200ms;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.21);
}
}
}
}
}

BIN
src/components/Options/ft.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

BIN
src/components/Options/ft1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/components/Options/ftb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

BIN
src/components/Options/ftb1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/components/Options/mute.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/components/Options/on.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/components/Options/shortest.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/components/Options/shortest1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/components/Options/shortestb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
src/components/Options/shortestb1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/components/Options/zt.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

BIN
src/components/Options/zt1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

BIN
src/components/Options/ztb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

BIN
src/components/Options/ztb1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

50
src/components/Popup/Popup.js

@ -0,0 +1,50 @@
import React from "react";
import desc from "./desc.png";
import closeButton from "./close.png";
import "./Popup.scss";
import navpng from "./nav.png";
import arpng from "./ar.png";
const Popup = ({
showPopup,
defaultPopup,
floors,
closePopup,
shop,
showDetail,
setEnd,
handleAR,
}) => (
<div className={["popup-wrapper", showPopup ? "" : "noshow"].join(" ")}>
{floors}
{defaultPopup}
{showPopup && (
<div className="popup">
<img
alt="关闭"
className="close-icon"
src={closeButton}
onClick={() => closePopup()}
></img>
<div className="r1">
<div style={{ flex: 1 }}>
<div className="popupname">{shop.name}</div>
<div className="popupdesc">
<span> {shop.shopFormat ? shop.shopFormat : " "}</span>
<span> {shop.floorName}</span>
</div>
</div>
</div>
<div className="tabs">
<div className="tab" onClick={() => setEnd()}>
<img src={navpng} /> 导航动画
</div>
<div className="tab" onClick={() => handleAR(shop)}>
<img src={arpng} /> AR实时导航
</div>
</div>
</div>
)}
</div>
);
export default Popup;

185
src/components/Popup/Popup.scss

@ -0,0 +1,185 @@
.popup-wrapper {
position: absolute;
width: 100vw;
left: 0;
bottom: 0;
z-index: 10000;
box-sizing: border-box;
&.noshow {
padding: 0;
}
.popup {
position: relative;
width: calc(100vw - 20px);
margin-left: 10px;
margin-bottom: 26px;
background: #ffffff;
box-shadow: 0px 12px 16px rgba(104, 110, 127, 0.08);
border-radius: 18px;
height: 200px;
background: #fff;
.close-icon {
position: absolute;
top: 16px;
right: 16px;
width: 24px;
height: 24px;
z-index: 10;
}
.r1 {
display: flex;
width: 100%;
padding: 14px 20px 0 14px;
height: 110px;
.avatar {
width: 80px;
height: 80px;
background: #ffffff;
box-shadow: 0px 8px 16px rgba(104, 110, 127, 0.08);
border-radius: 6px;
margin-right: 22px;
background-size: 64px 64px;
background-position: center;
background-repeat: no-repeat;
}
.popupname {
font-family: PingFang SC;
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 34px;
color: #323337;
margin-top: 10px;
margin-bottom: 10px;
}
.popupdesc {
display: flex;
align-items: center;
justify-content: space-between;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #a1a5b3;
}
}
.tabs {
position: relative;
display: flex;
padding: 20px 14px;
padding-bottom: 0;
&::after {
content: "";
position: absolute;
top: 0;
left: 20px;
right: 20px;
border-top: 1px dashed #edeff3;
}
.tab {
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex: 1;
height: 56px;
background: #53c79d;
border-radius: 10px;
text-align: center;
font-family: PingFang SC;
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 22px;
color: #ffffff;
img {
width: 20px;
height: 20px;
margin-right: 8px;
}
}
.tab + .tab {
margin-left: 14px;
}
}
.meta {
line-height: 12px;
vertical-align: middle;
img {
width: 12px;
}
.title {
color: #7e7e7e;
font-size: 12px;
margin-left: 5px;
}
}
.detail {
color: #a9a9a9;
font-size: 12px;
line-height: 16px;
padding-right: 17px;
padding-bottom: 30px;
.title {
margin-bottom: 4px;
color: #7e7e7e;
font-size: 14px;
line-height: 14px;
}
.content {
max-height: 64px;
overflow: scroll;
}
}
.navbtn {
position: absolute;
top: 46px;
right: 14px;
text-align: center;
&::before {
content: "";
display: block;
position: absolute;
top: 9px;
left: 0;
right: 0;
margin: 0 auto;
width: 74px;
height: 30px;
background: linear-gradient(
90deg,
rgba(46, 177, 255, 1) 0%,
rgba(61, 140, 255, 1) 100%
);
border-radius: 17px;
opacity: 0.5;
filter: blur(4px);
z-index: -2;
}
&::after {
content: "去这里";
display: block;
top: 0;
right: 0;
z-index: 2;
background: linear-gradient(
90deg,
rgba(46, 177, 255, 1) 0%,
rgba(61, 140, 255, 1) 100%
);
border-radius: 17px;
line-height: 34px;
padding: 0 18px;
font-size: 12px;
font-family: SourceHanSansCN-Medium, SourceHanSansCN;
font-weight: 500;
color: #fff;
}
}
}
}

BIN
src/components/Popup/ar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
src/components/Popup/close.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

BIN
src/components/Popup/desc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

BIN
src/components/Popup/nav.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

39
src/components/Shop/Shop.js

@ -0,0 +1,39 @@
import React from "react";
import "./Shop.scss";
import { LazyLoadImage } from "react-lazy-load-image-component";
const Shop = ({
name,
floorName,
shopFormat,
hasBorderTop,
onClick,
logoPath,
isRow,
houseNum,
}) => (
<div
onClick={() => onClick()}
className={
"shop" +
(hasBorderTop ? " has-border-top" : "") +
(isRow ? " is-row" : "")
}
>
{isRow ? (
<>
<div className="name">{name}</div>
<div className="format">{shopFormat}</div>
<div className="houseNum">{houseNum}</div>
</>
) : (
<>
<div className="r1">{name} </div>
<div className="r2">
{shopFormat} <span>{floorName}</span>
</div>
</>
)}
</div>
);
export default Shop;

92
src/components/Shop/Shop.scss

@ -0,0 +1,92 @@
/*postcss-pxtransform disable*/
.shop {
font-size: 0;
line-height: 0;
overflow: hidden;
&.is-row {
position: relative;
height: 56px;
background: #ffffff;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.08);
border-radius: 8px;
.avatar {
position: absolute;
width: 50px;
height: 50px;
top: 8px;
left: 8px;
border: 1px solid #e9d7ad;
box-sizing: border-box;
border-radius: 6px;
padding: 7px;
}
.name {
position: absolute;
top: 10px;
left: 66px;
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 22px;
color: #333333;
}
.houseNum {
position: absolute;
left: 67px;
bottom: 13px;
font-style: normal;
font-weight: 500;
font-size: 10px;
line-height: 13px;
color: #b3aea7;
}
.format {
position: absolute;
top: 11px;
right: 16px;
font-style: normal;
font-weight: bold;
font-size: 12px;
line-height: 17px;
color: #b3aea7;
}
}
.avatar {
width: calc((100vw - 96px - 18px - 10px - 11px) / 3);
height: calc((100vw - 96px - 18px - 10px - 11px) / 3);
padding: 8px;
background: #ffffff;
box-sizing: border-box;
border-radius: 6px;
}
.r1 {
padding: 0 4px;
margin-top: 4px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 12px;
line-height: 17px;
color: #323337;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.r2 {
margin-top: 4px;
display: flex;
justify-content: space-between;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 9px;
line-height: 13px;
color: #a1a5b3;
padding: 0 4px;
span {
font-weight: normal;
font-size: 10px;
color: #a1a5b3;
}
}
}

BIN
src/components/Shop/desc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

58
src/components/ShopList/ShopList.js

@ -0,0 +1,58 @@
import React, { useState, useRef } from "react";
import Shop from "../Shop/Shop";
import "./ShopList.scss";
import InfiniteScroll from "react-infinite-scroller";
const ShopList = ({
mall,
q,
onClick,
top,
isRow = false,
format = null,
floorOrder = null,
}) => {
const [index, setIndex] = useState(0);
const shops = mall.shopInfo.reduce(
(acc, { shopList }) => acc.concat(shopList),
[]
);
const filteredShops = shops
.filter(({ yaxis }) => yaxis)
.filter(({ name, floorName }) =>
!q ? true : name.includes(q) || floorName.includes(q)
)
.filter(
({ shopFormat, floorOrder: floorOrder1 }) =>
(format === null ? true : shopFormat === format) &&
(floorOrder === null ? true : floorOrder1 === floorOrder)
);
const listRef = useRef(null);
return (
<div className={"shop-list " + (isRow ? "is-row" : "")} ref={listRef}>
<InfiniteScroll
pageStart={0}
loadMore={() => setIndex(index + 1)}
hasMore={filteredShops && filteredShops.length > index * 10}
useWindow={false}
getScrollParent={() => listRef && listRef.current}
>
<div className="list">
{filteredShops.map((shop, i) => (
<Shop
{...shop}
hasBorderTop={i !== 0}
key={`${shop.name}_${shop.houseNum}`}
onClick={() => onClick(shop)}
isRow={isRow}
></Shop>
))}
</div>
<div className="end">END</div>
</InfiniteScroll>
</div>
);
};
export default ShopList;

70
src/components/ShopList/ShopList.scss

@ -0,0 +1,70 @@
.shop-list {
width: 100%;
padding: 0 10px;
&.is-row {
padding: 0 8px;
.list {
display: grid;
gap: 8px;
grid-template-columns: 1fr;
}
}
.list {
display: grid;
gap: 12px 9px;
grid-template-columns: 1fr 1fr 1fr;
padding: 10px 0;
}
.search-his {
position: relative;
padding: 0 14px 0 15px;
color: #5a5a5a;
font-weight: 400;
font-size: 12px;
line-height: 45px;
background: #fff;
.button {
float: right;
color: #4d9fdd;
}
&::after {
content: "";
position: absolute;
display: block;
width: calc(100vw - 29px);
bottom: 0;
border-top: 1px solid #f7f7f7;
}
}
.end {
display: flex;
width: 100%;
height: 24px;
justify-content: center;
align-items: center;
margin-top: 24px;
font-family: DINPro;
font-style: normal;
font-weight: 500;
font-size: 18.5px;
line-height: 24px;
text-align: center;
color: #c9cbd1;
}
.end::before {
content: "";
display: inline-block;
width: 84px;
border-top: 1px solid #dedede;
vertical-align: middle;
margin-right: 10px;
}
.end::after {
content: "";
display: inline-block;
width: 84px;
border-top: 1px solid #dedede;
vertical-align: middle;
margin-left: 10px;
}
}

24
src/components/ShopTabs/ShopTabs.js

@ -0,0 +1,24 @@
import React from "react";
import "./ShopTabs.scss";
import floor from "./floor.png";
import floorActive from "./floor_active.png";
import format from "./format.png";
import formatActive from "./format_active.png";
export default ({ tab, onSetTab }) => (
<div className="shop-tabs">
<div
onClick={() => onSetTab("业态")}
className={["tab", tab === "业态" ? "active" : ""].join(" ")}
>
<img src={tab === "业态" ? formatActive : format}></img>
业态
</div>
<div
onClick={() => onSetTab("楼层")}
className={["tab", tab === "楼层" ? "active" : ""].join(" ")}
>
<img src={tab === "楼层" ? floorActive : floor}></img>
楼层
</div>
</div>
);

32
src/components/ShopTabs/ShopTabs.scss

@ -0,0 +1,32 @@
.shop-tabs {
display: flex;
width: calc(100vw - 20px);
height: 56px;
background: #edeff3;
border-radius: 12px;
justify-content: space-around;
align-items: center;
.tab {
display: flex;
width: calc((100vw - 20px - 8px - 3px) / 2);
height: 48px;
justify-content: center;
align-items: center;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 22px;
color: #7a7e8d;
&.active {
background: #ffffff;
border-radius: 8px;
color: #323337;
}
img {
width: 20px;
height: 20px;
margin-right: 8px;
}
}
}

BIN
src/components/ShopTabs/floor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

BIN
src/components/ShopTabs/floor_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

BIN
src/components/ShopTabs/format.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/components/ShopTabs/format_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

83
src/components/ShopsWithFilter/ShopsWithFilter.js

@ -0,0 +1,83 @@
import React, { useState, useEffect } from "react";
import "./ShopsWithFilter.scss";
import SideBar from "../SideBar/SideBar";
import nav from "./nav.png";
const ShopsWithFilter = ({
mall,
onClick,
reset,
onReset = () => {},
wingHeight,
}) => {
const [format, setFormat] = useState(null);
const [floorOrder, setFloorOrder] = useState(0);
useEffect(() => {
if (reset) {
setFormat(null);
setFloorOrder(0);
onReset();
}
}, [reset]);
const shopsByFloorOrder =
mall && mall.shopInfo[floorOrder] ? mall.shopInfo[floorOrder].shopList : [];
const formats = shopsByFloorOrder.reduce(
(acc1, nxt) =>
acc1.includes(nxt.shopFormat) ? [...acc1] : [...acc1, nxt.shopFormat],
[]
);
const shopsByFloorOrderNFormat = shopsByFloorOrder.filter(
({ shopFormat }) => shopFormat === format
);
useEffect(() => {
setFormat(formats[0]);
}, [floorOrder]);
const floors = mall ? mall.floorData : [];
return (
<>
<div className="floortabs">
{floors
.filter(({ url }) => url !== null)
.map((floor) => (
<div
className={
"tab" + (floorOrder === floor.floorOrder ? " active" : "")
}
key={floor.floorOrder}
onClick={() => setFloorOrder(floor.floorOrder)}
>
{floor.name}
</div>
))}
</div>
<div className="wings" style={{ height: wingHeight }}>
<div className="left">
<SideBar
active={format}
onSetActive={(el) => {
setFormat(el);
}}
list={formats}
></SideBar>
</div>
<div className="right">
{shopsByFloorOrderNFormat.map((shop) => (
<div
className="item"
key={shop.houseNum}
onClick={(e) => {
e.stopPropagation();
onClick(shop);
}}
>
<div className="floorname">{shop.floorName}</div>
<div className="name">{shop.name}</div>
<img className="icon" src={nav}></img>
</div>
))}
</div>
</div>
</>
);
};
export default ShopsWithFilter;

100
src/components/ShopsWithFilter/ShopsWithFilter.scss

@ -0,0 +1,100 @@
.floortabs {
display: flex;
margin: 24px 14px 14px 14px;
height: 32px;
.tab {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
border: 1px solid #ebedf0;
border-radius: 4px;
font-family: PingFang SC;
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 20px;
text-align: center;
color: #333333;
&.active {
color: #53c79d;
border: 1px solid #53c79d;
}
}
.tab + .tab {
margin-left: 5px;
}
}
.wings {
display: flex;
width: 100%;
padding: 0 14px;
height: calc(100vh - 156px);
.left {
width: 110px;
flex: 0 0 110px;
height: 100%;
background: #eef5f2;
border-radius: 8px;
overflow-x: hidden;
overflow-y: auto;
}
.right {
flex: 1;
display: inline-flex;
flex-direction: column;
height: 100%;
padding-top: 8px;
padding-left: 8px;
padding-bottom: 8px;
overflow-x: hidden;
overflow-y: auto;
.item {
position: relative;
display: flex;
align-items: center;
height: 32px;
border: 1px solid #f2f3f5;
border-radius: 4px;
.floorname {
display: flex;
width: 36px;
flex: 0 0 36px;
border-right: 1px solid #f2f3f5;
font-family: DINPro;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #333333;
align-items: center;
justify-content: center;
margin-right: 8px;
}
.name {
flex: 1;
text-align: left;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-family: PingFang SC;
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #000000;
}
.icon {
flex: 0 0 12px;
width: 12px;
height: 12px;
margin-right: 10px;
}
}
.item + .item {
margin-top: 12px;
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save