提交 4af6cc74 作者: ZhangLingKun

功能:抢单大厅

上级 75029653
流水线 #8164 已通过 于阶段
in 5 分 43 秒
......@@ -353,6 +353,8 @@ export type AppPublishListType = InterFunction<
userAccountId: number;
id: number;
requireNum: number;
repertory: number;
pilotCertificationUserId: number;
}[]
>;
// 需求类型列表
......
......@@ -5,12 +5,12 @@ import {
VerticalAlignTopOutlined,
} from '@ant-design/icons';
import dayjs from 'dayjs';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterDataType } from '@/api/interface';
import { AppPublishListType } from '@/api/interface/home';
import QrcodePopover from '@/components/qrcodePopover';
import { setGlobalData } from '@/store/module/globalData';
import { UserInfoState } from '@/store/module/userInfo';
import { formatLocationStr } from '@/utils/formatLocation';
......@@ -20,6 +20,8 @@ import { bigNumberTransform } from '@/utils/money';
type ListType = InterDataType<AppPublishListType>;
const HomeTaskView = () => {
// 路由钩子
const router = useRouter();
// store
const dispatch = useDispatch();
// userInfo
......@@ -135,6 +137,10 @@ const HomeTaskView = () => {
}),
);
};
// 跳转更多列表
const handleMore = async () => {
await router.push('/service/task');
};
// 组件挂载
useEffect(() => {
getRequirementsListType().then();
......@@ -144,14 +150,12 @@ const HomeTaskView = () => {
<HomeTaskWrap>
<div className="task-title">
<div className="title-label">抢单大厅</div>
<QrcodePopover path={'page-service/service-task/index'}>
<div className="title-more">
<div className="more-label">更多</div>
<div className="more-icon">
<RightOutlined style={{ color: '#998e8b', fontSize: 13 }} />
</div>
<div className="title-more cursor-pointer" onClick={handleMore}>
<div className="more-label">更多</div>
<div className="more-icon">
<RightOutlined style={{ color: '#998e8b', fontSize: 13 }} />
</div>
</QrcodePopover>
</div>
</div>
<div className="task-list">
{requireList?.map((i, j) => (
......
......@@ -17,12 +17,14 @@ const LayoutView: React.FC<{
autoChange?: boolean;
topDistance?: number;
background?: string;
contentTitle?: string;
}> = ({
children,
placeholder = true,
autoChange = false,
topDistance = 0,
background = 'transparent',
contentTitle,
}) => {
// store
const dispatch = useDispatch();
......@@ -47,7 +49,14 @@ const LayoutView: React.FC<{
autoChange={autoChange}
topDistance={topDistance}
></HeaderView>
<ContentView>{children}</ContentView>
<ContentView>
{!!contentTitle && (
<div className="flex h-14 w-full items-center justify-center bg-[#5D656F]">
<div className="text-2xl text-white">{contentTitle}</div>
</div>
)}
{children}
</ContentView>
<FooterView></FooterView>
{/* 登录弹窗 */}
<LoginModalView
......
import React from 'react';
import { VerticalAlignTopOutlined } from '@ant-design/icons';
import { Statistic } from 'antd';
import dayjs from 'dayjs';
import styled from 'styled-components';
import { InterDataType, InterListType } from '@/api/interface';
import { AppPublishListType } from '@/api/interface/home';
import { GetIndustryListPagesType } from '@/api/interface/service';
import { formatLocationStr } from '@/utils/formatLocation';
import { bigNumberTransform } from '@/utils/money';
// 列表类型
type DetailType = InterDataType<AppPublishListType>[0];
// 行业类型
type IndustryType = InterListType<GetIndustryListPagesType>;
// 服务标签类型
const serviceTagList = [
{
label: '加急单',
code: 'RUSH_ORDER',
color: '#FF0000',
text: '急',
},
{
label: '置顶单',
icon: <VerticalAlignTopOutlined />,
code: 'TOP_ORDER',
color: '#FF7A2C',
},
];
// 保险列表
const insuranceList = [
{ name: '飞手险', value: 1 },
{ name: '机身险', value: 2 },
{ name: '三者险', value: 3 },
];
// 订单状态展示
const orderStateList = [
{ name: '抢单中', color: '#FE2910', value: ['100', '170'] },
{
name: '进行中',
color: '#4D9BFF',
value: ['150', '200', '300', '400', '500', '550', '600'],
},
{ name: '已取消', color: '#999999', value: ['700'] },
{ name: '已完成', color: '#36C1AF', value: ['600'] },
];
const ServiceTaskItem: React.FC<{
detail: DetailType;
industryList: IndustryType;
}> = ({ detail, industryList }) => {
// 根据服务id获取服务详情
const getIndustryItem = industryList.find((i) => i.id === detail?.serviceId);
// 获取任务时间
const getTaskRange = `${dayjs(detail?.taskStartTime).format('MM.DD')}~${dayjs(
detail?.taskEndTime,
).format('MM.DD')}`;
// 获取当前服务的标签类型
const getServiceTag = () => {
const item = serviceTagList?.find((i) => i.code === detail?.orderLevelEnum);
return item || serviceTagList?.[0];
};
// 获取订单金额
const getOrderAmount = () => {
const money = (
bigNumberTransform(detail?.orderAmount, true) as string
)?.replace('.00', '');
// formatMoney(props.detail?.orderAmount)
return detail?.orderAmount > 1 ? money : detail?.orderAmount;
};
// 获取任务城市
const getTaskCity = () => {
const item = formatLocationStr(detail?.taskAddress);
return item?.city || '深圳市';
};
// 获取保险列表
const getInsuranceList =
detail?.insurance
?.split(',')
?.map((i) => insuranceList.find((n) => n.value === Number(i))?.name)
?.join(' | ') || '无';
// 根据订单状态回显对应的状态
const getOrderStatus = () => {
// 如果剩余接单人数为0,则直接返回已完成
if (detail?.repertory === 0) return orderStateList.at(-1);
// 根据订单状态返回对应的状态
return orderStateList.find((i) => i.value.includes(detail?.orderStatus));
};
// 跳转详情
const handleDetail = () => {
console.log('handleDetail');
};
return (
<ServiceTaskItemWrap onClick={handleDetail} className="select-none">
{/* 头部区域 */}
<div className="item-head">
{getIndustryItem?.typeImg && (
<img
className="head-img"
src={`${getIndustryItem?.typeImg}?x-oss-process=image/quality,q_25`}
alt="服务类型图标"
/>
)}
<div className="head-name text-ellipsis" title={detail.serviceName}>
{detail.serviceName}
</div>
<div className="head-text">{getTaskRange}</div>
<div className="head-num">
<span>任务{detail.requireNum || 1}人</span>
{!!detail.repertory && detail.requireNum > 1 && (
<span>(剩余{detail.repertory}人)</span>
)}
</div>
</div>
{/* 标题区域 */}
<div className="item-title">
{getServiceTag() && detail?.orderLevelEnum !== 'REGULAR_ORDER' && (
<div
className="title-tag"
style={{ background: getServiceTag().color }}
>
{getServiceTag()?.icon && getServiceTag()?.icon}
{getServiceTag()?.text && (
<div className="text">{getServiceTag()?.text}</div>
)}
</div>
)}
<div
className="title-text text-ellipsis"
title={detail.requireDescription}
>
{detail.requireDescription}
</div>
{detail.orderAmount && (
<div className="title-price">
<span className="label">¥</span>
<span className="num">{getOrderAmount()}</span>
</div>
)}
</div>
{/* 内容区域 */}
<div className="item-content">
<div className="content-label">{getTaskCity()}</div>
<div className="content-text">
<span>剩余:</span>
<Statistic.Countdown
value={dayjs(`${detail?.taskEndTime} 23:59:59`).valueOf()}
format="D 天 H 时 m 分 s 秒"
valueStyle={{ fontSize: '12px', color: '#999999' }}
/>
</div>
</div>
<div className="item-content dot">
<div className="content-label">保险要求</div>
<div className="content-text">{getInsuranceList}</div>
</div>
{/* 发布时间 */}
<div className="item-time">
{dayjs(detail.createTime).format('MM-DD HH:mm')} 发布
</div>
{/* 订单状态 */}
{getOrderStatus() && (
<div className="item-state" style={{ color: getOrderStatus()?.color }}>
{getOrderStatus()?.name}
</div>
)}
</ServiceTaskItemWrap>
);
};
export default ServiceTaskItem;
// 样式
const ServiceTaskItemWrap = styled.div`
position: relative;
box-sizing: border-box;
width: calc((100% - (0.83rem * 3)) / 4);
height: 7rem;
margin: 0 0.83rem 0.83rem 0;
background: #ffffff;
box-shadow: 0 0.17rem 0.67rem 0 rgba(24, 90, 190, 0.09);
border-radius: 0.33rem;
overflow: hidden;
&:nth-child(4n) {
margin-right: 0;
}
&:hover {
cursor: pointer;
background: #f6f6f6;
}
.item-head {
position: relative;
width: 100%;
height: 1.5rem;
display: flex;
align-items: center;
justify-content: flex-start;
background: linear-gradient(
91deg,
#eaf6ff 0%,
rgba(255, 255, 255, 0.04) 100%
);
box-sizing: border-box;
padding: 0 0.5rem;
margin-bottom: 0.33rem;
.head-img {
width: 0.83rem;
height: 0.83rem;
margin-right: 0.5rem;
}
.head-name {
max-width: 10rem;
font-weight: bold;
margin-right: 0.5rem;
}
.head-text {
color: #666;
}
.head-num {
position: absolute;
right: 0.5rem;
}
}
.item-title {
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
padding: 0 0 0 0.5rem;
margin-bottom: 0.25rem;
.title-tag {
width: 1rem;
height: 1rem;
border-radius: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
line-height: 0.75rem;
margin-right: 0.25rem;
.text {
font-weight: 400;
color: #ffffff;
}
.icon {
width: 0.75rem;
height: 0.75rem;
}
}
.title-text {
font-weight: bold;
max-width: 12rem;
}
.title-price {
position: absolute;
right: 0.5rem;
width: 5rem;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
transform: translateY(0) scaleX(0.9);
.num {
font-size: 1.5rem;
color: #fe2910;
font-weight: bold;
//text-shadow: 1rpx 0 0 #fe2910, 0 -1rpx 0 #fe2910, -1rpx 0 0 #fe2910, 0 1rpx 0 #fe2910;
//text-shadow: 0 0 0 #fe2910, 0 0 0 #fe2910;
}
.label {
color: #fe2910;
font-size: 1rem;
font-weight: bold;
}
}
}
.item-content {
position: relative;
width: 100%;
box-sizing: border-box;
padding: 0 0.5rem 0 1rem;
display: flex;
align-items: center;
justify-content: flex-start;
&::after {
position: absolute;
top: calc((100% - 4px) / 2);
left: 6px;
content: '';
width: 4px;
height: 4px;
background: #fe2910;
border-radius: 50%;
}
.content-label {
color: #666666;
font-weight: bold;
margin-right: 0.5rem;
}
.content-text {
display: flex;
align-items: center;
justify-content: flex-start;
color: #999999;
}
}
.dot::after {
background: #36c1af !important;
}
.item-time {
position: absolute;
right: 0.5rem;
bottom: 0.25rem;
}
.item-state {
position: absolute;
right: 0.5rem;
bottom: 1.5rem;
}
`;
import React, { useEffect, useState } from 'react';
import { Empty, Segmented, SegmentedProps } from 'antd';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterDataType, InterListType } from '@/api/interface';
import { AppPublishListType } from '@/api/interface/home';
import { GetIndustryListPagesType } from '@/api/interface/service';
import { ServiceAPI } from '@/api/modules/service';
import LayoutView from '@/components/layout';
import ProductListView from '@/components/productList';
import ServiceTaskItem from '@/pages/service/task/comp/_serviceTaskItem';
// 列表类型
type ListType = InterDataType<AppPublishListType>;
// 行业类型
type IndustryType = InterListType<GetIndustryListPagesType>;
// 任务列表筛选
const actionList = [
{
label: '附近',
value: 0,
data: {
cityCode: '330111',
districtCode: '330100',
provinceCode: '330000',
},
},
{ label: '最新', value: 1, data: { isNewRequirements: true } },
{ label: '高佣', value: 2, data: { isHighCommission: true } },
];
const ServiceTaskListPage = () => {
// 行业列表
const [industryList, setIndustryList] = useState<IndustryType>([]);
// 分页数据
const [pagination, setPagination] = useState({
pageNo: 1,
pageSize: 16,
totalCount: 0,
});
// 实时订单列表
const [requireList, setRequireList] = useState<ListType>([]);
// 获取实时订单列表
const getAppPublishList = async (index = 0) => {
const res = await HomeAPI.appPublishList({
...actionList[index].data,
});
if (res && res.code === '200') {
const list = res.result?.filter((i) => i?.publish) || [];
setRequireList(list);
setPagination({
...pagination,
pageNo: 1,
totalCount: list?.length || 0,
});
}
};
// 根据分页数据返回商品列表
const getPaginationList = () => {
const { pageNo, pageSize, totalCount } = pagination;
return requireList?.slice(
(pageNo - 1) * pageSize,
pageNo * (pageSize < totalCount ? pageSize : totalCount),
);
};
// 翻页回调
const handlePageChange = (pageNo: number, pageSize: number) => {
setPagination({ ...pagination, pageNo, pageSize });
};
// 获取各个目录及分类信息
const getIndustryListPages = async () => {
const res = await ServiceAPI.getIndustryListPages({
pageNo: 1,
pageSize: 999,
});
if (res && res.code === '200') {
setIndustryList(res?.result?.list || []);
}
};
// 筛选数据
const handleSelectAction: SegmentedProps['onChange'] = (e) => {
const index = actionList?.find((i) => i?.label === e)?.value || 0;
getAppPublishList(index).then();
};
// 组件挂载
useEffect(() => {
Promise.all([getAppPublishList(), getIndustryListPages()]).then();
}, []);
return (
<LayoutView contentTitle={'抢单大厅'}>
<ServiceTaskListWrap>
<div className="tab">
<Segmented
options={actionList?.map((i) => i?.label)}
size="large"
defaultValue={'附近'}
onChange={handleSelectAction}
/>
</div>
{/* 产品列表 */}
<ProductListView pagination={pagination} onChange={handlePageChange}>
{getPaginationList()?.length ? (
getPaginationList()?.map((i, j) => (
<ServiceTaskItem key={j} detail={i} industryList={industryList} />
))
) : (
<div className="list-empty flex-center">
<Empty />
</div>
)}
</ProductListView>
</ServiceTaskListWrap>
</LayoutView>
);
};
export default ServiceTaskListPage;
// 样式
const ServiceTaskListWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 1rem 0 0 0;
margin: 0 auto;
.tab {
position: relative;
width: 100%;
display: flex;
justify-content: flex-end;
margin-bottom: 1rem;
}
`;
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论