不需要。用 MyBatis-Plus 完全可以实现,而且更优雅。​ 一般不需要写原生 JOIN 的 SQL。因为一般的联查和串联查询性能差距不是很大。

不需要。用 MyBatis-Plus 完全可以实现,而且更优雅。​ 不需要写原生 JOIN 的 SQL。


推荐做法:MyBatis-Plus 关联查询

你可以用 Service 层封装关联查询,或者直接用 MyBatis-Plus 的自动注入功能。以下是两种方案:

方案一:Service 层封装(推荐,逻辑清晰)

AfterSalesItemService中写一个方法,完成数据组装:

VO 对象示例

@Data
public class AfterSalesItemVO extends AfterSalesItem {
    // 商品快照信息
    private Long skuId;
    private String skuName;
    private String specInfo;
    private String mainImage;
    private BigDecimal unitPrice;
    private BigDecimal payAmount;
    
    // 还可以根据需要加入当前商品信息
    private Sku currentSkuInfo;
}

方案二:使用 MyBatis-Plus 的自动注入(更简洁)

在实体类上使用 @TableFieldselect = false配合自定义查询:

@Data
@TableName("after_sales_item")
public class AfterSalesItem {
    private Long id;
    private Long afterSalesId;
    private Long orderItemId;
    private Integer quantity;
    
    // 商品快照信息(非数据库字段,查询时自动注入)
    @TableField(exist = false)
    private String skuName;
    
    @TableField(exist = false)
    private String specInfo;
    
    @TableField(exist = false)
    private BigDecimal payAmount;
    // ... 其他字段
}

在 Mapper 中写自定义查询(用 MyBatis 的 XML 或注解,现在一般不推荐这样写,不好维护):

public interface AfterSalesItemMapper extends BaseMapper<AfterSalesItem> {
    /**
     * 联查订单商品快照
     */
    @Select("SELECT asi.*, oi.sku_name, oi.spec_info, oi.pay_amount " +
            "FROM after_sales_item asi " +
            "LEFT JOIN order_item oi ON asi.order_item_id = oi.id " +
            "WHERE asi.after_sales_id = #{afterSalesId}")
    List<AfterSalesItem> selectWithSnapshot(@Param("afterSalesId") Long afterSalesId);
}

性能对比与选择建议

方案 优点 缺点 适用场景
Service 层封装 1. 逻辑清晰,易于维护和调试
2. 可以利用 MyBatis-Plus 的缓存、分页等特性
3. 便于后续加缓存
1. 需要手动组装
2. 有两次查询(售后商品 + 订单快照)
绝大多数场景,推荐使用
MyBatis-Plus 联查 1. 一次查询,理论上更快
2. 代码简洁
1. 破坏了 MyBatis-Plus 的简单性
2. 复杂查询难以维护
3. 无法利用 MP 的高级特性
简单、固定的关联查询

实际开发中的最佳实践

1.用 Service 层封装为主,保持代码清晰。

2.利用批量查询:如方案一中的 orderItemService.listByIds()就是批量查询,避免 N+1 问题。

3.考虑分页:如果售后商品可能很多,在第一步查询售后商品时就可以加分页。

4.使用缓存OrderItem是历史快照,不会变更,非常适合用缓存(如 Redis)存储,进一步提高查询性能。

5.使用异步多线程:若有多表查询方案,可使用异步多线程方式实现提高速度。(多表写入不推荐,无法跟踪异常)

最近醉风云博客主站遭受很多英文垃圾评论,每个几天就有上千条,很烦。

开启评论审核 ≠ 阻止数据库写入,这正是 WordPress 的“设计缺陷”。垃圾评论虽然显示“待审核”,但实际上已经写入数据库,占用空间、拖慢网站、暴露漏洞。

在“写入数据库”时,WordPress 并没有真正的过滤机制。

看了一下相关的垃圾评论插件,没有更好的防英文功能,而且大多都收费。

自己写一下吧。

WordPress 原生不支持“基于语言过滤”,但我们可以通过多种方法在数据库写入前拦截。

将下述代码放到function.php中就可以。

/**
 * 在数据库写入前检查评论语言
 * 阻止全英文评论
 */
add_filter('preprocess_comment', 'block_english_comments_before_db');

