groups.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>群组管理 - 后台管理系统</title>
  7. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
  8. <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">
  9. <style>
  10. .sidebar {
  11. position: fixed;
  12. top: 60px;
  13. bottom: 0;
  14. left: 0;
  15. z-index: 100;
  16. padding: 0;
  17. box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
  18. background-color: #f8f9fa;
  19. }
  20. .sidebar-sticky {
  21. position: relative;
  22. top: 0;
  23. height: calc(100vh - 60px);
  24. padding-top: .5rem;
  25. overflow-x: hidden;
  26. overflow-y: auto;
  27. }
  28. .navbar {
  29. box-shadow: 0 2px 4px rgba(0,0,0,.1);
  30. height: 60px;
  31. }
  32. .main-content {
  33. margin-left: 240px;
  34. padding: 20px;
  35. margin-top: 60px;
  36. }
  37. .nav-link {
  38. color: #333;
  39. padding: 10px 20px;
  40. }
  41. .nav-link:hover {
  42. background-color: #e9ecef;
  43. }
  44. .nav-link.active {
  45. color: #0d6efd;
  46. background-color: #e9ecef;
  47. }
  48. .card {
  49. margin-bottom: 20px;
  50. box-shadow: 0 0 10px rgba(0,0,0,0.1);
  51. }
  52. .btn-action {
  53. margin-right: 5px;
  54. }
  55. </style>
  56. </head>
  57. <body>
  58. <nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top">
  59. <div class="container-fluid">
  60. <a class="navbar-brand" href="#">后台管理系统</a>
  61. <div class="d-flex">
  62. <button class="btn btn-outline-danger" id="logoutBtn">退出登录</button>
  63. </div>
  64. </div>
  65. </nav>
  66. <div class="container-fluid">
  67. <div class="row">
  68. <nav class="col-md-3 col-lg-2 d-md-block sidebar">
  69. <div class="sidebar-sticky">
  70. <ul class="nav flex-column">
  71. <li class="nav-item">
  72. <a class="nav-link" href="/admin/views/dashboard.html" data-page="dashboard">
  73. <i class="bi bi-speedometer2"></i> 仪表板
  74. </a>
  75. </li>
  76. <li class="nav-item">
  77. <a class="nav-link active" href="/admin/views/groups.html" data-page="groups">
  78. <i class="bi bi-people"></i> 群组管理
  79. </a>
  80. </li>
  81. <li class="nav-item">
  82. <a class="nav-link" href="/admin/views/transactions.html" data-page="transactions">
  83. <i class="bi bi-cash-stack"></i> 交易记录
  84. </a>
  85. </li>
  86. <li class="nav-item">
  87. <a class="nav-link" href="/admin/views/statistics.html" data-page="statistics">
  88. <i class="bi bi-graph-up"></i> 统计报表
  89. </a>
  90. </li>
  91. <li class="nav-item">
  92. <a class="nav-link" href="/admin/views/settings.html" data-page="settings">
  93. <i class="bi bi-gear"></i> 系统设置
  94. </a>
  95. </li>
  96. </ul>
  97. </div>
  98. </nav>
  99. <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
  100. <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
  101. <h1 class="h2">群组管理</h1>
  102. <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addGroupModal">
  103. <i class="bi bi-plus-lg"></i> 添加群组
  104. </button>
  105. </div>
  106. <div class="card">
  107. <div class="card-body">
  108. <div class="table-responsive">
  109. <table class="table table-hover">
  110. <thead>
  111. <tr>
  112. <th>群组ID</th>
  113. <th>群组名称</th>
  114. <th>状态</th>
  115. <th>创建时间</th>
  116. <th>操作</th>
  117. </tr>
  118. </thead>
  119. <tbody id="groupsList">
  120. </tbody>
  121. </table>
  122. </div>
  123. </div>
  124. </div>
  125. </main>
  126. </div>
  127. </div>
  128. <!-- 添加群组模态框 -->
  129. <div class="modal fade" id="addGroupModal" tabindex="-1">
  130. <div class="modal-dialog">
  131. <div class="modal-content">
  132. <div class="modal-header">
  133. <h5 class="modal-title">添加群组</h5>
  134. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  135. </div>
  136. <div class="modal-body">
  137. <form id="addGroupForm">
  138. <div class="mb-3">
  139. <label for="groupId" class="form-label">群组ID</label>
  140. <input type="text" class="form-control" id="groupId" required>
  141. </div>
  142. <div class="mb-3">
  143. <label for="groupName" class="form-label">群组名称</label>
  144. <input type="text" class="form-control" id="groupName" required>
  145. </div>
  146. </form>
  147. </div>
  148. <div class="modal-footer">
  149. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
  150. <button type="button" class="btn btn-primary" id="saveGroupBtn">保存</button>
  151. </div>
  152. </div>
  153. </div>
  154. </div>
  155. <!-- 编辑群组模态框 -->
  156. <div class="modal fade" id="editGroupModal" tabindex="-1">
  157. <div class="modal-dialog">
  158. <div class="modal-content">
  159. <div class="modal-header">
  160. <h5 class="modal-title">编辑群组</h5>
  161. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  162. </div>
  163. <div class="modal-body">
  164. <form id="editGroupForm">
  165. <input type="hidden" id="editGroupId">
  166. <div class="mb-3">
  167. <label for="editGroupName" class="form-label">群组名称</label>
  168. <input type="text" class="form-control" id="editGroupName" required>
  169. </div>
  170. <div class="mb-3">
  171. <div class="form-check">
  172. <input class="form-check-input" type="checkbox" id="editGroupStatus">
  173. <label class="form-check-label" for="editGroupStatus">
  174. 启用群组
  175. </label>
  176. </div>
  177. </div>
  178. </form>
  179. </div>
  180. <div class="modal-footer">
  181. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
  182. <button type="button" class="btn btn-primary" id="updateGroupBtn">更新</button>
  183. </div>
  184. </div>
  185. </div>
  186. </div>
  187. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
  188. <script>
  189. // 检查登录状态
  190. function checkAuth() {
  191. const token = localStorage.getItem('token');
  192. console.log('检查登录状态,token:', token ? '存在' : '不存在');
  193. if (!token) {
  194. console.log('未找到token,跳转到登录页');
  195. window.location.href = '/admin/views/login.html';
  196. return;
  197. }
  198. // 验证token是否有效
  199. fetch('/api/users/profile', {
  200. headers: {
  201. 'Authorization': `Bearer ${token}`
  202. }
  203. })
  204. .then(response => {
  205. if (!response.ok) {
  206. throw new Error('Token无效');
  207. }
  208. return response.json();
  209. })
  210. .then(data => {
  211. console.log('用户信息:', data);
  212. // 可以在这里保存用户信息到全局变量
  213. window.currentUser = data;
  214. })
  215. .catch(error => {
  216. console.error('验证token时出错:', error);
  217. localStorage.removeItem('token');
  218. window.location.href = '/admin/views/login.html';
  219. });
  220. }
  221. // 加载群组列表
  222. async function loadGroups() {
  223. try {
  224. const token = localStorage.getItem('token');
  225. const response = await fetch('/api/groups', {
  226. headers: {
  227. 'Authorization': `Bearer ${token}`
  228. }
  229. });
  230. if (response.ok) {
  231. const groups = await response.json();
  232. updateGroupsList(groups);
  233. } else {
  234. if (response.status === 401) {
  235. window.location.href = '/';
  236. }
  237. }
  238. } catch (error) {
  239. console.error('加载群组失败:', error);
  240. }
  241. }
  242. // 更新群组列表
  243. function updateGroupsList(groups) {
  244. const groupsList = document.getElementById('groupsList');
  245. groupsList.innerHTML = groups.map(group => `
  246. <tr>
  247. <td>${group.group_id}</td>
  248. <td>${group.group_name}</td>
  249. <td>
  250. <span class="badge ${group.is_active ? 'bg-success' : 'bg-danger'}">
  251. ${group.is_active ? '启用' : '禁用'}
  252. </span>
  253. </td>
  254. <td>${new Date(group.created_at).toLocaleString()}</td>
  255. <td>
  256. <button class="btn btn-sm btn-primary btn-action" onclick="editGroup('${group.id}')">
  257. <i class="bi bi-pencil"></i>
  258. </button>
  259. <button class="btn btn-sm btn-danger btn-action" onclick="deleteGroup('${group.id}')">
  260. <i class="bi bi-trash"></i>
  261. </button>
  262. </td>
  263. </tr>
  264. `).join('');
  265. }
  266. // 添加群组
  267. document.getElementById('saveGroupBtn').addEventListener('click', async () => {
  268. const groupId = document.getElementById('groupId').value;
  269. const groupName = document.getElementById('groupName').value;
  270. try {
  271. const token = localStorage.getItem('token');
  272. const response = await fetch('/api/groups', {
  273. method: 'POST',
  274. headers: {
  275. 'Authorization': `Bearer ${token}`,
  276. 'Content-Type': 'application/json'
  277. },
  278. body: JSON.stringify({
  279. groupId,
  280. groupName,
  281. creatorId: window.currentUser._id
  282. })
  283. });
  284. if (response.ok) {
  285. const modal = bootstrap.Modal.getInstance(document.getElementById('addGroupModal'));
  286. modal.hide();
  287. document.getElementById('addGroupForm').reset();
  288. loadGroups();
  289. } else {
  290. const data = await response.json();
  291. alert(data.message || '添加群组失败');
  292. }
  293. } catch (error) {
  294. console.error('添加群组失败:', error);
  295. alert('添加群组失败,请稍后重试');
  296. }
  297. });
  298. // 编辑群组
  299. async function editGroup(groupId) {
  300. try {
  301. const token = localStorage.getItem('token');
  302. const response = await fetch(`/api/groups/${groupId}`, {
  303. headers: {
  304. 'Authorization': `Bearer ${token}`
  305. }
  306. });
  307. if (response.ok) {
  308. const group = await response.json();
  309. document.getElementById('editGroupId').value = group.id;
  310. document.getElementById('editGroupName').value = group.group_name;
  311. document.getElementById('editGroupStatus').checked = group.is_active;
  312. const modal = new bootstrap.Modal(document.getElementById('editGroupModal'));
  313. modal.show();
  314. }
  315. } catch (error) {
  316. console.error('加载群组信息失败:', error);
  317. alert('加载群组信息失败,请稍后重试');
  318. }
  319. }
  320. // 更新群组
  321. document.getElementById('updateGroupBtn').addEventListener('click', async () => {
  322. const groupId = document.getElementById('editGroupId').value;
  323. const groupName = document.getElementById('editGroupName').value;
  324. const isActive = document.getElementById('editGroupStatus').checked;
  325. try {
  326. const token = localStorage.getItem('token');
  327. const response = await fetch(`/api/groups/${groupId}`, {
  328. method: 'PUT',
  329. headers: {
  330. 'Authorization': `Bearer ${token}`,
  331. 'Content-Type': 'application/json'
  332. },
  333. body: JSON.stringify({ groupName, isActive })
  334. });
  335. if (response.ok) {
  336. const modal = bootstrap.Modal.getInstance(document.getElementById('editGroupModal'));
  337. modal.hide();
  338. loadGroups();
  339. } else {
  340. const data = await response.json();
  341. alert(data.message || '更新群组失败');
  342. }
  343. } catch (error) {
  344. console.error('更新群组失败:', error);
  345. alert('更新群组失败,请稍后重试');
  346. }
  347. });
  348. // 删除群组
  349. async function deleteGroup(groupId) {
  350. if (!confirm('确定要删除这个群组吗?')) {
  351. return;
  352. }
  353. try {
  354. const token = localStorage.getItem('token');
  355. const response = await fetch(`/api/groups/${groupId}`, {
  356. method: 'DELETE',
  357. headers: {
  358. 'Authorization': `Bearer ${token}`
  359. }
  360. });
  361. if (response.ok) {
  362. loadGroups();
  363. } else {
  364. const data = await response.json();
  365. alert(data.message || '删除群组失败');
  366. }
  367. } catch (error) {
  368. console.error('删除群组失败:', error);
  369. alert('删除群组失败,请稍后重试');
  370. }
  371. }
  372. // 退出登录
  373. document.getElementById('logoutBtn').addEventListener('click', () => {
  374. localStorage.removeItem('token');
  375. window.location.href = '/';
  376. });
  377. // 页面加载时检查登录状态并加载数据
  378. checkAuth();
  379. loadGroups();
  380. </script>
  381. </body>
  382. </html>