提交 a36e0940 作者: ZhangLingKun

Merge branch 'develop'

流水线 #7690 已通过 于阶段
in 5 分 46 秒
...@@ -79,7 +79,8 @@ ...@@ -79,7 +79,8 @@
"jsx-a11y/no-static-element-interactions": "off", "jsx-a11y/no-static-element-interactions": "off",
"eqeqeq": "warn", "eqeqeq": "warn",
"no-unsafe-optional-chaining": "warn", "no-unsafe-optional-chaining": "warn",
"no-param-reassign": "warn" "no-param-reassign": "warn",
"react-hooks/exhaustive-deps": "off"
} }
} }
] ]
......
...@@ -4,9 +4,14 @@ ENV PROFILES_ACTIVE=$PROFILES_ACTIVE ...@@ -4,9 +4,14 @@ ENV PROFILES_ACTIVE=$PROFILES_ACTIVE
# Install dependencies only when needed # Install dependencies only when needed
FROM base AS deps FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
# 设置淘宝镜像
RUN npm config set registry https://registry.npmmirror.com
RUN yarn config set registry https://registry.npmmirror.com
# Install dependencies based on the preferred package manager # Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \ RUN \
...@@ -16,6 +21,8 @@ RUN \ ...@@ -16,6 +21,8 @@ RUN \
else echo "Lockfile not found." && exit 1; \ else echo "Lockfile not found." && exit 1; \
fi fi
# elif [ -f pnpm-lock.yaml ]; then corepack enable && pnpm i --frozen-lockfile; \
FROM base AS builder FROM base AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
......
...@@ -14,4 +14,4 @@ patches: ...@@ -14,4 +14,4 @@ patches:
images: images:
- name: REGISTRY/NAMESPACE/IMAGE:TAG - name: REGISTRY/NAMESPACE/IMAGE:TAG
newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly-dev/web newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly-dev/web
newTag: 69ee5b55eedcad8b4838f59a13c557279b813862 newTag: 818d779104308947c9cbd3615a4c3b39910c794c
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",
"@ant-design/cssinjs": "^1.17.2", "@ant-design/cssinjs": "^1.17.2",
"@ant-design/icons": "^5.2.6", "@ant-design/icons": "^5.2.6",
"@reduxjs/toolkit": "^1.9.7",
"@types/styled-components": "^5.1.29", "@types/styled-components": "^5.1.29",
"antd": "^5.11.0", "antd": "^5.11.0",
"axios": "^1.6.0", "axios": "^1.6.0",
...@@ -30,10 +31,15 @@ ...@@ -30,10 +31,15 @@
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"next": "14.0.1", "next": "14.0.1",
"next-redux-wrapper": "^8.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-infinite-scroll-component": "^6.1.0", "react-infinite-scroll-component": "^6.1.0",
"styled-components": "^6.1.0" "react-redux": "^8.1.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"styled-components": "^6.1.0",
"swiper": "^11.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/big.js": "^6.2.0", "@types/big.js": "^6.2.0",
......
...@@ -14,6 +14,9 @@ dependencies: ...@@ -14,6 +14,9 @@ dependencies:
'@ant-design/icons': '@ant-design/icons':
specifier: ^5.2.6 specifier: ^5.2.6
version: registry.npmmirror.com/@ant-design/icons@5.2.6(react-dom@18.2.0)(react@18.2.0) version: registry.npmmirror.com/@ant-design/icons@5.2.6(react-dom@18.2.0)(react@18.2.0)
'@reduxjs/toolkit':
specifier: ^1.9.7
version: registry.npmmirror.com/@reduxjs/toolkit@1.9.7(react-redux@8.1.3)(react@18.2.0)
'@types/styled-components': '@types/styled-components':
specifier: ^5.1.29 specifier: ^5.1.29
version: registry.npmmirror.com/@types/styled-components@5.1.29 version: registry.npmmirror.com/@types/styled-components@5.1.29
...@@ -44,6 +47,9 @@ dependencies: ...@@ -44,6 +47,9 @@ dependencies:
next: next:
specifier: 14.0.1 specifier: 14.0.1
version: registry.npmmirror.com/next@14.0.1(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5) version: registry.npmmirror.com/next@14.0.1(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5)
next-redux-wrapper:
specifier: ^8.1.0
version: registry.npmmirror.com/next-redux-wrapper@8.1.0(next@14.0.1)(react-redux@8.1.3)(react@18.2.0)
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: registry.npmmirror.com/react@18.2.0 version: registry.npmmirror.com/react@18.2.0
...@@ -53,9 +59,21 @@ dependencies: ...@@ -53,9 +59,21 @@ dependencies:
react-infinite-scroll-component: react-infinite-scroll-component:
specifier: ^6.1.0 specifier: ^6.1.0
version: registry.npmmirror.com/react-infinite-scroll-component@6.1.0(react@18.2.0) version: registry.npmmirror.com/react-infinite-scroll-component@6.1.0(react@18.2.0)
react-redux:
specifier: ^8.1.3
version: registry.npmmirror.com/react-redux@8.1.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1)
redux:
specifier: ^4.2.1
version: registry.npmmirror.com/redux@4.2.1
redux-persist:
specifier: ^6.0.0
version: registry.npmmirror.com/redux-persist@6.0.0(react@18.2.0)(redux@4.2.1)
styled-components: styled-components:
specifier: ^6.1.0 specifier: ^6.1.0
version: registry.npmmirror.com/styled-components@6.1.0(react-dom@18.2.0)(react@18.2.0) version: registry.npmmirror.com/styled-components@6.1.0(react-dom@18.2.0)(react@18.2.0)
swiper:
specifier: ^11.0.5
version: registry.npmmirror.com/swiper@11.0.5
devDependencies: devDependencies:
'@types/big.js': '@types/big.js':
...@@ -887,6 +905,28 @@ packages: ...@@ -887,6 +905,28 @@ packages:
react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0) react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0)
dev: false dev: false
registry.npmmirror.com/@reduxjs/toolkit@1.9.7(react-redux@8.1.3)(react@18.2.0):
resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-1.9.7.tgz}
id: registry.npmmirror.com/@reduxjs/toolkit/1.9.7
name: '@reduxjs/toolkit'
version: 1.9.7
peerDependencies:
react: ^16.9.0 || ^17.0.0 || ^18
react-redux: ^7.2.1 || ^8.0.2
peerDependenciesMeta:
react:
optional: true
react-redux:
optional: true
dependencies:
immer: registry.npmmirror.com/immer@9.0.21
react: registry.npmmirror.com/react@18.2.0
react-redux: registry.npmmirror.com/react-redux@8.1.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1)
redux: registry.npmmirror.com/redux@4.2.1
redux-thunk: registry.npmmirror.com/redux-thunk@2.4.2(redux@4.2.1)
reselect: registry.npmmirror.com/reselect@4.1.8
dev: false
registry.npmmirror.com/@rushstack/eslint-patch@1.5.1: registry.npmmirror.com/@rushstack/eslint-patch@1.5.1:
resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz} resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz}
name: '@rushstack/eslint-patch' name: '@rushstack/eslint-patch'
...@@ -951,7 +991,6 @@ packages: ...@@ -951,7 +991,6 @@ packages:
version: 18.2.15 version: 18.2.15
dependencies: dependencies:
'@types/react': registry.npmmirror.com/@types/react@18.2.37 '@types/react': registry.npmmirror.com/@types/react@18.2.37
dev: true
registry.npmmirror.com/@types/react@18.2.37: registry.npmmirror.com/@types/react@18.2.37:
resolution: {integrity: sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/react/-/react-18.2.37.tgz} resolution: {integrity: sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/react/-/react-18.2.37.tgz}
...@@ -989,6 +1028,12 @@ packages: ...@@ -989,6 +1028,12 @@ packages:
version: 4.2.2 version: 4.2.2
dev: false dev: false
registry.npmmirror.com/@types/use-sync-external-store@0.0.3:
resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz}
name: '@types/use-sync-external-store'
version: 0.0.3
dev: false
registry.npmmirror.com/@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.0.0)(typescript@5.0.2): registry.npmmirror.com/@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.0.0)(typescript@5.0.2):
resolution: {integrity: sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz} resolution: {integrity: sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz}
id: registry.npmmirror.com/@typescript-eslint/eslint-plugin/6.10.0 id: registry.npmmirror.com/@typescript-eslint/eslint-plugin/6.10.0
...@@ -2911,6 +2956,12 @@ packages: ...@@ -2911,6 +2956,12 @@ packages:
engines: {node: '>= 4'} engines: {node: '>= 4'}
dev: true dev: true
registry.npmmirror.com/immer@9.0.21:
resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz}
name: immer
version: 9.0.21
dev: false
registry.npmmirror.com/immutable@4.3.4: registry.npmmirror.com/immutable@4.3.4:
resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz} resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz}
name: immutable name: immutable
...@@ -3485,6 +3536,21 @@ packages: ...@@ -3485,6 +3536,21 @@ packages:
version: 1.4.0 version: 1.4.0
dev: true dev: true
registry.npmmirror.com/next-redux-wrapper@8.1.0(next@14.0.1)(react-redux@8.1.3)(react@18.2.0):
resolution: {integrity: sha512-2hIau0hcI6uQszOtrvAFqgc0NkZegKYhBB7ZAKiG3jk7zfuQb4E7OV9jfxViqqojh3SEHdnFfPkN9KErttUKuw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/next-redux-wrapper/-/next-redux-wrapper-8.1.0.tgz}
id: registry.npmmirror.com/next-redux-wrapper/8.1.0
name: next-redux-wrapper
version: 8.1.0
peerDependencies:
next: '>=9'
react: '*'
react-redux: '*'
dependencies:
next: registry.npmmirror.com/next@14.0.1(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5)
react: registry.npmmirror.com/react@18.2.0
react-redux: registry.npmmirror.com/react-redux@8.1.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1)
dev: false
registry.npmmirror.com/next@14.0.1(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5): registry.npmmirror.com/next@14.0.1(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5):
resolution: {integrity: sha512-s4YaLpE4b0gmb3ggtmpmV+wt+lPRuGtANzojMQ2+gmBpgX9w5fTbjsy6dXByBuENsdCX5pukZH/GxdFgO62+pA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/next/-/next-14.0.1.tgz} resolution: {integrity: sha512-s4YaLpE4b0gmb3ggtmpmV+wt+lPRuGtANzojMQ2+gmBpgX9w5fTbjsy6dXByBuENsdCX5pukZH/GxdFgO62+pA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/next/-/next-14.0.1.tgz}
id: registry.npmmirror.com/next/14.0.1 id: registry.npmmirror.com/next/14.0.1
...@@ -4484,6 +4550,43 @@ packages: ...@@ -4484,6 +4550,43 @@ packages:
version: 18.2.0 version: 18.2.0
dev: false dev: false
registry.npmmirror.com/react-redux@8.1.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1):
resolution: {integrity: sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/react-redux/-/react-redux-8.1.3.tgz}
id: registry.npmmirror.com/react-redux/8.1.3
name: react-redux
version: 8.1.3
peerDependencies:
'@types/react': ^16.8 || ^17.0 || ^18.0
'@types/react-dom': ^16.8 || ^17.0 || ^18.0
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
react-native: '>=0.59'
redux: ^4 || ^5.0.0-beta.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
react-dom:
optional: true
react-native:
optional: true
redux:
optional: true
dependencies:
'@babel/runtime': registry.npmmirror.com/@babel/runtime@7.23.2
'@types/hoist-non-react-statics': registry.npmmirror.com/@types/hoist-non-react-statics@3.3.4
'@types/react': registry.npmmirror.com/@types/react@18.2.37
'@types/react-dom': registry.npmmirror.com/@types/react-dom@18.2.15
'@types/use-sync-external-store': registry.npmmirror.com/@types/use-sync-external-store@0.0.3
hoist-non-react-statics: registry.npmmirror.com/hoist-non-react-statics@3.3.2
react: registry.npmmirror.com/react@18.2.0
react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0)
react-is: registry.npmmirror.com/react-is@18.2.0
redux: registry.npmmirror.com/redux@4.2.1
use-sync-external-store: registry.npmmirror.com/use-sync-external-store@1.2.0(react@18.2.0)
dev: false
registry.npmmirror.com/react@18.2.0: registry.npmmirror.com/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/react/-/react-18.2.0.tgz} resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/react/-/react-18.2.0.tgz}
name: react name: react
...@@ -4501,6 +4604,41 @@ packages: ...@@ -4501,6 +4604,41 @@ packages:
dependencies: dependencies:
picomatch: registry.npmmirror.com/picomatch@2.3.1 picomatch: registry.npmmirror.com/picomatch@2.3.1
registry.npmmirror.com/redux-persist@6.0.0(react@18.2.0)(redux@4.2.1):
resolution: {integrity: sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/redux-persist/-/redux-persist-6.0.0.tgz}
id: registry.npmmirror.com/redux-persist/6.0.0
name: redux-persist
version: 6.0.0
peerDependencies:
react: '>=16'
redux: '>4.0.0'
peerDependenciesMeta:
react:
optional: true
dependencies:
react: registry.npmmirror.com/react@18.2.0
redux: registry.npmmirror.com/redux@4.2.1
dev: false
registry.npmmirror.com/redux-thunk@2.4.2(redux@4.2.1):
resolution: {integrity: sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/redux-thunk/-/redux-thunk-2.4.2.tgz}
id: registry.npmmirror.com/redux-thunk/2.4.2
name: redux-thunk
version: 2.4.2
peerDependencies:
redux: ^4
dependencies:
redux: registry.npmmirror.com/redux@4.2.1
dev: false
registry.npmmirror.com/redux@4.2.1:
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/redux/-/redux-4.2.1.tgz}
name: redux
version: 4.2.1
dependencies:
'@babel/runtime': registry.npmmirror.com/@babel/runtime@7.23.2
dev: false
registry.npmmirror.com/reflect.getprototypeof@1.0.4: registry.npmmirror.com/reflect.getprototypeof@1.0.4:
resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz} resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz}
name: reflect.getprototypeof name: reflect.getprototypeof
...@@ -4538,6 +4676,12 @@ packages: ...@@ -4538,6 +4676,12 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
registry.npmmirror.com/reselect@4.1.8:
resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/reselect/-/reselect-4.1.8.tgz}
name: reselect
version: 4.1.8
dev: false
registry.npmmirror.com/resize-observer-polyfill@1.5.1: registry.npmmirror.com/resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz} resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz}
name: resize-observer-polyfill name: resize-observer-polyfill
...@@ -4921,6 +5065,13 @@ packages: ...@@ -4921,6 +5065,13 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true dev: true
registry.npmmirror.com/swiper@11.0.5:
resolution: {integrity: sha512-rhCwupqSyRnWrtNzWzemnBLMoyYuoDgGgspAm/8iBD3jCvAWycPLH4Z3TB0O5520DHLzMx94yUMH/B9Efpa48w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/swiper/-/swiper-11.0.5.tgz}
name: swiper
version: 11.0.5
engines: {node: '>= 4.7.0'}
dev: false
registry.npmmirror.com/synckit@0.8.5: registry.npmmirror.com/synckit@0.8.5:
resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/synckit/-/synckit-0.8.5.tgz} resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/synckit/-/synckit-0.8.5.tgz}
name: synckit name: synckit
...@@ -5124,6 +5275,17 @@ packages: ...@@ -5124,6 +5275,17 @@ packages:
punycode: registry.npmmirror.com/punycode@2.3.1 punycode: registry.npmmirror.com/punycode@2.3.1
dev: true dev: true
registry.npmmirror.com/use-sync-external-store@1.2.0(react@18.2.0):
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz}
id: registry.npmmirror.com/use-sync-external-store/1.2.0
name: use-sync-external-store
version: 1.2.0
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: registry.npmmirror.com/react@18.2.0
dev: false
registry.npmmirror.com/v8-compile-cache@2.4.0: registry.npmmirror.com/v8-compile-cache@2.4.0:
resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz} resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz}
name: v8-compile-cache name: v8-compile-cache
......
import { CommonAPI } from './modules/common'; import { CommonAPI } from './modules/common';
import { HomeAPI } from './modules/home'; import { HomeAPI } from './modules/home';
import { UserAPI } from './modules/user';
import { WalletAPI } from './modules/wallet';
export { CommonAPI, HomeAPI }; export { CommonAPI, HomeAPI, UserAPI, WalletAPI };
import { InterFunction } from '@/api/interface';
// 获取二维码
export type GetAppletQRCode = InterFunction<
// 入参
{
page: string;
scene: string;
},
// 出参
string
>;
// 查询登录信息
export type GetLoginInfo = InterFunction<
{
randomLoginCode: string;
},
{
token: string;
openId: string;
userAccountId: number;
accountNo: null;
portType: number;
uid: string;
phoneNum: string;
userName: string;
nickName: string;
companyInfoVO: null;
roleInfo: null;
appUserAccountId: null;
}
>;
// 获取用户信息
export type GetAccountInfo = InterFunction<
{},
{
accountStatus: number;
accountType: number;
companyAuthStatus: number;
email: string;
id: number;
nickName: string;
phoneNum: string;
portType: number;
source: number;
uid: string;
userImg: string;
userName: string;
userSex: number;
realNameAuthStatus: number;
auditStatus: number;
totalPoints: number;
xzAuthStatus: number;
cooperationTagVOS: {
createTime: string;
id: number;
tagDescription: string;
tagImg: string;
tagName: string;
tagRequire: string;
}[];
coverPicture: string;
districtChildId: number;
region: string;
briefIntroduction: string;
companyInfoVO: {
address: string;
backImg: string;
backUserAccountId: number;
brandLogo: string;
brandName: string;
companyName: string;
companyType: number;
companyUserName: string;
content: string;
creditCode: string;
distance: number;
fullName: string;
id: number;
lat: number;
leader: number;
licenseImg: string;
lon: number;
phoneNum: number;
remark: string;
score: string;
userAccountId: number;
};
createTime: string;
}
>;
...@@ -4,6 +4,7 @@ import { ...@@ -4,6 +4,7 @@ import {
InterItemFunction, InterItemFunction,
InterListFunction, InterListFunction,
} from '@/api/interface'; } from '@/api/interface';
import { QueryGoodsInfoByCategorySub } from '@/api/interface/mall';
// 获取图片-小程序 // 获取图片-小程序
export type ListBannerImgType = InterFunction< export type ListBannerImgType = InterFunction<
...@@ -39,7 +40,7 @@ export type AppCategoryInfoType = InterFunction< ...@@ -39,7 +40,7 @@ export type AppCategoryInfoType = InterFunction<
icon?: string; icon?: string;
createTime?: string; createTime?: string;
updateTime?: null; updateTime?: null;
subDTOList?: Array<{ subDTOList: Array<{
id: number; id: number;
name: string; name: string;
description?: string; description?: string;
...@@ -51,48 +52,6 @@ export type AppCategoryInfoType = InterFunction< ...@@ -51,48 +52,6 @@ export type AppCategoryInfoType = InterFunction<
sort?: number; sort?: number;
}[] }[]
>; >;
// 分类下的商品数据
export type QueryGoodsInfoByCategorySub = InterFunction<
number[],
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: Array<{
chooseType: number;
goodsSpecValuesList: Array<{
channelPrice: number;
goodsSpecId: number;
id: number;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock: number;
}>;
id: number;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}>;
id: number;
labelShow: number;
resourcesList: Array<{
id: number;
type: number;
url: string;
}>;
shelfStatus: number;
tradeName: string;
userAccountId: number;
recommend: number;
}[][]
>;
// 一级行业列表 // 一级行业列表
export type IndustryListPagesType = InterListFunction< export type IndustryListPagesType = InterListFunction<
{ {
......
import { InterDataType, InterFunction } from '@/api/interface';
// 小程序分类信息--含一二级分类
export type GetAppCategoryInfo = InterFunction<
{},
{
id: number;
name: string;
description?: string;
icon?: string;
createTime?: string;
updateTime?: null;
subDTOList?: Array<{
id: number;
name: string;
description?: string;
categoryPrimaryId?: number;
createTime?: string;
updateTime?: string;
subDTOList?: InterDataType<QueryGoodsInfoByCategorySub>;
}>;
sort?: number;
}[]
>;
export type QueryGoodsInfoByCategorySub = InterFunction<
number[],
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: Array<{
chooseType: number;
goodsSpecValuesList: Array<{
channelPrice: number;
goodsSpecId: number;
id: number;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock: number;
}>;
id: number;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}>;
id: number;
labelShow: number;
resourcesList: Array<{
id: number;
type: number;
url: string;
}>;
shelfStatus: number;
tradeName: string;
userAccountId: number;
recommend: number;
priceShow: number;
specAttrList: null;
priceStock: Array<{
id: number;
productSpec: string;
salePrice: number;
skuImage: null;
channelPrice: null;
stock: null;
skuNo: null;
}>;
companyName: string;
}[]
>;
// 小程序商品详情
export type AppMallGoodsDetails = InterFunction<
{ id: number },
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
id: number;
labelShow: number;
priceStock: Array<{
channelPrice: number;
id: number;
productSpec: string;
salePrice: number;
skuImage: string;
skuNo: string;
stock: number;
}>;
resourcesList: Array<{
id: number;
type: number;
url: string;
}>;
shelfStatus: number;
specAttrList: Array<{
id: number;
specName: string;
specValuesList: Array<{
id: number;
specName: string;
specValuesList: null;
}>;
}>;
tradeName: string;
userAccountId: number;
priceShow: number;
}
>;
// pc-后台用户id单位查询
export type GetCompanyInfoByBUId = InterFunction<
{
backUserAccountId: number;
},
{
address: string;
brandLogo: string;
brandName: string;
companyName: string;
companyType: number;
companyUserName: string;
content: string;
creditCode: string;
fullName: string;
id: number;
lat: number;
leader: number;
licenseImg: string;
lon: number;
phoneNum: string;
remark: string;
score: string;
userAccountId: number;
province: string;
city: string;
}
>;
import { InterFunction, InterListFunction } from '@/api/interface';
// 一级行业列表
export type GetIndustryListPagesType = InterListFunction<
{
pageNo: number;
pageSize: number;
typeName?: string;
id?: number;
},
{
createTime: string;
description: string;
id: number;
inspectionDTOS: Array<{
caseImg: string;
caseVideo: string;
companyInspectionDTOS: Array<{
companyInfoId: number;
companyName: string;
detailPage: string;
id: number;
industryTypeDTO: {};
inspectionDTO: {};
inspectionFileDTOS: Array<{
companyInspectionId: number;
fileType: number;
fileUrl: string;
first: number;
id: number;
}>;
inspectionFirstImg: string;
inspectionId: number;
inspectionPriceUnitId: number;
inspectionTagDTO: {
id: number;
inspectionId: number;
tagName: string;
};
inspectionTagId: number;
price: number;
priceRemark: string;
remark: string;
saleState: number;
serviceArea: string;
}>;
id: number;
industryTypeId: number;
inspectionDescription: string;
inspectionImg: string;
inspectionName: string;
inspectionNo: string;
saleState: number;
}>;
saleState: number;
typeImg: string;
typeName: string;
}
>;
// 单位服务列表-小程序展示
export type GetListAPPCompanyInspectionPageType = InterListFunction<
{
companyInfoId?: number;
industryTypeId?: number;
inspectionId?: number;
inspectionTagId?: number;
keyword?: string;
},
{
id: number;
companyInfoId: number;
serviceArea: string;
inspectionId: number;
inspectionTagId: null;
price: number;
priceRemark: string;
inspectionPriceUnitId: number;
detailPage: string;
saleState: number;
remark: string;
inspectionFirstImg: string;
industryTypeDTO: {
id: number;
typeName: string;
typeImg: null;
description: null;
saleState: null;
createTime: null;
inspectionDTOS: null;
};
inspectionDTO: {
id: number;
inspectionNo: string;
inspectionName: string;
industryTypeId: null;
inspectionImg: null;
inspectionDescription: null;
saleState: null;
caseImg: null;
caseVideo: null;
createTime: null;
companyInspectionDTOS: null;
};
inspectionTagDTO: {
id: null;
tagName: null;
inspectionId: number;
};
inspectionFileDTOS: Array<{
id: number;
fileType: number;
first: number;
companyInspectionId: number;
fileUrl: string;
}>;
companyName: string;
}
>;
// 价格单位列表
export type ListInspectionPriceUnit = InterFunction<
{},
{
id: number;
unitName: string;
}[]
>;
// 单位服务详情
export type GetCompanyInspectionById = InterFunction<
{
id: number;
},
{
companyInfoId: number;
companyName: string;
detailPage: string;
id: number;
industryTypeDTO: {
createTime: string;
description: string;
id: number;
inspectionDTOS: Array<{
caseImg: string;
caseVideo: string;
companyInspectionDTOS: null;
id: number;
industryTypeId: number;
inspectionDescription: string;
inspectionImg: string;
inspectionName: string;
inspectionNo: string;
saleState: number;
}>;
saleState: number;
typeImg: string;
typeName: string;
};
inspectionDTO: {
caseImg: string;
caseVideo: string;
companyInspectionDTOS: null;
id: number;
industryTypeId: number;
inspectionDescription: string;
inspectionImg: string;
inspectionName: string;
inspectionNo: string;
saleState: number;
};
inspectionFileDTOS: Array<{
companyInspectionId: number;
fileType: number;
fileUrl: string;
first: number;
id: number;
}>;
inspectionFirstImg: string;
inspectionId: number;
inspectionPriceUnitId: number;
inspectionTagDTO: {
id: number;
inspectionId: number;
tagName: string;
};
inspectionTagId: number;
price: number;
priceRemark: string;
remark: string;
saleState: number;
serviceArea: string;
}
>;
// 单位查询
export type GetCompanyInfoById = InterFunction<
{
id: number;
},
{
address: string;
brandLogo: string;
brandName: string;
companyName: string;
companyType: number;
companyUserName: string;
content: string;
creditCode: string;
fullName: string;
id: number;
lat: number;
leader: number;
licenseImg: string;
lon: number;
phoneNum: number;
remark: string;
score: string;
userAccountId: number;
backImg: string;
backUserAccountId: number;
backUserId: number;
}
>;
import { InterFunction } from '@/api/interface';
// 查询用户地址列表-条件查询
export type UserAddressSelectList = InterFunction<
{
userAccountId?: number;
},
{
id: number;
takeAddress: string;
takeName: string;
takePhone: string;
takeRegion: string;
type: number;
}[]
>;
import { InterFunction } from '@/api/interface';
// 获取用户钱包(新)
export type UserPayWalletInfoType = InterFunction<
{},
{
cashAmt: number;
cashFreeze: number;
cashPaid: number;
id: number;
rebateWdl: number;
salaryAmt: number;
salaryFreeze: number;
salaryPaid: number;
totalAmount: number;
totalCash: number;
totalFreeze: number;
totalSalary: number;
userAccountId: number;
userName: string;
}
>;
import {
GetAccountInfo,
GetAppletQRCode,
GetLoginInfo,
} from '@/api/interface/common';
import request from '../request'; import request from '../request';
export class CommonAPI { export class CommonAPI {
...@@ -8,4 +13,16 @@ export class CommonAPI { ...@@ -8,4 +13,16 @@ export class CommonAPI {
// 测试接口 // 测试接口
static cooperationListTag: any = (params: any) => static cooperationListTag: any = (params: any) =>
request.get('/userapp/cooperation/listTag', { params }); request.get('/userapp/cooperation/listTag', { params });
// 获取二维码
static getAppletQRCode: GetAppletQRCode = (params) =>
request.get('/userapp/wx/getAppletQRCode', { params });
// 查询登录信息
static getLoginInfo: GetLoginInfo = (params) =>
request.get('/userapp/temp-auth/getLoginInfo', { params });
// 获取用户信息
static getAccountInfo: GetAccountInfo = (params) =>
request.get('/userapp/user-account/info', { params });
} }
import {
QueryGoodsInfoByCategorySub,
GetAppCategoryInfo,
AppMallGoodsDetails,
GetCompanyInfoByBUId,
} from '@/api/interface/mall';
import request from '../request';
export class MallAPI {
// 小程序分类信息--含一二级分类
static getAppCategoryInfo: GetAppCategoryInfo = (params) =>
request.get('/pms/category/appCategoryInfo', { params });
// 根据子分类查询商品信息
static queryGoodsInfoByCategorySub: QueryGoodsInfoByCategorySub = (params) =>
request.post('/pms/app/goods/queryGoodsInfoByCategorySub', params);
// 小程序商品详情
static appMallGoodsDetails: AppMallGoodsDetails = (params) =>
request.get('/pms/app/goods/appMallGoodsDetails', { params });
// pc-后台用户id单位查询
static getCompanyInfoByBUId: GetCompanyInfoByBUId = (params) =>
request.get('/userapp/company/getCompanyInfoByBUId', { params });
}
import {
GetCompanyInfoById,
GetCompanyInspectionById,
GetIndustryListPagesType,
GetListAPPCompanyInspectionPageType,
ListInspectionPriceUnit,
} from '@/api/interface/service';
import request from '@/api/request';
export class ServiceAPI {
// 一级行业列表
static getIndustryListPages: GetIndustryListPagesType = (params) =>
request.post('/pms/industry/listPages', params);
// 单位服务列表-小程序展示
static getListAPPCompanyInspectionPage: GetListAPPCompanyInspectionPageType =
(params) =>
request.post(
'/pms/company-inspection/listAPPCompanyInspectionPage',
params,
);
// 价格单位列表
static listInspectionPriceUnit: ListInspectionPriceUnit = (params) =>
request.get('/pms/company-inspection/listInspectionPriceUnit', params);
// 单位服务详情
static getCompanyInspectionById: GetCompanyInspectionById = (params) =>
request.get('/pms/company-inspection/getCompanyInspectionById', { params });
// 单位查询
static getCompanyInfoById: GetCompanyInfoById = (params) =>
request.get('/userapp/company/getCompanyInfoById', { params });
}
import { UserAddressSelectList } from '@/api/interface/user';
import request from '../request';
export class UserAPI {
// 查询用户地址列表-条件查询
static userAddressSelectList: UserAddressSelectList = (params) =>
request.post('/oms/user-address/selectList', params);
}
import { UserPayWalletInfoType } from '@/api/interface/wallet';
import request from '@/api/request';
export class WalletAPI {
// 获取用户钱包(新)
static getUserPayWalletInfo: UserPayWalletInfoType = () =>
request.get('/userapp/pay/getCurrentUserPayWalletInfo');
}
...@@ -3,19 +3,16 @@ import axios, { AxiosResponse } from 'axios'; ...@@ -3,19 +3,16 @@ import axios, { AxiosResponse } from 'axios';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
// 接口地址
const { NEXT_PUBLIC_BASE_URL } = process.env;
// 创建服务 // 创建服务
const service = axios.create({ const service = axios.create({
baseURL: NEXT_PUBLIC_BASE_URL, baseURL: process.env.NEXT_PUBLIC_BASE_URL, // 接口地址
timeout: 3600000, timeout: 36000,
}); });
service.interceptors.request.use( service.interceptors.request.use(
(config: any) => { (config: any) => {
const token = Cookies.get('SHAREFLY-TOKEN'); const token = Cookies.get('SHAREFLY-WEB-TOKEN');
// console.log('config --->', NEXT_PUBLIC_BASE_URL); // console.log('config ==========>', config);
if (token) { if (token) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
config.headers.token = token; config.headers.token = token;
...@@ -43,8 +40,8 @@ service.interceptors.response.use( ...@@ -43,8 +40,8 @@ service.interceptors.response.use(
) )
) { ) {
message.error(data.message).then(); message.error(data.message).then();
Cookies.remove('SHAREFLY-TOKEN'); Cookies.remove('SHAREFLY-WEB-TOKEN');
localStorage.removeItem('roleId'); localStorage.removeItem('persist:SHAREFLY-WEB-STORAGE');
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 1000); }, 1000);
...@@ -55,8 +52,18 @@ service.interceptors.response.use( ...@@ -55,8 +52,18 @@ service.interceptors.response.use(
if (data instanceof Blob || Base64.isValid(data)) { if (data instanceof Blob || Base64.isValid(data)) {
return Promise.resolve(data); return Promise.resolve(data);
} }
// 判断是服务端还是客户端
const isServer = typeof window === 'undefined';
if (isServer) {
// 如果是服务端
// eslint-disable-next-line no-alert
window.confirm(data.message || '啊呀,出错了');
// eslint-disable-next-line no-restricted-globals
history.back();
} else {
// 如果还有其他报错那么就弹出报错信息(不需要对每个接口的报错做单独判断) // 如果还有其他报错那么就弹出报错信息(不需要对每个接口的报错做单独判断)
message.error(data.message || '啊呀,出错了').then(); message.error(data.message || '啊呀,出错了').then();
}
return Promise.reject(data); return Promise.reject(data);
} }
// 网络错误或链接超时 // 网络错误或链接超时
......
import React from 'react';
import LayoutView from '@/components/layout';
import { PageNotFoundStyle } from './styled';
const PageNotFoundView = () => {
return (
<LayoutView>
<PageNotFoundStyle>
<div className="tips">
<svg className="me404" viewBox="0 0 1000 480">
<path
id="cloud"
className="st0"
d="M658.4,345.2c-10.9,0-19.7-8.8-19.7-19.7c0-10.9,8.8-19.7,19.7-19.7h50.1c9.9-1.5,17.5-10,17.5-20.3
c0-11.4-9.2-20.6-20.6-20.6v-0.2H633c-11.4,0-20.6-6.7-20.6-18.1c0-11.4,9.2-19.3,20.6-19.3h70.4l2-0.2c7.3-3.1,12.5-11,12.5-19.5
c0-8.5-4.2-16.7-11.4-19.2l-2.5-0.3h-11.3c-11.9,0-21.6-8.9-21.6-19.9c0-11,9.7-19.9,21.6-19.9h15.8l1.4-0.3
c8.6-2.5,14.8-10.1,14.8-19.5c0-11.4-9.2-20.6-20.6-20.6h-1.2h-69.2H382.5c-19.8-0.9-19.9-15.9-19.8-17.8c0-0.1,0-0.1,0-0.2
c0-9.9-8.1-18-18-18h-93.5c-9.9,0-18,8.1-18,18c0,9.4,7.2,17.1,16.3,17.9h9.3c0.2,0,0,0,0.6,0l0.5,0l0.4,0l0.2,0
c10.1,0.9,18,9.3,18,19.6c0,10.9-8.8,19.7-19.7,19.7h-70.7c-11.3,0-20.5,9.2-20.5,20.6c0,11.3,9.1,20.5,20.4,20.6h48.8
c10.3,0,18.7,8.4,18.7,18.7c0,10.3-8.4,18.7-18.7,18.7h-23.2c-11.3,0.1-20.4,9.2-20.4,20.6c0,11.3,9.2,20.5,20.5,20.6h6.3
c10.7,0,19.3,8.7,19.3,19.3c0,10.7-7.8,19.3-18.4,19.3l-1.5,0l-2.8,0.4c-7.3,3.1-11.8,11-11.5,18.9c0.3,8.5,4.2,16.5,11.7,19.6
c1.1,0.7,3.4,0.9,4.4,0.9h4.5H296h19.7c3.9,0.5,8.2,4.2,7.4,10.4c0,0.4,0,0.8,0.1,1.1c0,0.5-0.1,1-0.1,1.5c0,9.7,7.9,17.5,17.5,17.5
h60.2c9.7,0,17.5-7.9,17.5-17.5c0-0.4,0-0.8-0.1-1.2c0.1-0.3,0-0.7,0.1-1.1c0.3-6.5,6.4-10.9,10.6-10.8h110.1
c8.5,0,16.9,6.6,16.9,14.8c0,8.2,6.6,14.8,14.8,14.8h92.6c8.2,0,14.8-6.6,14.8-14.8c0-8.2-6.6-14.8-14.8-14.8 M332.8,187.1h-21.2
c-11.4,0-20.6-9.2-20.6-20.6c0-11.4,9.2-20.6,20.6-20.6h21.2c11.4,0,20.6,9.2,20.6,20.6C353.3,177.9,344.1,187.1,332.8,187.1z"
/>
<g id="triforce">
<path
id="zelda_stroke"
className="st1"
d="M138.4,59.5h36.9l-18.5,32L138.4,59.5z M193.8,91.5l18.5-32h-36.9L193.8,91.5z M175.4,123.5
l18.5-32h-36.9L175.4,123.5z"
/>
<path
id="zelda_dark_shadow"
className="st0"
d="M156.9,91.5l-18.5-32l18.5,10.7L156.9,91.5z M193.8,70.2l-18.5-10.7l18.5,32
L193.8,70.2z M175.4,102.2l-18.5-10.7l18.5,32L175.4,102.2z"
/>
<path
id="zelda_light_shadow"
className="st2"
d="M175.4,59.5l-18.5,10.7l-18.5-10.7H175.4z M175.4,59.5l18.5,10.7l18.5-10.7H175.4z
M156.9,91.5l18.5,10.7l18.5-10.7H156.9z"
/>
<path
id="zelda_highlight"
className="st3"
d="M150.6,66.6h12.5l-6.3,10.8L150.6,66.6z M193.8,77.4l6.3-10.8h-12.5L193.8,77.4z
M175.4,109.4l6.3-10.8h-12.5L175.4,109.4z"
/>
</g>
<g id="monkey">
<path
id="foot_back"
className="st4"
d="M187.3,354.5c2.2-4.5,1.6-12.8-3.3-18.5l-9.3,2c2.2,3.5,8.3,7.7,2.3,20.8 c-1.9,4.2-0.8,8.7,4,8.7h22.3c6.5,0,5.3-7.9,2-10.5c-4.2-3.3-10.2-3.6-15.3-1C187.9,357.1,185.3,358.7,187.3,354.5z"
/>
<path
id="foot_front"
className="st5"
d="M166.3,354.5c2.2-4.5,1.6-12.8-3.3-18.5l-9.3,2c2.2,3.5,8.3,7.7,2.3,20.8 c-1.9,4.2-0.8,8.7,4,8.7h22.3c6.5,0,5.3-7.9,2-10.5c-4.2-3.3-10.2-3.6-15.3-1C166.9,357.1,164.3,358.7,166.3,354.5z"
/>
<path
id="body"
className="st5"
d="M199.8,299.3l9-55.5c0,0-2.1-3.6-7.2-7.1c1.4-1.2,2.2-3.1,1.8-5c-0.6-3.1-3.9-5.3-7.5-4.8 c-2.9,0.4-5,2.4-5.4,4.8l0,0c-7.2-1.9-16.5-1.9-29.5,1.6c-1.5-3.1-5.6-5.4-9.3-5.7c-5.5-0.4-9.3,3.7-9.7,9.3 c-0.3,4.4,2.2,8.3,6.1,9.9c-16,25.6-14.6,58.2-11,71.9c4.3,16.1,18.2,21.8,26.3,21.8c13,0,33.8-1.9,37.5-17.7 C202.9,315,202,303.9,199.8,299.3z"
/>
<path
id="rock"
className="st6"
d="M93.4,367.5H89 M104,367.5h144l-11,17.2c-0.9,1.4-2.5,2.3-4.2,2.3H203c-1.6,0-3,0.7-4,2l-40,52"
/>
<path
id="tail"
className="st7"
d="M89,315c2.2-15.2-23-13.2-21.6,4.8c1.7,22.3,24.4,22.1,42.5,9.1c10.8-7.8,15.3-1.8,19.1,1.1 c2.3,1.7,6.7,3.3,11-3"
/>
<path
id="face"
className="st8"
d="M213.7,245.2c0,0-6-2.9-11,0.2c-4.6,2.8-9.4,1.7-14,0c-4.6-1.7-16-5.1-19.2,2.6 c-2,3.8-2.3,9.7,3.8,16.3c-0.9,10.1-2.9,37.9,28.6,34.2c10.1-1.2,24.8-12.7,25.4-18.2s-1.7-7.4-6.5-6.5 c-1.3-6.5-2.3-12.9-10.7-11.8c-3.9,0.2,7.5,0,8.1-7.5C218.6,247.8,213.7,245.2,213.7,245.2z"
/>
<path
id="mouth"
className="st9"
d="M220.6,274.8c0,0-0.3,0.2-0.7,0.5c-0.2,0.2-0.6,0.3-1,0.5c-0.4,0.2-0.9,0.3-1.4,0.5 c-1,0.3-2.1,0.5-3.3,0.6c-1.2,0.2-2.4,0.3-3.7,0.5c-0.6,0.1-1.2,0.2-1.8,0.4c-0.6,0.1-1.1,0.3-1.7,0.5c-0.5,0.2-1,0.4-1.4,0.7 c-0.5,0.2-0.8,0.5-1.2,0.8c-0.4,0.2-0.6,0.6-0.9,0.9c-0.3,0.3-0.4,0.5-0.6,0.7c-0.3,0.4-0.5,0.7-0.5,0.7l0,0.1 c-0.2,0.2-0.5,0.3-0.7,0.1c-0.2-0.1-0.3-0.4-0.2-0.7c0,0,0.2-0.3,0.5-0.8c0.2-0.3,0.3-0.6,0.6-0.9c0.3-0.3,0.5-0.7,0.9-1 c0.4-0.3,0.8-0.7,1.3-1c0.5-0.3,1-0.6,1.6-0.9c0.6-0.2,1.2-0.5,1.8-0.7c0.6-0.2,1.3-0.3,1.9-0.5c1.3-0.3,2.5-0.5,3.7-0.7 c1.2-0.2,2.2-0.4,3-0.7c0.4-0.2,0.8-0.3,1.1-0.4c0.3-0.2,0.5-0.2,0.8-0.4c0.5-0.3,0.7-0.5,0.7-0.5c0.5-0.3,1.1-0.2,1.4,0.2 C221.2,273.9,221.1,274.5,220.6,274.8C220.6,274.8,220.6,274.8,220.6,274.8z"
/>
<path
id="nose_hole"
className="st10"
d="M213.2,266.3c0.6,0,1,0.5,0.9,1.1c0,0.6-0.5,1-1.1,0.9c-0.6,0-1-0.5-0.9-1.1
C212.1,266.6,212.6,266.2,213.2,266.3z"
/>
<path
id="nose_hole_1_"
className="st10"
d="M208.1,266.9c0.6,0,1,0.5,0.9,1.1c0,0.6-0.5,1-1.1,0.9c-0.6,0-1-0.5-0.9-1.1
C207.1,267.3,207.6,266.9,208.1,266.9z"
/>
<path
id="monkey-eye-r"
className="st10"
d="M205,253.5c1.1,0.1,1.9,1,1.9,2.1c-0.1,1.1-1,1.9-2.1,1.9c-1.1-0.1-1.9-1-1.9-2.1 C203,254.3,203.9,253.4,205,253.5z"
/>
<path
id="monkey-eye-l"
className="st10"
d="M191.5,254.6c1.4,0.1,2.4,1.3,2.3,2.7c-0.1,1.4-1.3,2.4-2.7,2.3c-1.4-0.1-2.4-1.3-2.3-2.7 C188.9,255.6,190.1,254.5,191.5,254.6z"
/>
<path
id="mongkey_shadow_1_"
className="st0"
d="M209.1,281c0.9-0.9,9.4-2.6,12-3c2.4-0.4-1.6,4.1-5,5S208.2,282,209.1,281z M143.6,237.1c-0.3,3.6,1.8,7,5.2,8.4c0.4,0.2,0.7,0.5,0.8,0.9c0.1,0.4,0.1,0.9-0.2,1.2c-15.1,24.2-14.7,56.3-10.8,70.8 c4,15.2,17.1,20.7,24.8,20.7c8.9,0,16.1-1,21.8-2.9c-67.5,2.2-35-81.7-33.3-87.3c0.2-0.8,1.2-4.4,1-5c-0.6-1.6-3.5-0.2-6-4 c-2.9-4.5,1.2-9.2,2.6-10.6C146.3,230.1,143.9,233,143.6,237.1z M201.7,297.5c7.8-0.9,17.9-8,22.3-13.3
c-27.4,14.7-44.4,3.1-50.1-9.8c0.3,5.9,1.6,12.6,5.9,17.3C184.4,296.7,191.8,298.7,201.7,297.5z M208.6,261.2
c-5.7,0.8-8.6-1.1-11.6,1.8c-2.8,2.7-7.7,4.6-3.8,4.1c3.9-0.6,10.1-3.4,16.8-4.1c0,0,0,0,0,0l-0.5,0c-0.2,0-0.3,0-0.4,0
c-0.5,0-1-0.4-1-0.9C208.2,261.9,208.2,261.5,208.6,261.2z M198.4,300c0-0.1,0-0.1-0.1-0.2c-0.7,0-1.4,0.1-2,0.1
c-7.8,0-13.9-2.3-18-6.8c-7.7-8.4-6.6-22.5-6.1-28.4c-5.6-6.2-5.6-11.5-4.6-15c-2,2.3-4.8,8.5,2.1,16.1c-3.9,6.4-5.4,26.5,9.2,36.2
c7.2,4.8,16.6,5.3,20.8,2.8C199.5,302.9,199,301.2,198.4,300z"
/>
<path
id="belly"
className="st11"
d="M189.1,304c6.2,3,8.1,11.5,5.9,19c-2.3,7.4-9.8,10-16,7c-6.2-3-7.6-10.4-5.3-17.8
S182.9,301.1,189.1,304z"
/>
<path
id="belly_button"
className="st9"
d="M191.2,322.3c0-0.1-0.1-0.2-0.2-0.2l-1.9-1.4l1-1.9c0.1-0.1,0.1-0.2,0-0.3
c-0.1-0.2-0.4-0.4-0.7-0.3c-0.2,0-0.4,0.2-0.5,0.3l-0.9,1.7l-1.6-1.2c-0.2-0.1-0.3-0.1-0.5-0.1c-0.4,0.1-0.5,0.4-0.5,0.6
c0,0.1,0.1,0.2,0.2,0.2l1.8,1.3l-1.1,2.1c-0.1,0.1-0.1,0.2,0,0.3c0.1,0.3,0.4,0.4,0.7,0.4c0.2,0,0.3-0.1,0.4-0.3l1-1.9l1.7,1.3
c0.1,0.1,0.3,0.1,0.5,0.1C191.1,322.8,191.3,322.5,191.2,322.3z"
/>
<g id="monkey_arm">
<path
id="monkey-arm"
className="st5"
d="M164.3,344.1c-0.9-0.3-1.8-0.2-2.5,0.2c-0.3-0.2-0.6-0.3-0.9-0.4c-0.8-0.3-1.5-0.5-2.3-0.5
c-0.1,0-0.2-0.1-0.3-0.3c-2.4-11.4-1.1-27.6,0.3-43.8c0-0.1,1.2-5.7-2.6-7.2c-5.2-2.1-5.5,2.5-5.5,2.7c-0.5,4.8-3.6,39,1.1,51.4
c0,0.1,0,0.2,0,0.3c-0.4,0.5-0.7,1-0.9,1.7c-1.5,3.9,0.7,8.3,4.8,9.9c4.1,1.6,8.7-0.3,10.1-4.2c0.5-1.3,0.6-2.7,0.3-4
c0-0.1,0-0.2,0.1-0.2c0.5-0.7,0.9-1.6,0.5-2.9C166.2,345.5,165.4,344.4,164.3,344.1z"
/>
<g id="armpit">
<path
className="st12"
d="M165,296c0-4.3-1.8-10.8-6-12c-12.5-3.5-12.4,11.1-12.4,11.1s10.8-1.4,16.7,9.6
C163.3,304.6,165,300.3,165,296z"
/>
<path
className="st11"
d="M146.6,295.1c0,0,10.8-1.4,16.7,9.6"
/>
<path className="st11" d="M144.4,296c0,0,8.7-6.6,19.2,0" />
</g>
</g>
</g>
<g id="tetris-path">
<g id="tetris">
<path
id="tetris_stroke"
className="st13"
d="M487.5,323.5h34v34h-34V323.5z M487.5,357.5h34v34h-34V357.5z M521.5,357.5h34v34h-34 V357.5z M555.5,357.5h34v34h-34V357.5z M555.5,391.5h34v34h-34V391.5z"
/>
<path
id="tetris_dark_shadow"
className="st2"
d="M489,356l6-6c0.9-0.9,2.2-1.5,3.5-1.5h13.9l7.5,7.5H489z M489,390l6-6 c0.9-0.9,2.2-1.5,3.5-1.5h13.9l7.5,7.5H489z M523,390l6-6c0.9-0.9,2.2-1.5,3.5-1.5h13.9l7.5,7.5H523z M557,390l6-6 c0.9-0.9,2.2-1.5,3.5-1.5h13.9l7.5,7.5H557z M557,424l6-6c0.9-0.9,2.2-1.5,3.5-1.5h13.9l7.5,7.5H557z"
/>
<path
id="tetris_light_shadow"
className="st0"
d="M520,356l-8-7.5v-13.9c0-1.4,0.6-2.7,1.6-3.6l6.4-6V356z M520,390l-8-7.5v-13.9 c0-1.4,0.6-2.7,1.6-3.6l6.4-6V390z M554,390l-8-7.5v-13.9c0-1.4,0.6-2.7,1.6-3.6l6.4-6V390z M588,390l-8-7.5v-13.9 c0-1.4,0.6-2.7,1.6-3.6l6.4-6V390z M588,424l-8-7.5v-13.9c0-1.4,0.6-2.7,1.6-3.6l6.4-6V424z"
/>
</g>
</g>
<g id="stars">
<path
id="star1"
className="st5"
d="M652.6,332.5c-5.3,3.1-12.1,1.2-15.1-4.1l-1.4-2.4l1.4,2.4c3.1,5.3,1.2,12.1-4.1,15.1l-2.4,1.4 l2.4-1.4c5.3-3.1,12.1-1.2,15.1,4.1l1.4,2.4l-1.4-2.4C645.5,342.3,647.3,335.5,652.6,332.5l2.4-1.4L652.6,332.5z"
/>
<path
id="star2"
className="st5"
d="M503.4,73.7c-8,4.6-18.1,1.9-22.7-6.1l-2.1-3.6l2.1,3.6c4.6,8,1.9,18.1-6.1,22.7l-3.6,2.1l3.6-2.1 c8-4.6,18.1-1.9,22.7,6.1l2.1,3.6l-2.1-3.6C492.7,88.4,495.4,78.3,503.4,73.7l3.6-2.1L503.4,73.7z"
/>
<path
id="star3"
className="st5"
d="M330.4,335.7c-8,4.6-18.1,1.9-22.7-6.1l-2.1-3.6l2.1,3.6c4.6,8,1.9,18.1-6.1,22.7l-3.6,2.1 l3.6-2.1c8-4.6,18.1-1.9,22.7,6.1l2.1,3.6l-2.1-3.6C319.7,350.4,322.4,340.3,330.4,335.7l3.6-2.1L330.4,335.7z"
/>
<path
id="star4"
className="st5"
d="M135.6,176.5c-5.3,3.1-12.1,1.2-15.1-4.1l-1.4-2.4l1.4,2.4c3.1,5.3,1.2,12.1-4.1,15.1l-2.4,1.4 l2.4-1.4c5.3-3.1,12.1-1.2,15.1,4.1l1.4,2.4l-1.4-2.4C128.5,186.3,130.3,179.5,135.6,176.5l2.4-1.4L135.6,176.5z"
/>
</g>
<g id="moon">
<path
id="moon_body"
className="st5"
d="M641,34c26,0,47,21,47,47s-21,47-47,47s-47-21-47-47S615,34,641,34z"
/>
<path
id="moon_shades"
className="st0"
d="M622.5,55.9c1.3,2.3,0,5.8-3.1,7.7c-3,2-6.6,1.7-7.9-0.6c-1.3-2.3,0-5.8,3.1-7.7
C617.6,53.3,621.1,53.6,622.5,55.9z M628.8,94.1c-4.1-6.1-11.6-9-16.7-6.4c-5.1,2.6-5.9,9.6-1.7,15.7c4.1,6.1,11.6,9,16.7,6.4
C632.2,107.2,632.9,100.2,628.8,94.1z M644.5,109c-3.6,0-6.5,2.2-6.5,5s2.9,5,6.5,5s6.5-2.2,6.5-5S648.1,109,644.5,109z
M645.7,95.8c-2.3-1.2-5-0.5-6,1.4c-1,2,0,4.5,2.3,5.7c2.3,1.2,5,0.5,6-1.4C649,99.6,648,97,645.7,95.8z M686.5,81
c0-25.1-20.4-45.5-45.5-45.5c-16.1,0-30.2,8.4-38.3,21c7.9-5.9,17.7-9.5,28.3-9.5c26,0,47,21,47,47c0,6.3-1.3,12.3-3.5,17.8
C681.9,103.6,686.5,92.8,686.5,81z"
/>
</g>
<g id="number_4">
<path
id="number_4_outline"
className="st1"
d="M379.5,235.5c0-4.9-3.9-9.1-8.7-9.1h-11.4v-72.5c0-9.1-8.5-15.7-17.6-15
c-6,0-11.8,3.1-15.1,8l-52.7,79.8c-1.2,2.1-2.1,4.5-2.1,6.6c0,6.6,5,11.1,10.3,11.1H339v24.3c0,5.6,4.3,10.1,9.9,10.1
c6,0,10.5-4.5,10.5-10.1v-24.3h11.4C375.6,244.3,379.5,240.4,379.5,235.5z M339,226.4h-45.5l45.5-67.8V226.4z"
/>
<path
id="number_4_inner_lines"
className="st14"
d="M349,158v109.2 M345.9,147c-5.6,0-10.9,2.8-14,7.2l-47.1,69.5
c-1.2,1.9-3.3,4.3-3.6,5.8c-0.8,4.6,2.3,5.5,7.3,5.5H340 M359.5,235H379 M342,232l-4,7 M345,232l-4,7 M358,232l-4,7 M361,232l-4,7"
/>
<path
id="number_4_dots"
className="st10"
d="M349,266c1.6,0,2.9,1.3,2.9,2.9c0,1.6-1.3,2.9-2.9,2.9c-1.6,0-2.9-1.3-2.9-2.9
C346.1,267.3,347.4,266,349,266z M349,155.1c1.6,0,2.9,1.3,2.9,2.9s-1.3,2.9-2.9,2.9c-1.6,0-2.9-1.3-2.9-2.9S347.4,155.1,349,155.1
z M344.4,144.6c1.6,0,2.9,1.3,2.9,2.9c0,1.6-1.3,2.9-2.9,2.9c-1.6,0-2.9-1.3-2.9-2.9C341.4,145.9,342.7,144.6,344.4,144.6z"
/>
</g>
<g id="number_4_2">
<path
id="number_4_outline_2"
className="st1"
d="M627,235.5c0-4.9-3.9-9.1-8.7-9.1h-11.4v-72.5c0-9.1-8.5-15.7-17.6-15
c-6,0-11.8,3.1-15.1,8l-52.7,79.8c-1.2,2.1-2.1,4.5-2.1,6.6c0,6.6,5,11.1,10.3,11.1h56.7v24.3c0,5.6,4.3,10.1,9.9,10.1
c6,0,10.5-4.5,10.5-10.1v-24.3h11.4C623.1,244.3,627,240.4,627,235.5z M586.5,226.4H541l45.5-67.8V226.4z"
/>
<path
id="number_4_inner_lines_2"
className="st14"
d="M596.5,158v109.2 M593.3,147c-5.6,0-10.9,2.8-14,7.2l-47.1,69.5
c-1.2,1.9-3.3,4.3-3.6,5.8c-0.8,4.6,2.3,5.5,7.3,5.5h51.5 M607,235h19.5 M589.5,232l-4,7 M592.5,232l-4,7 M605.5,232l-4,7
M608.5,232l-4,7"
/>
<path
id="number_4_dots_2"
className="st10"
d="M596.5,266c1.6,0,2.9,1.3,2.9,2.9c0,1.6-1.3,2.9-2.9,2.9c-1.6,0-2.9-1.3-2.9-2.9
C593.6,267.3,594.9,266,596.5,266z M596.5,155.1c1.6,0,2.9,1.3,2.9,2.9s-1.3,2.9-2.9,2.9c-1.6,0-2.9-1.3-2.9-2.9
S594.9,155.1,596.5,155.1z M591.8,144.6c1.6,0,2.9,1.3,2.9,2.9c0,1.6-1.3,2.9-2.9,2.9c-1.6,0-2.9-1.3-2.9-2.9
C588.9,145.9,590.2,144.6,591.8,144.6z"
/>
</g>
<g id="number_0">
<path
id="number_0_outline"
className="st1"
d="M502,208.9c0-34-15.9-70.9-54-70.9c-38.3,0-54,36.9-54,70.9s15.7,71.1,54,71.1
C486.1,280,502,242.9,502,208.9z M481.1,208.9c0,26.8-8.7,53-33.1,53c-24.6,0-33.1-26.2-33.1-53c0-26.8,8.5-52.8,33.1-52.8
C472.4,156.1,481.1,182.1,481.1,208.9z"
/>
<path
id="number_0_inner_lines"
className="st15"
d="M487.2,175.7c-6.7-16.8-19.3-29.4-39.2-29.4c-32,0-45.1,32.5-45.1,62.4
s13.1,62.6,45.1,62.6c31.8,0,44.1-32.6,44.1-62.6 M487.5,172c3,0,5.5,2.5,5.5,5.5c0,3-2.5,5.5-5.5,5.5c-3,0-5.5-2.5-5.5-5.5
C482,174.5,484.5,172,487.5,172z M492.5,202c3,0,5.5,2.5,5.5,5.5c0,3-2.5,5.5-5.5,5.5c-3,0-5.5-2.5-5.5-5.5
C487,204.5,489.5,202,492.5,202z"
/>
<path
id="number_0_dots"
className="st10"
d="M492.5,205c1.4,0,2.5,1.1,2.5,2.5c0,1.4-1.1,2.5-2.5,2.5c-1.4,0-2.5-1.1-2.5-2.5
C490,206.1,491.1,205,492.5,205z M487.5,175c1.4,0,2.5,1.1,2.5,2.5c0,1.4-1.1,2.5-2.5,2.5c-1.4,0-2.5-1.1-2.5-2.5
C485,176.1,486.1,175,487.5,175z M448.1,143.4c1.6,0,2.9,1.3,2.9,2.9c0,1.6-1.3,2.9-2.9,2.9c-1.6,0-2.9-1.3-2.9-2.9
C445.1,144.7,446.4,143.4,448.1,143.4z M448.1,268.3c1.6,0,2.9,1.3,2.9,2.9c0,1.6-1.3,2.9-2.9,2.9c-1.6,0-2.9-1.3-2.9-2.9
C445.1,269.6,446.4,268.3,448.1,268.3z"
/>
</g>
<g id="sword-path">
<g id="sword">
<path
id="sword_handle"
className="st5"
d="M444.6,196.6l0.6-0.8c1.5-2,1.8-4.3,3.8-2.8l8.9,6.8c2,1.5,2.4,4.3,0.9,6.3l-0.6,0.8 c-1.5,2-4.3,2.4-6.3,0.9L443,201C441,199.5,443.1,198.5,444.6,196.6z"
/>
<path
id="sword_handle_line"
className="st14"
d="M453.9,197c2,1.5,2.4,4.3,0.9,6.3l-0.6,0.8c-1.5,2-4.3,2.4-6.3,0.9"
/>
<path
id="sword_hilt"
className="st5"
d="M432.5,197.1l10.6-13.9c1.6-2.2,4.7-2.6,6.9-0.9c2.2,1.6,2.6,4.7,0.9,6.9l-10.6,13.9 c-1.6,2.2-4.7,2.6-6.9,0.9C431.2,202.4,430.8,199.3,432.5,197.1z"
/>
<polygon
id="sword_blade"
className="st1"
points="437,199 446,187.3 387.3,138.9 366.3,136.7 372.2,154 "
/>
<polygon
id="sword_blade_shadow"
className="st0"
points="436.7,197 440.3,192.3 369,138.5 368.5,138.4 373.5,153 "
/>
</g>
</g>
</svg>
</div>
</PageNotFoundStyle>
</LayoutView>
);
};
export default PageNotFoundView;
import styled from 'styled-components';
export default function Index() {
return <></>;
}
export const PageNotFoundStyle = styled.div`
.tips {
font-size: 50px;
text-align: center;
margin-top: 10px;
color: #000;
font-weight: 600;
.me404 {
width: 1000px;
height: 480px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -400px;
margin-top: -240px;
}
.st0 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #e8ebed;
}
.st1 {
fill: #ffffff;
stroke: #89949b;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st2 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #dbdfe1;
}
.st3 {
fill: #ffffff;
}
.st4 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #e8ebed;
stroke: #89949b;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st5 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #ffffff;
stroke: #89949b;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st6 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: none;
stroke: #89949b;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st7 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: none;
stroke: #89949b;
stroke-width: 4;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st8 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #ffffff;
stroke: #89949b;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st9 {
fill: #89949b;
}
.st10 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #89949b;
}
.st11 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: none;
stroke: #89949b;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st12 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #ffffff;
}
.st13 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #ffffff;
stroke: #8894a0;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st14 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: none;
stroke: #89949b;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st15 {
fill: none;
stroke: #89949b;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
#monkey-eye-l {
transform-origin: 191px 257px;
animation: blink-l 12s infinite ease-in-out;
}
#monkey-eye-r {
transform-origin: 205px 256px;
animation: blink-r 12s infinite ease-in-out;
}
@keyframes blink-l {
0% {
transform: rotateX(0deg);
}
2% {
transform: rotateX(80deg);
}
4%,
20% {
transform: rotateX(0deg);
}
22% {
transform: rotateX(80deg);
}
24%,
30% {
transform: rotateX(0deg);
}
32% {
transform: rotateX(80deg);
}
34%,
70% {
transform: rotateX(0deg);
}
72% {
transform: rotateX(80deg);
}
74%,
100% {
transform: rotateX(0deg);
}
}
@keyframes blink-r {
0% {
transform: rotateX(0deg);
}
2% {
transform: rotateX(80deg);
}
4%,
30% {
transform: rotateX(0deg);
}
32% {
transform: rotateX(80deg);
}
34%,
50% {
transform: rotateX(0deg);
}
52% {
transform: rotateX(80deg);
}
54%,
100% {
transform: rotateX(0deg);
}
}
}
`;
import React from 'react';
import { Breadcrumb } from 'antd';
import { useRouter } from 'next/router';
import styled from 'styled-components';
const BreadcrumbWrap = styled.div`
position: relative;
width: 100%;
display: flex;
align-content: center;
justify-content: flex-start;
.title {
font-size: 13px;
font-weight: 400;
color: #666666;
margin-bottom: 0.75rem;
}
`;
const BreadcrumbView: React.FC = () => {
// 路由钩子
const router = useRouter();
// 路由对应列表
const routerList = [
{ name: '云享商城', path: 'mall' },
{ name: '行业服务', path: 'service' },
{ name: '设备租赁', path: 'rent' },
{ name: '执照培训', path: 'train' },
{ name: '飞手约单', path: 'flyer' },
{ name: '商品详情', path: 'product' },
{ name: '购物车', path: 'cart' },
{ name: '提交订单', path: 'submit' },
{ name: '服务详情', path: 'detail' },
];
// 转换路由
const getCurrentRouter = () => {
const arr = router?.pathname
?.split('/')
?.map((i, j) => {
const href = `/${router?.pathname
.split('/')
.slice(1, j + 1)
.join('/')}`;
const title = routerList?.find((n) => n.path === i)?.name;
return { title, href };
})
?.filter((i) => i.title); // 过滤掉没有title的项
// 最后一项不跳转
arr[arr.length - 1] = { title: arr[arr.length - 1]?.title, href: '' };
return arr;
};
return (
<BreadcrumbWrap>
<div className="title">您的位置:</div>
<Breadcrumb
separator=">"
items={[
{
title: '首页',
href: '/',
},
...getCurrentRouter(),
]}
/>
</BreadcrumbWrap>
);
};
export default BreadcrumbView;
import React, { useEffect, useState } from 'react';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import styled from 'styled-components';
// 分类列表类型
export type CategoryType = {
value: number;
label: string;
children?: CategoryType;
}[];
const CategorySelectView: React.FC<{
list: CategoryType;
onMain?: (arr: number) => void;
onSecond?: (arr: number[]) => void;
onSelect?: ({ main, second }: { main?: number; second?: number[] }) => void;
isMultiple?: boolean;
allText?: string;
}> = ({ list, onMain, onSecond, onSelect, allText, isMultiple }) => {
CategorySelectView.defaultProps = { allText: '全部商品', isMultiple: true };
// 图标样式
const IconStyle = {
fontSize: '8px',
color: 'rgba(153,153,153,0.5)',
transform: 'scaleX(1.2)',
};
// 路由钩子
const router = useRouter();
// 当前选择索引
const [currentIndex, setCurrentIndex] = useState<number>(0);
// 二级索引列表
const [secondIndex, setSecondIndex] = useState<number[]>([]);
// 二级索引选择
const handleSecondary = (index: number) => {
const has = secondIndex?.some((i) => i === index);
const arr = has
? secondIndex?.filter((i) => i !== index)
: [...secondIndex, index];
const brr = isMultiple ? arr : [index];
setSecondIndex(brr);
// 二级筛选列表
const second =
brr?.length !== 0
? brr?.map((i) => list[currentIndex]?.children?.[i]?.value || 0)
: list[currentIndex]?.children?.map((i) => i?.value) || [];
// 二级筛选回调
onSecond?.(second);
// 统一返回筛选
onSelect?.({
main: list[currentIndex]?.value,
second,
});
};
// 主索引选择
const handleMain = (index: number) => {
setCurrentIndex(index);
setSecondIndex([]);
// 切换分类
onMain?.(list[index]?.value);
// 清空二级分类,并返回全部二级分类
onSecond?.(list[index]?.children?.map((i) => i?.value) || []);
// 统一返回筛选
onSelect?.({
main: list[index]?.value,
second: list[index]?.children?.map((i) => i?.value) || [],
});
};
// 选择全部商品
const handleAll = () => {
setSecondIndex([]);
onSecond?.(list[currentIndex]?.children?.map((i) => i?.value) || []);
// 统一返回筛选
onSelect?.({
main: list[currentIndex]?.value,
second: list[currentIndex]?.children?.map((i) => i?.value) || [],
});
};
// 组件挂载
useEffect(() => {
if (!list?.length) return;
// 一级分类id
const mainID = Number(router?.query?.main);
// 二级分类id
const secondID = Number(router?.query?.second);
// 如果路由里面有一级分类id,则初始化一级分类
if (mainID) {
const index = list?.findIndex((i) => i?.value === mainID) || 0;
setCurrentIndex(index);
onMain?.(list[index]?.value);
if (!secondID)
onSecond?.(list[index]?.children?.map((i) => i?.value) || []);
}
// 如果路由里面有二级分类id,则初始化二级分类
if (secondID) {
const children = list?.find((i) => i?.value === mainID)?.children;
const index = children?.findIndex((i) => i?.value === secondID) || 0;
setSecondIndex([index]);
onSecond?.(children?.[index]?.value ? [children?.[index]?.value] : []);
}
// 如果任何id都没有
if (!router?.query?.main && !router?.query?.second) {
// 初始化返回一级分类
onMain?.(list[currentIndex]?.value);
// 初始化全部二级分类
onSecond?.(list[currentIndex]?.children?.map((i) => i?.value) || []);
}
// 统一返回筛选
onSelect?.({
main: mainID || list[currentIndex]?.value,
second: secondID
? [secondID]
: list[currentIndex]?.children?.map((i) => i?.value) || [],
});
}, [list, router]);
return (
<CategorySelectWrap>
<div className="category-select flex-start">
<div className="select-item">类别:</div>
{list?.map((i, j) => (
<div
className={`select-item ${currentIndex === j && 'item-active'}`}
key={j}
onClick={() => handleMain(j)}
>
<div className="text">{i.label}</div>
{currentIndex === j ? (
<UpOutlined style={IconStyle} />
) : (
<DownOutlined style={IconStyle} />
)}
</div>
))}
</div>
<div className="category-list flex-start">
<div
className={`list-item ${secondIndex?.length === 0 && 'item-active'}`}
onClick={handleAll}
>
{allText || '全部商品'}
</div>
{list[currentIndex]?.children?.map((i, j) => (
<div
className={`list-item ${secondIndex?.includes(j) && 'item-active'}`}
key={j}
onClick={() => handleSecondary(j)}
>
{i.label}
</div>
))}
</div>
</CategorySelectWrap>
);
};
export default CategorySelectView;
const CategorySelectWrap = styled.div`
position: relative;
width: 100%;
min-height: 2rem;
border: 1px solid #ebebeb;
margin-bottom: 1rem;
.category-select {
position: relative;
width: 100%;
min-height: 2rem;
flex-wrap: wrap;
border-bottom: 1px solid #ebebeb;
.select-item {
position: relative;
box-sizing: border-box;
color: #333333;
padding: 0 1.25rem;
min-height: 2rem;
line-height: 2rem;
display: flex;
align-items: center;
justify-content: center;
&:first-child {
color: #999999;
}
&:not(:first-child):active,
&:not(:first-child):hover {
background: #ececec;
}
&:not(:first-child) {
cursor: pointer;
}
.text {
margin-right: 0.25rem;
}
}
.item-active {
background: #ececec;
}
}
.category-list {
position: relative;
width: 100%;
min-height: 3rem;
flex-wrap: wrap;
box-sizing: border-box;
padding: 0 0.5rem;
.list-item {
position: relative;
min-height: 1.68rem;
line-height: 1.68rem;
padding: 0 0.5rem;
cursor: pointer;
margin: 0.25rem 0.5rem 0.25rem 0;
}
.item-active {
color: #fff;
background: #ff552d;
border-radius: 4px;
}
}
`;
...@@ -4,6 +4,7 @@ import styled from 'styled-components'; ...@@ -4,6 +4,7 @@ import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface'; import { InterListType } from '@/api/interface';
import { ListCompanyInfoByCoopIdType } from '@/api/interface/home'; import { ListCompanyInfoByCoopIdType } from '@/api/interface/home';
import QrcodePopover from '@/components/qrcodePopover';
// 列表类型 // 列表类型
type ListType = InterListType<ListCompanyInfoByCoopIdType>; type ListType = InterListType<ListCompanyInfoByCoopIdType>;
...@@ -34,6 +35,7 @@ const HomeBrandView = () => { ...@@ -34,6 +35,7 @@ const HomeBrandView = () => {
}, []); }, []);
return ( return (
<HomeBrandWrap> <HomeBrandWrap>
<QrcodePopover path={'page-product/product-company/index'}>
<div className="brand-item"> <div className="brand-item">
<div className="item-icon"> <div className="item-icon">
<img <img
...@@ -43,8 +45,14 @@ const HomeBrandView = () => { ...@@ -43,8 +45,14 @@ const HomeBrandView = () => {
/> />
</div> </div>
</div> </div>
</QrcodePopover>
{companyList?.map((i, j) => ( {companyList?.map((i, j) => (
<div key={j} className="brand-item"> <QrcodePopover
key={j}
path={'page-product/product-store/index'}
scene={`id=${Number(i?.backUserAccountId)}`}
>
<div className="brand-item">
<div className="item-logo"> <div className="item-logo">
<img src={i.brandLogo} alt={i.brandName} className="image" /> <img src={i.brandLogo} alt={i.brandName} className="image" />
</div> </div>
...@@ -52,6 +60,7 @@ const HomeBrandView = () => { ...@@ -52,6 +60,7 @@ const HomeBrandView = () => {
{i.brandName || i.companyName} {i.brandName || i.companyName}
</Button> </Button>
</div> </div>
</QrcodePopover>
))} ))}
</HomeBrandWrap> </HomeBrandWrap>
); );
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import MapContainer from '@/components/map'; import MapContainer from '@/components/map';
import { RootState } from '@/store';
import { AddressState } from '@/store/module/address';
const HomeMapView = () => { const HomeMapView = () => {
// 选择索引 // 选择索引
...@@ -18,6 +21,10 @@ const HomeMapView = () => { ...@@ -18,6 +21,10 @@ const HomeMapView = () => {
setCurrentIndex(index); setCurrentIndex(index);
getServiceBitmap().then(); getServiceBitmap().then();
}; };
// address
const address = useSelector(
(state: RootState) => state.address,
) as AddressState;
// 地图网点列表 // 地图网点列表
const [mapMarkerList, setMapMarkerList] = useState< const [mapMarkerList, setMapMarkerList] = useState<
{ lat: number; lon: number; name: string }[] { lat: number; lon: number; name: string }[]
...@@ -46,7 +53,10 @@ const HomeMapView = () => { ...@@ -46,7 +53,10 @@ const HomeMapView = () => {
}, []); }, []);
return ( return (
<HomeMapWrap> <HomeMapWrap>
<MapContainer list={mapMarkerList} /> <MapContainer
list={mapMarkerList}
center={[address?.longitude, address?.latitude]}
/>
<div className="map-wrap flex-around"> <div className="map-wrap flex-around">
{networkTypeList?.map((i, j) => ( {networkTypeList?.map((i, j) => (
<div <div
......
...@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; ...@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { RightOutlined } from '@ant-design/icons'; import { RightOutlined } from '@ant-design/icons';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import QrcodePopover from '@/components/qrcodePopover';
const HomeNewsView = () => { const HomeNewsView = () => {
// 帖子类型 // 帖子类型
...@@ -114,10 +115,12 @@ const HomeNewsView = () => { ...@@ -114,10 +115,12 @@ const HomeNewsView = () => {
</div> </div>
))} ))}
</div> </div>
<QrcodePopover path={'page-home/news-list/index'}>
<div className="head-more flex-between"> <div className="head-more flex-between">
<div className="more-text">更多</div> <div className="more-text">更多</div>
<RightOutlined style={{ fontSize: 10, color: '#33333380' }} /> <RightOutlined style={{ fontSize: 10, color: '#33333380' }} />
</div> </div>
</QrcodePopover>
<img <img
className="head-bg" className="head-bg"
src="https://pad-video-x.oss-cn-shenzhen.aliyuncs.com/file/sharefly-web-news.png" src="https://pad-video-x.oss-cn-shenzhen.aliyuncs.com/file/sharefly-web-news.png"
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
...@@ -8,6 +9,8 @@ import { RecommendGoodsType } from '@/api/interface/home'; ...@@ -8,6 +9,8 @@ import { RecommendGoodsType } from '@/api/interface/home';
type ListType = InterDataType<RecommendGoodsType>; type ListType = InterDataType<RecommendGoodsType>;
const HomeProductView = () => { const HomeProductView = () => {
// 路由钩子
const router = useRouter();
// 推荐商品列表 // 推荐商品列表
const [recommendGoodsList, setRecommendGoodsList] = useState< const [recommendGoodsList, setRecommendGoodsList] = useState<
ListType[0]['mallGoodsList'] ListType[0]['mallGoodsList']
...@@ -33,6 +36,10 @@ const HomeProductView = () => { ...@@ -33,6 +36,10 @@ const HomeProductView = () => {
).salePrice || 0 ).salePrice || 0
); );
}; };
// 跳转详情
const handleDetail = (item: ListType[0]['mallGoodsList'][0]) => {
router.push(`/mall/product/${item.id}`).then();
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getRecommendGoodsList().then(); getRecommendGoodsList().then();
...@@ -40,7 +47,11 @@ const HomeProductView = () => { ...@@ -40,7 +47,11 @@ const HomeProductView = () => {
return ( return (
<HomeProductWrap> <HomeProductWrap>
{recommendGoodsList?.map((i, j) => ( {recommendGoodsList?.map((i, j) => (
<div className="product-item flex-start" key={j}> <div
className="product-item flex-start"
key={j}
onClick={() => handleDetail(i)}
>
<img <img
className="item-image" className="item-image"
src={`${i.resourcesList[0].url}?x-oss-process=image/quality,q_25`} src={`${i.resourcesList[0].url}?x-oss-process=image/quality,q_25`}
......
import React from 'react'; import React from 'react';
import { Input } from 'antd'; import { Input } from 'antd';
import { HomeSearchWrap } from '@/pages/home/comp/home-search/styled'; import { HomeSearchWrap } from './styled';
const HomeSearchView = () => { const HomeSearchView = () => {
return ( return (
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface'; import { InterListType } from '@/api/interface';
...@@ -8,6 +9,8 @@ import { IndustryListPagesType } from '@/api/interface/home'; ...@@ -8,6 +9,8 @@ import { IndustryListPagesType } from '@/api/interface/home';
type ListType = InterListType<IndustryListPagesType>; type ListType = InterListType<IndustryListPagesType>;
const HomeServiceView = () => { const HomeServiceView = () => {
// 路由钩子
const router = useRouter();
// 刷新子组件 // 刷新子组件
const [refresh, setRefresh] = useState<boolean>(true); const [refresh, setRefresh] = useState<boolean>(true);
// 服务列表 // 服务列表
...@@ -33,6 +36,10 @@ const HomeServiceView = () => { ...@@ -33,6 +36,10 @@ const HomeServiceView = () => {
setRefresh(true); setRefresh(true);
}, 0); }, 0);
}; };
// 跳转详情
const handleDetail = (item: ListType[0]['inspectionDTOS'][0]) => {
router.push(`/service/${item?.industryTypeId}/${item?.id}`).then();
};
// componentDidMount // componentDidMount
useEffect(() => { useEffect(() => {
getIndustryListPages().then(); getIndustryListPages().then();
...@@ -56,6 +63,7 @@ const HomeServiceView = () => { ...@@ -56,6 +63,7 @@ const HomeServiceView = () => {
<div <div
className="service-item animate__animated animate__fast animate__fadeIn" className="service-item animate__animated animate__fast animate__fadeIn"
key={j} key={j}
onClick={() => handleDetail(i)}
> >
<img <img
className="item-image" className="item-image"
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { AppCategoryInfoType } from '@/api/interface/home'; import { AppCategoryInfoType } from '@/api/interface/home';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型 // 列表类型
type ListType = InterDataType<AppCategoryInfoType>; type ListType = InterDataType<AppCategoryInfoType>;
const TabView01 = () => { const TabView01 = () => {
// 导航钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 列表数据 // 列表数据
const [tabList, setTabList] = useState<ListType>([]); const [tabList, setTabList] = useState<ListType>([]);
// 获取云享商城分类 // 获取云享商城分类
...@@ -21,6 +28,24 @@ const TabView01 = () => { ...@@ -21,6 +28,24 @@ const TabView01 = () => {
// console.log('获取云享商城分类 --->', tabList); // console.log('获取云享商城分类 --->', tabList);
} }
}; };
// 跳转一级分类详情
const handleMain = (i: ListType[0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/mall/${i?.id}`).then();
};
// 跳转二级分类详情
const handleSecond = (i: ListType[0], n: ListType[0]['subDTOList'][0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/mall/${i?.id}/${n?.id}`).then();
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getAppCategoryInfo().then(); getAppCategoryInfo().then();
...@@ -33,11 +58,22 @@ const TabView01 = () => { ...@@ -33,11 +58,22 @@ const TabView01 = () => {
{!!i.icon && ( {!!i.icon && (
<img src={i.icon} alt={i.name} className="title-image" /> <img src={i.icon} alt={i.name} className="title-image" />
)} )}
<div className="title-name">{i.name}</div> <Button
type={'link'}
className="title-name"
onClick={() => handleMain(i)}
>
{i.name}
</Button>
</div> </div>
<div className="tab-list flex-start"> <div className="tab-list flex-start">
{i.subDTOList?.map((n, m) => ( {i.subDTOList?.map((n, m) => (
<Button type={'link'} key={m} className="list-item"> <Button
type={'link'}
key={m}
className="list-item"
onClick={() => handleSecond(i, n)}
>
{n.name} {n.name}
</Button> </Button>
))} ))}
...@@ -70,6 +106,7 @@ const TabViewWrap = styled.div` ...@@ -70,6 +106,7 @@ const TabViewWrap = styled.div`
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 550; font-weight: 550;
color: #333333; color: #333333;
padding: 0;
} }
} }
.tab-list { .tab-list {
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface'; import { InterListType } from '@/api/interface';
import { IndustryListPagesType } from '@/api/interface/home'; import { IndustryListPagesType } from '@/api/interface/home';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型 // 列表类型
type ListType = InterListType<IndustryListPagesType>; type ListType = InterListType<IndustryListPagesType>;
const TabView02 = () => { const TabView02 = () => {
// 导航钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 列表数据 // 列表数据
const [tabList, setTabList] = useState<ListType>([]); const [tabList, setTabList] = useState<ListType>([]);
// 获取分类列表 // 获取分类列表
...@@ -22,6 +29,27 @@ const TabView02 = () => { ...@@ -22,6 +29,27 @@ const TabView02 = () => {
// console.log('获取分类列表 --->', res.result); // console.log('获取分类列表 --->', res.result);
} }
}; };
// 跳转一级分类详情
const handleMain = (i: ListType[0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/service/${i?.id}`).then();
};
// 跳转二级分类详情
const handleSecond = (
i: ListType[0],
n: ListType[0]['inspectionDTOS'][0],
) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/service/${i?.id}/${n?.id}`).then();
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getIndustryListPages().then(); getIndustryListPages().then();
...@@ -34,11 +62,22 @@ const TabView02 = () => { ...@@ -34,11 +62,22 @@ const TabView02 = () => {
{!!i.typeImg && ( {!!i.typeImg && (
<img src={i.typeImg} alt={i.typeName} className="title-image" /> <img src={i.typeImg} alt={i.typeName} className="title-image" />
)} )}
<div className="title-name">{i.typeName}</div> <Button
type={'link'}
className="title-name"
onClick={() => handleMain(i)}
>
{i.typeName}
</Button>
</div> </div>
<div className="tab-list flex-start"> <div className="tab-list flex-start">
{i.inspectionDTOS?.map((n, m) => ( {i.inspectionDTOS?.map((n, m) => (
<Button type={'link'} key={m} className="list-item"> <Button
type={'link'}
key={m}
className="list-item"
onClick={() => handleSecond(i, n)}
>
{n.inspectionName} {n.inspectionName}
</Button> </Button>
))} ))}
...@@ -65,7 +104,6 @@ const TabViewWrap = styled.div` ...@@ -65,7 +104,6 @@ const TabViewWrap = styled.div`
.title-image { .title-image {
width: 1.68rem; width: 1.68rem;
height: 1.68rem; height: 1.68rem;
margin-right: 0.5rem;
} }
.title-name { .title-name {
font-size: 0.75rem; font-size: 0.75rem;
......
...@@ -2,12 +2,12 @@ import React, { useEffect, useState } from 'react'; ...@@ -2,12 +2,12 @@ import React, { useEffect, useState } from 'react';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { ListBannerImgType } from '@/api/interface/home'; import { ListBannerImgType } from '@/api/interface/home';
import TabView01 from '@/pages/home/comp/home-tab/comp/tabView01'; import TabView01 from './comp/tabView01';
import TabView02 from '@/pages/home/comp/home-tab/comp/tabView02'; import TabView02 from './comp/tabView02';
import TabView03 from '@/pages/home/comp/home-tab/comp/tabView03'; import TabView03 from './comp/tabView03';
import TabView04 from '@/pages/home/comp/home-tab/comp/tabView04'; import TabView04 from './comp/tabView04';
import TabView05 from '@/pages/home/comp/home-tab/comp/tabView05'; import TabView05 from './comp/tabView05';
import { HomeTabWrap } from '@/pages/home/comp/home-tab/styled'; import { HomeTabWrap } from './styled';
// 分类列表类型 // 分类列表类型
type CategoryListType = InterDataType<ListBannerImgType>; type CategoryListType = InterDataType<ListBannerImgType>;
...@@ -23,7 +23,7 @@ const HomeTabView = () => { ...@@ -23,7 +23,7 @@ const HomeTabView = () => {
moduleCode: 'HOME_MENU_NEW', moduleCode: 'HOME_MENU_NEW',
}); });
if (res && res.code === '200') { if (res && res.code === '200') {
setCategoryList(res.result || []); setCategoryList(res.result.slice(0, 1) || []);
} }
}; };
// 选择分类 // 选择分类
......
...@@ -5,10 +5,14 @@ import { ...@@ -5,10 +5,14 @@ import {
VerticalAlignTopOutlined, VerticalAlignTopOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { AppPublishListType } from '@/api/interface/home'; import { AppPublishListType } from '@/api/interface/home';
import QrcodePopover from '@/components/qrcodePopover';
import { setGlobalData } from '@/store/module/globalData';
import { UserInfoState } from '@/store/module/userInfo';
import { formatLocationStr } from '@/utils/formatLocation'; import { formatLocationStr } from '@/utils/formatLocation';
import { bigNumberTransform } from '@/utils/money'; import { bigNumberTransform } from '@/utils/money';
...@@ -16,6 +20,10 @@ import { bigNumberTransform } from '@/utils/money'; ...@@ -16,6 +20,10 @@ import { bigNumberTransform } from '@/utils/money';
type ListType = InterDataType<AppPublishListType>; type ListType = InterDataType<AppPublishListType>;
const HomeTaskView = () => { const HomeTaskView = () => {
// store
const dispatch = useDispatch();
// userInfo
const userInfo = useSelector((state: any) => state.userInfo) as UserInfoState;
// 服务标签类型 // 服务标签类型
const serviceTagList = [ const serviceTagList = [
{ {
...@@ -108,6 +116,25 @@ const HomeTaskView = () => { ...@@ -108,6 +116,25 @@ const HomeTaskView = () => {
// formatMoney(props.detail?.orderAmount) // formatMoney(props.detail?.orderAmount)
return i?.orderAmount > 1 ? money : i?.orderAmount; return i?.orderAmount > 1 ? money : i?.orderAmount;
}; };
// 跳转详情
const handleDetail = (item: ListType[0]) => {
// 判断是否登录
if (!userInfo?.id) {
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
return;
}
dispatch(
setGlobalData({
qrcodeModalVisible: true,
qrcodeModalPath: 'page-order/order-flyer/index',
qrcodeModalScene: `id=${Number(item?.id)}`,
}),
);
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getRequirementsListType().then(); getRequirementsListType().then();
...@@ -117,16 +144,18 @@ const HomeTaskView = () => { ...@@ -117,16 +144,18 @@ const HomeTaskView = () => {
<HomeTaskWrap> <HomeTaskWrap>
<div className="task-title"> <div className="task-title">
<div className="title-label">抢单大厅</div> <div className="title-label">抢单大厅</div>
<QrcodePopover path={'page-service/service-task/index'}>
<div className="title-more"> <div className="title-more">
<div className="more-label">更多</div> <div className="more-label">更多</div>
<div className="more-icon"> <div className="more-icon">
<RightOutlined style={{ color: '#998e8b', fontSize: 13 }} /> <RightOutlined style={{ color: '#998e8b', fontSize: 13 }} />
</div> </div>
</div> </div>
</QrcodePopover>
</div> </div>
<div className="task-list"> <div className="task-list">
{requireList?.map((i, j) => ( {requireList?.map((i, j) => (
<div className="list-item" key={j}> <div className="list-item" key={j} onClick={() => handleDetail(i)}>
<div className="item-title"> <div className="item-title">
{i?.orderLevelEnum !== 'REGULAR_ORDER' && ( {i?.orderLevelEnum !== 'REGULAR_ORDER' && (
<div <div
......
import React from 'react'; import React from 'react';
import { RightOutlined } from '@ant-design/icons'; import { RightOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import styled from 'styled-components'; import styled from 'styled-components';
const HomeTitleView = ({ title }: { title: string }) => { const HomeTitleView: React.FC<{ title: string; path?: string }> = ({
title,
path,
}) => {
// 路由钩子
const router = useRouter();
// 跳转详情
const handleDetail = async () => {
if (!path) return;
await router.push(path);
};
return ( return (
<HomeTitleWrap> <HomeTitleWrap onClick={handleDetail}>
<div className="title-text">{title}</div> <div className="title-text">{title}</div>
<div className="title-more flex-start"> <div className="title-more flex-start">
<div className="more-text">更多</div> <div className="more-text">更多</div>
...@@ -27,6 +38,8 @@ export const HomeTitleWrap = styled.div` ...@@ -27,6 +38,8 @@ export const HomeTitleWrap = styled.div`
background-size: cover; background-size: cover;
background-position: center; background-position: center;
z-index: 1; z-index: 1;
cursor: pointer;
user-select: none;
&::after { &::after {
position: absolute; position: absolute;
content: ''; content: '';
......
import React from 'react'; import React, { useEffect } from 'react';
import { Spin } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import { ContentWrap } from '@/components/layout/content/styled'; import { ContentWrap } from '@/components/layout/content/styled';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
const ContentView = ({ children }: { children?: React.ReactNode }) => { const ContentView = ({ children }: { children?: React.ReactNode }) => {
return <ContentWrap>{!!children && children}</ContentWrap>; // store
const dispatch = useDispatch();
// globalData
const globalData = useSelector(
(state: any) => state.globalData,
) as GlobalDataState;
// 页面挂载时自动将加载中移除
useEffect(() => {
dispatch(
setGlobalData({
loadingSpinnerVisible: false,
}),
);
}, []);
return (
<div className={'animate__animated animate__faster animate__fadeIn'}>
<Spin spinning={globalData?.loadingSpinnerVisible} fullscreen />
<ContentWrap>{!!children && children}</ContentWrap>
</div>
);
}; };
export default ContentView; export default ContentView;
...@@ -3,7 +3,7 @@ import styled from 'styled-components'; ...@@ -3,7 +3,7 @@ import styled from 'styled-components';
export const ContentWrap = styled.div` export const ContentWrap = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;
min-height: 100vh; min-height: 78vh;
box-sizing: border-box; box-sizing: border-box;
border-radius: 0; border-radius: 0;
opacity: 1; opacity: 1;
......
import React from 'react'; import React from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import { FooterWrap } from '@/components/layout/footer/styled'; import { FooterWrap } from '@/components/layout/footer/styled';
import QrcodePopover from '@/components/qrcodePopover';
const FooterView = () => { const FooterView = () => {
// 跳转外部网址
const handleLink = (url: string) => {
window.open(url);
};
// 关于列表 // 关于列表
const aboutList = [ const aboutList = [
{ {
...@@ -21,6 +26,22 @@ const FooterView = () => { ...@@ -21,6 +26,22 @@ const FooterView = () => {
alt: '官方社群', alt: '官方社群',
}, },
]; ];
// 集团列表
const groupList = [
{ name: '浙江科比特', url: 'https://www.mmcuav.cn/' },
{
name: '更多产业布局',
url: 'https://www.mmcuav.cn/about-mmc/industrial-layout/',
},
];
// 科比特航空
const mmcList = [
{ name: '产品中心', url: 'https://www.mmcuav.cn/product-center/' },
{ name: '解决方案', url: 'https://www.mmcuav.cn/solution/' },
// { name: '服务平台', url: 'https://www.mmcuav.cn/service-platform/' },
// { name: '技术支持', url: 'https://www.mmcuav.cn/support/' },
{ name: '关于我们', url: 'https://www.mmcuav.cn/about-mmc/' },
];
return ( return (
<FooterWrap> <FooterWrap>
<div className="footer-wrap"> <div className="footer-wrap">
...@@ -33,36 +54,48 @@ const FooterView = () => { ...@@ -33,36 +54,48 @@ const FooterView = () => {
</div> </div>
<div className="start-item"> <div className="start-item">
<div className="item-title">集团公司</div> <div className="item-title">集团公司</div>
<Button type={'link'} className="item-link"> {groupList.map((i, j) => (
浙江科比特 <Button
</Button> type={'link'}
<Button type={'link'} className="item-link"> className="item-link"
深圳科比特 key={j}
</Button> onClick={() => handleLink(i?.url)}
<Button type={'link'} className="item-link"> >
更多集团公司 {i?.name}
</Button> </Button>
))}
</div> </div>
<div className="start-item"> <div className="start-item">
<div className="item-title">合作伙伴</div> <div className="item-title">科比特航空</div>
<Button type={'link'} className="item-link"> {mmcList.map((i, j) => (
浙江科比特 <Button
</Button> type={'link'}
<Button type={'link'} className="item-link"> className="item-link"
深圳科比特 key={j}
</Button> onClick={() => handleLink(i?.url)}
<Button type={'link'} className="item-link"> >
更多合作伙伴 {i?.name}
</Button> </Button>
))}
</div> </div>
<div className="start-item"> <div className="start-item">
<div className="item-title">商家入驻</div> <div className="item-title">加入云享飞</div>
<QrcodePopover
path={'page-identity/identity-list/index'}
placement={'bottomLeft'}
>
<Button type={'link'} className="item-link"> <Button type={'link'} className="item-link">
合作咨询 合作咨询
</Button> </Button>
</QrcodePopover>
<QrcodePopover
path={'page-identity/identity-list/index'}
placement={'bottomLeft'}
>
<Button type={'link'} className="item-link"> <Button type={'link'} className="item-link">
加盟政策 加盟政策
</Button> </Button>
</QrcodePopover>
</div> </div>
<div className="start-item"> <div className="start-item">
<div className="item-title">关于我们</div> <div className="item-title">关于我们</div>
...@@ -77,15 +110,21 @@ const FooterView = () => { ...@@ -77,15 +110,21 @@ const FooterView = () => {
</div> </div>
</div> </div>
<div className="footer-end"> <div className="footer-end">
<QrcodePopover path={'page-mine/help-center/index'}>
<Button type={'link'} className="end-item"> <Button type={'link'} className="end-item">
常见问题 常见问题
</Button> </Button>
</QrcodePopover>
<QrcodePopover path={'page-mine/help-center/index'}>
<Button type={'link'} className="end-item"> <Button type={'link'} className="end-item">
意见反馈 意见反馈
</Button> </Button>
</QrcodePopover>
<QrcodePopover path={'page-mine/sharefly-about/index'}>
<Button type={'link'} className="end-item"> <Button type={'link'} className="end-item">
加盟入驻 免责声明
</Button> </Button>
</QrcodePopover>
<Button <Button
type={'link'} type={'link'}
className="end-item" className="end-item"
......
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { EnvironmentFilled } from '@ant-design/icons'; import {
import { Button } from 'antd'; EnvironmentFilled,
import { useRouter } from 'next/router'; LogoutOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import { Button, Dropdown, MenuProps, Modal } from 'antd';
import dayjs from 'dayjs';
import Cookies from 'js-cookie';
import { useDispatch, useSelector } from 'react-redux';
import { CommonAPI } from '@/api';
import { HeaderWrap } from '@/components/layout/header/styled'; import { HeaderWrap } from '@/components/layout/header/styled';
import QrcodePopover from '@/components/qrcodePopover';
import { RootState } from '@/store';
import { AddressState, setAddress } from '@/store/module/address';
import { setGlobalData } from '@/store/module/globalData';
import { setSystem, SystemState } from '@/store/module/system';
import { setUserInfo, UserInfoState } from '@/store/module/userInfo';
import getLocationByIP from '@/utils/getLocationByIP';
const HeaderView: React.FC<{ const HeaderView: React.FC<{
placeholder?: boolean; placeholder: boolean;
}> = ({ placeholder }) => { }> = ({ placeholder }) => {
HeaderView.defaultProps = { const token = Cookies.get('SHAREFLY-WEB-TOKEN');
placeholder: true,
};
// 当前的路由数据 // 当前的路由数据
const router = useRouter(); // const router = useRouter();
// store
const dispatch = useDispatch();
// system
const system = useSelector((state: RootState) => state.system) as SystemState;
// address
const address = useSelector(
(state: RootState) => state.address,
) as AddressState;
// userInfo
const userInfo = useSelector(
(state: RootState) => state.userInfo,
) as UserInfoState;
// 打开登录弹窗
const handleLogin = () => {
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
};
// 获取用户信息
const getAccountInfo = async () => {
if (!system?.token || !token) return;
const res = await CommonAPI.getAccountInfo();
if (res && res.code === '200') {
dispatch(setUserInfo(res.result));
}
};
// 计算天数与当前时间的差值
const getDiffDay = (date: string) => dayjs().diff(dayjs(date), 'day');
// 重新获取用户的地址信息
const handleReload = () => {
getLocationByIP().then((res) => {
dispatch(setAddress(res));
});
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
console.log('HeaderView --->', router); if (!address?.city) {
}, [router]); // 获取当前地址
getLocationByIP().then((res) => {
dispatch(setAddress(res));
});
}
// 当前是否登录
if (!token) {
dispatch(setSystem({ token: undefined }));
dispatch(setUserInfo(null));
} else {
getAccountInfo().then();
}
}, []);
// 顶部Tab列表
const tabList = [
{ label: '个人中心', value: 'pages/mine/index' },
{ label: '购物车', value: 'page-cart/cart-list/index' },
{ label: '我的订单', value: 'pages/order/index' },
{ label: '消息', value: 'pages/message/index' },
{ label: '联系客服', value: 'page-mine/help-center/index' },
];
// 右上角按钮
const items: MenuProps['items'] = [
{
key: '1',
label: (
<div style={{ textAlign: 'left', marginBottom: '20px' }}>
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
用户信息:
</div>
<div>昵称:{userInfo?.nickName}</div>
<div>手机号:{userInfo?.phoneNum}</div>
<div>来到云享飞 {getDiffDay(userInfo?.createTime)}</div>
</div>
),
},
{
key: '0',
label: (
<Button
style={{ color: '#666666' }}
type="link"
icon={<LogoutOutlined />}
onClick={() => {
Modal.confirm({
title: '退出登录',
content: '退出后未保存数据将会丢失,确定登出吗?',
onOk: () => {
dispatch(setUserInfo(null));
dispatch(setSystem({ token: undefined }));
Cookies.remove('SHAREFLY-TOKEN');
},
});
}}
>
退出登录
</Button>
),
},
];
return ( return (
<HeaderWrap> <>
<HeaderWrap
style={{
background: placeholder ? '#2A2A2A' : 'rgba(86, 86, 86, 0.25)',
}}
>
<div className="header-wrap"> <div className="header-wrap">
<div className="header-location"> <div className="header-location">
<div className="location-icon"> <div className="location-icon">
...@@ -26,42 +138,73 @@ const HeaderView: React.FC<{ ...@@ -26,42 +138,73 @@ const HeaderView: React.FC<{
/> />
</div> </div>
<Button type={'link'} className="location-address"> <Button type={'link'} className="location-address">
杭州 {address?.city || '定位中...'}
</Button> </Button>
<div className="location-hello">Hi,欢迎来云享飞</div> <Button
type={'link'}
icon={<ReloadOutlined style={{ fontSize: '10px' }} />}
className="location-reload"
title={'刷新位置'}
onClick={() => handleReload()}
></Button>
<div className="location-hello">Hi,欢迎来到云享飞</div>
</div> </div>
<div className="header-nav"> <div className="header-nav">
<div className="nav-tab"> <div className="nav-tab">
{tabList?.map((i, j) => (
<QrcodePopover path={i.value} key={j}>
<Button type={'link'} className="tab-item"> <Button type={'link'} className="tab-item">
个人中心 {i.label}
</Button>
<Button type={'link'} className="tab-item">
购物车
</Button>
<Button type={'link'} className="tab-item">
我的订单
</Button>
<Button type={'link'} className="tab-item">
消息
</Button>
<Button type={'link'} className="tab-item">
联系客服
</Button> </Button>
</QrcodePopover>
))}
</div> </div>
{!!userInfo?.id && (
<div className="nav-user">
<Dropdown
overlayStyle={{ textAlign: 'center' }}
menu={{ items }}
placement="bottomRight"
arrow
>
<div
className="user-avatar"
style={{ backgroundImage: `url(${userInfo?.userImg})` }}
></div>
</Dropdown>
</div>
)}
<div className="nav-action"> <div className="nav-action">
<Button type={'primary'} className="action-item"> {!userInfo?.id && (
<Button
type={'primary'}
className="action-item"
onClick={handleLogin}
>
登录 登录
</Button> </Button>
)}
<QrcodePopover path={'page-service/service-flyer/index'}>
<Button type={'primary'} className="action-item"> <Button type={'primary'} className="action-item">
发布需求 发布任务
</Button> </Button>
</QrcodePopover>
<QrcodePopover path={'page-identity/identity-list/index'}>
<Button type={'primary'} className="action-item"> <Button type={'primary'} className="action-item">
加盟入驻 加盟入驻
</Button> </Button>
</QrcodePopover>
</div> </div>
</div> </div>
</div> </div>
</HeaderWrap> </HeaderWrap>
{placeholder && (
<div
className="header-wrap"
style={{ width: '100%', height: '3rem' }}
></div>
)}
</>
); );
}; };
......
...@@ -27,7 +27,7 @@ export const HeaderWrap = styled.div` ...@@ -27,7 +27,7 @@ export const HeaderWrap = styled.div`
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
.location-icon { .location-icon {
margin: 0 0 0.2rem 0; margin: 0 0.5rem 0 0;
} }
.location-address, .location-address,
.location-hello { .location-hello {
...@@ -38,6 +38,15 @@ export const HeaderWrap = styled.div` ...@@ -38,6 +38,15 @@ export const HeaderWrap = styled.div`
.location-hello { .location-hello {
width: 8rem; width: 8rem;
} }
.location-address {
margin: 0 0.25rem 0 0 !important;
}
.location-reload,
.location-address {
width: auto;
margin: 0 0.5rem 0 0;
padding: 0;
}
} }
.header-nav { .header-nav {
display: flex; display: flex;
...@@ -50,6 +59,7 @@ export const HeaderWrap = styled.div` ...@@ -50,6 +59,7 @@ export const HeaderWrap = styled.div`
justify-content: flex-end; justify-content: flex-end;
} }
.nav-tab { .nav-tab {
margin-right: 0.5rem;
.tab-item { .tab-item {
//font-size: 0.67rem; //font-size: 0.67rem;
font-weight: 500; font-weight: 500;
...@@ -62,11 +72,26 @@ export const HeaderWrap = styled.div` ...@@ -62,11 +72,26 @@ export const HeaderWrap = styled.div`
} }
} }
.nav-action { .nav-action {
margin-left: 1rem; margin-left: 0.5rem;
.action-item { .action-item {
margin-left: 0.33rem; margin-left: 0.33rem;
} }
} }
.nav-user {
.user-avatar {
width: 2rem;
height: 2rem;
border-radius: 50%;
//background: #cdcdcd;
border: 1px solid #ffffff;
background-size: cover;
background-position: center;
&:active,
&:hover {
filter: brightness(0.9);
}
}
}
} }
// 媒体查询 // 媒体查询
@media screen and (min-width: 1600px) { @media screen and (min-width: 1600px) {
......
import React, { useState } from 'react'; import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import ContentView from '@/components/layout/content'; import ContentView from '@/components/layout/content';
import FooterView from '@/components/layout/footer'; import FooterView from '@/components/layout/footer';
import HeaderView from '@/components/layout/header'; import HeaderView from '@/components/layout/header';
import { LayoutWrap } from '@/components/layout/styled'; import { LayoutWrap } from '@/components/layout/styled';
import LoginModalView from '@/components/loginModal';
import QrcodeModalView from '@/components/qrcodeModal'; import QrcodeModalView from '@/components/qrcodeModal';
import ToastModalView from '@/components/toastModal';
import { RootState } from '@/store';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
const LayoutView = ({ children }: { children?: React.ReactNode }) => { const LayoutView: React.FC<{
// 打开二维码弹窗 children?: React.ReactNode;
const [qrcodeShow, setQrcodeShow] = useState<boolean>(false); placeholder?: boolean;
}> = ({ children, placeholder }) => {
// store
const dispatch = useDispatch();
// system
const globalData = useSelector(
(state: RootState) => state.globalData,
) as GlobalDataState;
// 关闭弹窗
const handleModalClose = () => {
dispatch(
setGlobalData({
loginModalVisible: false,
qrcodeModalVisible: false,
toastModalVisible: false,
}),
);
};
return ( return (
<div className={'animate__animated animate__faster animate__fadeIn'}>
<LayoutWrap> <LayoutWrap>
<div <div
onClick={() => { onClick={() => {
setQrcodeShow(!qrcodeShow); // setQrcodeShow(!qrcodeShow);
}} }}
> >
<HeaderView></HeaderView> {placeholder || placeholder === undefined ? (
<HeaderView placeholder={true}></HeaderView>
) : (
<HeaderView placeholder={false}></HeaderView>
)}
<ContentView>{children}</ContentView> <ContentView>{children}</ContentView>
<FooterView></FooterView> <FooterView></FooterView>
</div> </div>
{/* 登录弹窗 */}
<LoginModalView
open={globalData?.loginModalVisible}
onCancel={handleModalClose}
/>
{/* 功能正在完善中 */}
<QrcodeModalView <QrcodeModalView
open={qrcodeShow} open={globalData?.qrcodeModalVisible}
onCancel={() => setQrcodeShow(false)} onCancel={handleModalClose}
/>
{/* 功能正在完善中 */}
<ToastModalView
open={globalData?.toastModalVisible}
onCancel={handleModalClose}
/> />
</LayoutWrap> </LayoutWrap>
</div>
); );
}; };
......
import React, { useEffect, useState } from 'react';
import { ReloadOutlined } from '@ant-design/icons';
import { message, Modal } from 'antd';
import Cookies from 'js-cookie';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { CommonAPI } from '@/api';
import { RootState } from '@/store';
import { GlobalDataState } from '@/store/module/globalData';
import { setSystem } from '@/store/module/system';
import { setUserInfo } from '@/store/module/userInfo';
export const LoginModalWrap = styled.div`
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
box-sizing: border-box;
padding: 0.68rem 0;
.qrcode {
position: relative;
width: 10.68rem;
height: 10.68rem;
margin: 1.5rem 0 1rem 0;
//background-image: url('https://file.iuav.com/file/sharefly-qrcode-wx.jpg');
//background-size: 100% 100%;
//background-size: cover;
//background-position: center;
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
.shadow {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
flex-direction: column;
background: rgba(0, 0, 0, 0.68);
.refresh {
position: relative;
width: 3rem;
height: 3rem;
border-radius: 50%;
background: #fff;
margin-bottom: 1rem;
&:active {
filter: brightness(0.9);
transform: rotate(360deg);
transition: all 0.3s ease-in-out;
}
}
.text {
color: #fff;
}
}
}
.title {
color: #222;
font-size: 16px;
font-weight: bold;
}
.text {
color: #333;
font-size: 12px;
line-height: 20px;
text-align: center;
}
.action {
color: #999;
font-size: 12px;
line-height: 20px;
text-align: center;
margin: 1rem 0;
text-decoration: underline;
cursor: pointer;
&:hover,
&:active {
color: #ff552d;
}
}
`;
// 定时器暂存
let timer: NodeJS.Timer;
const LoginModalView = ({
open,
onCancel,
}: {
open: boolean;
onCancel: () => void;
}) => {
// system
const globalData = useSelector(
(state: RootState) => state.globalData,
) as GlobalDataState;
// store
const dispatch = useDispatch();
// 获取小程序二维码唯一标识
const [randomLoginCode, setRandomLoginCode] = useState<string>(
`${parseInt(String(Math.random() * 10001), 10)}`,
);
// 二维码是否过期
const [qrCodeExpired, setQrCodeExpired] = useState<boolean>(false);
// 登录二维码的地址
const [qrCodeData, setQrCodeData] = useState<string>();
// 获取登录二维码
const getQrcodeLogin = async () => {
// 获取二维码
const res = await CommonAPI.getAppletQRCode({
page: 'page-identity/identity-empower/index',
scene: `randomLoginCode=${randomLoginCode}&port=0`,
});
if (res && res.code === '200') {
if (!res.result) {
message.warning('获取登录二维码失败');
return;
}
// 设置当前登录的二维码
setQrCodeData(`data:image/png;base64,${res.result}`);
// 设置二维码倒计时
setQrCodeExpiredTimer();
}
};
// 跳转管理平台
const handleBackEnd = () => {
window.open('https://admin.iuav.com');
};
// 刷新二维码
const handleRefresh = () => {
setRandomLoginCode(`${parseInt(String(Math.random() * 10001), 10)}`);
getQrcodeLogin().then(() => {
setQrCodeExpired(false);
});
};
// 定时器设置二维码过期
const setQrCodeExpiredTimer = () => {
setTimeout(
() => {
setQrCodeExpired(true);
// 关闭定时器
if (timer) clearInterval(timer);
},
1000 * 60 * 5,
);
};
// 获取登录信息
const getLoginInfo = async () => {
if (!randomLoginCode) return;
const res = await CommonAPI.getLoginInfo({
randomLoginCode,
});
// console.log('获取登录信息 --->', res);
if (res && res.code === '200') {
// 关闭定时器
if (timer) clearInterval(timer);
// 刷新二维码
setQrCodeExpired(true);
// 关闭弹窗
onCancel?.();
// 设置用户信息
dispatch(setUserInfo(res.result));
// 设置token
dispatch(setSystem({ token: res.result.token }));
// 设置token
Cookies.set('SHAREFLY-WEB-TOKEN', res.result.token);
message.success('登录成功');
// 刷新当前页面
window.location.reload();
}
};
// 设置定时器轮询获取信息
const setLoginInfoTimer = () => {
timer = setInterval(async () => {
await getLoginInfo();
}, 2500);
};
// 关闭弹窗
const handleClose = () => {
onCancel?.();
// 关闭定时器
if (timer) clearInterval(timer);
};
// 组件挂载
useEffect(() => {
if (!open) {
// 关闭定时器
if (timer) clearInterval(timer);
return;
}
// 获取二维码
getQrcodeLogin().then(() => {
setQrCodeExpired(false);
setLoginInfoTimer();
});
}, [open]);
return (
<Modal open={open} footer={null} onCancel={handleClose}>
<LoginModalWrap>
<div className="title">
{globalData?.loginModalTitle || `微信扫码登录`}
</div>
<div className="qrcode">
{qrCodeData && (
<img src={qrCodeData} alt="登录二维码" className="image" />
)}
{qrCodeExpired && (
<div className="shadow flex-center animate__animated animate__fast animate__fadeIn">
<div className="refresh flex-center" onClick={handleRefresh}>
<ReloadOutlined style={{ fontSize: '20px' }} />
</div>
<div className="text">二维码已过期,请重新扫描</div>
</div>
)}
</div>
<div className="text">微信扫一扫,打开小程序</div>
<div className="text">即可登录/注册</div>
<div
className="action"
onClick={handleBackEnd}
style={{ visibility: 'hidden' }}
>
管理平台登录 {'>'}
</div>
<div className="text">「云享飞,让天空为世界所用」</div>
</LoginModalWrap>
</Modal>
);
};
export default LoginModalView;
import React, { useEffect, useState } from 'react';
import { Empty } from 'antd';
import { InterDataType } from '@/api/interface';
import {
GetAppCategoryInfo,
QueryGoodsInfoByCategorySub,
} from '@/api/interface/mall';
import { MallAPI } from '@/api/modules/mall';
import BreadcrumbView from '@/components/breadcrumb';
import CategorySelectView, { CategoryType } from '@/components/categorySelect';
import LayoutView from '@/components/layout';
import ProductItemView from '@/components/productItem';
import ProductListView from '@/components/productList';
import { MallWrap } from '@/pages/mall/styled';
// 分类列表类型
type CategoryListType = InterDataType<GetAppCategoryInfo>;
// 商品列表类型
type GoodsInfoListType = InterDataType<QueryGoodsInfoByCategorySub>;
const MallView: React.FC<{
categoryList: CategoryListType;
}> = (props) => {
// 分类列表
const [categoryList, setCategoryList] = useState<CategoryType>([]);
// 商品列表
const [goodsInfoList, setGoodsInfoList] = useState<GoodsInfoListType>([]);
// 分页数据
const [pagination, setPagination] = useState({
pageNo: 1,
pageSize: 18,
totalCount: 0,
});
// 转换分类列表
const getCategoryList = () => {
setCategoryList(
props?.categoryList
?.filter((i) => i.subDTOList?.length)
?.map((i) => ({
value: i.id,
label: i.name,
children: i.subDTOList?.map((n) => ({ value: n.id, label: n.name })),
})),
);
};
// 获取商品列表
const getGoodsInfoByCategorySub = async (list: number[]) => {
const res = await MallAPI.queryGoodsInfoByCategorySub(list);
if (res && res.code === '200') {
setGoodsInfoList(res.result || []);
setPagination({
...pagination,
pageNo: 1,
totalCount: res.result?.length || 0,
});
// console.log('商品列表 --->', res.result);
}
};
// 根据分页数据返回商品列表
const getGoodsInfoListByPage = () => {
const { pageNo, pageSize, totalCount } = pagination;
return goodsInfoList?.slice(
(pageNo - 1) * pageSize,
pageNo * (pageSize < totalCount ? pageSize : totalCount),
);
};
// 翻页回调
const handlePageChange = (pageNo: number, pageSize: number) => {
setPagination({ ...pagination, pageNo, pageSize });
};
// 组件挂载
useEffect(() => {
if (!props) return;
getCategoryList();
// console.log('执行到此处12312 --->', props);
}, [props]);
return (
<>
<LayoutView placeholder={true}>
<MallWrap>
{/* 面包屑 */}
<BreadcrumbView />
{/* 类型筛选 */}
<CategorySelectView
list={categoryList}
onSecond={getGoodsInfoByCategorySub}
/>
{/* 产品列表 */}
<ProductListView pagination={pagination} onChange={handlePageChange}>
{getGoodsInfoListByPage()?.length ? (
getGoodsInfoListByPage()?.map((i, j) => (
<ProductItemView key={j} detail={i} />
))
) : (
<div className="list-empty flex-center">
<Empty />
</div>
)}
</ProductListView>
</MallWrap>
</LayoutView>
</>
);
};
export default MallView;
...@@ -2,7 +2,8 @@ import { FC, useEffect } from 'react'; ...@@ -2,7 +2,8 @@ import { FC, useEffect } from 'react';
const MapContainer: FC<{ const MapContainer: FC<{
list: { lat: number; lon: number; name: string }[]; list: { lat: number; lon: number; name: string }[];
}> = ({ list }) => { center?: [number, number];
}> = ({ list, center }) => {
// 地图实例 // 地图实例
let map: any = null; let map: any = null;
// 高德地图 // 高德地图
...@@ -58,7 +59,7 @@ const MapContainer: FC<{ ...@@ -58,7 +59,7 @@ const MapContainer: FC<{
// 设置地图容器id // 设置地图容器id
viewMode: '3D', // 是否为3D地图模式 viewMode: '3D', // 是否为3D地图模式
zoom: 10, // 初始化地图级别 zoom: 10, // 初始化地图级别
center: [119.96043, 30.04885], // 初始化地图中心点位置 center: center || [119.96043, 30.04885], // 初始化地图中心点位置
}); });
// 用户定位 // 用户定位
map.plugin('AMap.Geolocation', () => { map.plugin('AMap.Geolocation', () => {
...@@ -89,7 +90,7 @@ const MapContainer: FC<{ ...@@ -89,7 +90,7 @@ const MapContainer: FC<{
return () => { return () => {
map?.destroy(); map?.destroy();
}; };
}, [list]); }, [list, center]);
return ( return (
<div <div
id="container" id="container"
......
import React, { useEffect, useState } from 'react';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, InputNumber, Space } from 'antd';
import { InputNumberProps } from 'antd/es/input-number';
const NumberBox: React.FC<InputNumberProps> = (props) => {
// 使用state hooks来定义value和setValue
const [value, setValue] = useState<number>(1);
// 定义增加和减少的函数
const handleIncrement = () => {
// 不能大于最大
if (props?.max && value >= Number(props?.max)) return;
props?.onChange?.(value + 1);
setValue(value + 1);
};
const handleDecrement = () => {
// 不能低于最小
if (props?.min && value <= Number(props?.min)) return;
props?.onChange?.(value - 1);
setValue(value - 1);
};
// 监听value的变化
useEffect(() => {
if (!props?.value) return;
setValue(Number(props?.value) || 1);
}, [props?.value]);
return (
<Space.Compact block style={{ width: 'auto' }}>
<Button icon={<MinusOutlined />} onClick={handleDecrement} />
<InputNumber
{...props}
controls={false}
style={{ width: '42px' }}
value={value}
/>
<Button icon={<PlusOutlined />} onClick={handleIncrement} />
</Space.Compact>
);
};
export default NumberBox;
import React, { useEffect, useState } from 'react';
import { Checkbox } from 'antd';
import { CheckboxValueType } from 'antd/es/checkbox/Group';
import Big from 'big.js';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { UserPayWalletInfoType } from '@/api/interface/wallet';
import { SystemState } from '@/store/module/system';
import { isNone } from '@/utils';
import getCurrentUserPayWalletInfo from '@/utils/getCurrentUserPayWalletInfo';
import { formatMoney } from '@/utils/money';
// 钱包类型
type WalletType = InterDataType<UserPayWalletInfoType>;
const PaymentCheckout: React.FC<{ cost: number }> = ({ cost }) => {
// system
const system = useSelector((state: any) => state.system) as SystemState;
// 选择的支付方式
const [paymentTypeValue, setPaymentTypeValue] = useState<CheckboxValueType[]>(
[],
);
// 钱包详情
const [walletInfo, setWalletInfo] = useState<WalletType>();
// 获取钱包详情
const getUserPayWalletInfo = async () => {
const res = await getCurrentUserPayWalletInfo();
if (res) setWalletInfo(res);
};
// 获取用户最多可抵扣的云享金余额
const getOrderPriceCashMax = () => {
// 当前的云享金余额
const cash = walletInfo?.cashAmt || 0;
// 如果用户的钱包金额大于等于订单金额 则可抵扣金额为订单金额
return cash >= (cost || 0) ? cost || 0 : cash;
};
// 获取用户最多可抵扣的薪水余额
const getOrderPriceSalaryMax = () => {
// 当前的薪水余额
const salary = walletInfo?.salaryAmt || 0;
// 如果用户的钱包金额大于等于订单金额 则可抵扣金额为订单金额
return salary >= (cost || 0) ? cost || 0 : salary;
};
// 获取用户最多可抵扣的总的金额
const getOrderPriceTotalMax = () => {
const cash = Big(getOrderPriceCashMax());
const salary = Big(getOrderPriceSalaryMax());
return cash.add(salary).toNumber();
};
// 获取用户的云享金余额
const getOrderPriceCash = () => {
// 云享金是否选中
const selectCash = paymentTypeValue?.includes(1);
// 余额是否选中
const selectSalary = paymentTypeValue?.includes(2);
// 总的抵扣金额
const total =
getOrderPriceTotalMax() > cost ? cost : getOrderPriceTotalMax();
// 只选其中一个的情况
if (!selectCash && selectSalary) {
return Big(total).minus(Big(getOrderPriceSalaryMax())).toNumber();
}
// 返回结果
return getOrderPriceCashMax();
};
// 获取用户的薪水余额
const getOrderPriceSalary = () => {
// 云享金是否选中
const selectCash = paymentTypeValue?.includes(1);
// 余额是否选中
const selectSalary = paymentTypeValue?.includes(2);
// 总的抵扣金额
const total =
getOrderPriceTotalMax() > cost ? cost : getOrderPriceTotalMax();
// 只选其中一个的情况
if (!selectCash && selectSalary) {
return getOrderPriceSalaryMax();
}
// 返回结果
return Big(total).minus(Big(getOrderPriceCashMax())).toNumber();
};
// 根据用户钱包和需要支付的金额设置默认选中
const setDefaultSelect = () => {
// 必须有金额时才继续执行
if (isNone(walletInfo?.cashAmt) || isNone(walletInfo?.salaryAmt)) {
return;
}
// 云享金
const cash = Big(walletInfo?.cashAmt || 0).toNumber();
// 余额
const salary = Big(walletInfo?.salaryAmt || 0).toNumber();
// 如果需要支付金额为零 则后面的判断都不执行
if (!cost || cost <= 0) {
setPaymentTypeValue([]);
return;
}
// 如果云享金大于等于订单金额 则默认选中云享金
if (cash >= cost) {
setPaymentTypeValue([1]);
return;
}
// 如果余额大于等于订单金额 则默认选中余额
if (salary >= cost) {
setPaymentTypeValue([2]);
return;
}
// 如果云享金有钱
if (cash > 0 && salary === 0) {
setPaymentTypeValue([1]);
return;
}
// 如果余额有钱
if (cash === 0 && salary > 0) {
setPaymentTypeValue([2]);
return;
}
// 如果两者都有余额
if (cash > 0 && salary > 0) {
setPaymentTypeValue([1, 2]);
}
};
// 组件挂载
useEffect(() => {
if (!system?.token) return;
getUserPayWalletInfo().then();
if (!cost) return;
setDefaultSelect();
}, [cost]);
return (
<PaymentCheckoutWrap>
<Checkbox.Group value={paymentTypeValue} onChange={setPaymentTypeValue}>
<div className="payment-item flex-start">
<img
src="https://file.iuav.com/file/checkout_icon_01.png"
alt="云享金"
className="icon"
/>
<div className="title">云享金</div>
<div className="num">-可抵扣¥{formatMoney(getOrderPriceCash())}</div>
<div className="checkbox">
<Checkbox value={1} />
</div>
</div>
<div className="payment-item flex-start">
<img
src="https://file.iuav.com/file/checkout_icon_02.png"
alt="余额"
className="icon"
/>
<div className="title">余额</div>
<div className="num">
-可抵扣¥{formatMoney(getOrderPriceSalary())}
</div>
<div className="checkbox">
<Checkbox value={2} />
</div>
</div>
</Checkbox.Group>
</PaymentCheckoutWrap>
);
};
export default PaymentCheckout;
// 样式
const PaymentCheckoutWrap = styled.div`
position: relative;
width: 18.6rem;
.payment-item {
position: relative;
width: 100%;
&:not(:last-child) {
margin-bottom: 0.66rem;
}
.icon {
width: 1.25rem;
height: 1.25rem;
}
.title,
.num {
margin-left: 0.5rem;
}
.num {
color: #ff4600;
}
.checkbox {
position: absolute;
top: 0;
right: 0;
}
}
`;
import React, { useEffect, useState } from 'react';
import { EnvironmentOutlined } from '@ant-design/icons';
import { message } from 'antd';
import Big from 'big.js';
import dayjs from 'dayjs';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { AppMallGoodsDetails } from '@/api/interface/mall';
import NumberBox from '@/components/numberBox';
import ProductSwiperView from '@/components/product-swiper';
import { AddressState } from '@/store/module/address';
import { setGlobalData } from '@/store/module/globalData';
import { UserInfoState } from '@/store/module/userInfo';
// 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>;
const ProductHeadView: React.FC<{ detail: DetailType }> = ({ detail }) => {
// 当前的路由数据
const router = useRouter();
// store
const dispatch = useDispatch();
// address
const address = useSelector((state: any) => state.address) as AddressState;
// userInfo
const userInfo = useSelector((state: any) => state.userInfo) as UserInfoState;
// 当前选中的规格
const [selectSpecList, setSelectSpecList] = useState<
{ row: number; index: number }[]
>([]);
// 商品的购买数量
const [productNum, setProductNum] = useState<number>(1);
// 获取后天的日期
const getAfterTomorrow = () => {
return dayjs().add(2, 'day').format('MM月DD日');
};
// 判断当前选项是否选中
const isSelect = (row: number, index: number) => {
return !!selectSpecList.find((i) => i.row === row && i.index === index);
};
// 选中项目
const handleSelect = (row: number, index: number) => {
let arr = selectSpecList;
// 选中列表中是否有当前选项
if (arr.find((i) => i.row === row && i.index === index)) {
// 如果有就删除
setSelectSpecList(arr.filter((i) => i.row !== row || i.index !== index));
return;
}
// 如果是单选,就删除当前组别的所有选项
arr = arr.filter((i) => i.row !== row);
// 将当前选项添加到选中列表中
arr = [...arr, { row, index }].sort((a, b) => a.row - b.row);
setSelectSpecList(arr);
};
// 获取当前最便宜的规格,然后选中
const getLowerSpec = () => {
const item = detail?.priceStock
?.sort((a, b) => a.salePrice - b.salePrice)
?.at(0)?.productSpec;
const arr = Object.entries(JSON.parse(item || '{}'))?.map((i) => {
const index = detail?.specAttrList?.findIndex((k) => k.specName === i[0]);
return {
row: index,
index: detail?.specAttrList?.[index]?.specValuesList?.findIndex(
(k) => k.specName === i[1],
),
};
});
setSelectSpecList(arr || []);
};
// 获取规格中最低的价格
const getLowerPrice = () => {
return detail?.priceStock?.sort((a, b) => a.salePrice - b.salePrice)?.at(0)
?.salePrice;
};
// 获取当前规格的价格
const getSpecPrice = () => {
let price = 0;
if (selectSpecList?.length !== detail?.specAttrList?.length) {
price = getLowerPrice() || 0;
} else {
const item = selectSpecList?.map((i) => {
const row = detail?.specAttrList?.[i.row];
const index = row?.specValuesList?.[i.index];
return [row?.specName, index?.specName];
});
price =
detail?.priceStock?.find(
(i) => i.productSpec === JSON.stringify(Object.fromEntries(item)),
)?.salePrice || 0;
}
const money = Big(price).mul(Big(productNum)).toNumber().toLocaleString();
return money.endsWith('.00') ? money : `${money}.00`;
};
// 获取选中规格列表的补充数据
const getSelectPriceStock = () => {
const arr =
selectSpecList.map((i) => [
detail?.specAttrList?.[i.row]?.specName,
detail?.specAttrList?.[i.row]?.specValuesList?.[i.index]?.specName,
]) || [];
return Object.fromEntries(arr);
};
// 获取规格的选项
const getSelectPriceStockItem = () => {
const item = getSelectPriceStock();
return detail?.priceStock?.find(
(i) => i.productSpec === JSON.stringify(item),
);
};
// 提交事件
const handleSubmit = async (type: number) => {
// 判断是否选择了规格
if (selectSpecList.length === 0) {
message.warning('请选择规格');
return;
}
// 去重掉同分组的之后,再判断必选项是否已选
if (detail?.specAttrList?.length !== selectSpecList.length) {
const arr =
detail?.specAttrList
?.filter((i, j) => !selectSpecList.map((n) => n.row).includes(j))
.map((i) => i.specName) || [];
message.warning(`请选择必选项 ${arr.join(' ')}`);
return;
}
// 判断是否登录
if (!userInfo?.id) {
dispatch(
setGlobalData({
loginModalVisible: true,
loginModalTitle: '购买商品,请先登录',
}),
);
return;
}
// 当前的规格数据
const specItem = getSelectPriceStockItem();
// 立即购买
if (type !== 3) {
await router.push(
`/cart/submit/${detail?.id}?num=${productNum}&specId=${specItem?.id}&type=${type}`,
);
}
};
// 组件挂载
useEffect(() => {
if (!detail) return;
getLowerSpec();
// console.log('detail --->', detail);
}, [detail]);
return (
<ProductHeadWrap>
<div className="product-swiper">
<ProductSwiperView list={detail?.resourcesList} />
</div>
<div className="product-content">
<div className="content-title">{detail?.tradeName}</div>
<div className="content-desc">{detail?.description}</div>
<div className="content-price flex-start">
<div className="price-label">价格</div>
<div className="price-money">
{detail?.priceShow ? (
<>
<span className="label">¥</span>
<span className="num">{getSpecPrice()}</span>
</>
) : (
<span className="label">咨询报价</span>
)}
</div>
</div>
<div className="content-item flex-start align-start">
<div className="item-label">送至</div>
<div className="item-content">
<div className="content-address flex-start">
<EnvironmentOutlined
style={{ color: '#ff6700', fontSize: '12px' }}
/>
<span className="text">{address?.province}</span>
<span className="text">{address?.city}</span>
</div>
<div className="content-address">
现货,今天22:00前下单,最快预计后天({getAfterTomorrow()})送达
</div>
</div>
</div>
<div className="content-item flex-start">
<div className="item-label">规格</div>
<div className="item-content">
共 {detail?.priceStock?.length || 0} 种可选
</div>
</div>
{detail?.specAttrList?.map((i, j) => (
<div className="content-item flex-start align-start" key={j}>
<div className="item-label two-line-ellipsis" title={i.specName}>
{i.specName}
</div>
<div className="item-content flex-start ">
{i.specValuesList?.map((n, m) => (
<div
className={`content-spec cursor-pointer select-none ${
isSelect(j, m) && 'spec-active'
}`}
key={m}
onClick={() => handleSelect(j, m)}
title={n.specName}
>
{n.specName}
</div>
))}
</div>
</div>
))}
<div className="content-item flex-start">
<div className="item-label">数量</div>
<div className="item-content">
<NumberBox
min={1}
max={9999}
precision={0}
value={productNum}
onChange={(e) => setProductNum(Number(e))}
/>
</div>
</div>
<div className="content-action flex-start select-none">
{/* <div className="action-item" onClick={() => handleSubmit(3)}> */}
{/* 加入购物车 */}
{/* </div> */}
<div className="action-item" onClick={() => handleSubmit(2)}>
提交意向
</div>
<div className="action-item" onClick={() => handleSubmit(1)}>
确认购买
</div>
</div>
</div>
</ProductHeadWrap>
);
};
export default ProductHeadView;
// 样式
const ProductHeadWrap = styled.div`
position: relative;
width: calc((100% - 0.83rem) / 10 * 7.5);
min-height: 28rem;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
box-sizing: border-box;
margin-right: 0.83rem;
display: flex;
align-items: flex-start;
justify-content: flex-start;
padding: 2rem 1rem 2rem 1rem;
.product-swiper {
position: relative;
width: 22rem;
height: 26rem;
box-sizing: border-box;
}
.product-content {
position: relative;
width: calc(100% - 22rem);
height: 100%;
box-sizing: border-box;
padding: 0 0 0 1rem;
//background: lightblue;
.content-title {
width: 100%;
font-size: 24px;
font-weight: 500;
color: #212121;
margin-bottom: 0.71rem;
// 双行省略
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
}
.content-desc {
font-weight: 400;
color: #666666;
margin-bottom: 0.71rem;
}
.content-price {
position: relative;
width: 100%;
background: #f3f3f3;
box-sizing: border-box;
padding: 0.8rem;
margin-bottom: 1rem;
align-items: baseline;
.price-label {
width: 2.5rem;
color: #999999;
text-align: justify;
text-align-last: justify;
margin-right: 1rem;
transform: translateY(-0.1rem);
}
.price-money {
font-size: 24px;
//line-height: 1;
color: #ff6700;
.label {
font-size: 18px;
}
}
}
.content-item {
margin-bottom: 1rem;
padding: 0 0.8rem;
//flex-wrap: nowrap;
.item-label {
width: 2.5rem;
color: #999999;
text-align: justify;
text-align-last: justify;
margin-right: 1rem;
}
.item-content {
width: calc(100% - 3.5rem);
.content-address {
font-size: 12px;
&:first-child {
margin-bottom: 0.3rem;
.text {
color: #8e8e8e;
}
}
.text {
margin-left: 0.3rem;
font-weight: 400;
}
}
.content-spec {
//min-width: max-content;
height: 2rem;
background: #f2f2f2;
border-radius: 0.08rem;
text-align: center;
line-height: 2rem;
font-weight: 400;
box-sizing: border-box;
padding: 0 1rem;
border: 0.04rem solid #f2f2f2;
margin: 0 0.5rem 0.5rem 0;
&:last-child {
margin-right: 0;
}
}
.spec-active {
background: #ffede8;
border: 0.04rem solid #ff552d;
color: #ff552d;
}
}
}
.content-action {
//position: absolute;
//left: 1rem;
//bottom: 0;
position: relative;
width: 100%;
margin-top: 2rem;
.action-item {
width: 8.63rem;
height: 2.33rem;
background: #fff0e5;
border: 0.04rem solid #ff552d;
text-align: center;
line-height: 2.33rem;
font-size: 13px;
font-weight: 400;
color: #ff552d;
cursor: pointer;
&:hover,
&:active {
filter: brightness(0.95);
}
}
.action-item:not(:last-child) {
margin-right: 0.8rem;
}
.action-item:last-child {
background: #ff552d;
color: #fff;
}
}
}
`;
import React from 'react';
import { PhoneOutlined, ShopOutlined } from '@ant-design/icons';
import { Button, Rate } from 'antd';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import {
AppMallGoodsDetails,
GetCompanyInfoByBUId,
} from '@/api/interface/mall';
import QrcodePopover from '@/components/qrcodePopover';
// 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>;
// 商城详情类型
type StoreType = InterDataType<GetCompanyInfoByBUId>;
const ProductStoreView: React.FC<{ detail: DetailType; store: StoreType }> = ({
store,
}) => {
return (
<ProductStoreWrap>
<div className="store-card flex-start">
<img
className="image"
src={store?.brandLogo}
alt={store?.companyName}
/>
<div className="card-content">
<div className="title">{store?.companyName}</div>
<div className="star flex-start">
<div className="tag select-none">店铺星级</div>
<Rate allowHalf defaultValue={5} style={{ fontSize: '10px' }} />
</div>
<div className="text two-line-ellipsis" title={store?.content}>
{store?.content}
</div>
</div>
</div>
<div className="store-item flex-start">
<div className="item-label">地址:</div>
<div className="item-value">{store?.address}</div>
</div>
<div className="store-item flex-start">
<div className="item-label">电话:</div>
<div className="item-value">{store?.phoneNum || '18626051369'}</div>
</div>
<div className="store-action flex-start">
<QrcodePopover
path="page-service/service-store/index"
scene={`id=${Number(store?.id)}`}
>
<Button className="action-item flex-start">
<ShopOutlined style={{ color: '#FF552D' }} />
<div className="text">进店逛逛</div>
</Button>
</QrcodePopover>
<QrcodePopover
path="page-service/service-store/index"
scene={`id=${Number(store?.id)}`}
>
<Button className="action-item flex-start">
<PhoneOutlined
style={{ color: '#FF552D', transform: 'rotateY(180deg)' }}
/>
<div className="text">联系方式</div>
</Button>
</QrcodePopover>
</div>
</ProductStoreWrap>
);
};
export default ProductStoreView;
// 样式
const ProductStoreWrap = styled.div`
position: relative;
width: calc((100% - 0.83rem) / 10 * 2.5);
min-height: 18rem;
//height: 100%;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
box-sizing: border-box;
padding: 1rem;
.store-card {
width: 100%;
min-height: 8em;
background: linear-gradient(270deg, #5f5f5f 0%, #060606 100%);
border-radius: 0.33rem;
box-sizing: border-box;
padding: 0.58rem 1rem;
margin-bottom: 1rem;
align-items: flex-start;
.image {
width: 3.25rem;
height: 3.25rem;
border: 0.02rem solid #e3e3e3;
margin-right: 0.67rem;
}
.card-content {
position: relative;
width: calc(100% - 3.25rem - 0.67rem);
.title {
font-weight: 500;
color: #ffffff;
}
.star {
margin-bottom: 0.5rem;
.tag {
position: relative;
height: 1.2rem;
line-height: 1rem;
background: #fff3ee;
border-radius: 0.13rem;
border: 0.02rem solid #ff552d;
box-sizing: border-box;
padding: 0 0.33rem;
text-align: center;
font-size: 10px;
color: #ff552d;
margin-right: 0.25rem;
transform: scale(0.8);
}
}
.text {
font-size: 12px;
font-weight: 400;
color: rgba(255, 255, 255, 0.8);
}
}
}
.store-item {
align-items: flex-start;
flex-wrap: nowrap;
margin-bottom: 1rem;
.item-label {
width: 3rem;
color: #666666;
}
.item-value {
width: calc(100% - 3rem);
font-size: 12px;
font-weight: 400;
color: #666666;
}
}
.store-action {
position: relative;
width: 100%;
.action-item {
position: relative;
background: #ffffff;
box-sizing: border-box;
padding: 0 1rem;
border: 0.04rem solid #a8a8a8;
cursor: pointer;
margin-right: 0.83rem;
.text {
color: #a8a8a8;
font-weight: 400;
font-size: 12px;
margin-left: 0.33rem;
}
&:last-child {
border: 0.04rem solid #ff552d;
.text {
color: #ff552d;
}
}
&:hover {
filter: brightness(0.95);
}
}
}
`;
import React, { useState } from 'react';
import styled from 'styled-components';
import {
FreeMode,
Navigation,
Thumbs,
Autoplay,
Mousewheel,
} from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import 'swiper/css/free-mode';
import 'swiper/css/navigation';
import 'swiper/css/thumbs';
const ProductSwiperWrap = styled.div`
position: relative;
width: 200%;
height: 200%;
box-sizing: border-box;
transform: scale(0.5) translateX(-50%) translateY(-50%);
.swiper-main {
position: relative;
width: 100%;
height: calc(100% - 9rem);
margin-bottom: 0.5rem;
.swiper-slide {
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
.swiper-second {
position: relative;
width: 100%;
height: 9rem;
.swiper-slide {
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
`;
const ProductSwiperView: React.FC<{
list: { id: number; url: string }[];
}> = ({ list }) => {
const [thumbsSwiper, setThumbsSwiper] = useState(null);
// 图片列表
const SwiperListArr = list?.map((i, j) => (
<SwiperSlide key={j}>
<img src={i.url} alt={'图片'} />
</SwiperSlide>
));
return (
<ProductSwiperWrap>
<Swiper
className="swiper-main"
style={
{
'--swiper-navigation-color': '#fff',
'--swiper-pagination-color': '#fff',
} as any
}
loop={true}
// spaceBetween={10}
navigation={true}
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs, Autoplay, Mousewheel]}
autoplay={{
delay: 5000,
}}
mousewheel={true}
grabCursor={true}
>
{SwiperListArr}
</Swiper>
<Swiper
className="swiper-second"
// @ts-ignore
onSwiper={setThumbsSwiper}
loop={true}
spaceBetween={10}
slidesPerView={list?.length > 5 ? 5 : list?.length}
slidesPerGroupAuto={true}
freeMode={true}
watchSlidesProgress={true}
modules={[FreeMode, Navigation, Thumbs]}
grabCursor={true}
>
{SwiperListArr}
</Swiper>
</ProductSwiperWrap>
);
};
export default ProductSwiperView;
import React from 'react';
import { PropertySafetyFilled, ShoppingCartOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { QueryGoodsInfoByCategorySub } from '@/api/interface/mall';
const ProductItemWrap = styled.div`
position: relative;
box-sizing: border-box;
width: calc((100% - (0.83rem * 5)) / 6);
height: 16rem;
border: 0.02rem solid #e3e3e3;
margin: 0 0.83rem 0.83rem 0;
padding: 0.67rem;
cursor: pointer;
&:nth-child(6n) {
margin-right: 0;
}
&:active,
&:hover {
background: #fff9f6;
border: 0.04rem solid #ff7a3e;
}
.product-image {
position: relative;
width: 100%;
height: 8rem;
box-sizing: border-box;
margin-bottom: 0.5rem;
.image {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.product-title {
width: 100%;
font-size: 13px;
font-weight: 500;
color: #333333;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
margin-bottom: 0.33rem;
min-height: 2rem;
}
.product-desc {
position: relative;
width: 100%;
font-size: 12px;
font-weight: 400;
color: #999999;
margin-bottom: 0.1rem;
.label {
width: 60%;
}
.text {
width: 40%;
text-align: right;
}
}
.product-money {
font-size: 16px;
font-weight: 500;
color: #ff1b1b;
.label {
font-size: 12px;
font-weight: bold;
}
}
.product-store {
position: absolute;
left: 0.67rem;
bottom: 0.67rem;
.title {
width: 6rem;
font-size: 12px;
font-weight: 400;
color: #666666;
margin-left: 0.25rem;
}
}
.product-cart {
position: absolute;
right: 0.67rem;
bottom: 0.67rem;
width: 2rem;
height: 2rem;
background: #ff8b2e;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&:active,
&:hover {
filter: brightness(0.9);
}
}
`;
// 商品详情类型
type GoodsInfoListType = InterDataType<QueryGoodsInfoByCategorySub>[0];
const ProductItemView: React.FC<{
detail: GoodsInfoListType;
}> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// 获取最低价格
const getLowerPrice = (item: GoodsInfoListType) => {
const price =
item.priceStock?.reduce((a: any, b: any) =>
a.salePrice < b.salePrice ? a : b,
).salePrice || 0;
return price.toLocaleString();
};
// 跳转商品详情
const handleDetail = () => {
router.push(`/mall/product/${detail?.id}`).then();
};
return (
<ProductItemWrap onClick={handleDetail}>
<div className="product-image">
<img
className="image"
src={`${detail?.resourcesList?.at(0)
?.url}?x-oss-process=image/quality,q_20`}
alt={detail?.tradeName}
/>
</div>
<div className="product-title">{detail?.tradeName}</div>
<div className="product-desc flex-between">
<div className="label text-ellipsis">{detail?.description}</div>
<div className="text text-ellipsis">成交{detail?.id}件</div>
</div>
<div className="product-money">
{detail?.priceShow ? (
<>
<span className="label">¥</span>
<span className="num">{getLowerPrice(detail)}</span>
</>
) : (
<span className="label">咨询报价</span>
)}
</div>
<div className="product-store flex-start">
<PropertySafetyFilled style={{ color: '#FF552D' }} />
<div className="title text-ellipsis" title={detail?.companyName}>
{detail?.companyName}
</div>
</div>
<div className="product-cart">
<ShoppingCartOutlined style={{ color: '#ffffff', fontSize: '16px' }} />
</div>
</ProductItemWrap>
);
};
export default ProductItemView;
import React from 'react';
import { Pagination } from 'antd';
import styled from 'styled-components';
// 样式
const ProductListWrap = styled.div`
position: relative;
width: 100%;
.mall-list {
position: relative;
width: 100%;
min-height: 60vh;
flex-wrap: wrap;
align-items: flex-start;
}
.list-empty {
position: relative;
width: 100%;
height: 60vh;
}
.mall-pagination {
position: relative;
width: 100%;
height: 4rem;
box-sizing: border-box;
margin-bottom: 1rem;
}
`;
// 分页数据类型
type PaginationProps = {
pageNo: number;
pageSize: number;
totalCount: number;
};
const ProductListView: React.FC<{
children: React.ReactNode;
pagination: PaginationProps;
onChange: (pageNo: number, pageSize: number) => void;
}> = ({ children, pagination, onChange }) => {
return (
<ProductListWrap>
<div className="mall-list flex-start">{children}</div>
<div className="mall-pagination flex-end">
<Pagination
showSizeChanger
onChange={onChange}
defaultPageSize={pagination.pageSize}
current={pagination.pageNo}
total={pagination.totalCount}
showTotal={(total) => `共 ${total} 条`}
pageSizeOptions={['6', '12', '18', '36', '72']}
/>
</div>
</ProductListWrap>
);
};
export default ProductListView;
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Modal } from 'antd'; import { message, Modal } from 'antd';
import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { CommonAPI } from '@/api';
import { RootState } from '@/store';
import { GlobalDataState } from '@/store/module/globalData';
const QrcodeModalView = ({
open,
onCancel,
}: {
open: boolean;
onCancel: () => void;
}) => {
// system
const globalData = useSelector(
(state: RootState) => state.globalData,
) as GlobalDataState;
// 登录二维码的地址
const [qrCodeData, setQrCodeData] = useState<string>();
// 获取登录二维码
const getQrcodeLogin = async () => {
// 获取二维码
const res = await CommonAPI.getAppletQRCode({
page: globalData?.qrcodeModalPath || 'pages/welcome/index',
scene: globalData?.qrcodeModalScene || 'type=share',
});
if (res && res.code === '200') {
if (!res.result) {
message.warning('获取登录二维码失败');
return;
}
// 设置当前登录的二维码
setQrCodeData(`data:image/png;base64,${res.result}`);
}
};
// 组件挂载
useEffect(() => {
if (!open) return;
getQrcodeLogin().then();
}, [open]);
return (
<Modal
title="提示"
open={open}
footer={null}
onCancel={onCancel}
width={350}
>
<QrcodeModalWrap>
<div className="qrcode">
{qrCodeData && (
<img
className="animate__animated animate__faster animate__fadeIn"
style={{ width: '100%', height: '100%' }}
src={qrCodeData}
alt="云享飞小程序"
/>
)}
</div>
<div className="title">请前往小程序继续操作</div>
</QrcodeModalWrap>
</Modal>
);
};
export default QrcodeModalView;
export const QrcodeModalWrap = styled.div` export const QrcodeModalWrap = styled.div`
position: relative; position: relative;
...@@ -14,11 +78,11 @@ export const QrcodeModalWrap = styled.div` ...@@ -14,11 +78,11 @@ export const QrcodeModalWrap = styled.div`
padding-top: 1rem; padding-top: 1rem;
.qrcode { .qrcode {
width: 12.68rem; width: 12.68rem;
height: 15.68rem; height: 12.68rem;
background-image: url('https://file.iuav.com/file/sharefly-qrcode-wx.jpg'); //background-image: url('https://file.iuav.com/file/sharefly-qrcode-wx.jpg');
//background-size: 100% 100%; //background-size: 100% 100%;
background-size: cover; //background-size: cover;
background-position: center; //background-position: center;
} }
.title { .title {
color: #000; color: #000;
...@@ -27,21 +91,3 @@ export const QrcodeModalWrap = styled.div` ...@@ -27,21 +91,3 @@ export const QrcodeModalWrap = styled.div`
font-weight: bold; font-weight: bold;
} }
`; `;
const QrcodeModalView = ({
open,
onCancel,
}: {
open: boolean;
onCancel: () => void;
}) => {
return (
<Modal title="提示" open={open} footer={null} onCancel={onCancel}>
<QrcodeModalWrap>
<div className="qrcode"></div>
<div className="title">功能正在完善中</div>
<div className="title">请前往小程序以获得更好体验</div>
</QrcodeModalWrap>
</Modal>
);
};
export default QrcodeModalView;
import React, { useState } from 'react';
import { Button, message, Popover } from 'antd';
import { PopoverProps } from 'antd/es/popover';
import { CommonAPI } from '@/api';
const QrcodePopover: React.FC<{
children?: React.ReactNode;
text?: string;
path?: string;
scene?: string;
placement?: PopoverProps['placement'];
}> = ({ children, text, path, scene, placement }) => {
QrcodePopover.defaultProps = {
path: 'pages/welcome/index',
scene: 'type=share',
placement: 'bottomRight',
};
// 登录二维码的地址
const [qrCodeData, setQrCodeData] = useState<string>();
// 获取登录二维码
const getQrcodeLogin = async () => {
// 获取二维码
const res = await CommonAPI.getAppletQRCode({
page: path || 'pages/welcome/index',
scene: scene || 'type=share',
});
if (res && res.code === '200') {
if (!res.result) {
message.warning('获取登录二维码失败');
return;
}
// 设置当前登录的二维码
setQrCodeData(`data:image/png;base64,${res.result}`);
}
};
// 开启回调
const handleOpenChange = async (e: boolean) => {
if (e) {
// 如果获取成功就不再获取
if (qrCodeData) return;
// 如果开启,就获取二维码
await getQrcodeLogin();
}
};
const content = (
<div className="flex-center" style={{ flexDirection: 'column' }}>
<div style={{ width: '12.5rem', height: '12.5rem' }}>
{qrCodeData && (
<img
className="animate__animated animate__faster animate__fadeIn"
style={{ width: '12.5rem', height: '12.5rem' }}
src={qrCodeData}
alt="云享飞小程序"
/>
)}
</div>
<div style={{ marginTop: '1rem' }}>请前往小程序进行继续操作</div>
</div>
);
return (
<Popover
content={content}
trigger={'click'}
placement={placement || 'bottomRight'}
onOpenChange={handleOpenChange}
style={{ display: 'none' }}
>
<div style={{ display: 'none' }}>
{children && children}
{text && (
<Button
type="link"
style={{ color: '#3366cc', padding: 0, margin: 0 }}
>
{text}
</Button>
)}
</div>
</Popover>
);
};
export default QrcodePopover;
import React, { useEffect, useState } from 'react';
import { Empty } from 'antd';
import styled from 'styled-components';
import { InterListType, InterReqType } from '@/api/interface';
import {
GetIndustryListPagesType,
GetListAPPCompanyInspectionPageType,
} from '@/api/interface/service';
import { ServiceAPI } from '@/api/modules/service';
import BreadcrumbView from '@/components/breadcrumb';
import CategorySelectView, { CategoryType } from '@/components/categorySelect';
import LayoutView from '@/components/layout';
import ProductListView from '@/components/productList';
import ServiceItemView from '@/components/serviceItem';
// 分类列表类型
type CategoryListType = InterListType<GetIndustryListPagesType>;
// 请求参数类型
type ReqType = InterReqType<GetListAPPCompanyInspectionPageType>;
// 服务列表
type ListType = InterListType<GetListAPPCompanyInspectionPageType>;
const ServiceView: React.FC<{
categoryList: CategoryListType;
}> = (props) => {
// 分类列表
const [categoryList, setCategoryList] = useState<CategoryType>([]);
// 转换分类列表
const getCategoryList = () => {
setCategoryList(
props?.categoryList
?.filter((i) => i?.inspectionDTOS?.length)
?.map((i) => ({
value: i.id,
label: i.typeName,
children: i?.inspectionDTOS?.map((n) => ({
value: n.id,
label: n.inspectionName,
})),
})),
);
};
// 分页数据
const [pagination, setPagination] = useState({
pageNo: 1,
pageSize: 18,
totalCount: 0,
});
// 服务列表
const [inspectionList, setInspectionList] = useState<ListType>([]);
// 获取服务列表
const getListAPPCompanyInspectionPage = async (data: ReqType) => {
const res = await ServiceAPI.getListAPPCompanyInspectionPage({
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
...data,
});
if (res && res.code === '200') {
const { list, totalCount, pageNo, pageSize } = res.result;
setInspectionList(list || []);
setPagination({
...pagination,
totalCount,
pageNo,
pageSize,
});
}
};
// 分类筛选
const handleSelect = async (e: { main?: number; second?: number[] }) => {
if (e.second?.length && e.second?.length > 1) {
await getListAPPCompanyInspectionPage({ industryTypeId: e?.main });
} else {
await getListAPPCompanyInspectionPage({ inspectionId: e?.second?.[0] });
}
};
// 翻页回调
const handlePageChange = async (pageNo: number, pageSize: number) => {
await getListAPPCompanyInspectionPage({ pageNo, pageSize });
};
// 组件挂载
useEffect(() => {
if (!props) return;
getCategoryList();
}, [props]);
return (
<LayoutView>
<ServiceWrap>
{/* 面包屑 */}
<BreadcrumbView />
{/* 类型筛选 */}
<CategorySelectView
allText="全部服务"
list={categoryList}
isMultiple={false}
onSelect={handleSelect}
/>
{/* 产品列表 */}
<ProductListView pagination={pagination} onChange={handlePageChange}>
{inspectionList?.length ? (
inspectionList?.map((i, j) => (
<ServiceItemView key={j} detail={i} />
))
) : (
<div className="list-empty flex-center">
<Empty />
</div>
)}
</ProductListView>
</ServiceWrap>
</LayoutView>
);
};
export default ServiceView;
export const ServiceWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
`;
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { GetCompanyInspectionById } from '@/api/interface/service';
import ProductSwiperView from '@/components/product-swiper';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
import { UserInfoState } from '@/store/module/userInfo';
import { formatMoney } from '@/utils/money';
// 商品详情类型
type DetailType = InterDataType<GetCompanyInspectionById>;
const ServiceHeadView: React.FC<{ detail: DetailType }> = ({ detail }) => {
// 当前的路由数据
// const router = useRouter();
// store
const dispatch = useDispatch();
// userInfo
const userInfo = useSelector((state: any) => state.userInfo) as UserInfoState;
// globalData
const globalData = useSelector(
(state: any) => state.globalData,
) as GlobalDataState;
// 提交事件
const handleSubmit = async (type: number) => {
// 判断是否登录
if (!userInfo?.id) {
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
return;
}
// 立即购买
if (type === 1) {
dispatch(
setGlobalData({
qrcodeModalVisible: true,
qrcodeModalPath: 'page-service/service-flyer/index',
qrcodeModalScene: `id=${Number(detail?.id)}`,
}),
);
return;
}
// 在线沟通
if (type === 2) {
dispatch(
setGlobalData({
qrcodeModalVisible: true,
qrcodeModalPath: 'page-service/service-detail/index',
qrcodeModalScene: `id=${Number(detail?.id)}`,
}),
);
}
};
// 转换swiper图片
const getSwiperList = () => {
return detail?.inspectionFileDTOS
?.filter((i) => i.fileType !== 1)
?.sort((a, b) => b.first - a.first)
?.map((i) => ({ id: i.id, url: i.fileUrl }));
};
// 获取商品的单位
const getPriceUnit = () => {
const unit = globalData?.priceUnitList?.find(
(i) => i.id === detail?.inspectionPriceUnitId,
);
return unit?.unitName || '次';
};
return (
<ServiceHeadWrap>
<div className="product-swiper">
<ProductSwiperView list={getSwiperList()} />
</div>
<div className="product-content">
<div className="content-title">
【{detail?.industryTypeDTO?.typeName}】
{detail?.inspectionDTO?.inspectionName}
</div>
<div className="content-price flex-start">
<div className="price-label">价格</div>
<div className="price-money">
{detail?.price ? (
<>
<span className="label">¥</span>
<span
className="num"
title={`${formatMoney(detail?.price)}元起每${getPriceUnit()}`}
>
{formatMoney(detail?.price)}
</span>
<span
className="unit text-ellipsis"
title={`${formatMoney(detail?.price)}元起每${getPriceUnit()}`}
>
起/{getPriceUnit()}
</span>
</>
) : (
<span className="label">咨询报价</span>
)}
</div>
</div>
<div className="content-item flex-start">
<div className="item-label">团队</div>
<div className="item-content flex-start">
<img
className="team-label"
src="https://file.iuav.com/file/sharefly-service-label01.png"
alt="专业飞手"
/>
<div className="team-text">飞手已通过认证培训</div>
</div>
</div>
<div className="content-item flex-start item-bottom">
<div className="item-label">服务</div>
<div className="item-content flex-start">
<div className="item-tag">7x24小时服务</div>
<div className="item-tag">已售{Math.floor(detail.id * 2.22)}</div>
</div>
</div>
<div className="content-action flex-start select-none">
<div className="action-item" onClick={() => handleSubmit(2)}>
在线沟通
</div>
<div className="action-item" onClick={() => handleSubmit(1)}>
马上预约
</div>
</div>
</div>
</ServiceHeadWrap>
);
};
export default ServiceHeadView;
// 样式
const ServiceHeadWrap = styled.div`
position: relative;
width: calc((100% - 0.83rem) / 10 * 7.5);
min-height: 28rem;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
box-sizing: border-box;
margin-right: 0.83rem;
display: flex;
align-items: flex-start;
justify-content: flex-start;
padding: 2rem 1rem 2rem 1rem;
.product-swiper {
position: relative;
width: 22rem;
height: 26rem;
box-sizing: border-box;
}
.product-content {
position: relative;
width: calc(100% - 22rem);
height: 100%;
box-sizing: border-box;
padding: 0 0 0 1rem;
//background: lightblue;
.content-title {
width: 100%;
font-size: 24px;
font-weight: 500;
color: #212121;
margin-bottom: 0.71rem;
// 双行省略
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
}
.content-desc {
font-weight: 400;
color: #666666;
margin-bottom: 0.71rem;
}
.content-price {
position: relative;
width: 100%;
background: #f3f3f3;
box-sizing: border-box;
padding: 0.8rem;
margin-bottom: 1rem;
align-items: baseline;
.price-label {
width: 2.5rem;
color: #999999;
text-align: justify;
text-align-last: justify;
margin-right: 1rem;
transform: translateY(-0.1rem);
}
.price-money {
font-size: 24px;
//line-height: 1;
color: #ff6700;
.label {
font-size: 18px;
}
.unit {
font-size: 13px;
color: #999999;
margin-left: 0.5rem;
}
}
}
.content-item {
margin-bottom: 1rem;
padding: 0 0.8rem;
//flex-wrap: nowrap;
.item-label {
width: 2.5rem;
color: #999999;
text-align: justify;
text-align-last: justify;
margin-right: 1rem;
}
.item-content {
width: calc(100% - 3.5rem);
.team-label {
height: 2rem;
width: 6rem;
object-fit: contain;
margin-right: 0.5rem;
}
.team-text {
color: #666666;
}
.item-tag {
box-sizing: border-box;
background: #f4f4f4;
border-radius: 0.13rem;
padding: 0 0.33rem 0 0.33rem;
color: #333333;
&:not(:last-child) {
margin-right: 0.5rem;
}
}
.content-address {
font-size: 12px;
&:first-child {
margin-bottom: 0.3rem;
.text {
color: #8e8e8e;
}
}
.text {
margin-left: 0.3rem;
font-weight: 400;
}
}
.content-spec {
//min-width: max-content;
height: 2rem;
background: #f2f2f2;
border-radius: 0.08rem;
text-align: center;
line-height: 2rem;
font-weight: 400;
box-sizing: border-box;
padding: 0 1rem;
border: 0.04rem solid #f2f2f2;
margin: 0 0.5rem 0.5rem 0;
&:last-child {
margin-right: 0;
}
}
.spec-active {
background: #ffede8;
border: 0.04rem solid #ff552d;
color: #ff552d;
}
}
}
.item-bottom {
margin-bottom: 6.8rem;
}
.content-action {
//position: absolute;
//left: 1rem;
//bottom: 0;
position: relative;
width: 100%;
margin-top: 2rem;
.action-item {
width: 8.63rem;
height: 2.33rem;
background: #fff0e5;
border: 0.04rem solid #ff552d;
text-align: center;
line-height: 2.33rem;
font-size: 13px;
font-weight: 400;
color: #ff552d;
cursor: pointer;
&:hover,
&:active {
filter: brightness(0.95);
}
}
.action-item:not(:last-child) {
margin-right: 0.8rem;
}
.action-item:last-child {
background: #ff552d;
color: #fff;
}
}
}
`;
import React from 'react';
import { PhoneOutlined, ShopOutlined } from '@ant-design/icons';
import { Button, Rate } from 'antd';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import {
GetCompanyInfoById,
GetCompanyInspectionById,
} from '@/api/interface/service';
import QrcodePopover from '@/components/qrcodePopover';
// 商品详情类型
type DetailType = InterDataType<GetCompanyInspectionById>;
// 商城详情类型
type StoreType = InterDataType<GetCompanyInfoById>;
const ServiceStoreView: React.FC<{ detail: DetailType; store: StoreType }> = ({
store,
}) => {
return (
<ServiceStoreWrap>
<div className="store-card flex-start">
<img
className="image"
src={store?.brandLogo}
alt={store?.companyName}
/>
<div className="card-content">
<div className="title">{store?.companyName}</div>
<div className="star flex-start">
<div className="tag select-none">店铺星级</div>
<Rate allowHalf defaultValue={5} style={{ fontSize: '10px' }} />
</div>
<div className="text two-line-ellipsis" title={store?.content}>
{store?.content}
</div>
</div>
</div>
<div className="store-item flex-start">
<div className="item-label">地址:</div>
<div className="item-value">{store?.address}</div>
</div>
<div className="store-item flex-start">
<div className="item-label">电话:</div>
<div className="item-value">{store?.phoneNum || '18626051369'}</div>
</div>
<div className="store-action flex-start">
<QrcodePopover
path="page-service/service-store/index"
scene={`id=${Number(store?.id)}`}
>
<Button className="action-item flex-start">
<ShopOutlined style={{ color: '#FF552D' }} />
<div className="text">进店逛逛</div>
</Button>
</QrcodePopover>
<QrcodePopover
path="page-service/service-store/index"
scene={`id=${Number(store?.id)}`}
>
<Button className="action-item flex-start">
<PhoneOutlined
style={{ color: '#FF552D', transform: 'rotateY(180deg)' }}
/>
<div className="text">联系方式</div>
</Button>
</QrcodePopover>
</div>
</ServiceStoreWrap>
);
};
export default ServiceStoreView;
// 样式
const ServiceStoreWrap = styled.div`
position: relative;
width: calc((100% - 0.83rem) / 10 * 2.5);
min-height: 18rem;
//height: 100%;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
box-sizing: border-box;
padding: 1rem;
.store-card {
width: 100%;
min-height: 8em;
background: linear-gradient(270deg, #5f5f5f 0%, #060606 100%);
border-radius: 0.33rem;
box-sizing: border-box;
padding: 0.58rem 1rem;
margin-bottom: 1rem;
align-items: flex-start;
.image {
width: 3.25rem;
height: 3.25rem;
border: 0.02rem solid #e3e3e3;
margin-right: 0.67rem;
}
.card-content {
position: relative;
width: calc(100% - 3.25rem - 0.67rem);
.title {
font-weight: 500;
color: #ffffff;
}
.star {
margin-bottom: 0.5rem;
.tag {
position: relative;
height: 1.2rem;
line-height: 1rem;
background: #fff3ee;
border-radius: 0.13rem;
border: 0.02rem solid #ff552d;
box-sizing: border-box;
padding: 0 0.33rem;
text-align: center;
font-size: 10px;
color: #ff552d;
margin-right: 0.25rem;
transform: scale(0.8);
}
}
.text {
font-size: 12px;
font-weight: 400;
color: rgba(255, 255, 255, 0.8);
}
}
}
.store-item {
align-items: flex-start;
flex-wrap: nowrap;
margin-bottom: 1rem;
.item-label {
width: 3rem;
color: #666666;
}
.item-value {
width: calc(100% - 3rem);
font-size: 12px;
font-weight: 400;
color: #666666;
}
}
.store-action {
position: relative;
width: 100%;
.action-item {
position: relative;
background: #ffffff;
box-sizing: border-box;
padding: 0 1rem;
border: 0.04rem solid #a8a8a8;
cursor: pointer;
margin-right: 0.83rem;
.text {
color: #a8a8a8;
font-weight: 400;
font-size: 12px;
margin-left: 0.33rem;
}
&:not(:last-child) {
margin-right: 0.83rem;
}
&:last-child {
border: 0.04rem solid #ff552d;
.text {
color: #ff552d;
}
}
&:hover {
filter: brightness(0.95);
}
}
}
`;
import React from 'react';
import { PropertySafetyFilled, ShoppingCartOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { InterListType } from '@/api/interface';
import { GetListAPPCompanyInspectionPageType } from '@/api/interface/service';
import { GlobalDataState } from '@/store/module/globalData';
import { formatMoney } from '@/utils/money';
// 商品详情类型
type GoodsInfoListType = InterListType<GetListAPPCompanyInspectionPageType>[0];
const ServiceItemView: React.FC<{
detail: GoodsInfoListType;
}> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// globalData
const globalData = useSelector(
(state: any) => state.globalData,
) as GlobalDataState;
// 跳转商品详情
const handleDetail = () => {
router.push(`/service/detail/${detail?.id}`).then();
};
// 获取商品的单位
const getPriceUnit = () => {
const unit = globalData?.priceUnitList?.find(
(i) => i.id === detail?.inspectionPriceUnitId,
);
return unit?.unitName || '次';
};
return (
<ProductItemWrap onClick={handleDetail}>
<div className="product-image">
<img
className="image"
src={`${detail?.inspectionFirstImg}?x-oss-process=image/quality,q_10`}
alt="图片"
/>
</div>
<div className="product-title">
【{detail?.industryTypeDTO?.typeName}】
{detail?.inspectionDTO?.inspectionName}
</div>
<div className="product-desc flex-between">
<div className="money">
{detail?.price ? (
<>
<span className="label">¥</span>
<span
className="num"
title={`${formatMoney(detail?.price)}元起每${getPriceUnit()}`}
>
{formatMoney(detail?.price)}
</span>
<div
className="unit text-ellipsis"
title={`${formatMoney(detail?.price)}元起每${getPriceUnit()}`}
>
起/{getPriceUnit()}
</div>
</>
) : (
<span className="label">咨询报价</span>
)}
</div>
<div className="text text-ellipsis">
成交{Math.floor(detail.id * 3.14)}件
</div>
</div>
<div className="product-store flex-start">
<PropertySafetyFilled style={{ color: '#FF552D' }} />
<div className="title text-ellipsis" title={detail?.companyName}>
{detail?.companyName}
</div>
</div>
<div className="product-cart">
<ShoppingCartOutlined style={{ color: '#ffffff', fontSize: '16px' }} />
</div>
</ProductItemWrap>
);
};
export default ServiceItemView;
const ProductItemWrap = styled.div`
position: relative;
box-sizing: border-box;
width: calc((100% - (0.83rem * 5)) / 6);
height: 16rem;
border: 0.02rem solid #e3e3e3;
margin: 0 0.83rem 0.83rem 0;
padding: 0.67rem;
cursor: pointer;
&:nth-child(6n) {
margin-right: 0;
}
&:active,
&:hover {
background: #fff9f6;
border: 0.04rem solid #ff7a3e;
}
.product-image {
position: relative;
width: 100%;
height: 8rem;
box-sizing: border-box;
margin-bottom: 0.5rem;
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.product-title {
width: 100%;
font-size: 13px;
font-weight: 500;
color: #333333;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
margin-bottom: 0.33rem;
min-height: 2rem;
}
.product-desc {
position: relative;
width: 100%;
font-size: 12px;
font-weight: 400;
color: #999999;
margin-bottom: 0.1rem;
align-items: baseline;
.label {
width: 60%;
}
.text {
width: 40%;
text-align: right;
}
.money {
font-size: 16px;
font-weight: 500;
color: #ff1b1b;
.label {
font-size: 12px;
font-weight: bold;
}
.num {
max-width: 60%;
}
.unit {
margin-left: 0.68rem;
font-size: 10px;
font-weight: 400;
color: #999999;
}
}
}
.product-store {
position: absolute;
left: 0.67rem;
bottom: 0.67rem;
.title {
width: 6rem;
font-size: 12px;
font-weight: 400;
color: #666666;
margin-left: 0.25rem;
}
}
.product-cart {
position: absolute;
right: 0.67rem;
bottom: 0.67rem;
width: 2rem;
height: 2rem;
background: #ff8b2e;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&:active,
&:hover {
filter: brightness(0.9);
}
}
`;
import React, { useEffect, useState } from 'react';
import { EnvironmentOutlined, ReloadOutlined } from '@ant-design/icons';
import { Button, Radio, RadioChangeEvent, Space } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { UserAPI } from '@/api';
import { InterDataType } from '@/api/interface';
import { UserAddressSelectList } from '@/api/interface/user';
import QrcodePopover from '@/components/qrcodePopover';
import { setGlobalData } from '@/store/module/globalData';
import { SystemState } from '@/store/module/system';
import { UserInfoState } from '@/store/module/userInfo';
// 列表类型
type ListType = InterDataType<UserAddressSelectList>;
const SubmitAddressView = () => {
// store
const dispatch = useDispatch();
// system
const system = useSelector((state: any) => state.system) as SystemState;
// userInfo
const userInfo = useSelector((state: any) => state.userInfo) as UserInfoState;
// 收货地址列表
const [addressList, setAddressList] = useState<ListType>();
// 当前选中的地址
const [currentValue, setCurrentValue] = useState<number>();
// 获取用户的收货地址
const getUserAddressList = async () => {
const res = await UserAPI.userAddressSelectList({
userAccountId: userInfo?.id,
});
if (res && res.code === '200') {
setAddressList(res.result || []);
// 设置默认地址
setCurrentValue(res.result?.find((i) => i.type === 0)?.id);
// 将地址列表存入store
dispatch(
setGlobalData({
userAddressList: res.result,
userAddressSelectId: res.result?.find((i) => i.type === 0)?.id,
}),
);
}
};
// 转换地址
const transformAddress = (address: string) => {
return address?.split('/');
};
// 选择地址事件
const handleSelect = (e: RadioChangeEvent) => {
setCurrentValue(e?.target?.value);
// 将地址列表存入store
dispatch(
setGlobalData({
userAddressSelectId: e?.target?.value,
}),
);
};
// 组件挂载
useEffect(() => {
if (!system?.token) {
// 请先完成登录
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
} else {
getUserAddressList().then();
}
}, [system?.token]);
return (
<SubmitAddressWrap>
<div className="address-title flex-between">
<div className="title flex-baseline">
<span>确认收货地址</span>
<Button
type="link"
icon={<ReloadOutlined />}
onClick={getUserAddressList}
>
刷新地址
</Button>
</div>
<QrcodePopover
text="管理收货地址"
path="page-mine/address-management/index"
/>
</div>
{!addressList?.length && (
<div className="address-none flex-start">
<EnvironmentOutlined style={{ color: '#ff6700', fontSize: '13px' }} />
<div className="text">
暂无地址,请打开手机端【云享飞】微信小程序,【我的】-【个人设置】-【地址管理】添加
</div>
</div>
)}
<Radio.Group value={currentValue} onChange={(e) => handleSelect(e)}>
<Space direction="vertical">
{addressList?.map((i, j) => (
<div
className={`address-item ${
i?.id === currentValue && 'item-active'
} flex-start`}
key={j}
>
<div className="label">
{i?.id === currentValue && (
<EnvironmentOutlined
style={{ color: '#ff6700', fontSize: '13px' }}
/>
)}
</div>
<Radio value={i.id}>
{transformAddress(i?.takeRegion)?.map((n, m) => (
<span className="text" key={m}>
{n}
</span>
))}
<span className="text">{i?.takeAddress}</span>
<span className="text">{i?.takeName} 收)</span>
<span className="text">{i?.takePhone}</span>
{i?.type === 0 && <span className="text">默认地址</span>}
</Radio>
</div>
))}
</Space>
</Radio.Group>
</SubmitAddressWrap>
);
};
export default SubmitAddressView;
// 样式
const SubmitAddressWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
padding-bottom: 2rem;
.address-title {
position: relative;
width: 100%;
border-bottom: 2px solid #f1f1f1;
box-sizing: border-box;
padding: 1rem 0 0.5rem 0;
margin-bottom: 1rem;
.title {
font-size: 18px;
font-weight: bold;
color: #333333;
}
.action {
font-weight: 400;
color: #3366cc;
cursor: pointer;
}
}
.address-none {
position: relative;
width: 100%;
height: 2.5rem;
line-height: 2.5rem;
background: #fff1e8;
border: 0.04rem solid #ff552d;
box-sizing: border-box;
padding: 0 0.58rem;
.text {
margin-left: 0.5rem;
}
}
.address-item {
position: relative;
width: 100%;
height: 2.5rem;
box-sizing: border-box;
padding: 0 0.5rem;
.text {
margin-right: 0.3rem;
}
.ant-radio + span {
color: #666666;
}
.ant-wave-target {
margin-right: 0.3rem;
}
.label {
position: relative;
width: 4rem;
}
}
.item-active {
background: #fff1e8;
border: 0.04rem solid #ff552d;
.ant-radio + span {
color: #000;
}
.label::after {
position: absolute;
top: -0.3rem;
right: 0.5rem;
content: '寄送至';
font-size: 13px;
color: #ff552d;
}
}
.ant-radio-group,
.ant-space {
width: 100%;
}
`;
import React, { useEffect, useState } from 'react';
import Big from 'big.js';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { AppMallGoodsDetails } from '@/api/interface/mall';
import NumberBox from '@/components/numberBox';
import { setGlobalData } from '@/store/module/globalData';
import { formatMoney } from '@/utils/money';
// 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>;
const SubmitProductView: React.FC<{ detail: DetailType }> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 商品的购买数量
const [productNum, setProductNum] = useState<number>(1);
// 获取当前选择的规格
const getCurrentSpec = () => {
const { specId } = router.query;
const item = detail?.priceStock?.find((i) => i?.id === Number(specId));
// 找不到就用默认的
return item || detail?.priceStock?.at(0);
};
// 转换规格展示
const getCurrentSpecText = () => {
const productSpec = getCurrentSpec()?.productSpec || undefined;
if (!productSpec) return '请选择规格';
return Object.entries(JSON.parse(productSpec))
?.map((i) => `【${i[0]}${i[1]}`)
?.join(' +');
};
// 获取购买的价格
const getCurrentPrice = () => {
return Big(getCurrentSpec()?.salePrice || 1)
.mul(Big(productNum))
?.toFixed(2)
?.toLocaleString();
};
// 设置购买数量
const handleProductNum = (value: any) => {
setProductNum(value);
// 将购买数量存入store
dispatch(
setGlobalData({
productSpecNum: value,
}),
);
};
// 组件挂载
useEffect(() => {
// 设置购买数量
if (router?.query?.num) {
setProductNum(Number(router?.query?.num));
// 将购买数量存入store
dispatch(
setGlobalData({
productSpecNum: Number(router?.query?.num),
}),
);
}
}, [router?.query]);
return (
<SubmitProductWrap>
<div className="submit-title flex-between">
<div className="title">确认订单信息</div>
</div>
<div className="submit-td flex-start">
<div className="item">宝贝</div>
<div className="item">单价</div>
<div className="item">数量</div>
<div className="item">合计</div>
</div>
<div className="submit-tr flex-start">
<div className="item submit-product flex-start">
<img
src={detail?.resourcesList?.at(0)?.url}
alt={detail?.tradeName}
className="product-img"
/>
<div className="product-content">
<div className="title two-line-ellipsis">{detail?.tradeName}</div>
<div className="desc">已选:{getCurrentSpecText()}</div>
</div>
</div>
<div className="item">{formatMoney(getCurrentSpec()?.salePrice)}</div>
<div className="item">
<NumberBox
min={1}
max={9999}
precision={0}
value={productNum}
onChange={handleProductNum}
/>
</div>
<div className="item submit-money">
<span className="label"></span>
<span className="num">{getCurrentPrice()}</span>
</div>
</div>
</SubmitProductWrap>
);
};
export default SubmitProductView;
// 样式
const SubmitProductWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
padding-bottom: 1rem;
.submit-title {
position: relative;
width: 100%;
//border-bottom: 2px solid #f1f1f1;
box-sizing: border-box;
padding: 1rem 0 0.5rem 0;
.title {
font-size: 18px;
font-weight: bold;
color: #333333;
}
.action {
font-weight: 400;
color: #3366cc;
cursor: pointer;
}
}
.submit-td,
.submit-tr {
position: relative;
width: 100%;
min-height: 2rem;
line-height: 2rem;
border-bottom: 0.02rem solid #1f8afe;
flex-wrap: nowrap;
.item {
text-align: center;
color: #666666;
font-weight: bold;
&:nth-child(1) {
width: 40%;
}
&:not(:nth-child(1)) {
width: calc((100% - 40%) / 3);
}
}
}
.submit-tr {
border: none;
.item {
display: flex;
align-items: center;
justify-content: center;
min-height: 3.33rem;
font-weight: normal;
}
.submit-product {
width: 100%;
justify-content: flex-start;
box-sizing: border-box;
padding: 0.67rem 0.5rem;
.product-img {
width: 3.33rem;
height: 3.33rem;
}
.product-content {
padding-left: 0.5rem;
text-align: left;
.title {
font-weight: bold;
color: #333333;
}
.desc {
font-size: 12px;
font-weight: 400;
color: #666666;
line-height: normal;
}
}
}
.submit-money {
font-size: 18px;
//line-height: 1;
color: #ff6700;
.label {
font-size: 14px;
}
}
}
`;
import React from 'react';
import { CheckCircleOutlined } from '@ant-design/icons';
import { Modal } from 'antd';
import { useRouter } from 'next/router';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { RootState } from '@/store';
import { GlobalDataState } from '@/store/module/globalData';
const ToastModalView: React.FC<{
open: boolean;
onCancel: () => void;
}> = ({ open, onCancel }) => {
// 路由钩子
const router = useRouter();
// system
const globalData = useSelector(
(state: RootState) => state.globalData,
) as GlobalDataState;
// 关闭弹窗
const handleClose = () => {
onCancel?.();
// 如果需要返回
if (globalData.toastModalBack) router.back();
};
return (
<Modal
open={open}
title={'提示'}
onCancel={handleClose}
width={400}
footer={null}
>
<ToastModalWrap>
<CheckCircleOutlined style={{ color: '#07c160', fontSize: 68 }} />
<div className="toast-title">订单提交成功</div>
</ToastModalWrap>
</Modal>
);
};
export default ToastModalView;
// 样式
const ToastModalWrap = styled.div`
position: relative;
width: 100%;
height: 16.8rem;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
box-sizing: border-box;
padding-top: 3rem;
.toast-title {
font-size: 20px;
font-weight: bold;
margin-top: 2.5rem;
}
`;
import React from 'react'; import React from 'react';
import { ConfigProvider, theme } from 'antd'; import { ConfigProvider, theme } from 'antd';
// eslint-disable-next-line import/order import zhCN from 'antd/locale/zh_CN';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import '../styles/animate.css'; import '../styles/animate.css';
import '../styles/globals.css'; import '../styles/globals.css';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { wrapper } from '@/store';
import themeConfig from '../theme/themeConfig'; import themeConfig from '../theme/themeConfig';
const App = ({ Component, pageProps }: AppProps) => ( const App = ({ Component, ...rest }: AppProps) => {
const { store, props } = wrapper.useWrappedStore(rest);
return (
<Provider store={store}>
{/* eslint-disable-next-line no-underscore-dangle */}
<PersistGate persistor={(store as any)?.__persistor} loading={null}>
<ConfigProvider <ConfigProvider
locale={zhCN}
theme={{ theme={{
algorithm: theme.compactAlgorithm, algorithm: theme.compactAlgorithm,
...themeConfig, ...themeConfig,
}} }}
> >
<Component {...pageProps} /> <Component {...props.pageProps} />
</ConfigProvider> </ConfigProvider>
); </PersistGate>
</Provider>
);
};
export default App; export default App;
...@@ -17,6 +17,8 @@ const MyDocument = () => ( ...@@ -17,6 +17,8 @@ const MyDocument = () => (
{/* eslint-disable-next-line */} {/* eslint-disable-next-line */}
<title>云享飞</title> <title>云享飞</title>
{/* <title>【云享飞 iuav.com】无人机分类信息 - 让世界为天空所用</title> */} {/* <title>【云享飞 iuav.com】无人机分类信息 - 让世界为天空所用</title> */}
{/* 强制使用浅色模式 */}
<meta name="color-scheme" content="light" />
</Head> </Head>
<body> <body>
<Main /> <Main />
......
import React from 'react';
import { Input, Modal } from 'antd';
import Big from 'big.js';
import { GetServerSidePropsContext } from 'next';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { AppMallGoodsDetails } from '@/api/interface/mall';
import { MallAPI } from '@/api/modules/mall';
import BreadcrumbView from '@/components/breadcrumb';
import LayoutView from '@/components/layout';
import PaymentCheckout from '@/components/payment-checkout';
import SubmitAddressView from '@/components/submit-comp/submit-address';
import SubmitProductView from '@/components/submit-comp/submit-product';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
import { formatMoney } from '@/utils/money';
// 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>;
// 每次加载页面都会执行
export async function getServerSideProps(context: GetServerSidePropsContext) {
// 商品id
const id: number = Number(context.params?.id);
// 商品详情
let productDetail: DetailType | undefined;
// 获取商品详情
const getMallGoodsDetails = async () => {
const res = await MallAPI.appMallGoodsDetails({ id });
if (res && res.code === '200') {
productDetail = res.result;
// console.log('获取商品详情 --->', res);
}
};
// 依次获取接口数据
await (async () => {
await getMallGoodsDetails();
})();
return { props: { id, productDetail } };
}
// 客户端组件
const MallCartSubmitView: React.FC<{
productDetail: DetailType;
}> = ({ productDetail }) => {
// 路由钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// globalData
const globalData = useSelector(
(state: any) => state.globalData,
) as GlobalDataState;
// 获取当前用户选择的地址
const getUserAddressCurrent = () => {
const item = globalData?.userAddressList?.find(
(i) => i.id === globalData?.userAddressSelectId,
);
return {
...item,
takeRegion: item?.takeRegion?.split('/')?.join(' '),
};
};
// 获取商品的结算价格
const getCheckoutPrice = () => {
const item = router?.query?.specId
? productDetail?.priceStock?.find(
(i) => i.id === Number(router?.query?.specId),
)
: productDetail?.priceStock?.at(0);
const salePrice = Big(item?.salePrice || 0);
const productSpecNum = Big(globalData?.productSpecNum || 1);
return salePrice.mul(productSpecNum)?.toNumber();
};
// 提交订单
const handleSubmit = () => {
if (!productDetail?.id) return;
Modal.confirm({
title: '提示',
content: `确认提交订单?`,
onOk: () => {
dispatch(setGlobalData({ loadingSpinnerVisible: true }));
setTimeout(() => {
dispatch(
setGlobalData({
toastModalVisible: true,
toastModalBack: true,
loadingSpinnerVisible: false,
}),
);
}, 1000);
},
});
// const { id, specId, type } = router.query as { [key: string]: string };
// const path = `k=${id}&sId=${specId}&sNum=${
// globalData?.productSpecNum || 1
// }&t=${type}`;
// dispatch(
// setGlobalData({
// qrcodeModalVisible: true,
// qrcodeModalPath:
// path.length >= 32
// ? 'page-product/product-detail/index'
// : 'page-order/product-confirm/index',
// qrcodeModalScene: path.length >= 32 ? `id=${id}` : path,
// }),
// );
};
return (
<LayoutView>
<MallCartSubmitWrap>
<BreadcrumbView />
<SubmitAddressView />
<SubmitProductView detail={productDetail} />
<div className="submit-remark flex-start">
<div className="remark-view flex-start">
<div className="label">备注:</div>
<div className="input">
<Input.TextArea
placeholder={'请输入备注'}
maxLength={50}
showCount
/>
</div>
</div>
</div>
<div className="submit-checkout flex-start">
<div className="checkout-view">
<div className="checkout-item checkout-price flex-end">
<div className="label">实付款:</div>
<div className="unit"></div>
<div className="price">{formatMoney(getCheckoutPrice())}</div>
</div>
<div className="checkout-item flex-end">
<div className="label">寄送至:</div>
<div className="text">
{getUserAddressCurrent()?.takeRegion}{' '}
{getUserAddressCurrent()?.takeAddress}
</div>
</div>
<div className="checkout-item item-last flex-end">
<div className="label">收货人:</div>
<div className="text">
{getUserAddressCurrent()?.takeName}{' '}
{getUserAddressCurrent()?.takePhone}
</div>
</div>
<PaymentCheckout cost={getCheckoutPrice()} />
</div>
<div
className="checkout-submit select-none cursor-pointer"
onClick={handleSubmit}
>
{router?.query?.type === '1' ? '提交订单' : '提交意向'}
</div>
</div>
</MallCartSubmitWrap>
</LayoutView>
);
};
export default MallCartSubmitView;
// 样式
const MallCartSubmitWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
.submit-remark {
position: relative;
width: 100%;
min-height: 4.5rem;
background: #f2f7ff;
border: 0.04rem solid #d0eaf5;
padding: 0.67rem;
margin-bottom: 1rem;
.remark-view {
position: relative;
height: 100%;
align-items: flex-start;
.label {
font-weight: bold;
color: #333333;
}
.input {
min-height: 4rem;
width: 25rem;
}
}
}
.submit-checkout {
position: relative;
width: 100%;
padding-bottom: 2rem;
box-sizing: border-box;
align-items: flex-end;
flex-direction: column;
.checkout-view {
position: relative;
width: 25rem;
min-height: 8rem;
border: 1px solid #ff5001;
box-sizing: border-box;
display: flex;
align-items: flex-end;
justify-content: flex-start;
flex-direction: column;
padding: 0.5rem 0.67rem 1rem 0.67rem;
.checkout-item {
align-items: baseline;
margin-bottom: 0.33rem;
.label {
font-weight: bold;
color: #333333;
}
.unit {
color: #ff6700;
font-size: 16px;
}
.price {
color: #ff6700;
font-size: 24px;
font-weight: bold;
}
.text {
max-width: 86%;
}
}
.item-last {
margin-bottom: 0.66rem;
}
}
.checkout-submit {
position: relative;
width: 8.58rem;
height: 2.63rem;
background: #ff552d;
line-height: 2.63rem;
border: 0.04rem solid #ff5001;
text-align: center;
font-size: 16px;
color: #ffffff;
box-sizing: border-box;
&:active {
filter: brightness(0.9);
}
}
}
`;
import React from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import HomeBottomView from '@/components/home-comp/home-bottom';
import HomeBottomView from '@/pages/home/comp/home-bottom'; import HomeMapView from '@/components/home-comp/home-map';
import HomeBrandView from '@/pages/home/comp/home-brand'; import HomeNewsView from '@/components/home-comp/home-news';
import HomeMapView from '@/pages/home/comp/home-map'; import HomeProductView from '@/components/home-comp/home-product';
import HomeNewsView from '@/pages/home/comp/home-news'; import HomeTabView from '@/components/home-comp/home-tab';
import HomeProductView from '@/pages/home/comp/home-product'; import HomeTitleView from '@/components/home-comp/home-title';
import HomeSearchView from '@/pages/home/comp/home-search';
import HomeServiceView from '@/pages/home/comp/home-service';
import HomeTabView from '@/pages/home/comp/home-tab';
import HomeTaskView from '@/pages/home/comp/home-task';
import HomeTitleView from '@/pages/home/comp/home-title';
import { HomeWrap } from '@/pages/home/styled'; import { HomeWrap } from '@/pages/home/styled';
const HomeView = () => { const HomeView = () => {
// 路由钩子 // 路由钩子
const router = useRouter(); // const router = useRouter();
// 消息提醒 // 消息提醒
// const handleClick = async () => { // const handleClick = async () => {
// await message.success('你好'); // await message.success('你好');
...@@ -26,7 +21,7 @@ const HomeView = () => { ...@@ -26,7 +21,7 @@ const HomeView = () => {
return ( return (
<HomeWrap> <HomeWrap>
{/* 主页搜索 */} {/* 主页搜索 */}
<HomeSearchView /> {/* <HomeSearchView /> */}
{/* 网格布局 */} {/* 网格布局 */}
<div className="home-grid"> <div className="home-grid">
{/* 主页分类 */} {/* 主页分类 */}
...@@ -39,17 +34,17 @@ const HomeView = () => { ...@@ -39,17 +34,17 @@ const HomeView = () => {
{/* 弹性布局 */} {/* 弹性布局 */}
<div className="home-wrap"> <div className="home-wrap">
{/* 抢单大厅 */} {/* 抢单大厅 */}
<HomeTaskView /> {/* <HomeTaskView /> */}
{/* 品牌企业 */} {/* 品牌企业 */}
<HomeBrandView /> {/* <HomeBrandView /> */}
{/* 大家都在买 */} {/* 大家都在买 */}
<HomeTitleView title="大家都在买" /> <HomeTitleView title="大家都在买" path="/mall" />
{/* 推荐商品 */} {/* 推荐商品 */}
<HomeProductView /> <HomeProductView />
{/* 无人机服务 */} {/* 无人机服务 */}
<HomeTitleView title="无人机服务" /> {/* <HomeTitleView title="无人机服务" path="/service" /> */}
{/* 服务列表 */} {/* 服务列表 */}
<HomeServiceView /> {/* <HomeServiceView /> */}
{/* 底部标签 */} {/* 底部标签 */}
<HomeBottomView /> <HomeBottomView />
</div> </div>
......
import styled from 'styled-components'; import styled from 'styled-components';
import PageNotFoundView from '@/components/404';
export default function Style() { export default function Index() {
return <></>; return <PageNotFoundView />;
} }
export const HomeWrap = styled.div` export const HomeWrap = styled.div`
......
...@@ -4,7 +4,7 @@ import HomeView from '@/pages/home'; ...@@ -4,7 +4,7 @@ import HomeView from '@/pages/home';
const App = () => { const App = () => {
return ( return (
<LayoutView> <LayoutView placeholder={true}>
<HomeView></HomeView> <HomeView></HomeView>
</LayoutView> </LayoutView>
); );
......
import React from 'react';
import { InterDataType } from '@/api/interface';
import { GetAppCategoryInfo } from '@/api/interface/mall';
import { MallAPI } from '@/api/modules/mall';
import MallView from '@/components/mall-comp/index';
// 分类列表类型
type CategoryListType = InterDataType<GetAppCategoryInfo>;
// 每次加载页面都会执行
export async function getServerSideProps() {
// 分类数据
let categoryList: CategoryListType = [];
// 获取各个目录及分类信息
const getAppCategoryInfo = async () => {
const res = await MallAPI.getAppCategoryInfo({
type: 4,
});
if (res && res.code === '200') {
categoryList = res?.result || [];
}
};
// 依次获取接口数据
await (async () => {
await getAppCategoryInfo();
})();
return { props: { categoryList } };
}
const MallSecondView: React.FC<{
categoryList: CategoryListType;
}> = (props) => MallView(props);
export default MallSecondView;
import React from 'react';
import { InterDataType } from '@/api/interface';
import { GetAppCategoryInfo } from '@/api/interface/mall';
import { MallAPI } from '@/api/modules/mall';
import MallView from '@/components/mall-comp/index';
// 分类列表类型
type CategoryListType = InterDataType<GetAppCategoryInfo>;
// 每次加载页面都会执行
export async function getServerSideProps() {
// 分类数据
let categoryList: CategoryListType = [];
// 获取各个目录及分类信息
const getAppCategoryInfo = async () => {
const res = await MallAPI.getAppCategoryInfo({
type: 4,
});
if (res && res.code === '200') {
categoryList = res?.result || [];
}
};
// 依次获取接口数据
await (async () => {
await getAppCategoryInfo();
})();
return { props: { categoryList } };
}
const MallMainView: React.FC<{
categoryList: CategoryListType;
}> = (props) => MallView(props);
export default MallMainView;
import React, { useEffect } from 'react'; import React from 'react';
import { Button } from 'antd'; import { InterDataType } from '@/api/interface';
import { useRouter } from 'next/router'; import { GetAppCategoryInfo } from '@/api/interface/mall';
import { CommonAPI } from '@/api'; import { MallAPI } from '@/api/modules/mall';
import LayoutView from '@/components/layout'; import MallView from '@/components/mall-comp/index';
// 分类列表类型
type CategoryListType = InterDataType<GetAppCategoryInfo>;
// 每次加载页面都会执行 // 每次加载页面都会执行
export async function getServerSideProps() { export async function getServerSideProps() {
// 分类数据 // 分类数据
let categoryData = {}; let categoryList: CategoryListType = [];
// 获取分类数据 // 获取各个目录及分类信息
const getPageHomeCategories = async () => { const getAppCategoryInfo = async () => {
const res = await CommonAPI.getPageHomeCategories({ const res = await MallAPI.getAppCategoryInfo({
type: 4, type: 4,
}); });
if (res && res.code === '200') { if (res && res.code === '200') {
categoryData = res; categoryList = res?.result || [];
} }
}; };
// 依次获取接口数据 // 依次获取接口数据
await (async () => { await (async () => {
await getPageHomeCategories(); await getAppCategoryInfo();
})(); })();
return { props: { categoryData } }; return { props: { categoryList } };
} }
const MallView = (props: any) => { const MallPageView: React.FC<{
// 路由钩子 categoryList: CategoryListType;
const router = useRouter(); }> = (props) => <MallView {...props} />;
// 获取接口数据
const getCooperationListTag = async () => {
const res = await CommonAPI.cooperationListTag();
if (res && res.code === '200') {
console.log('获取接口数据 --->', res);
}
};
useEffect(() => {
getCooperationListTag().then();
console.log('执行到此处12312 --->', props);
}, [props]);
return (
<>
<LayoutView>
<div>测试</div>
<Button
onClick={() => {
router.back();
}}
>
返回
</Button>
</LayoutView>
</>
);
};
export default MallView; export default MallPageView;
import React from 'react';
import { GetServerSidePropsContext } from 'next';
import { InterDataType } from '@/api/interface';
import {
AppMallGoodsDetails,
GetCompanyInfoByBUId,
} from '@/api/interface/mall';
import { MallAPI } from '@/api/modules/mall';
import BreadcrumbView from '@/components/breadcrumb';
import LayoutView from '@/components/layout';
import ProductHeadView from '@/components/product-comp/product-head';
import ProductStoreView from '@/components/product-comp/product-store';
import { ProductWrap } from '@/pages/mall/product/styled';
// 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>;
// 商城详情类型
type StoreType = InterDataType<GetCompanyInfoByBUId>;
// 每次加载页面都会执行
export async function getServerSideProps(context: GetServerSidePropsContext) {
// 商品id
const id: number = Number(context.params?.id);
// 商品详情
let productDetail: DetailType | undefined;
// 店铺详情
let storeDetail: StoreType | undefined;
// 获取商品详情
const getMallGoodsDetails = async () => {
const res = await MallAPI.appMallGoodsDetails({ id });
if (res && res.code === '200') {
productDetail = res.result;
// console.log('获取商品详情 --->', res);
}
};
// 获取店铺详情
const getCompanyInfoById = async () => {
const res = await MallAPI.getCompanyInfoByBUId({
backUserAccountId: Number(productDetail?.userAccountId),
});
if (res && res.code === '200') {
storeDetail = res.result;
// console.log('获取店铺详情 --->', res);
}
};
// 依次获取接口数据
await (async () => {
await getMallGoodsDetails();
await getCompanyInfoById();
})();
return { props: { id, productDetail, storeDetail } };
}
const MallProductView: React.FC<{
id: number;
productDetail: DetailType;
storeDetail: StoreType;
}> = ({ productDetail, storeDetail }) => {
return (
<LayoutView>
<ProductWrap>
{/* 面包屑 */}
<BreadcrumbView />
<div className="flex-start align-start">
<ProductHeadView detail={productDetail} />
<ProductStoreView detail={productDetail} store={storeDetail} />
</div>
<div className="product-title">商品详情</div>
<div className="product-richText">
{productDetail?.goodsDetails && (
<div
className="content-html"
dangerouslySetInnerHTML={{ __html: productDetail?.goodsDetails }}
/>
)}
</div>
</ProductWrap>
</LayoutView>
);
};
export default MallProductView;
import styled from 'styled-components';
import PageNotFoundView from '@/components/404';
export default function Index() {
return <PageNotFoundView />;
}
export const ProductWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
.product-title {
position: relative;
width: 100%;
text-align: center;
padding: 1.5rem 0;
color: #989898;
font-size: 14px;
&::after,
&::before {
position: absolute;
content: '';
top: 50%;
left: 40%;
width: 4.74rem;
height: 0.02rem;
background: #d4d4d4;
}
&::before {
left: auto;
right: 40%;
}
}
.product-richText {
position: relative;
width: 100%;
margin-bottom: 3.5rem;
.content-html {
width: 100%;
img {
width: 100%;
}
}
}
`;
import styled from 'styled-components';
import PageNotFoundView from '@/components/404';
export default function Index() {
return <PageNotFoundView />;
}
export const MallWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
`;
import React from 'react';
import { InterListType } from '@/api/interface';
import { GetIndustryListPagesType } from '@/api/interface/service';
import { ServiceAPI } from '@/api/modules/service';
import ServiceView from '@/components/service-comp';
import { wrapper } from '@/store';
import { setGlobalData } from '@/store/module/globalData';
// 分类列表类型
type CategoryListType = InterListType<GetIndustryListPagesType>;
// 使用wrapper.getServerSideProps高阶函数包裹getServerSideProps函数
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async () => {
// 分类数据
let categoryList: CategoryListType = [];
// 获取各个目录及分类信息
const getIndustryListPages = async () => {
const res = await ServiceAPI.getIndustryListPages({
pageNo: 1,
pageSize: 999,
});
if (res && res.code === '200') {
categoryList = res?.result?.list || [];
}
};
// 获取价格单位列表
const getPriceUnitList = async () => {
const res = await ServiceAPI.listInspectionPriceUnit();
if (res && res.code === '200') {
store.dispatch(setGlobalData({ priceUnitList: res?.result || [] }));
}
};
// 依次获取接口数据
await (async () => {
await getIndustryListPages();
await getPriceUnitList();
})();
return { props: { categoryList } };
},
);
const ServiceSecondView: React.FC<{
categoryList: CategoryListType;
}> = (props) => <ServiceView {...props} />;
export default ServiceSecondView;
import React from 'react';
import { InterListType } from '@/api/interface';
import { GetIndustryListPagesType } from '@/api/interface/service';
import { ServiceAPI } from '@/api/modules/service';
import ServiceView from '@/components/service-comp';
import { wrapper } from '@/store';
import { setGlobalData } from '@/store/module/globalData';
// 分类列表类型
type CategoryListType = InterListType<GetIndustryListPagesType>;
// 使用wrapper.getServerSideProps高阶函数包裹getServerSideProps函数
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async () => {
// 分类数据
let categoryList: CategoryListType = [];
// 获取各个目录及分类信息
const getIndustryListPages = async () => {
const res = await ServiceAPI.getIndustryListPages({
pageNo: 1,
pageSize: 999,
});
if (res && res.code === '200') {
categoryList = res?.result?.list || [];
}
};
// 获取价格单位列表
const getPriceUnitList = async () => {
const res = await ServiceAPI.listInspectionPriceUnit();
if (res && res.code === '200') {
store.dispatch(setGlobalData({ priceUnitList: res?.result || [] }));
}
};
// 依次获取接口数据
await (async () => {
await getIndustryListPages();
await getPriceUnitList();
})();
return { props: { categoryList } };
},
);
const ServiceMainView: React.FC<{
categoryList: CategoryListType;
}> = (props) => <ServiceView {...props} />;
export default ServiceMainView;
import React from 'react';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import {
GetCompanyInfoById,
GetCompanyInspectionById,
} from '@/api/interface/service';
import { ServiceAPI } from '@/api/modules/service';
import BreadcrumbView from '@/components/breadcrumb';
import LayoutView from '@/components/layout';
import ServiceHeadView from '@/components/service-detail/comp/detail-head';
import ServiceStoreView from '@/components/service-detail/comp/detail-store';
import { wrapper } from '@/store';
import { setGlobalData } from '@/store/module/globalData';
// 商品详情类型
type DetailType = InterDataType<GetCompanyInspectionById>;
// 商城详情类型
type StoreType = InterDataType<GetCompanyInfoById>;
// 使用wrapper.getServerSideProps高阶函数包裹getServerSideProps函数
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async (context) => {
// 商品id
const id: number = Number(context.params?.id);
// 商品详情
let serviceDetail: DetailType | undefined;
// 店铺详情
let storeDetail: StoreType | undefined;
// 获取服务详情
const getCompanyInspection = async () => {
const res = await ServiceAPI.getCompanyInspectionById({ id });
if (res && res.code === '200') {
serviceDetail = res.result;
// console.log('获取服务详情 --->', serviceDetail);
}
};
// 获取店铺详情
const getCompanyInfo = async () => {
const res = await ServiceAPI.getCompanyInfoById({
id: Number(serviceDetail?.companyInfoId),
});
if (res && res.code === '200') {
storeDetail = res.result;
}
};
// 获取价格单位列表
const getPriceUnitList = async () => {
const res = await ServiceAPI.listInspectionPriceUnit();
if (res && res.code === '200') {
store.dispatch(setGlobalData({ priceUnitList: res?.result || [] }));
}
};
// 依次获取接口数据
await (async () => {
await getCompanyInspection();
await getCompanyInfo();
await getPriceUnitList();
})();
return { props: { serviceDetail, storeDetail } };
},
);
const ServiceDetailView: React.FC<{
id: number;
serviceDetail: DetailType;
storeDetail: StoreType;
}> = ({ serviceDetail, storeDetail }) => {
return (
<LayoutView>
<ServiceDetailWrap>
<BreadcrumbView />
<div className="flex-start align-start">
<ServiceHeadView detail={serviceDetail} />
<ServiceStoreView detail={serviceDetail} store={storeDetail} />
</div>
<div className="product-title">商品详情</div>
<div className="product-richText">
{serviceDetail?.detailPage && (
<div
className="content-html"
dangerouslySetInnerHTML={{ __html: serviceDetail?.detailPage }}
/>
)}
</div>
</ServiceDetailWrap>
</LayoutView>
);
};
export default ServiceDetailView;
// 样式
const ServiceDetailWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
.product-title {
position: relative;
width: 100%;
text-align: center;
padding: 1.5rem 0;
color: #989898;
font-size: 14px;
&::after,
&::before {
position: absolute;
content: '';
top: 50%;
left: 40%;
width: 4.74rem;
height: 0.02rem;
background: #d4d4d4;
}
&::before {
left: auto;
right: 40%;
}
}
.product-richText {
position: relative;
width: 100%;
margin-bottom: 3.5rem;
.content-html {
width: 100%;
img {
width: 100%;
}
}
}
`;
import React from 'react';
import { InterListType } from '@/api/interface';
import { GetIndustryListPagesType } from '@/api/interface/service';
import { ServiceAPI } from '@/api/modules/service';
import ServiceView from '@/components/service-comp';
import { wrapper } from '@/store';
import { setGlobalData } from '@/store/module/globalData';
// 分类列表类型
type CategoryListType = InterListType<GetIndustryListPagesType>;
// 使用wrapper.getServerSideProps高阶函数包裹getServerSideProps函数
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async () => {
// 分类数据
let categoryList: CategoryListType = [];
// 获取各个目录及分类信息
const getIndustryListPages = async () => {
const res = await ServiceAPI.getIndustryListPages({
pageNo: 1,
pageSize: 999,
});
if (res && res.code === '200') {
categoryList = res?.result?.list || [];
}
};
// 获取价格单位列表
const getPriceUnitList = async () => {
const res = await ServiceAPI.listInspectionPriceUnit();
if (res && res.code === '200') {
store.dispatch(setGlobalData({ priceUnitList: res?.result || [] }));
}
};
// 依次获取接口数据
await (async () => {
await getIndustryListPages();
await getPriceUnitList();
})();
return { props: { categoryList } };
},
);
const ServicePageView: React.FC<{
categoryList: CategoryListType;
}> = (props) => <ServiceView {...props} />;
export default ServicePageView;
// index.ts
import {
combineReducers,
configureStore,
getDefaultMiddleware,
} from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import addressReducer from './module/address';
import globalDataReducer from './module/globalData';
import systemReducer from './module/system';
import userInfoReducer from './module/userInfo';
// 单独创建rootReducer供服务端和客户端创建store使用;
const rootReducer = combineReducers({
userInfo: userInfoReducer,
address: addressReducer,
system: systemReducer,
globalData: globalDataReducer,
});
const makeStore = () => {
const isServer = typeof window === 'undefined';
// 区分客户端和服务端,服务端不需要持久存储,客户端存在在localStorage中;
if (isServer) {
return configureStore({
reducer: rootReducer,
devTools: true,
});
}
const persistConfig = {
key: 'SHAREFLY-WEB-STORAGE',
whiteList: ['userInfo', 'address', 'system'],
blacklist: ['globalData'],
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = configureStore({
reducer: persistedReducer,
devTools: process.env.NODE_ENV !== 'production',
middleware: getDefaultMiddleware({
serializableCheck: false, // 添加这一行
}),
});
// @ts-ignore 只使用客户端渲染不需要此种做法,只需导出persistor即可;
// eslint-disable-next-line no-underscore-dangle
(store as any).__persistor = persistStore(store);
return store;
};
export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore['getState']>;
export const wrapper = createWrapper(makeStore);
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import { LocationType } from '@/utils/getLocationByIP';
export type AddressState = LocationType;
const initialState: AddressState | {} = {};
const addressSlice = createSlice({
name: 'address',
initialState,
reducers: {
setAddress: (state, action) => {
return action.payload;
},
},
extraReducers: {
// hydrated 用于获取服务端注入的state并选择更新
[HYDRATE]: (state, action) => {
return {
...action.payload.address,
};
},
},
});
export const { setAddress } = addressSlice.actions;
export default addressSlice.reducer;
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import { InterDataType } from '@/api/interface';
import { ListInspectionPriceUnit } from '@/api/interface/service';
import { UserAddressSelectList } from '@/api/interface/user';
export type GlobalDataState = {
loadingSpinnerVisible: boolean;
loginModalVisible: boolean;
loginModalTitle?: string;
qrcodeModalVisible: boolean;
qrcodeModalPath?: string;
qrcodeModalScene?: string;
userAddressList?: InterDataType<UserAddressSelectList>;
userAddressSelectId?: number;
productSpecNum?: number;
priceUnitList?: InterDataType<ListInspectionPriceUnit>;
toastModalVisible: boolean;
toastModalBack: boolean;
};
const initialState: GlobalDataState = {
loadingSpinnerVisible: false,
loginModalVisible: false,
qrcodeModalVisible: false,
toastModalVisible: false,
toastModalBack: false,
};
const globalDataSlice = createSlice({
name: 'globalData',
initialState,
reducers: {
setGlobalData: (state, action) => {
return { ...state, ...action.payload };
},
},
extraReducers: {
// hydrated 用于获取服务端注入的state并选择更新
[HYDRATE]: (state, action) => {
return {
...state,
...action.payload.globalData,
};
},
},
});
export const { setGlobalData } = globalDataSlice.actions;
export default globalDataSlice.reducer;
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
export type SystemState = {
token?: string;
};
const initialState: SystemState = {};
const systemSlice = createSlice({
name: 'system',
initialState,
reducers: {
setSystem: (state, action) => {
return { ...state, ...action.payload };
},
},
extraReducers: {
// hydrated 用于获取服务端注入的state并选择更新
[HYDRATE]: (state, action) => {
return {
...state,
...action.payload.system,
};
},
},
});
export const { setSystem } = systemSlice.actions;
export default systemSlice.reducer;
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import { InterDataType } from '@/api/interface';
import { GetAccountInfo } from '@/api/interface/common';
export type UserInfoState = InterDataType<GetAccountInfo>;
const initialState: UserInfoState | {} = {};
const userInfoSlice = createSlice({
name: 'userInfo',
initialState,
reducers: {
setUserInfo: (state, action) => {
return action.payload;
},
},
extraReducers: {
// hydrated 用于获取服务端注入的state并选择更新
[HYDRATE]: (state, action) => {
return {
...action.payload.user,
};
},
},
});
export const { setUserInfo } = userInfoSlice.actions;
export default userInfoSlice.reducer;
...@@ -158,6 +158,25 @@ a { ...@@ -158,6 +158,25 @@ a {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.select-none{
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
.align-start {
align-items: flex-start;
}
.cursor-pointer {
cursor: pointer;
}
.two-line-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.scroll-view::-webkit-scrollbar { .scroll-view::-webkit-scrollbar {
background-color: transparent; background-color: transparent;
-webkit-border-radius: 2em; -webkit-border-radius: 2em;
......
...@@ -7,9 +7,9 @@ const themeConfig: ThemeConfig = { ...@@ -7,9 +7,9 @@ const themeConfig: ThemeConfig = {
}, },
components: { components: {
Button: { Button: {
colorLink: '#ffffff', colorLink: 'rgba(255,85,45,0.8)',
colorLinkActive: '#ff552d', colorLinkActive: '#ff552d',
colorLinkHover: '#ff552d', colorLinkHover: '#e1251b',
contentFontSize: 12, contentFontSize: 12,
}, },
}, },
......
import { WalletAPI } from '@/api';
import { InterDataType } from '@/api/interface';
import { UserPayWalletInfoType } from '@/api/interface/wallet';
// 钱包数据类型
export type CurrentUserPayWalletInfoType = InterDataType<UserPayWalletInfoType>;
// 获取当前用户的钱包信息
const getCurrentUserPayWalletInfo = (): Promise<CurrentUserPayWalletInfoType> =>
new Promise((resolve, reject) => {
WalletAPI.getUserPayWalletInfo({})
.then((res) => {
if (res && res.code === '200') {
resolve(res.result);
} else {
reject(res.result);
}
})
.catch((err) => {
reject(err);
});
});
export default getCurrentUserPayWalletInfo;
import { message } from 'antd';
import Axios from 'axios';
// 位置的类型
export interface LocationType {
ip: string;
network: string;
version: string;
city: string;
region: string;
region_code: string;
country: string;
country_name: string;
country_code: string;
country_code_iso3: string;
country_capital: string;
country_tld: string;
continent_code: string;
in_eu: boolean;
postal: null;
latitude: number;
longitude: number;
timezone: string;
utc_offset: string;
country_calling_code: string;
currency: string;
currency_name: string;
languages: string;
country_area: number;
country_population: number;
asn: string;
org: string;
status: string;
info: string;
infocode: string;
province: string;
adcode: string;
rectangle: string;
}
// 根据用户的ip地址获取当前位置
export default function getLocationByIP(): Promise<LocationType> {
return new Promise((resolve, reject) => {
// 用户的ip信息
let ipInfo: LocationType;
// 获取用户的ip信息
const getIPInfo = async () => {
const res = await Axios.get('https://ipapi.co/json/');
if (res && res?.status === 200) {
ipInfo = res.data;
// console.log('获取位置1 --->', ipInfo);
return;
}
message.warning('获取位置失败').then();
};
// 根据ip信息获取位置信息
const getLocationByIPInfo = async () => {
const res = await Axios.get(
`https://restapi.amap.com/v3/ip?key=4f98d03f611b80f95a0ad56b044ffb59&ip=${ipInfo.ip}`,
);
if (res && res?.status === 200) {
const { province, city, adcode, rectangle } = res.data;
ipInfo.province = province;
ipInfo.city = city;
ipInfo.adcode = adcode;
ipInfo.rectangle = rectangle;
// console.log('获取位置 --->', ipInfo);
resolve(ipInfo);
return;
}
message.warning('获取位置失败').then();
reject(ipInfo);
};
// 自执行
(async () => {
await getIPInfo();
await getLocationByIPInfo();
})();
});
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论