function block_english_comments_before_db($commentdata) {
    $content = $commentdata['comment_content'];
    
    // 检测是否为全英文(排除标点符号和数字)
    $english_ratio = calculate_english_ratio($content);
    
    // 设置阈值:英文内容超过 90% 则阻止
    if ($english_ratio > 0.9) {
        wp_die(
            '<h3>评论被阻止</h3>' .
            '<p>本网站暂不接受全英文评论。请至少包含少量中文内容。</p>' .
            '<p><a href="javascript:history.back()">返回修改</a></p>',
            '评论语言不符合要求',
            403
        );
    }
    
    return $commentdata;
}

/**
 * 计算英文内容比例
 */
function calculate_english_ratio($text) {
    if (empty($text)) return 0;
    
    // 移除标点、数字、空格
    $clean_text = preg_replace('/[0-9\pP\s]/u', '', $text);
    if (empty($clean_text)) return 0;
    
    // 统计英文字符
    preg_match_all('/[a-zA-Z]/', $clean_text, $english_matches);
    $english_count = count($english_matches[0]);
    
    // 统计中文字符
    preg_match_all('/[\x{4e00}-\x{9fa5}]/u', $clean_text, $chinese_matches);
    $chinese_count = count($chinese_matches[0]);
    
    // 统计总字符数
    $total_count = mb_strlen($clean_text, 'UTF-8');
    
    // 如果只有英文,返回1
    if ($chinese_count == 0 && $english_count > 0) {
        return 1;
    }
    
    // 计算英文比例
    if ($total_count > 0) {
        return $english_count / $total_count;
    }
    
    return 0;
}

/**
 * 在评论表单添加语言提示
 */
add_action('comment_form_before', 'add_language_notice');
function add_language_notice() {
    echo '<div class="language-notice" style="background:#f0f8ff; padding:10px; margin:10px 0; border-left:4px solid #0066cc;">
        <strong>📢 评论语言要求:</strong>请确保评论中包含中文内容。全英文评论将被自动阻止。
    </div>';
}

在 Vue3 中使用 TypeScript 时,通过 ref 获取标签(包括 DOM 元素或组件实例)的 TypeScript 类型定义需根据场景区分:


1. 获取 DOM 元素的类型

若 ref 绑定的是原生 DOM 元素(如 <div><input>),需使用 HTML 元素的内置类型

import { ref } from 'vue';

// 示例:获取 div 元素
const divRef = ref<HTMLDivElement | null>(null);

// 示例:获取 input 元素
const inputRef = ref<HTMLInputElement | null>(null);
  • 原因:DOM 元素类型由浏览器环境定义,如 HTMLDivElementHTMLInputElement 等。
  • 注意:初始值需设为 null,因为元素在组件挂载前不存在 。

2. 获取子组件的类型

若 ref 绑定的是 Vue 组件(自定义组件或第三方组件),需使用 InstanceType<typeof 组件> 工具类型:

import { ref } from 'vue';
import MyComponent from '@/components/MyComponent.vue';

// 获取自定义组件的实例类型
const myCompRef = ref<InstanceType<typeof MyComponent> | null>(null);

第三方组件库示例(如 Element Plus)

import { ref } from 'vue';
import { ElTree } from 'element-plus';

// 定义组件实例类型
type TreeType = InstanceType<typeof ElTree>;
const treeRef = ref<TreeType | null>(null);
  • 关键点
    • typeof 组件 获取组件的构造函数类型。
    • InstanceType 提取该构造函数的实例类型(即组件暴露的方法/属性)。
    • 联合 null 处理初始状态 。

3. 动态生成标签的 ref 类型

若通过函数动态绑定 ref(如 :ref="getRef"),需在函数内手动指定类型:

<template>
  <el-input :ref="getInputRef" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { ElInput } from 'element-plus';

type InputType = InstanceType<typeof ElInput>;
const inputRef = ref<InputType | null>(null);

const getInputRef = (el: unknown) => {
  inputRef.value = el as InputType; // 显式类型断言
};
</script>
  • 原因:动态 ref 的回调参数 el 类型为 unknown,需手动转换为目标类型

