message —— Mensaje global
Un mensaje global es la «retroalimentación ligera tras una operación»: guardado con éxito, error de red, copia con éxito. Se desliza desde la parte superior de la pantalla, permanece unos segundos y desaparece solo, sin interrumpir lo que hace el usuario. core/message gestiona la contabilidad de la cola de mensajes: la duración se deduce por semántica, el recorte al superar el límite, la generación de registros y el rol de live region.
Cuándo usarlo
Sección titulada «Cuándo usarlo»- Cada framework implementa
LhMessage(API de servicio:LhMessage.success('Guardado')): retroalimentación de operaciones, avisos ligeros de error, aviso de carga en curso. - Notificaciones instantáneas de texto corto que no requieren ninguna acción del usuario.
- No usarlo para: confirmaciones que exigen acción del usuario (usa
dialog/popconfirm); contenidos más extensos o con título (usanotification); barras de aviso fijas dentro de la página (usaalert).
Importación
Sección titulada «Importación»import { LH_MESSAGE_DURATION_DEFAULT, LH_MESSAGE_MAX_DEFAULT, LH_MESSAGE_OFFSET_DEFAULT, getMessageRole, getMessageIconName, getMessageClassNames, resolveMessageDuration, clampMessageQueue, createMessageRecord,} from '@lh-ui/share/core/message'import type { LhMessageType, LhMessageRecord, LhMessageState } from '@lh-ui/share/core/message'Intención de diseño
Sección titulada «Intención de diseño»La duración semántica de resolveMessageDuration: el tipo loading devuelve 0 (sin cierre automático) por defecto — un mensaje de «cargando» que desaparece solo es absurdo; debe cerrarlo explícitamente el código de negocio. Pero si se pasa duration de forma explícita, se respeta: resolveMessageDuration(0, 'loading') devuelve 0 (explícito) y resolveMessageDuration(undefined, 'loading') también devuelve 0 (valor semántico por defecto); ambos llegan al mismo resultado por caminos distintos; resolveMessageDuration(5000, 'loading') devuelve 5000 (el valor explícito prevalece).
clampMessageQueue devuelve los registros eliminados: no solo devuelve los que se conservan, también devuelve removed — es responsabilidad del llamador invocar su onClose. Este diseño es importante: cuando se recorta la cola, los mensajes se eliminan a la fuerza y, si no se dispara onClose, las devoluciones de llamada de limpieza del negocio (telemetría, reseteo de estado) no se ejecutan nunca.
Mapeo de roles de live region (mismo paradigma que alert / notification): warning / danger / error → role="alert" (interrumpe y se anuncia de inmediato); el resto → role="status" (cola de anuncio suave). La distinción tiene un sentido práctico: los errores suelen requerir que el usuario lo sepa de inmediato, mientras que los éxitos pueden esperar a que el usuario termine su operación actual.
getMessageIconName devuelve null para loading: el icono giratorio de loading lo renderiza el componente LhSpinner (necesita animación), no es un icono estático — la capa UI renderiza un componente u otro en consecuencia.
Tabla de funciones
Sección titulada «Tabla de funciones»| Función | Firma | Devuelve | Función |
|---|---|---|---|
getMessageRole |
(type: LhMessageType) => LhAlertRole |
‘alert’ | ‘status’ | rol de live region |
getMessageIconName |
(type) => string | null |
nombre del icono | loading devuelve null |
getMessageClassNames |
(state: LhMessageState) => string[] |
clases | tipo + cerrable |
resolveMessageDuration |
(duration: number | undefined, type) => number |
milisegundos | loading por defecto 0 |
clampMessageQueue |
(items, max) => { kept, removed } |
resultado del recorte | conserva los últimos max |
createMessageRecord |
(partial, seq) => LhMessageRecord |
registro | completa duración y desplazamiento por defecto |
Constantes: LH_MESSAGE_DURATION_DEFAULT = 3000, LH_MESSAGE_MAX_DEFAULT = 5 y LH_MESSAGE_OFFSET_DEFAULT = 20.
Definiciones de tipos
Sección titulada «Definiciones de tipos»export type LhMessageType = 'info' | 'success' | 'warning' | 'danger' | 'error' | 'loading'
export interface LhMessageRecord { id: number type: LhMessageType content: string duration: number // 0 significa que no se cierra solo showClose: boolean offset: number // desplazamiento en px desde la parte superior del viewport sx?: LhSxProps onClose?: () => void}
export interface LhMessageState { type: LhMessageType showClose: boolean}LhMessageType añade loading a las constantes semánticas generales (LH_TYPES_CONST: info/success/warning/danger/error).
Accesibilidad
Sección titulada «Accesibilidad»getMessageRole('danger') // 'alert'getMessageRole('warning') // 'alert'getMessageRole('info') // 'status'getMessageRole('success') // 'status'getMessageRole('loading') // 'status'- Un elemento con
role="alert"se anuncia tan pronto como aparece en el DOM, ideal para errores;role="status"se anuncia en cola, ideal para éxitos e infos. - Complemento de aria-live: al renderizar, la capa UI suele acompañarlo con
aria-live="polite"(status) oaria-live="assertive"(alert) — elroleya lo implica y repetirlo no suele hacer daño, pero algunos lectores de pantalla soportan mejor el role; escribir ambos es lo más seguro. - Anuncio de mensajes duplicados: tres «Guardado con éxito» seguidos se anuncian tres veces, y eso es correcto (cada uno es un evento nuevo). Si quieres suprimirlos, que la capa de negocio detecte los duplicados.
- Botón de cerrar:
aria-label="Cerrar"(botón de icono sin texto).
Ejemplos de uso
Sección titulada «Ejemplos de uso»import { resolveMessageDuration, clampMessageQueue, createMessageRecord } from '@lh-ui/share/core/message'
resolveMessageDuration(undefined, 'success') // 3000resolveMessageDuration(undefined, 'loading') // 0 (la carga no se cierra sola)resolveMessageDuration(5000, 'loading') // 5000 (el valor explícito prevalece)
clampMessageQueue([{ id: 1 }, { id: 2 }, { id: 3 }], 2)// { kept: [{ id: 2 }, { id: 3 }], removed: [{ id: 1 }] } ← se elimina el más antiguo
clampMessageQueue([{ id: 1 }, { id: 2 }], 0) // { kept: [...], removed: [] } (max <= 0 no recorta)
createMessageRecord({ type: 'success', content: 'Guardado', showClose: true }, 7)// { id: 7, type: 'success', content: 'Guardado', showClose: true,// duration: 3000, offset: 20 }// Implementación del servicio (capa UI): cola única + contador autoincrementallet seq = 0const queue = ref<LhMessageRecord[]>([])
function open(type: LhMessageType, content: string, options?: Partial<LhMessageRecord>) { const record = createMessageRecord( { type, content, showClose: false, ...options }, ++seq, ) queue.value = [...queue.value, record] // Cola por encima del límite: se elimina el más antiguo y se dispara su onClose const { kept, removed } = clampMessageQueue(queue.value, LH_MESSAGE_MAX_DEFAULT) removed.forEach((r) => r.onClose?.()) queue.value = kept // Cierre automático if (record.duration > 0) { setTimeout(() => close(record.id), record.duration) } return record.id}
const api = { info: (c: string, o?) => open('info', c, o), success: (c: string, o?) => open('success', c, o), warning: (c: string, o?) => open('warning', c, o), error: (c: string, o?) => open('error', c, o), loading: (c: string, o?) => open('loading', c, o),}<!-- Vue3: capa de renderizado --><teleport to="body"> <div class="lh-message" :style="{ top: offset + 'px' }"> <transition-group name="lh-message-fade" tag="div" class="lh-message__list"> <div v-for="record in queue" :key="record.id" :class="getMessageClassNames({ type: record.type, showClose: record.showClose })" :role="getMessageRole(record.type)" > <LhSpinner v-if="record.type === 'loading'" :size="14" /> <LhIcon v-else :name="getMessageIconName(record.type)!" /> <span class="lh-message__content">{{ record.content }}</span> <button v-if="record.showClose" type="button" aria-label="Cerrar" @click="close(record.id)">✕</button> </div> </transition-group> </div></teleport>// Uso típico de loading: se abre al guardar y se cierra al terminarconst id = LhMessage.loading('Guardando…')await save()LhMessage.close(id)LhMessage.success('Guardado')// React: createPortal renderiza la cola; el cierre gestiona el temporizador de cada mensaje con clearTimeout// uniapp: posicionamiento fixed + animación; la cola usa el mismo modelo y, fuera de h5,// LhSpinner se implementa con un view girandoEstilos y tokens
Sección titulada «Estilos y tokens»| Clase | Semántica |
|---|---|
.lh-message |
contenedor (fixed, centrado arriba) |
.lh-message__list |
cola (transition-group) |
.lh-message__item |
un solo mensaje |
.lh-message__item--{info|success|warning|danger|error|loading} |
tipo semántico (color del icono + borde/fondo) |
.lh-message__item--closable |
cerrable (reserva espacio para el botón de cerrar) |
.lh-message__content |
texto |
Cuatro colores semánticos: --lh-info-color / --lh-success-color / --lh-warning-color / --lh-danger-color; fondo --lh-bg-color + borde ligero + sombra.
Advertencias
Sección titulada «Advertencias»- El mensaje
loadingdebe cerrarse a mano:resolveMessageDuration(undefined, 'loading')devuelve 0 y no desaparece solo. Olvidar cerrarlo deja un «Cargando» permanente — es el uso erróneo más frecuente. - La relación entre
errorydanger: ambos se mapean al rolalerty al iconoerror—dangeres el nombre de las constantes semánticas generales yerrores su alias (para alinearse con la API habitualLhMessage.error()). La capa de negocio puede usar cualquiera de los dos. - Límite de la cola
LH_MESSAGE_MAX_DEFAULT = 5: en escenarios de alta frecuencia (como el progreso de una subida) es fácil superar 5 mensajes y que se recorten — elonClosede los registros recortados lo dispara el llamador, y los temporizadores del negocio deben limpiarse al unísono. - El significado de
offset: es el desplazamiento inicial desde la parte superior del viewport; la separación entre mensajes apilados la acumula la capa UI según la altura (sigue elcomputeStackOffsetsdenotification). - Unicidad del
id: elseqdel servicio se incrementa a nivel de módulo; si conviven varias instancias (microfrontends), hace falta un prefijo de id externo para evitar conflictos. - Seguridad SSR: el servicio de mensajes depende del DOM (teleport, setTimeout); bajo SSR debería omitir la creación automática y quedar disponible solo tras la activación en el cliente.
- El callejón sin salida de
duration: 0conshowClose: false: un mensaje que no se cierra solo ni puede cerrarse a mano permanece para siempre — salvo que el negocio guarde el id y llame aclose(id). Deja siempre al menos una vía de cierre para el usuario.
@lh-ui/share/core/message → resolveMessageDuration( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue