彻底理解 CSS :has() – 告别父选择器难题 – wiki基地


彻底理解 CSS :has() – 告别父选择器难题

在 CSS 的世界里,我们习惯于从父级向下选择元素,比如选择某个 div 内部的 p 标签 (div p),或者选择某个元素的直接子元素 (ul > li),或者选择某个元素后面的兄弟元素 (h2 + p)。然而,长期以来,CSS 的一个显著痛点是缺乏一个直接的“父选择器”——即根据其子元素或后代元素的特性来选择父元素或祖先元素的能力。

试想一下,你想要给一个列表 (<ul>) 添加一个特定的边框,如果这个列表包含一个带有 .active 类的列表项 (<li>);或者你想要让一个表单容器 (<form>) 显示一个醒目的轮廓,如果它内部的任何一个输入框 (<input>) 是无效的 :invalid 状态;再或者,你希望当鼠标悬停在一个图片 (<img>) 上时,改变其父容器 (<div>) 的背景颜色。在 :has() 出现之前,实现这些效果通常需要借助 JavaScript 来动态添加或移除类,或者通过调整 HTML 结构来利用现有的兄弟选择器,这些方法往往不够优雅、不够纯粹,增加了代码的复杂性和维护成本。

好消息是,CSS 终于迎来了革命性的 :has() 伪类。它被形象地称为“家庭选择器”(Family Selector),因为它允许我们基于元素内部或附近的元素状态来选择该元素本身。:has() 的出现,彻底终结了长期困扰我们的“父选择器”难题,并开启了 CSS 选择能力的新纪元。

本文将带您彻底理解 :has() 伪类,包括它的语法、工作原理、丰富的应用场景、浏览器支持以及一些注意事项。

1. 过去的难题:为什么需要“父选择器”?

在深入 :has() 之前,让我们先回顾一下在它到来之前,我们是如何解决那些依赖“子元素状态来选择父元素”的场景,以及这些方法的局限性。

传统的 CSS 选择器,如:

  • 后代选择器 (Descendant Selector): A B – 选择所有作为 A 的后代的 B。
  • 子代选择器 (Child Selector): A > B – 选择所有作为 A 的直接子代的 B。
  • 相邻兄弟选择器 (Adjacent Sibling Selector): A + B – 选择紧跟在 A 后面的第一个 B。
  • 通用兄弟选择器 (General Sibling Selector): A ~ B – 选择所有跟在 A 后面的 B(不必紧跟)。

这些选择器都是从一个元素出发,向下或向后遍历 DOM 树来查找匹配的元素。这种“从上往下”或“向前向后”的选择模式是 CSS 的基础。

然而,当需求变成“如果满足某个条件,请选择这个元素的父级祖先级”时,问题就出现了。例如:

  • 场景 1: 样式化一个 div如果它包含一个 <img>
    html
    <div class="card">
    <img src="image.jpg">
    <p>这是一段文字</p>
    </div>
    <div class="card">
    <p>这是一段文字</p>
    </div>

    我们想给第一个 .card 添加一个特殊样式,因为它有图片。用传统 CSS 无法直接做到 img 的父级 div 这种选择。

  • 场景 2: 样式化一个 <ul> 列表,如果其中存在一个 .completed<li>
    html
    <ul>
    <li>任务 1</li>
    <li class="completed">任务 2 (已完成)</li>
    <li>任务 3</li>
    </ul>
    <ul>
    <li>任务 A</li>
    <li>任务 B</li>
    </ul>

    我们想给第一个 <ul> 添加样式,因为它有一个已完成的任务项。同样,无法直接实现 有 .completed 列表项的 ul 这种选择。

  • 场景 3: 当一个输入框 :invalid 时,样式化其包裹的 .form-group 容器。
    html
    <div class="form-group">
    <label>用户名:</label>
    <input type="text" required value="">
    </div>
    <div class="form-group">
    <label>邮箱:</label>
    <input type="email" value="[email protected]">
    </div>

    我们想让第一个 .form-group 在输入框为空且 required 生效时(即 :invalid 时)有警告样式。传统 CSS 无法从 :invalid 状态的 input 回溯选择其父级 div.form-group

