提交 a36e0940 作者: ZhangLingKun

Merge branch 'develop'

流水线 #7690 已通过 于阶段
in 5 分 46 秒
...@@ -79,7 +79,8 @@ ...@@ -79,7 +79,8 @@
"jsx-a11y/no-static-element-interactions": "off", "jsx-a11y/no-static-element-interactions": "off",
"eqeqeq": "warn", "eqeqeq": "warn",
"no-unsafe-optional-chaining": "warn", "no-unsafe-optional-chaining": "warn",
"no-param-reassign": "warn" "no-param-reassign": "warn",
"react-hooks/exhaustive-deps": "off"
} }
} }
] ]
......
...@@ -4,9 +4,14 @@ ENV PROFILES_ACTIVE=$PROFILES_ACTIVE ...@@ -4,9 +4,14 @@ ENV PROFILES_ACTIVE=$PROFILES_ACTIVE
# Install dependencies only when needed # Install dependencies only when needed
FROM base AS deps FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
# 设置淘宝镜像
RUN npm config set registry https://registry.npmmirror.com
RUN yarn config set registry https://registry.npmmirror.com
# Install dependencies based on the preferred package manager # Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \ RUN \
...@@ -16,6 +21,8 @@ RUN \ ...@@ -16,6 +21,8 @@ RUN \
else echo "Lockfile not found." && exit 1; \ else echo "Lockfile not found." && exit 1; \
fi fi
# elif [ -f pnpm-lock.yaml ]; then corepack enable && pnpm i --frozen-lockfile; \
FROM base AS builder FROM base AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
......
...@@ -14,4 +14,4 @@ patches: ...@@ -14,4 +14,4 @@ patches:
images: images:
- name: REGISTRY/NAMESPACE/IMAGE:TAG - name: REGISTRY/NAMESPACE/IMAGE:TAG
newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly-dev/web newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly-dev/web
newTag: 69ee5b55eedcad8b4838f59a13c557279b813862 newTag: 818d779104308947c9cbd3615a4c3b39910c794c
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",
"@ant-design/cssinjs": "^1.17.2", "@ant-design/cssinjs": "^1.17.2",
"@ant-design/icons": "^5.2.6", "@ant-design/icons": "^5.2.6",
"@reduxjs/toolkit": "^1.9.7",
"@types/styled-components": "^5.1.29", "@types/styled-components": "^5.1.29",
"antd": "^5.11.0", "antd": "^5.11.0",
"axios": "^1.6.0", "axios": "^1.6.0",
...@@ -30,10 +31,15 @@ ...@@ -30,10 +31,15 @@
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"next": "14.0.1", "next": "14.0.1",
"next-redux-wrapper": "^8.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-infinite-scroll-component": "^6.1.0", "react-infinite-scroll-component": "^6.1.0",
"styled-components": "^6.1.0" "react-redux": "^8.1.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"styled-components": "^6.1.0",
"swiper": "^11.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/big.js": "^6.2.0", "@types/big.js": "^6.2.0",
......
import { CommonAPI } from './modules/common'; import { CommonAPI } from './modules/common';
import { HomeAPI } from './modules/home'; import { HomeAPI } from './modules/home';
import { UserAPI } from './modules/user';
import { WalletAPI } from './modules/wallet';
export { CommonAPI, HomeAPI }; export { CommonAPI, HomeAPI, UserAPI, WalletAPI };
import { InterFunction } from '@/api/interface';
// 获取二维码
export type GetAppletQRCode = InterFunction<
// 入参
{
page: string;
scene: string;
},
// 出参
string
>;
// 查询登录信息
export type GetLoginInfo = InterFunction<
{
randomLoginCode: string;
},
{
token: string;
openId: string;
userAccountId: number;
accountNo: null;
portType: number;
uid: string;
phoneNum: string;
userName: string;
nickName: string;
companyInfoVO: null;
roleInfo: null;
appUserAccountId: null;
}
>;
// 获取用户信息
export type GetAccountInfo = InterFunction<
{},
{
accountStatus: number;
accountType: number;
companyAuthStatus: number;
email: string;
id: number;
nickName: string;
phoneNum: string;
portType: number;
source: number;
uid: string;
userImg: string;
userName: string;
userSex: number;
realNameAuthStatus: number;
auditStatus: number;
totalPoints: number;
xzAuthStatus: number;
cooperationTagVOS: {
createTime: string;
id: number;
tagDescription: string;
tagImg: string;
tagName: string;
tagRequire: string;
}[];
coverPicture: string;
districtChildId: number;
region: string;
briefIntroduction: string;
companyInfoVO: {
address: string;
backImg: string;
backUserAccountId: number;
brandLogo: string;
brandName: string;
companyName: string;
companyType: number;
companyUserName: string;
content: string;
creditCode: string;
distance: number;
fullName: string;
id: number;
lat: number;
leader: number;
licenseImg: string;
lon: number;
phoneNum: number;
remark: string;
score: string;
userAccountId: number;
};
createTime: string;
}
>;
...@@ -4,6 +4,7 @@ import { ...@@ -4,6 +4,7 @@ import {
InterItemFunction, InterItemFunction,
InterListFunction, InterListFunction,
} from '@/api/interface'; } from '@/api/interface';
import { QueryGoodsInfoByCategorySub } from '@/api/interface/mall';
// 获取图片-小程序 // 获取图片-小程序
export type ListBannerImgType = InterFunction< export type ListBannerImgType = InterFunction<
...@@ -39,7 +40,7 @@ export type AppCategoryInfoType = InterFunction< ...@@ -39,7 +40,7 @@ export type AppCategoryInfoType = InterFunction<
icon?: string; icon?: string;
createTime?: string; createTime?: string;
updateTime?: null; updateTime?: null;
subDTOList?: Array<{ subDTOList: Array<{
id: number; id: number;
name: string; name: string;
description?: string; description?: string;
...@@ -51,48 +52,6 @@ export type AppCategoryInfoType = InterFunction< ...@@ -51,48 +52,6 @@ export type AppCategoryInfoType = InterFunction<
sort?: number; sort?: number;
}[] }[]
>; >;
// 分类下的商品数据
export type QueryGoodsInfoByCategorySub = InterFunction<
number[],
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: Array<{
chooseType: number;
goodsSpecValuesList: Array<{
channelPrice: number;
goodsSpecId: number;
id: number;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock: number;
}>;
id: number;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}>;
id: number;
labelShow: number;
resourcesList: Array<{
id: number;
type: number;
url: string;
}>;
shelfStatus: number;
tradeName: string;
userAccountId: number;
recommend: number;
}[][]
>;
// 一级行业列表 // 一级行业列表
export type IndustryListPagesType = InterListFunction< export type IndustryListPagesType = InterListFunction<
{ {
......
import { InterDataType, InterFunction } from '@/api/interface';
// 小程序分类信息--含一二级分类
export type GetAppCategoryInfo = InterFunction<
{},
{
id: number;
name: string;
description?: string;
icon?: string;
createTime?: string;
updateTime?: null;
subDTOList?: Array<{
id: number;
name: string;
description?: string;
categoryPrimaryId?: number;
createTime?: string;
updateTime?: string;
subDTOList?: InterDataType<QueryGoodsInfoByCategorySub>;
}>;
sort?: number;
}[]
>;
export type QueryGoodsInfoByCategorySub = InterFunction<
number[],
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: Array<{
chooseType: number;
goodsSpecValuesList: Array<{
channelPrice: number;
goodsSpecId: number;
id: number;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock: number;
}>;
id: number;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}>;
id: number;
labelShow: number;
resourcesList: Array<{
id: number;
type: number;
url: string;
}>;
shelfStatus: number;
tradeName: string;
userAccountId: number;
recommend: number;
priceShow: number;
specAttrList: null;
priceStock: Array<{
id: number;
productSpec: string;
salePrice: number;
skuImage: null;
channelPrice: null;
stock: null;
skuNo: null;
}>;
companyName: string;
}[]
>;
// 小程序商品详情
export type AppMallGoodsDetails = InterFunction<
{ id: number },
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
id: number;
labelShow: number;
priceStock: Array<{
channelPrice: number;
id: number;
productSpec: string;
salePrice: number;
skuImage: string;
skuNo: string;
stock: number;
}>;
resourcesList: Array<{
id: number;
type: number;
url: string;
}>;
shelfStatus: number;
specAttrList: Array<{
id: number;
specName: string;
specValuesList: Array<{
id: number;
specName: string;
specValuesList: null;
}>;
}>;
tradeName: string;
userAccountId: number;
priceShow: number;
}
>;
// pc-后台用户id单位查询
export type GetCompanyInfoByBUId = InterFunction<
{
backUserAccountId: number;
},
{
address: string;
brandLogo: string;
brandName: string;
companyName: string;
companyType: number;
companyUserName: string;
content: string;
creditCode: string;
fullName: string;
id: number;
lat: number;
leader: number;
licenseImg: string;
lon: number;
phoneNum: string;
remark: string;
score: string;
userAccountId: number;
province: string;
city: string;
}
>;
import { InterFunction, InterListFunction } from '@/api/interface';
// 一级行业列表
export type GetIndustryListPagesType = InterListFunction<
{
pageNo: number;
pageSize: number;
typeName?: string;
id?: number;
},
{
createTime: string;
description: string;
id: number;
inspectionDTOS: Array<{
caseImg: string;
caseVideo: string;
companyInspectionDTOS: Array<{
companyInfoId: number;
companyName: string;
detailPage: string;
id: number;
industryTypeDTO: {};
inspectionDTO: {};
inspectionFileDTOS: Array<{
companyInspectionId: number;
fileType: number;
fileUrl: string;
first: number;
id: number;
}>;
inspectionFirstImg: string;
inspectionId: number;
inspectionPriceUnitId: number;
inspectionTagDTO: {
id: number;
inspectionId: number;
tagName: string;
};
inspectionTagId: number;
price: number;
priceRemark: string;
remark: string;
saleState: number;
serviceArea: string;
}>;
id: number;
industryTypeId: number;
inspectionDescription: string;
inspectionImg: string;
inspectionName: string;
inspectionNo: string;
saleState: number;
}>;
saleState: number;
typeImg: string;
typeName: string;
}
>;
// 单位服务列表-小程序展示
export type GetListAPPCompanyInspectionPageType = InterListFunction<
{
companyInfoId?: number;
industryTypeId?: number;
inspectionId?: number;
inspectionTagId?: number;
keyword?: string;
},
{
id: number;
companyInfoId: number;
serviceArea: string;
inspectionId: number;
inspectionTagId: null;
price: number;
priceRemark: string;
inspectionPriceUnitId: number;
detailPage: string;
saleState: number;
remark: string;
inspectionFirstImg: string;
industryTypeDTO: {
id: number;
typeName: string;
typeImg: null;
description: null;
saleState: null;
createTime: null;
inspectionDTOS: null;
};
inspectionDTO: {
id: number;
inspectionNo: string;
inspectionName: string;
industryTypeId: null;
inspectionImg: null;
inspectionDescription: null;
saleState: null;
caseImg: null;
caseVideo: null;
createTime: null;
companyInspectionDTOS: null;
};
inspectionTagDTO: {
id: null;
tagName: null;
inspectionId: number;
};
inspectionFileDTOS: Array<{
id: number;
fileType: number;
first: number;
companyInspectionId: number;
fileUrl: string;
}>;
companyName: string;
}
>;
// 价格单位列表
export type ListInspectionPriceUnit = InterFunction<
{},
{
id: number;
unitName: string;
}[]
>;
// 单位服务详情
export type GetCompanyInspectionById = InterFunction<
{
id: number;
},
{
companyInfoId: number;
companyName: string;
detailPage: string;
id: number;
industryTypeDTO: {
createTime: string;
description: string;
id: number;
inspectionDTOS: Array<{
caseImg: string;
caseVideo: string;
companyInspectionDTOS: null;
id: number;
industryTypeId: number;
inspectionDescription: string;
inspectionImg: string;
inspectionName: string;
inspectionNo: string;
saleState: number;
}>;
saleState: number;
typeImg: string;
typeName: string;
};
inspectionDTO: {
caseImg: string;
caseVideo: string;
companyInspectionDTOS: null;
id: number;
industryTypeId: number;
inspectionDescription: string;
inspectionImg: string;
inspectionName: string;
inspectionNo: string;
saleState: number;
};
inspectionFileDTOS: Array<{
companyInspectionId: number;
fileType: number;
fileUrl: string;
first: number;
id: number;
}>;
inspectionFirstImg: string;
inspectionId: number;
inspectionPriceUnitId: number;
inspectionTagDTO: {
id: number;
inspectionId: number;
tagName: string;
};
inspectionTagId: number;
price: number;
priceRemark: string;
remark: string;
saleState: number;
serviceArea: string;
}
>;
// 单位查询
export type GetCompanyInfoById = InterFunction<
{
id: number;
},
{
address: string;
brandLogo: string;
brandName: string;
companyName: string;
companyType: number;
companyUserName: string;
content: string;
creditCode: string;
fullName: string;
id: number;
lat: number;
leader: number;
licenseImg: string;
lon: number;
phoneNum: number;
remark: string;
score: string;
userAccountId: number;
backImg: string;
backUserAccountId: number;
backUserId: number;
}
>;
import { InterFunction } from '@/api/interface';
// 查询用户地址列表-条件查询
export type UserAddressSelectList = InterFunction<
{
userAccountId?: number;
},
{
id: number;
takeAddress: string;
takeName: string;
takePhone: string;
takeRegion: string;
type: number;
}[]
>;
import { InterFunction } from '@/api/interface';
// 获取用户钱包(新)
export type UserPayWalletInfoType = InterFunction<
{},
{
cashAmt: number;
cashFreeze: number;
cashPaid: number;
id: number;
rebateWdl: number;
salaryAmt: number;
salaryFreeze: number;
salaryPaid: number;
totalAmount: number;
totalCash: number;
totalFreeze: number;
totalSalary: number;
userAccountId: number;
userName: string;
}
>;
import {
GetAccountInfo,
GetAppletQRCode,
GetLoginInfo,
} from '@/api/interface/common';
import request from '../request'; import request from '../request';
export class CommonAPI { export class CommonAPI {
...@@ -8,4 +13,16 @@ export class CommonAPI { ...@@ -8,4 +13,16 @@ export class CommonAPI {
// 测试接口 // 测试接口
static cooperationListTag: any = (params: any) => static cooperationListTag: any = (params: any) =>
request.get('/userapp/cooperation/listTag', { params }); request.get('/userapp/cooperation/listTag', { params });
// 获取二维码
static getAppletQRCode: GetAppletQRCode = (params) =>
request.get('/userapp/wx/getAppletQRCode', { params });
// 查询登录信息
static getLoginInfo: GetLoginInfo = (params) =>
request.get('/userapp/temp-auth/getLoginInfo', { params });
// 获取用户信息
static getAccountInfo: GetAccountInfo = (params) =>
request.get('/userapp/user-account/info', { params });
} }
import {
QueryGoodsInfoByCategorySub,
GetAppCategoryInfo,
AppMallGoodsDetails,
GetCompanyInfoByBUId,
} from '@/api/interface/mall';
import request from '../request';
export class MallAPI {
// 小程序分类信息--含一二级分类
static getAppCategoryInfo: GetAppCategoryInfo = (params) =>
request.get('/pms/category/appCategoryInfo', { params });
// 根据子分类查询商品信息
static queryGoodsInfoByCategorySub: QueryGoodsInfoByCategorySub = (params) =>
request.post('/pms/app/goods/queryGoodsInfoByCategorySub', params);
// 小程序商品详情
static appMallGoodsDetails: AppMallGoodsDetails = (params) =>
request.get('/pms/app/goods/appMallGoodsDetails', { params });
// pc-后台用户id单位查询
static getCompanyInfoByBUId: GetCompanyInfoByBUId = (params) =>
request.get('/userapp/company/getCompanyInfoByBUId', { params });
}
import {
GetCompanyInfoById,
GetCompanyInspectionById,
GetIndustryListPagesType,
GetListAPPCompanyInspectionPageType,
ListInspectionPriceUnit,
} from '@/api/interface/service';
import request from '@/api/request';
export class ServiceAPI {
// 一级行业列表
static getIndustryListPages: GetIndustryListPagesType = (params) =>
request.post('/pms/industry/listPages', params);
// 单位服务列表-小程序展示
static getListAPPCompanyInspectionPage: GetListAPPCompanyInspectionPageType =
(params) =>
request.post(
'/pms/company-inspection/listAPPCompanyInspectionPage',
params,
);
// 价格单位列表
static listInspectionPriceUnit: ListInspectionPriceUnit = (params) =>
request.get('/pms/company-inspection/listInspectionPriceUnit', params);
// 单位服务详情
static getCompanyInspectionById: GetCompanyInspectionById = (params) =>
request.get('/pms/company-inspection/getCompanyInspectionById', { params });
// 单位查询
static getCompanyInfoById: GetCompanyInfoById = (params) =>
request.get('/userapp/company/getCompanyInfoById', { params });
}
import { UserAddressSelectList } from '@/api/interface/user';
import request from '../request';
export class UserAPI {
// 查询用户地址列表-条件查询
static userAddressSelectList: UserAddressSelectList = (params) =>
request.post('/oms/user-address/selectList', params);
}
import { UserPayWalletInfoType } from '@/api/interface/wallet';
import request from '@/api/request';
export class WalletAPI {
// 获取用户钱包(新)
static getUserPayWalletInfo: UserPayWalletInfoType = () =>
request.get('/userapp/pay/getCurrentUserPayWalletInfo');
}
...@@ -3,19 +3,16 @@ import axios, { AxiosResponse } from 'axios'; ...@@ -3,19 +3,16 @@ import axios, { AxiosResponse } from 'axios';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
// 接口地址
const { NEXT_PUBLIC_BASE_URL } = process.env;
// 创建服务 // 创建服务
const service = axios.create({ const service = axios.create({
baseURL: NEXT_PUBLIC_BASE_URL, baseURL: process.env.NEXT_PUBLIC_BASE_URL, // 接口地址
timeout: 3600000, timeout: 36000,
}); });
service.interceptors.request.use( service.interceptors.request.use(
(config: any) => { (config: any) => {
const token = Cookies.get('SHAREFLY-TOKEN'); const token = Cookies.get('SHAREFLY-WEB-TOKEN');
// console.log('config --->', NEXT_PUBLIC_BASE_URL); // console.log('config ==========>', config);
if (token) { if (token) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
config.headers.token = token; config.headers.token = token;
...@@ -43,8 +40,8 @@ service.interceptors.response.use( ...@@ -43,8 +40,8 @@ service.interceptors.response.use(
) )
) { ) {
message.error(data.message).then(); message.error(data.message).then();
Cookies.remove('SHAREFLY-TOKEN'); Cookies.remove('SHAREFLY-WEB-TOKEN');
localStorage.removeItem('roleId'); localStorage.removeItem('persist:SHAREFLY-WEB-STORAGE');
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 1000); }, 1000);
...@@ -55,8 +52,18 @@ service.interceptors.response.use( ...@@ -55,8 +52,18 @@ service.interceptors.response.use(
if (data instanceof Blob || Base64.isValid(data)) { if (data instanceof Blob || Base64.isValid(data)) {
return Promise.resolve(data); return Promise.resolve(data);
} }
// 如果还有其他报错那么就弹出报错信息(不需要对每个接口的报错做单独判断) // 判断是服务端还是客户端
message.error(data.message || '啊呀,出错了').then(); const isServer = typeof window === 'undefined';
if (isServer) {
// 如果是服务端
// eslint-disable-next-line no-alert
window.confirm(data.message || '啊呀,出错了');
// eslint-disable-next-line no-restricted-globals
history.back();
} else {
// 如果还有其他报错那么就弹出报错信息(不需要对每个接口的报错做单独判断)
message.error(data.message || '啊呀,出错了').then();
}
return Promise.reject(data); return Promise.reject(data);
} }
// 网络错误或链接超时 // 网络错误或链接超时
......
import styled from 'styled-components';
export default function Index() {
return <></>;
}
export const PageNotFoundStyle = styled.div`
.tips {
font-size: 50px;
text-align: center;
margin-top: 10px;
color: #000;
font-weight: 600;
.me404 {
width: 1000px;
height: 480px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -400px;
margin-top: -240px;
}
.st0 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #e8ebed;
}
.st1 {
fill: #ffffff;
stroke: #89949b;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st2 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #dbdfe1;
}
.st3 {
fill: #ffffff;
}
.st4 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #e8ebed;
stroke: #89949b;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st5 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #ffffff;
stroke: #89949b;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st6 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: none;
stroke: #89949b;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st7 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: none;
stroke: #89949b;
stroke-width: 4;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st8 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #ffffff;
stroke: #89949b;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st9 {
fill: #89949b;
}
.st10 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #89949b;
}
.st11 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: none;
stroke: #89949b;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st12 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #ffffff;
}
.st13 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #ffffff;
stroke: #8894a0;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st14 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: none;
stroke: #89949b;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
.st15 {
fill: none;
stroke: #89949b;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
#monkey-eye-l {
transform-origin: 191px 257px;
animation: blink-l 12s infinite ease-in-out;
}
#monkey-eye-r {
transform-origin: 205px 256px;
animation: blink-r 12s infinite ease-in-out;
}
@keyframes blink-l {
0% {
transform: rotateX(0deg);
}
2% {
transform: rotateX(80deg);
}
4%,
20% {
transform: rotateX(0deg);
}
22% {
transform: rotateX(80deg);
}
24%,
30% {
transform: rotateX(0deg);
}
32% {
transform: rotateX(80deg);
}
34%,
70% {
transform: rotateX(0deg);
}
72% {
transform: rotateX(80deg);
}
74%,
100% {
transform: rotateX(0deg);
}
}
@keyframes blink-r {
0% {
transform: rotateX(0deg);
}
2% {
transform: rotateX(80deg);
}
4%,
30% {
transform: rotateX(0deg);
}
32% {
transform: rotateX(80deg);
}
34%,
50% {
transform: rotateX(0deg);
}
52% {
transform: rotateX(80deg);
}
54%,
100% {
transform: rotateX(0deg);
}
}
}
`;
import React from 'react';
import { Breadcrumb } from 'antd';
import { useRouter } from 'next/router';
import styled from 'styled-components';
const BreadcrumbWrap = styled.div`
position: relative;
width: 100%;
display: flex;
align-content: center;
justify-content: flex-start;
.title {
font-size: 13px;
font-weight: 400;
color: #666666;
margin-bottom: 0.75rem;
}
`;
const BreadcrumbView: React.FC = () => {
// 路由钩子
const router = useRouter();
// 路由对应列表
const routerList = [
{ name: '云享商城', path: 'mall' },
{ name: '行业服务', path: 'service' },
{ name: '设备租赁', path: 'rent' },
{ name: '执照培训', path: 'train' },
{ name: '飞手约单', path: 'flyer' },
{ name: '商品详情', path: 'product' },
{ name: '购物车', path: 'cart' },
{ name: '提交订单', path: 'submit' },
{ name: '服务详情', path: 'detail' },
];
// 转换路由
const getCurrentRouter = () => {
const arr = router?.pathname
?.split('/')
?.map((i, j) => {
const href = `/${router?.pathname
.split('/')
.slice(1, j + 1)
.join('/')}`;
const title = routerList?.find((n) => n.path === i)?.name;
return { title, href };
})
?.filter((i) => i.title); // 过滤掉没有title的项
// 最后一项不跳转
arr[arr.length - 1] = { title: arr[arr.length - 1]?.title, href: '' };
return arr;
};
return (
<BreadcrumbWrap>
<div className="title">您的位置:</div>
<Breadcrumb
separator=">"
items={[
{
title: '首页',
href: '/',
},
...getCurrentRouter(),
]}
/>
</BreadcrumbWrap>
);
};
export default BreadcrumbView;
import React, { useEffect, useState } from 'react';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import styled from 'styled-components';
// 分类列表类型
export type CategoryType = {
value: number;
label: string;
children?: CategoryType;
}[];
const CategorySelectView: React.FC<{
list: CategoryType;
onMain?: (arr: number) => void;
onSecond?: (arr: number[]) => void;
onSelect?: ({ main, second }: { main?: number; second?: number[] }) => void;
isMultiple?: boolean;
allText?: string;
}> = ({ list, onMain, onSecond, onSelect, allText, isMultiple }) => {
CategorySelectView.defaultProps = { allText: '全部商品', isMultiple: true };
// 图标样式
const IconStyle = {
fontSize: '8px',
color: 'rgba(153,153,153,0.5)',
transform: 'scaleX(1.2)',
};
// 路由钩子
const router = useRouter();
// 当前选择索引
const [currentIndex, setCurrentIndex] = useState<number>(0);
// 二级索引列表
const [secondIndex, setSecondIndex] = useState<number[]>([]);
// 二级索引选择
const handleSecondary = (index: number) => {
const has = secondIndex?.some((i) => i === index);
const arr = has
? secondIndex?.filter((i) => i !== index)
: [...secondIndex, index];
const brr = isMultiple ? arr : [index];
setSecondIndex(brr);
// 二级筛选列表
const second =
brr?.length !== 0
? brr?.map((i) => list[currentIndex]?.children?.[i]?.value || 0)
: list[currentIndex]?.children?.map((i) => i?.value) || [];
// 二级筛选回调
onSecond?.(second);
// 统一返回筛选
onSelect?.({
main: list[currentIndex]?.value,
second,
});
};
// 主索引选择
const handleMain = (index: number) => {
setCurrentIndex(index);
setSecondIndex([]);
// 切换分类
onMain?.(list[index]?.value);
// 清空二级分类,并返回全部二级分类
onSecond?.(list[index]?.children?.map((i) => i?.value) || []);
// 统一返回筛选
onSelect?.({
main: list[index]?.value,
second: list[index]?.children?.map((i) => i?.value) || [],
});
};
// 选择全部商品
const handleAll = () => {
setSecondIndex([]);
onSecond?.(list[currentIndex]?.children?.map((i) => i?.value) || []);
// 统一返回筛选
onSelect?.({
main: list[currentIndex]?.value,
second: list[currentIndex]?.children?.map((i) => i?.value) || [],
});
};
// 组件挂载
useEffect(() => {
if (!list?.length) return;
// 一级分类id
const mainID = Number(router?.query?.main);
// 二级分类id
const secondID = Number(router?.query?.second);
// 如果路由里面有一级分类id,则初始化一级分类
if (mainID) {
const index = list?.findIndex((i) => i?.value === mainID) || 0;
setCurrentIndex(index);
onMain?.(list[index]?.value);
if (!secondID)
onSecond?.(list[index]?.children?.map((i) => i?.value) || []);
}
// 如果路由里面有二级分类id,则初始化二级分类
if (secondID) {
const children = list?.find((i) => i?.value === mainID)?.children;
const index = children?.findIndex((i) => i?.value === secondID) || 0;
setSecondIndex([index]);
onSecond?.(children?.[index]?.value ? [children?.[index]?.value] : []);
}
// 如果任何id都没有
if (!router?.query?.main && !router?.query?.second) {
// 初始化返回一级分类
onMain?.(list[currentIndex]?.value);
// 初始化全部二级分类
onSecond?.(list[currentIndex]?.children?.map((i) => i?.value) || []);
}
// 统一返回筛选
onSelect?.({
main: mainID || list[currentIndex]?.value,
second: secondID
? [secondID]
: list[currentIndex]?.children?.map((i) => i?.value) || [],
});
}, [list, router]);
return (
<CategorySelectWrap>
<div className="category-select flex-start">
<div className="select-item">类别:</div>
{list?.map((i, j) => (
<div
className={`select-item ${currentIndex === j && 'item-active'}`}
key={j}
onClick={() => handleMain(j)}
>
<div className="text">{i.label}</div>
{currentIndex === j ? (
<UpOutlined style={IconStyle} />
) : (
<DownOutlined style={IconStyle} />
)}
</div>
))}
</div>
<div className="category-list flex-start">
<div
className={`list-item ${secondIndex?.length === 0 && 'item-active'}`}
onClick={handleAll}
>
{allText || '全部商品'}
</div>
{list[currentIndex]?.children?.map((i, j) => (
<div
className={`list-item ${secondIndex?.includes(j) && 'item-active'}`}
key={j}
onClick={() => handleSecondary(j)}
>
{i.label}
</div>
))}
</div>
</CategorySelectWrap>
);
};
export default CategorySelectView;
const CategorySelectWrap = styled.div`
position: relative;
width: 100%;
min-height: 2rem;
border: 1px solid #ebebeb;
margin-bottom: 1rem;
.category-select {
position: relative;
width: 100%;
min-height: 2rem;
flex-wrap: wrap;
border-bottom: 1px solid #ebebeb;
.select-item {
position: relative;
box-sizing: border-box;
color: #333333;
padding: 0 1.25rem;
min-height: 2rem;
line-height: 2rem;
display: flex;
align-items: center;
justify-content: center;
&:first-child {
color: #999999;
}
&:not(:first-child):active,
&:not(:first-child):hover {
background: #ececec;
}
&:not(:first-child) {
cursor: pointer;
}
.text {
margin-right: 0.25rem;
}
}
.item-active {
background: #ececec;
}
}
.category-list {
position: relative;
width: 100%;
min-height: 3rem;
flex-wrap: wrap;
box-sizing: border-box;
padding: 0 0.5rem;
.list-item {
position: relative;
min-height: 1.68rem;
line-height: 1.68rem;
padding: 0 0.5rem;
cursor: pointer;
margin: 0.25rem 0.5rem 0.25rem 0;
}
.item-active {
color: #fff;
background: #ff552d;
border-radius: 4px;
}
}
`;
...@@ -4,6 +4,7 @@ import styled from 'styled-components'; ...@@ -4,6 +4,7 @@ import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface'; import { InterListType } from '@/api/interface';
import { ListCompanyInfoByCoopIdType } from '@/api/interface/home'; import { ListCompanyInfoByCoopIdType } from '@/api/interface/home';
import QrcodePopover from '@/components/qrcodePopover';
// 列表类型 // 列表类型
type ListType = InterListType<ListCompanyInfoByCoopIdType>; type ListType = InterListType<ListCompanyInfoByCoopIdType>;
...@@ -34,24 +35,32 @@ const HomeBrandView = () => { ...@@ -34,24 +35,32 @@ const HomeBrandView = () => {
}, []); }, []);
return ( return (
<HomeBrandWrap> <HomeBrandWrap>
<div className="brand-item"> <QrcodePopover path={'page-product/product-company/index'}>
<div className="item-icon"> <div className="brand-item">
<img <div className="item-icon">
src="/assets/image/home/home-brand-all.png" <img
alt="全部品牌" src="/assets/image/home/home-brand-all.png"
className="image" alt="全部品牌"
/> className="image"
/>
</div>
</div> </div>
</div> </QrcodePopover>
{companyList?.map((i, j) => ( {companyList?.map((i, j) => (
<div key={j} className="brand-item"> <QrcodePopover
<div className="item-logo"> key={j}
<img src={i.brandLogo} alt={i.brandName} className="image" /> path={'page-product/product-store/index'}
scene={`id=${Number(i?.backUserAccountId)}`}
>
<div className="brand-item">
<div className="item-logo">
<img src={i.brandLogo} alt={i.brandName} className="image" />
</div>
<Button type={'link'} className="item-name text-ellipsis">
{i.brandName || i.companyName}
</Button>
</div> </div>
<Button type={'link'} className="item-name text-ellipsis"> </QrcodePopover>
{i.brandName || i.companyName}
</Button>
</div>
))} ))}
</HomeBrandWrap> </HomeBrandWrap>
); );
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import MapContainer from '@/components/map'; import MapContainer from '@/components/map';
import { RootState } from '@/store';
import { AddressState } from '@/store/module/address';
const HomeMapView = () => { const HomeMapView = () => {
// 选择索引 // 选择索引
...@@ -18,6 +21,10 @@ const HomeMapView = () => { ...@@ -18,6 +21,10 @@ const HomeMapView = () => {
setCurrentIndex(index); setCurrentIndex(index);
getServiceBitmap().then(); getServiceBitmap().then();
}; };
// address
const address = useSelector(
(state: RootState) => state.address,
) as AddressState;
// 地图网点列表 // 地图网点列表
const [mapMarkerList, setMapMarkerList] = useState< const [mapMarkerList, setMapMarkerList] = useState<
{ lat: number; lon: number; name: string }[] { lat: number; lon: number; name: string }[]
...@@ -46,7 +53,10 @@ const HomeMapView = () => { ...@@ -46,7 +53,10 @@ const HomeMapView = () => {
}, []); }, []);
return ( return (
<HomeMapWrap> <HomeMapWrap>
<MapContainer list={mapMarkerList} /> <MapContainer
list={mapMarkerList}
center={[address?.longitude, address?.latitude]}
/>
<div className="map-wrap flex-around"> <div className="map-wrap flex-around">
{networkTypeList?.map((i, j) => ( {networkTypeList?.map((i, j) => (
<div <div
......
...@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; ...@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { RightOutlined } from '@ant-design/icons'; import { RightOutlined } from '@ant-design/icons';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import QrcodePopover from '@/components/qrcodePopover';
const HomeNewsView = () => { const HomeNewsView = () => {
// 帖子类型 // 帖子类型
...@@ -114,10 +115,12 @@ const HomeNewsView = () => { ...@@ -114,10 +115,12 @@ const HomeNewsView = () => {
</div> </div>
))} ))}
</div> </div>
<div className="head-more flex-between"> <QrcodePopover path={'page-home/news-list/index'}>
<div className="more-text">更多</div> <div className="head-more flex-between">
<RightOutlined style={{ fontSize: 10, color: '#33333380' }} /> <div className="more-text">更多</div>
</div> <RightOutlined style={{ fontSize: 10, color: '#33333380' }} />
</div>
</QrcodePopover>
<img <img
className="head-bg" className="head-bg"
src="https://pad-video-x.oss-cn-shenzhen.aliyuncs.com/file/sharefly-web-news.png" src="https://pad-video-x.oss-cn-shenzhen.aliyuncs.com/file/sharefly-web-news.png"
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
...@@ -8,6 +9,8 @@ import { RecommendGoodsType } from '@/api/interface/home'; ...@@ -8,6 +9,8 @@ import { RecommendGoodsType } from '@/api/interface/home';
type ListType = InterDataType<RecommendGoodsType>; type ListType = InterDataType<RecommendGoodsType>;
const HomeProductView = () => { const HomeProductView = () => {
// 路由钩子
const router = useRouter();
// 推荐商品列表 // 推荐商品列表
const [recommendGoodsList, setRecommendGoodsList] = useState< const [recommendGoodsList, setRecommendGoodsList] = useState<
ListType[0]['mallGoodsList'] ListType[0]['mallGoodsList']
...@@ -33,6 +36,10 @@ const HomeProductView = () => { ...@@ -33,6 +36,10 @@ const HomeProductView = () => {
).salePrice || 0 ).salePrice || 0
); );
}; };
// 跳转详情
const handleDetail = (item: ListType[0]['mallGoodsList'][0]) => {
router.push(`/mall/product/${item.id}`).then();
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getRecommendGoodsList().then(); getRecommendGoodsList().then();
...@@ -40,7 +47,11 @@ const HomeProductView = () => { ...@@ -40,7 +47,11 @@ const HomeProductView = () => {
return ( return (
<HomeProductWrap> <HomeProductWrap>
{recommendGoodsList?.map((i, j) => ( {recommendGoodsList?.map((i, j) => (
<div className="product-item flex-start" key={j}> <div
className="product-item flex-start"
key={j}
onClick={() => handleDetail(i)}
>
<img <img
className="item-image" className="item-image"
src={`${i.resourcesList[0].url}?x-oss-process=image/quality,q_25`} src={`${i.resourcesList[0].url}?x-oss-process=image/quality,q_25`}
......
import React from 'react'; import React from 'react';
import { Input } from 'antd'; import { Input } from 'antd';
import { HomeSearchWrap } from '@/pages/home/comp/home-search/styled'; import { HomeSearchWrap } from './styled';
const HomeSearchView = () => { const HomeSearchView = () => {
return ( return (
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface'; import { InterListType } from '@/api/interface';
...@@ -8,6 +9,8 @@ import { IndustryListPagesType } from '@/api/interface/home'; ...@@ -8,6 +9,8 @@ import { IndustryListPagesType } from '@/api/interface/home';
type ListType = InterListType<IndustryListPagesType>; type ListType = InterListType<IndustryListPagesType>;
const HomeServiceView = () => { const HomeServiceView = () => {
// 路由钩子
const router = useRouter();
// 刷新子组件 // 刷新子组件
const [refresh, setRefresh] = useState<boolean>(true); const [refresh, setRefresh] = useState<boolean>(true);
// 服务列表 // 服务列表
...@@ -33,6 +36,10 @@ const HomeServiceView = () => { ...@@ -33,6 +36,10 @@ const HomeServiceView = () => {
setRefresh(true); setRefresh(true);
}, 0); }, 0);
}; };
// 跳转详情
const handleDetail = (item: ListType[0]['inspectionDTOS'][0]) => {
router.push(`/service/${item?.industryTypeId}/${item?.id}`).then();
};
// componentDidMount // componentDidMount
useEffect(() => { useEffect(() => {
getIndustryListPages().then(); getIndustryListPages().then();
...@@ -56,6 +63,7 @@ const HomeServiceView = () => { ...@@ -56,6 +63,7 @@ const HomeServiceView = () => {
<div <div
className="service-item animate__animated animate__fast animate__fadeIn" className="service-item animate__animated animate__fast animate__fadeIn"
key={j} key={j}
onClick={() => handleDetail(i)}
> >
<img <img
className="item-image" className="item-image"
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { AppCategoryInfoType } from '@/api/interface/home'; import { AppCategoryInfoType } from '@/api/interface/home';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型 // 列表类型
type ListType = InterDataType<AppCategoryInfoType>; type ListType = InterDataType<AppCategoryInfoType>;
const TabView01 = () => { const TabView01 = () => {
// 导航钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 列表数据 // 列表数据
const [tabList, setTabList] = useState<ListType>([]); const [tabList, setTabList] = useState<ListType>([]);
// 获取云享商城分类 // 获取云享商城分类
...@@ -21,6 +28,24 @@ const TabView01 = () => { ...@@ -21,6 +28,24 @@ const TabView01 = () => {
// console.log('获取云享商城分类 --->', tabList); // console.log('获取云享商城分类 --->', tabList);
} }
}; };
// 跳转一级分类详情
const handleMain = (i: ListType[0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/mall/${i?.id}`).then();
};
// 跳转二级分类详情
const handleSecond = (i: ListType[0], n: ListType[0]['subDTOList'][0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/mall/${i?.id}/${n?.id}`).then();
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getAppCategoryInfo().then(); getAppCategoryInfo().then();
...@@ -33,11 +58,22 @@ const TabView01 = () => { ...@@ -33,11 +58,22 @@ const TabView01 = () => {
{!!i.icon && ( {!!i.icon && (
<img src={i.icon} alt={i.name} className="title-image" /> <img src={i.icon} alt={i.name} className="title-image" />
)} )}
<div className="title-name">{i.name}</div> <Button
type={'link'}
className="title-name"
onClick={() => handleMain(i)}
>
{i.name}
</Button>
</div> </div>
<div className="tab-list flex-start"> <div className="tab-list flex-start">
{i.subDTOList?.map((n, m) => ( {i.subDTOList?.map((n, m) => (
<Button type={'link'} key={m} className="list-item"> <Button
type={'link'}
key={m}
className="list-item"
onClick={() => handleSecond(i, n)}
>
{n.name} {n.name}
</Button> </Button>
))} ))}
...@@ -70,6 +106,7 @@ const TabViewWrap = styled.div` ...@@ -70,6 +106,7 @@ const TabViewWrap = styled.div`
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 550; font-weight: 550;
color: #333333; color: #333333;
padding: 0;
} }
} }
.tab-list { .tab-list {
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface'; import { InterListType } from '@/api/interface';
import { IndustryListPagesType } from '@/api/interface/home'; import { IndustryListPagesType } from '@/api/interface/home';
import { setGlobalData } from '@/store/module/globalData';
// 列表类型 // 列表类型
type ListType = InterListType<IndustryListPagesType>; type ListType = InterListType<IndustryListPagesType>;
const TabView02 = () => { const TabView02 = () => {
// 导航钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 列表数据 // 列表数据
const [tabList, setTabList] = useState<ListType>([]); const [tabList, setTabList] = useState<ListType>([]);
// 获取分类列表 // 获取分类列表
...@@ -22,6 +29,27 @@ const TabView02 = () => { ...@@ -22,6 +29,27 @@ const TabView02 = () => {
// console.log('获取分类列表 --->', res.result); // console.log('获取分类列表 --->', res.result);
} }
}; };
// 跳转一级分类详情
const handleMain = (i: ListType[0]) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/service/${i?.id}`).then();
};
// 跳转二级分类详情
const handleSecond = (
i: ListType[0],
n: ListType[0]['inspectionDTOS'][0],
) => {
dispatch(
setGlobalData({
loadingSpinnerVisible: true,
}),
);
router.push(`/service/${i?.id}/${n?.id}`).then();
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getIndustryListPages().then(); getIndustryListPages().then();
...@@ -34,11 +62,22 @@ const TabView02 = () => { ...@@ -34,11 +62,22 @@ const TabView02 = () => {
{!!i.typeImg && ( {!!i.typeImg && (
<img src={i.typeImg} alt={i.typeName} className="title-image" /> <img src={i.typeImg} alt={i.typeName} className="title-image" />
)} )}
<div className="title-name">{i.typeName}</div> <Button
type={'link'}
className="title-name"
onClick={() => handleMain(i)}
>
{i.typeName}
</Button>
</div> </div>
<div className="tab-list flex-start"> <div className="tab-list flex-start">
{i.inspectionDTOS?.map((n, m) => ( {i.inspectionDTOS?.map((n, m) => (
<Button type={'link'} key={m} className="list-item"> <Button
type={'link'}
key={m}
className="list-item"
onClick={() => handleSecond(i, n)}
>
{n.inspectionName} {n.inspectionName}
</Button> </Button>
))} ))}
...@@ -65,7 +104,6 @@ const TabViewWrap = styled.div` ...@@ -65,7 +104,6 @@ const TabViewWrap = styled.div`
.title-image { .title-image {
width: 1.68rem; width: 1.68rem;
height: 1.68rem; height: 1.68rem;
margin-right: 0.5rem;
} }
.title-name { .title-name {
font-size: 0.75rem; font-size: 0.75rem;
......
...@@ -2,12 +2,12 @@ import React, { useEffect, useState } from 'react'; ...@@ -2,12 +2,12 @@ import React, { useEffect, useState } from 'react';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { ListBannerImgType } from '@/api/interface/home'; import { ListBannerImgType } from '@/api/interface/home';
import TabView01 from '@/pages/home/comp/home-tab/comp/tabView01'; import TabView01 from './comp/tabView01';
import TabView02 from '@/pages/home/comp/home-tab/comp/tabView02'; import TabView02 from './comp/tabView02';
import TabView03 from '@/pages/home/comp/home-tab/comp/tabView03'; import TabView03 from './comp/tabView03';
import TabView04 from '@/pages/home/comp/home-tab/comp/tabView04'; import TabView04 from './comp/tabView04';
import TabView05 from '@/pages/home/comp/home-tab/comp/tabView05'; import TabView05 from './comp/tabView05';
import { HomeTabWrap } from '@/pages/home/comp/home-tab/styled'; import { HomeTabWrap } from './styled';
// 分类列表类型 // 分类列表类型
type CategoryListType = InterDataType<ListBannerImgType>; type CategoryListType = InterDataType<ListBannerImgType>;
...@@ -23,7 +23,7 @@ const HomeTabView = () => { ...@@ -23,7 +23,7 @@ const HomeTabView = () => {
moduleCode: 'HOME_MENU_NEW', moduleCode: 'HOME_MENU_NEW',
}); });
if (res && res.code === '200') { if (res && res.code === '200') {
setCategoryList(res.result || []); setCategoryList(res.result.slice(0, 1) || []);
} }
}; };
// 选择分类 // 选择分类
......
...@@ -5,10 +5,14 @@ import { ...@@ -5,10 +5,14 @@ import {
VerticalAlignTopOutlined, VerticalAlignTopOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { AppPublishListType } from '@/api/interface/home'; import { AppPublishListType } from '@/api/interface/home';
import QrcodePopover from '@/components/qrcodePopover';
import { setGlobalData } from '@/store/module/globalData';
import { UserInfoState } from '@/store/module/userInfo';
import { formatLocationStr } from '@/utils/formatLocation'; import { formatLocationStr } from '@/utils/formatLocation';
import { bigNumberTransform } from '@/utils/money'; import { bigNumberTransform } from '@/utils/money';
...@@ -16,6 +20,10 @@ import { bigNumberTransform } from '@/utils/money'; ...@@ -16,6 +20,10 @@ import { bigNumberTransform } from '@/utils/money';
type ListType = InterDataType<AppPublishListType>; type ListType = InterDataType<AppPublishListType>;
const HomeTaskView = () => { const HomeTaskView = () => {
// store
const dispatch = useDispatch();
// userInfo
const userInfo = useSelector((state: any) => state.userInfo) as UserInfoState;
// 服务标签类型 // 服务标签类型
const serviceTagList = [ const serviceTagList = [
{ {
...@@ -108,6 +116,25 @@ const HomeTaskView = () => { ...@@ -108,6 +116,25 @@ const HomeTaskView = () => {
// formatMoney(props.detail?.orderAmount) // formatMoney(props.detail?.orderAmount)
return i?.orderAmount > 1 ? money : i?.orderAmount; return i?.orderAmount > 1 ? money : i?.orderAmount;
}; };
// 跳转详情
const handleDetail = (item: ListType[0]) => {
// 判断是否登录
if (!userInfo?.id) {
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
return;
}
dispatch(
setGlobalData({
qrcodeModalVisible: true,
qrcodeModalPath: 'page-order/order-flyer/index',
qrcodeModalScene: `id=${Number(item?.id)}`,
}),
);
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
getRequirementsListType().then(); getRequirementsListType().then();
...@@ -117,16 +144,18 @@ const HomeTaskView = () => { ...@@ -117,16 +144,18 @@ const HomeTaskView = () => {
<HomeTaskWrap> <HomeTaskWrap>
<div className="task-title"> <div className="task-title">
<div className="title-label">抢单大厅</div> <div className="title-label">抢单大厅</div>
<div className="title-more"> <QrcodePopover path={'page-service/service-task/index'}>
<div className="more-label">更多</div> <div className="title-more">
<div className="more-icon"> <div className="more-label">更多</div>
<RightOutlined style={{ color: '#998e8b', fontSize: 13 }} /> <div className="more-icon">
<RightOutlined style={{ color: '#998e8b', fontSize: 13 }} />
</div>
</div> </div>
</div> </QrcodePopover>
</div> </div>
<div className="task-list"> <div className="task-list">
{requireList?.map((i, j) => ( {requireList?.map((i, j) => (
<div className="list-item" key={j}> <div className="list-item" key={j} onClick={() => handleDetail(i)}>
<div className="item-title"> <div className="item-title">
{i?.orderLevelEnum !== 'REGULAR_ORDER' && ( {i?.orderLevelEnum !== 'REGULAR_ORDER' && (
<div <div
......
import React from 'react'; import React from 'react';
import { RightOutlined } from '@ant-design/icons'; import { RightOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import styled from 'styled-components'; import styled from 'styled-components';
const HomeTitleView = ({ title }: { title: string }) => { const HomeTitleView: React.FC<{ title: string; path?: string }> = ({
title,
path,
}) => {
// 路由钩子
const router = useRouter();
// 跳转详情
const handleDetail = async () => {
if (!path) return;
await router.push(path);
};
return ( return (
<HomeTitleWrap> <HomeTitleWrap onClick={handleDetail}>
<div className="title-text">{title}</div> <div className="title-text">{title}</div>
<div className="title-more flex-start"> <div className="title-more flex-start">
<div className="more-text">更多</div> <div className="more-text">更多</div>
...@@ -27,6 +38,8 @@ export const HomeTitleWrap = styled.div` ...@@ -27,6 +38,8 @@ export const HomeTitleWrap = styled.div`
background-size: cover; background-size: cover;
background-position: center; background-position: center;
z-index: 1; z-index: 1;
cursor: pointer;
user-select: none;
&::after { &::after {
position: absolute; position: absolute;
content: ''; content: '';
......
import React from 'react'; import React, { useEffect } from 'react';
import { Spin } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import { ContentWrap } from '@/components/layout/content/styled'; import { ContentWrap } from '@/components/layout/content/styled';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
const ContentView = ({ children }: { children?: React.ReactNode }) => { const ContentView = ({ children }: { children?: React.ReactNode }) => {
return <ContentWrap>{!!children && children}</ContentWrap>; // store
const dispatch = useDispatch();
// globalData
const globalData = useSelector(
(state: any) => state.globalData,
) as GlobalDataState;
// 页面挂载时自动将加载中移除
useEffect(() => {
dispatch(
setGlobalData({
loadingSpinnerVisible: false,
}),
);
}, []);
return (
<div className={'animate__animated animate__faster animate__fadeIn'}>
<Spin spinning={globalData?.loadingSpinnerVisible} fullscreen />
<ContentWrap>{!!children && children}</ContentWrap>
</div>
);
}; };
export default ContentView; export default ContentView;
...@@ -3,7 +3,7 @@ import styled from 'styled-components'; ...@@ -3,7 +3,7 @@ import styled from 'styled-components';
export const ContentWrap = styled.div` export const ContentWrap = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;
min-height: 100vh; min-height: 78vh;
box-sizing: border-box; box-sizing: border-box;
border-radius: 0; border-radius: 0;
opacity: 1; opacity: 1;
......
import React from 'react'; import React from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import { FooterWrap } from '@/components/layout/footer/styled'; import { FooterWrap } from '@/components/layout/footer/styled';
import QrcodePopover from '@/components/qrcodePopover';
const FooterView = () => { const FooterView = () => {
// 跳转外部网址
const handleLink = (url: string) => {
window.open(url);
};
// 关于列表 // 关于列表
const aboutList = [ const aboutList = [
{ {
...@@ -21,6 +26,22 @@ const FooterView = () => { ...@@ -21,6 +26,22 @@ const FooterView = () => {
alt: '官方社群', alt: '官方社群',
}, },
]; ];
// 集团列表
const groupList = [
{ name: '浙江科比特', url: 'https://www.mmcuav.cn/' },
{
name: '更多产业布局',
url: 'https://www.mmcuav.cn/about-mmc/industrial-layout/',
},
];
// 科比特航空
const mmcList = [
{ name: '产品中心', url: 'https://www.mmcuav.cn/product-center/' },
{ name: '解决方案', url: 'https://www.mmcuav.cn/solution/' },
// { name: '服务平台', url: 'https://www.mmcuav.cn/service-platform/' },
// { name: '技术支持', url: 'https://www.mmcuav.cn/support/' },
{ name: '关于我们', url: 'https://www.mmcuav.cn/about-mmc/' },
];
return ( return (
<FooterWrap> <FooterWrap>
<div className="footer-wrap"> <div className="footer-wrap">
...@@ -33,36 +54,48 @@ const FooterView = () => { ...@@ -33,36 +54,48 @@ const FooterView = () => {
</div> </div>
<div className="start-item"> <div className="start-item">
<div className="item-title">集团公司</div> <div className="item-title">集团公司</div>
<Button type={'link'} className="item-link"> {groupList.map((i, j) => (
浙江科比特 <Button
</Button> type={'link'}
<Button type={'link'} className="item-link"> className="item-link"
深圳科比特 key={j}
</Button> onClick={() => handleLink(i?.url)}
<Button type={'link'} className="item-link"> >
更多集团公司 {i?.name}
</Button> </Button>
))}
</div> </div>
<div className="start-item"> <div className="start-item">
<div className="item-title">合作伙伴</div> <div className="item-title">科比特航空</div>
<Button type={'link'} className="item-link"> {mmcList.map((i, j) => (
浙江科比特 <Button
</Button> type={'link'}
<Button type={'link'} className="item-link"> className="item-link"
深圳科比特 key={j}
</Button> onClick={() => handleLink(i?.url)}
<Button type={'link'} className="item-link"> >
更多合作伙伴 {i?.name}
</Button> </Button>
))}
</div> </div>
<div className="start-item"> <div className="start-item">
<div className="item-title">商家入驻</div> <div className="item-title">加入云享飞</div>
<Button type={'link'} className="item-link"> <QrcodePopover
合作咨询 path={'page-identity/identity-list/index'}
</Button> placement={'bottomLeft'}
<Button type={'link'} className="item-link"> >
加盟政策 <Button type={'link'} className="item-link">
</Button> 合作咨询
</Button>
</QrcodePopover>
<QrcodePopover
path={'page-identity/identity-list/index'}
placement={'bottomLeft'}
>
<Button type={'link'} className="item-link">
加盟政策
</Button>
</QrcodePopover>
</div> </div>
<div className="start-item"> <div className="start-item">
<div className="item-title">关于我们</div> <div className="item-title">关于我们</div>
...@@ -77,15 +110,21 @@ const FooterView = () => { ...@@ -77,15 +110,21 @@ const FooterView = () => {
</div> </div>
</div> </div>
<div className="footer-end"> <div className="footer-end">
<Button type={'link'} className="end-item"> <QrcodePopover path={'page-mine/help-center/index'}>
常见问题 <Button type={'link'} className="end-item">
</Button> 常见问题
<Button type={'link'} className="end-item"> </Button>
意见反馈 </QrcodePopover>
</Button> <QrcodePopover path={'page-mine/help-center/index'}>
<Button type={'link'} className="end-item"> <Button type={'link'} className="end-item">
加盟入驻 意见反馈
</Button> </Button>
</QrcodePopover>
<QrcodePopover path={'page-mine/sharefly-about/index'}>
<Button type={'link'} className="end-item">
免责声明
</Button>
</QrcodePopover>
<Button <Button
type={'link'} type={'link'}
className="end-item" className="end-item"
......
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { EnvironmentFilled } from '@ant-design/icons'; import {
import { Button } from 'antd'; EnvironmentFilled,
import { useRouter } from 'next/router'; LogoutOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import { Button, Dropdown, MenuProps, Modal } from 'antd';
import dayjs from 'dayjs';
import Cookies from 'js-cookie';
import { useDispatch, useSelector } from 'react-redux';
import { CommonAPI } from '@/api';
import { HeaderWrap } from '@/components/layout/header/styled'; import { HeaderWrap } from '@/components/layout/header/styled';
import QrcodePopover from '@/components/qrcodePopover';
import { RootState } from '@/store';
import { AddressState, setAddress } from '@/store/module/address';
import { setGlobalData } from '@/store/module/globalData';
import { setSystem, SystemState } from '@/store/module/system';
import { setUserInfo, UserInfoState } from '@/store/module/userInfo';
import getLocationByIP from '@/utils/getLocationByIP';
const HeaderView: React.FC<{ const HeaderView: React.FC<{
placeholder?: boolean; placeholder: boolean;
}> = ({ placeholder }) => { }> = ({ placeholder }) => {
HeaderView.defaultProps = { const token = Cookies.get('SHAREFLY-WEB-TOKEN');
placeholder: true,
};
// 当前的路由数据 // 当前的路由数据
const router = useRouter(); // const router = useRouter();
// store
const dispatch = useDispatch();
// system
const system = useSelector((state: RootState) => state.system) as SystemState;
// address
const address = useSelector(
(state: RootState) => state.address,
) as AddressState;
// userInfo
const userInfo = useSelector(
(state: RootState) => state.userInfo,
) as UserInfoState;
// 打开登录弹窗
const handleLogin = () => {
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
};
// 获取用户信息
const getAccountInfo = async () => {
if (!system?.token || !token) return;
const res = await CommonAPI.getAccountInfo();
if (res && res.code === '200') {
dispatch(setUserInfo(res.result));
}
};
// 计算天数与当前时间的差值
const getDiffDay = (date: string) => dayjs().diff(dayjs(date), 'day');
// 重新获取用户的地址信息
const handleReload = () => {
getLocationByIP().then((res) => {
dispatch(setAddress(res));
});
};
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
console.log('HeaderView --->', router); if (!address?.city) {
}, [router]); // 获取当前地址
return ( getLocationByIP().then((res) => {
<HeaderWrap> dispatch(setAddress(res));
<div className="header-wrap"> });
<div className="header-location"> }
<div className="location-icon"> // 当前是否登录
<EnvironmentFilled if (!token) {
style={{ color: '#FF552D', fontSize: '0.86rem' }} dispatch(setSystem({ token: undefined }));
/> dispatch(setUserInfo(null));
} else {
getAccountInfo().then();
}
}, []);
// 顶部Tab列表
const tabList = [
{ label: '个人中心', value: 'pages/mine/index' },
{ label: '购物车', value: 'page-cart/cart-list/index' },
{ label: '我的订单', value: 'pages/order/index' },
{ label: '消息', value: 'pages/message/index' },
{ label: '联系客服', value: 'page-mine/help-center/index' },
];
// 右上角按钮
const items: MenuProps['items'] = [
{
key: '1',
label: (
<div style={{ textAlign: 'left', marginBottom: '20px' }}>
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
用户信息:
</div> </div>
<Button type={'link'} className="location-address"> <div>昵称:{userInfo?.nickName}</div>
杭州 <div>手机号:{userInfo?.phoneNum}</div>
</Button> <div>来到云享飞 {getDiffDay(userInfo?.createTime)}</div>
<div className="location-hello">Hi,欢迎来云享飞</div>
</div> </div>
<div className="header-nav"> ),
<div className="nav-tab"> },
<Button type={'link'} className="tab-item"> {
个人中心 key: '0',
</Button> label: (
<Button type={'link'} className="tab-item"> <Button
购物车 style={{ color: '#666666' }}
</Button> type="link"
<Button type={'link'} className="tab-item"> icon={<LogoutOutlined />}
我的订单 onClick={() => {
</Button> Modal.confirm({
<Button type={'link'} className="tab-item"> title: '退出登录',
消息 content: '退出后未保存数据将会丢失,确定登出吗?',
</Button> onOk: () => {
<Button type={'link'} className="tab-item"> dispatch(setUserInfo(null));
联系客服 dispatch(setSystem({ token: undefined }));
Cookies.remove('SHAREFLY-TOKEN');
},
});
}}
>
退出登录
</Button>
),
},
];
return (
<>
<HeaderWrap
style={{
background: placeholder ? '#2A2A2A' : 'rgba(86, 86, 86, 0.25)',
}}
>
<div className="header-wrap">
<div className="header-location">
<div className="location-icon">
<EnvironmentFilled
style={{ color: '#FF552D', fontSize: '0.86rem' }}
/>
</div>
<Button type={'link'} className="location-address">
{address?.city || '定位中...'}
</Button> </Button>
<Button
type={'link'}
icon={<ReloadOutlined style={{ fontSize: '10px' }} />}
className="location-reload"
title={'刷新位置'}
onClick={() => handleReload()}
></Button>
<div className="location-hello">Hi,欢迎来到云享飞</div>
</div> </div>
<div className="nav-action"> <div className="header-nav">
<Button type={'primary'} className="action-item"> <div className="nav-tab">
登录 {tabList?.map((i, j) => (
</Button> <QrcodePopover path={i.value} key={j}>
<Button type={'primary'} className="action-item"> <Button type={'link'} className="tab-item">
发布需求 {i.label}
</Button> </Button>
<Button type={'primary'} className="action-item"> </QrcodePopover>
加盟入驻 ))}
</Button> </div>
{!!userInfo?.id && (
<div className="nav-user">
<Dropdown
overlayStyle={{ textAlign: 'center' }}
menu={{ items }}
placement="bottomRight"
arrow
>
<div
className="user-avatar"
style={{ backgroundImage: `url(${userInfo?.userImg})` }}
></div>
</Dropdown>
</div>
)}
<div className="nav-action">
{!userInfo?.id && (
<Button
type={'primary'}
className="action-item"
onClick={handleLogin}
>
登录
</Button>
)}
<QrcodePopover path={'page-service/service-flyer/index'}>
<Button type={'primary'} className="action-item">
发布任务
</Button>
</QrcodePopover>
<QrcodePopover path={'page-identity/identity-list/index'}>
<Button type={'primary'} className="action-item">
加盟入驻
</Button>
</QrcodePopover>
</div>
</div> </div>
</div> </div>
</div> </HeaderWrap>
</HeaderWrap> {placeholder && (
<div
className="header-wrap"
style={{ width: '100%', height: '3rem' }}
></div>
)}
</>
); );
}; };
......
...@@ -27,7 +27,7 @@ export const HeaderWrap = styled.div` ...@@ -27,7 +27,7 @@ export const HeaderWrap = styled.div`
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
.location-icon { .location-icon {
margin: 0 0 0.2rem 0; margin: 0 0.5rem 0 0;
} }
.location-address, .location-address,
.location-hello { .location-hello {
...@@ -38,6 +38,15 @@ export const HeaderWrap = styled.div` ...@@ -38,6 +38,15 @@ export const HeaderWrap = styled.div`
.location-hello { .location-hello {
width: 8rem; width: 8rem;
} }
.location-address {
margin: 0 0.25rem 0 0 !important;
}
.location-reload,
.location-address {
width: auto;
margin: 0 0.5rem 0 0;
padding: 0;
}
} }
.header-nav { .header-nav {
display: flex; display: flex;
...@@ -50,6 +59,7 @@ export const HeaderWrap = styled.div` ...@@ -50,6 +59,7 @@ export const HeaderWrap = styled.div`
justify-content: flex-end; justify-content: flex-end;
} }
.nav-tab { .nav-tab {
margin-right: 0.5rem;
.tab-item { .tab-item {
//font-size: 0.67rem; //font-size: 0.67rem;
font-weight: 500; font-weight: 500;
...@@ -62,11 +72,26 @@ export const HeaderWrap = styled.div` ...@@ -62,11 +72,26 @@ export const HeaderWrap = styled.div`
} }
} }
.nav-action { .nav-action {
margin-left: 1rem; margin-left: 0.5rem;
.action-item { .action-item {
margin-left: 0.33rem; margin-left: 0.33rem;
} }
} }
.nav-user {
.user-avatar {
width: 2rem;
height: 2rem;
border-radius: 50%;
//background: #cdcdcd;
border: 1px solid #ffffff;
background-size: cover;
background-position: center;
&:active,
&:hover {
filter: brightness(0.9);
}
}
}
} }
// 媒体查询 // 媒体查询
@media screen and (min-width: 1600px) { @media screen and (min-width: 1600px) {
......
import React, { useState } from 'react'; import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import ContentView from '@/components/layout/content'; import ContentView from '@/components/layout/content';
import FooterView from '@/components/layout/footer'; import FooterView from '@/components/layout/footer';
import HeaderView from '@/components/layout/header'; import HeaderView from '@/components/layout/header';
import { LayoutWrap } from '@/components/layout/styled'; import { LayoutWrap } from '@/components/layout/styled';
import LoginModalView from '@/components/loginModal';
import QrcodeModalView from '@/components/qrcodeModal'; import QrcodeModalView from '@/components/qrcodeModal';
import ToastModalView from '@/components/toastModal';
import { RootState } from '@/store';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
const LayoutView = ({ children }: { children?: React.ReactNode }) => { const LayoutView: React.FC<{
// 打开二维码弹窗 children?: React.ReactNode;
const [qrcodeShow, setQrcodeShow] = useState<boolean>(false); placeholder?: boolean;
}> = ({ children, placeholder }) => {
// store
const dispatch = useDispatch();
// system
const globalData = useSelector(
(state: RootState) => state.globalData,
) as GlobalDataState;
// 关闭弹窗
const handleModalClose = () => {
dispatch(
setGlobalData({
loginModalVisible: false,
qrcodeModalVisible: false,
toastModalVisible: false,
}),
);
};
return ( return (
<div className={'animate__animated animate__faster animate__fadeIn'}> <LayoutWrap>
<LayoutWrap> <div
<div onClick={() => {
onClick={() => { // setQrcodeShow(!qrcodeShow);
setQrcodeShow(!qrcodeShow); }}
}} >
> {placeholder || placeholder === undefined ? (
<HeaderView></HeaderView> <HeaderView placeholder={true}></HeaderView>
<ContentView>{children}</ContentView> ) : (
<FooterView></FooterView> <HeaderView placeholder={false}></HeaderView>
</div> )}
<QrcodeModalView <ContentView>{children}</ContentView>
open={qrcodeShow} <FooterView></FooterView>
onCancel={() => setQrcodeShow(false)} </div>
/> {/* 登录弹窗 */}
</LayoutWrap> <LoginModalView
</div> open={globalData?.loginModalVisible}
onCancel={handleModalClose}
/>
{/* 功能正在完善中 */}
<QrcodeModalView
open={globalData?.qrcodeModalVisible}
onCancel={handleModalClose}
/>
{/* 功能正在完善中 */}
<ToastModalView
open={globalData?.toastModalVisible}
onCancel={handleModalClose}
/>
</LayoutWrap>
); );
}; };
......
import React, { useEffect, useState } from 'react';
import { ReloadOutlined } from '@ant-design/icons';
import { message, Modal } from 'antd';
import Cookies from 'js-cookie';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { CommonAPI } from '@/api';
import { RootState } from '@/store';
import { GlobalDataState } from '@/store/module/globalData';
import { setSystem } from '@/store/module/system';
import { setUserInfo } from '@/store/module/userInfo';
export const LoginModalWrap = styled.div`
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
box-sizing: border-box;
padding: 0.68rem 0;
.qrcode {
position: relative;
width: 10.68rem;
height: 10.68rem;
margin: 1.5rem 0 1rem 0;
//background-image: url('https://file.iuav.com/file/sharefly-qrcode-wx.jpg');
//background-size: 100% 100%;
//background-size: cover;
//background-position: center;
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
.shadow {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
flex-direction: column;
background: rgba(0, 0, 0, 0.68);
.refresh {
position: relative;
width: 3rem;
height: 3rem;
border-radius: 50%;
background: #fff;
margin-bottom: 1rem;
&:active {
filter: brightness(0.9);
transform: rotate(360deg);
transition: all 0.3s ease-in-out;
}
}
.text {
color: #fff;
}
}
}
.title {
color: #222;
font-size: 16px;
font-weight: bold;
}
.text {
color: #333;
font-size: 12px;
line-height: 20px;
text-align: center;
}
.action {
color: #999;
font-size: 12px;
line-height: 20px;
text-align: center;
margin: 1rem 0;
text-decoration: underline;
cursor: pointer;
&:hover,
&:active {
color: #ff552d;
}
}
`;
// 定时器暂存
let timer: NodeJS.Timer;
const LoginModalView = ({
open,
onCancel,
}: {
open: boolean;
onCancel: () => void;
}) => {
// system
const globalData = useSelector(
(state: RootState) => state.globalData,
) as GlobalDataState;
// store
const dispatch = useDispatch();
// 获取小程序二维码唯一标识
const [randomLoginCode, setRandomLoginCode] = useState<string>(
`${parseInt(String(Math.random() * 10001), 10)}`,
);
// 二维码是否过期
const [qrCodeExpired, setQrCodeExpired] = useState<boolean>(false);
// 登录二维码的地址
const [qrCodeData, setQrCodeData] = useState<string>();
// 获取登录二维码
const getQrcodeLogin = async () => {
// 获取二维码
const res = await CommonAPI.getAppletQRCode({
page: 'page-identity/identity-empower/index',
scene: `randomLoginCode=${randomLoginCode}&port=0`,
});
if (res && res.code === '200') {
if (!res.result) {
message.warning('获取登录二维码失败');
return;
}
// 设置当前登录的二维码
setQrCodeData(`data:image/png;base64,${res.result}`);
// 设置二维码倒计时
setQrCodeExpiredTimer();
}
};
// 跳转管理平台
const handleBackEnd = () => {
window.open('https://admin.iuav.com');
};
// 刷新二维码
const handleRefresh = () => {
setRandomLoginCode(`${parseInt(String(Math.random() * 10001), 10)}`);
getQrcodeLogin().then(() => {
setQrCodeExpired(false);
});
};
// 定时器设置二维码过期
const setQrCodeExpiredTimer = () => {
setTimeout(
() => {
setQrCodeExpired(true);
// 关闭定时器
if (timer) clearInterval(timer);
},
1000 * 60 * 5,
);
};
// 获取登录信息
const getLoginInfo = async () => {
if (!randomLoginCode) return;
const res = await CommonAPI.getLoginInfo({
randomLoginCode,
});
// console.log('获取登录信息 --->', res);
if (res && res.code === '200') {
// 关闭定时器
if (timer) clearInterval(timer);
// 刷新二维码
setQrCodeExpired(true);
// 关闭弹窗
onCancel?.();
// 设置用户信息
dispatch(setUserInfo(res.result));
// 设置token
dispatch(setSystem({ token: res.result.token }));
// 设置token
Cookies.set('SHAREFLY-WEB-TOKEN', res.result.token);
message.success('登录成功');
// 刷新当前页面
window.location.reload();
}
};
// 设置定时器轮询获取信息
const setLoginInfoTimer = () => {
timer = setInterval(async () => {
await getLoginInfo();
}, 2500);
};
// 关闭弹窗
const handleClose = () => {
onCancel?.();
// 关闭定时器
if (timer) clearInterval(timer);
};
// 组件挂载
useEffect(() => {
if (!open) {
// 关闭定时器
if (timer) clearInterval(timer);
return;
}
// 获取二维码
getQrcodeLogin().then(() => {
setQrCodeExpired(false);
setLoginInfoTimer();
});
}, [open]);
return (
<Modal open={open} footer={null} onCancel={handleClose}>
<LoginModalWrap>
<div className="title">
{globalData?.loginModalTitle || `微信扫码登录`}
</div>
<div className="qrcode">
{qrCodeData && (
<img src={qrCodeData} alt="登录二维码" className="image" />
)}
{qrCodeExpired && (
<div className="shadow flex-center animate__animated animate__fast animate__fadeIn">
<div className="refresh flex-center" onClick={handleRefresh}>
<ReloadOutlined style={{ fontSize: '20px' }} />
</div>
<div className="text">二维码已过期,请重新扫描</div>
</div>
)}
</div>
<div className="text">微信扫一扫,打开小程序</div>
<div className="text">即可登录/注册</div>
<div
className="action"
onClick={handleBackEnd}
style={{ visibility: 'hidden' }}
>
管理平台登录 {'>'}
</div>
<div className="text">「云享飞,让天空为世界所用」</div>
</LoginModalWrap>
</Modal>
);
};
export default LoginModalView;
import React, { useEffect, useState } from 'react';
import { Empty } from 'antd';
import { InterDataType } from '@/api/interface';
import {
GetAppCategoryInfo,
QueryGoodsInfoByCategorySub,
} from '@/api/interface/mall';
import { MallAPI } from '@/api/modules/mall';
import BreadcrumbView from '@/components/breadcrumb';
import CategorySelectView, { CategoryType } from '@/components/categorySelect';
import LayoutView from '@/components/layout';
import ProductItemView from '@/components/productItem';
import ProductListView from '@/components/productList';
import { MallWrap } from '@/pages/mall/styled';
// 分类列表类型
type CategoryListType = InterDataType<GetAppCategoryInfo>;
// 商品列表类型
type GoodsInfoListType = InterDataType<QueryGoodsInfoByCategorySub>;
const MallView: React.FC<{
categoryList: CategoryListType;
}> = (props) => {
// 分类列表
const [categoryList, setCategoryList] = useState<CategoryType>([]);
// 商品列表
const [goodsInfoList, setGoodsInfoList] = useState<GoodsInfoListType>([]);
// 分页数据
const [pagination, setPagination] = useState({
pageNo: 1,
pageSize: 18,
totalCount: 0,
});
// 转换分类列表
const getCategoryList = () => {
setCategoryList(
props?.categoryList
?.filter((i) => i.subDTOList?.length)
?.map((i) => ({
value: i.id,
label: i.name,
children: i.subDTOList?.map((n) => ({ value: n.id, label: n.name })),
})),
);
};
// 获取商品列表
const getGoodsInfoByCategorySub = async (list: number[]) => {
const res = await MallAPI.queryGoodsInfoByCategorySub(list);
if (res && res.code === '200') {
setGoodsInfoList(res.result || []);
setPagination({
...pagination,
pageNo: 1,
totalCount: res.result?.length || 0,
});
// console.log('商品列表 --->', res.result);
}
};
// 根据分页数据返回商品列表
const getGoodsInfoListByPage = () => {
const { pageNo, pageSize, totalCount } = pagination;
return goodsInfoList?.slice(
(pageNo - 1) * pageSize,
pageNo * (pageSize < totalCount ? pageSize : totalCount),
);
};
// 翻页回调
const handlePageChange = (pageNo: number, pageSize: number) => {
setPagination({ ...pagination, pageNo, pageSize });
};
// 组件挂载
useEffect(() => {
if (!props) return;
getCategoryList();
// console.log('执行到此处12312 --->', props);
}, [props]);
return (
<>
<LayoutView placeholder={true}>
<MallWrap>
{/* 面包屑 */}
<BreadcrumbView />
{/* 类型筛选 */}
<CategorySelectView
list={categoryList}
onSecond={getGoodsInfoByCategorySub}
/>
{/* 产品列表 */}
<ProductListView pagination={pagination} onChange={handlePageChange}>
{getGoodsInfoListByPage()?.length ? (
getGoodsInfoListByPage()?.map((i, j) => (
<ProductItemView key={j} detail={i} />
))
) : (
<div className="list-empty flex-center">
<Empty />
</div>
)}
</ProductListView>
</MallWrap>
</LayoutView>
</>
);
};
export default MallView;
...@@ -2,7 +2,8 @@ import { FC, useEffect } from 'react'; ...@@ -2,7 +2,8 @@ import { FC, useEffect } from 'react';
const MapContainer: FC<{ const MapContainer: FC<{
list: { lat: number; lon: number; name: string }[]; list: { lat: number; lon: number; name: string }[];
}> = ({ list }) => { center?: [number, number];
}> = ({ list, center }) => {
// 地图实例 // 地图实例
let map: any = null; let map: any = null;
// 高德地图 // 高德地图
...@@ -58,7 +59,7 @@ const MapContainer: FC<{ ...@@ -58,7 +59,7 @@ const MapContainer: FC<{
// 设置地图容器id // 设置地图容器id
viewMode: '3D', // 是否为3D地图模式 viewMode: '3D', // 是否为3D地图模式
zoom: 10, // 初始化地图级别 zoom: 10, // 初始化地图级别
center: [119.96043, 30.04885], // 初始化地图中心点位置 center: center || [119.96043, 30.04885], // 初始化地图中心点位置
}); });
// 用户定位 // 用户定位
map.plugin('AMap.Geolocation', () => { map.plugin('AMap.Geolocation', () => {
...@@ -89,7 +90,7 @@ const MapContainer: FC<{ ...@@ -89,7 +90,7 @@ const MapContainer: FC<{
return () => { return () => {
map?.destroy(); map?.destroy();
}; };
}, [list]); }, [list, center]);
return ( return (
<div <div
id="container" id="container"
......
import React, { useEffect, useState } from 'react';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, InputNumber, Space } from 'antd';
import { InputNumberProps } from 'antd/es/input-number';
const NumberBox: React.FC<InputNumberProps> = (props) => {
// 使用state hooks来定义value和setValue
const [value, setValue] = useState<number>(1);
// 定义增加和减少的函数
const handleIncrement = () => {
// 不能大于最大
if (props?.max && value >= Number(props?.max)) return;
props?.onChange?.(value + 1);
setValue(value + 1);
};
const handleDecrement = () => {
// 不能低于最小
if (props?.min && value <= Number(props?.min)) return;
props?.onChange?.(value - 1);
setValue(value - 1);
};
// 监听value的变化
useEffect(() => {
if (!props?.value) return;
setValue(Number(props?.value) || 1);
}, [props?.value]);
return (
<Space.Compact block style={{ width: 'auto' }}>
<Button icon={<MinusOutlined />} onClick={handleDecrement} />
<InputNumber
{...props}
controls={false}
style={{ width: '42px' }}
value={value}
/>
<Button icon={<PlusOutlined />} onClick={handleIncrement} />
</Space.Compact>
);
};
export default NumberBox;
import React, { useEffect, useState } from 'react';
import { Checkbox } from 'antd';
import { CheckboxValueType } from 'antd/es/checkbox/Group';
import Big from 'big.js';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { UserPayWalletInfoType } from '@/api/interface/wallet';
import { SystemState } from '@/store/module/system';
import { isNone } from '@/utils';
import getCurrentUserPayWalletInfo from '@/utils/getCurrentUserPayWalletInfo';
import { formatMoney } from '@/utils/money';
// 钱包类型
type WalletType = InterDataType<UserPayWalletInfoType>;
const PaymentCheckout: React.FC<{ cost: number }> = ({ cost }) => {
// system
const system = useSelector((state: any) => state.system) as SystemState;
// 选择的支付方式
const [paymentTypeValue, setPaymentTypeValue] = useState<CheckboxValueType[]>(
[],
);
// 钱包详情
const [walletInfo, setWalletInfo] = useState<WalletType>();
// 获取钱包详情
const getUserPayWalletInfo = async () => {
const res = await getCurrentUserPayWalletInfo();
if (res) setWalletInfo(res);
};
// 获取用户最多可抵扣的云享金余额
const getOrderPriceCashMax = () => {
// 当前的云享金余额
const cash = walletInfo?.cashAmt || 0;
// 如果用户的钱包金额大于等于订单金额 则可抵扣金额为订单金额
return cash >= (cost || 0) ? cost || 0 : cash;
};
// 获取用户最多可抵扣的薪水余额
const getOrderPriceSalaryMax = () => {
// 当前的薪水余额
const salary = walletInfo?.salaryAmt || 0;
// 如果用户的钱包金额大于等于订单金额 则可抵扣金额为订单金额
return salary >= (cost || 0) ? cost || 0 : salary;
};
// 获取用户最多可抵扣的总的金额
const getOrderPriceTotalMax = () => {
const cash = Big(getOrderPriceCashMax());
const salary = Big(getOrderPriceSalaryMax());
return cash.add(salary).toNumber();
};
// 获取用户的云享金余额
const getOrderPriceCash = () => {
// 云享金是否选中
const selectCash = paymentTypeValue?.includes(1);
// 余额是否选中
const selectSalary = paymentTypeValue?.includes(2);
// 总的抵扣金额
const total =
getOrderPriceTotalMax() > cost ? cost : getOrderPriceTotalMax();
// 只选其中一个的情况
if (!selectCash && selectSalary) {
return Big(total).minus(Big(getOrderPriceSalaryMax())).toNumber();
}
// 返回结果
return getOrderPriceCashMax();
};
// 获取用户的薪水余额
const getOrderPriceSalary = () => {
// 云享金是否选中
const selectCash = paymentTypeValue?.includes(1);
// 余额是否选中
const selectSalary = paymentTypeValue?.includes(2);
// 总的抵扣金额
const total =
getOrderPriceTotalMax() > cost ? cost : getOrderPriceTotalMax();
// 只选其中一个的情况
if (!selectCash && selectSalary) {
return getOrderPriceSalaryMax();
}
// 返回结果
return Big(total).minus(Big(getOrderPriceCashMax())).toNumber();
};
// 根据用户钱包和需要支付的金额设置默认选中
const setDefaultSelect = () => {
// 必须有金额时才继续执行
if (isNone(walletInfo?.cashAmt) || isNone(walletInfo?.salaryAmt)) {
return;
}
// 云享金
const cash = Big(walletInfo?.cashAmt || 0).toNumber();
// 余额
const salary = Big(walletInfo?.salaryAmt || 0).toNumber();
// 如果需要支付金额为零 则后面的判断都不执行
if (!cost || cost <= 0) {
setPaymentTypeValue([]);
return;
}
// 如果云享金大于等于订单金额 则默认选中云享金
if (cash >= cost) {
setPaymentTypeValue([1]);
return;
}
// 如果余额大于等于订单金额 则默认选中余额
if (salary >= cost) {
setPaymentTypeValue([2]);
return;
}
// 如果云享金有钱
if (cash > 0 && salary === 0) {
setPaymentTypeValue([1]);
return;
}
// 如果余额有钱
if (cash === 0 && salary > 0) {
setPaymentTypeValue([2]);
return;
}
// 如果两者都有余额
if (cash > 0 && salary > 0) {
setPaymentTypeValue([1, 2]);
}
};
// 组件挂载
useEffect(() => {
if (!system?.token) return;
getUserPayWalletInfo().then();
if (!cost) return;
setDefaultSelect();
}, [cost]);
return (
<PaymentCheckoutWrap>
<Checkbox.Group value={paymentTypeValue} onChange={setPaymentTypeValue}>
<div className="payment-item flex-start">
<img
src="https://file.iuav.com/file/checkout_icon_01.png"
alt="云享金"
className="icon"
/>
<div className="title">云享金</div>
<div className="num">-可抵扣¥{formatMoney(getOrderPriceCash())}</div>
<div className="checkbox">
<Checkbox value={1} />
</div>
</div>
<div className="payment-item flex-start">
<img
src="https://file.iuav.com/file/checkout_icon_02.png"
alt="余额"
className="icon"
/>
<div className="title">余额</div>
<div className="num">
-可抵扣¥{formatMoney(getOrderPriceSalary())}
</div>
<div className="checkbox">
<Checkbox value={2} />
</div>
</div>
</Checkbox.Group>
</PaymentCheckoutWrap>
);
};
export default PaymentCheckout;
// 样式
const PaymentCheckoutWrap = styled.div`
position: relative;
width: 18.6rem;
.payment-item {
position: relative;
width: 100%;
&:not(:last-child) {
margin-bottom: 0.66rem;
}
.icon {
width: 1.25rem;
height: 1.25rem;
}
.title,
.num {
margin-left: 0.5rem;
}
.num {
color: #ff4600;
}
.checkbox {
position: absolute;
top: 0;
right: 0;
}
}
`;
import React from 'react';
import { PhoneOutlined, ShopOutlined } from '@ant-design/icons';
import { Button, Rate } from 'antd';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import {
AppMallGoodsDetails,
GetCompanyInfoByBUId,
} from '@/api/interface/mall';
import QrcodePopover from '@/components/qrcodePopover';
// 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>;
// 商城详情类型
type StoreType = InterDataType<GetCompanyInfoByBUId>;
const ProductStoreView: React.FC<{ detail: DetailType; store: StoreType }> = ({
store,
}) => {
return (
<ProductStoreWrap>
<div className="store-card flex-start">
<img
className="image"
src={store?.brandLogo}
alt={store?.companyName}
/>
<div className="card-content">
<div className="title">{store?.companyName}</div>
<div className="star flex-start">
<div className="tag select-none">店铺星级</div>
<Rate allowHalf defaultValue={5} style={{ fontSize: '10px' }} />
</div>
<div className="text two-line-ellipsis" title={store?.content}>
{store?.content}
</div>
</div>
</div>
<div className="store-item flex-start">
<div className="item-label">地址:</div>
<div className="item-value">{store?.address}</div>
</div>
<div className="store-item flex-start">
<div className="item-label">电话:</div>
<div className="item-value">{store?.phoneNum || '18626051369'}</div>
</div>
<div className="store-action flex-start">
<QrcodePopover
path="page-service/service-store/index"
scene={`id=${Number(store?.id)}`}
>
<Button className="action-item flex-start">
<ShopOutlined style={{ color: '#FF552D' }} />
<div className="text">进店逛逛</div>
</Button>
</QrcodePopover>
<QrcodePopover
path="page-service/service-store/index"
scene={`id=${Number(store?.id)}`}
>
<Button className="action-item flex-start">
<PhoneOutlined
style={{ color: '#FF552D', transform: 'rotateY(180deg)' }}
/>
<div className="text">联系方式</div>
</Button>
</QrcodePopover>
</div>
</ProductStoreWrap>
);
};
export default ProductStoreView;
// 样式
const ProductStoreWrap = styled.div`
position: relative;
width: calc((100% - 0.83rem) / 10 * 2.5);
min-height: 18rem;
//height: 100%;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
box-sizing: border-box;
padding: 1rem;
.store-card {
width: 100%;
min-height: 8em;
background: linear-gradient(270deg, #5f5f5f 0%, #060606 100%);
border-radius: 0.33rem;
box-sizing: border-box;
padding: 0.58rem 1rem;
margin-bottom: 1rem;
align-items: flex-start;
.image {
width: 3.25rem;
height: 3.25rem;
border: 0.02rem solid #e3e3e3;
margin-right: 0.67rem;
}
.card-content {
position: relative;
width: calc(100% - 3.25rem - 0.67rem);
.title {
font-weight: 500;
color: #ffffff;
}
.star {
margin-bottom: 0.5rem;
.tag {
position: relative;
height: 1.2rem;
line-height: 1rem;
background: #fff3ee;
border-radius: 0.13rem;
border: 0.02rem solid #ff552d;
box-sizing: border-box;
padding: 0 0.33rem;
text-align: center;
font-size: 10px;
color: #ff552d;
margin-right: 0.25rem;
transform: scale(0.8);
}
}
.text {
font-size: 12px;
font-weight: 400;
color: rgba(255, 255, 255, 0.8);
}
}
}
.store-item {
align-items: flex-start;
flex-wrap: nowrap;
margin-bottom: 1rem;
.item-label {
width: 3rem;
color: #666666;
}
.item-value {
width: calc(100% - 3rem);
font-size: 12px;
font-weight: 400;
color: #666666;
}
}
.store-action {
position: relative;
width: 100%;
.action-item {
position: relative;
background: #ffffff;
box-sizing: border-box;
padding: 0 1rem;
border: 0.04rem solid #a8a8a8;
cursor: pointer;
margin-right: 0.83rem;
.text {
color: #a8a8a8;
font-weight: 400;
font-size: 12px;
margin-left: 0.33rem;
}
&:last-child {
border: 0.04rem solid #ff552d;
.text {
color: #ff552d;
}
}
&:hover {
filter: brightness(0.95);
}
}
}
`;
import React, { useState } from 'react';
import styled from 'styled-components';
import {
FreeMode,
Navigation,
Thumbs,
Autoplay,
Mousewheel,
} from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import 'swiper/css/free-mode';
import 'swiper/css/navigation';
import 'swiper/css/thumbs';
const ProductSwiperWrap = styled.div`
position: relative;
width: 200%;
height: 200%;
box-sizing: border-box;
transform: scale(0.5) translateX(-50%) translateY(-50%);
.swiper-main {
position: relative;
width: 100%;
height: calc(100% - 9rem);
margin-bottom: 0.5rem;
.swiper-slide {
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
.swiper-second {
position: relative;
width: 100%;
height: 9rem;
.swiper-slide {
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
`;
const ProductSwiperView: React.FC<{
list: { id: number; url: string }[];
}> = ({ list }) => {
const [thumbsSwiper, setThumbsSwiper] = useState(null);
// 图片列表
const SwiperListArr = list?.map((i, j) => (
<SwiperSlide key={j}>
<img src={i.url} alt={'图片'} />
</SwiperSlide>
));
return (
<ProductSwiperWrap>
<Swiper
className="swiper-main"
style={
{
'--swiper-navigation-color': '#fff',
'--swiper-pagination-color': '#fff',
} as any
}
loop={true}
// spaceBetween={10}
navigation={true}
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs, Autoplay, Mousewheel]}
autoplay={{
delay: 5000,
}}
mousewheel={true}
grabCursor={true}
>
{SwiperListArr}
</Swiper>
<Swiper
className="swiper-second"
// @ts-ignore
onSwiper={setThumbsSwiper}
loop={true}
spaceBetween={10}
slidesPerView={list?.length > 5 ? 5 : list?.length}
slidesPerGroupAuto={true}
freeMode={true}
watchSlidesProgress={true}
modules={[FreeMode, Navigation, Thumbs]}
grabCursor={true}
>
{SwiperListArr}
</Swiper>
</ProductSwiperWrap>
);
};
export default ProductSwiperView;
import React from 'react';
import { PropertySafetyFilled, ShoppingCartOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { QueryGoodsInfoByCategorySub } from '@/api/interface/mall';
const ProductItemWrap = styled.div`
position: relative;
box-sizing: border-box;
width: calc((100% - (0.83rem * 5)) / 6);
height: 16rem;
border: 0.02rem solid #e3e3e3;
margin: 0 0.83rem 0.83rem 0;
padding: 0.67rem;
cursor: pointer;
&:nth-child(6n) {
margin-right: 0;
}
&:active,
&:hover {
background: #fff9f6;
border: 0.04rem solid #ff7a3e;
}
.product-image {
position: relative;
width: 100%;
height: 8rem;
box-sizing: border-box;
margin-bottom: 0.5rem;
.image {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.product-title {
width: 100%;
font-size: 13px;
font-weight: 500;
color: #333333;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
margin-bottom: 0.33rem;
min-height: 2rem;
}
.product-desc {
position: relative;
width: 100%;
font-size: 12px;
font-weight: 400;
color: #999999;
margin-bottom: 0.1rem;
.label {
width: 60%;
}
.text {
width: 40%;
text-align: right;
}
}
.product-money {
font-size: 16px;
font-weight: 500;
color: #ff1b1b;
.label {
font-size: 12px;
font-weight: bold;
}
}
.product-store {
position: absolute;
left: 0.67rem;
bottom: 0.67rem;
.title {
width: 6rem;
font-size: 12px;
font-weight: 400;
color: #666666;
margin-left: 0.25rem;
}
}
.product-cart {
position: absolute;
right: 0.67rem;
bottom: 0.67rem;
width: 2rem;
height: 2rem;
background: #ff8b2e;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&:active,
&:hover {
filter: brightness(0.9);
}
}
`;
// 商品详情类型
type GoodsInfoListType = InterDataType<QueryGoodsInfoByCategorySub>[0];
const ProductItemView: React.FC<{
detail: GoodsInfoListType;
}> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// 获取最低价格
const getLowerPrice = (item: GoodsInfoListType) => {
const price =
item.priceStock?.reduce((a: any, b: any) =>
a.salePrice < b.salePrice ? a : b,
).salePrice || 0;
return price.toLocaleString();
};
// 跳转商品详情
const handleDetail = () => {
router.push(`/mall/product/${detail?.id}`).then();
};
return (
<ProductItemWrap onClick={handleDetail}>
<div className="product-image">
<img
className="image"
src={`${detail?.resourcesList?.at(0)
?.url}?x-oss-process=image/quality,q_20`}
alt={detail?.tradeName}
/>
</div>
<div className="product-title">{detail?.tradeName}</div>
<div className="product-desc flex-between">
<div className="label text-ellipsis">{detail?.description}</div>
<div className="text text-ellipsis">成交{detail?.id}件</div>
</div>
<div className="product-money">
{detail?.priceShow ? (
<>
<span className="label">¥</span>
<span className="num">{getLowerPrice(detail)}</span>
</>
) : (
<span className="label">咨询报价</span>
)}
</div>
<div className="product-store flex-start">
<PropertySafetyFilled style={{ color: '#FF552D' }} />
<div className="title text-ellipsis" title={detail?.companyName}>
{detail?.companyName}
</div>
</div>
<div className="product-cart">
<ShoppingCartOutlined style={{ color: '#ffffff', fontSize: '16px' }} />
</div>
</ProductItemWrap>
);
};
export default ProductItemView;
import React from 'react';
import { Pagination } from 'antd';
import styled from 'styled-components';
// 样式
const ProductListWrap = styled.div`
position: relative;
width: 100%;
.mall-list {
position: relative;
width: 100%;
min-height: 60vh;
flex-wrap: wrap;
align-items: flex-start;
}
.list-empty {
position: relative;
width: 100%;
height: 60vh;
}
.mall-pagination {
position: relative;
width: 100%;
height: 4rem;
box-sizing: border-box;
margin-bottom: 1rem;
}
`;
// 分页数据类型
type PaginationProps = {
pageNo: number;
pageSize: number;
totalCount: number;
};
const ProductListView: React.FC<{
children: React.ReactNode;
pagination: PaginationProps;
onChange: (pageNo: number, pageSize: number) => void;
}> = ({ children, pagination, onChange }) => {
return (
<ProductListWrap>
<div className="mall-list flex-start">{children}</div>
<div className="mall-pagination flex-end">
<Pagination
showSizeChanger
onChange={onChange}
defaultPageSize={pagination.pageSize}
current={pagination.pageNo}
total={pagination.totalCount}
showTotal={(total) => `共 ${total} 条`}
pageSizeOptions={['6', '12', '18', '36', '72']}
/>
</div>
</ProductListWrap>
);
};
export default ProductListView;
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Modal } from 'antd'; import { message, Modal } from 'antd';
import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { CommonAPI } from '@/api';
import { RootState } from '@/store';
import { GlobalDataState } from '@/store/module/globalData';
const QrcodeModalView = ({
open,
onCancel,
}: {
open: boolean;
onCancel: () => void;
}) => {
// system
const globalData = useSelector(
(state: RootState) => state.globalData,
) as GlobalDataState;
// 登录二维码的地址
const [qrCodeData, setQrCodeData] = useState<string>();
// 获取登录二维码
const getQrcodeLogin = async () => {
// 获取二维码
const res = await CommonAPI.getAppletQRCode({
page: globalData?.qrcodeModalPath || 'pages/welcome/index',
scene: globalData?.qrcodeModalScene || 'type=share',
});
if (res && res.code === '200') {
if (!res.result) {
message.warning('获取登录二维码失败');
return;
}
// 设置当前登录的二维码
setQrCodeData(`data:image/png;base64,${res.result}`);
}
};
// 组件挂载
useEffect(() => {
if (!open) return;
getQrcodeLogin().then();
}, [open]);
return (
<Modal
title="提示"
open={open}
footer={null}
onCancel={onCancel}
width={350}
>
<QrcodeModalWrap>
<div className="qrcode">
{qrCodeData && (
<img
className="animate__animated animate__faster animate__fadeIn"
style={{ width: '100%', height: '100%' }}
src={qrCodeData}
alt="云享飞小程序"
/>
)}
</div>
<div className="title">请前往小程序继续操作</div>
</QrcodeModalWrap>
</Modal>
);
};
export default QrcodeModalView;
export const QrcodeModalWrap = styled.div` export const QrcodeModalWrap = styled.div`
position: relative; position: relative;
...@@ -14,11 +78,11 @@ export const QrcodeModalWrap = styled.div` ...@@ -14,11 +78,11 @@ export const QrcodeModalWrap = styled.div`
padding-top: 1rem; padding-top: 1rem;
.qrcode { .qrcode {
width: 12.68rem; width: 12.68rem;
height: 15.68rem; height: 12.68rem;
background-image: url('https://file.iuav.com/file/sharefly-qrcode-wx.jpg'); //background-image: url('https://file.iuav.com/file/sharefly-qrcode-wx.jpg');
//background-size: 100% 100%; //background-size: 100% 100%;
background-size: cover; //background-size: cover;
background-position: center; //background-position: center;
} }
.title { .title {
color: #000; color: #000;
...@@ -27,21 +91,3 @@ export const QrcodeModalWrap = styled.div` ...@@ -27,21 +91,3 @@ export const QrcodeModalWrap = styled.div`
font-weight: bold; font-weight: bold;
} }
`; `;
const QrcodeModalView = ({
open,
onCancel,
}: {
open: boolean;
onCancel: () => void;
}) => {
return (
<Modal title="提示" open={open} footer={null} onCancel={onCancel}>
<QrcodeModalWrap>
<div className="qrcode"></div>
<div className="title">功能正在完善中</div>
<div className="title">请前往小程序以获得更好体验</div>
</QrcodeModalWrap>
</Modal>
);
};
export default QrcodeModalView;
import React, { useState } from 'react';
import { Button, message, Popover } from 'antd';
import { PopoverProps } from 'antd/es/popover';
import { CommonAPI } from '@/api';
const QrcodePopover: React.FC<{
children?: React.ReactNode;
text?: string;
path?: string;
scene?: string;
placement?: PopoverProps['placement'];
}> = ({ children, text, path, scene, placement }) => {
QrcodePopover.defaultProps = {
path: 'pages/welcome/index',
scene: 'type=share',
placement: 'bottomRight',
};
// 登录二维码的地址
const [qrCodeData, setQrCodeData] = useState<string>();
// 获取登录二维码
const getQrcodeLogin = async () => {
// 获取二维码
const res = await CommonAPI.getAppletQRCode({
page: path || 'pages/welcome/index',
scene: scene || 'type=share',
});
if (res && res.code === '200') {
if (!res.result) {
message.warning('获取登录二维码失败');
return;
}
// 设置当前登录的二维码
setQrCodeData(`data:image/png;base64,${res.result}`);
}
};
// 开启回调
const handleOpenChange = async (e: boolean) => {
if (e) {
// 如果获取成功就不再获取
if (qrCodeData) return;
// 如果开启,就获取二维码
await getQrcodeLogin();
}
};
const content = (
<div className="flex-center" style={{ flexDirection: 'column' }}>
<div style={{ width: '12.5rem', height: '12.5rem' }}>
{qrCodeData && (
<img
className="animate__animated animate__faster animate__fadeIn"
style={{ width: '12.5rem', height: '12.5rem' }}
src={qrCodeData}
alt="云享飞小程序"
/>
)}
</div>
<div style={{ marginTop: '1rem' }}>请前往小程序进行继续操作</div>
</div>
);
return (
<Popover
content={content}
trigger={'click'}
placement={placement || 'bottomRight'}
onOpenChange={handleOpenChange}
style={{ display: 'none' }}
>
<div style={{ display: 'none' }}>
{children && children}
{text && (
<Button
type="link"
style={{ color: '#3366cc', padding: 0, margin: 0 }}
>
{text}
</Button>
)}
</div>
</Popover>
);
};
export default QrcodePopover;
import React, { useEffect, useState } from 'react';
import { Empty } from 'antd';
import styled from 'styled-components';
import { InterListType, InterReqType } from '@/api/interface';
import {
GetIndustryListPagesType,
GetListAPPCompanyInspectionPageType,
} from '@/api/interface/service';
import { ServiceAPI } from '@/api/modules/service';
import BreadcrumbView from '@/components/breadcrumb';
import CategorySelectView, { CategoryType } from '@/components/categorySelect';
import LayoutView from '@/components/layout';
import ProductListView from '@/components/productList';
import ServiceItemView from '@/components/serviceItem';
// 分类列表类型
type CategoryListType = InterListType<GetIndustryListPagesType>;
// 请求参数类型
type ReqType = InterReqType<GetListAPPCompanyInspectionPageType>;
// 服务列表
type ListType = InterListType<GetListAPPCompanyInspectionPageType>;
const ServiceView: React.FC<{
categoryList: CategoryListType;
}> = (props) => {
// 分类列表
const [categoryList, setCategoryList] = useState<CategoryType>([]);
// 转换分类列表
const getCategoryList = () => {
setCategoryList(
props?.categoryList
?.filter((i) => i?.inspectionDTOS?.length)
?.map((i) => ({
value: i.id,
label: i.typeName,
children: i?.inspectionDTOS?.map((n) => ({
value: n.id,
label: n.inspectionName,
})),
})),
);
};
// 分页数据
const [pagination, setPagination] = useState({
pageNo: 1,
pageSize: 18,
totalCount: 0,
});
// 服务列表
const [inspectionList, setInspectionList] = useState<ListType>([]);
// 获取服务列表
const getListAPPCompanyInspectionPage = async (data: ReqType) => {
const res = await ServiceAPI.getListAPPCompanyInspectionPage({
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
...data,
});
if (res && res.code === '200') {
const { list, totalCount, pageNo, pageSize } = res.result;
setInspectionList(list || []);
setPagination({
...pagination,
totalCount,
pageNo,
pageSize,
});
}
};
// 分类筛选
const handleSelect = async (e: { main?: number; second?: number[] }) => {
if (e.second?.length && e.second?.length > 1) {
await getListAPPCompanyInspectionPage({ industryTypeId: e?.main });
} else {
await getListAPPCompanyInspectionPage({ inspectionId: e?.second?.[0] });
}
};
// 翻页回调
const handlePageChange = async (pageNo: number, pageSize: number) => {
await getListAPPCompanyInspectionPage({ pageNo, pageSize });
};
// 组件挂载
useEffect(() => {
if (!props) return;
getCategoryList();
}, [props]);
return (
<LayoutView>
<ServiceWrap>
{/* 面包屑 */}
<BreadcrumbView />
{/* 类型筛选 */}
<CategorySelectView
allText="全部服务"
list={categoryList}
isMultiple={false}
onSelect={handleSelect}
/>
{/* 产品列表 */}
<ProductListView pagination={pagination} onChange={handlePageChange}>
{inspectionList?.length ? (
inspectionList?.map((i, j) => (
<ServiceItemView key={j} detail={i} />
))
) : (
<div className="list-empty flex-center">
<Empty />
</div>
)}
</ProductListView>
</ServiceWrap>
</LayoutView>
);
};
export default ServiceView;
export const ServiceWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
`;
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { GetCompanyInspectionById } from '@/api/interface/service';
import ProductSwiperView from '@/components/product-swiper';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
import { UserInfoState } from '@/store/module/userInfo';
import { formatMoney } from '@/utils/money';
// 商品详情类型
type DetailType = InterDataType<GetCompanyInspectionById>;
const ServiceHeadView: React.FC<{ detail: DetailType }> = ({ detail }) => {
// 当前的路由数据
// const router = useRouter();
// store
const dispatch = useDispatch();
// userInfo
const userInfo = useSelector((state: any) => state.userInfo) as UserInfoState;
// globalData
const globalData = useSelector(
(state: any) => state.globalData,
) as GlobalDataState;
// 提交事件
const handleSubmit = async (type: number) => {
// 判断是否登录
if (!userInfo?.id) {
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
return;
}
// 立即购买
if (type === 1) {
dispatch(
setGlobalData({
qrcodeModalVisible: true,
qrcodeModalPath: 'page-service/service-flyer/index',
qrcodeModalScene: `id=${Number(detail?.id)}`,
}),
);
return;
}
// 在线沟通
if (type === 2) {
dispatch(
setGlobalData({
qrcodeModalVisible: true,
qrcodeModalPath: 'page-service/service-detail/index',
qrcodeModalScene: `id=${Number(detail?.id)}`,
}),
);
}
};
// 转换swiper图片
const getSwiperList = () => {
return detail?.inspectionFileDTOS
?.filter((i) => i.fileType !== 1)
?.sort((a, b) => b.first - a.first)
?.map((i) => ({ id: i.id, url: i.fileUrl }));
};
// 获取商品的单位
const getPriceUnit = () => {
const unit = globalData?.priceUnitList?.find(
(i) => i.id === detail?.inspectionPriceUnitId,
);
return unit?.unitName || '次';
};
return (
<ServiceHeadWrap>
<div className="product-swiper">
<ProductSwiperView list={getSwiperList()} />
</div>
<div className="product-content">
<div className="content-title">
【{detail?.industryTypeDTO?.typeName}】
{detail?.inspectionDTO?.inspectionName}
</div>
<div className="content-price flex-start">
<div className="price-label">价格</div>
<div className="price-money">
{detail?.price ? (
<>
<span className="label">¥</span>
<span
className="num"
title={`${formatMoney(detail?.price)}元起每${getPriceUnit()}`}
>
{formatMoney(detail?.price)}
</span>
<span
className="unit text-ellipsis"
title={`${formatMoney(detail?.price)}元起每${getPriceUnit()}`}
>
起/{getPriceUnit()}
</span>
</>
) : (
<span className="label">咨询报价</span>
)}
</div>
</div>
<div className="content-item flex-start">
<div className="item-label">团队</div>
<div className="item-content flex-start">
<img
className="team-label"
src="https://file.iuav.com/file/sharefly-service-label01.png"
alt="专业飞手"
/>
<div className="team-text">飞手已通过认证培训</div>
</div>
</div>
<div className="content-item flex-start item-bottom">
<div className="item-label">服务</div>
<div className="item-content flex-start">
<div className="item-tag">7x24小时服务</div>
<div className="item-tag">已售{Math.floor(detail.id * 2.22)}</div>
</div>
</div>
<div className="content-action flex-start select-none">
<div className="action-item" onClick={() => handleSubmit(2)}>
在线沟通
</div>
<div className="action-item" onClick={() => handleSubmit(1)}>
马上预约
</div>
</div>
</div>
</ServiceHeadWrap>
);
};
export default ServiceHeadView;
// 样式
const ServiceHeadWrap = styled.div`
position: relative;
width: calc((100% - 0.83rem) / 10 * 7.5);
min-height: 28rem;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
box-sizing: border-box;
margin-right: 0.83rem;
display: flex;
align-items: flex-start;
justify-content: flex-start;
padding: 2rem 1rem 2rem 1rem;
.product-swiper {
position: relative;
width: 22rem;
height: 26rem;
box-sizing: border-box;
}
.product-content {
position: relative;
width: calc(100% - 22rem);
height: 100%;
box-sizing: border-box;
padding: 0 0 0 1rem;
//background: lightblue;
.content-title {
width: 100%;
font-size: 24px;
font-weight: 500;
color: #212121;
margin-bottom: 0.71rem;
// 双行省略
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
}
.content-desc {
font-weight: 400;
color: #666666;
margin-bottom: 0.71rem;
}
.content-price {
position: relative;
width: 100%;
background: #f3f3f3;
box-sizing: border-box;
padding: 0.8rem;
margin-bottom: 1rem;
align-items: baseline;
.price-label {
width: 2.5rem;
color: #999999;
text-align: justify;
text-align-last: justify;
margin-right: 1rem;
transform: translateY(-0.1rem);
}
.price-money {
font-size: 24px;
//line-height: 1;
color: #ff6700;
.label {
font-size: 18px;
}
.unit {
font-size: 13px;
color: #999999;
margin-left: 0.5rem;
}
}
}
.content-item {
margin-bottom: 1rem;
padding: 0 0.8rem;
//flex-wrap: nowrap;
.item-label {
width: 2.5rem;
color: #999999;
text-align: justify;
text-align-last: justify;
margin-right: 1rem;
}
.item-content {
width: calc(100% - 3.5rem);
.team-label {
height: 2rem;
width: 6rem;
object-fit: contain;
margin-right: 0.5rem;
}
.team-text {
color: #666666;
}
.item-tag {
box-sizing: border-box;
background: #f4f4f4;
border-radius: 0.13rem;
padding: 0 0.33rem 0 0.33rem;
color: #333333;
&:not(:last-child) {
margin-right: 0.5rem;
}
}
.content-address {
font-size: 12px;
&:first-child {
margin-bottom: 0.3rem;
.text {
color: #8e8e8e;
}
}
.text {
margin-left: 0.3rem;
font-weight: 400;
}
}
.content-spec {
//min-width: max-content;
height: 2rem;
background: #f2f2f2;
border-radius: 0.08rem;
text-align: center;
line-height: 2rem;
font-weight: 400;
box-sizing: border-box;
padding: 0 1rem;
border: 0.04rem solid #f2f2f2;
margin: 0 0.5rem 0.5rem 0;
&:last-child {
margin-right: 0;
}
}
.spec-active {
background: #ffede8;
border: 0.04rem solid #ff552d;
color: #ff552d;
}
}
}
.item-bottom {
margin-bottom: 6.8rem;
}
.content-action {
//position: absolute;
//left: 1rem;
//bottom: 0;
position: relative;
width: 100%;
margin-top: 2rem;
.action-item {
width: 8.63rem;
height: 2.33rem;
background: #fff0e5;
border: 0.04rem solid #ff552d;
text-align: center;
line-height: 2.33rem;
font-size: 13px;
font-weight: 400;
color: #ff552d;
cursor: pointer;
&:hover,
&:active {
filter: brightness(0.95);
}
}
.action-item:not(:last-child) {
margin-right: 0.8rem;
}
.action-item:last-child {
background: #ff552d;
color: #fff;
}
}
}
`;
import React from 'react';
import { PhoneOutlined, ShopOutlined } from '@ant-design/icons';
import { Button, Rate } from 'antd';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import {
GetCompanyInfoById,
GetCompanyInspectionById,
} from '@/api/interface/service';
import QrcodePopover from '@/components/qrcodePopover';
// 商品详情类型
type DetailType = InterDataType<GetCompanyInspectionById>;
// 商城详情类型
type StoreType = InterDataType<GetCompanyInfoById>;
const ServiceStoreView: React.FC<{ detail: DetailType; store: StoreType }> = ({
store,
}) => {
return (
<ServiceStoreWrap>
<div className="store-card flex-start">
<img
className="image"
src={store?.brandLogo}
alt={store?.companyName}
/>
<div className="card-content">
<div className="title">{store?.companyName}</div>
<div className="star flex-start">
<div className="tag select-none">店铺星级</div>
<Rate allowHalf defaultValue={5} style={{ fontSize: '10px' }} />
</div>
<div className="text two-line-ellipsis" title={store?.content}>
{store?.content}
</div>
</div>
</div>
<div className="store-item flex-start">
<div className="item-label">地址:</div>
<div className="item-value">{store?.address}</div>
</div>
<div className="store-item flex-start">
<div className="item-label">电话:</div>
<div className="item-value">{store?.phoneNum || '18626051369'}</div>
</div>
<div className="store-action flex-start">
<QrcodePopover
path="page-service/service-store/index"
scene={`id=${Number(store?.id)}`}
>
<Button className="action-item flex-start">
<ShopOutlined style={{ color: '#FF552D' }} />
<div className="text">进店逛逛</div>
</Button>
</QrcodePopover>
<QrcodePopover
path="page-service/service-store/index"
scene={`id=${Number(store?.id)}`}
>
<Button className="action-item flex-start">
<PhoneOutlined
style={{ color: '#FF552D', transform: 'rotateY(180deg)' }}
/>
<div className="text">联系方式</div>
</Button>
</QrcodePopover>
</div>
</ServiceStoreWrap>
);
};
export default ServiceStoreView;
// 样式
const ServiceStoreWrap = styled.div`
position: relative;
width: calc((100% - 0.83rem) / 10 * 2.5);
min-height: 18rem;
//height: 100%;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
box-sizing: border-box;
padding: 1rem;
.store-card {
width: 100%;
min-height: 8em;
background: linear-gradient(270deg, #5f5f5f 0%, #060606 100%);
border-radius: 0.33rem;
box-sizing: border-box;
padding: 0.58rem 1rem;
margin-bottom: 1rem;
align-items: flex-start;
.image {
width: 3.25rem;
height: 3.25rem;
border: 0.02rem solid #e3e3e3;
margin-right: 0.67rem;
}
.card-content {
position: relative;
width: calc(100% - 3.25rem - 0.67rem);
.title {
font-weight: 500;
color: #ffffff;
}
.star {
margin-bottom: 0.5rem;
.tag {
position: relative;
height: 1.2rem;
line-height: 1rem;
background: #fff3ee;
border-radius: 0.13rem;
border: 0.02rem solid #ff552d;
box-sizing: border-box;
padding: 0 0.33rem;
text-align: center;
font-size: 10px;
color: #ff552d;
margin-right: 0.25rem;
transform: scale(0.8);
}
}
.text {
font-size: 12px;
font-weight: 400;
color: rgba(255, 255, 255, 0.8);
}
}
}
.store-item {
align-items: flex-start;
flex-wrap: nowrap;
margin-bottom: 1rem;
.item-label {
width: 3rem;
color: #666666;
}
.item-value {
width: calc(100% - 3rem);
font-size: 12px;
font-weight: 400;
color: #666666;
}
}
.store-action {
position: relative;
width: 100%;
.action-item {
position: relative;
background: #ffffff;
box-sizing: border-box;
padding: 0 1rem;
border: 0.04rem solid #a8a8a8;
cursor: pointer;
margin-right: 0.83rem;
.text {
color: #a8a8a8;
font-weight: 400;
font-size: 12px;
margin-left: 0.33rem;
}
&:not(:last-child) {
margin-right: 0.83rem;
}
&:last-child {
border: 0.04rem solid #ff552d;
.text {
color: #ff552d;
}
}
&:hover {
filter: brightness(0.95);
}
}
}
`;
import React from 'react';
import { PropertySafetyFilled, ShoppingCartOutlined } from '@ant-design/icons';
import { useRouter } from 'next/router';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { InterListType } from '@/api/interface';
import { GetListAPPCompanyInspectionPageType } from '@/api/interface/service';
import { GlobalDataState } from '@/store/module/globalData';
import { formatMoney } from '@/utils/money';
// 商品详情类型
type GoodsInfoListType = InterListType<GetListAPPCompanyInspectionPageType>[0];
const ServiceItemView: React.FC<{
detail: GoodsInfoListType;
}> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// globalData
const globalData = useSelector(
(state: any) => state.globalData,
) as GlobalDataState;
// 跳转商品详情
const handleDetail = () => {
router.push(`/service/detail/${detail?.id}`).then();
};
// 获取商品的单位
const getPriceUnit = () => {
const unit = globalData?.priceUnitList?.find(
(i) => i.id === detail?.inspectionPriceUnitId,
);
return unit?.unitName || '次';
};
return (
<ProductItemWrap onClick={handleDetail}>
<div className="product-image">
<img
className="image"
src={`${detail?.inspectionFirstImg}?x-oss-process=image/quality,q_10`}
alt="图片"
/>
</div>
<div className="product-title">
【{detail?.industryTypeDTO?.typeName}】
{detail?.inspectionDTO?.inspectionName}
</div>
<div className="product-desc flex-between">
<div className="money">
{detail?.price ? (
<>
<span className="label">¥</span>
<span
className="num"
title={`${formatMoney(detail?.price)}元起每${getPriceUnit()}`}
>
{formatMoney(detail?.price)}
</span>
<div
className="unit text-ellipsis"
title={`${formatMoney(detail?.price)}元起每${getPriceUnit()}`}
>
起/{getPriceUnit()}
</div>
</>
) : (
<span className="label">咨询报价</span>
)}
</div>
<div className="text text-ellipsis">
成交{Math.floor(detail.id * 3.14)}件
</div>
</div>
<div className="product-store flex-start">
<PropertySafetyFilled style={{ color: '#FF552D' }} />
<div className="title text-ellipsis" title={detail?.companyName}>
{detail?.companyName}
</div>
</div>
<div className="product-cart">
<ShoppingCartOutlined style={{ color: '#ffffff', fontSize: '16px' }} />
</div>
</ProductItemWrap>
);
};
export default ServiceItemView;
const ProductItemWrap = styled.div`
position: relative;
box-sizing: border-box;
width: calc((100% - (0.83rem * 5)) / 6);
height: 16rem;
border: 0.02rem solid #e3e3e3;
margin: 0 0.83rem 0.83rem 0;
padding: 0.67rem;
cursor: pointer;
&:nth-child(6n) {
margin-right: 0;
}
&:active,
&:hover {
background: #fff9f6;
border: 0.04rem solid #ff7a3e;
}
.product-image {
position: relative;
width: 100%;
height: 8rem;
box-sizing: border-box;
margin-bottom: 0.5rem;
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.product-title {
width: 100%;
font-size: 13px;
font-weight: 500;
color: #333333;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
margin-bottom: 0.33rem;
min-height: 2rem;
}
.product-desc {
position: relative;
width: 100%;
font-size: 12px;
font-weight: 400;
color: #999999;
margin-bottom: 0.1rem;
align-items: baseline;
.label {
width: 60%;
}
.text {
width: 40%;
text-align: right;
}
.money {
font-size: 16px;
font-weight: 500;
color: #ff1b1b;
.label {
font-size: 12px;
font-weight: bold;
}
.num {
max-width: 60%;
}
.unit {
margin-left: 0.68rem;
font-size: 10px;
font-weight: 400;
color: #999999;
}
}
}
.product-store {
position: absolute;
left: 0.67rem;
bottom: 0.67rem;
.title {
width: 6rem;
font-size: 12px;
font-weight: 400;
color: #666666;
margin-left: 0.25rem;
}
}
.product-cart {
position: absolute;
right: 0.67rem;
bottom: 0.67rem;
width: 2rem;
height: 2rem;
background: #ff8b2e;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&:active,
&:hover {
filter: brightness(0.9);
}
}
`;
import React, { useEffect, useState } from 'react';
import { EnvironmentOutlined, ReloadOutlined } from '@ant-design/icons';
import { Button, Radio, RadioChangeEvent, Space } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { UserAPI } from '@/api';
import { InterDataType } from '@/api/interface';
import { UserAddressSelectList } from '@/api/interface/user';
import QrcodePopover from '@/components/qrcodePopover';
import { setGlobalData } from '@/store/module/globalData';
import { SystemState } from '@/store/module/system';
import { UserInfoState } from '@/store/module/userInfo';
// 列表类型
type ListType = InterDataType<UserAddressSelectList>;
const SubmitAddressView = () => {
// store
const dispatch = useDispatch();
// system
const system = useSelector((state: any) => state.system) as SystemState;
// userInfo
const userInfo = useSelector((state: any) => state.userInfo) as UserInfoState;
// 收货地址列表
const [addressList, setAddressList] = useState<ListType>();
// 当前选中的地址
const [currentValue, setCurrentValue] = useState<number>();
// 获取用户的收货地址
const getUserAddressList = async () => {
const res = await UserAPI.userAddressSelectList({
userAccountId: userInfo?.id,
});
if (res && res.code === '200') {
setAddressList(res.result || []);
// 设置默认地址
setCurrentValue(res.result?.find((i) => i.type === 0)?.id);
// 将地址列表存入store
dispatch(
setGlobalData({
userAddressList: res.result,
userAddressSelectId: res.result?.find((i) => i.type === 0)?.id,
}),
);
}
};
// 转换地址
const transformAddress = (address: string) => {
return address?.split('/');
};
// 选择地址事件
const handleSelect = (e: RadioChangeEvent) => {
setCurrentValue(e?.target?.value);
// 将地址列表存入store
dispatch(
setGlobalData({
userAddressSelectId: e?.target?.value,
}),
);
};
// 组件挂载
useEffect(() => {
if (!system?.token) {
// 请先完成登录
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
} else {
getUserAddressList().then();
}
}, [system?.token]);
return (
<SubmitAddressWrap>
<div className="address-title flex-between">
<div className="title flex-baseline">
<span>确认收货地址</span>
<Button
type="link"
icon={<ReloadOutlined />}
onClick={getUserAddressList}
>
刷新地址
</Button>
</div>
<QrcodePopover
text="管理收货地址"
path="page-mine/address-management/index"
/>
</div>
{!addressList?.length && (
<div className="address-none flex-start">
<EnvironmentOutlined style={{ color: '#ff6700', fontSize: '13px' }} />
<div className="text">
暂无地址,请打开手机端【云享飞】微信小程序,【我的】-【个人设置】-【地址管理】添加
</div>
</div>
)}
<Radio.Group value={currentValue} onChange={(e) => handleSelect(e)}>
<Space direction="vertical">
{addressList?.map((i, j) => (
<div
className={`address-item ${
i?.id === currentValue && 'item-active'
} flex-start`}
key={j}
>
<div className="label">
{i?.id === currentValue && (
<EnvironmentOutlined
style={{ color: '#ff6700', fontSize: '13px' }}
/>
)}
</div>
<Radio value={i.id}>
{transformAddress(i?.takeRegion)?.map((n, m) => (
<span className="text" key={m}>
{n}
</span>
))}
<span className="text">{i?.takeAddress}</span>
<span className="text">{i?.takeName} 收)</span>
<span className="text">{i?.takePhone}</span>
{i?.type === 0 && <span className="text">默认地址</span>}
</Radio>
</div>
))}
</Space>
</Radio.Group>
</SubmitAddressWrap>
);
};
export default SubmitAddressView;
// 样式
const SubmitAddressWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
padding-bottom: 2rem;
.address-title {
position: relative;
width: 100%;
border-bottom: 2px solid #f1f1f1;
box-sizing: border-box;
padding: 1rem 0 0.5rem 0;
margin-bottom: 1rem;
.title {
font-size: 18px;
font-weight: bold;
color: #333333;
}
.action {
font-weight: 400;
color: #3366cc;
cursor: pointer;
}
}
.address-none {
position: relative;
width: 100%;
height: 2.5rem;
line-height: 2.5rem;
background: #fff1e8;
border: 0.04rem solid #ff552d;
box-sizing: border-box;
padding: 0 0.58rem;
.text {
margin-left: 0.5rem;
}
}
.address-item {
position: relative;
width: 100%;
height: 2.5rem;
box-sizing: border-box;
padding: 0 0.5rem;
.text {
margin-right: 0.3rem;
}
.ant-radio + span {
color: #666666;
}
.ant-wave-target {
margin-right: 0.3rem;
}
.label {
position: relative;
width: 4rem;
}
}
.item-active {
background: #fff1e8;
border: 0.04rem solid #ff552d;
.ant-radio + span {
color: #000;
}
.label::after {
position: absolute;
top: -0.3rem;
right: 0.5rem;
content: '寄送至';
font-size: 13px;
color: #ff552d;
}
}
.ant-radio-group,
.ant-space {
width: 100%;
}
`;
import React, { useEffect, useState } from 'react';
import Big from 'big.js';
import { useRouter } from 'next/router';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { InterDataType } from '@/api/interface';
import { AppMallGoodsDetails } from '@/api/interface/mall';
import NumberBox from '@/components/numberBox';
import { setGlobalData } from '@/store/module/globalData';
import { formatMoney } from '@/utils/money';
// 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>;
const SubmitProductView: React.FC<{ detail: DetailType }> = ({ detail }) => {
// 路由钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// 商品的购买数量
const [productNum, setProductNum] = useState<number>(1);
// 获取当前选择的规格
const getCurrentSpec = () => {
const { specId } = router.query;
const item = detail?.priceStock?.find((i) => i?.id === Number(specId));
// 找不到就用默认的
return item || detail?.priceStock?.at(0);
};
// 转换规格展示
const getCurrentSpecText = () => {
const productSpec = getCurrentSpec()?.productSpec || undefined;
if (!productSpec) return '请选择规格';
return Object.entries(JSON.parse(productSpec))
?.map((i) => `【${i[0]}${i[1]}`)
?.join(' +');
};
// 获取购买的价格
const getCurrentPrice = () => {
return Big(getCurrentSpec()?.salePrice || 1)
.mul(Big(productNum))
?.toFixed(2)
?.toLocaleString();
};
// 设置购买数量
const handleProductNum = (value: any) => {
setProductNum(value);
// 将购买数量存入store
dispatch(
setGlobalData({
productSpecNum: value,
}),
);
};
// 组件挂载
useEffect(() => {
// 设置购买数量
if (router?.query?.num) {
setProductNum(Number(router?.query?.num));
// 将购买数量存入store
dispatch(
setGlobalData({
productSpecNum: Number(router?.query?.num),
}),
);
}
}, [router?.query]);
return (
<SubmitProductWrap>
<div className="submit-title flex-between">
<div className="title">确认订单信息</div>
</div>
<div className="submit-td flex-start">
<div className="item">宝贝</div>
<div className="item">单价</div>
<div className="item">数量</div>
<div className="item">合计</div>
</div>
<div className="submit-tr flex-start">
<div className="item submit-product flex-start">
<img
src={detail?.resourcesList?.at(0)?.url}
alt={detail?.tradeName}
className="product-img"
/>
<div className="product-content">
<div className="title two-line-ellipsis">{detail?.tradeName}</div>
<div className="desc">已选:{getCurrentSpecText()}</div>
</div>
</div>
<div className="item">{formatMoney(getCurrentSpec()?.salePrice)}</div>
<div className="item">
<NumberBox
min={1}
max={9999}
precision={0}
value={productNum}
onChange={handleProductNum}
/>
</div>
<div className="item submit-money">
<span className="label"></span>
<span className="num">{getCurrentPrice()}</span>
</div>
</div>
</SubmitProductWrap>
);
};
export default SubmitProductView;
// 样式
const SubmitProductWrap = styled.div`
position: relative;
width: 100%;
box-sizing: border-box;
padding-bottom: 1rem;
.submit-title {
position: relative;
width: 100%;
//border-bottom: 2px solid #f1f1f1;
box-sizing: border-box;
padding: 1rem 0 0.5rem 0;
.title {
font-size: 18px;
font-weight: bold;
color: #333333;
}
.action {
font-weight: 400;
color: #3366cc;
cursor: pointer;
}
}
.submit-td,
.submit-tr {
position: relative;
width: 100%;
min-height: 2rem;
line-height: 2rem;
border-bottom: 0.02rem solid #1f8afe;
flex-wrap: nowrap;
.item {
text-align: center;
color: #666666;
font-weight: bold;
&:nth-child(1) {
width: 40%;
}
&:not(:nth-child(1)) {
width: calc((100% - 40%) / 3);
}
}
}
.submit-tr {
border: none;
.item {
display: flex;
align-items: center;
justify-content: center;
min-height: 3.33rem;
font-weight: normal;
}
.submit-product {
width: 100%;
justify-content: flex-start;
box-sizing: border-box;
padding: 0.67rem 0.5rem;
.product-img {
width: 3.33rem;
height: 3.33rem;
}
.product-content {
padding-left: 0.5rem;
text-align: left;
.title {
font-weight: bold;
color: #333333;
}
.desc {
font-size: 12px;
font-weight: 400;
color: #666666;
line-height: normal;
}
}
}
.submit-money {
font-size: 18px;
//line-height: 1;
color: #ff6700;
.label {
font-size: 14px;
}
}
}
`;
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论