移动端导航通知栏

通知栏NoticeBar

在页面顶部或内容区之间横向滚动展示一条通告(如「系统维护通知」)

反馈 规格 16 · 通知栏 NoticeBar 6 端实现 触摸优先

引入(H5 原生;其余 5 端见「快速开始」)
<!-- ① 令牌:PC 令牌 + 移动端 --kole-m-* 合成单文件,引一次 -->
<link rel="stylesheet" href="kole-ui/mobile/tokens.css">

<!-- ② 本组件样式(全量则用 kole-ui/mobile/components/index.css) -->
<link rel="stylesheet" href="kole-ui/mobile/components/mobile-noticebar.css">

<!-- ③ 结构照抄下方任一演示块(类名与 6 端实现一致) -->

演示

每个演示都是真实渲染:预览帧加载 frameworks-mobile/NoticeBar.html?demo=<id>(只显示该演示块),代码是该演示块在演示页里的原文,可复制。全部演示同屏可看 演示页 ↗。

01 组件类型

基础通告tone=default

品牌浅底 + 同族深字;容器带 role=status,读屏会朗读一次。

查看代码(演示页原文 · 7 行)
frameworks-mobile/NoticeBar.html · basic
<section class="demo-block" data-demo="basic">
  <p class="demo-label">基础通告(tone=default:品牌浅底,container 带 role=status 朗读一次)</p>
  <div class="kole-m-noticebar kole-m-noticebar--default" role="status" data-assert="noticebar-basic">
    <span class="kole-m-noticebar__icon" aria-hidden="true">▸</span>
    <span class="kole-m-noticebar__text">系统将于今晚 23:00 进行例行维护,预计 30 分钟。</span>
  </div>
</section>
可关闭closable=true

关闭按钮是原生 button、24×24 热区,aria-label 带上通告内容以便区分。

查看代码(演示页原文 · 9 行)
frameworks-mobile/NoticeBar.html · closable
<section class="demo-block" data-demo="closable">
  <p class="demo-label">可关闭(closable=true:关闭按钮是原生 button,热区 24×24,点后打标记)</p>
  <div class="kole-m-noticebar kole-m-noticebar--warning" id="noticebar-closable" role="status" data-assert="noticebar-closable">
    <span class="kole-m-noticebar__icon" aria-hidden="true">!</span>
    <span class="kole-m-noticebar__text">当前有 3 笔订单即将超时,请尽快处理。</span>
    <button class="kole-m-noticebar__close" type="button" aria-label="关闭通告:订单超时提醒"
            data-behavior="click-sets-attr:#noticebar-closable|data-closed|true">✕</button>
  </div>
</section>
跑马灯scrollable=true

文案超宽时横向循环滚动;系统开启「减少动态效果」时停止滚动并改为换行。

查看代码(演示页原文 · 12 行)
frameworks-mobile/NoticeBar.html · scrollable
<section class="demo-block" data-demo="scrollable">
  <p class="demo-label">跑马灯(scrollable=true:文字超宽时横向滚动;reduced-motion 下停止并换行)</p>
  <div class="kole-m-noticebar kole-m-noticebar--scrollable" role="status" data-assert="noticebar-scrollable">
    <span class="kole-m-noticebar__icon" aria-hidden="true">▸</span>
    <span class="kole-m-noticebar__text">
      <span class="kole-m-noticebar__track">
        <span class="kole-m-noticebar__seg">平台升级公告:新增批量导入与导出能力,历史数据无需迁移;如遇问题请联系客服。</span>
        <span class="kole-m-noticebar__seg kole-m-noticebar__seg--dup" aria-hidden="true">平台升级公告:新增批量导入与导出能力,历史数据无需迁移;如遇问题请联系客服。</span>
      </span>
    </span>
  </div>
</section>

02 组件状态

语义语气tone=success|danger

成功与失败语气用同族浅底,颜色之外还有图标区分,不只靠色相。