以下是优化 Vue3 异步加载模块并避免 Vite 打包生成过大 JS 文件的关键方法:

  1. 使用 defineAsyncComponent 定义异步组件
    Vue3 提供 defineAsyncComponent 方法,允许组件在渲染时动态加载,减少初始包体积。例如:

    const AsyncComponent = defineAsyncComponent(() => import('./Component.vue'));
    

    这会将组件拆分为独立 chunk,按需加载 。

  2. 结合 Vite 的 Glob 导入批量加载
    利用 Vite 的 import.meta.glob 实现批量异步组件加载:

    const modules = import.meta.glob('./components/*.vue');
    const asyncComponents = Object.entries(modules).map(([path, loader]) => {
      const name = path.split('/').pop().replace('.vue', '');
      return { [name]: defineAsyncComponent(loader) };
    });
    

    此方法自动拆分模块,避免生成单一巨文件 。

  3. 路由懒加载
    在 Vue Router 中动态导入路由组件:

    { path: '/admin', component: () => import('./AdminPage.vue') }
    

    仅当访问路由时加载对应组件,显著减小初始包 。

  4. 动态导入(Dynamic Import)
    使用 ES6 动态导入语法异步加载模块:

    const module = await import('./module.js');
    

    结合 Webpack/Vite 的代码分割功能,自动生成独立 chunk 。

  5. 配置加载状态与错误处理
    通过 defineAsyncComponent 的选项增强用户体验:

    defineAsyncComponent({
      loader: () => import('./Component.vue'),
      loadingComponent: LoadingSpinner, // 加载中组件
      errorComponent: ErrorDisplay,     // 错误组件
      timeout: 3000                     // 超时时间
    });
    

    避免界面空白,提升交互体验 。

  6. 使用 <Suspense> 管理异步状态
    用 <Suspense> 包裹异步组件,统一处理加载和错误状态:

    <Suspense>
      <template #default> <AsyncComponent /> </template>
      <template #fallback> <LoadingSpinner /> </template>
    </Suspense>
    

    简化异步逻辑 。

总结
通过 defineAsyncComponent + Vite Glob 导入实现批量异步组件,结合路由懒加载和动态导入,将代码拆分为按需加载的小 chunk;用 <Suspense> 和错误处理优化用户体验。这些策略可减少初始包体积 60% 以上,避免生成超大 JS 文件。

一、padStart()简介

JavaScript的字符串padStart()方法用于在当前字符串的开头添加指定数量的字符,以达到指定的字符串长度。如果当前字符串的长度大于或等于指定的字符串长度,则不会添加任何字符。

二、语法

string.padStart(targetLength [, padString])

三、参数解释

targetLength:要达到的字符串长度,必须为一个正整数。

padString:可选的填充字符串,如果不指定,则默认为一个空格。

四、使用实例

下面是一些使用padStart()方法的示例:

//小于两位补零
String(date.getMonth() + 1).padStart(2, '0');

//文件名生成
for (let i = 1; i <= 10; i++) {
    const fileName = `file${i.toString().padStart(2, '0')}.txt`;
    console.log(fileName); // 输出: file01.txt file02.txt file03.txt ... file10.txt
}

五、注意事项

1、如果fillString的长度大于targetLength,则会截取fillString的前面部分。

2、如果不指定fillString,则默认为一个空格。

3、如果当前字符串的长度大于或等于目标长度,则不会添加任何字符。

六、常用在哪里

padStart()方法常用于字符串的格式化,比如在输出表格时,确保每列的宽度相同,可以使用padStart()方法在开头添加空格或其他字符。还可以用于格式化日期、时间等。

七、关于padEnd

padEnd()可以在字符串的后面进行字符补全,语法参数等都和padStart()类似。

八、padEnd注意

如果补全字符串长度不足,则从左往右不断循环补全;如果长度超出可以补全的字符长度,则从左侧尽可能补全,补不到的没办法,只能忽略,例如'zhangxinxu'.padEnd(15, {})等同于执行'zhangxinxu'.padEnd(15, '[object Object]'),最多只能补5个字符,因此,只能补'[object Object]'前5个字符,于是最后结果是:'zhangxinxu[obje'

padString参数如果不设置,则会使用普通空格' '(U+0020)代替,也就是Space空格键敲出来的那个空格。

九、padEnd案例

