diff --git a/assets/css/admin/invite-codes.css b/assets/css/admin/invite-codes.css index c786c935..5efd51b7 100644 --- a/assets/css/admin/invite-codes.css +++ b/assets/css/admin/invite-codes.css @@ -2,10 +2,13 @@ .aksrt-inv-page [hidden] { display: none !important; } .aksrt-inv-page .aksrt-native-header__actions .button { display: inline-flex; align-items: center; gap: 5px; } .aksrt-inv-page .aksrt-native-header__actions .dashicons { margin: 0; font-size: 17px; line-height: 20px; } +.aksrt-inv-toolbar { padding: 14px 20px; border-bottom: 1px solid var(--an-border-soft); } +.aksrt-inv-toolbar__views { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; } .aksrt-inv-toolbar .subsubsub { margin: 0; padding: 0; box-shadow: none; } .aksrt-inv-toolbar .aksrt-native-filters { margin: 0; } -.aksrt-inv-filter-summary { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--an-border-soft); overflow-wrap: anywhere; } -.aksrt-inv-selection { color: var(--an-text-2); font-size: 12px; font-variant-numeric: tabular-nums; } +.aksrt-inv-filter-remark { max-width: 240px; color: var(--an-text-2); font-size: 12px; overflow-wrap: anywhere; } +.aksrt-inv-bulk { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 12px 20px; border-bottom: 1px solid var(--an-border-soft); } +.aksrt-inv-selection { margin-right: auto; color: var(--an-text-2); font-size: 12px; font-variant-numeric: tabular-nums; } .aksrt-inv-page .aksrt-inv-table { min-width: 1000px; table-layout: fixed; } .aksrt-inv-table .column-code { width: 21%; } @@ -90,8 +93,10 @@ html.aksrt-inv-modal-open { overflow: hidden; } @media (max-width: 600px) { .aksrt-inv-page .aksrt-native-header__actions { width: 100%; } .aksrt-inv-page .aksrt-native-header__actions .button { flex: 1; justify-content: center; } + .aksrt-inv-toolbar, + .aksrt-inv-bulk { padding-left: 14px; padding-right: 14px; } .aksrt-inv-toolbar .aksrt-native-filters { width: 100%; } - .aksrt-inv-toolbar input[type="search"] { flex: 1; width: 0; min-width: 0; } + .aksrt-inv-toolbar input[type="search"] { flex: 1 1 calc(100% - 76px); width: 0; min-width: 0; } .aksrt-inv-dialog { max-height: calc(100dvh - 24px); } .aksrt-inv-dialog__head, .aksrt-inv-dialog__body, diff --git a/src/Admin/Native/UsersMenusInviteCode.php b/src/Admin/Native/UsersMenusInviteCode.php index 07a1a0a5..86ffc55f 100644 --- a/src/Admin/Native/UsersMenusInviteCode.php +++ b/src/Admin/Native/UsersMenusInviteCode.php @@ -81,40 +81,38 @@ trait UsersMenusInviteCode { $filters = ['status' => $status, 's' => $s, 'remark' => $remark, 'orderby' => $data['orderby'], 'desc' => $data['desc']]; $filter_url = add_query_arg($filters, $this_url); - NativeUi::card_open('', '', false, 'aksrt-inv-toolbar'); - echo '
'; echo ''; foreach (array_diff_key($filters, ['s' => '']) as $key => $value) { echo ''; } echo ''; echo ''; - echo ''; - if ($s !== '' || $remark !== '' || $status !== 'all') { - echo '
'; - if ($s !== '') { - echo '' . esc_html(sprintf(__('关键词:%s', 'aksrt'), $s)) . ''; - } - if ($remark !== '') { - echo '' . esc_html(sprintf(__('标识:%s', 'aksrt'), $remark)) . ''; - } - echo '' . esc_html__('清除筛选', 'aksrt') . '
'; + echo ''; + if ($remark !== '') { + echo '' . esc_html(sprintf(__('标识:%s', 'aksrt'), $remark)) . ''; } - NativeUi::card_close(); + if ($s !== '' || $remark !== '' || $status !== 'all') { + echo '' . esc_html__('清除筛选', 'aksrt') . ''; + } + echo ''; echo '
'; echo ''; - $bulk = '
' + echo '
' . '' . '' . '
'; - NativeUi::table_card_open(__('邀请码列表', 'aksrt'), $bulk); + echo '
'; echo ''; $columns = [ @@ -171,8 +169,7 @@ trait UsersMenusInviteCode { 'type' => 'plain', 'prev_text' => '«', 'next_text' => '»', ]); } - echo ''; + echo ''; NativeUi::card_close(); - echo ''; } } diff --git a/tests/widget-system/invite-admin-contract.test.cjs b/tests/widget-system/invite-admin-contract.test.cjs index 8e5ef471..01cb6ce6 100644 --- a/tests/widget-system/invite-admin-contract.test.cjs +++ b/tests/widget-system/invite-admin-contract.test.cjs @@ -40,6 +40,43 @@ function hasBooleanAttr(tag, name) { assert.match(tag, new RegExp(`(?:\\s|^)${name}(?:\\s|>|$)`)); } +test('filters, selection, table and pagination share one list card', () => { + const { html } = harness(); + const cards = [...html.matchAll(/]*class="[^"]*\baksrt-native-card\b[^"]*"[^>]*>[\s\S]*?<\/section>/g)]; + assert.equal(cards.length, 1); + const card = cards[0][0]; + assert.match(card, /aksrt-inv-list-card/); + assert.match(card, /

邀请码列表<\/h2>/); + for (const marker of ['class="subsubsub"', 'class="aksrt-native-filters"', '清除筛选', 'class="aksrt-inv-bulk"', 'data-aksrt-inv-selection', 'aksrt-inv-table', 'class="tablenav bottom"']) { + assert.ok(card.includes(marker), `${marker} belongs to the list card`); + } + assert.doesNotMatch(html, /aksrt-inv-filter-summary|关键词:/); + assert.doesNotMatch(read('assets', 'css', 'admin', 'invite-codes.css'), /aksrt-inv-filter-summary/); +}); + +test('search and bulk actions remain independent sibling forms inside the list card', () => { + const { html } = harness(); + let depth = 0; + let count = 0; + for (const [tag] of html.matchAll(/<\/?form\b[^>]*>/g)) { + if (tag.startsWith('= 0 && depth <= 1, 'forms must never nest'); + } + assert.equal(depth, 0); + assert.equal(count, 4); + const searchEnd = html.indexOf('', html.indexOf('class="aksrt-native-filters"')); + const listStart = html.lastIndexOf('', listStart); + assert.ok(html.indexOf('class="tablenav bottom"', listStart) < listEnd); + assert.ok(listEnd < html.indexOf('', listStart)); +}); + test('page renders add and export dialogs beside, not inside, the list form', () => { const { html } = harness(); const listMarker = html.indexOf('data-aksrt-inv-list');

'; echo '