From ba094275eccebb676341cae1649b5e5e7ee9189d Mon Sep 17 00:00:00 2001 From: CieanYau Date: Sat, 7 Nov 2026 04:35:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=95=8C=E9=9D=A2=EF=BC=9A?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=B9=9D=E9=A1=B5=E5=88=97=E8=A1=A8=E5=AF=86?= =?UTF-8?q?=E5=BA=A6=E5=B9=B6=E4=BF=9D=E7=95=99=E7=A7=BB=E5=8A=A8=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/styles/domains.GiD6NSti.css | 59 ----- app/assets/styles/groups.Buw8nwl1.css | 59 ----- app/assets/styles/logbook.Y7gm684m.css | 59 ----- app/assets/styles/messages.asRdYcVu.css | 17 -- app/assets/styles/news.CbIzdrjt.css | 45 ---- app/assets/styles/notices.BXNGOj-Q.css | 23 -- app/assets/styles/ssh-keys.WWTOf2zG.css | 60 +---- app/assets/styles/tickets.Dz8JrDhQ.css | 59 ----- app/assets/styles/transfer.CAsJhVUW.css | 59 ----- tests/review-list-density.e2e.test.mjs | 315 ++++++++++++++++++++++++ 10 files changed, 316 insertions(+), 439 deletions(-) create mode 100644 tests/review-list-density.e2e.test.mjs diff --git a/app/assets/styles/domains.GiD6NSti.css b/app/assets/styles/domains.GiD6NSti.css index 90ae2b4..0afe529 100644 --- a/app/assets/styles/domains.GiD6NSti.css +++ b/app/assets/styles/domains.GiD6NSti.css @@ -28,78 +28,19 @@ color: var(--p-primary-color); text-decoration: none; } -.list-page[data-v-0a28aca5] { - gap: 21px; -} -.list-head h2[data-v-0a28aca5], -.page-head h2[data-v-0a28aca5] { - margin: 0; - font-size: var(--page-title-size); - font-weight: 600; - line-height: 24.5px; -} -.list-toolbar[data-v-0a28aca5], -.list-toolbar-right[data-v-0a28aca5] { - justify-content: flex-start; - gap: 7px; -} -.list-search[data-v-0a28aca5], -.list-select[data-v-0a28aca5] { - width: 280px; -} -.list-table[data-v-0a28aca5] { - border-radius: var(--radius-md); - box-shadow: 0 1px 3px #0f172a12; -} -.list-foot[data-v-0a28aca5] { - justify-content: center; -} .list-foot > .list-bulk[data-v-0a28aca5]:empty { display: none; } -[data-v-0a28aca5] .p-datatable-header-cell { - background: var(--p-content-hover-background); - padding: 10.5px 14px; - font-size: 1rem; - font-weight: 600; -} -[data-v-0a28aca5] .p-datatable-tbody > tr > td { - padding: 10.5px 14px; - font-size: 1rem; -} [data-v-0a28aca5] .p-tag { font-size: 0.875rem; font-weight: 500; } -[data-v-0a28aca5] .list-empty { - gap: 14px; - min-height: 300px; - padding: 42px 21px; -} -[data-v-0a28aca5] .list-empty i { - opacity: 1; - font-size: 1.875rem; -} -[data-v-0a28aca5] .list-empty-title { - font-size: 1rem; -} -[data-v-0a28aca5] .list-empty-desc { - font-size: 0.875rem; -} .field[data-v-0a28aca5] { gap: 7px; } .field label[data-v-0a28aca5] { font-size: 0.875rem; } -/* 视口条件 (width<=768px) 下的响应式覆盖;保留相对基础规则的层叠顺序。 */ -@media (width<=768px) { - .list-search[data-v-0a28aca5], - .list-select[data-v-0a28aca5], - .list-toolbar-right[data-v-0a28aca5] { - width: 100%; - } -} .domain-notice[data-v-0a28aca5] { padding: 7px; font-size: 1rem; diff --git a/app/assets/styles/groups.Buw8nwl1.css b/app/assets/styles/groups.Buw8nwl1.css index b25328e..9a70cb6 100644 --- a/app/assets/styles/groups.Buw8nwl1.css +++ b/app/assets/styles/groups.Buw8nwl1.css @@ -108,78 +108,19 @@ padding: 20px 0; font-size: 0.9375rem; } -.list-page[data-v-fe145bae] { - gap: 21px; -} -.list-head h2[data-v-fe145bae], -.page-head h2[data-v-fe145bae] { - margin: 0; - font-size: var(--page-title-size); - font-weight: 600; - line-height: 24.5px; -} -.list-toolbar[data-v-fe145bae], -.list-toolbar-right[data-v-fe145bae] { - justify-content: flex-start; - gap: 7px; -} -.list-search[data-v-fe145bae], -.list-select[data-v-fe145bae] { - width: 280px; -} -.list-table[data-v-fe145bae] { - border-radius: var(--radius-md); - box-shadow: 0 1px 3px #0f172a12; -} -.list-foot[data-v-fe145bae] { - justify-content: center; -} .list-foot > .list-bulk[data-v-fe145bae]:empty { display: none; } -[data-v-fe145bae] .p-datatable-header-cell { - background: var(--p-content-hover-background); - padding: 10.5px 14px; - font-size: 1rem; - font-weight: 600; -} -[data-v-fe145bae] .p-datatable-tbody > tr > td { - padding: 10.5px 14px; - font-size: 1rem; -} [data-v-fe145bae] .p-tag { font-size: 0.875rem; font-weight: 500; } -[data-v-fe145bae] .list-empty { - gap: 14px; - min-height: 300px; - padding: 42px 21px; -} -[data-v-fe145bae] .list-empty i { - opacity: 1; - font-size: 1.875rem; -} -[data-v-fe145bae] .list-empty-title { - font-size: 1rem; -} -[data-v-fe145bae] .list-empty-desc { - font-size: 0.875rem; -} .field[data-v-fe145bae] { gap: 7px; } .field label[data-v-fe145bae] { font-size: 0.875rem; } -/* 视口条件 (width<=768px) 下的响应式覆盖;保留相对基础规则的层叠顺序。 */ -@media (width<=768px) { - .list-search[data-v-fe145bae], - .list-select[data-v-fe145bae], - .list-toolbar-right[data-v-fe145bae] { - width: 100%; - } -} .detail-back[data-v-fe145bae], .group-name[data-v-fe145bae] { color: inherit; diff --git a/app/assets/styles/logbook.Y7gm684m.css b/app/assets/styles/logbook.Y7gm684m.css index bd79f9b..d872856 100644 --- a/app/assets/styles/logbook.Y7gm684m.css +++ b/app/assets/styles/logbook.Y7gm684m.css @@ -1,77 +1,18 @@ /* 操作日志:查询工具栏、表格与空态/分页。 */ /* 导入方:app/pages/logbook/index.vue。 */ -.list-page[data-v-0e32233b] { - gap: 21px; -} -.list-head h2[data-v-0e32233b], -.page-head h2[data-v-0e32233b] { - margin: 0; - font-size: var(--page-title-size); - font-weight: 600; - line-height: 24.5px; -} -.list-toolbar[data-v-0e32233b], -.list-toolbar-right[data-v-0e32233b] { - justify-content: flex-start; - gap: 7px; -} -.list-search[data-v-0e32233b], -.list-select[data-v-0e32233b] { - width: 280px; -} -.list-table[data-v-0e32233b] { - border-radius: var(--radius-md); - box-shadow: 0 1px 3px #0f172a12; -} -.list-foot[data-v-0e32233b] { - justify-content: center; -} .list-foot > .list-bulk[data-v-0e32233b]:empty { display: none; } -[data-v-0e32233b] .p-datatable-header-cell { - background: var(--p-content-hover-background); - padding: 10.5px 14px; - font-size: 1rem; - font-weight: 600; -} -[data-v-0e32233b] .p-datatable-tbody > tr > td { - padding: 10.5px 14px; - font-size: 1rem; -} [data-v-0e32233b] .p-tag { font-size: 0.875rem; font-weight: 500; } -[data-v-0e32233b] .list-empty { - gap: 14px; - min-height: 300px; - padding: 42px 21px; -} -[data-v-0e32233b] .list-empty i { - opacity: 1; - font-size: 1.875rem; -} -[data-v-0e32233b] .list-empty-title { - font-size: 1rem; -} -[data-v-0e32233b] .list-empty-desc { - font-size: 0.875rem; -} .field[data-v-0e32233b] { gap: 7px; } .field label[data-v-0e32233b] { font-size: 0.875rem; } -/* 视口条件 (width<=768px) 下的响应式覆盖;保留相对基础规则的层叠顺序。 */ -@media (width<=768px) { - .list-search[data-v-0e32233b], - .list-select[data-v-0e32233b], - .list-toolbar-right[data-v-0e32233b] { - width: 100%; - } -} @media (width<640px) { .list-table[data-v-0e32233b] .p-datatable-tbody diff --git a/app/assets/styles/messages.asRdYcVu.css b/app/assets/styles/messages.asRdYcVu.css index a18cecb..966c890 100644 --- a/app/assets/styles/messages.asRdYcVu.css +++ b/app/assets/styles/messages.asRdYcVu.css @@ -1,8 +1,5 @@ /* 站内消息:筛选、批量选中、未读标记及消息详情导航。 */ /* 导入方:app/pages/messages/index.vue。 */ -.list-page[data-v-e9dbafec] { - gap: 21px; -} .page-heading h2[data-v-e9dbafec] { margin: 0; font-size: var(--page-title-size); @@ -27,7 +24,6 @@ } .list-toolbar-right[data-v-e9dbafec] { flex: 1; - gap: 10px; } .search-field[data-v-e9dbafec] { flex: 1; @@ -74,19 +70,6 @@ .message-panel > .p-message[data-v-e9dbafec] { margin: 0 20px 14px; } -[data-v-e9dbafec] .p-datatable-header-cell { - background: var(--p-content-hover-background); - padding: 12px 20px; - font-size: 0.9375rem; - font-weight: 600; -} -[data-v-e9dbafec] .p-datatable-tbody > tr > td { - padding: 16px 20px; - font-size: 1rem; -} -[data-v-e9dbafec] .list-empty { - min-height: 300px; -} /* 允许连续长文本换行,防止IP、密钥或用户输入撑破当前容器;不会修改原始数据。 */ .mail-title[data-v-e9dbafec] { color: var(--p-text-muted-color); diff --git a/app/assets/styles/news.CbIzdrjt.css b/app/assets/styles/news.CbIzdrjt.css index 6f724a9..1d66345 100644 --- a/app/assets/styles/news.CbIzdrjt.css +++ b/app/assets/styles/news.CbIzdrjt.css @@ -94,32 +94,10 @@ display: none; } } -.list-page[data-v-0f61b69d] { - gap: 21px; -} -.list-head h2[data-v-0f61b69d], -.page-head h2[data-v-0f61b69d] { - margin: 0; - font-size: var(--page-title-size); - font-weight: 600; - line-height: 24.5px; -} -.list-toolbar[data-v-0f61b69d], -.list-toolbar-right[data-v-0f61b69d] { - justify-content: flex-start; - gap: 7px; -} -.list-search[data-v-0f61b69d], -.list-select[data-v-0f61b69d] { - width: 280px; -} .list-table[data-v-0f61b69d] { border-radius: var(--radius-md); box-shadow: 0 1px 3px #0f172a12; } -.list-foot[data-v-0f61b69d] { - justify-content: center; -} .list-foot > .list-bulk[data-v-0f61b69d]:empty { display: none; } @@ -137,35 +115,12 @@ font-size: 0.875rem; font-weight: 500; } -[data-v-0f61b69d] .list-empty { - gap: 14px; - min-height: 300px; - padding: 42px 21px; -} -[data-v-0f61b69d] .list-empty i { - opacity: 1; - font-size: 1.875rem; -} -[data-v-0f61b69d] .list-empty-title { - font-size: 1rem; -} -[data-v-0f61b69d] .list-empty-desc { - font-size: 0.875rem; -} .field[data-v-0f61b69d] { gap: 7px; } .field label[data-v-0f61b69d] { font-size: 0.875rem; } -/* 视口条件 (width<=768px) 下的响应式覆盖;保留相对基础规则的层叠顺序。 */ -@media (width<=768px) { - .list-search[data-v-0f61b69d], - .list-select[data-v-0f61b69d], - .list-toolbar-right[data-v-0f61b69d] { - width: 100%; - } -} .news-card[data-v-0f61b69d] { border-radius: var(--radius-md); padding: 14px; diff --git a/app/assets/styles/notices.BXNGOj-Q.css b/app/assets/styles/notices.BXNGOj-Q.css index cd8cf18..a4f02eb 100644 --- a/app/assets/styles/notices.BXNGOj-Q.css +++ b/app/assets/styles/notices.BXNGOj-Q.css @@ -45,9 +45,6 @@ .detail-content[data-v-4ba0157b] img { max-width: 100%; } -.list-page[data-v-4ba0157b] { - gap: 21px; -} .list-head h2[data-v-4ba0157b], .page-head h2[data-v-4ba0157b] { margin: 0; @@ -55,11 +52,6 @@ font-weight: 600; line-height: 24.5px; } -.list-toolbar[data-v-4ba0157b], -.list-toolbar-right[data-v-4ba0157b] { - justify-content: flex-start; - gap: 7px; -} .list-search[data-v-4ba0157b], .list-select[data-v-4ba0157b] { width: 280px; @@ -88,21 +80,6 @@ font-size: 0.875rem; font-weight: 500; } -[data-v-4ba0157b] .list-empty { - gap: 14px; - min-height: 300px; - padding: 42px 21px; -} -[data-v-4ba0157b] .list-empty i { - opacity: 1; - font-size: 1.875rem; -} -[data-v-4ba0157b] .list-empty-title { - font-size: 1rem; -} -[data-v-4ba0157b] .list-empty-desc { - font-size: 0.875rem; -} .field[data-v-4ba0157b] { gap: 7px; } diff --git a/app/assets/styles/ssh-keys.WWTOf2zG.css b/app/assets/styles/ssh-keys.WWTOf2zG.css index 8c612f6..4a3d802 100644 --- a/app/assets/styles/ssh-keys.WWTOf2zG.css +++ b/app/assets/styles/ssh-keys.WWTOf2zG.css @@ -21,6 +21,7 @@ .row-actions[data-v-31489433] { align-items: center; gap: 2px; + white-space: nowrap; display: flex; } .form[data-v-31489433] { @@ -42,78 +43,19 @@ .req[data-v-31489433] { color: #ef4444; } -.list-page[data-v-31489433] { - gap: 21px; -} -.list-head h2[data-v-31489433], -.page-head h2[data-v-31489433] { - margin: 0; - font-size: var(--page-title-size); - font-weight: 600; - line-height: 24.5px; -} -.list-toolbar[data-v-31489433], -.list-toolbar-right[data-v-31489433] { - justify-content: flex-start; - gap: 7px; -} -.list-search[data-v-31489433], -.list-select[data-v-31489433] { - width: 280px; -} -.list-table[data-v-31489433] { - border-radius: var(--radius-md); - box-shadow: 0 1px 3px #0f172a12; -} -.list-foot[data-v-31489433] { - justify-content: center; -} .list-foot > .list-bulk[data-v-31489433]:empty { display: none; } -[data-v-31489433] .p-datatable-header-cell { - background: var(--p-content-hover-background); - padding: 10.5px 14px; - font-size: 1rem; - font-weight: 600; -} -[data-v-31489433] .p-datatable-tbody > tr > td { - padding: 10.5px 14px; - font-size: 1rem; -} [data-v-31489433] .p-tag { font-size: 0.875rem; font-weight: 500; } -[data-v-31489433] .list-empty { - gap: 14px; - min-height: 300px; - padding: 42px 21px; -} -[data-v-31489433] .list-empty i { - opacity: 1; - font-size: 1.875rem; -} -[data-v-31489433] .list-empty-title { - font-size: 1rem; -} -[data-v-31489433] .list-empty-desc { - font-size: 0.875rem; -} .field[data-v-31489433] { gap: 7px; } .field label[data-v-31489433] { font-size: 0.875rem; } -/* 视口条件 (width<=768px) 下的响应式覆盖;保留相对基础规则的层叠顺序。 */ -@media (width<=768px) { - .list-search[data-v-31489433], - .list-select[data-v-31489433], - .list-toolbar-right[data-v-31489433] { - width: 100%; - } -} @media (width<640px) { .list-table[data-v-31489433] .p-datatable-tbody diff --git a/app/assets/styles/tickets.Dz8JrDhQ.css b/app/assets/styles/tickets.Dz8JrDhQ.css index 2fb9916..2b201c7 100644 --- a/app/assets/styles/tickets.Dz8JrDhQ.css +++ b/app/assets/styles/tickets.Dz8JrDhQ.css @@ -42,78 +42,19 @@ font-size: 0.875rem; display: inline-block; } -.list-page[data-v-1ff3e04d] { - gap: 21px; -} -.list-head h2[data-v-1ff3e04d], -.page-head h2[data-v-1ff3e04d] { - margin: 0; - font-size: var(--page-title-size); - font-weight: 600; - line-height: 24.5px; -} -.list-toolbar[data-v-1ff3e04d], -.list-toolbar-right[data-v-1ff3e04d] { - justify-content: flex-start; - gap: 7px; -} -.list-search[data-v-1ff3e04d], -.list-select[data-v-1ff3e04d] { - width: 280px; -} -.list-table[data-v-1ff3e04d] { - border-radius: var(--radius-md); - box-shadow: 0 1px 3px #0f172a12; -} -.list-foot[data-v-1ff3e04d] { - justify-content: center; -} .list-foot > .list-bulk[data-v-1ff3e04d]:empty { display: none; } -[data-v-1ff3e04d] .p-datatable-header-cell { - background: var(--p-content-hover-background); - padding: 10.5px 14px; - font-size: 1rem; - font-weight: 600; -} -[data-v-1ff3e04d] .p-datatable-tbody > tr > td { - padding: 10.5px 14px; - font-size: 1rem; -} [data-v-1ff3e04d] .p-tag { font-size: 0.875rem; font-weight: 500; } -[data-v-1ff3e04d] .list-empty { - gap: 14px; - min-height: 300px; - padding: 42px 21px; -} -[data-v-1ff3e04d] .list-empty i { - opacity: 1; - font-size: 1.875rem; -} -[data-v-1ff3e04d] .list-empty-title { - font-size: 1rem; -} -[data-v-1ff3e04d] .list-empty-desc { - font-size: 0.875rem; -} .field[data-v-1ff3e04d] { gap: 7px; } .field label[data-v-1ff3e04d] { font-size: 0.875rem; } -/* 视口条件 (width<=768px) 下的响应式覆盖;保留相对基础规则的层叠顺序。 */ -@media (width<=768px) { - .list-search[data-v-1ff3e04d], - .list-select[data-v-1ff3e04d], - .list-toolbar-right[data-v-1ff3e04d] { - width: 100%; - } -} .support-notice[data-v-1ff3e04d] { font-size: 0.875rem; line-height: 24.5px; diff --git a/app/assets/styles/transfer.CAsJhVUW.css b/app/assets/styles/transfer.CAsJhVUW.css index e6f7ad1..f6c6fa5 100644 --- a/app/assets/styles/transfer.CAsJhVUW.css +++ b/app/assets/styles/transfer.CAsJhVUW.css @@ -135,78 +135,19 @@ margin: 0 0 12px; font-size: 1rem; } -.list-page[data-v-559a870e] { - gap: 21px; -} -.list-head h2[data-v-559a870e], -.page-head h2[data-v-559a870e] { - margin: 0; - font-size: var(--page-title-size); - font-weight: 600; - line-height: 24.5px; -} -.list-toolbar[data-v-559a870e], -.list-toolbar-right[data-v-559a870e] { - justify-content: flex-start; - gap: 7px; -} -.list-search[data-v-559a870e], -.list-select[data-v-559a870e] { - width: 280px; -} -.list-table[data-v-559a870e] { - border-radius: var(--radius-md); - box-shadow: 0 1px 3px #0f172a12; -} -.list-foot[data-v-559a870e] { - justify-content: center; -} .list-foot > .list-bulk[data-v-559a870e]:empty { display: none; } -[data-v-559a870e] .p-datatable-header-cell { - background: var(--p-content-hover-background); - padding: 10.5px 14px; - font-size: 1rem; - font-weight: 600; -} -[data-v-559a870e] .p-datatable-tbody > tr > td { - padding: 10.5px 14px; - font-size: 1rem; -} [data-v-559a870e] .p-tag { font-size: 0.875rem; font-weight: 500; } -[data-v-559a870e] .list-empty { - gap: 14px; - min-height: 300px; - padding: 42px 21px; -} -[data-v-559a870e] .list-empty i { - opacity: 1; - font-size: 1.875rem; -} -[data-v-559a870e] .list-empty-title { - font-size: 1rem; -} -[data-v-559a870e] .list-empty-desc { - font-size: 0.875rem; -} .field[data-v-559a870e] { gap: 7px; } .field label[data-v-559a870e] { font-size: 0.875rem; } -/* 视口条件 (width<=768px) 下的响应式覆盖;保留相对基础规则的层叠顺序。 */ -@media (width<=768px) { - .list-search[data-v-559a870e], - .list-select[data-v-559a870e], - .list-toolbar-right[data-v-559a870e] { - width: 100%; - } -} .page-description[data-v-559a870e] { color: var(--p-text-muted-color); margin: 7px 0 0; diff --git a/tests/review-list-density.e2e.test.mjs b/tests/review-list-density.e2e.test.mjs new file mode 100644 index 0000000..a0dade5 --- /dev/null +++ b/tests/review-list-density.e2e.test.mjs @@ -0,0 +1,315 @@ +/** + * 列表视觉回归补齐迁移后的盲区:实际页面密度、主题、空态和手机卡片。 + * 只控制既有外部 HTTP 边界;登录、路由、OpenVue 和 CSS 层叠使用生产构建。 + */ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { backendOrigin, login, scenario } from "./helpers/console-browser.mjs"; + +const created = 1730000000; +const title = "本地列表验收"; +const list = (row, empty) => ({ + list: empty ? [] : [row], + count: Number(!empty), +}); +const pages = [ + ["SSH keys", "/security/ssh-keys", "table", "audit-key"], + ["logbook", "/logbook", "table", title], + ["domains", "/security/domains", "table", "example.invalid"], + ["groups", "/security/groups", "table", title], + ["messages", "/messages", "table", title], + ["news", "/news", "article", title], + ["notices", "/notices", "article", title], + ["tickets", "/tickets", "table", title], + ["transfer", "/transfer", "table", title], + ["embedded news", "/resources?section=news", "article", title], + ["embedded notices", "/resources?section=notices", "article", title], +]; + +async function listBoundary(page, state) { + await page.route(`${backendOrigin}/console/v1/**`, async (route) => { + const path = new URL(route.request().url()).pathname.replace( + /^\/console\/v1/, + "", + ); + const rows = { + "/ssh_key": { + id: 7, + name: "audit-key", + public_key: `ssh-ed25519 ${"A".repeat(180)}`, + finger_print: "SHA256:local-list-check", + }, + "/log": { + id: 7, + create_time: created, + description: title, + ip: "192.0.2.7", + }, + "/domain_whitelist/record": { + id: 7, + host_id: 5, + host_name: title, + product_name: "本地云产品", + domains: ["example.invalid"], + status: 1, + create_time: created, + can_remove: 0, + }, + "/security_group": { + id: 7, + name: title, + description: "规则说明", + rule_num: 2, + host_num: 1, + create_time: created, + }, + "/client_care/mail/list": { + id: 7, + title, + type: "product", + read: 0, + create_time: created, + }, + "/news": { + id: 7, + title, + excerpt: "文章摘要", + type: "产品", + create_time: created, + }, + "/announcement": { id: 7, title, create_time: created }, + "/ticket": { + id: 7, + ticket_num: "AUDIT-7", + title, + name: "产品支持", + status: "待回复", + color: "#0284c7", + create_time: created, + post_time: created, + last_reply_time: created, + }, + "/host_transfer/host": { + id: 7, + name: title, + product_name: "本地云产品", + dedicate_ip: "192.0.2.7", + renew_amount: "20.00", + billing_cycle_name: "月付", + due_time: created, + }, + }; + const staticResponses = { + "/domain_whitelist/host": { + list: [{ id: 5, name: title, product_name: "本地云产品" }], + }, + "/certification/info": { is_certification: true }, + "/account/notification-preferences": { + email_enabled: false, + sms_enabled: false, + email_available: true, + sms_available: false, + }, + "/news/type": { + list: [{ id: 1, name: "产品", news_num: Number(!state.empty) }], + }, + "/announcement/type": { list: [{ id: 1, name: "平台公告" }] }, + "/ticket/status": { list: [{ id: 1, name: "待回复", color: "#0284c7" }] }, + "/ticket/type": { list: [{ id: 1, name: "产品支持" }] }, + "/ticket/statistic": { 1: Number(!state.empty) }, + "/host_transfer/config": { certification: false }, + "/host_transfer/transfer_code": { transfer_code: "LOCAL-ONLY-TRANSFER" }, + }; + const data = Object.hasOwn(rows, path) + ? list(rows[path], state.empty) + : staticResponses[path]; + if (data === undefined) return route.fallback(); + return route.fulfill({ json: { status: 200, msg: "", data } }); + }); +} + +async function measure(page) { + return page.evaluate(() => { + const read = (selector) => { + const element = document.querySelector(selector); + if (!element) return null; + const style = getComputedStyle(element); + return { + gap: style.gap, + padding: style.padding, + background: style.backgroundColor, + color: style.color, + weight: style.fontWeight, + fontSize: style.fontSize, + shadow: style.boxShadow, + display: style.display, + }; + }; + const row = document.querySelector( + ".p-datatable-tbody > tr:not(.p-datatable-empty-message)", + ); + return { + dark: document.documentElement.classList.contains("app-dark"), + shell: Boolean(document.querySelector(".deni-layout")), + page: read(".list-page"), + toolbar: read(".list-toolbar"), + table: read(".list-table"), + article: read(".news-card, .notice-item"), + header: read(".p-datatable-header-cell"), + cell: read( + ".p-datatable-tbody > tr:not(.p-datatable-empty-message) > td", + ), + empty: read(".list-empty"), + emptyIcon: read(".list-empty > i"), + emptyTitle: read(".list-empty-title"), + articleCount: document.querySelectorAll(".news-card, .notice-item") + .length, + tableCount: document.querySelectorAll(".list-table .p-datatable").length, + rowDisplay: row ? getComputedStyle(row).display : null, + actionLabelLines: [ + ...document.querySelectorAll(".row-actions .p-button-label"), + ].map((label) => { + const range = document.createRange(); + range.selectNodeContents(label); + return [...range.getClientRects()].filter((rect) => rect.width > 0) + .length; + }), + visibleCells: row + ? [...row.querySelectorAll("td")] + .filter((cell) => getComputedStyle(cell).display !== "none") + .map((cell) => ({ + label: cell.getAttribute("data-label"), + text: cell.innerText.trim(), + })) + : [], + horizontalOverflow: document.documentElement.scrollWidth > innerWidth + 1, + }; + }); +} + +function luminance(color) { + const channels = color + .match(/[\d.]+/g) + .slice(0, 3) + .map((value) => { + const channel = Number(value) / 255; + return channel <= 0.04045 + ? channel / 12.92 + : ((channel + 0.055) / 1.055) ** 2.4; + }); + return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722; +} + +for (const [name, routePath, kind, visibleText] of pages) { + for (const width of [1440, 390]) { + for (const theme of ["light", "dark"]) { + test( + `${name} keeps shared list density at ${width}px in ${theme}`, + { timeout: 30000 }, + (t) => + scenario(t, { loginMode: "password-only" }, async (page) => { + await page.setViewportSize({ width, height: 1000 }); + await page.emulateMedia({ + colorScheme: theme, + reducedMotion: "reduce", + }); + const state = { empty: true }; + await listBoundary(page, state); + await login(page, routePath); + await page.locator(".list-empty").waitFor(); + await page + .locator(".p-datatable-mask") + .waitFor({ state: "hidden" }); + await page.waitForFunction( + () => + !document + .querySelector(".list-empty-title") + ?.textContent.includes("加载"), + ); + const empty = await measure(page); + state.empty = false; + await page.reload(); + const content = page.locator( + kind === "table" + ? ".p-datatable-tbody > tr:not(.p-datatable-empty-message)" + : ".news-card, .notice-item", + ); + await content.first().waitFor(); + await page + .locator(".p-datatable-mask") + .waitFor({ state: "hidden" }); + const filled = await measure(page); + const violations = []; + for (const [stateName, observed] of [ + ["empty", empty], + ["filled", filled], + ]) { + if (!observed.shell) + violations.push(`${stateName}: missing shell`); + if (observed.dark !== (theme === "dark")) + violations.push(`${stateName}: wrong theme`); + if (observed.page?.gap !== "16px") + violations.push(`${stateName}: page gap`); + if (observed.toolbar?.gap !== "8px") + violations.push(`${stateName}: toolbar gap`); + if (observed.horizontalOverflow) + violations.push(`${stateName}: document overflow`); + } + if (empty.empty?.gap !== "6px") violations.push("empty: gap"); + if (empty.empty?.padding !== "48px 24px") + violations.push("empty: padding"); + if (empty.emptyIcon?.fontSize !== "16px") + violations.push("empty: icon density"); + if (kind === "table") { + if (filled.tableCount !== 1) + violations.push("filled: missing actual table"); + if (filled.header?.background !== "rgba(0, 0, 0, 0)") + violations.push("filled: opaque header"); + if (filled.header?.weight !== "500") + violations.push("filled: header weight"); + if (filled.header?.padding !== "12px 16px") + violations.push("filled: header padding"); + if (filled.table?.shadow !== "none") + violations.push("filled: panel shadow"); + if (width > 640 && filled.cell?.padding !== "12px 16px") + violations.push("filled: cell density"); + if (width < 640 && !["block", "grid"].includes(filled.rowDisplay)) + violations.push("filled: mobile card missing"); + const visibleData = filled.visibleCells + .map((cell) => cell.text) + .join(" "); + if (!visibleData.includes(visibleText)) + violations.push("filled: card data hidden"); + if (filled.actionLabelLines.some((lines) => lines > 1)) + violations.push("filled: row action label wraps"); + } else { + if (filled.tableCount !== 0) + violations.push("filled: article page became a table"); + if (filled.articleCount !== 1) + violations.push("filled: missing article card"); + } + const surface = kind === "table" ? filled.table : filled.article; + const background = luminance(surface.background); + const foreground = luminance(surface.color); + if (theme === "light" ? background < 0.5 : background > 0.1) + violations.push("filled: surface ignores theme"); + if ( + (Math.max(background, foreground) + 0.05) / + (Math.min(background, foreground) + 0.05) < + 4.5 + ) + violations.push("filled: unreadable theme contrast"); + assert.equal( + await content + .getByText(visibleText, { exact: true }) + .first() + .isVisible(), + true, + ); + assert.deepEqual(violations, [], JSON.stringify({ empty, filled })); + return { routePath, width, theme, kind, empty, filled }; + }), + ); + } + } +}