在JS前端我们处理时间戳的时候单位都是ms毫秒,但是,后端同学返回的时间戳则不一样是毫秒,可能只有10位,以s秒为单位。所以,我们在前端处理这个时间戳的时候,保险起见,要先做一个13位的补全,保证单位是毫秒。使用示意:

timestamp = +String(timestamp).padEnd(13, '0');

十、兼容性

padStart()padEnd()是属于ES8(ES2017)的方法,兼容性:IE不支持,Chrome版本≥57

在OpenLayers中,根据多个经纬度坐标绘制多边形是一个常见的任务。你可以使用这些坐标来创建一个ol.geom.Polygon几何对象,然后将其添加到一个矢量图层中并在地图上显示。

以下是一个基本的步骤指南,教你如何在OpenLayers中根据多个经纬度坐标绘制多边形:

  1. 引入OpenLayers库
    确保你的HTML文件中已经引入了OpenLayers的JavaScript库。
  2. 创建地图容器
    在HTML中创建一个<div>元素作为地图的容器。
  3. 初始化地图
    使用OpenLayers的API来初始化地图,并设置视图。
  4. 创建多边形几何对象
    使用给定的经纬度坐标创建一个ol.geom.LinearRing(线性环,表示多边形的外环)和一个ol.geom.Polygon(多边形)几何对象。
  5. 创建矢量特征和图层
    将多边形几何对象添加到一个新的矢量特征中,并将该特征添加到一个矢量图层中。
  6. 将矢量图层添加到地图
    将矢量图层添加到地图中,以便在地图上显示多边形。

以下是一个具体的代码示例:

<!DOCTYPE html>  
<html lang="en">  
<head>  
  <meta charset="UTF-8">  
  <title>OpenLayers Polygon Example</title>  
  <link rel="stylesheet" href="https://openlayers.org/en/latest/css/ol.css" type="text/css">  
  <script src="https://openlayers.org/en/latest/build/ol.js"></script>  
  <style>  
    #map {  
      width: 100%;  
      height: 400px;  
    }  
  </style>  
</head>  
<body>  
  <div id="map"></div>  
  <script>  
    // 初始化地图  
    var map = new ol.Map({  
      target: 'map',  
      layers: [  
        new ol.layer.Tile({  
          source: new ol.source.OSM() // 使用OpenStreetMap作为地图源  
        })  
      ],  
      view: new ol.View({  
        center: ol.proj.fromLonLat([0, 0]), // 设置地图中心点  
        zoom: 2 // 设置地图缩放级别  
      })  
    });  
  
    // 定义多边形的经纬度坐标(注意:这些坐标需要是闭合的,即第一个和最后一个坐标应该是相同的)  
    var coordinates = [  
      [0, 0], // 第一个点  
      [10, 10], // 第二个点  
      [10, -10], // 第三个点  
      [0, 0] // 闭合多边形,回到第一个点  
    ];  
  
    // 将经纬度坐标转换为OpenLayers的内部坐标系统(如果需要的话,这里已经假设是WGS84经纬度)  
    // 注意:对于简单的经纬度坐标,如果地图视图已经设置为相应的投影(如EPSG:4326),则可能不需要转换。  
    // 但通常,我们会使用EPSG:3857(Web Mercator)作为地图的投影,因此这里可能需要转换,但在这个例子中我们省略了这一步,  
    // 因为我们直接使用了可以理解的经纬度坐标,并且OpenLayers在大多数情况下能够自动处理这些坐标。  
    // 如果你的坐标不是WGS84经纬度,或者你需要确保精确的坐标转换,请使用ol.proj.transform进行转换。  
  
    // 创建线性环(LinearRing)和多边形(Polygon)几何对象  
    var linearRing = new ol.geom.LinearRing(coordinates);  
    var polygon = new ol.geom.Polygon([linearRing]);  
  
    // 创建矢量特征,并将多边形几何对象设置为其几何属性  
    var feature = new ol.Feature(polygon);  
  
    // 创建矢量源,并将特征添加到源中  
    var vectorSource = new ol.source.Vector({  
      features: [feature]  
    });  
  
    // 创建矢量图层,并将源设置为其数据源  
    var vectorLayer = new ol.layer.Vector({  
      source: vectorSource,  
      style: new ol.style.Style({  
        fill: new ol.style.Fill({  
          color: 'rgba(255, 0, 0, 0.2)' // 设置填充颜色为红色透明  
        }),  
        stroke: new ol.style.Stroke({  
          color: '#ff0000', // 设置边框颜色为红色  
          width: 2 // 设置边框宽度  
        })  
      })  
    });  
  
    // 将矢量图层添加到地图中  
    map.addLayer(vectorLayer);  
  </script>  
