Browse Source

first commit

master
jiannibang 4 years ago
commit
aebad35898
  1. 25
      .gitignore
  2. 70
      README.md
  3. 52
      package.json
  4. 650
      public/aes.js
  5. 1
      public/es7.js
  6. BIN
      public/favicon.ico
  7. 39
      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. 211
      src/components/DefaultPopup/DefaultPopup.js
  17. BIN
      src/components/DefaultPopup/arrow.png
  18. BIN
      src/components/DefaultPopup/close.png
  19. BIN
      src/components/DefaultPopup/go.png
  20. BIN
      src/components/DefaultPopup/search.png
  21. 230
      src/components/DefaultPopup/style.scss
  22. 78
      src/components/Floors/Floors.js
  23. 95
      src/components/Floors/Floors.scss
  24. BIN
      src/components/Floors/loc.png
  25. BIN
      src/components/Floors/up.png
  26. 184
      src/components/HeadBar/HeadBar.js
  27. 271
      src/components/HeadBar/HeadBar.scss
  28. BIN
      src/components/HeadBar/arrow.png
  29. BIN
      src/components/HeadBar/back.png
  30. BIN
      src/components/HeadBar/end.png
  31. BIN
      src/components/HeadBar/start.png
  32. BIN
      src/components/HeadBar/switch.png
  33. 260
      src/components/Malls/Malls.js
  34. 242
      src/components/Malls/Malls.scss
  35. BIN
      src/components/Malls/close_white.png
  36. BIN
      src/components/Malls/pos.png
  37. BIN
      src/components/Malls/search.png
  38. BIN
      src/components/Malls/up.png
  39. BIN
      src/components/More/2d.png
  40. BIN
      src/components/More/3d.png
  41. 53
      src/components/More/More.js
  42. 53
      src/components/More/More.scss
  43. 180
      src/components/NavBottom/NavBottom.js
  44. 335
      src/components/NavBottom/NavBottom.scss
  45. BIN
      src/components/NavBottom/ar.png
  46. BIN
      src/components/NavBottom/back.png
  47. BIN
      src/components/NavBottom/bubble.png
  48. BIN
      src/components/NavBottom/close.png
  49. BIN
      src/components/NavBottom/des.png
  50. BIN
      src/components/NavBottom/des_active.png
  51. BIN
      src/components/NavBottom/followDirection.png
  52. BIN
      src/components/NavBottom/left.png
  53. BIN
      src/components/NavBottom/nav.png
  54. BIN
      src/components/NavBottom/nav_active.png
  55. BIN
      src/components/NavBottom/notFollowDirection.png
  56. BIN
      src/components/NavBottom/pause.png
  57. BIN
      src/components/NavBottom/play.png
  58. BIN
      src/components/NavBottom/right.png
  59. BIN
      src/components/NavBottom/straight.png
  60. BIN
      src/components/Options/2d.png
  61. BIN
      src/components/Options/3d.png
  62. 137
      src/components/Options/Options.js
  63. 106
      src/components/Options/Options.scss
  64. BIN
      src/components/Options/ft.png
  65. BIN
      src/components/Options/ft1.png
  66. BIN
      src/components/Options/ftb.png
  67. BIN
      src/components/Options/ftb1.png
  68. BIN
      src/components/Options/mute.png
  69. BIN
      src/components/Options/on.png
  70. BIN
      src/components/Options/shortest.png
  71. BIN
      src/components/Options/shortest1.png
  72. BIN
      src/components/Options/shortestb.png
  73. BIN
      src/components/Options/shortestb1.png
  74. BIN
      src/components/Options/zt.png
  75. BIN
      src/components/Options/zt1.png
  76. BIN
      src/components/Options/ztb.png
  77. BIN
      src/components/Options/ztb1.png
  78. 67
      src/components/Popup/Popup.js
  79. 194
      src/components/Popup/Popup.scss
  80. BIN
      src/components/Popup/ar.png
  81. BIN
      src/components/Popup/close.png
  82. BIN
      src/components/Popup/couponTop.png
  83. BIN
      src/components/Popup/desc.png
  84. BIN
      src/components/Popup/nav.png
  85. 40
      src/components/Shop/Shop.js
  86. 91
      src/components/Shop/Shop.scss
  87. BIN
      src/components/Shop/desc.png
  88. 82
      src/components/ShopList/ShopList.js
  89. 70
      src/components/ShopList/ShopList.scss
  90. 24
      src/components/ShopTabs/ShopTabs.js
  91. 32
      src/components/ShopTabs/ShopTabs.scss
  92. BIN
      src/components/ShopTabs/floor.png
  93. BIN
      src/components/ShopTabs/floor_active.png
  94. BIN
      src/components/ShopTabs/format.png
  95. BIN
      src/components/ShopTabs/format_active.png
  96. 103
      src/components/ShopsWithFilter/ShopsWithFilter.js
  97. 22
      src/components/ShopsWithFilter/ShopsWithFilter.scss
  98. 15
      src/components/SideBar/SideBar.js
  99. 20
      src/components/SideBar/SideBar.scss
  100. 16
      src/index.js

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)

