Files
24kycj 152228d41f 前端配置迁至 config/local.js,完善支付、模板库与部署脚本
- 页面直接引用 local.js 设置 SOON_DEPLOY_CONFIG,移除 deploy-config
- Docker sync-config 生成 local.js;更新 README 与 agent-core 说明
- 模板库自动扫描 .soon;新增后端部署/种子/排查脚本
- 完善支付配置、订阅弹窗与后台支付管理页

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 20:48:51 +08:00

273 lines
10 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 PARAM_KEYS = [
'site.base_url',
'site.front_base_url',
'alipay.app_id',
'wechat.app_id',
'wechat.mch_id',
'wechat.mch_serial_no',
];
var KEY_FIELDS = {
alipay: [
{ kind: 'private_key', file: 'alipay_private_key.pem', label: '应用私钥' },
{ kind: 'public_key', file: 'alipay_public_key.pem', label: '支付宝公钥' },
],
wechat: [
{ kind: 'mch_private_key', file: 'wechat_mch_private_key.pem', label: '商户私钥' },
{ kind: 'api_v3_key', file: 'wechat_api_v3_key.pem', label: 'APIv3 密钥', text: true },
{ kind: 'platform_cert', file: 'wechat_platform_cert.pem', label: '平台证书' },
],
};
function cfgId(key) {
return 'pay_' + key.replace(/\./g, '_');
}
function fileSet(items) {
var set = {};
(items || []).forEach(function (p) { set[p.name] = p; });
return set;
}
function deployDefaults() {
var cfg = window.SOON_DEPLOY_CONFIG || {};
return {
apiBase: String(cfg.api_v1_base || '').replace(/\/api\/v1\/?$/, ''),
frontBase: cfg.front_base_url || cfg.web_base || '',
};
}
function parseDisplayChannels(values) {
var raw = values && values['payment.display_channels'] != null
? values['payment.display_channels'] : 'alipay';
return String(raw).split(/[\s,]+/).filter(function (v) { return v === 'alipay' || v === 'wechat'; });
}
function statusLine(readiness) {
readiness = readiness || {};
var a = readiness.alipay || {};
var w = readiness.wechat || {};
return '<div class="soon-admin-pay-status">' +
'<span class="soon-admin-pay-status__item' + (a.ready ? ' is-ok' : ' is-miss') + '">支付宝</span>' +
'<span class="soon-admin-pay-status__item' + (w.ready ? ' is-ok' : ' is-miss') + '">微信支付</span></div>';
}
function field(label, control, wide) {
return '<div class="soon-admin-pay-field' + (wide ? ' soon-admin-pay-field--wide' : '') + '">' +
'<label class="soon-admin-pay-field__label">' + I().esc(label) + '</label>' +
'<div class="soon-admin-pay-field__control">' + control + '</div></div>';
}
function grid(cells) {
return '<div class="soon-admin-pay-grid">' + cells.join('') + '</div>';
}
function textInput(key, values, placeholder) {
var val = values && values[key] != null ? values[key] : '';
return Form().input(cfgId(key), { value: val, placeholder: placeholder || '' });
}
function readonlyInput(value) {
return '<input class="soon-input soon-input--sm soon-input--readonly" readonly value="' +
I().esc(value || '') + '">';
}
function displayChannelsField(selected) {
if (!selected.length) selected = ['alipay'];
var mk = function (ch, label) {
var on = selected.indexOf(ch) >= 0;
return '<label class="soon-admin-check"><input type="checkbox" class="pay-disp-ch" value="' + ch + '"' +
(on ? ' checked' : '') + '> ' + label + '</label>';
};
return field('用户端展示',
'<div class="soon-admin-pay-checks">' + mk('alipay', '支付宝') + mk('wechat', '微信') + '</div>', true);
}
function siteGrid(values, notifyUrls) {
var defs = deployDefaults();
return grid([
displayChannelsField(parseDisplayChannels(values)),
field('API 外网', textInput('site.base_url', values, defs.apiBase)),
field('前端站点', textInput('site.front_base_url', values, defs.frontBase)),
field('支付宝回调', readonlyInput(notifyUrls.alipay)),
field('微信回调', readonlyInput(notifyUrls.wechat)),
]);
}
function keyField(channel, keyDef, files) {
var ok = !!files[keyDef.file];
var id = channel + '_' + keyDef.kind;
var pasteId = id + '_paste';
var badge = '<span class="soon-admin-pay-key__badge ' + (ok ? 'is-ok' : 'is-miss') + '">' +
(ok ? '已配置' : '未配置') + '</span>';
var inner;
if (keyDef.text) {
inner = badge +
Form().input(id + '_text', { placeholder: 'APIv3 密钥', className: 'soon-admin-pay-key__text' }) +
'<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost pay-key-upload" data-channel="' +
channel + '" data-kind="' + keyDef.kind + '" data-source="' + id + '_text">写入</button>';
} else {
inner = badge +
'<label class="soon-btn soon-btn--xs soon-btn--ghost soon-admin-pay-key__file">上传' +
'<input type="file" class="pay-key-file" accept=".pem,.txt,.key" data-channel="' +
channel + '" data-kind="' + keyDef.kind + '" hidden></label>' +
'<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost pay-key-paste" data-target="' +
pasteId + '">粘贴</button>' +
'<div id="' + pasteId + '_box" class="soon-admin-pay-key__paste">' +
'<textarea id="' + pasteId + '" class="soon-input soon-input--sm" rows="2" placeholder="PEM"></textarea>' +
'<button type="button" class="soon-btn soon-btn--xs pay-key-commit" data-channel="' + channel +
'" data-kind="' + keyDef.kind + '" data-target="' + pasteId + '">确认</button></div>';
}
return field(keyDef.label, '<div class="soon-admin-pay-key">' + inner + '</div>');
}
function channelGrid(channel, files, values) {
var cells = [];
if (channel === 'alipay') {
cells.push(field('APPID', textInput('alipay.app_id', values, '开放平台 APPID')));
KEY_FIELDS.alipay.forEach(function (f) { cells.push(keyField('alipay', f, files)); });
} else {
cells.push(field('AppID', textInput('wechat.app_id', values, 'AppID')));
cells.push(field('商户号', textInput('wechat.mch_id', values, 'mch_id')));
cells.push(field('证书序列号', textInput('wechat.mch_serial_no', values, 'serial_no')));
KEY_FIELDS.wechat.forEach(function (f) { cells.push(keyField('wechat', f, files)); });
}
return grid(cells);
}
function formHtml(values, files, notifyUrls, readiness) {
var tabs = Tabs().html('paymentTabs', [
{ id: 'alipay', label: '支付宝' },
{ id: 'wechat', label: '微信' },
], {
alipay: channelGrid('alipay', files, values),
wechat: channelGrid('wechat', files, values),
}, 'alipay');
return '<div class="soon-admin-form soon-admin-form--pay">' +
'<div class="soon-admin-form-panel soon-admin-form-panel--pay">' +
statusLine(readiness) + siteGrid(values, notifyUrls) + tabs +
'</div>' + Form().actions('保存', 'payFormSave') + '</div>';
}
function collectParams() {
var items = PARAM_KEYS.map(function (key) {
var el = Ui().el(cfgId(key));
return { key: key, value: el ? el.value.trim() : '' };
});
var disp = [];
document.querySelectorAll('.pay-disp-ch:checked').forEach(function (el) { disp.push(el.value); });
items.push({ key: 'payment.display_channels', value: disp.join(',') });
return items;
}
function uploadKey(channel, kind, content) {
return Api().fetch('payment/upload', {
method: 'POST',
body: JSON.stringify({ channel: channel, kind: kind, content: content }),
});
}
function bindKeys(panel) {
panel.querySelectorAll('.pay-key-file').forEach(function (input) {
input.onchange = function () {
var file = input.files && input.files[0];
if (!file) return;
file.text().then(function (text) {
var content = text.trim();
if (!content) {
soonToast('文件为空', 'warn');
return;
}
uploadKey(input.dataset.channel, input.dataset.kind, content).then(function (j) {
if (j) {
soonToast('已保存', 'success');
render();
}
});
}).finally(function () { input.value = ''; });
};
});
panel.querySelectorAll('.pay-key-paste').forEach(function (btn) {
btn.onclick = function () {
var box = Ui().el(btn.dataset.target + '_box');
if (!box) return;
var open = box.classList.toggle('is-open');
var wrap = box.closest('.soon-admin-pay-field');
if (wrap) wrap.classList.toggle('soon-admin-pay-field--wide', open);
if (open) {
var ta = Ui().el(btn.dataset.target);
if (ta) ta.focus();
}
};
});
panel.querySelectorAll('.pay-key-upload, .pay-key-commit').forEach(function (btn) {
btn.onclick = function () {
var content = '';
if (btn.classList.contains('pay-key-upload')) {
var src = Ui().el(btn.dataset.source);
content = src ? src.value.trim() : '';
} else {
var ta = Ui().el(btn.dataset.target);
content = ta ? ta.value.trim() : '';
}
if (!content) {
soonToast('内容为空', 'warn');
return;
}
Ui().runFormSubmit(btn, function () {
return uploadKey(btn.dataset.channel, btn.dataset.kind, content).then(function (j) {
if (j) {
soonToast('已保存', 'success');
render();
}
});
});
};
});
}
function bindSave() {
Ui().bindFormSubmit('payFormSave', function () {
return Api().postJson('payment/config', { items: collectParams() }).then(function (j) {
if (j) {
soonToast('已保存', 'success');
render();
}
});
});
}
function render() {
var panel = Ui().el('panel-payment');
if (!panel) return;
var header = Ui().pageHeader('支付配置');
panel.innerHTML = '<div class="soon-admin-page">' + header + Ui().skeletonTable() + '</div>';
Api().get('payment/status').then(function (j) {
if (!j) return;
var data = j.data || {};
panel.innerHTML = '<div class="soon-admin-page">' + header +
formHtml(data.params || {}, fileSet(data.items), data.notify_urls || {}, data.readiness) +
'</div>';
Tabs().bind(Ui().el('paymentTabs'));
bindSave();
bindKeys(panel);
});
}
window.AdminViews = window.AdminViews || {};
window.AdminViews.payment = { render: render };
})();