</body>  
</html>

在这个例子中,我们创建了一个简单的红色多边形,它根据给定的经纬度坐标绘制在地图上。请注意,坐标数组是闭合的,即第一个和最后一个坐标是相同的,这是绘制多边形时的要求。如果你的坐标数组不是闭合的,OpenLayers可能无法正确识别它为多边形。

另外,请注意坐标系统的转换。在这个例子中,我们直接使用了经纬度坐标,并且假设地图视图已经设置为能够处理这些坐标。然而,在实际应用中,你可能需要将坐标从一种投影系统转换到另一种投影系统(例如,从WGS84经纬度转换到EPSG:3857 Web Mercator投影),这可以通过ol.proj.transform函数来实现。但在上面的例子中,我们省略了这一步,因为OpenLayers在大多数情况下能够自动处理这些坐标,并且我们使用了简单的经纬度坐标。

在OpenLayers中绘制多边形是一个常见的功能,它通常通过OpenLayers提供的绘制(Draw)交互来实现。以下是一个基本的步骤指南,教你如何在OpenLayers中绘制多边形:

1. 引入OpenLayers库

首先,你需要在HTML文件中引入OpenLayers库的JavaScript文件。你可以从OpenLayers的官方网站获取最新版本的链接。

<script src="https://openlayers.org/en/latest/build/ol.js"></script>

注意:这里的链接是示例用的,你应该使用OpenLayers提供的最新版本链接。

2. 创建地图容器

在HTML文件中创建一个用于显示地图的容器。这个容器将作为OpenLayers地图的视图窗口。

<div id="map" style="width: 100%; height: 400px;"></div>

3. 初始化地图

在JavaScript代码中初始化地图对象,并设置地图的中心点和缩放级别。

var map = new ol.Map({  
  target: 'map',  
  view: new ol.View({  
    center: ol.proj.fromLonLat([0, 0]), // 设置地图中心点  
    zoom: 2 // 设置地图缩放级别  
  })  
});

4. 添加绘制交互

创建一个绘制交互对象,并将其添加到地图中。这个交互对象将允许用户在地图上绘制多边形。

var draw = new ol.interaction.Draw({  
  source: new ol.source.Vector(),  
  type: 'Polygon' // 指定绘制类型为多边形  
});  
map.addInteraction(draw);

5. 监听绘制完成事件

监听绘制完成事件,并获取绘制的多边形几何对象。你可以在这个事件处理函数中执行一些额外的操作,比如保存多边形到数据库或进行空间分析。

draw.on('drawend', function(event) {  
  var feature = event.feature;  
  var geometry = feature.getGeometry(); // 获取绘制完成后的多边形几何对象  
  // 在这里可以对多边形进行进一步处理  
});

6. 样式设置(可选)

你可以为绘制的多边形设置样式,包括填充颜色、边框颜色、边框宽度等。

var styleFunction = function(feature) {  
  return new ol.style.Style({  
    fill: new ol.style.Fill({  
      color: 'rgba(255, 255, 255, 0.2)' // 设置填充颜色  
    }),  
    stroke: new ol.style.Stroke({  
      color: '#ffcc33', // 设置边框颜色  
      width: 2 // 设置边框宽度  
    }),  
    text: new ol.style.Text({  
      font: '12px Calibri,sans-serif',  
      text: '多边形',  
      fill: new ol.style.Fill({  
        color: '#000'  
      }),  
      stroke: new ol.style.Stroke({  
        color: '#fff',  
        width: 3  
      })  
    })  
  });  
};  
  
// 将样式应用到矢量图层上  
var vectorLayer = new ol.layer.Vector({  
  source: draw.getSource(),  
  style: styleFunction  
});  
map.addLayer(vectorLayer);