52
package.json

@ -0,0 +1,52 @@
{
"name": "qmplaza",
"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 qmplaza -f && coscmd upload -r build qmplaza",
"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"
]
}
}

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

39
public/index.html

@ -0,0 +1,39 @@
<!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" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<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>
<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();
});

211
src/components/DefaultPopup/DefaultPopup.js

@ -0,0 +1,211 @@
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";
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,
hasTab,
blurMap = () => {},
}) => {
const marginBottomStateMap = {
0: 47,
1: hasTab
? window.innerHeight - 52 - 179 - 98
: window.innerHeight - 52 - 179,
};
const baseMarginBottom = marginBottomStateMap[state];
const [facList, setFacList] = useState(null);
const [focused, setFocused] = useState(null);
const [start, setStart] = useState(null);
const [marginBottom, setMarginBottom] = useState(null);
const [doTransition, setdoTransition] = useState(false);
useEffect(() => {
setdoTransition(true);
setMarginBottom(baseMarginBottom);
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;
setMarginBottom(baseMarginBottom - 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);
setMarginBottom(baseMarginBottom);
setTimeout(() => {
setdoTransition(false);
}, 500);
} else setState(newState);
};
return (
<>
{state === STATES.init || state === STATES.moreFac ? (
<div
className={"dp" + (state === STATES.moreFac ? " more-fac" : "")}
style={{
marginBottom: marginBottom + "px",
transition: !doTransition ? "none" : "margin-bottom 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 className="sep"></div>
搜索店铺
</div>
<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>
) : (
<>
<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>
</>
)}
{state !== STATES.facList && (
<div
className="sf"
onClick={() => {
setState(STATES.moreFac);
}}
>
<ShopsWithFilter
mall={mall}
onClick={(e) => {
setState(STATES.init);
onClick(e);
}}
wingHeight="calc(100vh - 400px)"
></ShopsWithFilter>
</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/go.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/components/DefaultPopup/search.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

230
src/components/DefaultPopup/style.scss

@ -0,0 +1,230 @@
.dp {
position: relative;
width: calc(100vw - 20px);
margin-left: 10px;
height: 179px;
border-top-left-radius: 24px;
border-top-right-radius: 24px;
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: 18px;
&::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);
}
.facs {
max-height: 333px;
overflow-y: scroll;
}
}
.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: 60px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 16px;
color: #323337;
text-align: left;
background: #f3f4f8;
border: 1px solid #edeff3;
box-sizing: border-box;
border-radius: 10px;
padding-left: 14px;
.icon {
width: 24px;
height: 24px;
}
.sep {
width: 1px;
height: 16px;
background: #c9cbd1;
margin: 0 14px;
}
}
.facs {
display: inline-flex;
align-items: flex-start;
overflow-x: auto;
overflow-y: hidden;
width: 100%;
white-space: nowrap;
margin-top: 20px;
padding-left: 20px;
height: 75px;
.fac {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 65px;
margin-right: 20px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 11px;
line-height: 15px;
text-align: center;
color: #474a56;
img {
display: block;
width: 44px;
height: 44px;
}
}
}
}
.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 {
position: absolute;
top: 177px;
left: 0;
}

78
src/components/Floors/Floors.js

@ -0,0 +1,78 @@
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,
hasCoupon,
}) => {
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" : "",
hasCoupon ? "has-coupon" : "",
].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>
<img alt="上箭头" className="top" src={up}></img>
<img alt="下箭头" className="bottom" src={up}></img>
</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;

95
src/components/Floors/Floors.scss

