135 lines
4.8 KiB
JavaScript
135 lines
4.8 KiB
JavaScript
const $ = (id) => document.getElementById(id);
|
|
const msg = (t) => $("msg").textContent = typeof t === 'string' ? t : JSON.stringify(t, null, 2);
|
|
const tokenKey = 'asset_tracker_token';
|
|
let categories = [];
|
|
|
|
function token(){ return localStorage.getItem(tokenKey) || ''; }
|
|
|
|
async function api(path, method='GET', body){
|
|
const headers = {};
|
|
if(token()) headers['Authorization'] = `Bearer ${token()}`;
|
|
if(body) headers['Content-Type'] = 'application/json';
|
|
const res = await fetch(path, { method, headers, body: body ? JSON.stringify(body) : undefined });
|
|
const data = await res.json().catch(() => ({}));
|
|
if(!res.ok) throw new Error(data.error || `${res.status}`);
|
|
return data;
|
|
}
|
|
|
|
async function doLogin(){
|
|
try{
|
|
const data = await api('/api/v1/auth/login', 'POST', {
|
|
username: $('username').value.trim(),
|
|
password: $('password').value
|
|
});
|
|
localStorage.setItem(tokenKey, data.access_token);
|
|
$('login-section').classList.add('hidden');
|
|
$('app-section').classList.remove('hidden');
|
|
await reloadAll();
|
|
msg('登录成功');
|
|
}catch(e){ msg(`登录失败: ${e.message}`); }
|
|
}
|
|
|
|
async function loadCategories(){
|
|
const data = await api('/api/v1/categories');
|
|
categories = data.data || [];
|
|
$('category-list').innerHTML = categories.map(c => `<li>#${c.id} ${c.name} (${c.type})</li>`).join('') || '<li>暂无分类</li>';
|
|
$('asset-category').innerHTML = categories.map(c => `<option value="${c.id}">${c.name}</option>`).join('');
|
|
}
|
|
|
|
async function addCategory(){
|
|
try{
|
|
await api('/api/v1/categories', 'POST', {
|
|
name: $('cat-name').value.trim(),
|
|
type: $('cat-type').value
|
|
});
|
|
$('cat-name').value='';
|
|
await loadCategories();
|
|
msg('分类创建成功');
|
|
}catch(e){ msg(`分类创建失败: ${e.message}`); }
|
|
}
|
|
|
|
function toRFC3339(localVal){
|
|
if(!localVal) return '';
|
|
const d = new Date(localVal);
|
|
return d.toISOString();
|
|
}
|
|
|
|
async function addAsset(){
|
|
try{
|
|
await api('/api/v1/assets', 'POST', {
|
|
name: $('asset-name').value.trim(),
|
|
category_id: Number($('asset-category').value),
|
|
quantity: Number($('asset-quantity').value || 0),
|
|
unit_price: Number($('asset-price').value || 0),
|
|
currency: $('asset-currency').value.trim().toUpperCase(),
|
|
expiry_date: toRFC3339($('asset-expiry').value)
|
|
});
|
|
$('asset-name').value='';
|
|
await loadAssets();
|
|
await loadDashboard();
|
|
msg('资产创建成功');
|
|
}catch(e){ msg(`资产创建失败: ${e.message}`); }
|
|
}
|
|
|
|
async function loadAssets(){
|
|
const data = await api('/api/v1/assets?page=1&page_size=100');
|
|
const rows = data.data || [];
|
|
const map = new Map(categories.map(c => [c.id, c.name]));
|
|
$('asset-tbody').innerHTML = rows.map(a => `<tr>
|
|
<td>${a.id}</td>
|
|
<td>${a.name}</td>
|
|
<td>${map.get(a.category_id) || a.category_id}</td>
|
|
<td>${a.total_value} ${a.currency}</td>
|
|
<td>${a.status}</td>
|
|
<td>${a.expiry_date || '-'}</td>
|
|
<td><button onclick="delAsset(${a.id})">删除</button></td>
|
|
</tr>`).join('') || '<tr><td colspan="7">暂无资产</td></tr>';
|
|
|
|
$('asset-cards').innerHTML = rows.map(a => `<div class="asset-card">
|
|
<div class="line"><b>#${a.id} ${a.name}</b><span>${a.status}</span></div>
|
|
<div class="line"><span>分类</span><span>${map.get(a.category_id) || a.category_id}</span></div>
|
|
<div class="line"><span>金额</span><span>${a.total_value} ${a.currency}</span></div>
|
|
<div class="line"><span>到期</span><span>${a.expiry_date || '-'}</span></div>
|
|
<div class="line"><button onclick="delAsset(${a.id})">删除</button></div>
|
|
</div>`).join('') || '<div class="asset-card">暂无资产</div>';
|
|
}
|
|
|
|
async function delAsset(id){
|
|
try{
|
|
await api(`/api/v1/assets/${id}`, 'DELETE');
|
|
await loadAssets();
|
|
await loadDashboard();
|
|
msg(`已删除资产 #${id}`);
|
|
}catch(e){ msg(`删除失败: ${e.message}`); }
|
|
}
|
|
window.delAsset = delAsset;
|
|
|
|
async function loadDashboard(){
|
|
const d = await api('/api/v1/dashboard/summary');
|
|
const byCat = (d.by_category || []).map(x => `${x.category_name}: ${x.total_value}`).join('<br>') || '无';
|
|
$('dashboard').innerHTML = `
|
|
<div class="kpi"><b>总资产</b><div>${d.total_assets_value}</div></div>
|
|
<div class="kpi"><b>分类占比</b><div>${byCat}</div></div>
|
|
<div class="kpi"><b>30天到期</b><div>${(d.expiring_in_30_days || []).length} 条</div></div>
|
|
`;
|
|
}
|
|
|
|
async function reloadAll(){
|
|
await loadCategories();
|
|
await loadAssets();
|
|
await loadDashboard();
|
|
}
|
|
|
|
$('login-btn').addEventListener('click', doLogin);
|
|
$('add-cat').addEventListener('click', addCategory);
|
|
$('add-asset').addEventListener('click', addAsset);
|
|
$('refresh-dashboard').addEventListener('click', loadDashboard);
|
|
|
|
(async function init(){
|
|
if(token()){
|
|
$('login-section').classList.add('hidden');
|
|
$('app-section').classList.remove('hidden');
|
|
try{ await reloadAll(); }catch(e){ msg(`自动加载失败: ${e.message}`); }
|
|
}
|
|
})();
|