注意:在上面的代码中,我们创建了一个新的矢量图层,并将绘制交互的源(source)应用到这个图层上。同时,我们为这个图层设置了一个样式函数,用于定义多边形的样式。然而,在实际应用中,你可能不需要单独创建一个矢量图层来显示绘制的多边形,而是可以直接将绘制交互的源添加到已有的矢量图层上。

通过以上步骤,你就可以在OpenLayers中绘制多边形了。当然,OpenLayers的功能非常强大,你还可以进一步自定义绘制交互的行为和样式,以满足你的具体需求。

在 Vue.js 应用中,使用 Vue Router 进行路由管理时,常常需要在不同的路由之间传递参数。Vue Router 提供了几种方式来实现路由传参,包括通过 URL 路径参数、查询参数和命名视图。以下是一些常见的方法和示例:

1. 路径参数(Route Parameters)

路径参数通常用于传递具有唯一标识意义的参数,如用户 ID、文章 ID 等。

定义路由

// router/index.js  
import Vue from 'vue';  
import Router from 'vue-router';  
import User from '@/components/User.vue';  
import Post from '@/components/Post.vue';  
  
Vue.use(Router);  
  
export default new Router({  
  routes: [  
    {  
      path: '/user/:id', // 这里的 `:id` 是一个动态段  
      name: 'User',  
      component: User  
    },  
    {  
      path: '/post/:postId', // 这里的 `:postId` 是一个动态段  
      name: 'Post',  
      component: Post  
    }  
  ]  
});

访问路由并传递参数

// 在某个组件中  
this.$router.push({ name: 'User', params: { id: 123 } });  
this.$router.push({ name: 'Post', params: { postId: 456 } });

在组件中获取参数

// User.vue  
<template>  
  <div>User ID: {{ $route.params.id }}</div>  
</template>  
  
<script>  
export default {  
  computed: {  
    userId() {  
      return this.$route.params.id;  
    }  
  }  
};  
</script>  
  
// Post.vue  
<template>  
  <div>Post ID: {{ $route.params.postId }}</div>  
</template>  
  
<script>  
export default {  
  computed: {  
    postId() {  
      return this.$route.params.postId;  
    }  
  }  
};  
</script>

2. 查询参数(Query Parameters)

查询参数通常用于传递非唯一标识意义的参数,如搜索条件、分页信息等。

定义路由(不需要特殊定义)

// router/index.js  
import Vue from 'vue';  
import Router from 'vue-router';  
import SearchResults from '@/components/SearchResults.vue';  
  
Vue.use(Router);  
  
export default new Router({  
  routes: [  
    {  
      path: '/search',  
      name: 'SearchResults',  
      component: SearchResults  
    }  
  ]  
});

访问路由并传递参数

// 在某个组件中  
this.$router.push({ name: 'SearchResults', query: { q: 'vue', page: 2 } });

在组件中获取参数

// SearchResults.vue  
<template>  
  <div>  
    <p>Search Query: {{ $route.query.q }}</p>  
    <p>Page: {{ $route.query.page }}</p>  
  </div>  
</template>  
  
<script>  
export default {  
  computed: {  
    searchQuery() {  
      return this.$route.query.q;  
    },  
    page() {  
      return this.$route.query.page;  
    }  
  }  
};  
</script>

3. 编程式导航中的 props 传参

Vue Router 还支持将路由参数作为 props 传递给组件,这样可以使组件更加解耦和可复用。

定义路由时使用 props: true

// router/index.js  
import Vue from 'vue';  
import Router from 'vue-router';  
import User from '@/components/User.vue';  
  
Vue.use(Router);  
  
export default new Router({  
  routes: [  
    {  
      path: '/user/:id',  
      name: 'User',  
      component: User,  
      props: true // 这样可以将参数作为 props 传递给 User 组件  
    }  
  ]  
});

在组件中接收 props

// User.vue  
<template>  
  <div>User ID: {{ id }}</div>  
</template>  
  
<script>  
export default {  
  props: ['id']  
};  
</script>

使用 props 传递参数时,不需要通过 $route 对象来访问参数,直接通过 props 接收即可。

总结

  • 路径参数:适用于传递具有唯一标识意义的参数,如用户 ID。
  • 查询参数:适用于传递非唯一标识意义的参数,如搜索条件。
  • 编程式导航中的 props:使组件更加解耦和可复用。