@ -0,0 +1,95 @@
.floor-list {
position: absolute;
top: -172px;
left: 10px;
height: 160px;
width: 40px;
padding: 10px 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;
&.has-coupon {
top: -211px;
}
.show-nav {
top: -442px;
}
&.nav-end {
top: -212px;
}
ul {
overflow-x: hidden;
overflow-y: auto;
height: 140px;
&::-webkit-scrollbar {
display: none;
}
}
li {
display: inline-block;
line-height: 32px;
font-family: DINPro;
font-style: normal;
font-weight: normal;
font-size: 14px;
text-align: center;
color: #323337;
width: 32px;
height: 32px;
margin: 4px;
&.active {
background: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%);
border-radius: 8px;
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

184
src/components/HeadBar/HeadBar.js

@ -0,0 +1,184 @@
import React, { useState, useEffect } from "react";
import ShopList from "../ShopList/ShopList.js";
import "./HeadBar.scss";
import { searchTypes } from "../Options/Options";
import backpng from './back.png'
const HeadBar = ({
mall,
exit,
start,
end,
floors,
onSwap,
shop,
onSetStart,
onSetEnd,
isPick,
setIsPick,
blurMap,
showSearchType,
onClickSearchType,
searchType,
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 : "";
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">
<ShopList
mall={mall}
q={q}
onClick={(shop) => {
if (isTypingStart) {
onSetStart(shop);
setQ(null);
setIsTypingStart(false);
}
if (isTypingEnd) {
onSetEnd(shop);
setQ(null);
setIsTypingEnd(false);
}
}}
top={"70px"}
isRow={true}
></ShopList>
</div>
)}
</div>
);
};
export default HeadBar;

271
src/components/HeadBar/HeadBar.scss

@ -0,0 +1,271 @@
.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;
}
}
.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: #437af7;
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);
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.2 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: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%);
color: #fff;
}
&.big {
width: 40px;
height: 56px;
margin-top: 8px;
box-shadow: 0px 8px 8px rgba(104, 110, 127, 0.04);
}
}
}

180
src/components/NavBottom/NavBottom.js

@ -0,0 +1,180 @@
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">
{end.logoPath && <img className="avatar" src={end.logoPath} />}
<img
alt="关闭"
className="close-icon"
src={closeButton}
onClick={() => onExit()}
></img>
<div className="name">{end.name}</div>
<div className="meta">
<div> {end.floorName}</div> <div>{end.shopFormat}</div>
</div>
</div>
)}
</div>
);
};
export default NavBottom;

335
src/components/NavBottom/NavBottom.scss

