ebe191b06d
- 会员改为永久激活方案;云端文件不再拦截非会员;预览弹窗内导出/打印才校验 - 首页最近文件支持本地记录,登录后与云端合并;移除独立会员页与订阅页 - 模板库入库管理:soon_templates 表、Admin 上传 CRUD、/templates 轻量列表与按需下载 Co-authored-by: Cursor <cursoragent@cursor.com>
260 lines
10 KiB
JavaScript
260 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; };
|
||
|
||
function activeBadge(v) {
|
||
var on = v === true || parseInt(v, 10) === 1;
|
||
return I().badgeStatus(on ? 'active' : 'disabled');
|
||
}
|
||
|
||
function recBadge(v) {
|
||
if (!v) return '—';
|
||
return '<span class="soon-badge soon-badge--paid">推荐</span>';
|
||
}
|
||
|
||
function isFreePlan(plan) {
|
||
return plan && plan.code === 'free';
|
||
}
|
||
|
||
function isPaidMemberPlan(plan) {
|
||
return plan && plan.code !== 'free' && plan.price_cents > 0;
|
||
}
|
||
|
||
function isLifetimePlan(plan) {
|
||
return plan && plan.code === 'member_lifetime';
|
||
}
|
||
|
||
function editableFeatures(plan) {
|
||
if (plan.features_editable && plan.features_editable.length) {
|
||
return plan.features_editable.slice();
|
||
}
|
||
var lines = plan.features || [];
|
||
return lines.filter(function (l) {
|
||
return l.indexOf('订阅有效期') < 0
|
||
&& l.indexOf('订阅周期') < 0
|
||
&& l.indexOf('到期') < 0;
|
||
});
|
||
}
|
||
|
||
function featureRowsHtml(lines) {
|
||
var items = lines && lines.length ? lines : [''];
|
||
return items.map(function (text) {
|
||
return '<div class="soon-admin-feature-row">' +
|
||
'<input type="text" class="soon-input soon-input--sm soon-admin-feature-input" value="' +
|
||
I().esc(text) + '" placeholder="例如:预览成品效果">' +
|
||
'<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost soon-admin-feature-del" title="删除">×</button></div>';
|
||
}).join('');
|
||
}
|
||
|
||
function bindFeatureEditor() {
|
||
var list = document.getElementById('peFeatures');
|
||
var addBtn = document.getElementById('peFeatureAdd');
|
||
if (!list) return;
|
||
|
||
function bindDel(btn) {
|
||
if (!btn || btn.dataset.bound) return;
|
||
btn.dataset.bound = '1';
|
||
btn.onclick = function () {
|
||
var rows = list.querySelectorAll('.soon-admin-feature-row');
|
||
if (rows.length <= 1) {
|
||
var inp = list.querySelector('.soon-admin-feature-input');
|
||
if (inp) inp.value = '';
|
||
return;
|
||
}
|
||
var row = btn.closest('.soon-admin-feature-row');
|
||
if (row) row.remove();
|
||
};
|
||
}
|
||
|
||
list.querySelectorAll('.soon-admin-feature-del').forEach(bindDel);
|
||
|
||
if (addBtn && !addBtn.dataset.bound) {
|
||
addBtn.dataset.bound = '1';
|
||
addBtn.onclick = function () {
|
||
var row = document.createElement('div');
|
||
row.className = 'soon-admin-feature-row';
|
||
row.innerHTML =
|
||
'<input type="text" class="soon-input soon-input--sm soon-admin-feature-input" placeholder="例如:预览成品效果">' +
|
||
'<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost soon-admin-feature-del" title="删除">×</button>';
|
||
list.appendChild(row);
|
||
bindDel(row.querySelector('.soon-admin-feature-del'));
|
||
row.querySelector('.soon-admin-feature-input').focus();
|
||
};
|
||
}
|
||
}
|
||
|
||
function collectFeatures() {
|
||
var lines = [];
|
||
document.querySelectorAll('#peFeatures .soon-admin-feature-input').forEach(function (inp) {
|
||
var t = inp.value.trim();
|
||
if (t) lines.push(t);
|
||
});
|
||
return lines;
|
||
}
|
||
|
||
function periodHint(plan) {
|
||
if (isFreePlan(plan)) return '';
|
||
var days = parseInt(Ui().el('peDays') && Ui().el('peDays').value, 10);
|
||
if (!days || days <= 0) days = plan.duration_days || 0;
|
||
if (isLifetimePlan(plan)) return '永久激活方案,duration_days 固定为 0。';
|
||
if (days <= 0) return '付费方案保存后将自动追加周期说明。';
|
||
var label = days >= 365 ? '1 年' : (days === 90 ? '1 季' : (days >= 30 && days % 30 === 0 ? (days / 30) + ' 个月' : days + ' 天'));
|
||
return '保存后将自动追加周期说明(' + label + '),用户端不展示该行';
|
||
}
|
||
|
||
function openEditModal(plan) {
|
||
var free = isFreePlan(plan);
|
||
var paid = isPaidMemberPlan(plan);
|
||
var features = editableFeatures(plan);
|
||
|
||
var basicPanel = Form().panel('基本信息',
|
||
Form().field('代码', '<input class="soon-input soon-input--sm" value="' + I().esc(plan.code) + '" disabled>') +
|
||
Form().field('名称', Form().input('peName', { value: plan.name })) +
|
||
Form().field('简介', Form().input('peDesc', { value: plan.description || '' }))
|
||
);
|
||
|
||
var lifetime = isLifetimePlan(plan);
|
||
var pricePanel = Form().panel('价格与周期',
|
||
'<div class="soon-admin-form-grid">' +
|
||
Form().field('价格(元)',
|
||
Form().input('pePriceYuan', {
|
||
type: 'number',
|
||
value: (plan.price_cents / 100).toFixed(2),
|
||
disabled: free,
|
||
})) +
|
||
Form().field('有效天数',
|
||
Form().input('peDays', { type: 'number', value: plan.duration_days, disabled: lifetime }),
|
||
free ? '免费版填 0' : (lifetime ? '永久激活固定为 0' : '月付 30 / 季付 90 / 年付 365')) +
|
||
Form().field('排序',
|
||
Form().input('peSort', { type: 'number', value: plan.sort_order })) +
|
||
'</div>' +
|
||
'<div class="soon-admin-form-checks">' +
|
||
'<label class="soon-admin-check"><input type="checkbox" id="peRec"' +
|
||
(plan.is_recommended ? ' checked' : '') + (free ? ' disabled' : '') + '> 设为推荐</label>' +
|
||
'<label class="soon-admin-check"><input type="checkbox" id="peActive"' +
|
||
(plan.is_active ? ' checked' : '') + '> 上架展示</label>' +
|
||
(paid ? '<label class="soon-admin-check"><input type="checkbox" id="peSync" checked> 应用到全部付费方案</label>' : '') +
|
||
'</div>'
|
||
);
|
||
|
||
var quotaPanel = Form().panel('云端配额',
|
||
'<div class="soon-admin-form-grid">' +
|
||
Form().field('存储(MB)', Form().input('peQuota', { type: 'number', value: plan.quota_mb })) +
|
||
Form().field('文件数上限', Form().input('peMaxFiles', { type: 'number', value: plan.max_files })) +
|
||
'</div>',
|
||
'云端存储与文件数量上限。'
|
||
);
|
||
|
||
var benefitTitle = free ? '免费版展示' : '会员权益';
|
||
var benefitHint = free
|
||
? '展示在用户端身份说明中。'
|
||
: periodHint(plan);
|
||
var benefitPanel = Form().panel(benefitTitle,
|
||
'<div class="soon-admin-feature-list" id="peFeatures">' + featureRowsHtml(features) + '</div>' +
|
||
'<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost" id="peFeatureAdd">+ 添加权益</button>' +
|
||
(benefitHint ? '<p class="soon-admin-form-hint soon-admin-form-hint--tight">' + I().esc(benefitHint) + '</p>' : '')
|
||
);
|
||
|
||
var form = '<div class="soon-admin-form soon-admin-form--plan">' +
|
||
basicPanel + pricePanel + quotaPanel + benefitPanel +
|
||
Form().actions('保存', 'adminModalSave') +
|
||
'</div>';
|
||
|
||
Ui().openFormModal((free ? '免费版' : '会员激活') + ' · ' + plan.name, form, function () {
|
||
var name = Ui().el('peName').value.trim();
|
||
if (!name) {
|
||
soonToast('名称不能为空', 'warn');
|
||
return false;
|
||
}
|
||
var featureLines = collectFeatures();
|
||
if (!featureLines.length) {
|
||
soonToast('请至少填写一条权益', 'warn');
|
||
return false;
|
||
}
|
||
var yuan = free ? 0 : (parseFloat(Ui().el('pePriceYuan').value) || 0);
|
||
var body = {
|
||
code: plan.code,
|
||
name: name,
|
||
description: Ui().el('peDesc').value.trim(),
|
||
price_cents: Math.round(yuan * 100),
|
||
quota_mb: parseInt(Ui().el('peQuota').value, 10) || 0,
|
||
max_files: parseInt(Ui().el('peMaxFiles').value, 10) || 0,
|
||
duration_days: lifetime ? 0 : (parseInt(Ui().el('peDays').value, 10) || 0),
|
||
sort_order: parseInt(Ui().el('peSort').value, 10) || 0,
|
||
is_recommended: free ? 0 : (Ui().el('peRec').checked ? 1 : 0),
|
||
is_active: Ui().el('peActive').checked ? 1 : 0,
|
||
features: featureLines,
|
||
sync_member_benefits: paid && Ui().el('peSync') && Ui().el('peSync').checked ? 1 : 0,
|
||
};
|
||
return Api().postJson('plans', body).then(function (r) {
|
||
if (r) {
|
||
layer.closeAll();
|
||
soonToast('已保存', 'success');
|
||
render();
|
||
}
|
||
});
|
||
}, {
|
||
area: ['640px', '72vh'],
|
||
success: function () {
|
||
bindFeatureEditor();
|
||
var daysEl = Ui().el('peDays');
|
||
var hintEl = document.querySelector('.soon-admin-form-hint--tight');
|
||
if (daysEl && hintEl && paid) {
|
||
daysEl.oninput = function () {
|
||
hintEl.textContent = periodHint(plan);
|
||
};
|
||
}
|
||
},
|
||
});
|
||
}
|
||
|
||
function featureSummary(plan) {
|
||
var n = editableFeatures(plan).length;
|
||
return n > 0 ? n + ' 条' : '—';
|
||
}
|
||
|
||
function render() {
|
||
var panel = Ui().el('panel-plans');
|
||
if (!panel) return;
|
||
panel.innerHTML = '<div class="soon-admin-page">' +
|
||
Ui().pageHeader('会员激活', '配置激活价格、配额与用户端展示文案') +
|
||
Ui().skeletonTable() + '</div>';
|
||
|
||
Api().get('plans').then(function (j) {
|
||
if (!j) return;
|
||
var items = j.data.items || [];
|
||
var rows = items.map(function (p) {
|
||
return '<tr data-code="' + I().esc(p.code) + '"><td>' + I().esc(p.code) + '</td><td>' + I().esc(p.name) +
|
||
'</td><td>' + I().formatCents(p.price_cents) + '</td><td>' + featureSummary(p) + '</td><td>' +
|
||
p.quota_mb + ' MB</td><td>' + (p.duration_days > 0 ? p.duration_days + ' 天' : '永久') + '</td><td>' +
|
||
recBadge(p.is_recommended) + '</td><td>' + activeBadge(p.is_active) +
|
||
'</td><td><button type="button" class="soon-btn soon-btn--sm soon-btn--ghost plan-edit">编辑</button></td></tr>';
|
||
}).join('');
|
||
|
||
panel.innerHTML = '<div class="soon-admin-page">' +
|
||
Ui().pageHeader('会员激活', '配置激活价格、配额与用户端展示文案') +
|
||
Ui().dataTable(
|
||
['代码', '名称', '价格', '权益', '存储', '有效期', '推荐', '状态', '操作'],
|
||
rows
|
||
) + '</div>';
|
||
|
||
var planMap = {};
|
||
items.forEach(function (p) { planMap[p.code] = p; });
|
||
|
||
panel.querySelectorAll('.plan-edit').forEach(function (btn) {
|
||
btn.onclick = function () {
|
||
var code = btn.closest('tr').dataset.code;
|
||
if (planMap[code]) openEditModal(planMap[code]);
|
||
};
|
||
});
|
||
});
|
||
}
|
||
|
||
window.AdminViews = window.AdminViews || {};
|
||
window.AdminViews.plans = { render: render };
|
||
})();
|