From 5caf4c51cc8db574129a0c4e6f76806515601a27 Mon Sep 17 00:00:00 2001 From: Cheng Zhou Date: Wed, 4 Mar 2026 16:40:18 +0800 Subject: [PATCH] feat(ui): unify loading empty error states --- frontend/scripts/verify-ui-contract.mjs | 21 +++++++++++++ frontend/src/components/StateEmpty.vue | 32 ++++--------------- frontend/src/components/StateError.vue | 32 ++++--------------- frontend/src/components/StateLoading.vue | 23 +++++++------- frontend/src/styles/main.css | 39 ++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 64 deletions(-) diff --git a/frontend/scripts/verify-ui-contract.mjs b/frontend/scripts/verify-ui-contract.mjs index 721bfe31..0fc7ca2a 100644 --- a/frontend/scripts/verify-ui-contract.mjs +++ b/frontend/scripts/verify-ui-contract.mjs @@ -101,6 +101,27 @@ for (const className of requiredAdminProjectDetailClasses) { } } +const stateComponents = [ + "src/components/StateLoading.vue", + "src/components/StateEmpty.vue", + "src/components/StateError.vue" +]; + +const requiredStateClasses = [ + "ctms-state", + "ctms-state-title", + "ctms-state-desc" +]; + +for (const file of stateComponents) { + const content = readFileSync(file, "utf8"); + for (const className of requiredStateClasses) { + if (!content.includes(className)) { + missing.push(`${file}:${className}`); + } + } +} + if (missing.length) { console.error("UI contract check failed. Missing:", missing.join(", ")); process.exit(1); diff --git a/frontend/src/components/StateEmpty.vue b/frontend/src/components/StateEmpty.vue index 382c99e9..d5a9e435 100644 --- a/frontend/src/components/StateEmpty.vue +++ b/frontend/src/components/StateEmpty.vue @@ -1,12 +1,12 @@ diff --git a/frontend/src/components/StateError.vue b/frontend/src/components/StateError.vue index 047d951b..ad048a57 100644 --- a/frontend/src/components/StateError.vue +++ b/frontend/src/components/StateError.vue @@ -1,12 +1,12 @@ diff --git a/frontend/src/components/StateLoading.vue b/frontend/src/components/StateLoading.vue index ee2f00d3..1c76d558 100644 --- a/frontend/src/components/StateLoading.vue +++ b/frontend/src/components/StateLoading.vue @@ -1,25 +1,24 @@ - - diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css index 882e1afc..48999d1b 100644 --- a/frontend/src/styles/main.css +++ b/frontend/src/styles/main.css @@ -437,6 +437,45 @@ body { font-weight: 600; } +/* Unified loading / empty / error states */ +.ctms-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 28px 16px; + text-align: center; + border: 1px solid var(--ctms-border-color); + border-radius: var(--ctms-radius); + background: #ffffff; +} + +.ctms-state-icon { + font-size: 34px; +} + +.ctms-state-title { + margin: 0; + font-size: 15px; + font-weight: 600; +} + +.ctms-state-desc { + margin: 0; + font-size: 13px; + color: var(--ctms-text-secondary); +} + +.ctms-state-action { + margin-top: 4px; +} + +.ctms-state-loading .ctms-state-skeleton { + width: min(560px, 100%); + margin-top: 4px; +} + /* Tool classes */ .text-main { color: var(--ctms-text-main);