ebe191b06d
- 会员改为永久激活方案;云端文件不再拦截非会员;预览弹窗内导出/打印才校验 - 首页最近文件支持本地记录,登录后与云端合并;移除独立会员页与订阅页 - 模板库入库管理:soon_templates 表、Admin 上传 CRUD、/templates 轻量列表与按需下载 Co-authored-by: Cursor <cursoragent@cursor.com>
197 lines
7.3 KiB
JavaScript
197 lines
7.3 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 typeLabel(type) {
|
|
return Number(type) === 2 ? '双面' : '单面';
|
|
}
|
|
|
|
function formatBytes(n) {
|
|
n = parseInt(n, 10) || 0;
|
|
if (!n) return '—';
|
|
if (n < 1024) return n + ' B';
|
|
if (n < 1048576) return (n / 1024).toFixed(1) + ' KB';
|
|
return (n / 1048576).toFixed(1) + ' MB';
|
|
}
|
|
|
|
function readFileText(input) {
|
|
return new Promise(function (resolve, reject) {
|
|
var file = input && input.files && input.files[0];
|
|
if (!file) {
|
|
reject(new Error('no_file'));
|
|
return;
|
|
}
|
|
var reader = new FileReader();
|
|
reader.onload = function () { resolve(String(reader.result || '')); };
|
|
reader.onerror = function () { reject(new Error('read_failed')); };
|
|
reader.readAsText(file, 'UTF-8');
|
|
});
|
|
}
|
|
|
|
function defaultNameFromFile(input) {
|
|
var file = input && input.files && input.files[0];
|
|
if (!file) return '';
|
|
return file.name.replace(/\.soon$/i, '').replace(/[_-]+/g, ' ').trim();
|
|
}
|
|
|
|
function uploadFields(required) {
|
|
return Form().panel('模板文件',
|
|
Form().field('选择 .soon 文件',
|
|
'<input type="file" id="tplFile" class="soon-input soon-input--sm" accept=".soon,application/json">' +
|
|
(required ? '' : '<p class="soon-admin-form-hint soon-admin-form-hint--tight">留空则不替换已有文件</p>'),
|
|
required ? '须为有效的 .soon JSON 文件' : '')
|
|
);
|
|
}
|
|
|
|
function metaFields(plan) {
|
|
plan = plan || {};
|
|
return Form().panel('展示设置',
|
|
Form().field('名称', Form().input('tplName', { value: plan.name || '' })) +
|
|
'<div class="soon-admin-form-grid">' +
|
|
Form().field('排序', Form().input('tplSort', { type: 'number', value: plan.sort_order != null ? plan.sort_order : 0 })) +
|
|
Form().field('类型', '<input class="soon-input soon-input--sm" value="' + I().esc(typeLabel(plan.type)) + '" disabled>') +
|
|
'</div>' +
|
|
'<label class="soon-admin-check"><input type="checkbox" id="tplActive"' +
|
|
(plan.is_active !== false ? ' checked' : '') + '> 上架展示</label>'
|
|
);
|
|
}
|
|
|
|
function openCreateModal() {
|
|
var form = '<div class="soon-admin-form">' + uploadFields(true) + metaFields() +
|
|
Form().actions('上传', 'adminModalSave') + '</div>';
|
|
|
|
Ui().openFormModal('上传模板', form, function () {
|
|
var nameEl = Ui().el('tplName');
|
|
var fileEl = Ui().el('tplFile');
|
|
var name = nameEl.value.trim() || defaultNameFromFile(fileEl);
|
|
if (!name) {
|
|
soonToast('请填写名称或选择文件', 'warn');
|
|
return false;
|
|
}
|
|
return readFileText(fileEl).then(function (content) {
|
|
return Api().postJson('templates', {
|
|
name: name,
|
|
content: content,
|
|
sort_order: parseInt(Ui().el('tplSort').value, 10) || 0,
|
|
is_active: Ui().el('tplActive').checked ? 1 : 0,
|
|
}).then(function (r) {
|
|
if (r) {
|
|
layer.closeAll();
|
|
soonToast('已上传', 'success');
|
|
render();
|
|
}
|
|
});
|
|
}).catch(function (e) {
|
|
if (e && e.message === 'no_file') soonToast('请选择 .soon 文件', 'warn');
|
|
else soonToast('文件读取失败', 'error');
|
|
});
|
|
}, { area: ['480px', 'auto'] });
|
|
}
|
|
|
|
function openEditModal(plan) {
|
|
var form = '<div class="soon-admin-form">' + metaFields(plan) + uploadFields(false) +
|
|
Form().actions('保存', 'adminModalSave') + '</div>';
|
|
|
|
Ui().openFormModal('编辑模板 · ' + plan.name, form, function () {
|
|
var name = Ui().el('tplName').value.trim();
|
|
if (!name) {
|
|
soonToast('名称不能为空', 'warn');
|
|
return false;
|
|
}
|
|
var body = {
|
|
name: name,
|
|
sort_order: parseInt(Ui().el('tplSort').value, 10) || 0,
|
|
is_active: Ui().el('tplActive').checked ? 1 : 0,
|
|
};
|
|
var fileEl = Ui().el('tplFile');
|
|
var hasFile = fileEl && fileEl.files && fileEl.files[0];
|
|
var save = function (content) {
|
|
if (content) body.content = content;
|
|
return Api().putJson('templates/' + plan.id, body).then(function (r) {
|
|
if (r) {
|
|
layer.closeAll();
|
|
soonToast('已保存', 'success');
|
|
render();
|
|
}
|
|
});
|
|
};
|
|
if (hasFile) {
|
|
return readFileText(fileEl).then(save).catch(function () {
|
|
soonToast('文件读取失败', 'error');
|
|
});
|
|
}
|
|
return save(null);
|
|
}, { area: ['480px', 'auto'] });
|
|
}
|
|
|
|
function deleteTemplate(plan) {
|
|
Ui().confirm('确定删除模板「' + plan.name + '」?', function () {
|
|
Api().del('templates/' + plan.id).then(function (r) {
|
|
if (r) {
|
|
soonToast('已删除', 'success');
|
|
render();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function render() {
|
|
var panel = Ui().el('panel-templates');
|
|
if (!panel) return;
|
|
panel.innerHTML = '<div class="soon-admin-page">' +
|
|
Ui().pageHeader('模板库', '管理首页展示的 .soon 模板(缩略图与名称轻量加载)',
|
|
'<button type="button" class="soon-btn soon-btn--sm" id="tplCreate">上传模板</button>') +
|
|
Ui().skeletonTable() + '</div>';
|
|
|
|
Api().get('templates').then(function (j) {
|
|
if (!j) return;
|
|
var items = j.data.items || [];
|
|
var rows = items.map(function (p) {
|
|
return '<tr data-id="' + p.id + '"><td>' + p.id + '</td><td>' + I().esc(p.name) +
|
|
'</td><td>' + typeLabel(p.type) + '</td><td>' + formatBytes(p.file_size) + '</td><td>' +
|
|
p.sort_order + '</td><td>' + (p.has_thumb ? '有' : '—') + '</td><td>' +
|
|
activeBadge(p.is_active) + '</td><td>' +
|
|
'<button type="button" class="soon-btn soon-btn--sm soon-btn--ghost tpl-edit">编辑</button> ' +
|
|
'<button type="button" class="soon-btn soon-btn--sm soon-btn--ghost tpl-del">删除</button></td></tr>';
|
|
}).join('');
|
|
|
|
panel.innerHTML = '<div class="soon-admin-page">' +
|
|
Ui().pageHeader('模板库', '管理首页展示的 .soon 模板(缩略图与名称轻量加载)',
|
|
'<button type="button" class="soon-btn soon-btn--sm" id="tplCreate">上传模板</button>') +
|
|
Ui().dataTable(
|
|
['ID', '名称', '类型', '大小', '排序', '缩略图', '状态', '操作'],
|
|
rows
|
|
) + '</div>';
|
|
|
|
var planMap = {};
|
|
items.forEach(function (p) { planMap[p.id] = p; });
|
|
|
|
panel.querySelector('#tplCreate').onclick = openCreateModal;
|
|
panel.querySelectorAll('.tpl-edit').forEach(function (btn) {
|
|
btn.onclick = function () {
|
|
var id = parseInt(btn.closest('tr').dataset.id, 10);
|
|
if (planMap[id]) openEditModal(planMap[id]);
|
|
};
|
|
});
|
|
panel.querySelectorAll('.tpl-del').forEach(function (btn) {
|
|
btn.onclick = function () {
|
|
var id = parseInt(btn.closest('tr').dataset.id, 10);
|
|
if (planMap[id]) deleteTemplate(planMap[id]);
|
|
};
|
|
});
|
|
});
|
|
}
|
|
|
|
window.AdminViews = window.AdminViews || {};
|
|
window.AdminViews.templates = { render: render };
|
|
})();
|