提交 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'
......@@ -22,7 +22,8 @@
"endOfLine": "auto",
"plugins": [
"prettier-plugin-tailwindcss"
]
],
"tailwindConfig": "./tailwind.config.js"
}
]
},
......
......@@ -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 push "${ACR_EE_REGISTRY}/${ACR_EE_NAMESPACE}/${ACR_EE_IMAGE}:${TAG}"
- docker logout
after_script:
- chmod a+x ./send-webhook.sh # 给脚本文件添加可执行权限
- ./send-webhook.sh $ACR_EE_NAMESPACE $PROFILES_ACTIVE # 执行脚本文件
docker_build_prod:
stage: dockerbuild
......
......@@ -14,4 +14,4 @@ patches:
images:
- name: REGISTRY/NAMESPACE/IMAGE:TAG
newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly-dev/web
newTag: 8a764a4e7eecfcfbb9106015e461aaa32e732755
newTag: 2bcecb9934c506944d1bdfedebc33e45c31b05f1
......@@ -30,8 +30,10 @@
"env-cmd": "^10.1.0",
"js-base64": "^3.7.5",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"next": "^14.0.4",
"next-redux-wrapper": "^8.1.0",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-infinite-scroll-component": "^6.1.0",
......@@ -44,6 +46,7 @@
"devDependencies": {
"@types/big.js": "^6.2.0",
"@types/js-cookie": "^3.0.5",
"@types/lodash": "^4.14.202",
"@types/node": "^20",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
......
......@@ -44,12 +44,18 @@ dependencies:
js-cookie:
specifier: ^3.0.5
version: registry.npmmirror.com/js-cookie@3.0.5
lodash:
specifier: ^4.17.21
version: 4.17.21
next:
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)
next-redux-wrapper:
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)
query-string:
specifier: ^8.1.0
version: 8.1.0
react:
specifier: ^18.2.0
version: registry.npmmirror.com/react@18.2.0
......@@ -82,6 +88,9 @@ devDependencies:
'@types/js-cookie':
specifier: ^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':
specifier: ^20
version: registry.npmmirror.com/@types/node@20.0.0
......@@ -530,6 +539,10 @@ packages:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==, tarball: https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz}
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):
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}
......@@ -928,6 +941,11 @@ packages:
dependencies:
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:
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'}
......@@ -1378,6 +1396,11 @@ packages:
to-regex-range: 5.0.1
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:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==, tarball: https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz}
dependencies:
......@@ -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}
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:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, tarball: https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz}
hasBin: true
......@@ -2233,6 +2260,15 @@ packages:
react-is: 16.13.1
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:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz}
dev: true
......@@ -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}
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:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==, tarball: https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz}
engines: {node: '>=10.0.0'}
......@@ -3621,7 +3662,7 @@ packages:
'@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/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
styled-components: registry.npmmirror.com/styled-components@6.1.0(react-dom@18.2.0)(react@18.2.0)
transitivePeerDependencies:
......@@ -4973,12 +5014,6 @@ packages:
version: 4.6.2
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:
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
......
# 时间
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 { FlyerAPI } from './modules/flyer';
import { HomeAPI } from './modules/home';
import { UserAPI } from './modules/user';
import { WalletAPI } from './modules/wallet';
export { CommonAPI, HomeAPI, UserAPI, WalletAPI };
export { CommonAPI, HomeAPI, UserAPI, WalletAPI, FlyerAPI };
// 分页通用接口
import { AxiosRequestConfig } from 'axios';
export interface PaginationProps {
pageSize: number;
pageNo: number;
......@@ -39,17 +41,23 @@ export interface ResponseType<D> {
// 通用接口封装函数(分页) 建议用这个
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> {
(req?: D): Promise<ResponseType<T>>;
(req?: D, config?: AxiosRequestConfig): Promise<ResponseType<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<
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<
......@@ -144,3 +148,150 @@ export type GetCompanyInfoByBUId = InterFunction<
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 {
GetAccountInfo,
GetAppletQRCode,
GetLoginInfo,
GetVerifyCodeAuthType,
GetVerifyCodeType,
TestPhoneLogin,
WebLoginType,
WebRegisterType,
} from '@/api/interface/common';
import request from '../request';
......@@ -30,4 +34,20 @@ export class CommonAPI {
// 测试-手机号登录
static testPhoneLogin: 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 {
AllCommentListType,
AppCategoryInfoType,
AppGambitListsType,
AppListPilotType,
AppPublishListType,
ForumDetailType,
ForumListType,
GetAppGambitListType,
GetInfoById,
GetPageHomeCategoriesType,
GetSecondDistrictInfo,
IndustryListPagesType,
LeaseGoodsListType,
LikeOrCancelType,
ListBannerImgType,
ListBrandInfoType,
ListCompanyInfoByCoopIdType,
ListNewsType,
ListTenderInfoType,
NewDetailsType,
PublishCommentType,
RecommendGoodsType,
ReplyListType,
RequirementsListType,
ReviewLikesType,
} from '@/api/interface/home';
import request from '@/api/request';
......@@ -32,10 +44,14 @@ export class HomeAPI {
static appListPilot: AppListPilotType = (params) =>
request.post('/userapp/pilot/appListPilot', params);
// 话题-列表(小程序)
// 话题-列表(小程序)
static getAppGambitList: GetAppGambitListType = (data) =>
request.post('/release/dynamic/appGambitList', data);
// 话题-话题下的帖子
static getAppGambitLists: AppGambitListsType = (data) =>
request.post('/release/gambit/appGambitLists', data);
// 小程序-列表——需求发布
static appPublishList: AppPublishListType = (params) =>
request.post('/release/requirements/appPublishList', params);
......@@ -67,4 +83,52 @@ export class HomeAPI {
// web-首页分类数据-展示
static getPageHomeCategories: GetPageHomeCategoriesType = (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 {
GetAppCategoryInfo,
AppMallGoodsDetails,
GetCompanyInfoByBUId,
LeaseGoodsDetailsType,
LeaseTermInfoType,
ListCompanyInfoByCoopIdType,
BrandStoreListType,
} from '@/api/interface/mall';
import request from '../request';
......@@ -22,4 +26,20 @@ export class MallAPI {
// pc-后台用户id单位查询
static getCompanyInfoByBUId: 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({
service.interceptors.request.use(
(config: any) => {
const token = Cookies.get('SHAREFLY-WEB-TOKEN');
// console.log('config ==========>', config);
if (token) {
// eslint-disable-next-line no-param-reassign
config.headers.token = token;
// console.log('config ==========>', config.headers);
}
return config;
},
......@@ -24,6 +24,9 @@ service.interceptors.request.use(
},
);
// 判断是服务端还是客户端
const isServer = typeof window === 'undefined';
service.interceptors.response.use(
(response: AxiosResponse) => {
const { data, status } = response;
......@@ -39,6 +42,11 @@ service.interceptors.response.use(
data.code,
)
) {
if (isServer) {
// 直接阻止后续代码执行
// console.log('出错了 --->', response.config, data);
return Promise.reject(data);
}
message.error(data.message).then();
Cookies.remove('SHAREFLY-WEB-TOKEN');
localStorage.removeItem('persist:SHAREFLY-WEB-STORAGE');
......@@ -52,10 +60,9 @@ service.interceptors.response.use(
if (data instanceof Blob || Base64.isValid(data)) {
return Promise.resolve(data);
}
// 判断是服务端还是客户端
const isServer = typeof window === 'undefined';
if (isServer) {
// 如果是服务端
// console.log('出错了 --->', response.config, data);
return Promise.reject(data);
// eslint-disable-next-line no-alert
// 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`
color: #666666;
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 = () => {
......@@ -45,9 +58,28 @@ const BreadcrumbView: React.FC = () => {
{ name: '订单详情', path: 'product' },
],
},
{ name: '设备租赁', path: 'rent' },
{
name: '设备租赁',
path: 'rent',
children: [{ name: '租赁详情', path: 'detail' }],
},
{ name: '执照培训', path: 'train' },
{ 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 = () => {
......@@ -84,6 +116,7 @@ const BreadcrumbView: React.FC = () => {
},
...getCurrentRouter(),
]}
className="mt-0.5"
/>
</BreadcrumbWrap>
);
......
......@@ -79,12 +79,14 @@ const CategorySelectView: React.FC<{
// 组件挂载
useEffect(() => {
if (!list?.length) return;
const main = router?.query?.main;
const second = router?.query?.second;
// 一级分类id
const mainID = Number(router?.query?.main);
// 二级分类id
const secondID = Number(router?.query?.second);
// 如果路由里面有一级分类id,则初始化一级分类
if (mainID) {
if (mainID && main) {
const index = list?.findIndex((i) => i?.value === mainID) || 0;
setCurrentIndex(index);
onMain?.(list[index]?.value);
......@@ -92,7 +94,7 @@ const CategorySelectView: React.FC<{
onSecond?.(list[index]?.children?.map((i) => i?.value) || []);
}
// 如果路由里面有二级分类id,则初始化二级分类
if (secondID) {
if (secondID && second) {
const children = list?.find((i) => i?.value === mainID)?.children;
const index = children?.findIndex((i) => i?.value === secondID) || 0;
setSecondIndex([index]);
......@@ -220,4 +222,14 @@ const CategorySelectWrap = styled.div`
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 { RightOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import QrcodePopover from '@/components/qrcodePopover';
// 帖子类型
const indexList = [
{ label: 1, value: '#FF392B' },
{ label: 2, value: '#FE792B' },
{ label: 3, value: '#FEA32B' },
];
// 热门类型
const typeList = [
{ label: 1, value: '#FFB555' },
{ label: 2, value: '#FF6155' },
{ label: 3, value: '#B700D9' },
];
const HomeNewsView = () => {
// 帖子类型
const indexList = [
{ label: 1, value: '#FF392B' },
{ label: 2, value: '#FE792B' },
{ label: 3, value: '#FEA32B' },
];
// 热门类型
const typeList = [
{ label: 1, value: '#FFB555' },
{ label: 2, value: '#FF6155' },
{ label: 3, value: '#B700D9' },
];
// 路由钩子
const router = useRouter();
// tab栏数据
const [tabList, setTabList] = useState<{ label: string; value: any[] }[]>([
// {
......@@ -95,6 +99,14 @@ const HomeNewsView = () => {
const getTagBgColor = (item: any[0]) => {
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(() => {
handleSelect(0).then();
......@@ -116,7 +128,7 @@ const HomeNewsView = () => {
))}
</div>
<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>
<RightOutlined style={{ fontSize: 10, color: '#33333380' }} />
</div>
......@@ -140,7 +152,10 @@ const HomeNewsView = () => {
>
{j + 1}
</div>
<div className="item-title text-ellipsis">
<div
className="item-title text-ellipsis"
onClick={() => handleDetail(i, currentIndex)}
>
{i?.gambitName || i?.newsTitle || i?.tenderTitle}
</div>
{/* {currentIndex === 0 && ( */}
......@@ -228,7 +243,7 @@ const HomeNewWrap = styled.div`
width: 100%;
height: 12rem;
overflow: hidden;
overflow-y: auto;
//overflow-y: auto;
box-sizing: border-box;
padding: 0 1.2rem;
&::-webkit-scrollbar-thumb {
......
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface';
import { RecommendGoodsType } from '@/api/interface/home';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型
type ListType = InterDataType<RecommendGoodsType>;
......@@ -11,6 +13,8 @@ type ListType = InterDataType<RecommendGoodsType>;
const HomeProductView = () => {
// 路由钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 推荐商品列表
const [recommendGoodsList, setRecommendGoodsList] = useState<
ListType[0]['mallGoodsList']
......@@ -38,6 +42,11 @@ const HomeProductView = () => {
};
// 跳转详情
const handleDetail = (item: ListType[0]['mallGoodsList'][0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/mall/product/${item.id}`).then();
};
// 组件挂载
......@@ -54,7 +63,7 @@ const HomeProductView = () => {
>
<img
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}
/>
<div className="item-title text-ellipsis">{i.tradeName}</div>
......
......@@ -9,7 +9,7 @@ const HomeSearchView = () => {
<div className="head-logo">
<img
className="image"
src="/assets/image/home/sharefly-web-headbg.png"
src="/assets/image/home/sharefly-web-head-logo.png"
alt="云享飞官网"
/>
</div>
......
......@@ -34,11 +34,11 @@ export const HomeSearchWrap = styled.div`
position: relative;
//width: 11.33rem;
//height: 3.04rem;
width: 12.42rem;
height: 3.08rem;
width: 13.42rem;
height: 3.04rem;
//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-size: 100% 100%;
background-image: url('/assets/image/home/sharefly-web-head-logo.png');
background-size: cover;
margin-bottom: 1.88rem;
.image {
display: none;
......
......@@ -61,7 +61,7 @@ const HomeServiceView = () => {
{refresh &&
industryList?.[currentIndex]?.inspectionDTOS?.map((i, j) => (
<div
className="service-item animate__animated animate__fast animate__fadeIn"
className="service-item animate__animated animate__fast animate__fadeIn overflow-hidden rounded"
key={j}
onClick={() => handleDetail(i)}
>
......@@ -83,7 +83,7 @@ const HomeServiceWrap = styled.div`
width: 100%;
min-height: 16.75rem;
box-sizing: border-box;
//padding-bottom: 2rem;
padding-bottom: 2rem;
.service-tab {
position: relative;
width: 100%;
......
......@@ -53,7 +53,7 @@ const TabView01 = () => {
return (
<TabViewWrap className="animate__animated animate__fast animate__fadeIn">
{tabList.map((i, j) => (
<div key={j}>
<div className="tab-item align-start flex" key={j}>
<div className={'tab-little flex-start'}>
{!!i.icon && (
<img src={i.icon} alt={i.name} className="title-image" />
......@@ -90,20 +90,30 @@ const TabViewWrap = styled.div`
box-sizing: border-box;
padding: 0.79rem 1.58rem;
//background: lightyellow;
.tab-little {
.tab-item {
position: relative;
width: 100%;
box-sizing: border-box;
padding-bottom: 0.68rem;
margin-bottom: 0.48rem;
padding-bottom: 0.48rem;
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 {
width: 1.68rem;
height: 1.68rem;
margin-right: 0.5rem;
}
.title-name {
font-size: 0.75rem;
font-size: 0.78rem;
font-weight: 550;
color: #333333;
padding: 0;
......@@ -111,13 +121,29 @@ const TabViewWrap = styled.div`
}
.tab-list {
position: relative;
width: 100%;
flex-wrap: wrap;
margin-bottom: 1rem;
transform: translateX(-10px);
max-width: calc(100% - 6rem);
transform: translateY(0.86px);
//height: 1.58rem;
//overflow: hidden;
//margin-bottom: 1rem;
//transform: translateX(-10px);
.list-item {
color: #666666;
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 { RightOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
......@@ -55,20 +56,20 @@ const TabView02 = () => {
getIndustryListPages().then();
}, []);
return (
<TabViewWrap className="animate__animated animate__fast animate__fadeIn">
<TabViewWrap className="animate__animated animate__fast animate__fadeIn flex flex-wrap">
{tabList.map((i, j) => (
<div key={j}>
<div className={'tab-little flex-start'}>
<div className="tab-item" key={j}>
<div
className={'tab-little flex-start'}
onClick={() => handleMain(i)}
>
{!!i.typeImg && (
<img src={i.typeImg} alt={i.typeName} className="title-image" />
)}
<Button
type={'link'}
className="title-name"
onClick={() => handleMain(i)}
>
<Button type={'link'} className="title-name">
{i.typeName}
</Button>
<RightOutlined className="absolute right-[0.58rem] size-[10px] text-[#AEAFAF]" />
</div>
<div className="tab-list flex-start">
{i.inspectionDTOS?.map((n, m) => (
......@@ -92,18 +93,28 @@ const TabViewWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
padding: 0.79rem 1.58rem;
padding: 0.58rem;
//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 {
position: relative;
width: 100%;
box-sizing: border-box;
padding-bottom: 0.68rem;
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 {
width: 1.68rem;
height: 1.68rem;
width: 1.25rem;
height: 1.25rem;
}
.title-name {
font-size: 0.75rem;
......@@ -115,8 +126,8 @@ const TabViewWrap = styled.div`
position: relative;
width: 100%;
flex-wrap: wrap;
margin-bottom: 1rem;
transform: translateX(-10px);
height: 1.67rem;
overflow: hidden;
.list-item {
color: #666666;
font-weight: 500;
......
import React, { useEffect, useState } from 'react';
import { Button } from 'antd';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface';
import { ListBrandInfoType } from '@/api/interface/home';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型
type ListType = InterListType<ListBrandInfoType>;
const TabView03 = () => {
// 导航钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 列表数据
const [tabList, setTabList] = useState<ListType>([]);
// 获取云享商城分类
......@@ -22,6 +29,24 @@ const TabView03 = () => {
// 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(() => {
getListBrandInfo().then();
......@@ -31,11 +56,18 @@ const TabView03 = () => {
{tabList.map((i, j) => (
<div key={j}>
<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 className="tab-list flex-start">
{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}
</Button>
))}
......
import React, { useEffect, useState } from 'react';
import { Button } from 'antd';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
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 router = useRouter();
// store
const dispatch = useDispatch();
// 列表数据
const [tabList, setTabList] = useState<
Array<ListType[0] & { categoryList: ListType }>
>([]);
// 获取云享商城分类
const getListBrandInfo = async () => {
const res = await HomeAPI.getPageHomeCategories({
type: 3,
});
const [tabList, setTabList] = useState<ListType>([]);
// 获取执照培训分类
const getCategoryInfo = async () => {
const res = await CourseAPI.selectCurriculumClassify();
if (res && res.code === '200') {
setTabList([
{ id: 1, categoryName: '执照培训', categoryList: res.result },
]);
// console.log('获取云享商城分类 --->', res);
setTabList(
res.result
?.filter((i) => !i.twoCourseId)
?.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(() => {
getListBrandInfo().then();
getCategoryInfo().then();
}, []);
return (
<TabViewWrap className="animate__animated animate__fast animate__fadeIn">
{tabList.map((i, j) => (
<div key={j}>
<div className="tab-item align-start flex" key={j}>
<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 className="tab-list flex-start">
{i?.categoryList?.map((n, m) => (
<Button type={'link'} key={m} className="list-item">
{n?.categoryName}
{i.children?.map((n, m) => (
<Button
type={'link'}
key={m}
className="list-item"
onClick={() => handleSecond(i, n)}
>
{n.name}
</Button>
))}
</div>
......@@ -52,37 +100,63 @@ const TabView04 = () => {
const TabViewWrap = styled.div`
position: relative;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 0.79rem 1.58rem;
//background: lightyellow;
.tab-little {
.tab-item {
position: relative;
width: 100%;
box-sizing: border-box;
padding-bottom: 0.68rem;
margin-bottom: 0.48rem;
padding-bottom: 0.48rem;
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 {
width: 1.68rem;
height: 1.68rem;
margin-right: 0.5rem;
}
.title-name {
font-size: 0.75rem;
font-size: 0.78rem;
font-weight: 550;
color: #333333;
padding: 0;
}
}
.tab-list {
position: relative;
width: 100%;
flex-wrap: wrap;
margin-bottom: 1rem;
transform: translateX(-10px);
max-width: calc(100% - 6rem);
transform: translateY(0.86px);
//height: 1.58rem;
//overflow: hidden;
//margin-bottom: 1rem;
//transform: translateX(-10px);
.list-item {
color: #666666;
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 { 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 { HomeAPI } from '@/api';
import { InterListType, InterReqType } from '@/api/interface';
import { AppListPilotType } from '@/api/interface/home';
import SearchBoxView, { SearchColumns } from '@/components/SearchBox';
// 列表类型
type ListType = InterListType<AppListPilotType>;
......@@ -12,12 +14,14 @@ type ListType = InterListType<AppListPilotType>;
type ReqType = InterReqType<AppListPilotType>;
const TabView05 = () => {
// 路由钩子
const router = useRouter();
// 飞手列表
const [flyerList, setFlyerList] = useState<ListType>([]);
// 分页数据
const [pagination, setPagination] = useState({
pageNo: 1,
pageSize: 8,
pageSize: 6,
totalPage: 0,
totalCount: 0,
});
......@@ -42,18 +46,86 @@ const TabView05 = () => {
// 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(() => {
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 (
<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="title-name">飞手约单</div>
</div>
<div className="tab-list flex-start">
{flyerList?.map((i, j) => (
<div className="list-item" key={j}>
<div className="list-item list-none" key={j}>
<div className="item-arrow">
<RightOutlined style={{ fontSize: 10, color: '#A0A0A0' }} />
</div>
......@@ -85,7 +157,10 @@ const TabView05 = () => {
</div>
<div className="item-foot flex-start">
<div className="foot-state">空闲</div>
<div className="foot-text text-ellipsis">
<div
className="foot-text text-ellipsis"
title={i?.individualResume}
>
{i?.individualResume}
</div>
</div>
......@@ -112,13 +187,14 @@ const TabViewWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
padding: 1rem 1.58rem;
padding: 0.58rem;
.tab-title {
width: 100%;
box-sizing: border-box;
border-bottom: 0.02rem solid #ededed;
padding-bottom: 0.58rem;
margin-bottom: 0.42rem;
display: none;
.title-name {
font-size: 1rem;
font-weight: 550;
......@@ -129,10 +205,12 @@ const TabViewWrap = styled.div`
position: relative;
width: 100%;
flex-wrap: wrap;
margin-top: 0.5rem;
.list-item {
position: relative;
box-sizing: border-box;
width: 14.13rem;
width: calc(50% - 1rem);
//width: 14.13rem;
min-height: 5.54rem;
background: #ffffff;
border-radius: 0.25rem;
......@@ -149,6 +227,7 @@ const TabViewWrap = styled.div`
right: 0.67rem;
}
.item-head {
flex-wrap: nowrap;
.head-image {
width: 3rem;
height: 3rem;
......@@ -157,6 +236,7 @@ const TabViewWrap = styled.div`
}
.head-content {
position: relative;
width: calc(100% - 4rem);
.content-title {
align-items: baseline;
margin-bottom: 0.2rem;
......@@ -181,6 +261,7 @@ const TabViewWrap = styled.div`
.content-tag {
flex-wrap: wrap;
margin-bottom: 0.33rem;
min-height: 0.79rem;
.tag-item {
min-width: 2.42rem;
height: 0.79rem;
......@@ -199,7 +280,8 @@ const TabViewWrap = styled.div`
content: '';
bottom: 0;
left: 0;
width: 8.21rem;
width: 100%;
//width: 8.21rem;
height: 0.02rem;
background: #dedede;
}
......
......@@ -23,7 +23,7 @@ const HomeTabView = () => {
moduleCode: 'HOME_MENU_NEW',
});
if (res && res.code === '200') {
setCategoryList(res.result.slice(0, 1) || []);
setCategoryList(res.result || []);
}
};
// 选择分类
......
......@@ -81,7 +81,7 @@ export const HomeTabWrap = styled.div`
box-sizing: border-box;
overflow: hidden;
// 超出滚动
overflow-y: auto;
//overflow-y: auto;
border: 0.04rem solid #e3e3e3;
&::-webkit-scrollbar-thumb {
background: #d9d9d9;
......@@ -99,4 +99,18 @@ export const HomeTabWrap = styled.div`
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 {
VerticalAlignTopOutlined,
} from '@ant-design/icons';
import dayjs from 'dayjs';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface';
import { AppPublishListType } from '@/api/interface/home';
import QrcodePopover from '@/components/qrcodePopover';
import { setGlobalData } from '@/store/module/globalData';
import { UserInfoState } from '@/store/module/userInfo';
import { formatLocationStr } from '@/utils/formatLocation';
......@@ -20,6 +20,8 @@ import { bigNumberTransform } from '@/utils/money';
type ListType = InterDataType<AppPublishListType>;
const HomeTaskView = () => {
// 路由钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// userInfo
......@@ -135,6 +137,10 @@ const HomeTaskView = () => {
}),
);
};
// 跳转更多列表
const handleMore = async () => {
await router.push('/service/task');
};
// 组件挂载
useEffect(() => {
getRequirementsListType().then();
......@@ -144,18 +150,20 @@ const HomeTaskView = () => {
<HomeTaskWrap>
<div className="task-title">
<div className="title-label">抢单大厅</div>
<QrcodePopover path={'page-service/service-task/index'}>
<div className="title-more">
<div className="more-label">更多</div>
<div className="more-icon">
<RightOutlined style={{ color: '#998e8b', fontSize: 13 }} />
</div>
<div className="title-more cursor-pointer" onClick={handleMore}>
<div className="more-label">更多</div>
<div className="more-icon">
<RightOutlined style={{ color: '#998e8b', fontSize: 13 }} />
</div>
</QrcodePopover>
</div>
</div>
<div className="task-list">
{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">
{i?.orderLevelEnum !== 'REGULAR_ORDER' && (
<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 {
EnvironmentFilled,
LogoutOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import { Button, Dropdown, MenuProps, Modal } from 'antd';
import { App, Button, Dropdown, MenuProps, Modal } from 'antd';
import dayjs from 'dayjs';
import Cookies from 'js-cookie';
import { throttle } from 'lodash';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import { CommonAPI } from '@/api';
......@@ -21,7 +22,12 @@ import getLocationByIP from '@/utils/getLocationByIP';
const HeaderView: React.FC<{
placeholder: boolean;
}> = ({ placeholder }) => {
autoChange: boolean;
topDistance: number;
}> = ({ placeholder, autoChange, topDistance }) => {
// 静态方法
const { message } = App.useApp();
// token
const token = Cookies.get('SHAREFLY-WEB-TOKEN');
// 当前的路由数据
const router = useRouter();
......@@ -51,6 +57,7 @@ const HeaderView: React.FC<{
const res = await CommonAPI.getAccountInfo();
if (res && res.code === '200') {
dispatch(setUserInfo(res.result));
if (!res.result) message.error('获取用户信息失败');
}
};
// 计算天数与当前时间的差值
......@@ -65,6 +72,21 @@ const HeaderView: React.FC<{
const handleDetail = async (item: { label: string; value: string }) => {
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(() => {
if (!address?.city) {
......@@ -73,13 +95,23 @@ const HeaderView: React.FC<{
dispatch(setAddress(res));
});
}
// console.log('当前是否登录 --->', system, token);
// 当前是否登录
if (!token) {
dispatch(setSystem({ token: undefined }));
dispatch(setUserInfo(null));
} else {
if (!system?.token) dispatch(setSystem({ token }));
getAccountInfo().then();
}
if (autoChange) window.addEventListener('scroll', handleThrottle);
// 组件卸载前,移除监听器
return () => {
if (autoChange) {
window.removeEventListener('scroll', handleThrottle);
handleThrottle.cancel();
}
};
}, []);
// 顶部Tab列表
const tabList: { label: string; value: string; isQrcode?: boolean }[] = [
......@@ -118,7 +150,7 @@ const HeaderView: React.FC<{
onOk: () => {
dispatch(setUserInfo(null));
dispatch(setSystem({ token: undefined }));
Cookies.remove('SHAREFLY-TOKEN');
Cookies.remove('SHAREFLY-WEB-TOKEN');
},
});
}}
......@@ -132,7 +164,8 @@ const HeaderView: React.FC<{
<>
<HeaderWrap
style={{
background: placeholder ? '#2A2A2A' : 'rgba(86, 86, 86, 0.25)',
background:
placeholder || isAutoChange ? '#2A2A2A' : 'rgba(86, 86, 86, 0.25)',
}}
>
<div className="header-wrap">
......@@ -224,7 +257,7 @@ const HeaderView: React.FC<{
</div>
</div>
</HeaderWrap>
{placeholder && (
{(placeholder || isAutoChange) && (
<div
className="header-wrap"
style={{ width: '100%', height: '3rem' }}
......
......@@ -14,7 +14,18 @@ import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
const LayoutView: React.FC<{
children?: React.ReactNode;
placeholder?: boolean;
}> = ({ children, placeholder }) => {
autoChange?: boolean;
topDistance?: number;
background?: string;
contentTitle?: string;
}> = ({
children,
placeholder = true,
autoChange = false,
topDistance = 0,
background = 'transparent',
contentTitle,
}) => {
// store
const dispatch = useDispatch();
// system
......@@ -32,20 +43,21 @@ const LayoutView: React.FC<{
);
};
return (
<LayoutWrap>
<div
onClick={() => {
// setQrcodeShow(!qrcodeShow);
}}
>
{placeholder || placeholder === undefined ? (
<HeaderView placeholder={true}></HeaderView>
) : (
<HeaderView placeholder={false}></HeaderView>
<LayoutWrap style={{ background }}>
<HeaderView
placeholder={placeholder}
autoChange={autoChange}
topDistance={topDistance}
></HeaderView>
<ContentView>
{!!contentTitle && (
<div className="flex h-14 w-full items-center justify-center bg-[#5D656F]">
<div className="text-2xl text-white">{contentTitle}</div>
</div>
)}
<ContentView>{children}</ContentView>
<FooterView></FooterView>
</div>
{children}
</ContentView>
<FooterView></FooterView>
{/* 登录弹窗 */}
<LoginModalView
open={globalData?.loginModalVisible}
......
......@@ -6,11 +6,15 @@ import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { CommonAPI } from '@/api';
import { InterReqType } from '@/api/interface';
import { WebLoginType } from '@/api/interface/common';
import { RootState } from '@/store';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
import { setSystem } from '@/store/module/system';
import { setUserInfo } from '@/store/module/userInfo';
// 表单类型
type ReqType = InterReqType<WebLoginType>;
// 定时器暂存
let timer: NodeJS.Timer;
......@@ -30,10 +34,7 @@ const LoginModalView = ({
// store
const dispatch = useDispatch();
// 表单钩子
const [formRef] = Form.useForm<{
password: string;
username: string;
}>();
const [formRef] = Form.useForm<ReqType>();
// 是否选择协议
const [checkValue, setCheckValue] = useState<boolean>(false);
// 获取小程序二维码唯一标识
......@@ -142,13 +143,10 @@ const LoginModalView = ({
);
});
if (!valid) return;
if (valid?.password !== '123456') {
await message.warning('密码错误');
return;
}
// 获取token
const res = await CommonAPI.testPhoneLogin({
phone: valid?.username,
const res = await CommonAPI.webLogin({
accountNo: valid?.accountNo,
passWord: valid?.passWord,
});
if (res && res.code === '200') {
handleClose();
......@@ -231,13 +229,13 @@ const LoginModalView = ({
size="large"
>
<Form.Item
name="username"
name="accountNo"
rules={[{ required: true, message: '请输入登录的账号' }]}
>
<Input placeholder="请输入手机号" maxLength={11} allowClear />
</Form.Item>
<Form.Item
name="password"
name="passWord"
rules={[{ required: true, message: '请输入密码' }]}
>
<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<{
lat: any;
name: string;
}) => {
// console.log('执行到此处 ===>', AmapRef);
if (!AmapRef) return;
const icons = new AmapRef.Icon({
size: new AmapRef.Size(60, 60), // 图标尺寸
......@@ -37,10 +38,15 @@ const MapContainer: FC<{
};
// 设置地图点事件
const handleMarkerSet = () => {
const markers = list.map((i) => addMarkerEntry(i));
map?.add(markers);
// 自适应显示多个点位
map?.setFitView();
try {
const markers = list.map((i) => addMarkerEntry(i));
map?.add(markers);
// 自适应显示多个点位
map?.setFitView();
} catch (e) {
// eslint-disable-next-line no-console
// console.log('地图加载失败 ===>', e, AmapRef);
}
};
// 组件挂载
useEffect(() => {
......@@ -50,45 +56,55 @@ const MapContainer: FC<{
key: '87b424e68754efc3ba9d11ae07475091', // 申请好的Web端开发者Key,首次调用 load 时必填
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: [], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
}).then((AMap) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
AmapRef = AMap;
// setAmapRef(AMap);
// eslint-disable-next-line react-hooks/exhaustive-deps
map = new AMap.Map('container', {
// 设置地图容器id
viewMode: '3D', // 是否为3D地图模式
zoom: 10, // 初始化地图级别
center: center || [119.96043, 30.04885], // 初始化地图中心点位置
});
// 用户定位
map.plugin('AMap.Geolocation', () => {
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true, // 是否使用高精度定位,默认:true
timeout: 10000, // 超过10秒后停止定位,默认:5s
position: 'RB', // 定位按钮的停靠位置
offset: [10, 55], // 定位按钮与设置的停靠位置的偏移量,默认:[10, 20]
zoomToAccuracy: true, // 定位成功后是否自动调整地图视野到定位点
})
.then((AMap) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
AmapRef = AMap;
// setAmapRef(AMap);
// eslint-disable-next-line react-hooks/exhaustive-deps
map = new AMap.Map('container', {
// 设置地图容器id
viewMode: '3D', // 是否为3D地图模式
zoom: 10, // 初始化地图级别
center: center || [119.96043, 30.04885], // 初始化地图中心点位置
});
// 用户定位
map.plugin('AMap.Geolocation', () => {
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true, // 是否使用高精度定位,默认:true
timeout: 10000, // 超过10秒后停止定位,默认:5s
position: 'RB', // 定位按钮的停靠位置
offset: [10, 55], // 定位按钮与设置的停靠位置的偏移量,默认:[10, 20]
zoomToAccuracy: true, // 定位成功后是否自动调整地图视野到定位点
});
map.addControl(geolocation);
// geolocation.getCurrentPosition((status: string, result: any) => {
// console.log(result);
// if (status == 'complete') {
// onComplete(result);
// } else {
// onError(result);
// }
// });
});
map.addControl(geolocation);
// geolocation.getCurrentPosition((status: string, result: any) => {
// console.log(result);
// if (status == 'complete') {
// onComplete(result);
// } else {
// onError(result);
// }
// });
// 如果列表没有数据则停止执行
if (!list.length) return;
// 设置地图点
handleMarkerSet();
})
.catch((e) => {
// eslint-disable-next-line no-console
console.log('地图加载失败 ===>', e);
});
// 如果列表没有数据则停止执行
if (!list.length) return;
// 设置地图点
handleMarkerSet();
});
});
}
return () => {
map?.destroy();
try {
map?.destroy();
} catch (e) {
// eslint-disable-next-line no-console
console.log('地图销毁失败 ===>', e);
}
};
}, [list, center]);
return (
......
......@@ -98,6 +98,11 @@ const ProductItemWrap = styled.div`
filter: brightness(0.9);
}
}
@media (prefers-color-scheme: dark) {
.product-title {
color: #fff;
}
}
`;
// 商品详情类型
......
......@@ -12,6 +12,7 @@ const ProductListWrap = styled.div`
min-height: 60vh;
flex-wrap: wrap;
align-items: flex-start;
align-content: flex-start;
}
.list-empty {
position: relative;
......
......@@ -43,18 +43,17 @@ const QrcodePopover: React.FC<{
}
};
const content = (
<div className="flex-center" style={{ flexDirection: 'column' }}>
<div style={{ width: '12.5rem', height: '12.5rem' }}>
<div className="flex-center flex-col">
<div className="h-[12.5rem] w-[12.5rem]">
{qrCodeData && (
<img
className="animate__animated animate__faster animate__fadeIn"
style={{ width: '12.5rem', height: '12.5rem' }}
className="animate__animated animate__faster animate__fadeIn h-[12.5rem] w-[12.5rem]"
src={qrCodeData}
alt="云享飞小程序"
/>
)}
</div>
<div style={{ marginTop: '1rem' }}>请前往小程序进行继续操作</div>
<div className="mt-[1rem]">请前往小程序进行继续操作</div>
</div>
);
return (
......@@ -63,15 +62,11 @@ const QrcodePopover: React.FC<{
trigger={'click'}
placement={placement || 'bottomRight'}
onOpenChange={handleOpenChange}
style={{ display: 'none' }}
>
<div style={{ display: 'none' }}>
<div>
{children && children}
{text && (
<Button
type="link"
style={{ color: '#3366cc', padding: 0, margin: 0 }}
>
<Button type="link" className="m-0 p-0 text-[#3366cc]">
{text}
</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<{
// 分类筛选
const handleSelect = async (e: { main?: number; second?: number[] }) => {
if (e.second?.length && e.second?.length > 1) {
await getListAPPCompanyInspectionPage({ industryTypeId: e?.main });
await getListAPPCompanyInspectionPage({
industryTypeId: e?.main,
pageNo: 1,
});
} else {
await getListAPPCompanyInspectionPage({ inspectionId: e?.second?.[0] });
await getListAPPCompanyInspectionPage({
inspectionId: e?.second?.[0],
pageNo: 1,
});
}
};
// 翻页回调
......
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 type { AppProps } from 'next/app';
import '../styles/animate.css';
......@@ -23,7 +23,9 @@ const App = ({ Component, ...rest }: AppProps) => {
...themeConfig,
}}
>
<Component {...props.pageProps} />
<AppView>
<Component {...props.pageProps} />
</AppView>
</ConfigProvider>
</PersistGate>
</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%;
}
}
}
}
`;
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论