refactor: rename the theme brand and every identifier from AKSRT to Jio

Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-01 20:05:04 +08:00
1 parent 5b16bc6e15
commit 35bb96e82f
997 files changed
+28216 -28188

No files matched your search

+139 -139
View File
@@ -1,23 +1,23 @@
/**
* AKSRT Ticket Center — 用户中心「工单中心」面板。
* Jio Ticket Center — 用户中心「工单中心」面板。
*
* - 列表 / 统计 / 详情全部走 REST,模板只输出容器与首屏骨架(profile-tickets.php)
* - 统计卡兼作状态快捷筛选,与状态下拉双向同步;搜索输入防抖,旧请求结果按序号丢弃
* - 弹窗经 AksrtModalRuntime.open 打开(运行时自带标题栏 / 焦点陷阱 / 移动端底部抽屉),
* - 弹窗经 JioModalRuntime.open 打开(运行时自带标题栏 / 焦点陷阱 / 移动端底部抽屉),
* 先出运行时骨架,数据到达后填充,避免点击后无反馈
* - 详情为对话时间线:问题描述作为首条消息,用户靠右、客服靠左;回复后原地刷新而不是关窗
* - 详情三段式:顶部可折叠概要(默认收起)+ 唯一滚动区时间线 + 底部回复框;
* 回复框工具栏(图片 / 催单 / 关闭)在输入框上方,待发送图片以缩略图条展示
* - 提交工单与回复前先过人机验证(ticket_create / ticket_reply,场景未启用自动放行)
* - 图片走 /aksrt/v1/ticket/upload-attachment,上传中显示占位格,未传完禁止提交
* - 图片走 /jio/v1/ticket/upload-attachment,上传中显示占位格,未传完禁止提交
*
* @package AKSRT
* @package Jio
*/
(function () {
'use strict';
var core = window.AksrtTicketCore;
var core = window.JioTicketCore;
if (!core) return;
var settings = core.settings;
var attachmentEnabled = core.attachmentEnabled;
@@ -65,12 +65,12 @@
* ------------------------------------------------------------------ */
function panel() {
return document.querySelector('[data-aksrt-ticket-center]');
return document.querySelector('[data-jio-ticket-center]');
}
function listNode() {
var root = panel();
return root ? root.querySelector('[data-aksrt-ticket-list]') : null;
return root ? root.querySelector('[data-jio-ticket-list]') : null;
}
function hasFilters() {
@@ -78,7 +78,7 @@
}
function skeletonHtml() {
var card = '<div class="aksrt-tickets-page__card is-placeholder">'
var card = '<div class="jio-tickets-page__card is-placeholder">'
+ '<div class="placeholder s1"></div><div class="placeholder k1"></div>'
+ '<div class="placeholder k3" style="width: 42%;"></div></div>';
return '<div class="post_ajax_loader is-loading" aria-hidden="true">' + card + card + card + '</div>';
@@ -90,7 +90,7 @@
var seq = ++state.listSeq;
// 已有卡片时只做淡化,避免整块替换造成跳动;空态 / 错误态则换回骨架。
if (!list.querySelector('[data-aksrt-ticket-id]')) list.innerHTML = skeletonHtml();
if (!list.querySelector('[data-jio-ticket-id]')) list.innerHTML = skeletonHtml();
list.classList.add('is-busy');
list.setAttribute('aria-busy', 'true');
@@ -113,7 +113,7 @@
}).catch(function (error) {
if (seq !== state.listSeq) return;
list.innerHTML = emptyHtml('fa-exclamation-circle', '工单列表加载失败', failMessage(error, '网络异常,请稍后重试。'),
'<button type="button" class="btn-outline" data-aksrt-ticket-retry>重新加载</button>');
'<button type="button" class="btn-outline" data-jio-ticket-retry>重新加载</button>');
renderPagination(true);
}).then(function () {
if (seq !== state.listSeq) return;
@@ -128,9 +128,9 @@
if (!items.length) {
list.innerHTML = hasFilters()
? emptyHtml('fa-search', '没有符合条件的工单', '换个关键词,或清除筛选后再试。',
'<button type="button" class="btn-outline" data-aksrt-ticket-reset>清除筛选</button>')
'<button type="button" class="btn-outline" data-jio-ticket-reset>清除筛选</button>')
: emptyHtml('fa-headset', '还没有工单', '遇到问题时提交工单,我们会尽快处理。',
'<button type="button" class="btn-primary" data-aksrt-ticket-create>' + icon('fa-plus') + '提交第一个工单</button>');
'<button type="button" class="btn-primary" data-jio-ticket-create>' + icon('fa-plus') + '提交第一个工单</button>');
return;
}
list.innerHTML = items.map(cardHtml).join('');
@@ -144,14 +144,14 @@
meta.push('<span>' + icon('fa-clock') + '更新于 ' + timeTag(ticket.updated_at || ticket.created_at) + '</span>');
// 标题按钮的 ::after 铺满整卡,整张卡可点且只有一个可聚焦目标。
return '<article class="aksrt-tickets-page__card' + (ticket.is_closed ? ' is-closed' : '') + '"'
+ ' data-aksrt-ticket-id="' + id + '" style="--aksrt-ticket-color:' + escapeHtml(ticket.status_color || '#6b7280') + '">'
+ '<div class="aksrt-tickets-page__card-head">' + statusBadge(ticket)
+ '<span class="aksrt-tickets-page__card-num">#' + escapeHtml(ticket.ticket_num) + '</span></div>'
+ '<h3 class="aksrt-tickets-page__card-title"><button type="button" data-aksrt-ticket-view="' + id + '">'
return '<article class="jio-tickets-page__card' + (ticket.is_closed ? ' is-closed' : '') + '"'
+ ' data-jio-ticket-id="' + id + '" style="--jio-ticket-color:' + escapeHtml(ticket.status_color || '#6b7280') + '">'
+ '<div class="jio-tickets-page__card-head">' + statusBadge(ticket)
+ '<span class="jio-tickets-page__card-num">#' + escapeHtml(ticket.ticket_num) + '</span></div>'
+ '<h3 class="jio-tickets-page__card-title"><button type="button" data-jio-ticket-view="' + id + '">'
+ escapeHtml(ticket.title) + '</button></h3>'
+ '<div class="aksrt-tickets-page__card-meta">' + meta.join('') + '</div>'
+ '<span class="aksrt-tickets-page__card-arrow" aria-hidden="true">' + icon('fa-chevron-right') + '</span>'
+ '<div class="jio-tickets-page__card-meta">' + meta.join('') + '</div>'
+ '<span class="jio-tickets-page__card-arrow" aria-hidden="true">' + icon('fa-chevron-right') + '</span>'
+ '</article>';
}
@@ -168,13 +168,13 @@
}
function stepButton(page, label, iconName, disabled) {
return '<button type="button" class="aksrt-tickets-page__page-chip is-step" data-aksrt-ticket-page="' + page + '"'
return '<button type="button" class="jio-tickets-page__page-chip is-step" data-jio-ticket-page="' + page + '"'
+ ' aria-label="' + label + '"' + (disabled ? ' disabled' : '') + '>' + icon(iconName) + '</button>';
}
function renderPagination(hide) {
var root = panel();
var nav = root && root.querySelector('[data-aksrt-ticket-pagination]');
var nav = root && root.querySelector('[data-jio-ticket-pagination]');
if (!nav) return;
if (hide || state.totalPages <= 1) {
nav.hidden = true;
@@ -184,12 +184,12 @@
var html = stepButton(state.page - 1, '上一页', 'fa-chevron-left', state.page <= 1);
pageList(state.page, state.totalPages).forEach(function (page) {
if (page === 'gap') {
html += '<span class="aksrt-tickets-page__page-gap" aria-hidden="true">…</span>';
html += '<span class="jio-tickets-page__page-gap" aria-hidden="true">…</span>';
return;
}
var active = page === state.page;
html += '<button type="button" class="aksrt-tickets-page__page-chip' + (active ? ' is-active' : '') + '"'
+ ' data-aksrt-ticket-page="' + page + '"' + (active ? ' aria-current="page"' : '') + '>' + page + '</button>';
html += '<button type="button" class="jio-tickets-page__page-chip' + (active ? ' is-active' : '') + '"'
+ ' data-jio-ticket-page="' + page + '"' + (active ? ' aria-current="page"' : '') + '>' + page + '</button>';
});
html += stepButton(state.page + 1, '下一页', 'fa-chevron-right', state.page >= state.totalPages);
nav.innerHTML = html;
@@ -205,10 +205,10 @@
Object.keys(data).forEach(function (key) {
var count = parseInt(data[key], 10) || 0;
total += count;
var node = root.querySelector('[data-aksrt-ticket-stat="' + key + '"] strong');
var node = root.querySelector('[data-jio-ticket-stat="' + key + '"] strong');
if (node) node.textContent = String(count);
});
var all = root.querySelector('[data-aksrt-ticket-stat=""] strong');
var all = root.querySelector('[data-jio-ticket-stat=""] strong');
if (all) all.textContent = String(total);
}).catch(function () { /* 统计失败不阻塞列表 */ });
}
@@ -216,14 +216,14 @@
function syncFilters() {
var root = panel();
if (!root) return;
root.querySelectorAll('[data-aksrt-ticket-stat]').forEach(function (button) {
var active = button.getAttribute('data-aksrt-ticket-stat') === state.status;
root.querySelectorAll('[data-jio-ticket-stat]').forEach(function (button) {
var active = button.getAttribute('data-jio-ticket-stat') === state.status;
button.classList.toggle('is-active', active);
button.setAttribute('aria-pressed', active ? 'true' : 'false');
});
var select = root.querySelector('[data-aksrt-ticket-filter="status"]');
var select = root.querySelector('[data-jio-ticket-filter="status"]');
if (select) select.value = state.status;
var reset = root.querySelector('[data-aksrt-tickets-search] [data-aksrt-ticket-reset]');
var reset = root.querySelector('[data-jio-tickets-search] [data-jio-ticket-reset]');
if (reset) reset.hidden = !hasFilters();
}
@@ -236,9 +236,9 @@
function resetFilters() {
var root = panel();
if (root) {
var input = root.querySelector('[data-aksrt-tickets-search] input');
var input = root.querySelector('[data-jio-tickets-search] input');
if (input) input.value = '';
var type = root.querySelector('[data-aksrt-ticket-filter="ticket_type_id"]');
var type = root.querySelector('[data-jio-ticket-filter="ticket_type_id"]');
if (type) type.value = '';
}
applyFilters({ status: '', type: '', keywords: '' });
@@ -260,7 +260,7 @@
* ------------------------------------------------------------------ */
function openDetail(ticketId) {
var shell = openModal({ title: '工单详情', className: 'aksrt-ticket-modal aksrt-ticket-detail-modal', kind: 'ticket-detail' });
var shell = openModal({ title: '工单详情', className: 'jio-ticket-modal jio-ticket-detail-modal', kind: 'ticket-detail' });
if (shell) fetchDetail(shell, ticketId, { initial: true });
}
@@ -275,20 +275,20 @@
}
function renderDetail(shell, ticket, opts) {
var ctx = shell.__aksrtTicket || (shell.__aksrtTicket = bindShell(shell, onDetailClick));
var ctx = shell.__jioTicket || (shell.__jioTicket = bindShell(shell, onDetailClick));
ctx.ticket = ticket;
ctx.quoteId = 0;
ctx.urgeUntil = Date.now() + Math.max(0, parseInt(ticket.urge_wait, 10) || 0) * 1000;
window.clearTimeout(ctx.closeTimer);
var title = shell.querySelector('.aksrt-modal-runtime__title');
var title = shell.querySelector('.jio-modal-runtime__title');
if (title) title.textContent = '工单 #' + ticket.ticket_num;
setModalContent(shell, buildDetailHtml(ticket, !!ctx.summaryOpen));
// 有沟通记录或刚发完回复时定位到最新一条(聊天类产品惯例)。
var thread = shell.querySelector('[data-aksrt-ticket-thread]');
var thread = shell.querySelector('[data-jio-ticket-thread]');
if (thread && (opts.scrollEnd || (ticket.replies || []).length)) stickToEnd(thread);
var textarea = shell.querySelector('[data-aksrt-ticket-reply-form] textarea');
var textarea = shell.querySelector('[data-jio-ticket-reply-form] textarea');
if (opts.scrollEnd && textarea) textarea.focus();
if (textarea) {
textarea.addEventListener('keydown', function (event) {
@@ -304,7 +304,7 @@
});
}
startUrgeTicker(shell, ctx);
var form = shell.querySelector('[data-aksrt-ticket-reply-form]');
var form = shell.querySelector('[data-jio-ticket-reply-form]');
if (form) {
form.addEventListener('submit', function (event) {
event.preventDefault();
@@ -319,36 +319,36 @@
// 概要条:状态 + 单行标题 + 展开按钮;展开后显示完整标题与要素,状态跨重绘保留。
function summaryHtml(ticket, open) {
var panelId = 'aksrt-ticket-summary-' + escapeHtml(ticket.id);
var panelId = 'jio-ticket-summary-' + escapeHtml(ticket.id);
var facts = fact('工单编号', '#' + escapeHtml(ticket.ticket_num))
+ fact('提交时间', timeTag(ticket.created_at))
+ (ticket.ticket_type_name ? fact('工单部门', escapeHtml(ticket.ticket_type_name)) : '')
+ (ticket.order_number ? fact('关联订单', escapeHtml(ticket.order_number)) : '')
+ fact('处理客服', ticket.admin_name ? escapeHtml(ticket.admin_name) : '待分配');
return '<section class="aksrt-ticket-detail__summary' + (open ? ' is-open' : '') + '"'
+ ' style="--aksrt-ticket-color:' + escapeHtml(ticket.status_color || '#6b7280') + '">'
+ '<button type="button" class="aksrt-ticket-detail__toggle" data-aksrt-ticket-summary-toggle'
return '<section class="jio-ticket-detail__summary' + (open ? ' is-open' : '') + '"'
+ ' style="--jio-ticket-color:' + escapeHtml(ticket.status_color || '#6b7280') + '">'
+ '<button type="button" class="jio-ticket-detail__toggle" data-jio-ticket-summary-toggle'
+ ' aria-expanded="' + (open ? 'true' : 'false') + '" aria-controls="' + panelId + '">'
+ statusBadge(ticket)
+ '<span class="aksrt-ticket-detail__title">' + escapeHtml(ticket.title) + '</span>'
+ '<span class="aksrt-ticket-detail__toggle-text">' + (open ? '收起' : '详情') + icon('fa-chevron-down') + '</span>'
+ '<span class="jio-ticket-detail__title">' + escapeHtml(ticket.title) + '</span>'
+ '<span class="jio-ticket-detail__toggle-text">' + (open ? '收起' : '详情') + icon('fa-chevron-down') + '</span>'
+ '</button>'
+ '<div class="aksrt-ticket-detail__more" id="' + panelId + '"' + (open ? '' : ' hidden') + '>'
+ '<h4 class="aksrt-ticket-detail__full-title">' + escapeHtml(ticket.title) + '</h4>'
+ '<dl class="aksrt-ticket-detail__facts">' + facts + '</dl></div></section>';
+ '<div class="jio-ticket-detail__more" id="' + panelId + '"' + (open ? '' : ' hidden') + '>'
+ '<h4 class="jio-ticket-detail__full-title">' + escapeHtml(ticket.title) + '</h4>'
+ '<dl class="jio-ticket-detail__facts">' + facts + '</dl></div></section>';
}
function toggleSummary(shell, ctx) {
var section = shell.querySelector('.aksrt-ticket-detail__summary');
var button = section && section.querySelector('[data-aksrt-ticket-summary-toggle]');
var more = section && section.querySelector('.aksrt-ticket-detail__more');
var section = shell.querySelector('.jio-ticket-detail__summary');
var button = section && section.querySelector('[data-jio-ticket-summary-toggle]');
var more = section && section.querySelector('.jio-ticket-detail__more');
if (!button || !more) return;
ctx.summaryOpen = !ctx.summaryOpen;
section.classList.toggle('is-open', ctx.summaryOpen);
button.setAttribute('aria-expanded', ctx.summaryOpen ? 'true' : 'false');
more.hidden = !ctx.summaryOpen;
var text = button.querySelector('.aksrt-ticket-detail__toggle-text');
var text = button.querySelector('.jio-ticket-detail__toggle-text');
if (text) text.innerHTML = (ctx.summaryOpen ? '收起' : '详情') + icon('fa-chevron-down');
}
@@ -361,10 +361,10 @@
}
function buildDetailHtml(ticket, summaryOpen) {
var html = '<div class="aksrt-ticket-detail" data-aksrt-ticket-detail-id="' + escapeHtml(ticket.id) + '">'
var html = '<div class="jio-ticket-detail" data-jio-ticket-detail-id="' + escapeHtml(ticket.id) + '">'
+ summaryHtml(ticket, summaryOpen);
html += '<ol class="aksrt-ticket-thread" data-aksrt-ticket-thread tabindex="0" aria-label="沟通记录">';
html += '<ol class="jio-ticket-thread" data-jio-ticket-thread tabindex="0" aria-label="沟通记录">';
html += messageHtml({
id: 0,
author_type: 'user',
@@ -380,15 +380,15 @@
html += '</ol>';
html += ticket.is_closed
? '<p class="aksrt-ticket-detail__closed">' + icon('fa-lock') + '工单已关闭,如需继续沟通请提交新工单。</p>'
? '<p class="jio-ticket-detail__closed">' + icon('fa-lock') + '工单已关闭,如需继续沟通请提交新工单。</p>'
: composerHtml(ticket);
return html + '</div>';
}
function attachmentPreview(images) {
if (!images || !images.length) return '';
return '<div class="aksrt-ticket-attachments">' + images.map(function (url) {
return '<a class="aksrt-ticket-attachment" href="' + escapeHtml(url) + '" target="_blank" rel="noopener">'
return '<div class="jio-ticket-attachments">' + images.map(function (url) {
return '<a class="jio-ticket-attachment" href="' + escapeHtml(url) + '" target="_blank" rel="noopener">'
+ '<img src="' + escapeHtml(url) + '" alt="附件图片"></a>';
}).join('') + '</div>';
}
@@ -396,7 +396,7 @@
function quoteBlock(quote) {
if (!quote) return '';
var text = quote.is_deleted ? '原回复已删除' : (quote.content || '');
return '<blockquote class="aksrt-ticket-quote' + (quote.is_deleted ? ' is-deleted' : '') + '">'
return '<blockquote class="jio-ticket-quote' + (quote.is_deleted ? ' is-deleted' : '') + '">'
+ (quote.author_name ? '<strong>' + escapeHtml(quote.author_name) + '</strong>' : '')
+ '<span>' + escapeHtml(text) + '</span></blockquote>';
}
@@ -409,24 +409,24 @@
function messageHtml(msg, canQuote) {
if (msg.author_type === 'system') {
// 系统记录(如催单):居中提示,不可引用。
return '<li class="aksrt-ticket-msg is-system"><span class="aksrt-ticket-msg__system">'
return '<li class="jio-ticket-msg is-system"><span class="jio-ticket-msg__system">'
+ icon('fa-bell') + escapeHtml(msg.content) + timeTag(msg.created_at) + '</span></li>';
}
var admin = msg.author_type === 'admin';
var name = msg.author_name || (admin ? '客服' : '我');
var avatar = admin ? icon('fa-headset') : escapeHtml(Array.from(name)[0] || '我');
var tags = (admin ? '<span class="aksrt-ticket-msg__tag">客服</span>' : '')
+ (msg.isOrigin ? '<span class="aksrt-ticket-msg__tag">问题描述</span>' : '');
var tags = (admin ? '<span class="jio-ticket-msg__tag">客服</span>' : '')
+ (msg.isOrigin ? '<span class="jio-ticket-msg__tag">问题描述</span>' : '');
return '<li class="aksrt-ticket-msg is-' + (admin ? 'admin' : 'user') + (msg.isOrigin ? ' is-origin' : '') + '">'
+ '<span class="aksrt-ticket-msg__avatar" aria-hidden="true">' + avatar + '</span>'
+ '<div class="aksrt-ticket-msg__main">'
+ '<div class="aksrt-ticket-msg__head"><strong>' + escapeHtml(name) + '</strong>' + tags + timeTag(msg.created_at) + '</div>'
+ '<div class="aksrt-ticket-msg__bubble">' + quoteBlock(msg.quote)
+ '<div class="aksrt-ticket-msg__body">' + escapeHtml(msg.content) + '</div>'
return '<li class="jio-ticket-msg is-' + (admin ? 'admin' : 'user') + (msg.isOrigin ? ' is-origin' : '') + '">'
+ '<span class="jio-ticket-msg__avatar" aria-hidden="true">' + avatar + '</span>'
+ '<div class="jio-ticket-msg__main">'
+ '<div class="jio-ticket-msg__head"><strong>' + escapeHtml(name) + '</strong>' + tags + timeTag(msg.created_at) + '</div>'
+ '<div class="jio-ticket-msg__bubble">' + quoteBlock(msg.quote)
+ '<div class="jio-ticket-msg__body">' + escapeHtml(msg.content) + '</div>'
+ attachmentPreview(msg.attachment) + '</div>'
+ (canQuote && msg.id
? '<button type="button" class="aksrt-ticket-msg__quote" data-aksrt-ticket-quote="' + escapeHtml(msg.id) + '"'
? '<button type="button" class="jio-ticket-msg__quote" data-jio-ticket-quote="' + escapeHtml(msg.id) + '"'
+ ' data-name="' + escapeHtml(name) + '" data-excerpt="' + escapeHtml(excerpt(msg.content)) + '">'
+ icon('fa-reply') + '引用</button>'
: '')
@@ -434,34 +434,34 @@
}
function composerHtml(ticket) {
var fieldId = 'aksrt-ticket-reply-' + escapeHtml(ticket.id);
var fieldId = 'jio-ticket-reply-' + escapeHtml(ticket.id);
// 自上而下:工具栏(图片 | 催单 · 关闭)→ 引用条 → 待发送缩略图 → 输入框 → 发送行。
return '<form class="aksrt-ticket-composer" data-aksrt-ticket-reply-form novalidate>'
+ '<div class="aksrt-ticket-composer__toolbar" role="toolbar" aria-label="回复工具">'
+ '<div class="aksrt-ticket-composer__tools">' + attachButtonHtml() + '</div>'
+ '<div class="aksrt-ticket-composer__actions">'
+ '<button type="button" class="aksrt-ticket-tool is-chip" data-aksrt-ticket-urge title="提醒客服尽快处理">'
return '<form class="jio-ticket-composer" data-jio-ticket-reply-form novalidate>'
+ '<div class="jio-ticket-composer__toolbar" role="toolbar" aria-label="回复工具">'
+ '<div class="jio-ticket-composer__tools">' + attachButtonHtml() + '</div>'
+ '<div class="jio-ticket-composer__actions">'
+ '<button type="button" class="jio-ticket-tool is-chip" data-jio-ticket-urge title="提醒客服尽快处理">'
+ icon('fa-bell') + '<span>催单</span></button>'
+ '<button type="button" class="aksrt-ticket-tool is-chip is-danger" data-aksrt-ticket-close title="问题已解决时可关闭工单">'
+ '<button type="button" class="jio-ticket-tool is-chip is-danger" data-jio-ticket-close title="问题已解决时可关闭工单">'
+ icon('fa-check-circle') + '<span>关闭工单</span></button>'
+ '</div></div>'
+ '<div class="aksrt-ticket-composer__quote" data-aksrt-ticket-quote-preview hidden></div>'
+ '<div class="jio-ticket-composer__quote" data-jio-ticket-quote-preview hidden></div>'
+ attachTrayHtml()
+ '<div class="aksrt-ticket-composer__input">'
+ '<div class="jio-ticket-composer__input">'
+ '<label class="screen-reader-text" for="' + fieldId + '">回复内容</label>'
+ '<textarea id="' + fieldId + '" name="reply" rows="2" maxlength="' + lengthMax('reply') + '"'
+ ' placeholder="补充说明或回复客服…"></textarea></div>'
+ '<div class="aksrt-ticket-composer__bar">'
+ '<span class="aksrt-ticket-composer__hint">Ctrl + Enter 发送</span>'
+ '<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="reply">0 / ' + lengthMax('reply') + '</span>'
+ '<button type="submit" class="btn-primary" data-aksrt-ticket-submit>' + icon('fa-paper-plane') + '发送</button>'
+ '<div class="jio-ticket-composer__bar">'
+ '<span class="jio-ticket-composer__hint">Ctrl + Enter 发送</span>'
+ '<span class="jio-ticket-counter" data-jio-ticket-counter="reply">0 / ' + lengthMax('reply') + '</span>'
+ '<button type="submit" class="btn-primary" data-jio-ticket-submit>' + icon('fa-paper-plane') + '发送</button>'
+ '</div></form>';
}
function setQuote(shell, ctx, button) {
var preview = shell.querySelector('[data-aksrt-ticket-quote-preview]');
var preview = shell.querySelector('[data-jio-ticket-quote-preview]');
if (!preview) return;
ctx.quoteId = button ? parseInt(button.getAttribute('data-aksrt-ticket-quote'), 10) || 0 : 0;
ctx.quoteId = button ? parseInt(button.getAttribute('data-jio-ticket-quote'), 10) || 0 : 0;
if (!ctx.quoteId) {
preview.hidden = true;
preview.innerHTML = '';
@@ -470,27 +470,27 @@
preview.innerHTML = icon('fa-reply')
+ '<span>回复 <strong>' + escapeHtml(button.getAttribute('data-name')) + '</strong>:'
+ escapeHtml(button.getAttribute('data-excerpt')) + '</span>'
+ '<button type="button" data-aksrt-ticket-quote-cancel aria-label="取消引用">' + icon('fa-times') + '</button>';
+ '<button type="button" data-jio-ticket-quote-cancel aria-label="取消引用">' + icon('fa-times') + '</button>';
preview.hidden = false;
var textarea = shell.querySelector('[data-aksrt-ticket-reply-form] textarea');
var textarea = shell.querySelector('[data-jio-ticket-reply-form] textarea');
if (textarea) textarea.focus();
}
function onDetailClick(event, ctx) {
var shell = event.currentTarget;
if (event.target.closest('[data-aksrt-ticket-summary-toggle]')) return toggleSummary(shell, ctx);
var quote = event.target.closest('[data-aksrt-ticket-quote]');
if (event.target.closest('[data-jio-ticket-summary-toggle]')) return toggleSummary(shell, ctx);
var quote = event.target.closest('[data-jio-ticket-quote]');
if (quote) return setQuote(shell, ctx, quote);
if (event.target.closest('[data-aksrt-ticket-quote-cancel]')) return setQuote(shell, ctx, null);
var urge = event.target.closest('[data-aksrt-ticket-urge]');
if (event.target.closest('[data-jio-ticket-quote-cancel]')) return setQuote(shell, ctx, null);
var urge = event.target.closest('[data-jio-ticket-urge]');
if (urge) return handleUrge(urge, shell, ctx);
var close = event.target.closest('[data-aksrt-ticket-close]');
var close = event.target.closest('[data-jio-ticket-close]');
if (close) handleClose(close, shell, ctx);
}
function submitReply(shell, ctx) {
var form = shell.querySelector('[data-aksrt-ticket-reply-form]');
var button = form && form.querySelector('[data-aksrt-ticket-submit]');
var form = shell.querySelector('[data-jio-ticket-reply-form]');
var button = form && form.querySelector('[data-jio-ticket-submit]');
if (!form || !button || button.disabled) return;
var textarea = form.querySelector('textarea');
var content = (textarea.value || '').trim();
@@ -530,7 +530,7 @@
/** 刷新催单按钮;返回 true 表示仍在冷却、需要继续计时。 */
function syncUrge(shell, ctx) {
var button = shell.querySelector('[data-aksrt-ticket-urge]');
var button = shell.querySelector('[data-jio-ticket-urge]');
if (!button || button.classList.contains('is-busy')) return !!button;
var left = urgeLeft(ctx);
var label = button.querySelector('span');
@@ -616,7 +616,7 @@
}
function openCreate() {
var shell = openModal({ title: '提交工单', className: 'aksrt-ticket-modal aksrt-ticket-create-modal', kind: 'ticket-create' });
var shell = openModal({ title: '提交工单', className: 'jio-ticket-modal jio-ticket-create-modal', kind: 'ticket-create' });
if (!shell) return;
loadMeta().then(function (meta) {
if (!alive(shell)) return;
@@ -633,32 +633,32 @@
}
function field(label, control, extra) {
return '<div class="aksrt-ticket-field">' + label + control + (extra || '') + '</div>';
return '<div class="jio-ticket-field">' + label + control + (extra || '') + '</div>';
}
function buildCreateHtml(meta) {
var types = meta.types || [];
var orders = meta.orders || [];
var html = '<form class="aksrt-ticket-form" data-aksrt-ticket-create-form novalidate>';
var html = '<form class="jio-ticket-form" data-jio-ticket-create-form novalidate>';
html += field(
'<label for="aksrt-ticket-f-title">工单标题<em aria-hidden="true">*</em></label>',
'<input id="aksrt-ticket-f-title" type="text" name="title" maxlength="' + lengthMax('title') + '" autocomplete="off" placeholder="一句话概括问题,如:支付成功但会员未开通">',
'<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="title">0 / ' + lengthMax('title') + '</span>'
'<label for="jio-ticket-f-title">工单标题<em aria-hidden="true">*</em></label>',
'<input id="jio-ticket-f-title" type="text" name="title" maxlength="' + lengthMax('title') + '" autocomplete="off" placeholder="一句话概括问题,如:支付成功但会员未开通">',
'<span class="jio-ticket-counter" data-jio-ticket-counter="title">0 / ' + lengthMax('title') + '</span>'
);
html += '<fieldset class="aksrt-ticket-field aksrt-ticket-types" data-aksrt-ticket-types>'
+ '<legend>工单部门<em aria-hidden="true">*</em></legend><div class="aksrt-ticket-types__list">'
html += '<fieldset class="jio-ticket-field jio-ticket-types" data-jio-ticket-types>'
+ '<legend>工单部门<em aria-hidden="true">*</em></legend><div class="jio-ticket-types__list">'
+ types.map(function (type) {
return '<label class="aksrt-ticket-type"><input type="radio" name="ticket_type_id" value="' + escapeHtml(type.id) + '">'
return '<label class="jio-ticket-type"><input type="radio" name="ticket_type_id" value="' + escapeHtml(type.id) + '">'
+ '<span>' + escapeHtml(type.name) + '</span></label>';
}).join('')
+ '</div></fieldset>';
if (orders.length) {
html += field(
'<label for="aksrt-ticket-f-order">关联订单<span class="aksrt-ticket-field__optional">可选</span></label>',
'<select id="aksrt-ticket-f-order" name="order_number"><option value="">不关联订单</option>'
'<label for="jio-ticket-f-order">关联订单<span class="jio-ticket-field__optional">可选</span></label>',
'<select id="jio-ticket-f-order" name="order_number"><option value="">不关联订单</option>'
+ orders.map(function (order) {
return '<option value="' + escapeHtml(order.order_number) + '">'
+ escapeHtml(order.order_number + (order.product_name ? ' · ' + order.product_name : '')) + '</option>';
@@ -667,30 +667,30 @@
}
html += field(
'<label for="aksrt-ticket-f-content">问题描述<em aria-hidden="true">*</em></label>',
'<textarea id="aksrt-ticket-f-content" name="content" rows="5" maxlength="' + lengthMax('content') + '" placeholder="请描述问题现象、发生时间、操作步骤等,信息越完整处理越快"></textarea>',
'<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="content">0 / ' + lengthMax('content') + '</span>'
'<label for="jio-ticket-f-content">问题描述<em aria-hidden="true">*</em></label>',
'<textarea id="jio-ticket-f-content" name="content" rows="5" maxlength="' + lengthMax('content') + '" placeholder="请描述问题现象、发生时间、操作步骤等,信息越完整处理越快"></textarea>',
'<span class="jio-ticket-counter" data-jio-ticket-counter="content">0 / ' + lengthMax('content') + '</span>'
);
if (attachmentEnabled()) {
html += '<div class="aksrt-ticket-field"><span class="aksrt-ticket-field__label">图片附件<span class="aksrt-ticket-field__optional">可选</span></span>'
html += '<div class="jio-ticket-field"><span class="jio-ticket-field__label">图片附件<span class="jio-ticket-field__optional">可选</span></span>'
+ attachTrayHtml()
+ '<div class="aksrt-ticket-field__row">' + attachButtonHtml()
+ '<p class="aksrt-ticket-field__hint">支持 JPG / PNG / GIF / WebP,可直接粘贴截图,最多 ' + attachmentMax() + ' 张</p></div></div>';
+ '<div class="jio-ticket-field__row">' + attachButtonHtml()
+ '<p class="jio-ticket-field__hint">支持 JPG / PNG / GIF / WebP,可直接粘贴截图,最多 ' + attachmentMax() + ' 张</p></div></div>';
}
html += '<div class="aksrt-ticket-form__actions">'
+ '<button type="button" class="btn-outline" data-aksrt-modal-close>取消</button>'
+ '<button type="submit" class="btn-primary" data-aksrt-ticket-submit>提交工单</button></div>';
html += '<div class="jio-ticket-form__actions">'
+ '<button type="button" class="btn-outline" data-jio-modal-close>取消</button>'
+ '<button type="submit" class="btn-primary" data-jio-ticket-submit>提交工单</button></div>';
return html + '</form>';
}
function bindCreate(shell) {
var form = shell.querySelector('[data-aksrt-ticket-create-form]');
var form = shell.querySelector('[data-jio-ticket-create-form]');
if (!form) return;
bindShell(shell, function (event) {
var view = event.target.closest('[data-aksrt-ticket-view]');
if (view) openDetail(view.getAttribute('data-aksrt-ticket-view'));
var view = event.target.closest('[data-jio-ticket-view]');
if (view) openDetail(view.getAttribute('data-jio-ticket-view'));
});
form.addEventListener('submit', function (event) {
event.preventDefault();
@@ -699,7 +699,7 @@
}
function submitCreate(shell, form) {
var button = form.querySelector('[data-aksrt-ticket-submit]');
var button = form.querySelector('[data-jio-ticket-submit]');
if (!button || button.disabled) return;
var titleField = form.querySelector('[name="title"]');
var contentField = form.querySelector('[name="content"]');
@@ -709,7 +709,7 @@
var content = (contentField.value || '').trim();
if (!title) return markInvalid(titleField, '请填写工单标题');
if (!typeField) return markInvalid(form.querySelector('[data-aksrt-ticket-types]'), '请选择工单部门');
if (!typeField) return markInvalid(form.querySelector('[data-jio-ticket-types]'), '请选择工单部门');
if (!content) return markInvalid(contentField, '请填写问题描述');
if (isUploading(form)) return notyf('图片仍在上传,请稍候', 'danger');
@@ -739,18 +739,18 @@
// 成功页:承接后台「提交后显示平台公告」(notice_text 已经 wp_kses_post 过滤),并给出下一步。
function renderCreated(shell, id) {
var notice = (state.meta && state.meta.notice) || settings().notice || {};
var html = '<div class="aksrt-ticket-done" role="status">'
+ '<span class="aksrt-ticket-done__icon">' + icon('fa-check') + '</span>'
var html = '<div class="jio-ticket-done" role="status">'
+ '<span class="jio-ticket-done__icon">' + icon('fa-check') + '</span>'
+ '<h4>工单已提交</h4>'
+ '<p>我们会尽快处理,处理进度可随时在工单中心查看。</p>'
+ (notice.open && notice.text ? '<div class="aksrt-ticket-done__notice">' + notice.text + '</div>' : '')
+ '<div class="aksrt-ticket-form__actions">'
+ '<button type="button" class="btn-outline" data-aksrt-modal-close>完成</button>'
+ (id ? '<button type="button" class="btn-primary" data-aksrt-ticket-view="' + escapeHtml(id) + '">查看工单</button>' : '')
+ (notice.open && notice.text ? '<div class="jio-ticket-done__notice">' + notice.text + '</div>' : '')
+ '<div class="jio-ticket-form__actions">'
+ '<button type="button" class="btn-outline" data-jio-modal-close>完成</button>'
+ (id ? '<button type="button" class="btn-primary" data-jio-ticket-view="' + escapeHtml(id) + '">查看工单</button>' : '')
+ '</div></div>';
setModalContent(shell, html);
if (!id) return;
var view = shell.querySelector('[data-aksrt-ticket-view]');
var view = shell.querySelector('[data-jio-ticket-view]');
if (view) view.focus();
}
@@ -760,35 +760,35 @@
function initPanel() {
var root = panel();
if (!root || root.dataset.aksrtTicketReady === '1') return;
root.dataset.aksrtTicketReady = '1';
if (!root || root.dataset.jioTicketReady === '1') return;
root.dataset.jioTicketReady = '1';
root.addEventListener('click', function (event) {
var target = event.target;
var view = target.closest('[data-aksrt-ticket-view]');
if (view) return openDetail(view.getAttribute('data-aksrt-ticket-view'));
var view = target.closest('[data-jio-ticket-view]');
if (view) return openDetail(view.getAttribute('data-jio-ticket-view'));
var stat = target.closest('[data-aksrt-ticket-stat]');
var stat = target.closest('[data-jio-ticket-stat]');
if (stat) {
var key = stat.getAttribute('data-aksrt-ticket-stat') || '';
var key = stat.getAttribute('data-jio-ticket-stat') || '';
return applyFilters({ status: key === state.status ? '' : key });
}
var page = target.closest('[data-aksrt-ticket-page]');
var page = target.closest('[data-jio-ticket-page]');
if (page) {
var next = parseInt(page.getAttribute('data-aksrt-ticket-page'), 10) || 1;
var next = parseInt(page.getAttribute('data-jio-ticket-page'), 10) || 1;
if (page.disabled || next < 1 || next > state.totalPages || next === state.page) return;
state.page = next;
loadList();
return scrollPanelIntoView();
}
if (target.closest('[data-aksrt-ticket-reset]')) return resetFilters();
if (target.closest('[data-aksrt-ticket-retry]')) return refreshPanel();
if (target.closest('[data-aksrt-ticket-create]')) openCreate();
if (target.closest('[data-jio-ticket-reset]')) return resetFilters();
if (target.closest('[data-jio-ticket-retry]')) return refreshPanel();
if (target.closest('[data-jio-ticket-create]')) openCreate();
});
var search = root.querySelector('[data-aksrt-tickets-search]');
var search = root.querySelector('[data-jio-tickets-search]');
if (search) {
var input = search.querySelector('input');
var timer = 0;
@@ -812,9 +812,9 @@
});
}
root.querySelectorAll('[data-aksrt-ticket-filter]').forEach(function (select) {
root.querySelectorAll('[data-jio-ticket-filter]').forEach(function (select) {
select.addEventListener('change', function () {
var key = select.getAttribute('data-aksrt-ticket-filter');
var key = select.getAttribute('data-jio-ticket-filter');
applyFilters(key === 'status' ? { status: select.value } : { type: select.value });
});
});
@@ -826,7 +826,7 @@
initPanel();
// 面板是 AJAX 延迟加载的:user-center.js 每次切 Tab 都会触发 auto_fun。
if (window.jQuery) {
window.jQuery(document).off('auto_fun.aksrt-ticket').on('auto_fun.aksrt-ticket', initPanel);
window.jQuery(document).off('auto_fun.jio-ticket').on('auto_fun.jio-ticket', initPanel);
}
}