查看代码(演示页原文 · 11 行)
frameworks-mobile/NoticeBar.html · tone
<section class="demo-block" data-demo="tone">
  <p class="demo-label">四种语气(tone=success / danger:浅底 + 同族深字,亮暗两态都达标)</p>
  <div class="kole-m-noticebar kole-m-noticebar--success" role="status" data-assert="noticebar-tone">
    <span class="kole-m-noticebar__icon" aria-hidden="true">✓</span>
    <span class="kole-m-noticebar__text">本月账单已结清,感谢使用。</span>
  </div>
  <div class="kole-m-noticebar kole-m-noticebar--danger" role="status">
    <span class="kole-m-noticebar__icon" aria-hidden="true">!</span>
    <span class="kole-m-noticebar__text">账号存在异地登录,建议立即修改密码。</span>
  </div>
</section>

API

props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs

Props

名称类型默认值说明必传
tone'default' | 'success' | 'warning' | 'danger''default'变体 tone:四种语气(浅底 + 同族深字)(规格 §16.3)N
scrollablebooleanfalse变体 scrollable:文字超宽时是否跑马灯(规格 §16.3)N
closablebooleanfalse变体 closable:是否显示关闭按钮(规格 §16.3)N
textstring''通告文字(规格 §16.2 text)N

「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。

事件

名称参数说明
close—点击关闭按钮时触发;是否移除这条通告由宿主决定(规格 §16.5)

插槽

名称说明
default通告内容(与 text 二选一;text 优先)

CSS 变量

组件级变量(在组件样式表里定义)。业务侧可在自己的作用域内覆盖,不必改组件源码。

名称默认值说明
--kole-m-noticebar-icon-size16px组件内部默认值,可在业务侧覆盖

何时使用

  • 在页面顶部或内容区之间横向滚动展示一条通告(如「系统维护通知」)
  • 关闭按钮热区不小于 24×24(用负外边距扩展)
  • 跑马灯在 prefers-reduced-motion: reduce 下停止滚动并改为换行显示(无障碍硬要求)
  • 关闭动作只触发一次事件,是否移除由宿主决定
  • 容器 role="status"(读屏会朗读一次通告)
  • 跑马灯不得用 aria-live(滚动内容反复朗读会干扰)

交互与触控

  • 关闭按钮热区不小于 24×24(用负外边距扩展)
  • 跑马灯在 prefers-reduced-motion: reduce 下停止滚动并改为换行显示(无障碍硬要求)
  • 关闭动作只触发一次事件,是否移除由宿主决定

无障碍

  • 容器 role="status"(读屏会朗读一次通告)
  • 关闭按钮为原生 button 并带 aria-label
  • 跑马灯不得用 aria-live(滚动内容反复朗读会干扰)

从「该用哪一个」的角度区分;PC 端的对应实现见 PC 文档站。

组件何时用它而不是本组件
轻提示Toast一次性结果反馈用轻提示;需要常驻在页面里的通告才用通知栏
标签Tag很短的状态词用标签;整句通告用通知栏
分割线Divider通知栏与内容区之间靠浅底与留白分隔,不必再插分割线

规格未定 / 禁止发明

类别条目
禁止发明多条通告的排队 / 轮播
禁止发明滚动速度的可配置项
规格未定通告文字的最大长度
规格未定是否支持富文本

结构(anatomy)

字段说明
noticebar根容器
noticebar__icon左侧喇叭图标
noticebar__text通告文字(可滚动)
noticebar__close可选关闭按钮

变体维度与类名映射

类名映射由构建脚本从契约 variantClasses 生成,并被 verify:mobile-docs 逐条对照组件 CSS 校验(类/变量必须真实存在)。

维度取值对应类名 / 变量
tonedefault / success / warning / danger
default .kole-m-noticebar--default
success .kole-m-noticebar--success
warning .kole-m-noticebar--warning
danger .kole-m-noticebar--danger
scrollablefalse / true
false (由数据驱动,无专属类)
true .kole-m-noticebar--scrollable
closablefalse / true
false (由数据驱动,无专属类)
true .kole-m-noticebar__close

代表变体

变体标签
tone=default · scrollable=false · closable=false基础通告
tone=warning · scrollable=false · closable=true可关闭提醒
tone=default · scrollable=true · closable=false跑马灯长通告
tone=success · scrollable=false · closable=false成功语气

用到的令牌

