官网前端学习总结
结构目录
其中
dist: 项目打包后的产物
mock: 模拟数据
node_modules: 项目依赖
src: 源代码都在这里
assets: 静态资源
components: 公用组件
layouts: 通用布局
pages: 页面都放在这里
utils: 工具
global.tsx/global.less: 约定全局js和全局样式
项目配置
路由部分
路由是用全局layout,所以通过props.children渲染子组件
要看一个东西是组件还是页面,就看他的路由,路由配置了,那他就是页面,如下图,就是一个页面
项目改为多页面形式
ssr: {} // 预渲染, 解决没有服务端情况下,页面的 SEO 和首屏渲染提速。
exportStatic: { dynamicRoot: true, htmlSuffix: true }, // 配置 html 的输出形式
技术方案
大体是react+umi, 移动端方案是flexible.js+媒体查询
样式文件书写顺序
位置属性:position display float left top right bottom overflow clear z-index
尺寸(自身)属性:width height padding border margin
文字属性:font-family font-size font-style font-weight font-varient color text-align vertical-align
word-spacing white-space text-overflow
背景:background、border等
css3中新增属性:content box-shadow border-radius transform等
布局
第一个是layouts下的Header部分了
<div className={styles.wrapper}>
<div className={styles.container}>
</div>
</div>
.wrapper {
position: fixed;
top: 0;
z-index: 999;
width: 100%;
line-height: 100px;
background-color: #ffffff;
}
.container {
/* flex布局,让里面的模块自动分配空间 */
display: flex;
justify-content: space-between;
align-items: center;
width: 1220px;
margin: 0 auto;
/* 这个是导航区了,给一个定宽 margin: 0 auto 就可以居中了 */
}
wrapper是最外层,给一个通栏宽度就行了,用到了粘性定位,因为他要一直在顶部,不能划进去。
导航选中样式实现思路是:
- 获取地址栏地址
- 判断地址栏地址是否包含当前路由地址,若包含则给当前导航加上样式,这样即使页面刷新,当前样式也不会丢失
代码如下
const [selected, setSelected] = useState('');
const handleUrl = (params = '/solution/') => { //处理地址栏是否包含路由地址
return selected.includes(params);
};
useEffect(() => {
const urlParams = new URL(window.location.href); // 获取地址栏地址
const pathName = urlParams?.pathname;
setSelected(pathName);
}, []);
<a
href={item.href}
className={handleUrl(item.href) ? styles.active : ''} // 若包含则给当前导航加上样式
{...item?.otherProps}
>{item.name}</a>
第二个是Footer
主要就是footerContent设置居中,再给个padding把内容挤下去。
图片如果需要独立设置宽高,要加上display: block;变成块级元素, 不然会有问题
banner区域
如果你想放一张图片做背景,然后还需要在上面放文字之类的,如果你设置backgroundImage不给高度是没办法撑开div的,
但是给了高度页面宽度变化他高度不会变,所以我们可以用下面的方法
我们在banner里面放了一个img,宽度100%,高度自适应,这样图片就不会失真了,然后再放一个bannerContainer,定位在banner上层,就像加了一张遮罩层,然后把内容放在bannerContainer里就行了,样式代码如下
.banner {
position: relative;
width: 100%;
overflow: hidden;
.bannerContainer {
position: absolute;
left: 0;
top: 0;
z-index: 10;
width: 100%;
height: 100%;
}
}
如果你发现缩小屏幕后,图片宽度不一样,就像下面这样
这可能是因为和下面的图片不一样宽,下面比较宽,但是他已经最大了,所以就不会撑开了
这种情况我在global.less里加了下面,就可以解决了,给他一个最小宽度
body {
min-width: 1220px;
}
很多地方宽高不需要定死,这个需要自己看情况
抽离为公共组件
把多个具有相同样式或类似得抽离为公共组件,传入参数即可使用,比如下面,虽然内容一个靠左一个靠右,但是我们只需要传入一个type就可以
{list.map((item: any) => {
return item?.type === 'left' ? (
<div className={styles.content} key={item.id}>
</div>
) : (
<div className={styles.content} key={item.id}>
</div>
);
})}
我们组件公用组件在接受参数时,也可以设置默认值,如下,如果bgImg并没有传,那我们就使用默认值
const {
title, // banner标题
subTitle = [], // banner副标题
des = '', // 描述
titleColor = '#000', // 字体颜色
bgImg = '/images/software/banner.png',
// 背景图片
} = props;
硬件产品模块
滑动选择栏
实现这个是用了swiper插件,
这是要展示的组件,下面的产品点击跳转到该产品的详情页面, 同时选择栏也跳转到相应的slider,并且该slider居中
// 这是一个产品详情
const classifyRoom = {
type: 'large',
name: '智能垃圾分类箱房',
imgUrl: '/images/hardware/AIroomDetail.png',
}
___________________________________________________________________
const arr: any[] = [ // 这是没更新前的产品详情数组,里面不包含产品详情的组件,我们在下面更新进去
{
type: 'all',
title: '全部产品',
url: '/hardware/hardwareProduct',
},
{
title: '智能垃圾分类箱房',
detail: classifyRoom, // 这里映射上面的对象
url: '/hardware/classifyRoom',
},
];
// 处理点击时跳转
const handleClick = (id: number) => {
console.log('________', id);
swiperRef && swiperRef.slideTo(id, 1);
};
// 更新数组
const updateArr = arr.map((item: any) => {
return item?.type === 'all' // 如果类型为全部产品,则更新全部产品组件进去
? {
...item,
component: (
<HardwareProduct
onSelect={(item: any, id: any) => {
select(item);
handleClick(id); // 把方法传进全部产品组件, 点击了哪个就跳转到哪个
}}
/>
),
}
: {
...item, // <ProductDetail /> 为产品详情公用组件
component: <ProductDetail product={item.detail} />, // 把产品详情更新进去
};
});
<Swiper
spaceBetween={40} // 两个slider之间的距离
slidesPerView="auto" // slider自适应可以看见几个
className={styles.swiper}
slideToClickedSlide // 滑动到点击到的slider
centeredSlides // 当前选择的slider居中
centeredSlidesBounds // 在选中的slider居中时,第一个和最后一个slider贴合边缘
onSwiper={(swiper) => {
swiperRef = swiper; //获取swiper实例
}}
>
// 这里控制显示的组件
{updateArr.map((item: any) => {
return item.url === selected ? item.component : null;
})}
tabBar切换组件
思路都差不多,点击了把他的id存进去,如果他的id等于状态的id,则切换并加上样式
{tabSet.map((item: any) => {
return item.type === 'all' ? ( // 这里不能直接等于切换按钮的名字,应该等于id或者类型
<div>
{item.title}
</div>
) : (
<h2>
{item.title}
</h2>
);
})}
</div>
{tabSet.map((item: any) =>
item.id === selected ? item.component : null,
)}
申请试用
选择产品类型,这里用的字典接口实现
const getDictData = async () => {
const res = await getDict(); // 获取数据
const data = res?.data || [];
data.map((item: any, index: number) => { // 给每一项添加一个键,用于判断产品类型是否被选中
item.details.map((childItem: any, childIndex: number) => {
data[index]['details'][childIndex].checked = false;
setProductList(data);
});
});
};
// 处理产品类型选中与取消
const handleCheckbox = async (itemIndex: number, childItemIndex: number) => {
// 拿到当前的checked
const checked = productList[itemIndex]['details'][childItemIndex]?.checked;
// 取反
productList[itemIndex]['details'][childItemIndex].checked = !checked;
// 更新
setProductList(productList);
};
<div className={styles.interestTitle}>请选择您感兴趣的产品类型</div>
{productList.map((item: any, itemIndex: number) => {
return (
(childItem: any, childItemIndex: number) => (
<div
key={childItem.dictId}
// 为true添加样式,反之不加
className={childItem.checked ? styles.productChecked : ''}
onClick={() => {
handleCheckbox(itemIndex, childItemIndex);
// 点击时把productList的index和details的index传进去
}}
>
{childItem.dictValue}
</div>
),
)}
);
})}
正则表达式判断手机号码或12位座机号
const reg = /^(1[3|4|5|6|7|8|9])\d{9}$|^400-[016789]\d{2}-\d{4}$/;
reg.test(value)
? setValues({ ...values, [e.target.name]: value }) // 正则通过则存起来
: setValues({ ...values, [e.target.name]: '' }) // 不通过则置该值为空
提交
const handleSubmit = async (e: any) => {
let applyProTypeList: any = []; // 先声明一个数组
productList.map((item: any) => {
item.details.map((listItem: any) => { // 先把产品类型选中的存起来
listItem.checked ? applyProTypeList.push(listItem.dictKey) : '';
});
});
if (applyProTypeList.length === 0) {
message.error('请选择您感兴趣的产品类型');
return;
}
if (values.applyUserPhone === '') {
message.error('请输入正确的手机号码');
return;
}
submit({
...values,
applyProType: applyProTypeList?.join(','), // 把刚存起来的选中的分割存进要传递的参数中
});
};