提交 e413442d 作者: ZhangLingKun

Merge branch 'develop'

流水线 #8189 已通过 于阶段
in 6 分 7 秒
#请求接口地址 #请求接口地址
#正式服 #正式服
#NEXT_PUBLIC_BASE_URL='https://www.iuav.com' NEXT_PUBLIC_BASE_URL='https://www.iuav.com'
#测试服 #测试服
NEXT_PUBLIC_BASE_URL='https://test.iuav.com' #NEXT_PUBLIC_BASE_URL='https://test.iuav.com'
#版本 #版本
NODE_ENV='development' NODE_ENV='development'
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
"endOfLine": "auto", "endOfLine": "auto",
"plugins": [ "plugins": [
"prettier-plugin-tailwindcss" "prettier-plugin-tailwindcss"
] ],
"tailwindConfig": "./tailwind.config.js"
} }
] ]
}, },
......
...@@ -33,6 +33,11 @@ docker_build_dev: ...@@ -33,6 +33,11 @@ docker_build_dev:
- docker build --build-arg PROFILES_ACTIVE=$PROFILES_ACTIVE -t "$ACR_EE_REGISTRY/$ACR_EE_NAMESPACE/$ACR_EE_IMAGE:$TAG" . - docker build --build-arg PROFILES_ACTIVE=$PROFILES_ACTIVE -t "$ACR_EE_REGISTRY/$ACR_EE_NAMESPACE/$ACR_EE_IMAGE:$TAG" .
- docker push "${ACR_EE_REGISTRY}/${ACR_EE_NAMESPACE}/${ACR_EE_IMAGE}:${TAG}" - docker push "${ACR_EE_REGISTRY}/${ACR_EE_NAMESPACE}/${ACR_EE_IMAGE}:${TAG}"
- docker logout - docker logout
after_script:
- chmod a+x ./send-webhook.sh # 给脚本文件添加可执行权限
- ./send-webhook.sh $ACR_EE_NAMESPACE $PROFILES_ACTIVE # 执行脚本文件
docker_build_prod: docker_build_prod:
stage: dockerbuild stage: dockerbuild
......
...@@ -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: 8a764a4e7eecfcfbb9106015e461aaa32e732755 newTag: 2bcecb9934c506944d1bdfedebc33e45c31b05f1
...@@ -30,8 +30,10 @@ ...@@ -30,8 +30,10 @@
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"next": "^14.0.4", "next": "^14.0.4",
"next-redux-wrapper": "^8.1.0", "next-redux-wrapper": "^8.1.0",
"query-string": "^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",
...@@ -44,6 +46,7 @@ ...@@ -44,6 +46,7 @@
"devDependencies": { "devDependencies": {
"@types/big.js": "^6.2.0", "@types/big.js": "^6.2.0",
"@types/js-cookie": "^3.0.5", "@types/js-cookie": "^3.0.5",
"@types/lodash": "^4.14.202",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18.2.37", "@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15", "@types/react-dom": "^18.2.15",
......
...@@ -44,12 +44,18 @@ dependencies: ...@@ -44,12 +44,18 @@ dependencies:
js-cookie: js-cookie:
specifier: ^3.0.5 specifier: ^3.0.5
version: registry.npmmirror.com/js-cookie@3.0.5 version: registry.npmmirror.com/js-cookie@3.0.5
lodash:
specifier: ^4.17.21
version: 4.17.21
next: next:
specifier: ^14.0.4 specifier: ^14.0.4
version: 14.0.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5) version: 14.0.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5)
next-redux-wrapper: next-redux-wrapper:
specifier: ^8.1.0 specifier: ^8.1.0
version: registry.npmmirror.com/next-redux-wrapper@8.1.0(next@14.0.4)(react-redux@8.1.3)(react@18.2.0) version: registry.npmmirror.com/next-redux-wrapper@8.1.0(next@14.0.4)(react-redux@8.1.3)(react@18.2.0)
query-string:
specifier: ^8.1.0
version: 8.1.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
...@@ -82,6 +88,9 @@ devDependencies: ...@@ -82,6 +88,9 @@ devDependencies:
'@types/js-cookie': '@types/js-cookie':
specifier: ^3.0.5 specifier: ^3.0.5
version: registry.npmmirror.com/@types/js-cookie@3.0.5 version: registry.npmmirror.com/@types/js-cookie@3.0.5
'@types/lodash':
specifier: ^4.14.202
version: 4.14.202
'@types/node': '@types/node':
specifier: ^20 specifier: ^20
version: registry.npmmirror.com/@types/node@20.0.0 version: registry.npmmirror.com/@types/node@20.0.0
...@@ -530,6 +539,10 @@ packages: ...@@ -530,6 +539,10 @@ packages:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==, tarball: https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz} resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==, tarball: https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz}
dev: true dev: true
/@types/lodash@4.14.202:
resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==, tarball: https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.202.tgz}
dev: true
/@typescript-eslint/parser@6.10.0(eslint@8.0.0)(typescript@5.0.2): /@typescript-eslint/parser@6.10.0(eslint@8.0.0)(typescript@5.0.2):
resolution: {integrity: sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==, tarball: https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.10.0.tgz} resolution: {integrity: sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==, tarball: https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.10.0.tgz}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
...@@ -928,6 +941,11 @@ packages: ...@@ -928,6 +941,11 @@ packages:
dependencies: dependencies:
ms: 2.1.2 ms: 2.1.2
/decode-uri-component@0.4.1:
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==, tarball: https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz}
engines: {node: '>=14.16'}
dev: false
/define-data-property@1.1.1: /define-data-property@1.1.1:
resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.1.tgz} resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.1.tgz}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
...@@ -1378,6 +1396,11 @@ packages: ...@@ -1378,6 +1396,11 @@ packages:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
dev: true dev: true
/filter-obj@5.1.0:
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==, tarball: https://registry.npmmirror.com/filter-obj/-/filter-obj-5.1.0.tgz}
engines: {node: '>=14.16'}
dev: false
/for-each@0.3.3: /for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==, tarball: https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==, tarball: https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz}
dependencies: dependencies:
...@@ -1839,6 +1862,10 @@ packages: ...@@ -1839,6 +1862,10 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, tarball: https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, tarball: https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz}
dev: true dev: true
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, tarball: https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz}
dev: false
/loose-envify@1.4.0: /loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, tarball: https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz} resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, tarball: https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz}
hasBin: true hasBin: true
...@@ -2233,6 +2260,15 @@ packages: ...@@ -2233,6 +2260,15 @@ packages:
react-is: 16.13.1 react-is: 16.13.1
dev: true dev: true
/query-string@8.1.0:
resolution: {integrity: sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==, tarball: https://registry.npmmirror.com/query-string/-/query-string-8.1.0.tgz}
engines: {node: '>=14.16'}
dependencies:
decode-uri-component: 0.4.1
filter-obj: 5.1.0
split-on-first: 3.0.0
dev: false
/queue-microtask@1.2.3: /queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz}
dev: true dev: true
...@@ -2395,6 +2431,11 @@ packages: ...@@ -2395,6 +2431,11 @@ packages:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, tarball: https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz} resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, tarball: https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
/split-on-first@3.0.0:
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==, tarball: https://registry.npmmirror.com/split-on-first/-/split-on-first-3.0.0.tgz}
engines: {node: '>=12'}
dev: false
/streamsearch@1.1.0: /streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==, tarball: https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz} resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==, tarball: https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
...@@ -3621,7 +3662,7 @@ packages: ...@@ -3621,7 +3662,7 @@ packages:
'@babel/helper-annotate-as-pure': registry.npmmirror.com/@babel/helper-annotate-as-pure@7.22.5 '@babel/helper-annotate-as-pure': registry.npmmirror.com/@babel/helper-annotate-as-pure@7.22.5
'@babel/helper-module-imports': registry.npmmirror.com/@babel/helper-module-imports@7.22.15 '@babel/helper-module-imports': registry.npmmirror.com/@babel/helper-module-imports@7.22.15
'@babel/plugin-syntax-jsx': registry.npmmirror.com/@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.23.2) '@babel/plugin-syntax-jsx': registry.npmmirror.com/@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.23.2)
lodash: registry.npmmirror.com/lodash@4.17.21 lodash: 4.17.21
picomatch: registry.npmmirror.com/picomatch@2.3.1 picomatch: registry.npmmirror.com/picomatch@2.3.1
styled-components: registry.npmmirror.com/styled-components@6.1.0(react-dom@18.2.0)(react@18.2.0) styled-components: registry.npmmirror.com/styled-components@6.1.0(react-dom@18.2.0)(react@18.2.0)
transitivePeerDependencies: transitivePeerDependencies:
...@@ -4973,12 +5014,6 @@ packages: ...@@ -4973,12 +5014,6 @@ packages:
version: 4.6.2 version: 4.6.2
dev: true dev: true
registry.npmmirror.com/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz}
name: lodash
version: 4.17.21
dev: false
registry.npmmirror.com/loose-envify@1.4.0: registry.npmmirror.com/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz} resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz}
name: loose-envify name: loose-envify
......
# 时间
TIME=$(date +"%Y-%m-%d %T")
# webhook地址
api="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=a9a79936-bbbb-4a20-a40e-c0bc01a83e8c"
# 参数
data=$(printf '{"msgtype": "markdown","markdown": {"content": "**推送通知**\n- 项目:%s\n- 环境:%s\n- 状态:执行完成\n- 时间:%s"}}' "$1" "$2" "$TIME")
# 发送webhook请求
curl -X POST -H "Content-Type: application/json" -d "$data" "$api" # 使用 curl 命令向 webhook 地址发送请求
# 处理响应结果
if [ $? -eq 0 ]; then # 判断请求是否成功
echo "请求成功" # 打印请求成功
else
echo "请求失败" # 打印请求失败
fi
import { CommonAPI } from './modules/common'; import { CommonAPI } from './modules/common';
import { FlyerAPI } from './modules/flyer';
import { HomeAPI } from './modules/home'; import { HomeAPI } from './modules/home';
import { UserAPI } from './modules/user'; import { UserAPI } from './modules/user';
import { WalletAPI } from './modules/wallet'; import { WalletAPI } from './modules/wallet';
export { CommonAPI, HomeAPI, UserAPI, WalletAPI }; export { CommonAPI, HomeAPI, UserAPI, WalletAPI, FlyerAPI };
// 分页通用接口 // 分页通用接口
import { AxiosRequestConfig } from 'axios';
export interface PaginationProps { export interface PaginationProps {
pageSize: number; pageSize: number;
pageNo: number; pageNo: number;
...@@ -39,17 +41,23 @@ export interface ResponseType<D> { ...@@ -39,17 +41,23 @@ export interface ResponseType<D> {
// 通用接口封装函数(分页) 建议用这个 // 通用接口封装函数(分页) 建议用这个
export interface InterListFunction<D extends object, T> { export interface InterListFunction<D extends object, T> {
(req: D & Partial<PaginationProps>): Promise<ResponseListType<T>>; (
req: D & Partial<PaginationProps>,
config?: AxiosRequestConfig,
): Promise<ResponseListType<T>>;
} }
// 通用接口封装函数(不分页) 建议用这个 // 通用接口封装函数(不分页) 建议用这个
export interface InterFunction<D extends object, T> { export interface InterFunction<D extends object, T> {
(req?: D): Promise<ResponseType<T>>; (req?: D, config?: AxiosRequestConfig): Promise<ResponseType<T>>;
} }
// 通用接口封装函数(分页了,但又没有分页) 建议用这个 // 通用接口封装函数(分页了,但又没有分页) 建议用这个
export interface InterItemFunction<D extends object, T> { export interface InterItemFunction<D extends object, T> {
(req: D & Partial<PaginationProps>): Promise<ResponseItemType<T>>; (
req: D & Partial<PaginationProps>,
config?: AxiosRequestConfig,
): Promise<ResponseItemType<T>>;
} }
// 返回类型封装 // 返回类型封装
......
...@@ -105,3 +105,40 @@ export type TestPhoneLogin = InterFunction< ...@@ -105,3 +105,40 @@ export type TestPhoneLogin = InterFunction<
userAccountId: number; userAccountId: number;
} }
>; >;
// 获取修改手机获取验证码
export type GetVerifyCodeType = InterFunction<
{
userAccountId: number;
phoneNum: string;
},
{}
>;
// web注册获取手机号验证码
export type GetVerifyCodeAuthType = InterFunction<
{
phoneNum: string;
},
{}
>;
// web端注册
export type WebRegisterType = InterFunction<
{
code: string;
password: string;
phoneNum: string;
},
{}
>;
// web端账号密码登录
export type WebLoginType = InterFunction<
{
accountNo: string;
passWord: string;
},
{
nickName: string;
phoneNum: string;
token: string;
userAccountId: number;
}
>;
import { InterFunction, InterListFunction } from '@/api/interface';
// 所有课程分类列表
export type SelectCurriculumClassifyType = InterFunction<
{},
{
classifyDesc: string;
classifyUrl: string;
createTime: string;
id: number;
name: string;
oneCourseId: number;
twoCourseId: number;
updateTime: string;
}[]
>;
// V1.0.1课程视频列表
export type QueryCurriculumInfoListType = InterListFunction<
{
courseAttribute?: number;
curriculumName?: string;
oneCourseId?: number;
twoCourseId?: number;
isHot?: number;
},
{
id: number;
oneCourseId: number;
twoCourseId: number;
curriculumName: string;
curriculumDesc: string;
surfaceUrl: string;
videoUrl: string;
courseAttribute: number;
requireAmout: number;
requireIntegral: number;
isHot: number;
detailContent: string;
createTime: string;
updateTime: string;
}
>;
// 课程详情
export type CourseDetailType = InterFunction<
{ id: string },
{
id: number;
oneCourseId: number;
twoCourseId: number;
curriculumName: string;
curriculumDesc: string;
surfaceUrl: string;
videoUrl: string;
courseAttribute: number;
requireAmout: number;
requireIntegral: number;
isHot: number;
detailContent: null;
createTime: string;
updateTime: string;
buy: boolean;
}
>;
import { InterItemFunction, InterListFunction } from '@/api/interface';
// V1.0.1课程视频列表
// eslint-disable-next-line @typescript-eslint/naming-convention
export type queryCurriculumInfoListType = InterListFunction<
{
courseAttribute?: number;
curriculumName?: string;
oneCourseId?: number;
twoCourseId?: number;
isHot?: number;
},
{
id: number;
oneCourseId: number;
twoCourseId: number;
curriculumName: string;
curriculumDesc: string;
surfaceUrl: string;
videoUrl: string;
courseAttribute: number;
requireAmout: number;
requireIntegral: number;
isHot: number;
detailContent: string;
createTime: string;
updateTime: string;
}
>;
// 课程列表
// eslint-disable-next-line @typescript-eslint/naming-convention
export type courseVideoListType = InterItemFunction<
{
flightSkillsId?: number;
licenseId?: number;
regionId?: number;
curriculumName?: string;
},
{
id: number;
videoUrl: string;
supplierName: string;
curriculumDesc: string;
curriculumName: string;
price: string;
free: number;
peopleCount: number;
surfaceUrl: string;
}[]
>;
import { InterDataType, InterFunction } from '@/api/interface'; import {
InterDataType,
InterFunction,
InterListFunction,
} from '@/api/interface';
// 小程序分类信息--含一二级分类 // 小程序分类信息--含一二级分类
export type GetAppCategoryInfo = InterFunction< export type GetAppCategoryInfo = InterFunction<
...@@ -144,3 +148,150 @@ export type GetCompanyInfoByBUId = InterFunction< ...@@ -144,3 +148,150 @@ export type GetCompanyInfoByBUId = InterFunction<
city: string; city: string;
} }
>; >;
// 租赁商品详情
export type LeaseGoodsDetailsType = InterFunction<
{
id: number;
},
{
id: number;
tradeName: string;
sellingPoint: string;
level: number;
shelfStatus: number;
productTypeId: number;
brandInfoId: number;
deviceModeId: number;
productParam: string;
resourcesList: Array<{
id: number;
url: string;
type: number;
}>;
specAttrList: Array<{
id: number;
specName: string;
specValuesList: Array<{
id: number;
specName: string;
}>;
}>;
priceStock: Array<{
id: number;
productSpec: string;
cashPledge: number;
threeDaysRental: number;
sevenDaysRental: null;
thirtyDaysRental: null;
ninetyDaysRental: null;
maxDaysRental: null;
skuImage: string;
stock: null;
stockOut: number;
}>;
productDetails: string;
minLeaseTerm: number;
maxLeaseTerm: number;
leasePartsList: Array<{
id: number;
name: string;
number: number;
price: number;
}>;
shipAddress: number;
returnAddress: number;
logisticsCompany: string;
modeOfDelivery: number;
showPrice: number;
createTime: string;
userAccountId: number;
cashPledgeRange: null;
rentalRange: null;
stock: null;
districtCode: null;
productTypeName: string;
brandName: string;
deviceModeName: string;
modeOfDeliveryInfo: string;
}
>;
// 租赁-商品-租期信息
export type LeaseTermInfoType = InterFunction<
any,
{ id: number; leaseDate: string }[]
>;
// 合作商家列表-根据合作标签id获取
export type ListCompanyInfoByCoopIdType = InterListFunction<
{
coopId?: number;
lat?: number;
lon?: number;
// pageNo: number;
// pageSize: 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;
city: string;
distance: number;
district: string;
province: string;
backUserAccountId: number;
}
>;
// 品牌店铺列表
export type BrandStoreListType = InterFunction<
number[],
{
mallGoodsVOList: Array<{
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<null>;
}>;
tradeName: string;
userAccountId: number;
}>;
userAccountId: number;
}[]
>;
...@@ -2,7 +2,11 @@ import { ...@@ -2,7 +2,11 @@ import {
GetAccountInfo, GetAccountInfo,
GetAppletQRCode, GetAppletQRCode,
GetLoginInfo, GetLoginInfo,
GetVerifyCodeAuthType,
GetVerifyCodeType,
TestPhoneLogin, TestPhoneLogin,
WebLoginType,
WebRegisterType,
} from '@/api/interface/common'; } from '@/api/interface/common';
import request from '../request'; import request from '../request';
...@@ -30,4 +34,20 @@ export class CommonAPI { ...@@ -30,4 +34,20 @@ export class CommonAPI {
// 测试-手机号登录 // 测试-手机号登录
static testPhoneLogin: TestPhoneLogin = (params) => static testPhoneLogin: TestPhoneLogin = (params) =>
request.get('/userapp/auth/testPhoneLogin', { params }); request.get('/userapp/auth/testPhoneLogin', { params });
// 获取修改手机获取验证码
static getVerifyCode: GetVerifyCodeType = (params) =>
request.get('/userapp/user-account/getVerifyCode', { params });
// web注册获取手机号验证码
static getVerifyCodeAuth: GetVerifyCodeAuthType = (params) =>
request.get('/userapp/auth/getVerifyCode', { params });
// web端注册
static webRegister: WebRegisterType = (params) =>
request.post('/userapp/auth/webRegister', params);
// web端账号密码登录
static webLogin: WebLoginType = (params) =>
request.post('/userapp/auth/webLogin', params);
} }
import {
CourseDetailType,
QueryCurriculumInfoListType,
SelectCurriculumClassifyType,
} from '@/api/interface/course';
import request from '@/api/request';
export class CourseAPI {
// 所有课程分类列表
static selectCurriculumClassify: SelectCurriculumClassifyType = (params) =>
request.get('/release/curriculum/selectCurriculumClassify', { params });
// 课程列表
static getCourseVideoList: QueryCurriculumInfoListType = (params) =>
request.post('/release/curriculum/queryCurriculumInfoList', params);
// 课程详情
static getCourseDetail: CourseDetailType = (params, config) =>
request.get('/release/curriculum/curriculumDetails', { params, ...config });
}
import {
courseVideoListType,
queryCurriculumInfoListType,
} from '@/api/interface/flyer';
import request from '@/api/request';
export class FlyerAPI {
// 课程列表
static getCourseVideoList: courseVideoListType = (data) => {
return request.post('/release/curriculum/queryCurriculumInfoList', data);
};
// V1.0.1课程视频列表
static queryCurriculumInfoList: queryCurriculumInfoListType = (params) =>
request.post('/release/curriculum/queryCurriculumInfoList', params);
}
import { import {
AllCommentListType,
AppCategoryInfoType, AppCategoryInfoType,
AppGambitListsType,
AppListPilotType, AppListPilotType,
AppPublishListType, AppPublishListType,
ForumDetailType,
ForumListType,
GetAppGambitListType, GetAppGambitListType,
GetInfoById,
GetPageHomeCategoriesType, GetPageHomeCategoriesType,
GetSecondDistrictInfo,
IndustryListPagesType, IndustryListPagesType,
LeaseGoodsListType,
LikeOrCancelType,
ListBannerImgType, ListBannerImgType,
ListBrandInfoType, ListBrandInfoType,
ListCompanyInfoByCoopIdType, ListCompanyInfoByCoopIdType,
ListNewsType, ListNewsType,
ListTenderInfoType, ListTenderInfoType,
NewDetailsType,
PublishCommentType,
RecommendGoodsType, RecommendGoodsType,
ReplyListType,
RequirementsListType, RequirementsListType,
ReviewLikesType,
} from '@/api/interface/home'; } from '@/api/interface/home';
import request from '@/api/request'; import request from '@/api/request';
...@@ -36,6 +48,10 @@ export class HomeAPI { ...@@ -36,6 +48,10 @@ export class HomeAPI {
static getAppGambitList: GetAppGambitListType = (data) => static getAppGambitList: GetAppGambitListType = (data) =>
request.post('/release/dynamic/appGambitList', data); request.post('/release/dynamic/appGambitList', data);
// 话题-话题下的帖子
static getAppGambitLists: AppGambitListsType = (data) =>
request.post('/release/gambit/appGambitLists', data);
// 小程序-列表——需求发布 // 小程序-列表——需求发布
static appPublishList: AppPublishListType = (params) => static appPublishList: AppPublishListType = (params) =>
request.post('/release/requirements/appPublishList', params); request.post('/release/requirements/appPublishList', params);
...@@ -67,4 +83,52 @@ export class HomeAPI { ...@@ -67,4 +83,52 @@ export class HomeAPI {
// web-首页分类数据-展示 // web-首页分类数据-展示
static getPageHomeCategories: GetPageHomeCategoriesType = (params) => static getPageHomeCategories: GetPageHomeCategoriesType = (params) =>
request.get('/pms/product/mall/getPageHomeCategories', { params }); request.get('/pms/product/mall/getPageHomeCategories', { params });
// 论坛列表
static getForumList: ForumListType = (params) =>
request.get('/release/dynamic/dynamicList', { params });
// 论坛列表
static getForumList1: ForumListType = (params) =>
request.get('/release/dynamic/dynamicList1', { params });
// 类型列表
static leaseGoodsList: LeaseGoodsListType = (params) =>
request.post('/pms/app/lease/leaseGoodsList', params);
// 项目资讯-新闻详情
static getNewsDetail: NewDetailsType = (params) =>
request.get('/release/industry-news/details', { params });
// 查询-招标快讯详情
static getInfoById: GetInfoById = (params) =>
request.get('/release/tender/infoById', { params });
// 地域
static getSecondDistrictInfo: GetSecondDistrictInfo = (params) =>
request.get('/pms/webDevice/getSecondDistrictInfo', params);
// 论坛-详情
static getForumDetail: ForumDetailType = (params, config) =>
request.get('/release/dynamic/dynamicDetails', { params, ...config });
// 话题-所有评论
static getAllCommentList: AllCommentListType = (params) =>
request.post('/release/gambit/allCommentList', params);
// 话题-评论下的回复
static getReplyList: ReplyListType = (data) =>
request.post('/release/gambit/replyList', data);
// 论坛-评论-发表
static publishComment: PublishCommentType = (data) =>
request.post('/release/dynamic/comment', data);
// 话题-评论/回复点赞,取消
static reviewLikes: ReviewLikesType = (params) =>
request.get('/release/dynamic/reviewLikes', { params });
// 论坛-点赞或取消点赞
static likeOrCancel: LikeOrCancelType = (params) =>
request.get('/release/dynamic/likeOrCancel', { params });
} }
...@@ -3,6 +3,10 @@ import { ...@@ -3,6 +3,10 @@ import {
GetAppCategoryInfo, GetAppCategoryInfo,
AppMallGoodsDetails, AppMallGoodsDetails,
GetCompanyInfoByBUId, GetCompanyInfoByBUId,
LeaseGoodsDetailsType,
LeaseTermInfoType,
ListCompanyInfoByCoopIdType,
BrandStoreListType,
} from '@/api/interface/mall'; } from '@/api/interface/mall';
import request from '../request'; import request from '../request';
...@@ -22,4 +26,20 @@ export class MallAPI { ...@@ -22,4 +26,20 @@ export class MallAPI {
// pc-后台用户id单位查询 // pc-后台用户id单位查询
static getCompanyInfoByBUId: GetCompanyInfoByBUId = (params) => static getCompanyInfoByBUId: GetCompanyInfoByBUId = (params) =>
request.get('/userapp/company/getCompanyInfoByBUId', { params }); request.get('/userapp/company/getCompanyInfoByBUId', { params });
// 租赁商品详情
static leaseGoodsDetails: LeaseGoodsDetailsType = (params) =>
request.get('/pms/app/lease/leaseGoodsDetails', { params });
// 租赁-商品-租期信息
static getLeaseTermInfo: LeaseTermInfoType = () =>
request.post('/pms/lease/goods/getLeaseTermInfo');
// 合作商家列表-根据合作标签id获取
static listCompanyInfoByCoopId: ListCompanyInfoByCoopIdType = (params) =>
request.get('/userapp/cooperation/listCompanyInfoByCoopId', { params });
// 品牌店铺列表
static brandStoreList: BrandStoreListType = (params) =>
request.post('/pms/app/goods/brandStoreList', params);
} }
...@@ -12,10 +12,10 @@ const service = axios.create({ ...@@ -12,10 +12,10 @@ const service = axios.create({
service.interceptors.request.use( service.interceptors.request.use(
(config: any) => { (config: any) => {
const token = Cookies.get('SHAREFLY-WEB-TOKEN'); const token = Cookies.get('SHAREFLY-WEB-TOKEN');
// 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;
// console.log('config ==========>', config.headers);
} }
return config; return config;
}, },
...@@ -24,6 +24,9 @@ service.interceptors.request.use( ...@@ -24,6 +24,9 @@ service.interceptors.request.use(
}, },
); );
// 判断是服务端还是客户端
const isServer = typeof window === 'undefined';
service.interceptors.response.use( service.interceptors.response.use(
(response: AxiosResponse) => { (response: AxiosResponse) => {
const { data, status } = response; const { data, status } = response;
...@@ -39,6 +42,11 @@ service.interceptors.response.use( ...@@ -39,6 +42,11 @@ service.interceptors.response.use(
data.code, data.code,
) )
) { ) {
if (isServer) {
// 直接阻止后续代码执行
// console.log('出错了 --->', response.config, data);
return Promise.reject(data);
}
message.error(data.message).then(); message.error(data.message).then();
Cookies.remove('SHAREFLY-WEB-TOKEN'); Cookies.remove('SHAREFLY-WEB-TOKEN');
localStorage.removeItem('persist:SHAREFLY-WEB-STORAGE'); localStorage.removeItem('persist:SHAREFLY-WEB-STORAGE');
...@@ -52,10 +60,9 @@ service.interceptors.response.use( ...@@ -52,10 +60,9 @@ 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) { if (isServer) {
// 如果是服务端 // 如果是服务端
// console.log('出错了 --->', response.config, data);
return Promise.reject(data); return Promise.reject(data);
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
// window.confirm(data.message || '啊呀,出错了'); // window.confirm(data.message || '啊呀,出错了');
......
import React, { useEffect } from 'react';
import { debounce } from 'lodash';
const InfiniteScrollList: React.FC<{
children: React.ReactNode;
bottomDistance?: number;
onReachBottom?: () => void;
}> = ({ children, bottomDistance = 0, onReachBottom }) => {
// 获取滚动距离
const handleScroll = () => {
// 获取页面的视口高度和内容高度
const { scrollHeight, scrollTop } = document.documentElement;
// 对于现代浏览器,通常推荐使用 window.innerHeight 和 window.scrollY 来获取视口高度和滚动位置
const { innerHeight, scrollY } = window;
// 修正为使用 window 对象的属性获取滚动距离
const scrollTopValue = Math.max(
scrollY,
scrollTop,
document.body.scrollTop,
);
// 判断是否滚动到了底部
if (scrollTopValue + innerHeight >= scrollHeight - bottomDistance) {
onReachBottom?.();
}
};
// 使用防抖函数来减少频繁触发
const handleDebounce = debounce(handleScroll, 100);
// 使用 useEffect 钩子来添加和移除滚动事件的监听器
useEffect(() => {
// 组件挂载后,添加监听器
window.addEventListener('scroll', handleDebounce);
// 在组件卸载前,不仅要移除监听器,还需要清除防抖函数的定时器
return () => {
window.removeEventListener('scroll', handleDebounce);
handleDebounce.cancel(); // Lodash debounce 提供了取消方法
};
}, []); // 依赖数组为空,表示只执行一次
return <div>{children}</div>;
};
export default InfiniteScrollList;
import React, { useEffect, useState } from 'react';
import { ReloadOutlined, SearchOutlined } from '@ant-design/icons';
import {
App,
Button,
Cascader,
Form,
Input,
InputProps,
Select,
SelectProps,
Space,
} from 'antd';
import styled from 'styled-components';
// 搜索类型
export type SearchColumns = {
type: 'Input' | 'Select' | 'Cascader' | 'DatePicker' | 'RangePicker';
label?: string;
name: string;
placeholder: string;
maxlength?: number;
width?: number;
disable?: boolean;
required?: boolean;
options?: SelectProps['options'];
onSelect?: (e: SelectProps['onSelect']) => void;
onChange?: (e: InputProps['onChange']) => void;
api?: any;
transform?: (e: any) => SelectProps['options'];
};
// 组件传参
type PropsType = {
columns?: SearchColumns[];
onSearch?: (e: any) => void;
preFixChild?: React.ReactNode;
sufFixChild?: React.ReactNode;
children?: React.ReactNode;
isReset?: boolean;
isSearch?: boolean;
initialValues?: any;
// onRef?: any;
};
const SearchBoxView: React.FC<PropsType> = ({
columns,
initialValues,
onSearch,
isReset = true,
isSearch = true,
sufFixChild,
}) => {
// app
const { message } = App.useApp();
// 表单钩子
const [formRef] = Form.useForm();
// 提交数据
const handleSubmit = async () => {
const values = await formRef.validateFields().catch((e) => {
const item = e.errorFields?.[0];
message.warning(item?.errors?.[0]);
});
if (!values) return;
onSearch?.(values);
};
// 重置数据
const handleReset = () => {
formRef.resetFields();
onSearch?.({});
};
// 组件的optionList
const [optionList, setOptionList] = useState<SelectProps['options'][]>();
// 获取组件的option
const getOptionData = async () => {
// 如果没有api,直接返回
if (!columns?.some((i) => i?.api)) return;
// 获取数据
const dataList = await Promise.all(
// @ts-ignore es-lint-disable-next-line
columns?.map((i) => i?.api),
);
// 设置数据
setOptionList(
columns?.map((i, j) => {
if (i?.api) {
return (
i?.transform?.(dataList?.[j]?.result) || dataList?.[j]?.result || []
);
}
return i?.options || [];
}),
);
};
// 组件挂载
useEffect(() => {
// if (!columns?.length) return;
getOptionData().then();
}, []);
return (
<SearchBoxWrap>
<Form
form={formRef}
name="formRef"
// labelCol={{ span: 8 }}
// wrapperCol={{ span: 16 }}
style={{ width: '100%' }}
initialValues={initialValues}
layout={'inline'}
// onFinish={handleFinish}
autoComplete="off"
// onChange={handleChange}
>
{columns?.map((i, j) => (
<Form.Item
label={i?.label}
name={i?.name}
key={j}
rules={[
{
required: i?.required || false,
message: i?.label ? `请输入${i?.label}` : i?.placeholder,
},
]}
>
{i?.type === 'Input' && (
<Input
placeholder={i?.placeholder}
maxLength={i?.maxlength || 20}
style={{ width: i?.width || '200px' }}
disabled={i?.disable}
allowClear
/>
)}
{i?.type === 'Select' && (
<Select
placeholder={i?.placeholder}
style={{ width: i?.width || '200px' }}
disabled={i?.disable}
onSelect={i?.onSelect}
options={optionList?.[j] || i?.options || []}
allowClear
/>
)}
{i?.type === 'Cascader' && (
<Cascader
placeholder={i?.placeholder}
style={{ width: i?.width || '200px' }}
disabled={i?.disable}
options={optionList?.[j] || i?.options || []}
allowClear
/>
)}
</Form.Item>
))}
<Form.Item>
<Space>
{isSearch && (
<Button
type="primary"
icon={<SearchOutlined />}
onClick={handleSubmit}
>
搜索
</Button>
)}
{isReset && (
<Button
type="default"
icon={<ReloadOutlined />}
onClick={handleReset}
>
重置
</Button>
)}
</Space>
</Form.Item>
</Form>
{sufFixChild && <div className="suf-fix-child">{sufFixChild}</div>}
</SearchBoxWrap>
);
};
export default SearchBoxView;
// 样式
const SearchBoxWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
display: flex;
justify-content: flex-start;
align-items: flex-start;
`;
...@@ -15,6 +15,19 @@ const BreadcrumbWrap = styled.div` ...@@ -15,6 +15,19 @@ const BreadcrumbWrap = styled.div`
color: #666666; color: #666666;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
@media (prefers-color-scheme: dark) {
.title {
color: #fff;
}
.ant-breadcrumb {
.ant-breadcrumb-separator {
color: #d9d9d9;
}
.ant-breadcrumb-link {
color: #d9d9d9;
}
}
}
`; `;
const BreadcrumbView: React.FC = () => { const BreadcrumbView: React.FC = () => {
...@@ -45,9 +58,28 @@ const BreadcrumbView: React.FC = () => { ...@@ -45,9 +58,28 @@ const BreadcrumbView: React.FC = () => {
{ name: '订单详情', path: 'product' }, { name: '订单详情', path: 'product' },
], ],
}, },
{ name: '设备租赁', path: 'rent' }, {
name: '设备租赁',
path: 'rent',
children: [{ name: '租赁详情', path: 'detail' }],
},
{ name: '执照培训', path: 'train' }, { name: '执照培训', path: 'train' },
{ name: '飞手约单', path: 'flyer' }, { name: '飞手约单', path: 'flyer' },
{
name: '执照培训',
path: 'course',
children: [{ name: '课程详情', path: 'detail' }],
},
{
name: '行业新闻',
path: 'news',
children: [{ name: '文章详情', path: 'detail' }],
},
{
name: '服务网点',
path: 'store',
children: [{ name: '全国网点', path: 'map' }],
},
]; ];
// 转换路由 // 转换路由
const getCurrentRouter = () => { const getCurrentRouter = () => {
...@@ -84,6 +116,7 @@ const BreadcrumbView: React.FC = () => { ...@@ -84,6 +116,7 @@ const BreadcrumbView: React.FC = () => {
}, },
...getCurrentRouter(), ...getCurrentRouter(),
]} ]}
className="mt-0.5"
/> />
</BreadcrumbWrap> </BreadcrumbWrap>
); );
......
...@@ -79,12 +79,14 @@ const CategorySelectView: React.FC<{ ...@@ -79,12 +79,14 @@ const CategorySelectView: React.FC<{
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
if (!list?.length) return; if (!list?.length) return;
const main = router?.query?.main;
const second = router?.query?.second;
// 一级分类id // 一级分类id
const mainID = Number(router?.query?.main); const mainID = Number(router?.query?.main);
// 二级分类id // 二级分类id
const secondID = Number(router?.query?.second); const secondID = Number(router?.query?.second);
// 如果路由里面有一级分类id,则初始化一级分类 // 如果路由里面有一级分类id,则初始化一级分类
if (mainID) { if (mainID && main) {
const index = list?.findIndex((i) => i?.value === mainID) || 0; const index = list?.findIndex((i) => i?.value === mainID) || 0;
setCurrentIndex(index); setCurrentIndex(index);
onMain?.(list[index]?.value); onMain?.(list[index]?.value);
...@@ -92,7 +94,7 @@ const CategorySelectView: React.FC<{ ...@@ -92,7 +94,7 @@ const CategorySelectView: React.FC<{
onSecond?.(list[index]?.children?.map((i) => i?.value) || []); onSecond?.(list[index]?.children?.map((i) => i?.value) || []);
} }
// 如果路由里面有二级分类id,则初始化二级分类 // 如果路由里面有二级分类id,则初始化二级分类
if (secondID) { if (secondID && second) {
const children = list?.find((i) => i?.value === mainID)?.children; const children = list?.find((i) => i?.value === mainID)?.children;
const index = children?.findIndex((i) => i?.value === secondID) || 0; const index = children?.findIndex((i) => i?.value === secondID) || 0;
setSecondIndex([index]); setSecondIndex([index]);
...@@ -220,4 +222,14 @@ const CategorySelectWrap = styled.div` ...@@ -220,4 +222,14 @@ const CategorySelectWrap = styled.div`
border-radius: 4px; border-radius: 4px;
} }
} }
@media (prefers-color-scheme: dark) {
.category-select {
.select-item {
color: #fff;
}
.item-active {
color: #000;
}
}
}
`; `;
import React from 'react';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { InterListType } from '@/api/interface';
import { QueryCurriculumInfoListType } from '@/api/interface/course';
// 列表类型
type DetailType = InterListType<QueryCurriculumInfoListType>[0];
// 课程类型列表
const courseAttributeList: { label: string; value: number; color: string }[] = [
{ label: '免费', value: 0, color: '#f99a0b' },
{ label: '积分', value: 1, color: '#00AD53' },
{ label: '付费', value: 2, color: '#FF4600' },
];
const CourseListItem: React.FC<{ detail: DetailType }> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// 获取当前课程属性
const getCourseAttribute = () => {
return courseAttributeList.find((i) => i.value === detail?.courseAttribute);
};
// 跳转商品详情
const handleDetail = () => {
router.push(`/course/detail/${detail?.id}`).then();
};
return (
<CourseListItemWrap onClick={handleDetail}>
<div className="mb-2 h-32 w-full">
<img
className="h-full w-full object-cover"
src={
detail?.surfaceUrl
? `${detail?.surfaceUrl}?x-oss-process=image/quality,q_25`
: `${detail?.videoUrl}?x-oss-process=video/snapshot,t_1000,m_fast`
}
alt={detail?.curriculumName}
/>
</div>
<div className="course-content w-full px-2">
<div className="content-title flex w-full flex-nowrap">
<div className="title text-ellipsis">{detail?.curriculumName}</div>
<div
className="tag"
style={{ background: getCourseAttribute()?.color }}
>
{getCourseAttribute()?.label}
</div>
</div>
<div className="text-ellipsis text-xs text-gray-400">
{detail?.curriculumDesc}
</div>
{!!detail?.requireAmout && (
<div className="content-price">
<span className="label">¥</span>
<span className="num">{detail?.requireAmout}</span>
</div>
)}
{!!detail?.requireIntegral && (
<div className="content-price">
<span className="num">{detail?.requireIntegral}</span>
<span className="label">积分</span>
</div>
)}
</div>
<div className="course-footer flex justify-between">
<div className="label">课程供应商</div>
<div className="num">{(detail?.id || 1) * 333}人已学习</div>
</div>
</CourseListItemWrap>
);
};
export default CourseListItem;
// 样式
const CourseListItemWrap = styled.div`
position: relative;
box-sizing: border-box;
width: calc((100% - (0.83rem * 4)) / 5);
height: 14.5rem;
background: #ffffff;
box-shadow: 0 0.17rem 0.86rem 0 rgba(65, 65, 65, 0.08);
border-radius: 0.33rem;
margin: 0 0.83rem 0.83rem 0;
cursor: pointer;
overflow: hidden;
&:hover {
filter: brightness(0.9);
}
&:nth-child(5n) {
margin-right: 0;
}
.course-content {
.content-title {
margin-bottom: 0.25rem;
.title {
font-weight: bold;
color: #333333;
margin-right: 0.25rem;
}
.tag {
min-width: 2.25rem;
box-sizing: border-box;
font-size: 12px;
background: #f99a0b;
border-radius: 0.1rem;
text-align: center;
color: #ffffff;
padding: 0 0.25rem;
transform: scale(0.8);
}
}
.content-price {
font-weight: 500;
color: #ff3300;
.num {
font-size: 15px;
line-height: 35rpx;
margin-right: 8rpx;
}
.label {
font-size: 12px;
line-height: 28rpx;
font-weight: bold;
}
}
}
.course-footer {
position: absolute;
width: 100%;
bottom: 0.5rem;
left: 0;
box-sizing: border-box;
padding: 0 0.5rem;
color: #aaa;
}
`;
import React, { useState } from 'react';
import {
DownOutlined,
LikeOutlined,
MessageOutlined,
UpOutlined,
} from '@ant-design/icons';
import { App, Button, Input } from 'antd';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface';
import { AllCommentListType, ReplyListType } from '@/api/interface/home';
// 详情类型
type DetailType = InterListType<AllCommentListType>[0];
// 列表类型
type ListType = InterListType<ReplyListType>;
const ForumCommentItem: React.FC<{
detail: DetailType;
reply?: ListType;
origin?: DetailType;
onRefresh?: () => void;
}> = ({ detail, reply, origin, onRefresh }) => {
// 静态组件
const { message } = App.useApp();
// 是否显示回复输入
const [isInput, setIsInput] = useState(false);
// 是否展开评论列表
const [isReply, setIsReply] = useState(false);
// 回复列表
const [replyList, setReplyList] = useState<ListType>([]);
// 获取回复列表
const getReplyList = async () => {
const res = await HomeAPI.getReplyList({
dynamicId: detail?.dynamicId,
id: detail?.id,
pageNo: 1,
pageSize: 999,
});
if (res && res.code === '200') {
// console.log('回复列表 ===>', res.result?.list);
setReplyList(res.result?.list || []);
}
};
// 展开评论列表
const handleReply = async (e: Boolean) => {
setIsReply(!e);
if (!e) {
getReplyList().then();
} else {
setReplyList([]);
}
};
// 获取@人的名字
const getReplyName = () => {
if (!reply) return undefined;
const item = reply?.find((i) => i?.id === detail?.pid);
return item ? (
<>
回复&nbsp;
<Button type={'link'} className="px-0">
@{item?.userAccountVO?.nickName}
</Button>
&nbsp;
</>
) : undefined;
};
// 获取@人的名字
const getReplyNameStr = () => {
if (!reply) return `回复 @${detail?.userAccountVO?.nickName} `;
const item = reply?.find((i) => i?.id === detail?.pid);
return item
? `回复 @${item?.userAccountVO?.nickName} `
: `回复 @${detail?.userAccountVO?.nickName} `;
};
// 回复数据
const [replyText, setReplyText] = useState<string>();
// 提交数据
const handleSubmit = async () => {
if (!replyText) {
await message.warning('请输入内容');
return;
}
// 提交数据
const res = await HomeAPI.publishComment({
content: replyText,
dynamicId: detail?.dynamicId,
pid: detail?.id,
reviewId: origin?.id || detail?.id,
});
if (res && res.code === '200') {
message.success('回复成功');
setReplyText(undefined);
setIsInput(false);
onRefresh?.();
}
};
// 点赞
const handleReviewLikes = async () => {
const res = await HomeAPI.reviewLikes({
id: detail?.id,
status: !detail?.status,
});
if (res && res.code === '200') {
message.success(!detail?.status ? '点赞成功' : '取消点赞');
onRefresh?.();
}
};
return (
<ForumCommentWrap>
<img
className={'image'}
src={detail?.userAccountVO?.userImg}
alt={detail?.userAccountVO?.userName}
/>
<div className="content">
<div className="name">
{detail?.userAccountVO?.nickName ||
`云享飞用户_${detail?.userAccountId}`}
</div>
<div className="text">
{getReplyName()}
{detail?.content}
</div>
<div className="action">
<div className="date">{detail?.createTime}</div>
<div className="button">
<Button
type={'text'}
icon={<MessageOutlined />}
onClick={() => setIsInput(!isInput)}
>
{isInput ? '取消回复' : '回复'}
</Button>
<Button
type={'text'}
icon={<LikeOutlined />}
className={!detail?.status ? 'text-333' : 'text-primary'}
onClick={handleReviewLikes}
>
&nbsp;{detail?.likeCount || 0}
</Button>
</div>
</div>
{isInput && (
<div className="textarea">
<Input.TextArea
placeholder={getReplyNameStr()}
showCount
style={{ height: 86 }}
maxLength={140}
onChange={(e) => setReplyText(e.target.value)}
/>
<div className="action">
<Button type="primary" shape="round" onClick={handleSubmit}>
发布
</Button>
</div>
</div>
)}
{!!replyList?.length &&
replyList?.map((n, m) => (
<ForumCommentItem
detail={n as any}
key={m}
reply={replyList}
origin={detail}
onRefresh={getReplyList}
/>
))}
{!!detail?.replyCount && (
<div className="flex">
<div className="reply" onClick={() => handleReply(isReply)}>
{!isReply ? `展开${detail?.replyCount}条回复` : `收起`}
</div>
{!isReply ? (
<DownOutlined className="ml-1 text-xs text-999" />
) : (
<UpOutlined className="ml-1 text-xs text-999" />
)}
</div>
)}
</div>
</ForumCommentWrap>
);
};
export default ForumCommentItem;
// 样式
const ForumCommentWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
display: flex;
align-items: flex-start;
justify-content: flex-start;
&:not(:first-child) {
margin-top: 1rem;
}
.image {
width: 2.25rem;
height: 2.25rem;
border-radius: 50%;
margin-right: 1rem;
}
.content {
position: relative;
width: calc(100% - 3.5rem);
box-sizing: border-box;
.name {
font-weight: bold;
}
.text {
width: 100%;
color: #666666;
margin-bottom: 0.5rem;
}
.action {
position: relative;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.date {
color: #999999;
}
}
.textarea {
position: relative;
width: 100%;
box-sizing: border-box;
margin-top: 0.5rem;
.action {
margin-top: 1.5rem;
width: 100%;
display: flex;
justify-content: flex-end;
}
}
.reply {
cursor: pointer;
&:hover {
color: #ff392b;
}
}
}
`;
import React, { useEffect, useState } from 'react';
import {
ExportOutlined,
LikeOutlined,
MessageOutlined,
} from '@ant-design/icons';
import { App, Button, Image } from 'antd';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface';
import { ForumListType } from '@/api/interface/home';
import { RootState } from '@/store';
import { setGlobalData } from '@/store/module/globalData';
import { SystemState } from '@/store/module/system';
// 详情类型
type DetailType = InterListType<ForumListType>[0];
const ForumItemView: React.FC<{
detail: DetailType;
isDetail?: Boolean;
hiddenMedia?: Boolean;
onRefresh?: () => void;
}> = ({ detail, isDetail = false, onRefresh, hiddenMedia = false }) => {
// 静态组件
const { message } = App.useApp();
// 路由钩子
const router = useRouter();
// system
const system = useSelector((state: RootState) => state.system) as SystemState;
// store
const dispatch = useDispatch();
// 缓存贴子的数据
const [forumDetail, setForumDetail] = useState<DetailType>();
// 获取帖子的媒体信息 0图片 1视频
const getForumMedia = (type: number) => {
return detail?.mediaVO?.filter((i) => i?.type === type)?.slice(0, 4);
};
// 帖子相关操作
const handleAction = (type: number) => {
// 评论
if (type === 2) {
// 如果是在详情页面则不执行跳转
if (isDetail) return;
// 跳转到评论页
dispatch(setGlobalData({ loadingSpinnerVisible: true }));
router.push(`/forum/detail/${detail?.id}`).then();
}
};
// 点赞
const handleReviewLikes = async () => {
// 如果未登录,则弹出登录框
if (!system?.token) {
dispatch(setGlobalData({ loginModalVisible: true }));
return;
}
const res = await HomeAPI.likeOrCancel({
dynamicId: detail?.id,
});
if (res && res.code === '200') {
message.success(!forumDetail?.likes ? '点赞成功' : '取消点赞');
setForumDetail({
...detail,
likes: !forumDetail?.likes,
likesCount: !forumDetail?.likes
? Number(forumDetail?.likesCount) + 1
: Number(forumDetail?.likesCount) - 1,
});
}
};
// 监听变化
useEffect(() => {
if (!detail) return;
setForumDetail(detail);
}, [detail]);
return (
<ForumItemWrap>
<div className="relative mb-2 flex w-full items-center justify-start">
<img
className="h-9 w-9 rounded-full"
src={
detail?.userBaseInfo?.userImg ||
'https://file.iuav.com/file/sharefly-logo.png'
}
alt="头像"
/>
<div className="ml-2">
<div className="font-bold">
{detail?.userBaseInfo?.nickName || '微信用户'}
</div>
<div className="text-aaa">{detail?.dynamicPublishTime}</div>
</div>
</div>
<div className="mb-3 w-full leading-5 tracking-wide">
{detail?.forumGambitDTOList?.map((i, j) => (
<span key={j} className="mr-1 cursor-pointer font-bold text-tag">
{i.gambitName}
</span>
))}
<span>{detail?.description}</span>
</div>
{!hiddenMedia && (
<div className="forum-media flex w-full flex-wrap items-start justify-start">
{getForumMedia(0)?.length
? getForumMedia(0)?.map((i, j) => (
<Image
src={`${i?.url}?x-oss-process=image/quality,Q_50`}
alt="图片"
key={j}
fallback={
'https://pad-video-x.oss-cn-shenzhen.aliyuncs.com/file/identity-bg.png'
}
/>
))
: undefined}
{getForumMedia(1)?.length
? getForumMedia(1)?.map((i, j) => (
<div
className="media-video relative box-border overflow-hidden rounded-lg"
key={j}
>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video src={i.url} controls={true} />
</div>
))
: undefined}
</div>
)}
<div className="forum-action flex pb-2 text-777">
<div className="action-item mr-6" onClick={() => handleAction(1)}>
<Button
type={'text'}
icon={<LikeOutlined />}
className={!forumDetail?.likes ? 'text-333' : 'text-primary'}
onClick={handleReviewLikes}
>
&nbsp;{forumDetail?.likesCount}
</Button>
</div>
<div className="action-item mr-6" onClick={() => handleAction(2)}>
<Button type={'text'} icon={<MessageOutlined />}>
&nbsp;{forumDetail?.commentCount}
</Button>
</div>
<div className="action-item mr-6" onClick={() => handleAction(3)}>
<Button type={'text'} icon={<ExportOutlined />}>
&nbsp;{forumDetail?.transpond || 0}
</Button>
</div>
</div>
</ForumItemWrap>
);
};
export default ForumItemView;
// 样式
const ForumItemWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
margin-bottom: 1rem;
.forum-media {
.media-video {
width: 500px;
height: calc(500px / 16 * 9);
margin-bottom: 1rem;
video {
width: 100%;
height: 100%;
}
}
}
.forum-action {
.action-item {
cursor: pointer;
&:hover {
color: #ff552d;
}
}
}
.ant-image {
width: calc((100% - 0.67rem * 3) / 4);
height: 8.6rem;
margin: 0 0.67rem 0.67rem 0;
border-radius: 0.5rem;
overflow: hidden;
&:nth-child(4n) {
margin-right: 0;
}
.ant-image-img {
object-fit: cover;
height: 8.6rem;
}
}
`;
import React from 'react';
import styled from 'styled-components';
import { InterListType } from '@/api/interface';
import { AllCommentListType } from '@/api/interface/home';
// 详情类型
type DetailType = InterListType<AllCommentListType>[0];
const ForumReplyItem = () => {
return (
<ForumReplyWrap>
<div>ForumReplyItem</div>
</ForumReplyWrap>
);
};
export default ForumReplyItem;
// 样式
const ForumReplyWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
display: flex;
align-items: flex-start;
justify-content: flex-start;
margin-bottom: 1.5rem;
.image {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
margin-right: 1rem;
}
.content {
position: relative;
width: calc(100% - 3.5rem);
box-sizing: border-box;
}
`;
import React, { useEffect, useState } from 'react';
import { FlyerAPI } from '@/api';
import { InterListType } from '@/api/interface';
import { queryCurriculumInfoListType } from '@/api/interface/flyer';
// 列表类型
type ListType = InterListType<queryCurriculumInfoListType>;
// 课程类型列表
const courseAttributeList: { label: string; value: number; color: string }[] = [
{ label: '免费', value: 0, color: 'bg-[#f99a0b]' },
{ label: '积分', value: 1, color: 'bg-[#00AD53]' },
{ label: '付费', value: 2, color: 'bg-[#FF4600]' },
];
const HomeCourseView = () => {
// 课程列表
const [courseList, setCourseList] = useState<ListType>([]);
// 获取热门课程
const getCurriculumInfoList = async () => {
const res = await FlyerAPI.queryCurriculumInfoList({
pageSize: 5,
pageNo: 1,
isHot: 1,
});
if (res && res.code === '200') {
const list = res.result?.list || [];
setCourseList(list);
}
};
// 获取当前课程属性
const getCourseAttribute = (item: ListType[0]) => {
return courseAttributeList?.find((i) => i.value === item?.courseAttribute);
};
// 组件挂载
useEffect(() => {
getCurriculumInfoList().then();
}, []);
return (
<div className="mb-3 mt-3 flex w-full flex-nowrap items-center justify-start">
{courseList?.map((i, j) => (
<div
className="animate__animated animate__fast animate__fadeIn relative mr-3.5 w-1/5 cursor-pointer overflow-hidden rounded [box-shadow:0rem_0.17rem_0.86rem_0rem_rgba(65,65,65,0.08)] last:mr-0 hover:brightness-95 hover:filter"
key={j}
>
{!!i.surfaceUrl && (
<img
src={i.surfaceUrl}
alt={i.curriculumName}
className="h-28 w-full object-cover"
/>
)}
<div className="box-border min-h-20 w-full bg-white px-2 pt-1">
<div className="mb-1 flex">
<div className="text-ellipsis font-bold text-333">
{i.curriculumName}
</div>
<div
className={`${getCourseAttribute(i)
?.color} ml-2 min-w-8 rounded px-1 text-center text-xs text-white`}
>
{getCourseAttribute(i)?.label}
</div>
</div>
<div className="text-ellipsis text-xs text-777">
{i.curriculumDesc}
</div>
<div className="absolute bottom-1 right-0 flex w-full justify-between px-2 text-xs text-aaa">
<div>课程供应商</div>
<div>{i.id * 333} 人已学习</div>
</div>
</div>
</div>
))}
</div>
);
};
export default HomeCourseView;
import React, { useEffect, useState } from 'react';
import { Button } from 'antd';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface';
import { AppListPilotType } from '@/api/interface/home';
// 列表类型
type ListType = InterListType<AppListPilotType>;
const HomeFlyerView = () => {
// 飞手列表
const [flyerList, setFlyerList] = useState<ListType>();
// 获取飞手列表
const getFlyerList = async () => {
const res = await HomeAPI.appListPilot({
// 审批通过
auditStatus: 1,
pageNo: 1,
pageSize: 20,
});
if (res && res.code === '200') {
const list = res.result?.list || [];
setFlyerList(
Array.from(
{ length: list.length < 5 ? list.length : 5 },
() => list.splice(Math.floor(Math.random() * list.length), 1)[0],
),
);
// console.log('获取飞手列表 --->', flyerList.value)
}
};
// 组件挂载
useEffect(() => {
getFlyerList().then();
}, []);
return (
<HomeFlyerWrap>
{flyerList?.map((i, j) => (
<div className="flyer-item" key={j}>
<img
className="mb-2 h-12 w-12 rounded-full"
src={i?.userImg}
alt={i?.userName}
/>
<div className="mb-2 font-bold">{i?.userName}</div>
<div className="mb-2 text-aaa">
<span className="mr-2">{i.residentCity}</span>
<span>{i.yearsOfWorking}年经验</span>
</div>
<Button type="primary" shape="round">
关注
</Button>
</div>
))}
</HomeFlyerWrap>
);
};
export default HomeFlyerView;
// 样式
const HomeFlyerWrap = styled.div`
position: relative;
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 2rem;
.flyer-item {
position: relative;
width: calc((100% - 4rem) / 5);
height: 10rem;
box-shadow: 0 0.17rem 0.86rem 0 rgba(65, 65, 65, 0.08);
border-radius: 0.33rem;
margin-right: 1rem;
background: #ffffff;
//background: lightblue;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
&:last-child {
margin-right: 0;
}
}
`;
import React, { useEffect, useState } from 'react';
import { App, Skeleton } from 'antd';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterListType, PaginationProps } from '@/api/interface';
import { ForumListType } from '@/api/interface/home';
import ForumItemView from '@/components/forumItem';
import HomeFlyerView from '@/components/home-comp/home-flyer';
import HomeTopicView from '@/components/home-comp/home-topic';
import InfiniteScrollList from '@/components/InfiniteScrollList';
import { RootState } from '@/store';
import { SystemState } from '@/store/module/system';
// 列表类型
type ListType = InterListType<ForumListType>;
const HomeForumView = () => {
// store
const { message } = App.useApp();
// system
const system = useSelector((state: RootState) => state.system) as SystemState;
// 翻页数据
const [pagination, setPagination] = useState<
{ totalPage: number } & PaginationProps
>({
pageNo: 1,
pageSize: 10,
totalPage: 0,
});
// 论坛列表
const [forumList, setForumList] = useState<ListType>([]);
// 获取论坛列表
const getForumList = async () => {
const res = await HomeAPI[system?.token ? 'getForumList' : 'getForumList1'](
{
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
},
);
if (res && res.code === '200') {
const { list, totalPage, pageNo, pageSize } = res.result || {};
setForumList((prevList) => [...prevList, ...(list || [])]);
pagination.totalPage = totalPage;
setPagination({ pageNo, pageSize, totalPage });
}
};
// 添加 handleReachBottom 函数
const handleReachBottom = async () => {
if (pagination.pageNo < pagination.totalPage) {
pagination.pageNo += 1;
await getForumList();
} else {
message.success('没有更多数据了');
}
};
// 页面挂载
useEffect(() => {
getForumList().then();
}, []);
return (
<InfiniteScrollList bottomDistance={300} onReachBottom={handleReachBottom}>
<HomeForumWrap>
<div className="forum-list">
{forumList
?.slice(0, 1)
?.map((i, j) => <ForumItemView key={j} detail={i} />)}
<HomeFlyerView />
{forumList
?.slice(1)
?.map((i, j) => <ForumItemView key={j} detail={i} />)}
<Skeleton active avatar />
</div>
<HomeTopicView />
</HomeForumWrap>
</InfiniteScrollList>
);
};
export default HomeForumView;
// 样式
const HomeForumWrap = styled.div`
position: relative;
width: 100%;
display: flex;
align-items: flex-start;
justify-content: flex-start;
flex-wrap: nowrap;
.forum-list {
position: relative;
width: calc(100% - 20rem - 10rem);
margin-right: 10rem;
box-sizing: border-box;
}
`;
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } 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';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import QrcodePopover from '@/components/qrcodePopover'; import QrcodePopover from '@/components/qrcodePopover';
const HomeNewsView = () => { // 帖子类型
// 帖子类型 const indexList = [
const indexList = [
{ label: 1, value: '#FF392B' }, { label: 1, value: '#FF392B' },
{ label: 2, value: '#FE792B' }, { label: 2, value: '#FE792B' },
{ label: 3, value: '#FEA32B' }, { label: 3, value: '#FEA32B' },
]; ];
// 热门类型 // 热门类型
const typeList = [ const typeList = [
{ label: 1, value: '#FFB555' }, { label: 1, value: '#FFB555' },
{ label: 2, value: '#FF6155' }, { label: 2, value: '#FF6155' },
{ label: 3, value: '#B700D9' }, { label: 3, value: '#B700D9' },
]; ];
const HomeNewsView = () => {
// 路由钩子
const router = useRouter();
// tab栏数据 // tab栏数据
const [tabList, setTabList] = useState<{ label: string; value: any[] }[]>([ const [tabList, setTabList] = useState<{ label: string; value: any[] }[]>([
// { // {
...@@ -95,6 +99,14 @@ const HomeNewsView = () => { ...@@ -95,6 +99,14 @@ const HomeNewsView = () => {
const getTagBgColor = (item: any[0]) => { const getTagBgColor = (item: any[0]) => {
return typeList.find((i) => i.label === item.gambitProperty)?.value; return typeList.find((i) => i.label === item.gambitProperty)?.value;
}; };
// 跳转到更多了
const handleMore = async () => {
await router.push('/news');
};
// 跳转详情
const handleDetail = async (item: { id: number }, index: number) => {
await router.push(`/news/detail/${item.id}?type=${index + 1}`);
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
handleSelect(0).then(); handleSelect(0).then();
...@@ -116,7 +128,7 @@ const HomeNewsView = () => { ...@@ -116,7 +128,7 @@ const HomeNewsView = () => {
))} ))}
</div> </div>
<QrcodePopover path={'page-home/news-list/index'}> <QrcodePopover path={'page-home/news-list/index'}>
<div className="head-more flex-between"> <div className="head-more flex-between" onClick={handleMore}>
<div className="more-text">更多</div> <div className="more-text">更多</div>
<RightOutlined style={{ fontSize: 10, color: '#33333380' }} /> <RightOutlined style={{ fontSize: 10, color: '#33333380' }} />
</div> </div>
...@@ -140,7 +152,10 @@ const HomeNewsView = () => { ...@@ -140,7 +152,10 @@ const HomeNewsView = () => {
> >
{j + 1} {j + 1}
</div> </div>
<div className="item-title text-ellipsis"> <div
className="item-title text-ellipsis"
onClick={() => handleDetail(i, currentIndex)}
>
{i?.gambitName || i?.newsTitle || i?.tenderTitle} {i?.gambitName || i?.newsTitle || i?.tenderTitle}
</div> </div>
{/* {currentIndex === 0 && ( */} {/* {currentIndex === 0 && ( */}
...@@ -228,7 +243,7 @@ const HomeNewWrap = styled.div` ...@@ -228,7 +243,7 @@ const HomeNewWrap = styled.div`
width: 100%; width: 100%;
height: 12rem; height: 12rem;
overflow: hidden; overflow: hidden;
overflow-y: auto; //overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
padding: 0 1.2rem; padding: 0 1.2rem;
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router'; 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 { RecommendGoodsType } from '@/api/interface/home'; import { RecommendGoodsType } from '@/api/interface/home';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型 // 列表类型
type ListType = InterDataType<RecommendGoodsType>; type ListType = InterDataType<RecommendGoodsType>;
...@@ -11,6 +13,8 @@ type ListType = InterDataType<RecommendGoodsType>; ...@@ -11,6 +13,8 @@ type ListType = InterDataType<RecommendGoodsType>;
const HomeProductView = () => { const HomeProductView = () => {
// 路由钩子 // 路由钩子
const router = useRouter(); const router = useRouter();
// store
const dispatch = useDispatch();
// 推荐商品列表 // 推荐商品列表
const [recommendGoodsList, setRecommendGoodsList] = useState< const [recommendGoodsList, setRecommendGoodsList] = useState<
ListType[0]['mallGoodsList'] ListType[0]['mallGoodsList']
...@@ -38,6 +42,11 @@ const HomeProductView = () => { ...@@ -38,6 +42,11 @@ const HomeProductView = () => {
}; };
// 跳转详情 // 跳转详情
const handleDetail = (item: ListType[0]['mallGoodsList'][0]) => { const handleDetail = (item: ListType[0]['mallGoodsList'][0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/mall/product/${item.id}`).then(); router.push(`/mall/product/${item.id}`).then();
}; };
// 组件挂载 // 组件挂载
...@@ -54,7 +63,7 @@ const HomeProductView = () => { ...@@ -54,7 +63,7 @@ const HomeProductView = () => {
> >
<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_50`}
alt={i.tradeName} alt={i.tradeName}
/> />
<div className="item-title text-ellipsis">{i.tradeName}</div> <div className="item-title text-ellipsis">{i.tradeName}</div>
......
...@@ -9,7 +9,7 @@ const HomeSearchView = () => { ...@@ -9,7 +9,7 @@ const HomeSearchView = () => {
<div className="head-logo"> <div className="head-logo">
<img <img
className="image" className="image"
src="/assets/image/home/sharefly-web-headbg.png" src="/assets/image/home/sharefly-web-head-logo.png"
alt="云享飞官网" alt="云享飞官网"
/> />
</div> </div>
......
...@@ -34,11 +34,11 @@ export const HomeSearchWrap = styled.div` ...@@ -34,11 +34,11 @@ export const HomeSearchWrap = styled.div`
position: relative; position: relative;
//width: 11.33rem; //width: 11.33rem;
//height: 3.04rem; //height: 3.04rem;
width: 12.42rem; width: 13.42rem;
height: 3.08rem; height: 3.04rem;
//background-image: url('https://pad-video-x.oss-cn-shenzhen.aliyuncs.com/file/sharefly-web-headlogo.png'); //background-image: url('https://pad-video-x.oss-cn-shenzhen.aliyuncs.com/file/sharefly-web-headlogo.png');
background-image: url('/assets/image/home/sharefly-web-headbg.png'); background-image: url('/assets/image/home/sharefly-web-head-logo.png');
background-size: 100% 100%; background-size: cover;
margin-bottom: 1.88rem; margin-bottom: 1.88rem;
.image { .image {
display: none; display: none;
......
...@@ -61,7 +61,7 @@ const HomeServiceView = () => { ...@@ -61,7 +61,7 @@ const HomeServiceView = () => {
{refresh && {refresh &&
industryList?.[currentIndex]?.inspectionDTOS?.map((i, j) => ( industryList?.[currentIndex]?.inspectionDTOS?.map((i, j) => (
<div <div
className="service-item animate__animated animate__fast animate__fadeIn" className="service-item animate__animated animate__fast animate__fadeIn overflow-hidden rounded"
key={j} key={j}
onClick={() => handleDetail(i)} onClick={() => handleDetail(i)}
> >
...@@ -83,7 +83,7 @@ const HomeServiceWrap = styled.div` ...@@ -83,7 +83,7 @@ const HomeServiceWrap = styled.div`
width: 100%; width: 100%;
min-height: 16.75rem; min-height: 16.75rem;
box-sizing: border-box; box-sizing: border-box;
//padding-bottom: 2rem; padding-bottom: 2rem;
.service-tab { .service-tab {
position: relative; position: relative;
width: 100%; width: 100%;
......
...@@ -53,7 +53,7 @@ const TabView01 = () => { ...@@ -53,7 +53,7 @@ const TabView01 = () => {
return ( return (
<TabViewWrap className="animate__animated animate__fast animate__fadeIn"> <TabViewWrap className="animate__animated animate__fast animate__fadeIn">
{tabList.map((i, j) => ( {tabList.map((i, j) => (
<div key={j}> <div className="tab-item align-start flex" key={j}>
<div className={'tab-little flex-start'}> <div className={'tab-little flex-start'}>
{!!i.icon && ( {!!i.icon && (
<img src={i.icon} alt={i.name} className="title-image" /> <img src={i.icon} alt={i.name} className="title-image" />
...@@ -90,20 +90,30 @@ const TabViewWrap = styled.div` ...@@ -90,20 +90,30 @@ const TabViewWrap = styled.div`
box-sizing: border-box; box-sizing: border-box;
padding: 0.79rem 1.58rem; padding: 0.79rem 1.58rem;
//background: lightyellow; //background: lightyellow;
.tab-little { .tab-item {
position: relative; position: relative;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding-bottom: 0.68rem; margin-bottom: 0.48rem;
padding-bottom: 0.48rem;
border-bottom: 0.02rem solid #ededed; border-bottom: 0.02rem solid #ededed;
margin-bottom: 0.5rem; &:last-child {
border: none;
}
}
.tab-little {
position: relative;
box-sizing: border-box;
//padding-bottom: 0.68rem;
//border-bottom: 0.02rem solid #ededed;
//margin-bottom: 0.5rem;
.title-image { .title-image {
width: 1.68rem; width: 1.68rem;
height: 1.68rem; height: 1.68rem;
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.title-name { .title-name {
font-size: 0.75rem; font-size: 0.78rem;
font-weight: 550; font-weight: 550;
color: #333333; color: #333333;
padding: 0; padding: 0;
...@@ -111,13 +121,29 @@ const TabViewWrap = styled.div` ...@@ -111,13 +121,29 @@ const TabViewWrap = styled.div`
} }
.tab-list { .tab-list {
position: relative; position: relative;
width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 1rem; max-width: calc(100% - 6rem);
transform: translateX(-10px); transform: translateY(0.86px);
//height: 1.58rem;
//overflow: hidden;
//margin-bottom: 1rem;
//transform: translateX(-10px);
.list-item { .list-item {
color: #666666; color: #666666;
font-weight: 500; font-weight: 500;
padding-right: 0;
}
}
@media (prefers-color-scheme: dark) {
.tab-little {
.title-name {
color: #fff;
}
}
.tab-list {
.list-item {
color: #999;
}
} }
} }
`; `;
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { RightOutlined } from '@ant-design/icons';
import { Button } from 'antd'; import { Button } from 'antd';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
...@@ -55,20 +56,20 @@ const TabView02 = () => { ...@@ -55,20 +56,20 @@ const TabView02 = () => {
getIndustryListPages().then(); getIndustryListPages().then();
}, []); }, []);
return ( return (
<TabViewWrap className="animate__animated animate__fast animate__fadeIn"> <TabViewWrap className="animate__animated animate__fast animate__fadeIn flex flex-wrap">
{tabList.map((i, j) => ( {tabList.map((i, j) => (
<div key={j}> <div className="tab-item" key={j}>
<div className={'tab-little flex-start'}> <div
className={'tab-little flex-start'}
onClick={() => handleMain(i)}
>
{!!i.typeImg && ( {!!i.typeImg && (
<img src={i.typeImg} alt={i.typeName} className="title-image" /> <img src={i.typeImg} alt={i.typeName} className="title-image" />
)} )}
<Button <Button type={'link'} className="title-name">
type={'link'}
className="title-name"
onClick={() => handleMain(i)}
>
{i.typeName} {i.typeName}
</Button> </Button>
<RightOutlined className="absolute right-[0.58rem] size-[10px] text-[#AEAFAF]" />
</div> </div>
<div className="tab-list flex-start"> <div className="tab-list flex-start">
{i.inspectionDTOS?.map((n, m) => ( {i.inspectionDTOS?.map((n, m) => (
...@@ -92,18 +93,28 @@ const TabViewWrap = styled.div` ...@@ -92,18 +93,28 @@ const TabViewWrap = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 0.79rem 1.58rem; padding: 0.58rem;
//background: lightyellow; //background: lightyellow;
.tab-item {
position: relative;
width: calc((100% / 3) - 0.2rem);
box-sizing: border-box;
margin: 0 0.2rem 0.58rem 0;
&:nth-child(3n) {
margin-right: 0;
}
}
.tab-little { .tab-little {
position: relative; position: relative;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding-bottom: 0.68rem;
border-bottom: 0.02rem solid #ededed; border-bottom: 0.02rem solid #ededed;
margin-bottom: 0.5rem; background: #f6f9ff;
border-radius: 0.5rem 0.5rem 0 0;
padding: 0 0.58rem;
.title-image { .title-image {
width: 1.68rem; width: 1.25rem;
height: 1.68rem; height: 1.25rem;
} }
.title-name { .title-name {
font-size: 0.75rem; font-size: 0.75rem;
...@@ -115,8 +126,8 @@ const TabViewWrap = styled.div` ...@@ -115,8 +126,8 @@ const TabViewWrap = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 1rem; height: 1.67rem;
transform: translateX(-10px); overflow: hidden;
.list-item { .list-item {
color: #666666; color: #666666;
font-weight: 500; font-weight: 500;
......
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 { ListBrandInfoType } from '@/api/interface/home'; import { ListBrandInfoType } from '@/api/interface/home';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型 // 列表类型
type ListType = InterListType<ListBrandInfoType>; type ListType = InterListType<ListBrandInfoType>;
const TabView03 = () => { const TabView03 = () => {
// 导航钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 列表数据 // 列表数据
const [tabList, setTabList] = useState<ListType>([]); const [tabList, setTabList] = useState<ListType>([]);
// 获取云享商城分类 // 获取云享商城分类
...@@ -22,6 +29,24 @@ const TabView03 = () => { ...@@ -22,6 +29,24 @@ const TabView03 = () => {
// console.log('获取云享商城分类 --->', res); // console.log('获取云享商城分类 --->', res);
} }
}; };
// 跳转一级分类详情
const handleMain = (i: ListType[0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/rent/${i?.id}`).then();
};
// 跳转二级分类详情
const handleSecond = (i: ListType[0], n: ListType[0]['modeInfoList'][0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/rent/${i?.id}/${n?.id}`).then();
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getListBrandInfo().then(); getListBrandInfo().then();
...@@ -31,11 +56,18 @@ const TabView03 = () => { ...@@ -31,11 +56,18 @@ const TabView03 = () => {
{tabList.map((i, j) => ( {tabList.map((i, j) => (
<div key={j}> <div key={j}>
<div className={'tab-little flex-start'}> <div className={'tab-little flex-start'}>
<div className="title-name">{i.brandName}</div> <div className="title-name" onClick={() => handleMain(i)}>
{i.brandName}
</div>
</div> </div>
<div className="tab-list flex-start"> <div className="tab-list flex-start">
{i.modeInfoList?.map((n, m) => ( {i.modeInfoList?.map((n, m) => (
<Button type={'link'} key={m} className="list-item"> <Button
type={'link'}
key={m}
className="list-item"
onClick={() => handleSecond(i, n)}
>
{n.modeName} {n.modeName}
</Button> </Button>
))} ))}
......
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 { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { GetPageHomeCategoriesType } from '@/api/interface/home'; import { SelectCurriculumClassifyType } from '@/api/interface/course';
import { CourseAPI } from '@/api/modules/course';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型 // 列表类型
type ListType = InterDataType<GetPageHomeCategoriesType>; type ListType = Array<
InterDataType<SelectCurriculumClassifyType>[0] & {
children: InterDataType<SelectCurriculumClassifyType>;
}
>;
const TabView04 = () => { const TabView04 = () => {
// 导航钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 列表数据 // 列表数据
const [tabList, setTabList] = useState< const [tabList, setTabList] = useState<ListType>([]);
Array<ListType[0] & { categoryList: ListType }> // 获取执照培训分类
>([]); const getCategoryInfo = async () => {
// 获取云享商城分类 const res = await CourseAPI.selectCurriculumClassify();
const getListBrandInfo = async () => {
const res = await HomeAPI.getPageHomeCategories({
type: 3,
});
if (res && res.code === '200') { if (res && res.code === '200') {
setTabList([ setTabList(
{ id: 1, categoryName: '执照培训', categoryList: res.result }, res.result
]); ?.filter((i) => !i.twoCourseId)
// console.log('获取云享商城分类 --->', res); ?.map((i) => {
const children = res.result?.filter(
(k) => k.oneCourseId === i.oneCourseId && k.twoCourseId,
);
return {
...i,
children,
};
}),
);
} }
}; };
// 跳转一级分类详情
const handleMain = (i: ListType[0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/course/${i?.oneCourseId}`).then();
};
// 跳转二级分类详情
const handleSecond = (i: ListType[0], n: ListType[0]['children'][0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/course/${i?.oneCourseId}/${n?.twoCourseId}`).then();
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getListBrandInfo().then(); getCategoryInfo().then();
}, []); }, []);
return ( return (
<TabViewWrap className="animate__animated animate__fast animate__fadeIn"> <TabViewWrap className="animate__animated animate__fast animate__fadeIn">
{tabList.map((i, j) => ( {tabList.map((i, j) => (
<div key={j}> <div className="tab-item align-start flex" key={j}>
<div className={'tab-little flex-start'}> <div className={'tab-little flex-start'}>
<div className="title-name">{i?.categoryName}</div> {!!i.classifyUrl && (
<img src={i.classifyUrl} alt={i.name} className="title-image" />
)}
<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?.categoryList?.map((n, m) => ( {i.children?.map((n, m) => (
<Button type={'link'} key={m} className="list-item"> <Button
{n?.categoryName} type={'link'}
key={m}
className="list-item"
onClick={() => handleSecond(i, n)}
>
{n.name}
</Button> </Button>
))} ))}
</div> </div>
...@@ -52,37 +100,63 @@ const TabView04 = () => { ...@@ -52,37 +100,63 @@ const TabView04 = () => {
const TabViewWrap = styled.div` const TabViewWrap = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 0.79rem 1.58rem; padding: 0.79rem 1.58rem;
//background: lightyellow; //background: lightyellow;
.tab-little { .tab-item {
position: relative; position: relative;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding-bottom: 0.68rem; margin-bottom: 0.48rem;
padding-bottom: 0.48rem;
border-bottom: 0.02rem solid #ededed; border-bottom: 0.02rem solid #ededed;
margin-bottom: 0.5rem; &:last-child {
border: none;
}
}
.tab-little {
position: relative;
box-sizing: border-box;
//padding-bottom: 0.68rem;
//border-bottom: 0.02rem solid #ededed;
//margin-bottom: 0.5rem;
.title-image { .title-image {
width: 1.68rem; width: 1.68rem;
height: 1.68rem; height: 1.68rem;
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.title-name { .title-name {
font-size: 0.75rem; font-size: 0.78rem;
font-weight: 550; font-weight: 550;
color: #333333; color: #333333;
padding: 0;
} }
} }
.tab-list { .tab-list {
position: relative; position: relative;
width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 1rem; max-width: calc(100% - 6rem);
transform: translateX(-10px); transform: translateY(0.86px);
//height: 1.58rem;
//overflow: hidden;
//margin-bottom: 1rem;
//transform: translateX(-10px);
.list-item { .list-item {
color: #666666; color: #666666;
font-weight: 500; font-weight: 500;
padding-right: 0;
}
}
@media (prefers-color-scheme: dark) {
.tab-little {
.title-name {
color: #fff;
}
}
.tab-list {
.list-item {
color: #999;
}
} }
} }
`; `;
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { RightOutlined } from '@ant-design/icons'; import { RightOutlined } from '@ant-design/icons';
import { Pagination, Rate } from 'antd'; import { Button, Pagination, Rate } from 'antd';
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, InterReqType } from '@/api/interface'; import { InterListType, InterReqType } from '@/api/interface';
import { AppListPilotType } from '@/api/interface/home'; import { AppListPilotType } from '@/api/interface/home';
import SearchBoxView, { SearchColumns } from '@/components/SearchBox';
// 列表类型 // 列表类型
type ListType = InterListType<AppListPilotType>; type ListType = InterListType<AppListPilotType>;
...@@ -12,12 +14,14 @@ type ListType = InterListType<AppListPilotType>; ...@@ -12,12 +14,14 @@ type ListType = InterListType<AppListPilotType>;
type ReqType = InterReqType<AppListPilotType>; type ReqType = InterReqType<AppListPilotType>;
const TabView05 = () => { const TabView05 = () => {
// 路由钩子
const router = useRouter();
// 飞手列表 // 飞手列表
const [flyerList, setFlyerList] = useState<ListType>([]); const [flyerList, setFlyerList] = useState<ListType>([]);
// 分页数据 // 分页数据
const [pagination, setPagination] = useState({ const [pagination, setPagination] = useState({
pageNo: 1, pageNo: 1,
pageSize: 8, pageSize: 6,
totalPage: 0, totalPage: 0,
totalCount: 0, totalCount: 0,
}); });
...@@ -42,18 +46,86 @@ const TabView05 = () => { ...@@ -42,18 +46,86 @@ const TabView05 = () => {
// console.log('列表数据 --->', list, pageNo, totalPage); // console.log('列表数据 --->', list, pageNo, totalPage);
} }
}; };
// 搜索事件
const handleSearch = async (value: ReqType) => {
await getFlyerList({
...value,
areaNumber: (value?.areaNumber as unknown as number[])?.at(-1),
pageNo: 1,
});
};
// 跳转列表
const handleMore = async () => {
await router.push('/flyer');
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getFlyerList().then(); getFlyerList().then();
}, []); }, []);
// 搜索列数据
const searchColumns: SearchColumns[] = [
{
type: 'Cascader',
// label: '飞手姓名',
name: 'areaNumber',
placeholder: '请选择飞手地区',
maxlength: 20,
width: 120,
api: HomeAPI.getSecondDistrictInfo(),
transform: (data) =>
data?.map((i: any) => ({
label: i.name,
value: i.id,
children: i.childInfo?.map((k: any) => ({
label: k.name,
value: k.id,
})),
})),
},
{
type: 'Select',
// label: '任务能力',
name: 'abilityId',
placeholder: '请选择任务能力',
width: 120,
api: HomeAPI.IndustryListPages({ pageNo: 1, pageSize: 999 }),
transform: (data) =>
data.list?.map((i: any) => ({ label: i.typeName, value: i.id })),
},
{
type: 'Select',
// label: '执照类型',
name: 'licenseType',
placeholder: '请选择执照类型',
width: 120,
options: [
{ label: 'CAAC', value: 'CAAC' },
{ label: 'UTC', value: 'UTC' },
{ label: '其他', value: 'OTHER' },
],
},
];
return ( return (
<TabViewWrap className={'animate__animated animate__fast animate__fadeIn'}> <TabViewWrap className={'animate__animated animate__fast animate__fadeIn'}>
{/* 搜索组件 */}
<SearchBoxView
columns={searchColumns}
sufFixChild={
<div className="flex" onClick={handleMore}>
<Button type={'link'} className="px-0">
更多
</Button>
<RightOutlined className="text-primary" />
</div>
}
onSearch={handleSearch}
/>
<div className="tab-title flex-between"> <div className="tab-title flex-between">
<div className="title-name">飞手约单</div> <div className="title-name">飞手约单</div>
</div> </div>
<div className="tab-list flex-start"> <div className="tab-list flex-start">
{flyerList?.map((i, j) => ( {flyerList?.map((i, j) => (
<div className="list-item" key={j}> <div className="list-item list-none" key={j}>
<div className="item-arrow"> <div className="item-arrow">
<RightOutlined style={{ fontSize: 10, color: '#A0A0A0' }} /> <RightOutlined style={{ fontSize: 10, color: '#A0A0A0' }} />
</div> </div>
...@@ -85,7 +157,10 @@ const TabView05 = () => { ...@@ -85,7 +157,10 @@ const TabView05 = () => {
</div> </div>
<div className="item-foot flex-start"> <div className="item-foot flex-start">
<div className="foot-state">空闲</div> <div className="foot-state">空闲</div>
<div className="foot-text text-ellipsis"> <div
className="foot-text text-ellipsis"
title={i?.individualResume}
>
{i?.individualResume} {i?.individualResume}
</div> </div>
</div> </div>
...@@ -112,13 +187,14 @@ const TabViewWrap = styled.div` ...@@ -112,13 +187,14 @@ const TabViewWrap = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 1rem 1.58rem; padding: 0.58rem;
.tab-title { .tab-title {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
border-bottom: 0.02rem solid #ededed; border-bottom: 0.02rem solid #ededed;
padding-bottom: 0.58rem; padding-bottom: 0.58rem;
margin-bottom: 0.42rem; margin-bottom: 0.42rem;
display: none;
.title-name { .title-name {
font-size: 1rem; font-size: 1rem;
font-weight: 550; font-weight: 550;
...@@ -129,10 +205,12 @@ const TabViewWrap = styled.div` ...@@ -129,10 +205,12 @@ const TabViewWrap = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 0.5rem;
.list-item { .list-item {
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
width: 14.13rem; width: calc(50% - 1rem);
//width: 14.13rem;
min-height: 5.54rem; min-height: 5.54rem;
background: #ffffff; background: #ffffff;
border-radius: 0.25rem; border-radius: 0.25rem;
...@@ -149,6 +227,7 @@ const TabViewWrap = styled.div` ...@@ -149,6 +227,7 @@ const TabViewWrap = styled.div`
right: 0.67rem; right: 0.67rem;
} }
.item-head { .item-head {
flex-wrap: nowrap;
.head-image { .head-image {
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
...@@ -157,6 +236,7 @@ const TabViewWrap = styled.div` ...@@ -157,6 +236,7 @@ const TabViewWrap = styled.div`
} }
.head-content { .head-content {
position: relative; position: relative;
width: calc(100% - 4rem);
.content-title { .content-title {
align-items: baseline; align-items: baseline;
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
...@@ -181,6 +261,7 @@ const TabViewWrap = styled.div` ...@@ -181,6 +261,7 @@ const TabViewWrap = styled.div`
.content-tag { .content-tag {
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 0.33rem; margin-bottom: 0.33rem;
min-height: 0.79rem;
.tag-item { .tag-item {
min-width: 2.42rem; min-width: 2.42rem;
height: 0.79rem; height: 0.79rem;
...@@ -199,7 +280,8 @@ const TabViewWrap = styled.div` ...@@ -199,7 +280,8 @@ const TabViewWrap = styled.div`
content: ''; content: '';
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 8.21rem; width: 100%;
//width: 8.21rem;
height: 0.02rem; height: 0.02rem;
background: #dedede; background: #dedede;
} }
......
...@@ -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.slice(0, 1) || []); setCategoryList(res.result || []);
} }
}; };
// 选择分类 // 选择分类
......
...@@ -81,7 +81,7 @@ export const HomeTabWrap = styled.div` ...@@ -81,7 +81,7 @@ export const HomeTabWrap = styled.div`
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
// 超出滚动 // 超出滚动
overflow-y: auto; //overflow-y: auto;
border: 0.04rem solid #e3e3e3; border: 0.04rem solid #e3e3e3;
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #d9d9d9; background: #d9d9d9;
...@@ -99,4 +99,18 @@ export const HomeTabWrap = styled.div` ...@@ -99,4 +99,18 @@ export const HomeTabWrap = styled.div`
height: 7px; height: 7px;
} }
} }
@media (prefers-color-scheme: dark) {
.tab-label {
.label-item {
.item-name {
color: #fff;
}
}
.item-active {
.item-name {
color: #000;
}
}
}
}
`; `;
...@@ -5,12 +5,12 @@ import { ...@@ -5,12 +5,12 @@ import {
VerticalAlignTopOutlined, VerticalAlignTopOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux'; 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 { setGlobalData } from '@/store/module/globalData';
import { UserInfoState } from '@/store/module/userInfo'; import { UserInfoState } from '@/store/module/userInfo';
import { formatLocationStr } from '@/utils/formatLocation'; import { formatLocationStr } from '@/utils/formatLocation';
...@@ -20,6 +20,8 @@ import { bigNumberTransform } from '@/utils/money'; ...@@ -20,6 +20,8 @@ import { bigNumberTransform } from '@/utils/money';
type ListType = InterDataType<AppPublishListType>; type ListType = InterDataType<AppPublishListType>;
const HomeTaskView = () => { const HomeTaskView = () => {
// 路由钩子
const router = useRouter();
// store // store
const dispatch = useDispatch(); const dispatch = useDispatch();
// userInfo // userInfo
...@@ -135,6 +137,10 @@ const HomeTaskView = () => { ...@@ -135,6 +137,10 @@ const HomeTaskView = () => {
}), }),
); );
}; };
// 跳转更多列表
const handleMore = async () => {
await router.push('/service/task');
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getRequirementsListType().then(); getRequirementsListType().then();
...@@ -144,18 +150,20 @@ const HomeTaskView = () => { ...@@ -144,18 +150,20 @@ 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 cursor-pointer" onClick={handleMore}>
<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} onClick={() => handleDetail(i)}> <div
className="list-item list-none"
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, { useEffect, useState } from 'react';
import { Affix } from 'antd';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface';
import { GetAppGambitListType } from '@/api/interface/home';
// 列表类型
type ListType = InterListType<GetAppGambitListType>;
// 帖子类型
const indexList = [
{ label: 1, value: 'text-[#FF392B]' },
{ label: 2, value: 'text-[#FE792B]' },
{ label: 3, value: 'text-[#FEA32B]' },
{ label: 4, value: 'text-[#FEA32B]' },
{ label: 5, value: 'text-[#FEA32B]' },
];
const HomeTopicView = () => {
// 路由钩子
const router = useRouter();
// 话题列表
const [topicList, setTopicList] = useState<ListType>();
// 获取话题列表
const getListGambit = async () => {
const res = await HomeAPI.getAppGambitList({
pageNo: 1,
pageSize: 10,
});
if (res && res.code === '200') {
setTopicList(res.result.list || []);
}
};
// 跳转详情
const handleDetail = async (item: ListType[0]) => {
await router.push(`/forum/topic/${item.id}`);
};
// 组件挂载
useEffect(() => {
getListGambit().then();
}, []);
return (
<Affix offsetTop={58}>
<HomeTopicWrap>
<div className="title">热门话题</div>
{topicList?.map((i, j) => (
<div key={j} className="mb-3 flex">
<div className={`${indexList[j]?.value} font-bold`}>{j + 1}</div>
<div
className="ml-2 cursor-pointer text-ellipsis hover:text-tag"
onClick={() => handleDetail(i)}
>
{i.gambitName}
</div>
</div>
))}
</HomeTopicWrap>
</Affix>
);
};
export default HomeTopicView;
// 样式
const HomeTopicWrap = styled.div`
position: relative;
width: 20rem;
min-height: 20rem;
//background: linear-gradient(
// 180deg,
// #fff9f8 2%,
// #fff9f8 2%,
// #fffdfc 15%,
// #ffffff 24%,
// #ffffff 100%
//);
border-radius: 0.42rem;
box-sizing: border-box;
background-image: url('https://file.iuav.com/file/sharefly-topic-bg.png');
background-size: cover;
border: 1px solid #e3e3e3;
padding: 0.75rem 1.25rem 0 1.25rem;
.title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 1rem;
}
`;
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
EnvironmentFilled, EnvironmentFilled,
LogoutOutlined, LogoutOutlined,
ReloadOutlined, ReloadOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Button, Dropdown, MenuProps, Modal } from 'antd'; import { App, Button, Dropdown, MenuProps, Modal } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { throttle } from 'lodash';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { CommonAPI } from '@/api'; import { CommonAPI } from '@/api';
...@@ -21,7 +22,12 @@ import getLocationByIP from '@/utils/getLocationByIP'; ...@@ -21,7 +22,12 @@ import getLocationByIP from '@/utils/getLocationByIP';
const HeaderView: React.FC<{ const HeaderView: React.FC<{
placeholder: boolean; placeholder: boolean;
}> = ({ placeholder }) => { autoChange: boolean;
topDistance: number;
}> = ({ placeholder, autoChange, topDistance }) => {
// 静态方法
const { message } = App.useApp();
// token
const token = Cookies.get('SHAREFLY-WEB-TOKEN'); const token = Cookies.get('SHAREFLY-WEB-TOKEN');
// 当前的路由数据 // 当前的路由数据
const router = useRouter(); const router = useRouter();
...@@ -51,6 +57,7 @@ const HeaderView: React.FC<{ ...@@ -51,6 +57,7 @@ const HeaderView: React.FC<{
const res = await CommonAPI.getAccountInfo(); const res = await CommonAPI.getAccountInfo();
if (res && res.code === '200') { if (res && res.code === '200') {
dispatch(setUserInfo(res.result)); dispatch(setUserInfo(res.result));
if (!res.result) message.error('获取用户信息失败');
} }
}; };
// 计算天数与当前时间的差值 // 计算天数与当前时间的差值
...@@ -65,6 +72,21 @@ const HeaderView: React.FC<{ ...@@ -65,6 +72,21 @@ const HeaderView: React.FC<{
const handleDetail = async (item: { label: string; value: string }) => { const handleDetail = async (item: { label: string; value: string }) => {
await router.push(item.value); await router.push(item.value);
}; };
// 是否自动变化
const [isAutoChange, setIsAutoChange] = useState(false);
// 定义一个副作用函数,用于获取滚动距离并更新状态
const handleScroll = () => {
// 获取滚动距离,兼容多种浏览器
const scrollTop = Math.max(
window.scrollY,
document.documentElement.scrollTop,
document.body.scrollTop,
);
// 更新状态
setIsAutoChange(scrollTop >= topDistance);
};
// 节流函数
const handleThrottle = throttle(handleScroll, 500);
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
if (!address?.city) { if (!address?.city) {
...@@ -73,13 +95,23 @@ const HeaderView: React.FC<{ ...@@ -73,13 +95,23 @@ const HeaderView: React.FC<{
dispatch(setAddress(res)); dispatch(setAddress(res));
}); });
} }
// console.log('当前是否登录 --->', system, token);
// 当前是否登录 // 当前是否登录
if (!token) { if (!token) {
dispatch(setSystem({ token: undefined })); dispatch(setSystem({ token: undefined }));
dispatch(setUserInfo(null)); dispatch(setUserInfo(null));
} else { } else {
if (!system?.token) dispatch(setSystem({ token }));
getAccountInfo().then(); getAccountInfo().then();
} }
if (autoChange) window.addEventListener('scroll', handleThrottle);
// 组件卸载前,移除监听器
return () => {
if (autoChange) {
window.removeEventListener('scroll', handleThrottle);
handleThrottle.cancel();
}
};
}, []); }, []);
// 顶部Tab列表 // 顶部Tab列表
const tabList: { label: string; value: string; isQrcode?: boolean }[] = [ const tabList: { label: string; value: string; isQrcode?: boolean }[] = [
...@@ -118,7 +150,7 @@ const HeaderView: React.FC<{ ...@@ -118,7 +150,7 @@ const HeaderView: React.FC<{
onOk: () => { onOk: () => {
dispatch(setUserInfo(null)); dispatch(setUserInfo(null));
dispatch(setSystem({ token: undefined })); dispatch(setSystem({ token: undefined }));
Cookies.remove('SHAREFLY-TOKEN'); Cookies.remove('SHAREFLY-WEB-TOKEN');
}, },
}); });
}} }}
...@@ -132,7 +164,8 @@ const HeaderView: React.FC<{ ...@@ -132,7 +164,8 @@ const HeaderView: React.FC<{
<> <>
<HeaderWrap <HeaderWrap
style={{ style={{
background: placeholder ? '#2A2A2A' : 'rgba(86, 86, 86, 0.25)', background:
placeholder || isAutoChange ? '#2A2A2A' : 'rgba(86, 86, 86, 0.25)',
}} }}
> >
<div className="header-wrap"> <div className="header-wrap">
...@@ -224,7 +257,7 @@ const HeaderView: React.FC<{ ...@@ -224,7 +257,7 @@ const HeaderView: React.FC<{
</div> </div>
</div> </div>
</HeaderWrap> </HeaderWrap>
{placeholder && ( {(placeholder || isAutoChange) && (
<div <div
className="header-wrap" className="header-wrap"
style={{ width: '100%', height: '3rem' }} style={{ width: '100%', height: '3rem' }}
......
...@@ -14,7 +14,18 @@ import { GlobalDataState, setGlobalData } from '@/store/module/globalData'; ...@@ -14,7 +14,18 @@ import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
const LayoutView: React.FC<{ const LayoutView: React.FC<{
children?: React.ReactNode; children?: React.ReactNode;
placeholder?: boolean; placeholder?: boolean;
}> = ({ children, placeholder }) => { autoChange?: boolean;
topDistance?: number;
background?: string;
contentTitle?: string;
}> = ({
children,
placeholder = true,
autoChange = false,
topDistance = 0,
background = 'transparent',
contentTitle,
}) => {
// store // store
const dispatch = useDispatch(); const dispatch = useDispatch();
// system // system
...@@ -32,20 +43,21 @@ const LayoutView: React.FC<{ ...@@ -32,20 +43,21 @@ const LayoutView: React.FC<{
); );
}; };
return ( return (
<LayoutWrap> <LayoutWrap style={{ background }}>
<div <HeaderView
onClick={() => { placeholder={placeholder}
// setQrcodeShow(!qrcodeShow); autoChange={autoChange}
}} topDistance={topDistance}
> ></HeaderView>
{placeholder || placeholder === undefined ? ( <ContentView>
<HeaderView placeholder={true}></HeaderView> {!!contentTitle && (
) : ( <div className="flex h-14 w-full items-center justify-center bg-[#5D656F]">
<HeaderView placeholder={false}></HeaderView> <div className="text-2xl text-white">{contentTitle}</div>
</div>
)} )}
<ContentView>{children}</ContentView> {children}
</ContentView>
<FooterView></FooterView> <FooterView></FooterView>
</div>
{/* 登录弹窗 */} {/* 登录弹窗 */}
<LoginModalView <LoginModalView
open={globalData?.loginModalVisible} open={globalData?.loginModalVisible}
......
...@@ -6,11 +6,15 @@ import { useRouter } from 'next/router'; ...@@ -6,11 +6,15 @@ import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { CommonAPI } from '@/api'; import { CommonAPI } from '@/api';
import { InterReqType } from '@/api/interface';
import { WebLoginType } from '@/api/interface/common';
import { RootState } from '@/store'; import { RootState } from '@/store';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData'; import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
import { setSystem } from '@/store/module/system'; import { setSystem } from '@/store/module/system';
import { setUserInfo } from '@/store/module/userInfo'; import { setUserInfo } from '@/store/module/userInfo';
// 表单类型
type ReqType = InterReqType<WebLoginType>;
// 定时器暂存 // 定时器暂存
let timer: NodeJS.Timer; let timer: NodeJS.Timer;
...@@ -30,10 +34,7 @@ const LoginModalView = ({ ...@@ -30,10 +34,7 @@ const LoginModalView = ({
// store // store
const dispatch = useDispatch(); const dispatch = useDispatch();
// 表单钩子 // 表单钩子
const [formRef] = Form.useForm<{ const [formRef] = Form.useForm<ReqType>();
password: string;
username: string;
}>();
// 是否选择协议 // 是否选择协议
const [checkValue, setCheckValue] = useState<boolean>(false); const [checkValue, setCheckValue] = useState<boolean>(false);
// 获取小程序二维码唯一标识 // 获取小程序二维码唯一标识
...@@ -142,13 +143,10 @@ const LoginModalView = ({ ...@@ -142,13 +143,10 @@ const LoginModalView = ({
); );
}); });
if (!valid) return; if (!valid) return;
if (valid?.password !== '123456') {
await message.warning('密码错误');
return;
}
// 获取token // 获取token
const res = await CommonAPI.testPhoneLogin({ const res = await CommonAPI.webLogin({
phone: valid?.username, accountNo: valid?.accountNo,
passWord: valid?.passWord,
}); });
if (res && res.code === '200') { if (res && res.code === '200') {
handleClose(); handleClose();
...@@ -231,13 +229,13 @@ const LoginModalView = ({ ...@@ -231,13 +229,13 @@ const LoginModalView = ({
size="large" size="large"
> >
<Form.Item <Form.Item
name="username" name="accountNo"
rules={[{ required: true, message: '请输入登录的账号' }]} rules={[{ required: true, message: '请输入登录的账号' }]}
> >
<Input placeholder="请输入手机号" maxLength={11} allowClear /> <Input placeholder="请输入手机号" maxLength={11} allowClear />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="password" name="passWord"
rules={[{ required: true, message: '请输入密码' }]} rules={[{ required: true, message: '请输入密码' }]}
> >
<Input <Input
......
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="flex-center animate__animated animate__fast animate__fadeIn shadow">
<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;
...@@ -20,6 +20,7 @@ const MapContainer: FC<{ ...@@ -20,6 +20,7 @@ const MapContainer: FC<{
lat: any; lat: any;
name: string; name: string;
}) => { }) => {
// console.log('执行到此处 ===>', AmapRef);
if (!AmapRef) return; if (!AmapRef) return;
const icons = new AmapRef.Icon({ const icons = new AmapRef.Icon({
size: new AmapRef.Size(60, 60), // 图标尺寸 size: new AmapRef.Size(60, 60), // 图标尺寸
...@@ -37,10 +38,15 @@ const MapContainer: FC<{ ...@@ -37,10 +38,15 @@ const MapContainer: FC<{
}; };
// 设置地图点事件 // 设置地图点事件
const handleMarkerSet = () => { const handleMarkerSet = () => {
try {
const markers = list.map((i) => addMarkerEntry(i)); const markers = list.map((i) => addMarkerEntry(i));
map?.add(markers); map?.add(markers);
// 自适应显示多个点位 // 自适应显示多个点位
map?.setFitView(); map?.setFitView();
} catch (e) {
// eslint-disable-next-line no-console
// console.log('地图加载失败 ===>', e, AmapRef);
}
}; };
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
...@@ -50,7 +56,8 @@ const MapContainer: FC<{ ...@@ -50,7 +56,8 @@ const MapContainer: FC<{
key: '87b424e68754efc3ba9d11ae07475091', // 申请好的Web端开发者Key,首次调用 load 时必填 key: '87b424e68754efc3ba9d11ae07475091', // 申请好的Web端开发者Key,首次调用 load 时必填
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15 version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: [], // 需要使用的的插件列表,如比例尺'AMap.Scale'等 plugins: [], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
}).then((AMap) => { })
.then((AMap) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
AmapRef = AMap; AmapRef = AMap;
// setAmapRef(AMap); // setAmapRef(AMap);
...@@ -84,11 +91,20 @@ const MapContainer: FC<{ ...@@ -84,11 +91,20 @@ const MapContainer: FC<{
if (!list.length) return; if (!list.length) return;
// 设置地图点 // 设置地图点
handleMarkerSet(); handleMarkerSet();
})
.catch((e) => {
// eslint-disable-next-line no-console
console.log('地图加载失败 ===>', e);
}); });
}); });
} }
return () => { return () => {
try {
map?.destroy(); map?.destroy();
} catch (e) {
// eslint-disable-next-line no-console
console.log('地图销毁失败 ===>', e);
}
}; };
}, [list, center]); }, [list, center]);
return ( return (
......
...@@ -98,6 +98,11 @@ const ProductItemWrap = styled.div` ...@@ -98,6 +98,11 @@ const ProductItemWrap = styled.div`
filter: brightness(0.9); filter: brightness(0.9);
} }
} }
@media (prefers-color-scheme: dark) {
.product-title {
color: #fff;
}
}
`; `;
// 商品详情类型 // 商品详情类型
......
...@@ -12,6 +12,7 @@ const ProductListWrap = styled.div` ...@@ -12,6 +12,7 @@ const ProductListWrap = styled.div`
min-height: 60vh; min-height: 60vh;
flex-wrap: wrap; flex-wrap: wrap;
align-items: flex-start; align-items: flex-start;
align-content: flex-start;
} }
.list-empty { .list-empty {
position: relative; position: relative;
......
...@@ -43,18 +43,17 @@ const QrcodePopover: React.FC<{ ...@@ -43,18 +43,17 @@ const QrcodePopover: React.FC<{
} }
}; };
const content = ( const content = (
<div className="flex-center" style={{ flexDirection: 'column' }}> <div className="flex-center flex-col">
<div style={{ width: '12.5rem', height: '12.5rem' }}> <div className="h-[12.5rem] w-[12.5rem]">
{qrCodeData && ( {qrCodeData && (
<img <img
className="animate__animated animate__faster animate__fadeIn" className="animate__animated animate__faster animate__fadeIn h-[12.5rem] w-[12.5rem]"
style={{ width: '12.5rem', height: '12.5rem' }}
src={qrCodeData} src={qrCodeData}
alt="云享飞小程序" alt="云享飞小程序"
/> />
)} )}
</div> </div>
<div style={{ marginTop: '1rem' }}>请前往小程序进行继续操作</div> <div className="mt-[1rem]">请前往小程序进行继续操作</div>
</div> </div>
); );
return ( return (
...@@ -63,15 +62,11 @@ const QrcodePopover: React.FC<{ ...@@ -63,15 +62,11 @@ const QrcodePopover: React.FC<{
trigger={'click'} trigger={'click'}
placement={placement || 'bottomRight'} placement={placement || 'bottomRight'}
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
style={{ display: 'none' }}
> >
<div style={{ display: 'none' }}> <div>
{children && children} {children && children}
{text && ( {text && (
<Button <Button type="link" className="m-0 p-0 text-[#3366cc]">
type="link"
style={{ color: '#3366cc', padding: 0, margin: 0 }}
>
{text} {text}
</Button> </Button>
)} )}
......
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 { LeaseGoodsListType } from '@/api/interface/home';
import { GlobalDataState } from '@/store/module/globalData';
import { formatMoney } from '@/utils/money';
// 商品详情类型
type GoodsInfoListType = InterListType<LeaseGoodsListType>[0];
const RentListItemView: React.FC<{
detail: GoodsInfoListType;
}> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// globalData
const globalData = useSelector(
(state: any) => state.globalData,
) as GlobalDataState;
// 跳转商品详情
const handleDetail = () => {
router.push(`/rent/detail/${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_10`}
alt="图片"
/>
</div>
<div className="product-title">{detail?.tradeName}</div>
<div className="product-desc text-ellipsis">{detail?.sellingPoint}</div>
<div className="product-desc flex-between">
<div className="money">
{detail?.showPrice ? (
<>
<span className="label">¥</span>
<span
className="num"
title={`${formatMoney(detail?.showPrice)}元起每天`}
>
{formatMoney(detail?.showPrice)}
</span>
<span
className="unit text-ellipsis"
title={`${formatMoney(detail?.showPrice)}元起每天`}
>
起/天
</span>
</>
) : (
<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 RentListItemView;
const ProductItemWrap = styled.div`
position: relative;
box-sizing: border-box;
width: calc((100% - (0.83rem * 5)) / 6);
height: 16.5rem;
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;
max-height: 2rem;
line-height: 16px;
}
.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);
}
}
`;
...@@ -69,9 +69,15 @@ const ServiceView: React.FC<{ ...@@ -69,9 +69,15 @@ const ServiceView: React.FC<{
// 分类筛选 // 分类筛选
const handleSelect = async (e: { main?: number; second?: number[] }) => { const handleSelect = async (e: { main?: number; second?: number[] }) => {
if (e.second?.length && e.second?.length > 1) { if (e.second?.length && e.second?.length > 1) {
await getListAPPCompanyInspectionPage({ industryTypeId: e?.main }); await getListAPPCompanyInspectionPage({
industryTypeId: e?.main,
pageNo: 1,
});
} else { } else {
await getListAPPCompanyInspectionPage({ inspectionId: e?.second?.[0] }); await getListAPPCompanyInspectionPage({
inspectionId: e?.second?.[0],
pageNo: 1,
});
} }
}; };
// 翻页回调 // 翻页回调
......
import React from 'react'; import React from 'react';
import { ConfigProvider, theme } from 'antd'; import { ConfigProvider, theme, App as AppView } from 'antd';
import zhCN from 'antd/locale/zh_CN'; 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';
...@@ -23,7 +23,9 @@ const App = ({ Component, ...rest }: AppProps) => { ...@@ -23,7 +23,9 @@ const App = ({ Component, ...rest }: AppProps) => {
...themeConfig, ...themeConfig,
}} }}
> >
<AppView>
<Component {...props.pageProps} /> <Component {...props.pageProps} />
</AppView>
</ConfigProvider> </ConfigProvider>
</PersistGate> </PersistGate>
</Provider> </Provider>
......
import React from 'react';
import { InterDataType } from '@/api/interface';
import { SelectCurriculumClassifyType } from '@/api/interface/course';
import { CourseAPI } from '@/api/modules/course';
import CourseView from '@/pages/course/_view';
import { wrapper } from '@/store';
// 分类类型
type CategoryListType = Array<
InterDataType<SelectCurriculumClassifyType>[0] & {
children: InterDataType<SelectCurriculumClassifyType> | null;
}
>;
// 每次加载页面都会执行
export const getServerSideProps = wrapper.getServerSideProps(
(_store) => async () => {
// 分类数据
let categoryList: CategoryListType = [];
// 获取分类数据
const getCategoryInfo = async () => {
const res = await CourseAPI.selectCurriculumClassify();
if (res && res.code === '200') {
categoryList = res.result
?.filter((i) => !i.twoCourseId)
?.map((i) => {
const children = res.result?.filter(
(k) => k.oneCourseId === i.oneCourseId && k.twoCourseId,
);
return {
...i,
children: children?.length ? children : null,
};
});
}
};
// 依次获取接口数据
await (async () => {
await Promise.all([getCategoryInfo()]);
})();
return { props: { categoryList } };
},
);
const CourseSecondView: React.FC<{ categoryList: CategoryListType }> = (
props,
) => <CourseView {...props} />;
export default CourseSecondView;
import React from 'react';
import { InterDataType } from '@/api/interface';
import { SelectCurriculumClassifyType } from '@/api/interface/course';
import { CourseAPI } from '@/api/modules/course';
import CourseView from '@/pages/course/_view';
import { wrapper } from '@/store';
// 分类类型
type CategoryListType = Array<
InterDataType<SelectCurriculumClassifyType>[0] & {
children: InterDataType<SelectCurriculumClassifyType> | null;
}
>;
// 每次加载页面都会执行
export const getServerSideProps = wrapper.getServerSideProps(
(_store) => async () => {
// 分类数据
let categoryList: CategoryListType = [];
// 获取分类数据
const getCategoryInfo = async () => {
const res = await CourseAPI.selectCurriculumClassify();
if (res && res.code === '200') {
categoryList = res.result
?.filter((i) => !i.twoCourseId)
?.map((i) => {
const children = res.result?.filter(
(k) => k.oneCourseId === i.oneCourseId && k.twoCourseId,
);
return {
...i,
children: children?.length ? children : null,
};
});
}
};
// 依次获取接口数据
await (async () => {
await Promise.all([getCategoryInfo()]);
})();
return { props: { categoryList } };
},
);
const CourseMainView: React.FC<{ categoryList: CategoryListType }> = (
props,
) => <CourseView {...props} />;
export default CourseMainView;
import React, { useEffect, useState } from 'react';
import { Empty } from 'antd';
import styled from 'styled-components';
import { InterDataType, InterListType, InterReqType } from '@/api/interface';
import {
QueryCurriculumInfoListType,
SelectCurriculumClassifyType,
} from '@/api/interface/course';
import { CourseAPI } from '@/api/modules/course';
import BreadcrumbView from '@/components/breadcrumb';
import CategorySelectView, { CategoryType } from '@/components/categorySelect';
import CourseListItem from '@/components/courseListItem';
import LayoutView from '@/components/layout';
import ProductListView from '@/components/productList';
// 分类类型
type CategoryListType = Array<
InterDataType<SelectCurriculumClassifyType>[0] & {
children: InterDataType<SelectCurriculumClassifyType> | null;
}
>;
// 列表类型
type ListType = InterListType<QueryCurriculumInfoListType>;
// 请求参数类型
type ReqType = InterReqType<QueryCurriculumInfoListType>;
const CourseView: React.FC<{ categoryList: CategoryListType }> = (props) => {
// 分类列表
const [categoryList, setCategoryList] = useState<CategoryType>([]);
// 转换分类列表
const getCategoryList = () => {
setCategoryList(
props.categoryList?.map((i) => ({
value: i.oneCourseId,
label: i.name,
children: i.children?.map((n) => ({
value: n.twoCourseId,
label: n.name,
})),
})),
);
};
// 分页数据
const [pagination, setPagination] = useState({
pageNo: 1,
pageSize: 15,
totalCount: 0,
});
// 列表数据
const [dataList, setDataList] = useState<ListType>([]);
// 获取课程列表
const getDataList = async (data: ReqType) => {
const res = await CourseAPI.getCourseVideoList({
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
...data,
});
if (res && res.code === '200') {
console.log('获取商品列表 --->', res);
const { list, totalCount, pageNo, pageSize } = res.result;
setDataList(list || []);
setPagination({
...pagination,
totalCount,
pageNo,
pageSize,
});
}
};
// 翻页回调
const handlePageChange = async (pageNo: number, pageSize: number) => {
await getDataList({ pageNo, pageSize });
};
// 分类筛选
const handleSelect = async (e: { main?: number; second?: number[] }) => {
if (e.second?.length && e.second?.length > 1) {
await getDataList({
oneCourseId: e?.main,
pageNo: 1,
});
} else {
await getDataList({
oneCourseId: e?.main,
twoCourseId: e?.second?.[0],
pageNo: 1,
});
}
};
// 组件挂载
useEffect(() => {
if (!props) return;
getCategoryList();
}, [props]);
return (
<LayoutView>
<CourseViewWrap>
{/* 面包屑 */}
<BreadcrumbView />
{/* 类型筛选 */}
<CategorySelectView
list={categoryList}
isMultiple={false}
allText={'全部课程'}
onSelect={handleSelect}
/>
{/* 课程列表 */}
<ProductListView pagination={pagination} onChange={handlePageChange}>
{dataList?.length ? (
dataList?.map((i, j) => <CourseListItem key={j} detail={i} />)
) : (
<div className="list-empty flex-center">
<Empty />
</div>
)}
</ProductListView>
</CourseViewWrap>
</LayoutView>
);
};
export default CourseView;
// 样式
const CourseViewWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
`;
import React from 'react';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { CourseDetailType } from '@/api/interface/course';
import { CourseAPI } from '@/api/modules/course';
import BreadcrumbView from '@/components/breadcrumb';
import LayoutView from '@/components/layout';
import CourseMenuView from '@/pages/course/detail/comp/_courseMenu';
import { wrapper } from '@/store';
// 课程类型
type DetailType = InterDataType<CourseDetailType>;
// 每次加载页面都会执行
export const getServerSideProps = wrapper.getServerSideProps(
(_store) => async (ctx) => {
// 课程id
const id = String(ctx.params?.id);
// 课程详情
let courseDetail: DetailType | undefined;
// 获取课程详情
const getCourseDetail = async () => {
const res = await CourseAPI.getCourseDetail(
{ id },
{ headers: { token: ctx.req.cookies['SHAREFLY-WEB-TOKEN'] } },
);
if (res && res.code === '200') {
courseDetail = res.result;
// console.log('服务器渲染 ====>', ctx.req.cookies['SHAREFLY-WEB-TOKEN']);
}
};
// 依次获取接口数据
await (async () => {
await getCourseDetail();
})();
return { props: { courseDetail } };
},
);
// 课程类型列表
const courseAttributeList: { label: string; value: number; color: string }[] = [
{ label: '免费', value: 0, color: '#f99a0b' },
{ label: '积分', value: 1, color: '#00AD53' },
{ label: '付费', value: 2, color: '#FF4600' },
];
// 组件
const CourseDetailView: React.FC<{
courseDetail: DetailType;
}> = ({ courseDetail }) => {
// 获取当前课程属性
const getCourseAttribute = () => {
return courseAttributeList.find(
(i) => i.value === courseDetail?.courseAttribute,
);
};
return (
<LayoutView>
<CourseDetailWrap>
{/* 面包屑 */}
<BreadcrumbView />
{/* 课程详情 */}
<div className="course-head">
{/* 课程视频 */}
<div className="course-video">
{courseDetail?.buy || courseDetail?.courseAttribute === 0 ? (
// eslint-disable-next-line jsx-a11y/media-has-caption
<video
className="media"
src={courseDetail?.videoUrl}
controls={true}
/>
) : (
<img
className="media"
src={
courseDetail.surfaceUrl
? `${courseDetail?.surfaceUrl}?x-oss-process=image/quality,Q_50`
: `${courseDetail?.videoUrl}?x-oss-process=video/snapshot,t_1000,m_fast`
}
alt={courseDetail?.curriculumName}
/>
)}
{!courseDetail?.buy && courseDetail?.courseAttribute !== 0 ? (
<div className="flex-center shadow">
<div className="text">
{courseDetail?.courseAttribute === 1
? '兑换课程后可立即观看'
: '购买课程后可立即观看'}
</div>
</div>
) : undefined}
</div>
{/* 课程目录 */}
<div className="course-menu scroll-view">
<div className="menu-title">目录</div>
{/* 目录组件 */}
<CourseMenuView detail={courseDetail} />
</div>
</div>
{/* 课程介绍 */}
<div className="course-content">
<div className="content-title">
<div className="title">{courseDetail?.curriculumName}</div>
<div
className="tag"
style={{ background: getCourseAttribute()?.color }}
>
{getCourseAttribute()?.label}
</div>
</div>
{!!courseDetail?.requireAmout && (
<div className="content-price">
<span className="label"></span>
<span className="num">{courseDetail?.requireAmout}</span>
</div>
)}
{!!courseDetail?.requireIntegral && (
<div className="content-price">
<span className="num">{courseDetail?.requireIntegral}</span>
<span className="label">积分</span>
</div>
)}
<div className="content-desc">
<div className="title">课程简介</div>
<div className="desc">{courseDetail?.curriculumDesc}</div>
</div>
<div className="content-desc">
<div className="title">课程图文</div>
{courseDetail?.detailContent && (
<div
className="richText"
dangerouslySetInnerHTML={{
__html: courseDetail?.detailContent,
}}
/>
)}
</div>
</div>
</CourseDetailWrap>
</LayoutView>
);
};
export default CourseDetailView;
// 样式
const CourseDetailWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
.course-head {
position: relative;
width: 100%;
height: 26rem;
display: flex;
flex-wrap: nowrap;
.course-video {
position: relative;
width: calc(100% / 3 * 2);
height: 100%;
.media {
width: 100%;
height: 100%;
}
.shadow {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
.text {
position: relative;
height: 1.75rem;
line-height: 1.75rem;
background: #ff4600;
border-radius: 0.25rem;
color: #ffffff;
box-sizing: border-box;
padding: 0 0.75rem;
cursor: pointer;
&:hover {
filter: brightness(0.9);
}
}
}
}
.course-menu {
width: calc(100% / 3 * 1 - 2rem);
height: 100%;
margin-left: 2rem;
background: #000c17;
box-sizing: border-box;
padding: 0.67rem 0.67rem 0.67rem 1rem;
overflow: hidden;
overflow-y: scroll;
.menu-title {
position: relative;
width: 2rem;
color: #c3c3c3;
font-size: 16px;
text-align: center;
margin-bottom: 1rem;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background: #247bfe;
}
}
}
}
.course-content {
position: relative;
width: calc(100% / 3 * 2);
box-sizing: border-box;
padding: 0.5rem 0 0 0;
.content-title {
position: relative;
width: 100%;
display: flex;
align-items: baseline;
justify-content: flex-start;
flex-wrap: nowrap;
.title {
max-width: calc(100% - 2.25rem - 0.5rem);
font-size: 20px;
color: #333333;
font-weight: bold;
margin-right: 0.5rem;
}
.tag {
min-width: 2.25rem;
box-sizing: border-box;
font-size: 12px;
background: #f99a0b;
border-radius: 0.25rem;
text-align: center;
color: #ffffff;
padding: 0 0.25rem;
transform: scale(0.8);
}
}
.content-price {
font-weight: 500;
color: #ff3300;
.num {
font-size: 24px;
//line-height: 35px;
margin-right: 8rpx;
}
.label {
font-size: 16px;
line-height: 28rpx;
font-weight: bold;
}
}
.content-desc {
position: relative;
box-sizing: border-box;
width: 100%;
margin-bottom: 1rem;
.title {
font-size: 16px;
color: #333333;
font-weight: bold;
margin-bottom: 0.5rem;
}
.desc {
color: #666666;
}
.richText {
width: 100%;
img {
width: 100%;
}
}
}
}
`;
import React, { useEffect, useState } from 'react';
import { Menu, MenuProps } from 'antd';
import { useRouter } from 'next/router';
import { InterDataType, InterListType } from '@/api/interface';
import {
CourseDetailType,
QueryCurriculumInfoListType,
SelectCurriculumClassifyType,
} from '@/api/interface/course';
import { CourseAPI } from '@/api/modules/course';
// 详情类型
type DetailType = InterDataType<CourseDetailType>;
// 分类类型
type TabType = Array<
InterDataType<SelectCurriculumClassifyType>[0] & {
children?: InterDataType<SelectCurriculumClassifyType>;
}
>;
// 列表类型
type ListType = InterListType<QueryCurriculumInfoListType>;
const CourseMenuView: React.FC<{ detail: DetailType }> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// 分类列表
const [tabList, setTabList] = useState<TabType>([]);
// 课程列表
const [courseList, setCourseList] = useState<ListType>([]);
// 获取课程分类
const getCurriculumClassify = async () => {
const res = await CourseAPI.selectCurriculumClassify();
if (res && res.code === '200') {
setTabList(
res.result
?.filter((i) => !i.twoCourseId)
?.map((i) => {
const children = res.result?.filter(
(k) => k.oneCourseId === i.oneCourseId && k.twoCourseId,
);
return {
...i,
children: children?.length ? children : undefined,
};
}),
);
}
};
// 获取课程列表
const getCurriculumInfoList = async () => {
const res = await CourseAPI.getCourseVideoList({
pageNo: 1,
pageSize: 999,
oneCourseId: detail?.oneCourseId,
twoCourseId: detail?.twoCourseId,
});
if (res && res.code === '200') {
setCourseList(res.result.list || []);
}
};
// 获取当前的分类信息
const getCurrentMenu = (): MenuProps['items'] => {
const main = tabList?.find((i) => i.oneCourseId === detail?.oneCourseId);
const second = main?.children?.find(
(i) => i.twoCourseId === detail?.twoCourseId,
);
return [
{
label: String(second?.name),
key: String(second?.oneCourseId),
children: courseList?.map((i) => ({
label: i.curriculumName,
key: String(i.id),
})),
},
];
};
// 切换课程
const handleSelect: MenuProps['onSelect'] = (e) => {
router.push(`/course/detail/${e.key}`).then();
};
// 组件挂载
useEffect(() => {
if (!detail?.id) return;
Promise.all([getCurriculumClassify(), getCurriculumInfoList()]).then();
}, [detail]);
// 视图组件
return (
<div className="relative w-full">
<Menu
onSelect={handleSelect}
style={{ width: '100%' }}
mode="inline"
items={getCurrentMenu()}
theme="dark"
defaultOpenKeys={[String(detail?.oneCourseId)]}
defaultSelectedKeys={[String(detail?.id)]}
/>
</div>
);
};
export default CourseMenuView;
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论