feat(admin): open ticket creation in a dialog on the list page

Move "代客提交工单" back into a native <dialog> on the ticket list while
the ticket workspace stays a full page. Closing asks before discarding
input, ignores backdrop clicks, resets the form and returns focus; an
uncertain create result keeps the form locked behind "重新加载".

Remove the ?view=new create page route and its styles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-31 14:57:35 +08:00
1 parent d86a85e6a6
commit 54cc5301c3
9 files changed
+169 -65

No files matched your search

+13 -12
View File
@@ -13,12 +13,13 @@ function rendered() {
return fixture;
}
test('list view links to page views and renders no dialogs or eager conversations', () => {
test('list view links rows to the workspace and offers creation only as a dialog', () => {
const { list, initial } = rendered();
assert.doesNotMatch(list, /<dialog|data-aksrt-dialog-open|aria-haspopup="dialog"/);
assert.match(list, /aksrt-native-header__actions[^]*href="[^"]*page=aksrt_tickets&amp;view=new&amp;status=handling&amp;ticket_type_id=1&amp;s=needle&amp;paged=2"/);
assert.equal((list.match(/<dialog /g) || []).length, 1, 'only the create dialog');
assert.match(list, /aksrt-native-header__actions[^]*data-aksrt-dialog-open="aksrt-ticket-create-dialog"/);
assert.doesNotMatch(list, /view=new/);
assert.match(list, /<a class="row-title" href="[^"]*page=aksrt_tickets&amp;id=21&amp;status=handling&amp;ticket_type_id=1&amp;s=needle&amp;paged=2"/);
assert.doesNotMatch(list, /data-aksrt-ticket-detail=|data-aksrt-ticket-form=/);
assert.doesNotMatch(list, /data-aksrt-ticket-detail=|data-aksrt-ticket-form="(?:reply|status|note)"/);
assert.equal(initial.detail_reads, 0);
assert.equal(initial.reply_reads, 0);
assert.equal(initial.queries.at(-1).offset, 20);
@@ -88,14 +89,14 @@ test('missing ticket renders an empty state with a way back and no forms', () =>
assert.doesNotMatch(missing, /data-aksrt-ticket-form|data-aksrt-ticket-detail/);
});
test('create page is a full-page form that posts to REST and returns to the list context', () => {
const { create } = rendered();
assert.doesNotMatch(create, /<dialog|data-aksrt-dialog/);
assert.match(create, /<form method="post" action="https:\/\/example.test\/wp-json\/aksrt\/v1\/admin\/tickets" class="aksrt-tickets-admin__create-form" data-aksrt-ticket-form="create">/);
for (const name of ['user_id', 'ticket_type_id', 'title', 'order_number', 'content']) assert.ok(create.includes(`name="${name}"`), name);
assert.match(create, /data-aksrt-ticket-feedback/);
const back = /href="[^"]*page=aksrt_tickets&amp;status=pending&amp;paged=2"/g;
assert.equal((create.match(back) || []).length, 2, 'header back link and cancel both keep the list context');
test('create dialog is a labelled REST form with its own feedback and close controls', () => {
const { list } = rendered();
const dialog = list.match(/<dialog id="aksrt-ticket-create-dialog"[^]*?<\/dialog>/)?.[0] || '';
assert.match(dialog, /aria-labelledby="aksrt-ticket-create-title"/);
assert.match(dialog, /<form method="post" action="https:\/\/example.test\/wp-json\/aksrt\/v1\/admin\/tickets" class="aksrt-ticket-dialog__frame" data-aksrt-ticket-form="create">/);
for (const name of ['user_id', 'ticket_type_id', 'title', 'order_number', 'content']) assert.ok(dialog.includes(`name="${name}"`), name);
assert.match(dialog, /data-aksrt-ticket-feedback/);
assert.equal((dialog.match(/data-aksrt-dialog-close/g) || []).length, 2, 'header X and footer cancel');
});
test('real rendering escapes user content, quotes and metadata', () => {