Files
SoonDesign/frontend-web/pages/admin/assets/js/views/plans.js
T
24kycj ebe191b06d 永久会员与模板库后台化:预览门控、轻量首页与 Admin CRUD
- 会员改为永久激活方案;云端文件不再拦截非会员;预览弹窗内导出/打印才校验
- 首页最近文件支持本地记录,登录后与云端合并;移除独立会员页与订阅页
- 模板库入库管理:soon_templates 表、Admin 上传 CRUD、/templates 轻量列表与按需下载

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 23:52:56 +08:00

260 lines
10 KiB
JavaScript
Raw 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.
(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 };
})();