实战ant-design-pro管理后台的样式
效果图
PC端
手机端
CSS方法论及样式规范
CSS方法论是一种面向CSS、由个人和组织设计、已被诸多项目检验且公认有效的最佳实践。这些方法论都会涉及结构化的命名约定,并且在组织CSS时可提供相应的指南,从而提升代码的性能、可读性以及可维护性
。
实战项目主要采用BEM+SMACSS这两种方法论的结合命名约定,所以下面对这两种方法论进行简单介绍。
BEM
BEM(BlockElementModifier)是指块级元素修饰符,BEM分为三部分:
- 块(Block)是一个独立实体,最高级抽象,例如菜单、文本框等。
- 元素(Element)是块的组成部分,被包含在块中,无法自成一体,例如菜单项、标题等。
- 修饰符(Modifier)是块或元素的状态,可更改它们的外观或行为,例如高亮、选中等。
BEM中的块、元素和修饰符需要全部小写,名称中的单词用连字符(-)分隔,元素由双下划线(__)分隔,修饰符由双连字符(--)分隔。注意,块和元素都既不能是HTML元素名或ID,也不依赖其它块或元素。
.setting-menu{}
.setting-menu--open{}
.setting-menu__head{}
.setting-menu__head--fixed{}
.setting-menu__content{}
上面代码中,.setting-menu表示一个独立实体,.setting-menu__head、.setting-menu__content{}表示独立实体的组成部分,.setting-menu--open{}是对配置菜单展开状态的一种行为描述,.setting-menu__head--fixed{}是对配置菜单头部固定状态的一种行为描述。
SMACSS
指模块化的CSS架构
- 基础(Base)是为HTML元素定义默认样式,可以包含属性、伪类等选择器。
- 布局(Layout)会将页面分为几部分,可作为高级容器包含一个或多个模块,例如左右分栏、栅格系统等。
- 模块(Module)又名对象或块,是可重用的模块化部分,例如导航栏、产品列表等。
- 状态(State)描述的是任一模块或布局在特定状态下的外观,例如隐藏、激活等。
- 主题(Theme)也就是换肤,描述了页面的外观,它可修改前面四个类别的样式,例如链接颜色、布局方式等。
通过相应的命名前缀来完成对类别的表示,l-用作布局的前缀,例如.l-inline、.layout-grid等;m-模块命名前缀,例如.m-profile、.m-field等;is-用作状态的前缀,例如.is-collapsed、.is-active等;theme-用作主题的前缀,例如.theme-l-grid等。
在实际工作中,不需要局限于某一个CSS方法论,很多时候可以结合使用,共享模块化CSS的规则。例如遵循BEM的命名约定,以及SMACSS的分类前缀
,具体如下所列。
.m-setting-menu{}
.m-setting-menu--open{}
.m-setting-menu__head{}
.m-setting-menu__head--fixed{}
.m-setting-menu__content{}
CSS文件划分
在中大型项目中,一般会对CSS进行文件划分,根据文件的性质与用途,大概会分成:
- 公共型样式
- 特殊型样式
- 皮肤型样式
公共型样式可命名为global.css或common.css等名字,主要包括:
- 重置默认样式reset
- 网站通用布局
- 通用模块
- 通用元件
- 通用响应式系统
特殊型样式主要是根据当前页面来决定的文件,只针对当前页面做出特殊处理的样式,例如只在首页中用到的样式可放置到index.css中,在登录页中用到的样式可放置到login.css中。
皮肤型样式是针对网站需要皮肤功能时,把颜色、背景等抽离出来放到文件中的形式,例如theme-pink.css、theme-skyblue.css等。
CSS文件的引入顺序如下:
<linkhref="assets/css/global.css"rel="stylesheet"type="text/css"/><linkhref="assets/css/index.css"rel="stylesheet"type="text/css"/><linkhref="assets/css/theme.css"rel="stylesheet"type="text/css"/>
页面整体结构
<!-- g-ant布局样式 -->
<div class="g-ant">
<!-- 侧边栏 -->
<div class="g-ant__sider">
<div class="g-ant-sider__wrap g-ant-sider__wrap--fixed">
<!-- 侧边栏头部 -->
<div class="g-ant-sider__head"></div>
<!-- 侧边栏主题内容区 -->
<ul class="g-ant-sider__main">
<li class="m-menu m-menu--selected"></li>
</ul>
<!-- 侧边栏页脚 -->
<div class="g-ant-sider__foot">
<i class="iconfont icon-outdent u-bar"></i>
</div>
</div>
</div>
<!-- 遮罩层 -->
<div class="u-mask"></div>
<!-- 主内容区 -->
<div class="g-ant__main">
<!-- 主内容区头部 -->
<div class="g-ant-main__head g-ant-main__head--fixed"></div>
<!-- 主内容区的内容区 -->
<div class="g-ant-main__main"></div>
<!-- 主内容区页脚 -->
<div class="g-ant-main__foot"></div>
</div>
</div>
<!-- 设置区 -->
<div class="m-setting">
<div class="m-setting__bar">
<i class="iconfont icon-setting"></i>
</div>
<!-- 设置项 -->
<div class="m-setting__item"></div>
<!-- 分隔符 -->
<div class="u-divider"></div>
<div class="m-setting__item"></div>
</div>
<div class="u-mask"></div>
页面整体样式
通用布局-主体布局
.g-ant {
display: flex;
}
.g-ant__sider {
// 不缩小
flex-shrink: 0;
// 设置宽度
width: 208px;
min-height: 100vh;
}
.g-ant-sider__wrap {
height: 100%;
display: flex;
// 按列进行布局
flex-direction: column;
}
.g-ant__main {
// 沾满剩余空间
flex-grow: 1;
display: flex;
flex-direction: column;
}
侧边栏布局
stick footer
.g-ant-sider__head {
flex-shrink: 0;
}
.g-ant-sider__main {
flex-grow: 1;
overflow: hidden auto;
}
.g-ant-sider__foot {
flex-shrink: 0;
}
利用margin:auto实现如下效果:
<div class="m-menu__title">
<i class="iconfont icon-car"></i>
<span>Dashboard</span>
<i class="iconfont icon-arrowup"></i>
</div>
.m-menu__title {
display: flex;
align-items: center;
}
.m-menu__title > span {
margin-right: auto;
}
需求:当点击标题的时候把子菜单弹出,同时切换右侧的箭头及高亮
html结构
<li class="m-menu m-menu--selected">
<div class="m-menu__title">
<i class="iconfont icon-car"></i>
<span>Dashboard</span>
<i class="iconfont icon-arrowup"></i>
</div>
<ul class="m-menu__sub">
<li>分析页</li>
<li class="m-menu__sub--selected">监控页</li>
<li>工作台</li>
</ul>
</li>
css
.m-menu__sub {
display: none;
}
// 当选中的时候改变文字颜色
.m-menu--selected .m-menu__title {
color: white;
}
// 当选中的时候展开子菜单
.m-menu--selected .m-menu__sub {
display: block;
}
js
const menuTitles = document.querySelectorAll('.m-menu__title')
for (var i = 0; i < menuTitles.length; i++) {
menuTitles[i].onclick = function () {
var menu = this.parentElement
menu.classList.toggle('m-menu--selected')
if (menu.className.includes('m-menu--selected')) {
// 打开状态
var arrow = this.querySelector('[class*="icon-arrow"]')
arrow.className = 'iconfont icon-arrowup'
} else {
// 关闭状态
var arrow = this.querySelector('[class*="icon-arrow"]')
arrow.className = 'iconfont icon-arrowdown'
}
}
}
注意
className
获取或设置指定元素的class属性的值,我们可以直接进行设置Element.classList
是一个只读属性,返回一个元素的类属性的实时DOMTokenList
集合。相比将element.className
作为以空格分隔的字符串来使用,classList
是一种更方便的访问元素的类列表的方法
需求:当点击按钮把菜单栏缩小到只有图标
.g-ant__sider {
width: 208px;
}
.g-ant__sider--closed {
width: 48px;
overflow: hidden;
}
const sider = document.querySelector('.g-ant__sider')
sider.classList.toggle('g-ant__sider--closed')
即通过改变width来实现
主体区
<div class="g-ant__main">
<!-- 主内容区头部 -->
<div class="g-ant-main__head g-ant-main__head--fixed"></div>
<!-- 主内容区的内容区 -->
<div class="g-ant-main__main"></div>
<!-- 主内容区页脚 -->
<div class="g-ant-main__foot"></div>
</div>
.g-ant__main {
display: flex;
flex-direction: column;
}
.g-ant-main__head {
height: 48px;
flex-shrink: 0;
}
.g-ant-main__head--fixed {
position: fixed;
left: 0;
right: 0;
z-index: 2;
}
.g-ant-main__main {
flex-grow: 1;
}
.g-ant-main__foot {
margin-top: 70px;
margin-bottom: 30px;
}
侧边栏设置
<div class="m-setting">
<div class="m-setting__bar">
<i class="iconfont icon-setting"></i>
</div>
<!-- 设置项 -->
<div class="m-setting__item"></div>
<!-- 分隔符 -->
<div class="u-divider"></div>
<div class="m-setting__item"></div>
</div>
.m-setting {
// 宽度为300px
width: 300px;
// 高度为100%
height: 100vh;
position: fixed;
// 为了把这部分内容隐藏
right: -300px;
top: 0;
z-index: 10;
}
.m-setting__bar {
width: 48px;
height: 48px;
background: var(--theme);
position: absolute;
// 使这个按钮出现在屏幕上
left: -48px;
top: calc(50% - 24px);
}
// 当点击bar的时候使主体内容显示在屏幕上
.m-setting--open {
right: 0;
}
// 把遮罩层显示
.m-setting--open + .u-mask {
display: block;
}
.u-mask {
display: none;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.4);
position: fixed;
left: 0;
top: 0;
z-index: 5;
}
var settingBar = document.querySelector('.m-setting__bar')
settingBar.onclick = function () {
setting.classList.toggle('m-setting--open')
if (setting.className.includes('m-setting--open')) {
var icon = this.querySelector('i')
icon.className = 'iconfont icon-close'
} else {
var icon = this.querySelector('i')
icon.className = 'iconfont icon-setting'
}
}
css的命名
通用样式
:root {
--theme: #1890ff;
}
.show {
display: block !important;
}
.hide {
display: none !important;
}
通用布局
通用布局以g-ant-为开头
.g-ant {
display: flex;
}
.g-ant__sider {}
.g-ant__main {}
模块
模块以m-为开头
// logo模块
.m-logo {}
// 菜单模块
.m-menu {}
.m-menu__title {}
.m-menu__sub {}
.m-menu__sub--selected{}
// 设置模块
.m-setting {}
.m-setting--open {}
.m-setting__bar {}
通用元件
通用元件是指一些公用的样式,比如一个分割线,一个遮罩层,一个radio和一个checkbox。下面来介绍一个radio如何实现的,效果如下:
<div class="u-switch j-head--fixed">
<div class="u-switch__handle"></div>
</div>
.u-switch {
width: 28px;
height: 16px;
border-radius: 100px;
background: var(--theme);
cursor: pointer;
}
.u-switch--closed {
background: rgba(0, 0, 0, 0.25);
}
.u-switch--closed .u-switch__handle {
// 改变小圆的位置
left: 14px;
}
.u-switch__handle {
width: 12px;
height: 12px;
background: white;
border-radius: 50%;
position: relative;
left: 2px;
top: 2px;
}
var headFixed = document.querySelector('.j-head--fixed')
headFixed.onclick = function () {
this.classList.toggle('u-switch--closed')
}
设置颜色主题
<div class="m-setting__item">
<h3>主题色</h3>
<ul class="m-setting-item__theme">
<li class="u-foxiaolan">
<i class="iconfont icon-check"></i>
</li>
<li class="u-bomu"></li>
<li class="u-huoshan"></li>
<li class="u-rimu"></li>
<li class="u-mingqing"></li>
<li class="u-jiguanglv"></li>
<li class="u-jikelan"></li>
<li class="u-jiangzi"></li>
</ul>
</div>
.u-foxiaolan,
.u-bomu,
.u-huoshan,
.u-rimu,
.u-mingqing,
.u-jiguanglv,
.u-jikelan,
.u-jiangzi {
width: 20px;
height: 20px;
color: white;
border-radius: 2px;
cursor: pointer;
line-height: 20px;
text-align: center;
}
.u-foxiaolan {
background: #188efc;
}
.u-bomu {
background: #f5212d;
}
.u-huoshan {
background: #fe531f;
}
.u-rimu {
background: #fbae14;
}
.u-mingqing {
background: #14c2c3;
}
.u-jiguanglv {
background: #53c41a;
}
.u-jikelan {
background: #2f54eb;
}
.u-jiangzi {
background: #722ed1;
}
var themes = document.querySelectorAll('.m-setting-item__theme>li')
for (var i = 0; i < themes.length; i++) {
themes[i].onclick = function () {
// 先把里面的内容清空
for (var i = 0; i < themes.length; i++) {
themes[i].innerHTML = ''
}
// 然后把✔️加入进去
this.innerHTML = '<i class="iconfont icon-check"></i>'
// 获取所选取的颜色
var color = getComputedStyle(this).backgroundColor
// 设置颜色变量
document.documentElement.style.setProperty('--theme', color)
}
}
响应式
首先看看主体内容区如何实现响应式
<div class="index-main">
<div class="m-card">
<div class="m-card__title">活动实时交易情况</div>
<div class="m-card__body">
<div class="index-main__bg1"></div>
</div>
</div>
<div class="m-card">
<div class="m-card__title">活动情况预测</div>
<div class="m-card__body">
<div class="index-main__bg2"></div>
</div>
</div>
<div class="m-card">
<div class="m-card__title">券核效率</div>
<div class="m-card__body">
<div class="index-main__bg3"></div>
</div>
</div>
<div class="m-card">
<div class="m-card__title">各品类占比</div>
<div class="m-card__body">
<div class="index-main__bg4"></div>
</div>
</div>
<div class="m-card">
<div class="m-card__title">热门搜索</div>
<div class="m-card__body">
<div class="index-main__bg5"></div>
</div>
</div>
<div class="m-card">
<div class="m-card__title">资源剩余</div>
<div class="m-card__body">
<div class="index-main__bg6"></div>
</div>
</div>
</div>
.index-main {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 285px);
gap: 24px;
margin: 24px;
grid-template-areas:
'a1 a1 a1 a2'
'a1 a1 a1 a3'
'a4 a4 a5 a6';
}
.index-main .m-card:nth-of-type(1) {
grid-area: a1;
}
.index-main .m-card:nth-of-type(2) {
grid-area: a2;
}
.index-main .m-card:nth-of-type(3) {
grid-area: a3;
}
.index-main .m-card:nth-of-type(4) {
grid-area: a4;
}
.index-main .m-card:nth-of-type(5) {
grid-area: a5;
}
.index-main .m-card:nth-of-type(6) {
grid-area: a6;
}
@media (max-width: 1200px) {
.index-main {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(4, 285px);
gap: 24px;
margin: 24px;
grid-template-areas:
'a1 a1 a1'
'a1 a1 a1'
'a2 a3 a5'
'a4 a4 a6';
}
}
@media (max-width: 992px) {
.index-main {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(5, 285px);
gap: 24px;
margin: 24px;
grid-template-areas:
'a1 a1'
'a1 a1'
'a2 a3'
'a4 a4'
'a5 a6';
}
}
@media (max-width: 768px) {
.index-main {
display: grid;
grid-template-columns: repeat(1, 1fr);
grid-template-rows: repeat(6, 285px);
gap: 24px;
margin: 24px;
grid-template-areas:
'a1'
'a2'
'a3'
'a4'
'a5'
'a6';
}
}
接下来看看菜单栏如何响应式
@media (max-width: 992px) {
.g-ant__sider {
display: none;
position: fixed;
left: 0;
top: 0;
z-index: 6;
}
// g-ant__sider显示的时候展示遮罩层
.g-ant__sider:is(.show) + .u-mask {
display: block;
}
}
转载自:https://juejin.cn/post/7082344514660073486