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:
1 parent
5b16bc6e15
commit
35bb96e82f
997 files changed
+28216
-28188
No files matched your search
+139
-139
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user