feat: 实现社区激励金(公户)系统,经济闭环
- 新增 CommunityFund / FundLog 模型(公户余额单行表 + 6种流水类型) - 新增 FundRepo 仓储(GetBalance / IncrBalance / CreateLog / ListLogs / WithTx) - 改造 EnergyService 五个方法:Energize 税收入公户、DeductOnRename/DeductOnDeletePost 入公户、RefundOnRenameReject 从公户支出、AdminAdjust 拆分为 admin_transfer/system_operation - 新增 ErrInsufficientFund 错误哨兵 - 管理后台新增公户余额展示 + 公户流水页面 + 域能调整资金来源选择 - 新增 energy.css / energy.js 前端交互 - 侧边栏新增公户流水入口
This commit is contained in:
207
templates/admin/static/js/energy.js
Normal file
207
templates/admin/static/js/energy.js
Normal file
@ -0,0 +1,207 @@
|
||||
// 域能管理页面 JS
|
||||
(function () {
|
||||
const path = window.location.pathname;
|
||||
|
||||
if (path === '/admin/energy') {
|
||||
initEnergyPage();
|
||||
} else if (path === '/admin/energy/logs') {
|
||||
initEnergyLogPage();
|
||||
} else if (path === '/admin/energy/fund-logs') {
|
||||
initFundLogPage();
|
||||
}
|
||||
|
||||
// ----- 域能管理首页:公户余额 + 调整表单 -----
|
||||
function initEnergyPage() {
|
||||
var adjustForm = document.getElementById('adjust-form');
|
||||
if (!adjustForm) return;
|
||||
|
||||
adjustForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var form = e.target;
|
||||
var submitBtn = form.querySelector('button[type="submit"]');
|
||||
var msgEl = document.getElementById('adjust-message');
|
||||
if (msgEl) msgEl.className = 'form-message';
|
||||
|
||||
var userIDsStr = form.querySelector('[name="user_ids"]').value.trim();
|
||||
var amountStr = form.querySelector('[name="amount"]').value.trim();
|
||||
var desc = form.querySelector('[name="description"]').value.trim();
|
||||
var mode = form.querySelector('[name="mode"]').value;
|
||||
|
||||
if (!userIDsStr || !amountStr || !desc) {
|
||||
showMsg(msgEl, '请填写完整信息', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var userIDs = userIDsStr.split(',').map(function (s) { return parseInt(s.trim(), 10); }).filter(function (n) { return n > 0; });
|
||||
var amount = parseInt(amountStr, 10);
|
||||
if (userIDs.length === 0 || isNaN(amount) || amount === 0) {
|
||||
showMsg(msgEl, '用户ID和金额格式不正确', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
|
||||
api.post('/api/admin/energy/adjust', {
|
||||
user_ids: userIDs,
|
||||
amount: amount,
|
||||
description: desc,
|
||||
mode: mode
|
||||
}, function (res) {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
if (res.success) {
|
||||
showMsg(msgEl, res.message || '调整成功', 'success');
|
||||
// 刷新公户余额
|
||||
setTimeout(function () {
|
||||
api.get('/api/admin/energy/fund-balance', function (r) {
|
||||
if (r.success && r.data) {
|
||||
var balEl = document.getElementById('fund-balance');
|
||||
if (balEl) {
|
||||
balEl.textContent = r.data.balance_display.toFixed(1);
|
||||
balEl.className = 'fund-balance' + (r.data.balance < 0 ? ' negative' : '');
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
} else {
|
||||
showMsg(msgEl, res.message || '调整失败', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ----- 域能日志页 -----
|
||||
function initEnergyLogPage() {
|
||||
var page = 1;
|
||||
var typeFilter = document.getElementById('log-type-filter');
|
||||
|
||||
function loadLogs() {
|
||||
var logType = typeFilter ? typeFilter.value : '';
|
||||
api.get('/api/admin/energy/logs?page=' + page + '&page_size=20&type=' + encodeURIComponent(logType), function (res) {
|
||||
if (!res.success) return;
|
||||
renderLogTable('energy-log-tbody', res.data.items, function (item) {
|
||||
return [
|
||||
item.id,
|
||||
formatTime(item.created_at),
|
||||
item.user_id,
|
||||
'<span class="' + (item.amount >= 0 ? 'amount-positive' : 'amount-negative') + '">' + item.energy_display.toFixed(1) + '</span>',
|
||||
'<span class="type-badge">' + (item.type_name || item.type) + '</span>',
|
||||
escapeHtml(item.description || '')
|
||||
];
|
||||
}, 6);
|
||||
renderPagination('energy-log-pagination', page, res.data.total_pages, function (p) {
|
||||
page = p;
|
||||
loadLogs();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (typeFilter) {
|
||||
typeFilter.addEventListener('change', function () {
|
||||
page = 1;
|
||||
loadLogs();
|
||||
});
|
||||
}
|
||||
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
// ----- 公户流水页 -----
|
||||
function initFundLogPage() {
|
||||
var page = 1;
|
||||
var typeFilter = document.getElementById('fund-log-type-filter');
|
||||
|
||||
function loadLogs() {
|
||||
var logType = typeFilter ? typeFilter.value : '';
|
||||
api.get('/api/admin/energy/fund-logs?page=' + page + '&page_size=20&type=' + encodeURIComponent(logType), function (res) {
|
||||
if (!res.success) return;
|
||||
renderLogTable('fund-log-tbody', res.data.items, function (item) {
|
||||
return [
|
||||
item.id,
|
||||
formatTime(item.created_at),
|
||||
'<span class="' + (item.amount >= 0 ? 'amount-positive' : 'amount-negative') + '">' + item.amount_display.toFixed(1) + '</span>',
|
||||
'<span class="type-badge">' + (item.type_name || item.type) + '</span>',
|
||||
escapeHtml(item.description || '')
|
||||
];
|
||||
}, 5);
|
||||
renderPagination('fund-log-pagination', page, res.data.total_pages, function (p) {
|
||||
page = p;
|
||||
loadLogs();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (typeFilter) {
|
||||
typeFilter.addEventListener('change', function () {
|
||||
page = 1;
|
||||
loadLogs();
|
||||
});
|
||||
}
|
||||
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
// ----- 通用工具函数 -----
|
||||
function renderLogTable(tbodyId, items, rowFn, colCount) {
|
||||
var tbody = document.getElementById(tbodyId);
|
||||
if (!tbody) return;
|
||||
if (!items || items.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="' + colCount + '" class="empty-state">暂无记录</td></tr>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var cells = rowFn(items[i]);
|
||||
html += '<tr>';
|
||||
for (var j = 0; j < cells.length; j++) {
|
||||
html += '<td>' + cells[j] + '</td>';
|
||||
}
|
||||
html += '</tr>';
|
||||
}
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderPagination(paginationId, current, total, onChange) {
|
||||
var container = document.getElementById(paginationId);
|
||||
if (!container || total <= 1) { if (container) container.innerHTML = ''; return; }
|
||||
var html = '';
|
||||
html += '<button' + (current <= 1 ? ' disabled' : '') + ' data-page="' + (current - 1) + '">‹ 上一页</button>';
|
||||
var start = Math.max(1, current - 2);
|
||||
var end = Math.min(total, current + 2);
|
||||
for (var p = start; p <= end; p++) {
|
||||
html += '<button class="' + (p === current ? 'active' : '') + '" data-page="' + p + '">' + p + '</button>';
|
||||
}
|
||||
html += '<button' + (current >= total ? ' disabled' : '') + ' data-page="' + (current + 1) + '">下一页 ›</button>';
|
||||
container.innerHTML = html;
|
||||
|
||||
var buttons = container.querySelectorAll('button');
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var p = parseInt(this.getAttribute('data-page'), 10);
|
||||
if (p > 0 && p <= total && p !== current) {
|
||||
onChange(p);
|
||||
}
|
||||
});
|
||||
})(buttons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(isoStr) {
|
||||
if (!isoStr) return '';
|
||||
var d = new Date(isoStr);
|
||||
var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
|
||||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function showMsg(msgEl, text, type) {
|
||||
if (!msgEl) return;
|
||||
msgEl.textContent = text;
|
||||
msgEl.className = 'form-message ' + type;
|
||||
setTimeout(function () { msgEl.className = 'form-message'; }, 4000);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user