refactor(ticket): move the config tab into the SPA ticket center
users.php → 工单管理 的「配置」Tab 只管理部门 / 状态 / 预设回复三块 DB 字典,与 SPA「工单中心」已有的 8 个标量配置分裂成两处;且同一套 CRUD 在 admin_post 与 TicketConfigRest 里各写了一遍,REST 侧此前 没有任何调用方。 - 删除原生 config tab:UsersMenusTicketsConfig.php 整份移除, UsersMenusTicketsCenter 由 512 行降到 251 行(六个写处理方法、 分发分支、十条提示码与 ticket_delete_button 一并清理) - SPA /users/ticket 改为 customPage:内嵌 SchemaPage 承载原有 8 字段, 三块字典改为 n-data-table + n-modal + n-dialog,取色器/管理员多选/ 拖拽排序替代原生手填表单 - TicketConfigRest 成为三块字典的唯一实现,新增 api/modules/tickets.js 封装 aksrt/v1/admin/tickets* - 双向深链:原生页跳 SPA 配置页,SPA 页跳回原生工单列表 契约测试同步更新,并新增 ticket-spa-config-contract 守护接线。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d32bdc0581
commit
e7ca0ee5df
19 files changed
+1074
-509
No files matched your search
@@ -4,9 +4,10 @@
|
||||
* 工单后台(users.php → aksrt_tickets)契约:
|
||||
* 1) 子菜单已注册,trait 已组合,写操作走 admin_post 且每个都带 nonce;
|
||||
* 2) 分页用共享 AdminListPagination,不写死 limit;
|
||||
* 3) 默认状态与「仍有工单」两类删除被拒;
|
||||
* 3) 默认状态与「仍有工单」两类删除被拒(兜底在 TicketConfigRest,SPA 工单中心消费);
|
||||
* 4) SPA 深链 redirect 指向 users.php?page=aksrt_tickets;
|
||||
* 5) 前端 JS 与 PHP 的 data-* 挂载点对齐。
|
||||
* 部门 / 状态 / 预设回复的配置已迁至 SPA「工单中心」,本页只剩工单列表。
|
||||
*/
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
@@ -36,14 +37,48 @@ test('write actions are nonce-protected and dispatched by action name', () => {
|
||||
assert.match(center, /check_admin_referer\('aksrt_ticket_' \. \$action \. '_' \. \$id\);/);
|
||||
assert.ok(center.includes('current_user_can(self::CAPABILITY)'), 'capability check missing');
|
||||
|
||||
// 每个动作分支都要有对应处理器。
|
||||
for (const action of ['status', 'reply', 'type', 'status_def', 'prereply', 'prereply_delete', 'type_delete', 'status_delete', 'note', 'create']) {
|
||||
// 部门 / 状态 / 预设回复已迁至 SPA 工单中心,本页只剩工单自身的四个动作。
|
||||
for (const action of ['status', 'reply', 'note', 'create']) {
|
||||
assert.ok(center.includes(`case '${action}':`), `dispatch case ${action} missing`);
|
||||
}
|
||||
|
||||
// 表单里 nonce 以 action + id 为单位,删除类操作独立小表单 + 二次确认。
|
||||
assert.match(center, /wp_nonce_field\('aksrt_ticket_' \. \$action \. '_' \. \$id, '_wpnonce', true, false\)/);
|
||||
assert.match(center, /onsubmit="return confirm\(/);
|
||||
// nonce 以 action + id 为单位,行内表单各自带自己的 nonce(渲染在 List trait 中)。
|
||||
// 删除类操作已迁至 SPA(n-dialog 二次确认),本页不再有 onsubmit=confirm 的小表单。
|
||||
const list = read('src/Admin/Native/UsersMenusTicketsList.php');
|
||||
for (const hook of [
|
||||
'aksrt_ticket_reply_',
|
||||
'aksrt_ticket_status_',
|
||||
'aksrt_ticket_note_',
|
||||
]) {
|
||||
assert.ok(list.includes(hook), `nonce prefix ${hook} missing`);
|
||||
}
|
||||
// Tab 已移除,三个行内表单不再输出 ticket_tab 隐藏域。
|
||||
assert.ok(!list.includes('ticket_tab'), 'ticket_tab hidden input must be gone');
|
||||
assert.ok(!center.includes('onsubmit="return confirm('), 'config delete forms must be gone');
|
||||
});
|
||||
|
||||
test('the native page carries no config tab and deep-links the SPA instead', () => {
|
||||
const center = read('src/Admin/Native/UsersMenusTicketsCenter.php');
|
||||
|
||||
// Tab 结构已移除:不再有 nav-tab-wrapper,也不读 ?tab=。
|
||||
assert.ok(!center.includes('nav-tab-wrapper'), 'tab wrapper must be gone');
|
||||
assert.ok(!center.includes("'config'"), 'config tab must be gone');
|
||||
assert.ok(!/\$_GET\['tab'\]/.test(center), 'tab param must not be read');
|
||||
|
||||
// 配置 Tab 的渲染 trait 与删除按钮 helper 一并移除。
|
||||
assert.ok(!center.includes('UsersMenusTicketsConfig'), 'config trait must not be composed');
|
||||
assert.ok(!center.includes('render_ticket_config_tab'), 'config renderer must be gone');
|
||||
assert.ok(!center.includes('ticket_delete_button'), 'config-only delete helper must be gone');
|
||||
|
||||
// 六类配置写操作全部迁至 TicketConfigRest,本页不再分发。
|
||||
for (const action of ['type', 'status_def', 'prereply', 'prereply_delete', 'type_delete', 'status_delete']) {
|
||||
assert.ok(!center.includes(`case '${action}':`), `config action ${action} must be gone`);
|
||||
}
|
||||
|
||||
// 页面顶部提供跳往 SPA「工单中心」的快捷入口。
|
||||
assert.match(center, /admin_url\('admin\.php\?page=aksrt-settings#\/users\/ticket'\)/);
|
||||
assert.ok(!fs.existsSync(path.join(root, 'src/Admin/Native/UsersMenusTicketsConfig.php')),
|
||||
'the config trait file must be deleted');
|
||||
});
|
||||
|
||||
test('the ticket list paginates through the shared helper', () => {
|
||||
@@ -61,24 +96,6 @@ test('the ticket list paginates through the shared helper', () => {
|
||||
assert.match(list, /'limit' => self::TICKET_PER_PAGE,/);
|
||||
});
|
||||
|
||||
test('config tab edits departments, statuses and prereplies', () => {
|
||||
const config = read('src/Admin/Native/UsersMenusTicketsConfig.php');
|
||||
|
||||
assert.match(config, /render_ticket_types_panel/);
|
||||
assert.match(config, /render_ticket_statuses_panel/);
|
||||
assert.match(config, /render_ticket_prereplies_panel/);
|
||||
|
||||
// 默认状态行不给编辑/删除入口。
|
||||
assert.match(config, /empty\(\$row\['is_default'\]\)/);
|
||||
// 删除按钮带 confirm,且走独立小表单。
|
||||
assert.match(config, /ticket_delete_button\('status_delete', 'status_id'/);
|
||||
assert.match(config, /ticket_delete_button\('type_delete', 'type_id'/);
|
||||
assert.match(config, /ticket_delete_button\('prereply_delete', 'prereply_id'/);
|
||||
|
||||
// 状态徽章颜色来自状态表,不写死。
|
||||
assert.match(config, /--aksrt-ticket-color:%s/);
|
||||
});
|
||||
|
||||
test('detail panel renders replies, quote blocks and internal notes', () => {
|
||||
const list = read('src/Admin/Native/UsersMenusTicketsList.php');
|
||||
const body = list.slice(list.indexOf('private function render_ticket_detail_panel'));
|
||||
|
||||
Reference in new issue
Block a user