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:
30
templates/admin/html/energy/fund_logs.html
Normal file
30
templates/admin/html/energy/fund_logs.html
Normal file
@ -0,0 +1,30 @@
|
||||
{{template "admin/layout/base.html" .}}
|
||||
<div class="page-header">
|
||||
<h1>公户流水</h1>
|
||||
<p class="page-desc">查看社区激励金(公户)的全部收支记录</p>
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<label for="fund-log-type-filter" style="font-size:0.85rem;color:#4a5568;font-weight:500;">类型筛选:</label>
|
||||
<select id="fund-log-type-filter">
|
||||
<option value="">全部类型</option>
|
||||
{{range $type, $name := .LogTypes}}<option value="{{$type}}">{{$name}}</option>{{end}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table class="log-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>时间</th>
|
||||
<th>变动</th>
|
||||
<th>类型</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="fund-log-tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="fund-log-pagination" class="pagination"></div>
|
||||
{{template "admin/layout/footer.html" .}}
|
||||
@ -1,2 +1,44 @@
|
||||
{{template "admin/layout/base.html" .}}
|
||||
<div class="page-header">
|
||||
<h1>域能管理</h1>
|
||||
<p class="page-desc">管理公户余额与用户域能调整</p>
|
||||
</div>
|
||||
|
||||
<div class="fund-card">
|
||||
<div class="fund-info">
|
||||
<h3>公户余额</h3>
|
||||
<p id="fund-balance" class="fund-balance{{if lt .FundBalance 0}} negative{{end}}">{{printf "%.1f" .FundBalanceDisplay}}</p>
|
||||
</div>
|
||||
<div class="fund-icon">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="48" height="48"><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="adjust-form">
|
||||
<h3>调整用户域能</h3>
|
||||
<form id="adjust-form">
|
||||
<div class="form-group">
|
||||
<label for="user_ids">目标用户 UID(多个用英文逗号分隔)</label>
|
||||
<input type="text" id="user_ids" name="user_ids" placeholder="例如:1,2,3" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="amount">调整金额(正数为增加,负数为扣减,×10存储)</label>
|
||||
<input type="number" id="amount" name="amount" placeholder="例如:10 表示 +1.0 域能" required>
|
||||
<div class="form-hint">存储单位 ×10:输入 10 = +1.0 域能,输入 -5 = -0.5 域能</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="adjust-mode">资金来源</label>
|
||||
<select id="adjust-mode" name="mode">
|
||||
<option value="admin_transfer">公户转出(受余额约束)</option>
|
||||
{{if .IsOwner}}<option value="system_operation">系统操作(不受余额约束,仅站长可用)</option>{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">操作说明</label>
|
||||
<textarea id="description" name="description" placeholder="简要说明调整原因" required maxlength="500"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">确认调整</button>
|
||||
<div id="adjust-message" class="form-message"></div>
|
||||
</form>
|
||||
</div>
|
||||
{{template "admin/layout/footer.html" .}}
|
||||
|
||||
@ -1,2 +1,31 @@
|
||||
{{template "admin/layout/base.html" .}}
|
||||
<div class="page-header">
|
||||
<h1>域能日志</h1>
|
||||
<p class="page-desc">查看所有用户的域能流水记录</p>
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<label for="log-type-filter" style="font-size:0.85rem;color:#4a5568;font-weight:500;">类型筛选:</label>
|
||||
<select id="log-type-filter">
|
||||
<option value="">全部类型</option>
|
||||
{{range $type, $name := .LogTypes}}<option value="{{$type}}">{{$name}}</option>{{end}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table class="log-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>时间</th>
|
||||
<th>用户UID</th>
|
||||
<th>变动</th>
|
||||
<th>类型</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="energy-log-tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="energy-log-pagination" class="pagination"></div>
|
||||
{{template "admin/layout/footer.html" .}}
|
||||
|
||||
@ -48,9 +48,13 @@
|
||||
域能管理
|
||||
</a>
|
||||
<a href="/admin/energy/logs" class="sidebar-item {{if eq .CurrentPath "/admin/energy/logs"}}active{{end}}">
|
||||
<span class="sidebar-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span>
|
||||
<span class="sidebar-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/></svg></span>
|
||||
域能日志
|
||||
</a>
|
||||
<a href="/admin/energy/fund-logs" class="sidebar-item {{if eq .CurrentPath "/admin/energy/fund-logs"}}active{{end}}">
|
||||
<span class="sidebar-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg></span>
|
||||
公户流水
|
||||
</a>
|
||||
<a href="/admin/comments" class="sidebar-item {{if eq .CurrentPath "/admin/comments"}}active{{end}}">
|
||||
<span class="sidebar-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span>
|
||||
评论管理
|
||||
|
||||
259
templates/admin/static/css/energy.css
Normal file
259
templates/admin/static/css/energy.css
Normal file
@ -0,0 +1,259 @@
|
||||
/* 域能管理页面样式 */
|
||||
|
||||
.fund-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
color: #fff;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.fund-card .fund-info h3 {
|
||||
margin: 0 0 0.25rem 0;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.fund-card .fund-info .fund-balance {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fund-card .fund-info .fund-balance.negative {
|
||||
color: #fdd;
|
||||
}
|
||||
|
||||
.fund-card .fund-icon {
|
||||
font-size: 2.5rem;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* 调整表单 */
|
||||
.adjust-form {
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.adjust-form h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
color: #1a202c;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid #cbd5e0;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
color: #2d3748;
|
||||
background: #fff;
|
||||
transition: border-color 0.15s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
font-size: 0.78rem;
|
||||
color: #a0aec0;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 0.6rem 1.5rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5a67d8;
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 日志表格 */
|
||||
.log-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-table th {
|
||||
background: #f7fafc;
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: #4a5568;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.log-table td {
|
||||
padding: 0.7rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: #2d3748;
|
||||
border-bottom: 1px solid #edf2f7;
|
||||
}
|
||||
|
||||
.log-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-table .amount-positive {
|
||||
color: #38a169;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.log-table .amount-negative {
|
||||
color: #e53e3e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.type-badge {
|
||||
display: inline-block;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
background: #ebf8ff;
|
||||
color: #2b6cb0;
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-bar select {
|
||||
padding: 0.4rem 0.75rem;
|
||||
border: 1px solid #cbd5e0;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: #4a5568;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.filter-bar select:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pagination button {
|
||||
padding: 0.4rem 0.85rem;
|
||||
border: 1px solid #cbd5e0;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
font-size: 0.85rem;
|
||||
color: #4a5568;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination button:hover:not(:disabled) {
|
||||
background: #f7fafc;
|
||||
border-color: #a0aec0;
|
||||
}
|
||||
|
||||
.pagination button.active {
|
||||
background: #667eea;
|
||||
color: #fff;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.pagination button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 操作结果消息 */
|
||||
.form-message {
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-message.success {
|
||||
background: #f0fff4;
|
||||
color: #38a169;
|
||||
border: 1px solid #c6f6d5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-message.error {
|
||||
background: #fff5f5;
|
||||
color: #e53e3e;
|
||||
border: 1px solid #fed7d7;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 日志时间 */
|
||||
.log-time {
|
||||
font-size: 0.8rem;
|
||||
color: #a0aec0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
color: #a0aec0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
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