index.html 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  8. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  9. </head>
  10. <body>
  11. <div id="app" class="container mt-4">
  12. <h1 class="mb-4">用户管理系统</h1>
  13. <!-- 查询表单 -->
  14. <div class="card mb-4">
  15. <div class="card-header">
  16. <h5 class="mb-0">查询用户</h5>
  17. </div>
  18. <div class="card-body">
  19. <form @submit.prevent="searchUsers" class="row g-3">
  20. <div class="col-md-4">
  21. <label class="form-label">账号</label>
  22. <input type="text" class="form-control" v-model="searchParams.passport" placeholder="输入账号">
  23. </div>
  24. <div class="col-md-4">
  25. <label class="form-label">昵称</label>
  26. <input type="text" class="form-control" v-model="searchParams.nickname" placeholder="输入昵称">
  27. </div>
  28. <div class="col-md-4">
  29. <label class="form-label">每页数量</label>
  30. <select class="form-select" v-model="searchParams.pageSize">
  31. <option value="10">10</option>
  32. <option value="20">20</option>
  33. <option value="50">50</option>
  34. </select>
  35. </div>
  36. <div class="col-12">
  37. <button type="submit" class="btn btn-primary">查询</button>
  38. <button type="button" class="btn btn-secondary ms-2" @click="resetSearch">重置</button>
  39. </div>
  40. </form>
  41. </div>
  42. </div>
  43. <!-- 用户列表 -->
  44. <div class="card">
  45. <div class="card-header d-flex justify-content-between align-items-center">
  46. <h5 class="mb-0">用户列表</h5>
  47. <span class="text-muted">共 {{ total }} 条记录</span>
  48. </div>
  49. <div class="card-body">
  50. <div class="table-responsive">
  51. <table class="table table-striped table-hover">
  52. <thead>
  53. <tr>
  54. <th>ID</th>
  55. <th>账号</th>
  56. <th>昵称</th>
  57. <th>创建时间</th>
  58. <th>更新时间</th>
  59. </tr>
  60. </thead>
  61. <tbody>
  62. <tr v-for="user in userList" :key="user.id">
  63. <td>{{ user.id }}</td>
  64. <td>{{ user.passport }}</td>
  65. <td>{{ user.nickname }}</td>
  66. <td>{{ user.created_at }}</td>
  67. <td>{{ user.updated_at }}</td>
  68. </tr>
  69. <tr v-if="userList.length === 0">
  70. <td colspan="5" class="text-center text-muted">暂无数据</td>
  71. </tr>
  72. </tbody>
  73. </table>
  74. </div>
  75. <!-- 分页 -->
  76. <nav v-if="total > 0">
  77. <ul class="pagination justify-content-center">
  78. <li class="page-item" :class="{ disabled: currentPage === 1 }">
  79. <a class="page-link" href="#" @click.prevent="changePage(currentPage - 1)">上一页</a>
  80. </li>
  81. <li class="page-item" v-for="page in pages" :key="page" :class="{ active: page === currentPage }">
  82. <a class="page-link" href="#" @click.prevent="changePage(page)">{{ page }}</a>
  83. </li>
  84. <li class="page-item" :class="{ disabled: currentPage === totalPages }">
  85. <a class="page-link" href="#" @click.prevent="changePage(currentPage + 1)">下一页</a>
  86. </li>
  87. </ul>
  88. </nav>
  89. </div>
  90. </div>
  91. </div>
  92. <script>
  93. const { createApp, ref, computed, onMounted } = Vue;
  94. createApp({
  95. setup() {
  96. const userList = ref([]);
  97. const total = ref(0);
  98. const currentPage = ref(1);
  99. const pageSize = ref(10);
  100. const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
  101. const searchParams = ref({
  102. passport: '',
  103. nickname: '',
  104. pageSize: 10
  105. });
  106. const pages = computed(() => {
  107. const result = [];
  108. const start = Math.max(1, currentPage.value - 2);
  109. const end = Math.min(totalPages.value, start + 4);
  110. for (let i = start; i <= end; i++) {
  111. result.push(i);
  112. }
  113. return result;
  114. });
  115. async function fetchUsers(page = 1) {
  116. try {
  117. const params = new URLSearchParams({
  118. page: page,
  119. pageSize: searchParams.value.pageSize,
  120. passport: searchParams.value.passport,
  121. nickname: searchParams.value.nickname
  122. });
  123. const response = await fetch(`/user/list?${params}`);
  124. const data = await response.json();
  125. if (data.code === 0) {
  126. userList.value = data.data.list;
  127. total.value = data.data.total;
  128. currentPage.value = data.data.page;
  129. pageSize.value = searchParams.value.pageSize;
  130. } else {
  131. alert('获取用户列表失败: ' + data.message);
  132. }
  133. } catch (error) {
  134. console.error('Error:', error);
  135. alert('网络错误,请稍后重试');
  136. }
  137. }
  138. function searchUsers() {
  139. currentPage.value = 1;
  140. fetchUsers(1);
  141. }
  142. function resetSearch() {
  143. searchParams.value = {
  144. passport: '',
  145. nickname: '',
  146. pageSize: 10
  147. };
  148. searchUsers();
  149. }
  150. function changePage(page) {
  151. if (page < 1 || page > totalPages.value) return;
  152. fetchUsers(page);
  153. }
  154. onMounted(() => {
  155. fetchUsers(1);
  156. });
  157. return {
  158. userList,
  159. total,
  160. currentPage,
  161. totalPages,
  162. pages,
  163. searchParams,
  164. searchUsers,
  165. resetSearch,
  166. changePage
  167. };
  168. }
  169. }).mount('#app');
  170. </script>
  171. </body>
  172. </html>