Files
kate522andClaude Opus 5 35bb96e82f 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>
2026-11-01 20:05:04 +08:00

408 lines
17 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 支付中心概览:区间筛选 + 指标卡 + ECharts 图表(成交趋势 / 分类成交 / 支付方式)+ 转化与退款 + 热销排行。
*
* 首屏报表来自页面内 #jio-pay-overview-data(PayOverview 输出),之后切换区间 / 分类请求
* GET jio/v1/admin/pay-overview(PayOverviewController)。ECharts 子集由
* assets/js/runtime/libs/echarts-pay.min.js 提供(window.echarts)。仅本页入队。
*/
(function () {
'use strict';
var root = document.querySelector('.jio-po-page');
var dataEl = document.getElementById('jio-pay-overview-data');
if (!root || !dataEl || !window.echarts) return;
var boot;
try { boot = JSON.parse(dataEl.textContent || '{}'); } catch (e) { return; }
var echarts = window.echarts;
var i18n = boot.i18n || {};
var currency = boot.currency || '';
var COLORS = { paid: '#18a058', net: '#2080f0', refund: '#d03050', count: '#f0a020' };
var PALETTE = ['#18a058', '#2080f0', '#f0a020', '#8a63d2', '#86909c', '#13c2c2', '#eb2f96'];
var TEXT = '#4e5969', MUTED = '#86909c', LINE = '#f0f1f4';
var state = { from: boot.initial.from, to: boot.initial.to, group: boot.initial.group || '', report: boot.initial.report };
var charts = {};
var reqSeq = 0;
/* ---------------- 格式化 ---------------- */
function money(v) {
return currency + Number(v || 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function int(v) { return Number(v || 0).toLocaleString(); }
function compact(v) {
var n = Math.abs(v);
if (n >= 1e8) return (v / 1e8).toFixed(1).replace(/\.0$/, '') + '亿';
if (n >= 1e4) return (v / 1e4).toFixed(1).replace(/\.0$/, '') + '万';
return String(Math.round(v * 100) / 100);
}
function pct(v) { return v === null || v === undefined ? '—' : Number(v).toFixed(1) + '%'; }
function shortLabel(label) { return label.length === 10 ? label.slice(5) : label; }
/** 环比:基期为 0 时,本期有数为「新增」,否则不可比。 */
function deltaInfo(cur, prev) {
cur = Number(cur || 0); prev = Number(prev || 0);
if (prev <= 0) return cur > 0 ? { cls: 'is-up', text: i18n.new } : { cls: 'is-flat', text: '—' };
var d = (cur - prev) / prev * 100;
var cls = d > 0.05 ? 'is-up' : (d < -0.05 ? 'is-down' : 'is-flat');
return { cls: cls, text: (d > 0.05 ? '↑' : (d < -0.05 ? '↓' : '')) + Math.abs(d).toFixed(1) + '%' };
}
/* ---------------- 日期 ---------------- */
function parse(s) { var p = s.split('-'); return new Date(Date.UTC(+p[0], +p[1] - 1, +p[2])); }
function fmt(d) { return d.toISOString().slice(0, 10); }
function shift(s, days) { var d = parse(s); d.setUTCDate(d.getUTCDate() + days); return fmt(d); }
function spanDays(a, b) { return Math.round((parse(b) - parse(a)) / 864e5) + 1; }
function presetRange(key) {
var today = boot.today;
if (key === 'this_month') return [today.slice(0, 8) + '01', today];
if (key === 'last_month') {
var first = parse(today.slice(0, 8) + '01');
var lastEnd = new Date(first.getTime() - 864e5);
return [fmt(lastEnd).slice(0, 8) + '01', fmt(lastEnd)];
}
var n = parseInt(key.slice(1), 10);
return [shift(today, -(n - 1)), today];
}
/* ---------------- 数据加载 ---------------- */
function setBusy(busy) {
root.classList.toggle('is-loading', busy);
var body = root.querySelector('[data-jio-po-body]');
if (body) body.setAttribute('aria-busy', busy ? 'true' : 'false');
}
function showError(msg) {
var box = root.querySelector('[data-jio-po-error]');
if (!box) return;
box.hidden = !msg;
box.querySelector('p').textContent = msg || '';
}
function load(from, to, group) {
if (spanDays(from, to) < 1 || spanDays(from, to) > boot.maxDays || to > boot.today) {
showError(i18n.badRange);
return;
}
var seq = ++reqSeq;
var url = boot.rest.url + (boot.rest.url.indexOf('?') === -1 ? '?' : '&')
+ 'from=' + encodeURIComponent(from) + '&to=' + encodeURIComponent(to) + '&group=' + encodeURIComponent(group);
setBusy(true);
fetch(url, { credentials: 'same-origin', headers: { 'X-WP-Nonce': boot.rest.nonce } })
.then(function (res) { return res.json().then(function (json) { return { ok: res.ok, json: json }; }); })
.then(function (r) {
if (seq !== reqSeq) return;
if (!r.ok || !r.json || !r.json.success) throw new Error((r.json && r.json.message) || i18n.loadError);
state = { from: from, to: to, group: group, report: r.json.data };
showError('');
renderAll();
})
.catch(function (err) { if (seq === reqSeq) showError(err && err.message ? err.message : i18n.loadError); })
.then(function () { if (seq === reqSeq) setBusy(false); });
}
/* ---------------- 指标卡 / 转化 / 排行 / 提示 ---------------- */
function renderKpis(rep) {
var t = rep.totals || {}, p = rep.previous || {};
var defs = {
paid_amount: [money, function () { return int(t.paid_count) + ' ' + i18n.unit; }],
net_amount: [money, function () { return i18n.refund + ' ' + money(t.refund_amount); }],
paid_count: [int, function () { return ''; }],
buyers: [int, function () { return ''; }],
avg_amount: [money, function () { return ''; }],
refund_amount: [money, function () { return int(t.refund_count) + ' ' + i18n.unit; }],
};
root.querySelectorAll('[data-kpi]').forEach(function (card) {
var key = card.getAttribute('data-kpi'), def = defs[key];
if (!def) return;
card.querySelector('[data-kpi-value]').textContent = def[0](t[key]);
card.querySelector('[data-kpi-meta]').textContent = def[1]();
var d = deltaInfo(t[key], p[key]);
var el = card.querySelector('[data-kpi-delta]');
// 退款上升是坏事:颜色反转。
var cls = key === 'refund_amount' ? (d.cls === 'is-up' ? 'is-down' : (d.cls === 'is-down' ? 'is-up' : d.cls)) : d.cls;
el.className = 'jio-po-delta ' + cls;
el.textContent = i18n.basis + ' ' + d.text;
});
var label = root.querySelector('[data-jio-po-range-label]');
if (label && rep.range) label.textContent = rep.range.from + ' ~ ' + rep.range.to + '(' + rep.range.days + ' 天)';
}
function renderFunnel(rep) {
var t = rep.totals || {}, p = rep.previous || {};
root.querySelectorAll('[data-jio-po-funnel] [data-metric]').forEach(function (li) {
var key = li.getAttribute('data-metric');
var isCount = key === 'created_count';
li.querySelector('[data-value]').textContent = isCount ? int(t[key]) : pct(t[key]);
var bar = li.querySelector('[data-bar]');
bar.style.width = isCount ? '100%' : Math.max(0, Math.min(100, Number(t[key] || 0))) + '%';
var deltaEl = li.querySelector('[data-delta]');
if (isCount) {
var d = deltaInfo(t[key], p[key]);
deltaEl.className = 'jio-po-delta ' + d.cls;
deltaEl.textContent = i18n.basis + ' ' + d.text;
} else if (p[key] !== null && p[key] !== undefined && t[key] !== null && t[key] !== undefined) {
var diff = Number(t[key]) - Number(p[key]);
var good = key === 'conversion' ? diff >= 0 : diff <= 0;
deltaEl.className = 'jio-po-delta ' + (Math.abs(diff) < 0.05 ? 'is-flat' : (good ? 'is-up' : 'is-down'));
deltaEl.textContent = i18n.basis + ' ' + (diff > 0 ? '+' : '') + diff.toFixed(1) + 'pt';
} else {
deltaEl.className = 'jio-po-delta is-flat';
deltaEl.textContent = '';
}
});
}
function renderTop(rep) {
var body = root.querySelector('[data-jio-po-top]');
if (!body) return;
body.textContent = '';
var rows = rep.top || [];
if (!rows.length) {
var tr = body.insertRow(), td = tr.insertCell();
td.colSpan = 5;
td.className = 'jio-po-top__empty';
td.textContent = i18n.noTop;
return;
}
var total = rows.reduce(function (s, r) { return s + Number(r.amount || 0); }, 0) || 1;
rows.forEach(function (r, i) {
var tr = body.insertRow();
var rank = tr.insertCell();
rank.className = 'column-rank';
rank.innerHTML = '<span class="jio-po-rank' + (i < 3 ? ' is-top' : '') + '"></span>';
rank.firstChild.textContent = String(i + 1);
var title = tr.insertCell();
if (r.url) {
var a = document.createElement('a');
a.href = r.url;
a.textContent = r.title;
title.appendChild(a);
} else {
title.textContent = r.title;
}
var count = tr.insertCell(); count.className = 'column-num'; count.textContent = int(r.count);
var amount = tr.insertCell(); amount.className = 'column-num'; amount.textContent = money(r.amount);
var share = tr.insertCell(); share.className = 'column-share';
var ratio = Number(r.amount || 0) / total * 100;
share.innerHTML = '<span class="jio-po-share"><i></i></span><span></span>';
share.querySelector('i').style.width = ratio.toFixed(1) + '%';
share.lastChild.textContent = ratio.toFixed(1) + '%';
});
}
function renderExcluded(rep) {
var box = root.querySelector('[data-jio-po-excluded]');
if (!box) return;
var list = rep.excluded || [];
box.hidden = !list.length;
box.textContent = list.length ? i18n.excluded + list.map(function (x) {
return x.currency + ' ' + Number(x.amount).toFixed(2) + '(' + x.count + ' ' + i18n.unit + ')';
}).join(';') : '';
}
/* ---------------- 图表 ---------------- */
function chart(key) {
var host = root.querySelector('[data-jio-po-chart="' + key + '"]');
if (!host) return null;
if (!charts[key]) charts[key] = echarts.init(host, null, { renderer: 'svg' });
return charts[key];
}
function baseAxis(labels) {
return {
type: 'category',
boundaryGap: false,
data: labels,
axisLine: { lineStyle: { color: '#e5e6eb' } },
axisTick: { show: false },
axisLabel: { color: MUTED, hideOverlap: true, formatter: shortLabel },
};
}
function valueAxis(formatter, show) {
return {
type: 'value',
show: show !== false,
splitLine: { lineStyle: { color: LINE } },
axisLabel: { color: MUTED, formatter: formatter },
minInterval: 1,
};
}
function tooltip(formatters) {
return {
trigger: 'axis',
confine: true,
axisPointer: { type: 'line', lineStyle: { color: '#c9cdd4', type: 'dashed' } },
backgroundColor: '#fff',
borderColor: '#e5e6eb',
textStyle: { color: TEXT, fontSize: 12 },
extraCssText: 'box-shadow:0 8px 24px rgba(15,23,42,.12);border-radius:8px;',
valueFormatter: formatters,
};
}
function area(color) {
return {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: color + '38' },
{ offset: 1, color: color + '00' },
]),
};
}
function zoom(n) {
return n > 45 ? [{ type: 'inside', start: 0, end: 100 }, { type: 'slider', height: 18, bottom: 6, borderColor: 'transparent', fillerColor: 'rgba(24,160,88,.12)', handleSize: '80%', showDetail: false }] : [];
}
function emptyGraphic(isEmpty) {
return isEmpty ? [{ type: 'text', left: 'center', top: 'middle', silent: true, style: { text: i18n.empty, fill: MUTED, fontSize: 13 } }] : [];
}
function renderTrend(rep) {
var c = chart('trend');
if (!c) return;
var s = rep.series || { labels: [], paid: [], net: [], refund: [], count: [] };
var n = s.labels.length;
var isEmpty = !s.paid.some(function (v) { return v > 0; });
var smoothLine = function (name, data, color, extra) {
return Object.assign({ name: name, type: 'line', data: data, smooth: 0.35, showSymbol: false, symbolSize: 6, color: color, lineStyle: { width: 2.25 }, emphasis: { focus: 'series' } }, extra || {});
};
c.setOption({
aria: { enabled: true },
color: [COLORS.paid, COLORS.net, COLORS.refund, COLORS.count],
grid: { left: 8, right: 8, top: 40, bottom: n > 45 ? 36 : 8, containLabel: true },
legend: { top: 0, left: 0, icon: 'roundRect', itemWidth: 12, itemHeight: 4, textStyle: { color: TEXT }, selected: { [i18n.refund]: true } },
tooltip: tooltip(function (v) { return typeof v === 'number' && !Number.isInteger(v) ? money(v) : v; }),
xAxis: baseAxis(s.labels),
yAxis: [valueAxis(compact), valueAxis(function (v) { return v; })],
dataZoom: zoom(n),
graphic: emptyGraphic(isEmpty),
series: [
smoothLine(i18n.paid, s.paid, COLORS.paid, { areaStyle: area(COLORS.paid), tooltip: { valueFormatter: money } }),
smoothLine(i18n.net, s.net, COLORS.net, { tooltip: { valueFormatter: money } }),
smoothLine(i18n.refund, s.refund, COLORS.refund, { lineStyle: { width: 1.5, type: 'dashed' }, tooltip: { valueFormatter: money } }),
{
name: i18n.count, type: 'line', yAxisIndex: 1, data: s.count, smooth: false,
showSymbol: n <= 31, symbol: 'circle', symbolSize: 5, color: COLORS.count,
lineStyle: { width: 1.5 }, itemStyle: { borderColor: '#fff', borderWidth: 1.5 },
tooltip: { valueFormatter: function (v) { return int(v) + ' ' + i18n.unit; } },
},
],
}, true);
}
function renderGroups(rep) {
var c = chart('groups');
if (!c) return;
var s = rep.series || { labels: [], groups: [] };
var groups = s.groups || [];
var n = s.labels.length;
c.setOption({
aria: { enabled: true },
color: PALETTE,
grid: { left: 8, right: 8, top: 40, bottom: n > 45 ? 36 : 8, containLabel: true },
legend: { top: 0, left: 0, icon: 'roundRect', itemWidth: 12, itemHeight: 4, textStyle: { color: TEXT } },
tooltip: tooltip(money),
xAxis: baseAxis(s.labels),
yAxis: valueAxis(compact),
dataZoom: zoom(n),
graphic: emptyGraphic(!groups.length),
series: groups.map(function (g) {
return { name: g.name, type: 'line', data: g.data, smooth: 0.35, showSymbol: false, lineStyle: { width: 2 }, emphasis: { focus: 'series' } };
}),
}, true);
}
function renderMethods(rep) {
var c = chart('methods');
if (!c) return;
var list = rep.methods || [];
var total = list.reduce(function (s, m) { return s + Number(m.amount || 0); }, 0);
c.setOption({
aria: { enabled: true },
color: PALETTE,
tooltip: {
trigger: 'item', confine: true, backgroundColor: '#fff', borderColor: '#e5e6eb', textStyle: { color: TEXT, fontSize: 12 },
formatter: function (p) { return p.marker + p.name + '<br>' + money(p.value) + '(' + p.percent + '%,' + int(p.data.count) + ' ' + i18n.unit + ')'; },
},
legend: { bottom: 0, left: 'center', icon: 'circle', itemWidth: 8, itemHeight: 8, textStyle: { color: TEXT } },
graphic: list.length ? [{
type: 'group', left: 'center', top: '36%', silent: true,
children: [
{ type: 'text', style: { text: i18n.total, fill: MUTED, fontSize: 12, align: 'center' }, left: 'center', top: 0 },
{ type: 'text', style: { text: money(total), fill: '#1f2329', fontSize: 16, fontWeight: 600, align: 'center' }, left: 'center', top: 18 },
],
}] : emptyGraphic(true),
series: [{
type: 'pie', radius: ['52%', '72%'], center: ['50%', '44%'], avoidLabelOverlap: true,
itemStyle: { borderColor: '#fff', borderWidth: 2, borderRadius: 4 },
label: { show: false }, labelLine: { show: false },
emphasis: { scale: true, scaleSize: 4 },
data: list.map(function (m) { return { name: m.name, value: m.amount, count: m.count }; }),
}],
}, true);
}
function renderAll() {
var rep = state.report || {};
renderKpis(rep);
renderFunnel(rep);
renderTop(rep);
renderExcluded(rep);
renderTrend(rep);
renderGroups(rep);
renderMethods(rep);
}
/* ---------------- 交互 ---------------- */
var presets = root.querySelector('[data-jio-po-presets]');
var form = root.querySelector('[data-jio-po-range]');
function syncForm() {
if (!form) return;
form.elements.from.value = state.from;
form.elements.to.value = state.to;
form.elements.group.value = state.group;
}
function pressPreset(btn) {
if (!presets) return;
presets.querySelectorAll('button').forEach(function (b) { b.setAttribute('aria-pressed', b === btn ? 'true' : 'false'); });
}
if (presets) {
presets.addEventListener('click', function (e) {
var btn = e.target.closest('button[data-preset]');
if (!btn) return;
var r = presetRange(btn.getAttribute('data-preset'));
pressPreset(btn);
state.from = r[0]; state.to = r[1];
syncForm();
load(r[0], r[1], form ? form.elements.group.value : state.group);
});
}
if (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
var from = form.elements.from.value, to = form.elements.to.value;
if (!from || !to || from > to) { showError(i18n.badRange); return; }
pressPreset(null);
load(from, to, form.elements.group.value);
});
form.elements.group.addEventListener('change', function () { load(state.from, state.to, form.elements.group.value); });
}
var resizeTimer;
window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () { Object.keys(charts).forEach(function (k) { charts[k].resize(); }); }, 120);
});
syncForm();
renderAll();
})();