以前的 Workarounds (权宜之计):

  1. JavaScript: 最常见的方法是使用 JavaScript。当子元素状态改变时(例如,图片加载完成、列表项被标记为完成、输入框验证状态改变),通过 JS 查找其父元素,然后为其添加或移除一个辅助类。
    javascript
    const input = document.querySelector('input[required]');
    input.addEventListener('input', () => {
    const formGroup = input.closest('.form-group');
    if (input.validity.valid) {
    formGroup.classList.remove('is-invalid');
    } else {
    formGroup.classList.add('is-invalid');
    }
    });

    然后 CSS 就可以这样写:.form-group.is-invalid { border-color: red; }。这种方法有效,但需要编写额外的 JS 代码,与样式逻辑耦合,增加了页面的复杂性和 JS 的工作负担。

  2. HTML 结构调整 + 兄弟选择器: 在某些有限的情况下,可以通过调整 HTML 结构,让需要被样式化的父元素变成子元素的兄弟元素,然后利用兄弟选择器。但这往往违背语义,使 HTML 结构不合理,不具通用性。例如,为了让一个元素根据其子元素的状态改变样式,你不得不将子元素放在父元素的后面,这显然是本末倒置的。

  3. 伪类 :empty + :not(): 在判断父元素是否为空时,可以使用 div:empty。但判断“是否包含某个特定子元素”则非常困难或不可能。div:not(:empty) 可以判断非空,但无法判断具体内容。

这些权宜之计都表明了对一个原生“父选择器”能力的强烈需求。而 :has() 正是来填补这个空白的。

2. :has() 来了! – 解决父选择器难题

:has() 是一个结构性伪类(structural pseudo-class),它允许你选择一个元素,如果它内部(作为后代或直接子代)匹配了指定的相对选择器列表中的任何一个

基本语法:

css
:selector:has(relative-selector-list) {
/* styles */
}

  • :selector:这是你想要选择的主体元素。这个元素会被选中,如果它满足 :has() 中的条件。
  • :has(...):这是伪类本身。
  • relative-selector-list:这是一个或多个选择器的列表,用逗号分隔。这些选择器是相对于 :selector后代或自身进行匹配的。:selector 将会被选中,如果它的后代中(或者它本身,尽管这不太常用且需要理解相对性)有任何一个元素匹配 relative-selector-list 中的至少一个选择器。

让我们用 :has() 来解决之前提到的场景:

  • 场景 1 解决: 样式化一个 div如果它包含一个 <img>
    css
    /* 选择所有包含 img 元素的 .card */
    .card:has(img) {
    border: 2px solid gold; /* 示例样式 */
    }

    现在,第一个 .card 会有金色的边框,第二个则不会。这正是我们想要的“如果子元素是 img,则选择父元素 div”。

  • 场景 2 解决: 样式化一个 <ul> 列表,如果其中存在一个 .completed<li>
    css
    /* 选择所有包含 .completed 类的 li 元素的 ul */
    ul:has(.completed) {
    background-color: #e0ffe0; /* 示例样式:浅绿色背景 */
    padding: 1em;
    }

    第一个 <ul> 会有浅绿色背景和内边距,第二个则不会。

  • 场景 3 解决: 当一个输入框 :invalid 时,样式化其包裹的 .form-group 容器。
    css
    /* 选择所有包含处于无效状态的 input 元素的 .form-group */
    .form-group:has(input:invalid) {
    outline: 2px solid red; /* 示例样式:红色轮廓 */
    outline-offset: 4px;
    }

    第一个 .form-group 在输入框验证失败时会显示红色轮廓,第二个则不会。

正如这些例子所示,:has() 的出现使得“父选择器”的能力触手可及,大大简化了依赖子元素状态进行样式化的场景。

3. 深入了解 :has() 的语法和能力

:has() 的强大之处远不止选择直接父元素。它的参数 relative-selector-list 可以是任意复杂的相对选择器,这赋予了它令人惊叹的灵活性。

3.1 相对选择器列表 (Relative Selector List):

