const API_BASE = window.API_BASE || 'https://sms-api.ouai.nyc.mn'
const app = document.getElementById('app')
const state = {
view: 'login',
messages: [],
logs: [],
page: 1,
total: 0,
logsPage: 1,
logsTotal: 0,
currentMessage: null,
fromNumbers: [],
selectedFrom: '',
search: '',
stats: { total: 0, today: 0, failed: 0 },
startTs: 0,
endTs: 0,
}
function monthRange(offset = 0) {
const now = new Date()
const y = now.getFullYear()
const m = now.getMonth() + offset
const start = new Date(y, m, 1, 0, 0, 0)
const end = new Date(y, m + 1, 0, 23, 59, 59)
return [start.getTime(), end.getTime()]
}
function initDefaultRange() {
const [s, e] = monthRange(0)
state.startTs = s
state.endTs = e
}
async function api(path, options = {}) {
const res = await fetch(`${API_BASE}${path}`, {
credentials: 'include',
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
...options,
})
return res.json()
}
function setView(view) {
state.view = view
render()
}
function nav(active) {
return `
`
}
function loginView() {
return `
`
}
function statsCards() {
const s = state.stats
return `
`
}
function fromFilter() {
const tags = state.fromNumbers.map(n => `
${n}
`).join('')
return `
`
}
function timeFilter() {
return `
时间筛选
`
}
function toolbar() {
return `
`
}
function listView() {
const rows = state.messages.map(m => `
| ${m.id} |
${m.from_number} |
${m.content} |
${new Date(m.timestamp).toLocaleString()} |
详情 |
`).join('')
return `
${nav('list')}
${statsCards()}
${fromFilter()}
${timeFilter()}
${toolbar()}
`
}
function detailView() {
const m = state.currentMessage
if (!m) return ''
return `
${nav('list')}
📱 短信详情
入库时间
${new Date(m.created_at || m.timestamp).toLocaleString()}
签名验证
${m.sign_verified ? '已验证' : '未验证'}
设备信息
${m.device_info || '-'}
SIM 卡信息
${m.sim_info || '-'}
IP 地址
${m.ip_address || '-'}
`
}
function logsView() {
const rows = state.logs.map(l => `
| ${l.id} |
${l.from_number} |
${l.status} |
${l.error_message || ''} |
${new Date(l.timestamp).toLocaleString()} |
`).join('')
return `
${nav('logs')}
`
}
function statsView() {
return `
${nav('stats')}
${statsCards()}
`
}
async function loadStats() {
const data = await api('/api/stats')
if (data.success) state.stats = data.data
}
async function loadMessages() {
const data = await api(`/api/messages?page=${state.page}&limit=20&from=${encodeURIComponent(state.selectedFrom)}&q=${encodeURIComponent(state.search)}&start_ts=${state.startTs}&end_ts=${state.endTs}`)
if (!data.success) return
state.messages = data.data
state.total = data.total
state.fromNumbers = data.from_numbers || []
}
async function loadLogs() {
const data = await api(`/api/logs?page=${state.logsPage}&limit=20`)
if (!data.success) return
state.logs = data.data
state.logsTotal = data.total
}
async function render() {
if (state.view === 'login') {
app.innerHTML = loginView()
document.getElementById('loginBtn').onclick = async () => {
const username = document.getElementById('username').value
const password = document.getElementById('password').value
const res = await api('/api/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) })
if (res.success) {
initDefaultRange()
state.view = 'list'
await loadStats()
await loadMessages()
render()
} else alert(res.error || '登录失败')
}
return
}
if (state.view === 'list') {
await loadStats()
await loadMessages()
app.innerHTML = listView()
bindCommon()
document.querySelectorAll('.tag').forEach(tag => {
tag.onclick = () => { state.selectedFrom = tag.dataset.from || ''; state.page = 1; render(); }
})
document.getElementById('searchBtn').onclick = () => { state.search = document.getElementById('search').value; state.page = 1; render(); }
document.getElementById('refreshBtn').onclick = () => render()
document.getElementById('prevPage').onclick = () => { if (state.page>1) { state.page--; render(); } }
document.getElementById('nextPage').onclick = () => { state.page++; render(); }
document.getElementById('thisMonthBtn').onclick = () => { const [s,e] = monthRange(0); state.startTs=s; state.endTs=e; state.page=1; render(); }
document.getElementById('lastMonthBtn').onclick = () => { const [s,e] = monthRange(-1); state.startTs=s; state.endTs=e; state.page=1; render(); }
document.getElementById('allBtn').onclick = () => { state.startTs=0; state.endTs=0; state.page=1; render(); }
document.querySelectorAll('[data-action="detail"]').forEach(btn => {
btn.onclick = async () => {
const id = btn.dataset.id
const data = await api(`/api/messages/${id}`)
if (data.success) { state.currentMessage = data.data; state.view = 'detail'; render(); }
}
})
return
}
if (state.view === 'detail') {
app.innerHTML = detailView()
bindCommon()
document.getElementById('backBtn').onclick = () => { state.view = 'list'; render(); }
return
}
if (state.view === 'logs') {
await loadLogs()
app.innerHTML = logsView()
bindCommon()
document.getElementById('prevLogs').onclick = () => { if (state.logsPage>1) { state.logsPage--; render(); } }
document.getElementById('nextLogs').onclick = () => { state.logsPage++; render(); }
return
}
if (state.view === 'stats') {
await loadStats()
app.innerHTML = statsView()
bindCommon()
return
}
}
function bindCommon() {
document.querySelectorAll('[data-view]').forEach(a => {
a.onclick = () => setView(a.dataset.view)
})
const logout = document.getElementById('logoutBtn')
if (logout) logout.onclick = async () => { await api('/api/auth/logout', { method: 'POST' }); state.view='login'; render(); }
}
initDefaultRange()
render()