P0 - AI交互层:
- ai/prompt.js: 模块化 Prompt 模板
- ai/validator.js: JSON Schema 校验 + normalize
- ai/client.js: DeepSeek API 封装
- 金额计算唯一化,时间格式归一化
P1 - 后端架构:
- routes/: auth/rooms/messages/purchases 独立路由
- middleware/auth.js: 认证授权中间件
- ws/index.js: WebSocket 连接管理
- db.js: 启用 foreign_keys
- server.js: 从588行精简到40行入口
P2 - 前端架构:
- public/css/style.css: CSS 独立
- public/js/store.js: 全局状态 Store
- public/js/{chat,ws,purchases,auth}.js: 功能模块拆分
- index.html: 纯 HTML 结构, v2.6
147 lines
8.2 KiB
HTML
147 lines
8.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>小财记账</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
<meta name="theme-color" content="#1e3c72">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<link rel="stylesheet" href="/css/style.css">
|
|
</head>
|
|
<body>
|
|
<div class="app" id="app">
|
|
<!-- 登录 -->
|
|
<div id="login-page" class="modal-overlay">
|
|
<div class="modal">
|
|
<h2>登录小财记账</h2>
|
|
<input type="text" id="login-user" placeholder="用户名">
|
|
<input type="password" id="login-pass" placeholder="密码">
|
|
<button class="primary-btn" onclick="login()" style="width:100%;">登录</button>
|
|
<p id="login-error" style="color:red; margin-top:8px;"></p>
|
|
<div class="version">v2.6</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 主界面 -->
|
|
<div id="main-page" class="hidden" style="display:flex; flex-direction:column; height:100%;">
|
|
<!-- 群聊列表页 -->
|
|
<div id="list-page" style="display:flex; flex-direction:column; height:100%;">
|
|
<div class="header">
|
|
<div class="header-left">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" stroke="#fff" fill="none" stroke-width="1.5">
|
|
<rect x="3" y="3" width="18" height="18" rx="3"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="12" y2="17"/>
|
|
</svg>
|
|
<h2>小财记账</h2>
|
|
</div>
|
|
<div style="display:flex; gap:2px;">
|
|
<button class="icon-btn" id="create-room-btn" onclick="openCreateRoom()" title="新建群聊">
|
|
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
|
</button>
|
|
<button class="icon-btn" id="logout-btn" onclick="Store.logout()" title="注销登录">
|
|
<svg viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="chat-list" id="chat-list-container"></div>
|
|
<div class="summary-card" onclick="openSummary()">
|
|
<div>
|
|
<h4>采购汇总</h4>
|
|
<div class="summary-preview" id="summary-preview">加载中...</div>
|
|
</div>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" stroke="#888" fill="none" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 聊天窗口 -->
|
|
<div id="chat-page" class="chat-window hidden">
|
|
<div class="chat-header">
|
|
<button class="icon-btn back-btn" onclick="showList()">
|
|
<svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
|
</button>
|
|
<span class="chat-title" id="current-chat-name"></span>
|
|
<button class="icon-btn" onclick="openPurchasePanel()" title="采购清单">
|
|
<svg viewBox="0 0 24 24"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>
|
|
</button>
|
|
</div>
|
|
<div class="messages" id="messages-container"></div>
|
|
<div class="input-area">
|
|
<label class="file-upload-btn" title="上传图片">
|
|
<input type="file" id="file-input" accept="image/*" multiple style="display:none;" onchange="handleFileSelect(event)">
|
|
<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
|
|
</label>
|
|
<textarea id="msg-input" placeholder="输入消息..." rows="1"></textarea>
|
|
<button class="send-btn" onclick="sendMessage()">
|
|
<svg viewBox="0 0 24 24"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
|
|
</button>
|
|
</div>
|
|
<div id="purchase-panel-overlay" class="purchase-panel-overlay hidden">
|
|
<div class="purchase-panel-header">
|
|
<span>采购清单</span>
|
|
<button class="icon-btn close-btn" onclick="closePurchasePanel()">
|
|
<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
</button>
|
|
</div>
|
|
<div class="purchase-panel-body" id="purchase-panel-body"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 弹窗 -->
|
|
<div id="create-modal" class="modal-overlay hidden">
|
|
<div class="modal">
|
|
<button class="modal-close-btn" onclick="closeCreateModal()">
|
|
<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
</button>
|
|
<h3>新建项目群聊</h3>
|
|
<input type="text" id="new-room-name" placeholder="群聊名称">
|
|
<input type="text" id="white-list" placeholder="白名单成员(逗号分隔,留空全员可见)">
|
|
<button class="primary-btn" onclick="confirmCreateRoom()">创建</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="manage-modal" class="modal-overlay hidden">
|
|
<div class="modal">
|
|
<button class="modal-close-btn" onclick="closeManageModal()">
|
|
<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
</button>
|
|
<h3 id="manage-title">编辑群聊</h3>
|
|
<input type="text" id="manage-room-name" placeholder="群聊名称">
|
|
<input type="text" id="manage-white-list" placeholder="白名单成员(逗号分隔)">
|
|
<button class="primary-btn" onclick="saveRoom()">保存</button>
|
|
<button class="close-modal" style="background:#e53e3e;color:#fff;margin-top:8px;" onclick="deleteRoom()">删除群聊</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="detail-modal" class="modal-overlay hidden">
|
|
<div class="modal">
|
|
<button class="modal-close-btn" onclick="closeDetailModal()">
|
|
<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
</button>
|
|
<h3 id="detail-title"></h3>
|
|
<div id="detail-content"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/js/store.js"></script>
|
|
<script src="/js/chat.js"></script>
|
|
<script src="/js/ws.js"></script>
|
|
<script src="/js/purchases.js"></script>
|
|
<script src="/js/auth.js"></script>
|
|
<script>
|
|
document.addEventListener('input', function(e) {
|
|
if (e.target.id === 'msg-input') {
|
|
e.target.style.height = 'auto';
|
|
e.target.style.height = e.target.scrollHeight + 'px';
|
|
}
|
|
});
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.target.id === 'msg-input' && e.key === 'Enter' && !e.ctrlKey) { e.preventDefault(); sendMessage(); }
|
|
});
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html>
|