88c6ce8ccc
- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构 - 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐 - 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作 - 新增管理后台、支付与云端文件 API,更新 README 与项目规范 Co-authored-by: Cursor <cursoragent@cursor.com>
291 lines
6.0 KiB
JavaScript
291 lines
6.0 KiB
JavaScript
(function () {
|
||
|
||
'use strict';
|
||
|
||
|
||
|
||
var Api = function () { return window.AdminApi; };
|
||
|
||
var Ui = function () { return window.AdminUi; };
|
||
|
||
var I = function () { return window.AdminI18n; };
|
||
|
||
var Form = function () { return window.AdminForm; };
|
||
|
||
var Tabs = function () { return window.AdminTabs; };
|
||
|
||
|
||
|
||
var CHANNEL_CONFIG = {
|
||
|
||
alipay: {
|
||
|
||
label: '支付宝',
|
||
|
||
kinds: [
|
||
|
||
{ kind: 'private_key', file: 'alipay_private_key.pem', label: '应用私钥 private_key' },
|
||
|
||
{ kind: 'public_key', file: 'alipay_public_key.pem', label: '支付宝公钥 public_key' },
|
||
|
||
],
|
||
|
||
},
|
||
|
||
wechat: {
|
||
|
||
label: '微信支付',
|
||
|
||
kinds: [
|
||
|
||
{ kind: 'mch_private_key', file: 'wechat_mch_private_key.pem', label: '商户私钥 mch_private_key' },
|
||
|
||
{ kind: 'api_v3_key', file: 'wechat_api_v3_key.pem', label: 'APIv3 密钥 api_v3_key' },
|
||
|
||
{ kind: 'platform_cert', file: 'wechat_platform_cert.pem', label: '平台证书 platform_cert' },
|
||
|
||
],
|
||
|
||
},
|
||
|
||
};
|
||
|
||
|
||
|
||
function fileSet(items) {
|
||
|
||
var set = {};
|
||
|
||
(items || []).forEach(function (p) { set[p.name] = p; });
|
||
|
||
return set;
|
||
|
||
}
|
||
|
||
|
||
|
||
function channelFormHtml(channel, files) {
|
||
|
||
var cfg = CHANNEL_CONFIG[channel];
|
||
|
||
var rows = cfg.kinds.map(function (k) {
|
||
|
||
var ok = !!files[k.file];
|
||
|
||
var statusCls = ok ? 'soon-admin-key-form-row__status--ok' : 'soon-admin-key-form-row__status--miss';
|
||
|
||
var statusText = ok ? '已就绪' : '缺失';
|
||
|
||
var id = channel + '_' + k.kind;
|
||
|
||
return '<div class="soon-admin-key-form-row">' +
|
||
|
||
'<div><div class="soon-admin-key-form-row__label">' + I().esc(k.label) + '</div>' +
|
||
|
||
'<div class="soon-admin-key-form-row__status ' + statusCls + '">' + statusText + '</div></div>' +
|
||
|
||
'<textarea id="' + id + '" class="soon-input soon-input--sm soon-input--textarea" rows="4" ' +
|
||
|
||
'placeholder="粘贴 PEM 内容或拖入 .pem 文件"></textarea>' +
|
||
|
||
'<button type="button" class="soon-btn soon-btn--sm pay-upload-btn" data-channel="' + channel +
|
||
|
||
'" data-kind="' + k.kind + '" data-target="' + id + '">上传</button></div>';
|
||
|
||
}).join('');
|
||
|
||
return '<div class="soon-admin-form">' + Form().panel(cfg.label + ' 密钥', rows) +
|
||
|
||
'<details class="soon-admin-hint"><summary>回调说明</summary>' +
|
||
|
||
'<p>notify:<code>/api/v1/pay/' + channel + '/notify</code>(须配置 site.base_url 为 HTTPS 外网地址)</p></details>' +
|
||
|
||
'<div class="soon-admin-upload" id="payDrop_' + channel + '">拖拽 .pem 到此处自动填入对应字段</div></div>';
|
||
|
||
}
|
||
|
||
|
||
|
||
function filesTabHtml(items) {
|
||
|
||
var rows = (items || []).map(function (p) {
|
||
|
||
return '<tr><td>' + I().esc(p.name) + '</td><td>' + p.bytes + ' B</td><td>' +
|
||
|
||
I().formatDateTime(p.mtime) + '</td></tr>';
|
||
|
||
}).join('');
|
||
|
||
return Ui().dataTable(['文件名', '大小', '修改时间'], rows);
|
||
|
||
}
|
||
|
||
|
||
|
||
function uploadPem(channel, kind, content, onDone) {
|
||
|
||
return Api().fetch('payment/upload', {
|
||
|
||
method: 'POST',
|
||
|
||
body: JSON.stringify({ channel: channel, kind: kind, content: content }),
|
||
|
||
}).then(function (j) {
|
||
|
||
if (j) {
|
||
|
||
soonToast('密钥已上传', 'success');
|
||
|
||
if (onDone) onDone();
|
||
|
||
}
|
||
|
||
return j;
|
||
|
||
});
|
||
|
||
}
|
||
|
||
|
||
|
||
function bindChannel(channel, panel) {
|
||
|
||
panel.querySelectorAll('.pay-upload-btn').forEach(function (btn) {
|
||
|
||
btn.onclick = function () {
|
||
|
||
var target = Ui().el(btn.dataset.target);
|
||
|
||
var content = target ? target.value.trim() : '';
|
||
|
||
if (!content) {
|
||
|
||
soonToast('请先粘贴或拖入 PEM 内容', 'warn');
|
||
|
||
return;
|
||
|
||
}
|
||
|
||
Ui().runFormSubmit(btn, function () {
|
||
return uploadPem(btn.dataset.channel, btn.dataset.kind, content, render);
|
||
});
|
||
|
||
};
|
||
|
||
});
|
||
|
||
|
||
|
||
var drop = Ui().el('payDrop_' + channel);
|
||
|
||
if (!drop) return;
|
||
|
||
drop.ondragover = function (e) { e.preventDefault(); drop.classList.add('is-dragover'); };
|
||
|
||
drop.ondragleave = function () { drop.classList.remove('is-dragover'); };
|
||
|
||
drop.ondrop = function (e) {
|
||
|
||
e.preventDefault();
|
||
|
||
drop.classList.remove('is-dragover');
|
||
|
||
var file = e.dataTransfer.files[0];
|
||
|
||
if (!file) return;
|
||
|
||
file.text().then(function (text) {
|
||
|
||
var name = file.name.toLowerCase();
|
||
|
||
var cfg = CHANNEL_CONFIG[channel];
|
||
|
||
var matched = cfg.kinds.find(function (k) { return name.indexOf(k.kind.replace(/_/g, '')) >= 0 || name === k.file; });
|
||
|
||
var targetId = matched ? channel + '_' + matched.kind : channel + '_' + cfg.kinds[0].kind;
|
||
|
||
var ta = Ui().el(targetId);
|
||
|
||
if (ta) ta.value = text;
|
||
|
||
soonToast('已填入 ' + (matched ? matched.label : '首个字段') + ',请确认后点击上传', 'success');
|
||
|
||
});
|
||
|
||
};
|
||
|
||
}
|
||
|
||
|
||
|
||
function render() {
|
||
|
||
var panel = Ui().el('panel-payment');
|
||
|
||
if (!panel) return;
|
||
|
||
panel.innerHTML = '<div class="soon-admin-page">' + Ui().pageHeader('支付配置', '按渠道管理密钥文件,上传后立即热加载') +
|
||
|
||
Ui().skeletonTable() + '</div>';
|
||
|
||
|
||
|
||
Api().get('payment/status').then(function (j) {
|
||
|
||
if (!j) return;
|
||
|
||
var items = j.data.items || [];
|
||
|
||
var files = fileSet(items);
|
||
|
||
|
||
|
||
var tabsHtml = Tabs().html('paymentTabs', [
|
||
|
||
{ id: 'alipay', label: '支付宝' },
|
||
|
||
{ id: 'wechat', label: '微信支付' },
|
||
|
||
{ id: 'files', label: '文件清单' },
|
||
|
||
], {
|
||
|
||
alipay: channelFormHtml('alipay', files),
|
||
|
||
wechat: channelFormHtml('wechat', files),
|
||
|
||
files: filesTabHtml(items),
|
||
|
||
}, 'alipay');
|
||
|
||
|
||
|
||
panel.innerHTML = '<div class="soon-admin-page">' +
|
||
|
||
Ui().pageHeader('支付配置', '按渠道管理密钥文件,上传后立即热加载') +
|
||
|
||
tabsHtml + '</div>';
|
||
|
||
|
||
|
||
var tabsRoot = Ui().el('paymentTabs');
|
||
|
||
Tabs().bind(tabsRoot);
|
||
|
||
bindChannel('alipay', panel);
|
||
|
||
bindChannel('wechat', panel);
|
||
|
||
});
|
||
|
||
}
|
||
|
||
|
||
|
||
window.AdminViews = window.AdminViews || {};
|
||
|
||
window.AdminViews.payment = { render: render };
|
||
|
||
})();
|
||
|