通过这些方法,你可以在 Vue.js 应用中灵活地传递和使用路由参数。

前段时间在面试的时候,被问到原子类CSS,鉴于自己这个老前端已经几年没有关注前端最新的技术了,对于“原子类”这一名词有些困扰。

事后一查,这不就是“bootstrap”的样式类的新名词嘛?老旧的东西又拿出来说。其实这都是最原始的CSS设计模式了了。

原子类CSS(Atomic CSS)是一种CSS设计模式,它将样式属性拆分为独立的、具有特定用途的类。每个类通常只包含一个样式属性,或者是一组紧密相关的样式属性的组合。通过将这些类组合在一起,可以快速构建出复杂的样式。以下是对原子类CSS的详细解析:

一、原子类CSS的核心原则

  1. 原子化:将样式分解为最小的可重用单元,即“原子”。这些原子通常是单个像素或极其微小的变化,例如颜色、大小、位置等。
  2. 可重用性:每个原子类都是独立且可重用的,可以在不同的元素和场景中重复使用。
  3. 组合性:通过组合不同的原子类,可以构建出复杂的样式和布局,而无需编写大量的定制CSS。

二、原子类CSS的优势

  1. 提高开发效率:由于原子类CSS提供了大量的预定义类,开发人员可以快速应用样式,而无需从头编写CSS代码。
  2. 减少代码冗余:通过重用原子类,可以避免在多个地方重复相同的样式代码,从而减少代码冗余。
  3. 易于维护:由于每个原子类只负责一个或少数几个样式属性,因此代码更加清晰和易于理解。当需要修改样式时,只需调整相应的原子类即可。
  4. 增强可定制性:开发人员可以根据自己的需求添加自定义的原子类,或者根据项目的需求修改现有的原子类。

三、原子类CSS的实现方式

  1. 工具与库:原子类CSS可以通过工具如Tachyons、Tailwind CSS等实现。这些工具提供了一套预定义的原子类,可以快速地应用于HTML元素。
  2. 命名约定:原子类CSS通常使用功能性的命名约定,如.mr1(外边距右侧1个单位)、.bg-red(背景颜色为红色)等。这些命名直观且易于理解。
  3. 与前端框架的集成:原子类CSS可以与各种前端框架(如Angular、React、Vue等)集成,并提供大量的插件和扩展选项。

四、原子类CSS的应用场景

  1. 快速开发:在需要快速构建Web应用程序或原型时,原子类CSS可以大大提高开发效率。
  2. 团队协作:在团队协作中,使用原子类CSS可以减少代码冲突和不一致性,因为每个开发人员都可以使用相同的预定义类来构建样式。
  3. 定制化需求:当项目需要高度定制化时,原子类CSS提供了足够的灵活性和可定制性来满足这些需求。

五、示例

以下是一个使用Tailwind CSS(一种流行的原子类CSS框架)的示例:

<div class="bg-blue-500 text-white p-4">  
  Hello Tailwind CSS!  
</div>

在这个示例中,bg-blue-500 类设置了背景颜色为蓝色,text-white 类设置了文本颜色为白色,p-4 类设置了内边距为4个单位。通过组合这些原子类,我们可以快速构建出具有特定样式的HTML元素。

综上所述,原子类CSS是一种灵活且强大的CSS设计模式,它可以帮助简化样式管理并提高代码的可维护性。通过使用原子类CSS框架(如Tailwind CSS),开发人员可以更加高效地构建和维护Web应用程序的界面。

CSS变量简介

CSS变量的定义及使用如下,可定义的类型非常广泛。

/* 声明 */
--VAR_NAME: <声明值>;
/* 使用 */
var(--VAR_NAME)

/* 根元素选择器(全局作用域),例如 <html> */
:root {
  /* CSS 变量声明 */
  --main-color: #ff00ff;
  --main-bg: rgb(200, 255, 255);
  --logo-border-color: rebeccapurple;

  --header-height: 68px;
  --content-padding: 10px 20px;

  --base-line-height: 1.428571429;
  --transition-duration: .35s;
  --external-link: "external link";
  --margin-top: calc(2vh + 20px);
}

body {
  /* 使用变量 */
  color: var(--main-color);
}

与 SASS、LESS预处理器变量的编译时处理不同,CSS 变量由浏览器在运行时处理,这使得它们更加强大和灵活。

