课程:React18 系统精讲
章节:Redux-toolkit
讲师:阿莱克斯刘
课程内容
【详情页面搭建 1】页面框架、详情与日期选择
本节课,我们来使用最简单的mvc架构搭建产品的详情页面。先简单介绍一下产品详情页面的ui,整个页面分为三个大块,分别是网站的header顶部导航栏,中间部分的页面内容,以及最底部的网站footer。
顶部导航和底部导航可以复用已有的组件,所以我们的关注点是页面的内容。从内容结构的角度来说,我们可以把内容分为产品简介、产品特色、产品费用、预订须知、以及用户评价这5个模块。
对应这5个模块,除了用户评价以外,我们的后端都会返回相应的数据。所以我们可以先试用postman来检验一下数据。
请同学们打开“获得旅游路线”这条api, url我已经准备好了,url的最后一个部分是旅游路线的id。更改一下自己的icode,然后点击发送。如果请求成功,我们将会获得了响应数据,这些数据就是url中id所对应的旅游路线。数据非常详细,有id、有价格、有折扣、有创建时间,甚至还有使用html字符串所对应的产品特色feature、产品费用fees、以及预订须知notice这三个部分。从数据的角度来说,我们后端唯一欠缺的就是用户评价的数据,那么这部分我们将会使用假数据来代替。
回到网页,通过与分析数据,我们可以知道虽然页面被划分为5个模块,但实际上我们需要的却是3个组件,因为产品特色、产品费用、以及预订须知在直接渲染html字符串以后,这三个模块是可以复用组件的。
那么,接下来请打开项目,我们开始代码。产品详情的页面的初始化在之前的课程中已经完成了,我们接下来需要做的事就是拼凑页面组件。不过,在进入页面组件拼装之前,我们可以先通过调用api,取得页面将会使用到的数据,这样我们的开发就有有数据可以观察了。
如何在组件中调取api就不用多说了吧,我们之前讲过很多次。我们将会使用axios来进行api调用,所以引入axios
import axios from "axios";
- 接下来,从react-router-dom引入useParams,使用useParams来取得url中的路由参数,
const { touristRouteId } = useParams<MatchParams>();
- 然后,使用useState hook来配置三个state, loading, product, 以及error。Loading是boolean,初始化为true;product是any类型,初始化为null;error类型stirng或null,同样初始化为null。
const [loading, setLoading] = useState<boolean>(true);
const [product, setProduct] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
再然后,从react中引入副作用hook,useEffect。我们将会在useEffect使用异步方式来进行api的调用。异步函数中我们先设置loading为ture,然后发送api请求,await axios.get(。如果成功获得数据,那么设置produc数据,并设置loading为false。如果请求失败,我们需要使用try catch来捕获错误信息,设置error为错误信息,并设置loading为false。
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const { data } = await axios.get(
`http://123.56.149.216:8080/api/touristRoutes/${touristRouteId}`
);
setProduct(data);
setLoading(false);
} catch (e) {
setError(e.messagae);
setLoading(false);
}
};
fetchData();
}, []);
既然数据是异步取得的,那么我们ui也需要做一些保护措施,来避免error。这段代码同学可以直接从home页面中复制粘贴。if (loading) 和if (error)这两个部分
import { Spin } from "antd";
if (loading) {
return (
<Spin
size="large"
style={{
marginTop: 200,
marginBottom: 200,
marginLeft: "auto",
marginRight: "auto",
width: "100%",
}}
/>
);
}
if (error) {
return <div>网站出错:{error}</div>;
}
数据取得完成以后,我们就可以来搭建页面的基本骨架了。请同学们先把css样式赋值粘贴到DetailPage.module.css文件里去,然后在detailpage文件中引入样式,因为要复用header和footer,所以引入component文件夹
import styles from "./DetailPage.module.css";
import { Header,Footer } from "../../components";
先整理一下代码,然后我们把网站的复用组件header和footer先放进来。然后,先使用div元素定义一下页面的主要内容,组件样式classname使用page-content
。
接着补充页面的骨架,所有的骨架元素,我都会使用div来定义。
-
第一个组件,产品简介与日期选择,div,
className={styles["product-intro-container"]}
; -
第二个组件,带锚点功能的菜单组件,div,
className={styles["product-detail-anchor"]}
; -
第三个,产品特色,因为等一下这个组件要与我们的锚点菜单连接,所以,给这个组件加上id,feature,
className={styles["product-detail-container"]}
-
第四个,产品费用,同样的道理,组件加上id,费用,fees。因为产品特色、产品费用,以及接下来的预定须知和商品评价,都会使用相同的组件,所以,style也是一样的,
styles["product-detail-container"]
-
第五个,预定须知,id, notes,
className={styles["product-detail-container"]}
-
最后一个,商品评价,id,comments,
className={styles["product-detail-container"]}
export const DetailPage: React.FC<RouteComponentProps<MatchParams>> = () => {
return (
<>
<Header />
<div className={styles["page-content"]}>
{/* 产品简介 与 日期选择 */}
<div className={styles["product-intro-container"]}></div>
{/* 锚点菜单 */}
<div className={styles["product-detail-anchor"]}></div>
{/* 产品特色 */}
<div id="feature" className={styles["product-detail-container"]}></div>
{/* 费用 */}
<div id="fees" className={styles["product-detail-container"]}></div>
{/* 预订须知 */}
<div id="notes" className={styles["product-detail-container"]}></div>
{/* 商品评价*/}
<div id="comments" className={styles["product-detail-container"]}></div>
</div>
<Footer />
</>
);
};
好了,页面的骨架出来了,我们可以运行一下,看看效果。ok,效果大概就是这样,可以看到现在我们有若干个这样的白色的方块,接下来,我们的组件就会被填充在这些方块中。
第一个组件是产品的详情与日期选择,实际上这是左右两个组件组成,左边的产品详情组件需要我们自己搭建,而右边我们可以直接使用ant design的日期选择控件。左右布局我们使用ant desing的row和col组件。一个row,里面套两个col,第一个产品的详情,第二个日期选择,大小稍微设置一下,13 : 11
import { Row, Col } from "antd";
{/* 产品简介 与 日期选择 */}
<div className={styles["product-intro-container"]}>
<Row>
<Col span={13}></Col>
<Col span={11}></Col>
</Row>
</div>
先做简单的,右边的日期选择,可以使用ant design。我们先来完成预览一下ant design的相关文档,https://ant.design/components/date-picker-cn/。
ant design大厂出品,控件的质量都相当高,随便选一个都很好看吧,这个看起来不错,就这个吧
请同学直接复制代码就行了,RangePicker加个状态open,margin top 设置 20
import { DatePicker, Space } from 'antd';
const { RangePicker } = DatePicker;
<Col span={11}>
<RangePicker
open
style={{
marginTop: 20,
}}
/>
</Col>
接下来,我们来完成左边的产品简介组件。既然是组件,那么让我们在component文件夹中创建这个组件的相关文件。文件名称, productIntro,然后就是三大金刚,index.ts,ProductIntro.tsx,ProductIntro.module.css。
先打开样式文件,css文件我提前准备好了,样式特别简单,没啥技术含量,我就直接复制粘贴了。
接着,打开ProductIntro.tsx,先引入样式文件。
import styles from "./ProductIntro.module.css";
然后,来定义一下组件接口,对于产品细节来说,我们将会显示产品的标题,title;简介,shortDescription,字符串类型;价格,string或number;还有诸如优惠券coupons,积分points,折扣discount,评价rating,图片pictures,等等等等。
interface PropsType {
title: string;
shortDescription: string;
price: string | number;
coupons: string;
points: string;
discount: string;
rating: string | number;
pictures: string[];
}
接下来,是函数组件ProductIntro, 类型React.FC。参数使用花括号展开接口定义的数据。JSX retun 一个div元素把整个组件包裹起来,style等于intro-container。
export const ProductIntro: React.FC<PropsType> = ({
title,
shortDescription,
price,
coupons,
discount,
rating,
pictures,
}) => {
return <div className={styles["intro-container"]}></div>;
};
接下来,我们来补充div元素的内容。
第一个内容,当然是产品的名称,也就是参数title,我们使用ant design中的title组件。引入antd,并且引用Typography组件。使用Typography的子组件title, level 4。
第二行是产品的简洁,继续使用Typography组件,使用Typography子组件text。
<Typography.Title level={4}>{title}</Typography.Title>
<Typography.Text>{shortDescription}</Typography.Text>
接下来,是产品的评价与价格细节表。用div包裹一下,内部有两个元素,第一个是价格,使用ant design的text组件,marginleft左移 20个单位,内套一个span包裹一下文字,className等于styles["intro-detail-strong-text"],花括号引用参数price。加上修饰字符串,人民币符号 ¥ 和价格的单位 /人起。使用同样的道理完成评分评价元素,marginleft左移 50个单位
<div className={styles["intro-detail-content"]}>
<Text style={{ marginLeft: 20 }}>
¥<span className={styles["intro-detail-strong-text"]}>{price}</span>{" "}
/人起
</Text>
<Text style={{ marginLeft: 50 }}>
<span className={styles["intro-detail-strong-text"]}>{rating}</span>{" "}
分
</Text>
</div>
好了,再然后就是产品图片的轮博组件,继续使用ant design,使用他的走马灯组件Carousel和Image组件。走马灯,设置为autoplay,一次总共同时显示3张图片,slidesToShow等于3。
然后,来一个for loop来循环参数picture,每个图片都使用Image组件来熏染,高度设置为150个单位
<Carousel autoplay slidesToShow={3}>
{pictures.map((pic) => (
<Image src={pic} height={150} />
))}
</Carousel>
接下来,是课程的难点,我们将会是使用ant design ui中的表单组件 table,来显示路线名称、价格、限时抢购折扣、优惠券、以及线路评价。
那么我们先看看文档。https://ant.design/components/table-cn/ 。 根据文档的案例, 我们可以看到一个最简单的表单组件有两个属性需要提前定义好,
- 第一个是columns,用来配置表格的行列设置;
- 第二个是dataSource,也就是数据。
好了,对表单组件有这个基础认识以后,咱们就可以开始了。首先我们来定义一下,colomus。对于ant design表格的列配置,我们可以使用typesctip 来定义一下,引入ColumnsType。ColumnsType比较特殊,需要从antd/es/table中导出
import { ColumnsType } from "antd/es/table";
其实,我们只有两个列,第一个是title,标题,title、dataIndex、key都一样就好了,字体左对齐,宽度120;第二个是discription,描述,字体左对齐,宽度无所谓。
const columns: ColumnsType = [
{
title: "title",
dataIndex: "title",
key: "title",
align: "left",
width: 120,
},
{
title: "discription",
dataIndex: "discription",
key: "discription",
align: "left",
},
];
然后,我们来配置一下表格的行数据,实际上我们每行数据实际上只需要三个字段的数据,前两个字段与columns对应,我们需要标题title,和描述discription。除此以外,因为我们现在处理的是每行的数据,所以,还需要给每行都定义key,这个key最后会映射给react对象。
interface RowType {
key: number;
title: string;
discription: string | number | JSX.Element;
}
ok,现在表格的行列数据的类型都配置完成了,我们接下来就要组织表格数据了。
常量,tableDataSource,因为我们处理的将会是具体的数据,所以类型应该是列表形态RowType。 那么具体的数据,我就不一一讲解了,同学们可以自己看代码,基本上就是大白话,理解起来应该是没有问题的。
const tableDataSource: RowType[] = [
{
key: 0,
title: "路线名称",
discription: title,
},
{
key: 1,
title: "价格",
discription: (
<>
¥{" "}
<Typography.Text type="danger" strong>
{price}
</Typography.Text>
</>
),
},
{
key: 2,
title: "限时抢购折扣",
discription: discount ? (
<>
¥ <Typography.Text delete>{price}</Typography.Text>{" "}
<Typography.Text type="danger" strong>
¥ {discount}
</Typography.Text>
</>
) : (
"暂无折扣"
),
},
{
key: 2,
title: "领取优惠",
discription: coupons ? discount : "无优惠券可领",
},
{
key: 2,
title: "线路评价",
discription: (
<>
<Rate allowHalf defaultValue={+rating} />
<Typography.Text style={{ marginLeft: 10 }}>
{rating} 星
</Typography.Text>
</>
),
},
];
接下来,我们就可以在jsx中使用table组件了。先从ant design中导入Table组件,向组件传入columns和tableDataSource。
<Table columns={columns} dataSource={tableDataSource} />
不过报错了,错误就是因为我们还需要把ColumnsType和RowType连接起来。
const columns: ColumnsType<RowType> = [
更准确一点,我们还可以把RowType的类型定义给table使用,通过这样的定义,我们的table就形成了typescript的强类型定义了。虽然有点繁琐,但是在实操中过逞中,有强类型确实是可以避免很多潜在的错误。
接着,我们还需要给table再加几个属性。
<Table<RowType>
columns={columns}
dataSource={tableDataSource}
showHeader={false}
size="small"
bordered={false}
pagination={false}
/>
好了,组件完成,同学们记得在index中导出一下,我们就可以使用了,回到detail page。引入ProductIntro,然后直接在中使用这个组件。
<ProductIntro
title={product.title}
shortDescription={product.description}
price={product.originalPrice}
coupons={product.coupons}
points={product.points}
discount={product.price}
rating={product.rating}
pictures={product.touristRoutePictures.map((p) => p.url)}
/>
ok,我们来跑一下网站测试一下。效果不错,我们从后端api取得了数据,完成了页面的骨架、也顺便完成了页面的第一个组件。下节课,我们继续完成剩下的部分。
共同学习,写下你的评论
评论加载中...
作者其他优质文章