relative-selector-list 中的选择器是相对于 :selector(即 :has() 之前的主体元素)进行评估的。它们会尝试匹配 :selector 的后代或自身。

  • 简单选择器: 可以是类型选择器 (p)、类选择器 (.class)、ID 选择器 (#id)、属性选择器 ([attribute]) 等。
    css
    article:has(h1) { /* 选择包含 h1 的文章 */ }
    div:has(.icon-warning) { /* 选择包含 .icon-warning 元素的 div */ }
    li:has([data-selected]) { /* 选择包含带有 data-selected 属性的元素的 li */ }

  • 组合器 (Combinators): 可以使用后代 ()、子代 (>)、相邻兄弟 (+)、通用兄弟 (~) 组合器。需要注意的是,这些组合器是相对于 :selector后代进行匹配的。

    • div:has(> p): 选择包含直接子元素pdiv
    • section:has(article > h2): 选择包含一个 article 且该 article直接子元素h2section
    • ul:has(li + li): 选择包含至少两个 li 元素的 ul (因为 li + li 需要匹配到第二个或后续的 li)。
    • div:has(p ~ span): 选择包含一个 p 元素,并且该 p 元素后面有兄弟 span 元素的 div

    一个常见的误解是尝试用 :has() 和兄弟选择器来模拟选择 父元素 后面的 兄弟元素。例如,h2:has(+ p) 是选择紧跟在 h2 后面的 p 的父元素。 :has() 总是检查其主体元素 (h2 在这个例子中) 的后代。h2:has(+ p) 将尝试在 h2 的后代中找到一个紧跟在 h2 后面的兄弟 p,这在正常的 DOM 结构中是不可能的,因为它会尝试将 h2 自身作为起点查找兄弟。如果你想选择紧跟在 h2 后面的 p父元素,你可以尝试从一个共同的祖先开始:parentElement:has(h2 + p)

  • 伪类 (Pseudo-classes): 这是 :has() 真正强大的地方。可以将各种状态伪类、结构性伪类等放入 :has() 中。

    • 状态伪类:

      • a:has(:hover): 选择当其后代被悬停时处于悬停状态的链接 a(这通常与链接自身被悬停效果类似,但更通用)。
      • div:has(img:hover): 选择当其后代 img 被悬停时处于悬停状态的 div。这是一个非常实用的父级悬停效果!
      • form:has(:focus-within): 选择包含处于 :focus:focus-within 状态元素的表单。这比单独使用 :focus-within 在某些场景下更灵活。
      • select:has(option:checked): 选择包含被选中 optionselect
      • div:has(input[type="checkbox"]:checked): 选择包含被勾选的 checkbox 的 div
      • section:has(:valid) / section:has(:invalid): 选择包含有效/无效表单控件的 section。
    • 结构性伪类:

      • ul:has(li:first-child): 选择包含至少一个 li (因为有第一个 li) 的 ul。这基本上等同于 ul:has(li)
      • ol:has(li:last-child): 选择包含至少一个 liol
      • div:has(p:only-child): 选择其直接子元素只有一个且是 pdiv
      • nav:has(:nth-child(5n+1)) / nav:has(:nth-last-child(...)): 选择包含特定序号子元素的 nav
      • table:has(tr:nth-child(even)) / table:has(tr:nth-child(odd)): 选择包含偶数/奇数行的表格。
      • div:has(:empty): 选择包含空元素(无子元素或文本节点)的 div
    • :not() 伪类在 :has() 中: 可以在 :has() 中使用 :not() 来实现“不包含”的逻辑。

      • div:has(:not(img)): 选择包含 img 元素的 div(只要不是所有后代都是 img 就可以,这可能不是你真正想要的)。
      • div:has(p:not(.intro)): 选择包含一个 p 元素,且该 p 元素带有 .intro 类的 div
      • ul:has(li:not(:last-child)): 选择包含一个非最后一个 li 元素的 ul。实际上,任何包含多于一个 liul 都满足这个条件。
      • ul:not(:has(li)): 选择不包含 li 元素的 ul (即空列表)。这是判断父元素是否为空或不包含特定子元素的标准方法。
  • 伪元素 (Pseudo-elements): :has() 不能直接将伪元素作为其参数。例如,div:has(p::first-line) 是无效的。你选择的是包含某个元素的父元素,而不是包含某个伪元素的父元素。你可以选择包含 pdiv (div:has(p)),然后独立地样式化 p::first-line

3.2 组合和链式使用 :has()

:has() 可以与其他选择器组合使用,也可以多次出现在同一个选择器中:

  • 与其他选择器组合:

    • .container:hover:has(.item): 只有当鼠标悬停在 .container 上,并且 .container 包含一个 .item 时,才选中 .container
    • a.button:has(span): 选择带有 .button 类,并且包含 span 的链接 a
  • 链式 :has(): :has() 的参数本身也可以包含 :has() (理论上,虽然可能导致非常复杂的选择器)。

    • section:has(article:has(h1)) : 选择包含一个 article 元素,且该 article 元素又包含一个 h1 元素的 section

    更常见的是在同一选择器中多次使用 :has() 来表达更复杂的“且”关系:
    * .card:has(img):has(.caption): 选择同时包含 img 元素 .caption 元素的 .card

3.3 特定性 (Specificity)

:has() 伪类的特定性比较特殊。它本身的特定性是 0,0,0。然而,它会继承其最具体的参数的特定性。

  • div:has(img): 特异性由 div (0,0,1) 和 img (0,0,1) 决定。:has() 的特定性是其最具体参数 img 的特定性 (0,0,1)。因此,整个选择器 div:has(img) 的特定性是 div (0,0,1) + :has() 继承的 img 的特定性 (0,0,1) = 0,0,2
  • .card:has(img): 特定性由 .card (0,1,0) 和 img (0,0,1) 决定。:has() 的特定性是 .card (0,1,0) 和 img (0,0,1) 中最具体的 .card 的特定性 (0,1,0)。因此,整个选择器 .card:has(img) 的特定性是 .card (0,1,0) + :has() 继承的 .card 的特定性 (0,1,0) = 0,2,0
  • ul:has(li.completed): 特定性由 ul (0,0,1) 和 li.completed (0,1,1) 决定。:has() 继承 li.completed 的特定性 (0,1,1)。整个选择器的特定性是 ul (0,0,1) + :has() 继承的 li.completed 特定性 (0,1,1) = 0,1,2
  • .form-group:has(input:invalid): 特定性由 .form-group (0,1,0) 和 input:invalid (0,1,1) 决定。:has() 继承 input:invalid 的特定性 (0,1,1)。整个选择器的特定性是 .form-group (0,1,0) + :has() 继承的 input:invalid 特定性 (0,1,1) = 0,2,1

记住这个规则::has() 的特定性是其自身主体选择器(:selector)的特定性 加上 其参数 relative-selector-list最具体的选择器的特定性。这有助于理解在使用 :has() 时,哪些规则会最终生效。

4. 丰富的应用场景示例

:has() 的能力解锁了大量以前难以或无法纯 CSS 实现的交互和布局模式。以下是一些更详细的应用示例:

4.1 根据内容调整父容器样式

  • 带图片的卡片样式:
    html
    <div class="card">
    <img src="..." alt="...">
    <div class="content">...</div>
    </div>
    <div class="card">
    <div class="content">...</div>
    </div>

    css
    .card { /* 基础卡片样式 */ }
    .card:has(img) {
    /* 如果卡片有图片,调整布局或样式 */
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: 1em;
    border-left: 5px solid skyblue;
    }
    .card:not(:has(img)) {
    /* 如果卡片没有图片 */
    background-color: #f0f0f0;
    }

  • 列表状态指示:
    html
    <h2>待办事项</h2>
    <ul>
    <li>购物</li>
    <li>写代码</li>
    <li class="completed">读文章</li>
    </ul>
    <h2>已完成事项</h2>
    <ul>
    <li class="completed">跑步</li>
    <li class="completed">吃饭</li>
    </ul>
    <h2>空列表</h2>
    <ul></ul>

    css
    /* 如果列表包含已完成项,给列表添加一个特殊的下划线 */
    ul:has(.completed) {
    text-decoration: underline wavy green;
    }
    /* 如果列表是空的 */
    ul:not(:has(li)) {
    opacity: 0.7;
    border: 1px dashed grey;
    padding: 1em;
    font-style: italic;
    }

  • 表格行状态:
    html
    <table>
    <tr><td>数据 A</td><td>数据 B</td><td>数据 C</td></tr>
    <tr><td>数据 D</td><td class="error">数据 E (错误)</td><td>数据 F</td></tr>
    <tr><td>数据 G</td><td>数据 H</td><td>数据 I</td></tr>
    </table>

    css
    /* 如果某行包含 .error 单元格,整行标红 */
    tr:has(.error) {
    background-color: #ffebeb;
    color: #c0392b;
    }

4.2 基于子元素状态的父级交互效果

  • 子元素 Hover 影响父元素:
    html
    <div class="item-container">
    <img src="thumbnail.jpg" alt="缩略图">
    <span>商品名称</span>
    </div>

    css
    .item-container {
    border: 1px solid #ccc;
    transition: border-color 0.3s ease;
    }
    /* 当内部的 img 被悬停时,改变父容器的边框颜色 */
    .item-container:has(img:hover) {
    border-color: blue;
    }

  • 输入框 Focus 影响父级表单组:
    html
    <div class="form-group">
    <label for="name">姓名:</label>
    <input type="text" id="name">
    </div>

    css
    .form-group {
    padding: 1em;
    border: 1px solid transparent;
    }
    /* 当内部的 input 或 label 被 focus 时,给父级 form-group 添加轮廓 */
    /* 注意:这里 :focus-within 会更直接,但 :has(:focus) 也能实现类似效果,
    或者更复杂的 .form-group:has(input:focus), .form-group:has(textarea:focus) 等 */
    .form-group:has(:focus) {
    outline: 2px solid blue;
    outline-offset: 2px;
    border-color: transparent; /* 移除原边框避免冲突 */
    }

    结合 :valid, :invalid, :required:
    css
    .form-group:has(input:invalid:focus) { /* 无效且聚焦时 */
    outline-color: red;
    }
    .form-group:has(input:valid:focus) { /* 有效且聚焦时 */
    outline-color: green;
    }

  • 复选框/单选框状态影响兄弟内容:
    一个非常强大的模式是利用 :has() 结合通用兄弟选择器 (~) 来根据 checkbox 或 radio 的状态显示/隐藏相邻内容,无需 JavaScript。
    html
    <div>
    <label>
    <input type="checkbox" id="showExtra"> 显示额外选项
    </label>
    </div>
    <div class="extra-options">
    <!-- 只有当上面的 checkbox 被勾选时才显示 -->
    <p>这是额外的设置。</p>
    <button>保存</button>
    </div>

    “`css
    .extra-options {
    display: none; / 默认隐藏 /
    margin-top: 1em;
    padding: 1em;
    border: 1px dashed #ccc;
    }

    / 当 checkbox 的父级 div 包含一个被勾选的 checkbox 时,选择这个 div 的通用兄弟 .extra-options,并显示它 /
    div:has(input[type=”checkbox”]:checked) ~ .extra-options {
    display: block;
    }
    “`
    这个模式非常灵活,你可以选择 checkbox 的直接父级、某个祖先级,然后利用兄弟选择器去影响 DOM 中任意位置的兄弟元素。

4.3 复杂的结构性选择

  • 选择至少包含 N 个子元素的父元素:
    css
    /* 选择至少有 3 个列表项的 ul */
    ul:has(li:nth-child(3)) {
    border-top: 2px dashed purple;
    }
    /* 选择至少有 5 个直接子元素的 div */
    div:has(> :nth-child(5)) {
    background-color: #f5f5f5;
    }

  • 选择包含特定兄弟组合的父元素:
    html
    <div class="gallery">
    <img src="img1.jpg">
    <img src="img2.jpg">
    <span class="caption">图片集</span>
    <img src="img3.jpg">
    </div>
    <div class="gallery">
    <img src="img1.jpg">
    <span class="caption">单图</span>
    </div>

    css
    /* 选择包含 img 并且 img 后跟着一个 .caption 元素的 gallery */
    /* 注意这里的相对选择器 img ~ .caption 是相对于 .gallery 的后代来匹配 */
    .gallery:has(img ~ .caption) {
    border: 2px solid orange; /* 适用于第一个 gallery */
    }

4.4 替换某些 JavaScript 或 HTML 结构调整

很多之前需要 JS 监听事件并操作类名的场景,现在都可以用 :has() 纯 CSS 实现。这不仅减少了 JS 代码量,还让样式与结构更贴近,提高了可维护性。同时,也避免了为了满足 CSS 选择器要求而进行的非语义化的 HTML 结构调整。

5. 浏览器支持与性能

:has() 是一个相对较新的 CSS 特性。在撰写本文时(截至 2023/2024 年初),它的主要浏览器支持情况如下:

  • Chrome: 105+ 支持
  • Firefox: 105+ 支持
  • Safari: 14.5+ 支持 (最初在 Safari 14.5 中作为技术预览版引入,后续版本稳定支持)
  • Edge: 105+ 支持

可以说,:has() 在现代主流浏览器中已经获得了广泛的支持。然而,对于需要兼容老旧浏览器版本的项目,可能仍然需要采用回退方案(如使用 JS)。在使用时,建议查看 Can I Use (caniuse.com/?search=:has) 获取最准确和最新的支持信息。

性能考虑:

历史上,“父选择器”难以实现的一个重要原因在于浏览器的 CSS 匹配引擎通常是从右向左(或从下往上)遍历选择器。例如,div p 会先找到所有的 p 元素,然后向上检查它们的父元素是否是 div。这种方式对于“父选择器”是低效的,因为要确定一个父元素是否匹配 :has(),浏览器需要先检查其所有的后代。

浏览器厂商在实现 :has() 时投入了大量的优化工作。对于大多数常见的 :has() 用法(例如,:has(.class), :has(> element), :has(:state)),现代浏览器的性能已经非常优秀,与传统选择器相差无几,甚至优于需要 DOM 操作的 JavaScript 方案。

然而,过于复杂或深层嵌套的 :has() 选择器,尤其是在大型或频繁变动的 DOM 结构中,可能会带来一定的性能开销。例如:

  • container:has(.level-1 > .level-2:has(.level-3 ~ .level-4:has(:hover))) 这种深度嵌套且复杂的选择器,在每次 :hover 状态改变时,浏览器都需要从 :hover 的元素向上回溯,再检查 :has() 的条件,这可能会消耗更多资源。

总的来说,对于日常开发中的常见场景,:has() 的性能是完全可以接受的,且其带来的代码简洁性和可维护性优势往往 outweighs 潜在的微小性能差异。 如果在特定场景下遇到性能瓶颈,才应该考虑简化 :has() 选择器或寻找替代方案。

6. 局限性与注意事项

尽管 :has() 功能强大,但仍有一些限制和需要注意的地方:

  • 不能包含伪元素: 正如前面提到的,你不能在 :has() 中直接选择伪元素 (::before, ::after, ::first-line, ::first-letter, ::marker, ::selection)。div:has(p::first-line) 是无效的。
  • 不能选择文本节点: CSS 选择器本身就不能直接选择文本节点,:has() 也继承了这个限制。你只能选择包含文本节点的元素 (div:has(p)),而不能选择包含特定文本内容的父元素 (div:has("some text") 是无效的)。
  • 特定性规则: 理解 :has() 的特定性计算方式(继承最具体参数的特定性)非常重要,否则可能会遇到样式覆盖的问题。
  • 复杂性: 虽然 :has() 可以解决复杂问题,但滥用或编写过于复杂的 :has() 选择器可能会降低 CSS 代码的可读性和理解难度。适度且清晰地使用它。
  • 浏览器支持: 尽管支持越来越好,但在面向全球用户或需要兼容旧环境的应用中,仍需考虑回退策略或渐进增强。

7. 总结

CSS :has() 伪类是自 Flexbox 和 Grid 以来最重要的 CSS 新特性之一。它直接且优雅地解决了长期以来困扰开发者的“父选择器”难题,极大地扩展了 CSS 的选择能力。

通过 :has(),我们可以:

  • 根据子元素的类型、类名、属性、状态等来选择父元素或祖先元素。
  • 实现基于子元素状态(如 hover, focus, checked, valid/invalid, empty 等)的父级样式调整和交互效果。
  • 在许多场景下替代繁琐的 JavaScript 代码,实现纯 CSS 的动态样式控制。
  • 创建更具表现力、更简洁、更易于维护的 CSS 代码。
  • 让 HTML 结构更专注于语义,而不是为了满足样式需求而进行妥协。

:has() 并不是简单地引入了一个“父选择器”,而是提供了一个更通用的“根据内容或相对位置选择主体元素”的能力。它的出现标志着 CSS 选择器从单一的向下/向后遍历,迈向了更灵活、更具上下文感知能力的阶段。

掌握并善加利用 :has(),将能显著提升您的 CSS 技能和开发效率。从今天起,告别那些蹩脚的父选择器 workaround,拥抱强大的 :has() 吧!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部