@ -0,0 +1,335 @@
.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: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%);
box-shadow: 0px 6px 12px rgba(93, 172, 249, 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: #437af7;
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 #437af7;
border-radius: 12px;
font-family: DINPro;
font-style: normal;
font-weight: bold;
font-size: 10px;
color: #437af7;
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;
.avatar {
position: absolute;
width: 80px;
height: 80px;
left: 14px;
top: 14px;
background: #ffffff;
box-shadow: 0px 8px 16px rgba(104, 110, 127, 0.08);
border-radius: 6px;
}
.close-icon {
position: absolute;
top: 16px;
right: 16px;
width: 24px;
height: 24px;
z-index: 10;
}
.name {
position: absolute;
top: 24px;
left: 108px;
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: 108px;
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.4 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: 928 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

67
src/components/Popup/Popup.js

@ -0,0 +1,67 @@
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";
import couponTop from "./couponTop.png";
const Popup = ({
showPopup,
defaultPopup,
floors,
closePopup,
shop,
showDetail,
setEnd,
handleAR,
onClickCoupon,
}) => (
<div className={["popup-wrapper", showPopup ? "" : "noshow"].join(" ")}>
{floors}
{defaultPopup}
{showPopup && (
<div className="popup">
{shop.hasCoupon && (
<img
className="couponTop"
src={couponTop}
onClick={(shop) => onClickCoupon(shop)}
></img>
)}
<img
alt="关闭"
className="close-icon"
src={closeButton}
onClick={() => closePopup()}
></img>
<div className="r1">
{shop.logoPath && (
<div
className="avatar"
style={{
backgroundImage: `url(${shop.logoPath})`,
}}
></div>
)}
<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;

194
src/components/Popup/Popup.scss

@ -0,0 +1,194 @@
.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;
.couponTop {
position: absolute;
left: 0;
right: 0;
top: -40px;
z-index: -1;
width: 100%;
object-fit: contain;
}
.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: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%);
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/couponTop.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

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

40
src/components/Shop/Shop.js

@ -0,0 +1,40 @@
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" : "")
}
>
<LazyLoadImage className="avatar" src={logoPath}></LazyLoadImage>
{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;

91
src/components/Shop/Shop.scss

@ -0,0 +1,91 @@
/*postcss-pxtransform disable*/
.shop {
font-size: 0;
line-height: 0;
overflow: hidden;
&.is-row {
position: relative;
height: 66px;
background: #ffffff;
border-radius: 12px;
.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

82
src/components/ShopList/ShopList.js

@ -0,0 +1,82 @@
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 upperQ = q ? q.toUpperCase() : "";
const filteredShops = shops
.filter(({ yaxis }) => yaxis)
.filter(
({
name,
nameEn,
floorName,
initials,
spelling,
initialsCn,
initialsEn,
}) =>
!q
? true
: [
name,
nameEn,
floorName,
initials,
spelling,
initialsCn,
initialsEn,
]
.map(
(key) =>
key && key.toUpperCase && key.toUpperCase().includes(upperQ)
)
.reduce((acc, nxt) => acc || nxt, false)
)
.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

103
src/components/ShopsWithFilter/ShopsWithFilter.js

@ -0,0 +1,103 @@
import React, { useState, useEffect } from "react";
import ShopList from "../ShopList/ShopList.js";
import "./ShopsWithFilter.scss";
import ShopTabs from "../ShopTabs/ShopTabs";
import SideBar from "../SideBar/SideBar";
const ShopsWithFilter = ({
mall,
onClick,
reset,
onReset = () => {},
wingHeight,
}) => {
const [tab, setTab] = useState("业态");
const [format, setFormat] = useState(null);
const [floorOrder, setFloorOrder] = useState(null);
useEffect(() => {
if (reset) {
setFormat(null);
setFloorOrder(null);
setTab("业态");
onReset();
}
}, [reset]);
const formats = mall
? mall.shopInfo.reduce(
(acc, { shopList }) =>
shopList.reduce(
(acc1, nxt) =>
acc1.includes(nxt.shopFormat)
? [...acc1]
: [...acc1, nxt.shopFormat],
acc
),
[]
)
: [];
const floors = mall ? mall.floorData : [];
const floorNameOrderMap = floors.reduce(
(acc, nxt) => ({ ...acc, [nxt.name]: nxt.floorOrder }),
{}
);
const floorOrderNameMap = floors.reduce(
(acc, nxt) => ({ ...acc, [nxt.floorOrder]: nxt.name }),
{}
);
return (
<>
<div style={{ marginTop: "16px", marginLeft: "10px" }}>
<ShopTabs
tab={tab}
onSetTab={(el) => {
setFormat(null);
setFloorOrder(null);
setTab(el);
}}
></ShopTabs>
</div>
<div className="wings" style={{ height: wingHeight }}>
<div className="left">
<SideBar
active={
tab === "业态" && format === null
? "全部业态"
: tab === "楼层" && floorOrder === null
? "全部楼层"
: tab === "业态"
? format
: floorOrderNameMap[floorOrder]
}
onSetActive={(el) => {
if (tab === "业态") {
setFormat(el === "全部业态" ? null : el);
} else {
setFloorOrder(el === "全部楼层" ? null : floorNameOrderMap[el]);
}
}}
list={
tab === "业态"
? ["全部业态", ...formats]
: [
"全部楼层",
...floors
.filter(({ url }) => url !== null)
.map(({ name }) => name),
]
}
></SideBar>
</div>
<div className="right">
<ShopList
mall={mall}
isRow={false}
format={format}
floorOrder={floorOrder}
onClick={onClick}
></ShopList>
</div>
</div>
</>
);
};
export default ShopsWithFilter;

22
src/components/ShopsWithFilter/ShopsWithFilter.scss

@ -0,0 +1,22 @@
.wings {
display: flex;
width: 100vw;
height: calc(100vh - 156px);
border-top: 1px solid #edeff3;
margin-top: 8px;
.left {
width: 96px;
flex: 0 0 96px;
height: 100%;
background: #edeff3;
overflow-x: hidden;
overflow-y: auto;
}
.right {
flex: 1;
height: 100%;
background: #f3f4f8;
overflow-x: hidden;
overflow-y: auto;
}
}

15
src/components/SideBar/SideBar.js

@ -0,0 +1,15 @@
import React from "react";
import "./SideBar.scss";
export default ({ list, active, onSetActive }) => (
<div className="side-bar">
{list.map((el) => (
<div
key={el}
className={["option", active === el ? "active" : ""].join(" ")}
onClick={() => onSetActive(el)}
>
{el}
</div>
))}
</div>
);

20
src/components/SideBar/SideBar.scss

@ -0,0 +1,20 @@
.side-bar {
width: 100%;
.option {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 48px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 12px;
color: #474a56;
&.active {
background: #ffffff;
color: #437af7;
font-weight: 600;
}
}
}

16
src/index.js

@ -0,0 +1,16 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

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

Loading…
Cancel
Save