构建时从本组件样式表扫描得出。蓝色为移动端自有令牌,绿色为继承的 PC 令牌(改一处两端生效)。

--kole-m-font-size-caption --kole-m-font-size-label --kole-m-gutter --kole-m-touch-target --kole-color-brand --kole-color-brand-bg --kole-color-error --kole-color-focus-ring --kole-color-success --kole-color-table-header-bg --kole-color-tag-green-bg --kole-color-warning --kole-font-family --kole-space-48 --kole-space-8 --kole-m-noticebar-icon-size

6 端源码

同一组件的六份实现(生产环境的类名与结构一致,差异只在技术栈写法与单位)。点开查看,右侧可复制。

frameworks-mobile/NoticeBar.css · 纯样式(CSS) · 102 行
frameworks-mobile/NoticeBar.css
/* Kole UI Mobile · NoticeBar 样式 — 对齐移动端规格 §16
   通知栏:浅底 + 同族字色;跑马灯在 prefers-reduced-motion: reduce 下停止滚动并换行显示。 */

.kole-m-noticebar {
  --kole-m-noticebar-icon-size: 16px;
  display: flex;
  align-items: center;
  gap: var(--kole-space-8);
  box-sizing: border-box;
  min-height: var(--kole-m-touch-target);
  padding: var(--kole-space-8) var(--kole-m-gutter);
  background: var(--kole-color-brand-bg);
  color: var(--kole-color-brand);
  font-family: var(--kole-font-family);
  font-size: var(--kole-m-font-size-label);
  line-height: 1.4;
  overflow: hidden;
}

/* 变体 tone:四种语气(浅底 + 同族深字) */
.kole-m-noticebar--default { background: var(--kole-color-brand-bg); color: var(--kole-color-brand); }
.kole-m-noticebar--success { background: var(--kole-color-tag-green-bg); color: var(--kole-color-success); }
.kole-m-noticebar--warning { background: var(--kole-color-table-header-bg); color: var(--kole-color-warning); }
.kole-m-noticebar--danger { background: var(--kole-color-table-header-bg); color: var(--kole-color-error); }

.kole-m-noticebar__icon {
  flex: 0 0 auto;
  font-size: var(--kole-m-noticebar-icon-size);
  line-height: 1;
}

.kole-m-noticebar__text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* 变体 scrollable=true:文字超宽时跑马灯(内容用两段拼接实现无缝循环) */
.kole-m-noticebar--scrollable .kole-m-noticebar__text {
  text-overflow: clip;
}

.kole-m-noticebar--scrollable .kole-m-noticebar__track {
  display: inline-flex;
  gap: var(--kole-space-48);
  padding-inline-end: var(--kole-space-48);
  white-space: nowrap;
  animation: kole-m-noticebar-scroll 12s linear infinite;
}

@keyframes kole-m-noticebar-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* 无障碍硬要求:减少动态偏好下停止滚动并允许换行(规格 §16.5) */
@media (prefers-reduced-motion: reduce) {
  .kole-m-noticebar--scrollable .kole-m-noticebar__text {
    white-space: normal;
  }
  .kole-m-noticebar--scrollable .kole-m-noticebar__track {
    display: block;
    animation: none;
    padding-inline-end: 0;
  }
  /* 滚动形态下第二段是循环占位,静止时不该出现 */
  .kole-m-noticebar--scrollable .kole-m-noticebar__track > .kole-m-noticebar__seg--dup {
    display: none;
  }
}

/* 关闭按钮:原生 button,24×24 热区(负外边距吸收,不撑高栏体) */
.kole-m-noticebar__close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 24px;
  height: 24px;
  margin-inline-end: calc(-1 * var(--kole-space-8));
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: inherit;
  font-family: inherit;
  font-size: var(--kole-m-font-size-caption);
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}

.kole-m-noticebar__close:focus-visible {
  outline: 2px solid var(--kole-color-focus-ring);
  outline-offset: -2px;
}