CSS 到 JS

在 CSS 变量出现之前,将值从 CSS 传递到 JS 非常困难,甚至需要一些 hack 技巧。现在有了 CSS 变量,可以直接通过 JS 访问变量值并进行修改。

// 定义 CSS 变量
.breakpoints-data {
  --phone: 480px;
  --tablet: 800px;
}
const breakpointsData = document.querySelector('.breakpoints-data');

// 获取 CSS 变量的值
const phone = getComputedStyle(breakpointsData)
    .getPropertyValue('--phone');

// 设置 CSS 变量的新值
breakpointsData.style
    .setProperty('--phone', 'custom');

本站重构后,也合理的利用了CSS变量,但方式更加灵活。

比如本站的主题切换功能,白天与夜晚主题。在根节点root中使用多个变量组,使用不同的元素选择器进行分组。然后通过js更改元素的class类或者属性对主题进行切换。

:root {
    --wp--preset--font-size--normal: 16px;
    --wp--preset--font-size--huge: 42px;
}
:root {
    --my-preset-line-height-1: 1.8;
    --my-preset-letter-spacing-1: .04em;
    --my-preset-color-font-1: #444444;
    --my-preset-color-font-2: #333333;
    --my-preset-color-font-3: #000000;
    --my-preset-color-bg-1: #ffffff;
    --my-preset-color-bg-2: #fbfbfb;
    --my-preset-color-bg-3: #1f1e2c;
    --my-preset-color-alert-bg: #ffffff;
    --my-preset-color-button-bg-1: #ffffff;
    --my-preset-color-button-font-1: #f96b4d;
    --my-preset-color-button-bg-active: #f96b4d;
    --my-preset-color-active: #f96b4d;
    --my-preset-color-active-hover: #f5ad6e;
    --my-preset-color-font-gray-1: #9f9f9f;
    --my-preset-color-font-gray-2: #cdcdcd;
    --my-preset-color-font-gray-3: #888888;
    --my-preset-color-border-1: #d8d8d8;
    --my-preset-color-border-2: #cccccc;
    --my-preset-color-border-3: #eeeeee;
    --my-preset-color-bg-gray-1: #f8f8f8;
    --my-preset-color-bg-gray-2: #fbfbfb;
    --my-font-family-1: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Source Han Sans CN, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
    --my-font-family-2: NotoSerifSC, "Microsoft Yahei", 宋体, sans-serif;
    --my-font-family-3: 'Helvetica', 'Microsoft Yahei', '冬青黑体简体中文 w3', 微软雅黑, 'Tahoma', 'Arial', 'SimSun';
    --my-preset-color-scrollbar-thumb: #d2d2d2;
    --my-preset-color-scrollbar-track: rgba(51, 51, 51, 0.1);
}
[data-user-color-scheme='dark'] {
    --my-preset-color-bg-1: #1f1e2c;
    --my-preset-color-bg-2: #1a1927;
    --my-preset-color-bg-3: #0b0b10;
    --my-preset-color-alert-bg: #262631;
    --my-preset-color-font-1: #d5d5d5;
    --my-preset-color-font-2: #f5f5f5;
    --my-preset-color-font-3: #ffffff;
    --my-preset-color-border-3: #3f3d55;
    --my-preset-color-button-bg-1: #f96b4d;
    --my-preset-color-button-bg-active: #f5ad6e;
    --my-preset-color-button-font-1: #f5f5f5;
    --my-preset-color-bg-gray-1: #2e2d3b;
    --my-preset-color-font-gray-3: #a9a9a9;
    --my-preset-color-border-2: #aaaaaa;
    --my-preset-color-scrollbar-thumb: #3b3b5f;
    --my-preset-color-scrollbar-track: #1f1e2c;
}
export default {
  template,
  setup() {
    const colorScheme = ref(window.localStorage.getItem('user-color-scheme') || "light");
    const setColorScheme = (scheme) => {
      colorScheme.value = scheme;
      document.documentElement.setAttribute('data-user-color-scheme', scheme);
      window.localStorage.setItem('user-color-scheme', scheme);
    };

    return { colorScheme, setColorScheme };
  },
};

除此之外还有很多 css 原生能力,比如:Mixins、运算符等。