.kole-m-noticebar__close:active { background: var(--kole-color-table-header-bg); }
frameworks-mobile/NoticeBar.html · H5 原生(无框架) · 95 行
frameworks-mobile/NoticeBar.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Kole UI Mobile · NoticeBar(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="NoticeBar.css">
<style>
  body { margin: 0; background: var(--kole-color-page-bg); font-family: var(--kole-font-family); color: var(--kole-color-text-body); }
  .demo { max-width: 375px; margin: 0 auto; padding: var(--kole-m-gutter) 0; }
  .demo-label { margin: 0; padding: var(--kole-space-12) var(--kole-m-gutter) var(--kole-space-8);
    font-size: var(--kole-m-font-size-label); color: var(--kole-color-text-secondary); }
  .demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
  <section class="demo-block" data-demo="basic">
    <p class="demo-label">基础通告(tone=default:品牌浅底,container 带 role=status 朗读一次)</p>
    <div class="kole-m-noticebar kole-m-noticebar--default" role="status" data-assert="noticebar-basic">
      <span class="kole-m-noticebar__icon" aria-hidden="true">▸</span>
      <span class="kole-m-noticebar__text">系统将于今晚 23:00 进行例行维护,预计 30 分钟。</span>
    </div>
  </section>

  <section class="demo-block" data-demo="closable">
    <p class="demo-label">可关闭(closable=true:关闭按钮是原生 button,热区 24×24,点后打标记)</p>
    <div class="kole-m-noticebar kole-m-noticebar--warning" id="noticebar-closable" role="status" data-assert="noticebar-closable">
      <span class="kole-m-noticebar__icon" aria-hidden="true">!</span>
      <span class="kole-m-noticebar__text">当前有 3 笔订单即将超时,请尽快处理。</span>
      <button class="kole-m-noticebar__close" type="button" aria-label="关闭通告:订单超时提醒"
              data-behavior="click-sets-attr:#noticebar-closable|data-closed|true">✕</button>
    </div>
  </section>

  <section class="demo-block" data-demo="scrollable">
    <p class="demo-label">跑马灯(scrollable=true:文字超宽时横向滚动;reduced-motion 下停止并换行)</p>
    <div class="kole-m-noticebar kole-m-noticebar--scrollable" role="status" data-assert="noticebar-scrollable">
      <span class="kole-m-noticebar__icon" aria-hidden="true">▸</span>
      <span class="kole-m-noticebar__text">
        <span class="kole-m-noticebar__track">
          <span class="kole-m-noticebar__seg">平台升级公告:新增批量导入与导出能力,历史数据无需迁移;如遇问题请联系客服。</span>
          <span class="kole-m-noticebar__seg kole-m-noticebar__seg--dup" aria-hidden="true">平台升级公告:新增批量导入与导出能力,历史数据无需迁移;如遇问题请联系客服。</span>
        </span>
      </span>
    </div>
  </section>

  <section class="demo-block" data-demo="tone">
    <p class="demo-label">四种语气(tone=success / danger:浅底 + 同族深字,亮暗两态都达标)</p>
    <div class="kole-m-noticebar kole-m-noticebar--success" role="status" data-assert="noticebar-tone">
      <span class="kole-m-noticebar__icon" aria-hidden="true">✓</span>
      <span class="kole-m-noticebar__text">本月账单已结清,感谢使用。</span>
    </div>
    <div class="kole-m-noticebar kole-m-noticebar--danger" role="status">
      <span class="kole-m-noticebar__icon" aria-hidden="true">!</span>
      <span class="kole-m-noticebar__text">账号存在异地登录,建议立即修改密码。</span>
    </div>
  </section>
</div>
<script>
  /* 演示页交互:关闭按钮只打标记(真实业务里是否移除这条通告由宿主决定)。 */
  (function () {
    document.querySelectorAll('.kole-m-noticebar__close').forEach(function (btn) {
      btn.addEventListener('click', function () {
        var bar = btn.closest('.kole-m-noticebar');
        if (bar) bar.setAttribute('data-closed', 'true');
      });
    });
  })();
</script>
<script>
  /* ?demo=<id> → 只显示该演示块(文档站按块预览用;无参数时全部显示,测试与回归走无参数路径) */
  (function () {
    var id = new URLSearchParams(location.search).get('demo');
    if (!id) return;
    var blocks = Array.prototype.slice.call(document.querySelectorAll('.demo-block'));
    var hit = false;
    blocks.forEach(function (b) {
      var on = b.getAttribute('data-demo') === id;
      if (on) hit = true;
      b.hidden = !on;
    });
    if (!hit) { blocks.forEach(function (b) { b.hidden = false; }); return; }
    document.body.classList.add('demo-single');
    blocks.forEach(function (b) {
      var label = b.querySelector('.demo-label');
      if (label && !b.hidden) label.hidden = true;
    });
  })();
</script>
</body>
</html>
frameworks-mobile/NoticeBar.jsx · React · 53 行
frameworks-mobile/NoticeBar.jsx
import React from 'react';
import './NoticeBar.css';

/* 通知栏(移动端)— 规格 §16;容器 role=status(读屏朗读一次);
   跑马灯在 prefers-reduced-motion 下由 CSS 停止滚动并换行(无障碍硬要求)。 */
export default function NoticeBar({
  tone = 'default',
  scrollable = false,
  closable = false,
  text = '',
  onClose,
  children = null,
}) {
  const content = text || children;
  const cls =
    'kole-m-noticebar' +
    ` kole-m-noticebar--${tone}` +
    (scrollable ? ' kole-m-noticebar--scrollable' : '');

  return (
    <div className={cls} role="status">
      <span className="kole-m-noticebar__icon" aria-hidden="true">
        ▸
      </span>
      <span className="kole-m-noticebar__text">
        {scrollable ? (
          /* 无缝循环:两段相同文案,第二段是循环占位(静止时被 CSS 隐藏) */
          <span className="kole-m-noticebar__track">
            <span className="kole-m-noticebar__seg">{content}</span>
            <span className="kole-m-noticebar__seg kole-m-noticebar__seg--dup" aria-hidden="true">
              {content}
            </span>
          </span>
        ) : (
          content
        )}
      </span>
      {closable ? (
        <button
          className="kole-m-noticebar__close"
          type="button"
          aria-label={`关闭通告:${typeof content === 'string' ? content : ''}`}
          onClick={() => {
            if (onClose) onClose();
          }}
        >
          ✕
        </button>
      ) : null}
    </div>
  );
}
frameworks-mobile/NoticeBar.vue2.vue · Vue 2 · 60 行
frameworks-mobile/NoticeBar.vue2.vue
<template>
  <div class="kole-m-noticebar" :class="barClass" role="status">
    <span class="kole-m-noticebar__icon" aria-hidden="true">▸</span>
    <span class="kole-m-noticebar__text">
      <span v-if="scrollable" class="kole-m-noticebar__track">
        <span class="kole-m-noticebar__seg">{{ content }}</span>
        <span class="kole-m-noticebar__seg kole-m-noticebar__seg--dup" aria-hidden="true">{{ content }}</span>
      </span>
      <template v-else>{{ content }}</template>
    </span>
    <button
      v-if="closable"
      class="kole-m-noticebar__close"
      type="button"
      :aria-label="closeLabel"
      @click="onCloseClick"
    >✕</button>
  </div>
</template>

<script>
export default {
  name: 'KoleMNoticeBar',
  props: {
    tone: { type: String, default: 'default' },
    scrollable: { type: Boolean, default: false },
    closable: { type: Boolean, default: false },
    text: { type: String, default: '' }
  },
  computed: {
    content: function () {
      if (this.text) return this.text;
      var nodes = (this.$slots && this.$slots.default) || [];
      return nodes
        .map(function (n) {
          return n.children && typeof n.children === 'string' ? n.children : '';
        })
        .join('');
    },
    barClass: function () {
      return [
        'kole-m-noticebar--' + this.tone,
        this.scrollable ? 'kole-m-noticebar--scrollable' : ''
      ].filter(Boolean);
    },
    /* 关闭按钮的读屏名称带上通告内容,否则「✕」无法区分是哪一条(规格 §16.6) */
    closeLabel: function () {
      return '关闭通告:' + this.content;
    }
  },
  methods: {
    onCloseClick: function () {
      this.$emit('close');
    }
  }
};
</script>

<style src="./NoticeBar.css"></style>
frameworks-mobile/NoticeBar.vue3.vue · Vue 3 · 53 行
frameworks-mobile/NoticeBar.vue3.vue
<template>
  <div class="kole-m-noticebar" :class="barClass" role="status">
    <span class="kole-m-noticebar__icon" aria-hidden="true">▸</span>
    <span class="kole-m-noticebar__text">
      <span v-if="scrollable" class="kole-m-noticebar__track">
        <span class="kole-m-noticebar__seg">{{ content }}</span>
        <span class="kole-m-noticebar__seg kole-m-noticebar__seg--dup" aria-hidden="true">{{ content }}</span>
      </span>
      <template v-else>{{ content }}</template>
    </span>
    <button
      v-if="closable"
      class="kole-m-noticebar__close"
      type="button"
      :aria-label="closeLabel"
      @click="onCloseClick"
    >✕</button>
  </div>
</template>

<script setup>
import { computed, useSlots } from 'vue';

const props = defineProps({
  tone: { type: String, default: 'default' },
  scrollable: { type: Boolean, default: false },
  closable: { type: Boolean, default: false },
  text: { type: String, default: '' }
});
const emit = defineEmits(['close']);

const slots = useSlots();
const content = computed(() => {
  if (props.text) return props.text;
  const nodes = slots.default ? slots.default() : [];
  return nodes.map((n) => (typeof n.children === 'string' ? n.children : '')).join('');
});

const barClass = computed(() => [
  `kole-m-noticebar--${props.tone}`,
  props.scrollable ? 'kole-m-noticebar--scrollable' : ''
].filter(Boolean));

/* 关闭按钮的读屏名称带上通告内容,否则「✕」无法区分是哪一条(规格 §16.6) */
const closeLabel = computed(() => '关闭通告:' + content.value);

function onCloseClick() {
  emit('close');
}
</script>

<style src="./NoticeBar.css"></style>
frameworks-mobile/NoticeBar.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 115 行
frameworks-mobile/NoticeBar.uniapp.vue
<template>
  <view class="kole-m-noticebar" :class="barClass" :role="'status'">
    <text class="kole-m-noticebar__icon">▸</text>
    <view class="kole-m-noticebar__text">
      <view v-if="scrollable" class="kole-m-noticebar__track">
        <text class="kole-m-noticebar__seg">{{ content }}</text>
        <text class="kole-m-noticebar__seg kole-m-noticebar__seg--dup">{{ content }}</text>
      </view>
      <text v-else>{{ content }}</text>
    </view>
    <view
      v-if="closable"
      class="kole-m-noticebar__close"
      role="button"
      :aria-label="closeLabel"
      @tap="onCloseTap"
    ><text>✕</text></view>
  </view>
</template>

<script setup>
/* uni-app 端 · 通知栏(移动端)— 规格 §16
   跨端差异:用 view / text;关闭按钮用 view + role="button" + aria-label,点击用 @tap;
   尺寸用 rpx(2rpx ≈ 1px);跑马灯的 reduced-motion 降级由样式里的媒体查询承担。 */
import { computed, useSlots } from 'vue';

const props = defineProps({
  tone: { type: String, default: 'default' },
  scrollable: { type: Boolean, default: false },
  closable: { type: Boolean, default: false },
  text: { type: String, default: '' }
});
const emit = defineEmits(['close']);

const slots = useSlots();
const content = computed(() => {
  if (props.text) return props.text;
  const nodes = slots.default ? slots.default() : [];
  return nodes.map((n) => (typeof n.children === 'string' ? n.children : '')).join('');
});

const barClass = computed(() => [
  `kole-m-noticebar--${props.tone}`,
  props.scrollable ? 'kole-m-noticebar--scrollable' : ''
].filter(Boolean));

/* 关闭按钮的读屏名称带上通告内容,否则「✕」无法区分是哪一条(规格 §16.6) */
const closeLabel = computed(() => '关闭通告:' + content.value);

function onCloseTap() {
  emit('close');
}
</script>

<style>
.kole-m-noticebar {
  --kole-m-noticebar-icon-size: 32rpx;
  --kole-m-touch-target: 88rpx;
  --kole-m-font-size-label: 28rpx;
  --kole-m-font-size-caption: 22rpx;
  --kole-m-gutter: 32rpx;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  min-height: var(--kole-m-touch-target);
  padding: 16rpx var(--kole-m-gutter);
  background-color: var(--kole-color-brand-bg);
  color: var(--kole-color-brand);
  font-size: var(--kole-m-font-size-label);
  overflow: hidden;
}

.kole-m-noticebar--default { background-color: var(--kole-color-brand-bg); color: var(--kole-color-brand); }
.kole-m-noticebar--success { background-color: var(--kole-color-tag-green-bg); color: var(--kole-color-success); }
.kole-m-noticebar--warning { background-color: var(--kole-color-table-header-bg); color: var(--kole-color-warning); }
.kole-m-noticebar--danger { background-color: var(--kole-color-table-header-bg); color: var(--kole-color-error); }

.kole-m-noticebar__icon {
  flex-shrink: 0;
  margin-right: 16rpx;
  font-size: var(--kole-m-noticebar-icon-size);
}

.kole-m-noticebar__text {
  flex: 1;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.kole-m-noticebar--scrollable .kole-m-noticebar__track {
  display: flex;
  white-space: nowrap;
}

.kole-m-noticebar__seg {
  flex-shrink: 0;
  padding-right: 96rpx;
}

.kole-m-noticebar__close {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 48rpx;
  height: 48rpx;
  margin-right: -16rpx;
  border-radius: 50%;
  color: inherit;
  font-size: var(--kole-m-font-size-caption);
}
</style>

测试与回归

断言在真实的 375×640 设备帧里跑(引擎与 PC 侧共用 tests/_runtime.js,触控行为动词来自移动端 tests/mobile/_behaviors.js)。

断言 15 条 · 全部通过 报告 2026-09-20 16:50:33

复现命令
node site/dev-server.js &
REG_BASE=http://127.0.0.1:3311 node tools/run-mobile-regression.mjs   # 全量 5 个组件
npm run verify:mobile-docs                                           # 本页内容完整性 + API 与源码一致性

设计契约

components/mobile-noticebar.json(点击展开原始 JSON)
{
  "schemaVersion": 1,
  "sourceKind": "authored-spec",
  "provenance": "authored-in-repo",
  "specFile": "spec/移动端规格.md",
  "confidence": "high",
  "specSection": "16 · 通知栏 NoticeBar",
  "slug": "mobile-noticebar",
  "name": "通知栏 NoticeBar",
  "semanticTypeCandidates": [
    "notice",
    "banner",
    "marquee"
  ],
  "variantDimensions": [
    {
      "name": "tone",
      "values": [
        "default",
        "success",
        "warning",
        "danger"
      ]
    },
    {
      "name": "scrollable",
      "values": [
        "false",
        "true"
      ]
    },
    {
      "name": "closable",
      "values": [
        "false",
        "true"
      ]
    }
  ],
  "representativeVariants": [
    {
      "tone": "default",
      "scrollable": "false",
      "closable": "false",
      "label": "基础通告"
    },
    {
      "tone": "warning",
      "scrollable": "false",
      "closable": "true",
      "label": "可关闭提醒"
    },
    {
      "tone": "default",
      "scrollable": "true",
      "closable": "false",
      "label": "跑马灯长通告"
    },
    {
      "tone": "success",
      "scrollable": "false",
      "closable": "false",
      "label": "成功语气"
    }
  ],
  "anatomy": {
    "noticebar": "根容器",
    "noticebar__icon": "左侧喇叭图标",
    "noticebar__text": "通告文字(可滚动)",
    "noticebar__close": "可选关闭按钮"
  },
  "structurePatterns": {
    "tone": "default / success / warning / danger",
    "scrollable": "false / true(文字超宽时是否跑马灯)",
    "closable": "false / true"
  },
  "usageHints": [
    "在页面顶部或内容区之间横向滚动展示一条通告(如「系统维护通知」)",
    "关闭按钮热区不小于 24×24(用负外边距扩展)",
    "跑马灯在 prefers-reduced-motion: reduce 下停止滚动并改为换行显示(无障碍硬要求)",
    "关闭动作只触发一次事件,是否移除由宿主决定",
    "容器 role=\"status\"(读屏会朗读一次通告)",
    "跑马灯不得用 aria-live(滚动内容反复朗读会干扰)"
  ],
  "doNotInvent": [
    "多条通告的排队 / 轮播",
    "滚动速度的可配置项"
  ],
  "unknowns": [
    "通告文字的最大长度",
    "是否支持富文本"
  ],
  "interaction": [
    "关闭按钮热区不小于 24×24(用负外边距扩展)",
    "跑马灯在 prefers-reduced-motion: reduce 下停止滚动并改为换行显示(无障碍硬要求)",
    "关闭动作只触发一次事件,是否移除由宿主决定"
  ],
  "accessibility": [
    "容器 role=\"status\"(读屏会朗读一次通告)",
    "关闭按钮为原生 button 并带 aria-label",
    "跑马灯不得用 aria-live(滚动内容反复朗读会干扰)"
  ],
  "api": {
    "source": "implementation",
    "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
    "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
    "props": [
      {
        "name": "tone",
        "type": "'default' | 'success' | 'warning' | 'danger'",
        "default": "'default'",
        "desc": "变体 tone:四种语气(浅底 + 同族深字)(规格 §16.3)",
        "required": false
      },
      {
        "name": "scrollable",
        "type": "boolean",
        "default": "false",
        "desc": "变体 scrollable:文字超宽时是否跑马灯(规格 §16.3)",
        "required": false
      },
      {
        "name": "closable",
        "type": "boolean",
        "default": "false",
        "desc": "变体 closable:是否显示关闭按钮(规格 §16.3)",
        "required": false
      },
      {
        "name": "text",
        "type": "string",
        "default": "''",
        "desc": "通告文字(规格 §16.2 text)",
        "required": false
      }
    ],
    "events": [
      {
        "name": "close",
        "params": "—",
        "desc": "点击关闭按钮时触发;是否移除这条通告由宿主决定(规格 §16.5)"
      }
    ],
    "slots": [
      {
        "name": "default",
        "desc": "通告内容(与 text 二选一;text 优先)"
      }
    ]
  },
  "variantClasses": {
    "tone": {
      "default": [
        ".kole-m-noticebar--default"
      ],
      "success": [
        ".kole-m-noticebar--success"
      ],
      "warning": [
        ".kole-m-noticebar--warning"
      ],
      "danger": [
        ".kole-m-noticebar--danger"
      ]
    },
    "scrollable": {
      "false": [],
      "true": [
        ".kole-m-noticebar--scrollable"
      ]
    },
    "closable": {
      "false": [],
      "true": [
        ".kole-m-noticebar__close"
      ]
    }
  },
  "demos": [
    {
      "id": "basic",
      "group": "01 组件类型",
      "title": "基础通告",
      "desc": "品牌浅底 + 同族深字;容器带 role=status,读屏会朗读一次。",
      "variant": "tone=default"
    },
    {
      "id": "closable",
      "group": "01 组件类型",
      "title": "可关闭",
      "desc": "关闭按钮是原生 button、24×24 热区,aria-label 带上通告内容以便区分。",
      "variant": "closable=true"
    },
    {
      "id": "scrollable",
      "group": "01 组件类型",
      "title": "跑马灯",
      "desc": "文案超宽时横向循环滚动;系统开启「减少动态效果」时停止滚动并改为换行。",
      "variant": "scrollable=true"
    },
    {
      "id": "tone",
      "group": "02 组件状态",
      "title": "语义语气",
      "desc": "成功与失败语气用同族浅底,颜色之外还有图标区分,不只靠色相。",
      "variant": "tone=success|danger"
    }
  ],
  "related": [
    {
      "slug": "mobile-toast",
      "why": "一次性结果反馈用轻提示;需要常驻在页面里的通告才用通知栏"
    },
    {
      "slug": "mobile-tag",
      "why": "很短的状态词用标签;整句通告用通知栏"
    },
    {
      "slug": "mobile-divider",
      "why": "通知栏与内容区之间靠浅底与留白分隔,不必再插分割线"
    }
  ]
}