AdminUserList.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. <template>
  2. <el-container class="admin-user-container">
  3. <el-header height="auto" class="search-header">
  4. <el-form :inline="true" :model="queryParams" size="small" class="search-form" @submit.native.prevent>
  5. <el-form-item label="用户筛选">
  6. <el-input
  7. v-model="queryParams.screenName"
  8. clearable
  9. placeholder="输入显示名搜索"
  10. prefix-icon="el-icon-search"
  11. class="search-input"
  12. @keyup.enter.native="handleSearch"
  13. />
  14. </el-form-item>
  15. <el-form-item label="状态">
  16. <el-select
  17. v-model="queryParams.status"
  18. clearable
  19. placeholder="全部状态"
  20. class="status-select"
  21. @change="handleSearch"
  22. >
  23. <el-option label="正常" :value="1" />
  24. <el-option label="封禁中" :value="2" />
  25. <el-option label="已注销" :value="3" />
  26. </el-select>
  27. </el-form-item>
  28. <el-form-item class="search-actions">
  29. <el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
  30. <el-button icon="el-icon-refresh" class="btn-refresh" @click="onRefresh">重置</el-button>
  31. </el-form-item>
  32. </el-form>
  33. </el-header>
  34. <el-main class="table-main">
  35. <el-table
  36. v-loading="loading"
  37. :data="dataList"
  38. border
  39. stripe
  40. height="520"
  41. style="width: 100%"
  42. class="custom-table"
  43. >
  44. <el-table-column
  45. fixed="left"
  46. label="No"
  47. type="index"
  48. width="60"
  49. align="center"
  50. />
  51. <el-table-column
  52. label="头像"
  53. width="80"
  54. align="center"
  55. >
  56. <template slot-scope="scope">
  57. <el-avatar
  58. :size="36"
  59. :src="scope.row.avatarUrl"
  60. icon="el-icon-user-solid"
  61. class="table-avatar"
  62. />
  63. </template>
  64. </el-table-column>
  65. <el-table-column
  66. prop="userId"
  67. label="用户 ID"
  68. width="120"
  69. align="center"
  70. />
  71. <el-table-column
  72. prop="username"
  73. label="用户名"
  74. min-width="120"
  75. />
  76. <el-table-column
  77. prop="screenName"
  78. label="显示名"
  79. min-width="120"
  80. >
  81. <template slot-scope="scope">
  82. <router-link :to="`/user/${scope.row.userId}`" class="user-link">
  83. <span>{{ scope.row.screenName || '未设置' }}</span>
  84. <i class="el-icon-link link-icon" />
  85. </router-link>
  86. </template>
  87. </el-table-column>
  88. <el-table-column
  89. prop="signature"
  90. label="个性签名"
  91. min-width="180"
  92. >
  93. <template slot-scope="scope">
  94. <el-tooltip
  95. v-if="scope.row.signature"
  96. :content="scope.row.signature"
  97. placement="top"
  98. :open-delay="400"
  99. >
  100. <span class="ellipsis-text">{{ scope.row.signature }}</span>
  101. </el-tooltip>
  102. <span v-else class="text-muted">-</span>
  103. </template>
  104. </el-table-column>
  105. <el-table-column
  106. prop="pubDate"
  107. label="注册时间"
  108. width="160"
  109. align="center"
  110. />
  111. <el-table-column
  112. label="帐号状态"
  113. width="100"
  114. align="center"
  115. >
  116. <template slot-scope="scope">
  117. <el-tag v-if="scope.row.status === 1" type="success" size="mini" effect="light">
  118. 正常
  119. </el-tag>
  120. <el-tag v-else-if="scope.row.status === 2" type="danger" size="mini" effect="light">
  121. 封禁中
  122. </el-tag>
  123. <el-tag v-else type="info" size="mini" effect="light">
  124. 已注销
  125. </el-tag>
  126. </template>
  127. </el-table-column>
  128. <el-table-column
  129. label="拥有的角色"
  130. min-width="140"
  131. >
  132. <template slot-scope="scope">
  133. <div v-if="scope.row.roles && scope.row.roles.length" class="role-tags-wrapper">
  134. <el-tag
  135. v-for="role in (Array.isArray(scope.row.roles) ? scope.row.roles : scope.row.roles.split(','))"
  136. :key="role"
  137. size="mini"
  138. type="warning"
  139. class="role-tag"
  140. >
  141. {{ role }}
  142. </el-tag>
  143. </div>
  144. <span v-else class="text-muted">普通用户</span>
  145. </template>
  146. </el-table-column>
  147. <el-table-column
  148. fixed="right"
  149. label="操作"
  150. width="100"
  151. align="center"
  152. >
  153. <template slot-scope="scope">
  154. <el-button
  155. size="mini"
  156. type="text"
  157. class="btn-action-delete"
  158. icon="el-icon-lock"
  159. :disabled="scope.row.status !== 1"
  160. @click="handleDisable(scope.$index, scope.row)"
  161. >
  162. 禁用
  163. </el-button>
  164. </template>
  165. </el-table-column>
  166. </el-table>
  167. <div class="pagination-container">
  168. <el-pagination
  169. background
  170. layout="total, prev, pager, next, sizes"
  171. :page-sizes="[10, 12, 20, 50]"
  172. :page-size="pageSize"
  173. :current-page="currentPage"
  174. :total="totalSize"
  175. @current-change="handleCurrentChange"
  176. @size-change="handleSizeChange"
  177. />
  178. </div>
  179. </el-main>
  180. </el-container>
  181. </template>
  182. <script>
  183. import { getUserList } from '@/api/admin'
  184. export default {
  185. name: 'AdminUserList',
  186. data() {
  187. return {
  188. queryParams: {
  189. pn: 1,
  190. status: '', // 初始化为空字符串,以便触发 el-select 的 clearable
  191. screenName: ''
  192. },
  193. currentPage: 1,
  194. pageSize: 12,
  195. totalSize: 0,
  196. dataList: [],
  197. loading: false
  198. }
  199. },
  200. created() {
  201. this.getData()
  202. },
  203. methods: {
  204. getData() {
  205. this.loading = true
  206. this.queryParams.pn = this.currentPage
  207. // 过滤掉空字符串参数
  208. const params = { ...this.queryParams }
  209. if (!params.screenName.trim()) delete params.screenName
  210. if (params.status === '') delete params.status
  211. getUserList(params).then(resp => {
  212. if (resp.code === 0) {
  213. const respData = resp.data
  214. this.dataList = respData.list || []
  215. this.totalSize = respData.totalSize || 0
  216. } else {
  217. this.$message.error(resp.msg)
  218. }
  219. }).catch(err => {
  220. this.$message.error(err.message || '获取列表失败')
  221. }).finally(() => {
  222. this.loading = false
  223. })
  224. },
  225. // 点击查询或下拉切换时调用
  226. handleSearch() {
  227. this.currentPage = 1 // 搜索时重置回第一页
  228. this.getData()
  229. },
  230. // 重置/刷新
  231. onRefresh() {
  232. this.queryParams.screenName = ''
  233. this.queryParams.status = ''
  234. this.currentPage = 1
  235. this.getData()
  236. this.$message.success('数据已重置并刷新')
  237. },
  238. handleCurrentChange(pageNumber) {
  239. this.currentPage = pageNumber
  240. this.getData()
  241. // 丝滑滚动回顶部
  242. const tableBody = this.$el.querySelector('.el-table__body-wrapper')
  243. if (tableBody) tableBody.scrollTop = 0
  244. },
  245. handleSizeChange(newSize) {
  246. this.pageSize = newSize
  247. this.currentPage = 1
  248. this.getData()
  249. },
  250. handleDisable(index, row) {
  251. this.$confirm(`确定要封禁用户 [${row.screenName || row.username}] 吗?封禁后其所有正在进行的会话将被强制中断。`, '安全警告', {
  252. confirmButtonText: '确定封禁',
  253. cancelButtonText: '取消',
  254. confirmButtonClass: 'el-button--danger',
  255. type: 'warning'
  256. }).then(() => {
  257. // 调用封禁 API 成功后应执行刷新:this.getData()
  258. this.$message.success(`用户 ${row.userId} 封禁成功`)
  259. }).catch(() => {})
  260. }
  261. }
  262. }
  263. </script>
  264. <style scoped>
  265. /* 容器精细布局 */
  266. .admin-user-container {
  267. background: #fff;
  268. border-radius: 8px;
  269. display: flex;
  270. flex-direction: column;
  271. }
  272. /* 顶部搜索栏现代化微调 */
  273. .search-header {
  274. padding: 16px 4px 4px 4px;
  275. border-bottom: 1px solid #f1f5f9;
  276. }
  277. .search-form {
  278. display: flex;
  279. flex-wrap: wrap;
  280. gap: 4px 16px;
  281. }
  282. ::v-deep .el-form-item {
  283. margin-bottom: 12px;
  284. }
  285. ::v-deep .el-form-item__label {
  286. color: #64748b;
  287. font-weight: 500;
  288. padding-right: 8px;
  289. }
  290. .search-input {
  291. width: 200px;
  292. }
  293. .status-select {
  294. width: 130px;
  295. }
  296. .btn-refresh {
  297. background: #f8fafc;
  298. border-color: #cbd5e1;
  299. color: #64748b;
  300. }
  301. .btn-refresh:hover {
  302. color: #1890ff;
  303. border-color: #1890ff;
  304. background: #fff;
  305. }
  306. /* 表格主体美化 */
  307. .table-main {
  308. padding: 16px 0 0 0;
  309. }
  310. .custom-table {
  311. border-radius: 6px;
  312. overflow: hidden;
  313. }
  314. /* 只保留需要的行内溢出裁剪,优化自带 tooltip 样式 */
  315. .ellipsis-text {
  316. display: block;
  317. white-space: nowrap;
  318. overflow: hidden;
  319. text-overflow: ellipsis;
  320. color: #334155;
  321. }
  322. .text-muted {
  323. color: #94a3b8;
  324. }
  325. /* 头像与连接项美化 */
  326. .table-avatar {
  327. border: 1px solid #e2e8f0;
  328. background-color: #f8fafc;
  329. box-shadow: 0 2px 6px rgba(0,0,0,0.02);
  330. }
  331. .user-link {
  332. color: #1890ff;
  333. text-decoration: none;
  334. display: inline-flex;
  335. align-items: center;
  336. gap: 4px;
  337. font-weight: 500;
  338. }
  339. .user-link:hover {
  340. color: #40a9ff;
  341. text-decoration: underline;
  342. }
  343. .link-icon {
  344. font-size: 12px;
  345. opacity: 0.7;
  346. }
  347. /* 角色标签容器包裹弹性盒模型 */
  348. .role-tags-wrapper {
  349. display: flex;
  350. flex-wrap: wrap;
  351. gap: 4px;
  352. }
  353. .role-tag {
  354. border-radius: 4px;
  355. border: 1px solid #fee2e2;
  356. background: #fff7ed;
  357. color: #ea580c;
  358. }
  359. /* 操作项去纽扣化改文字 */
  360. .btn-action-delete {
  361. color: #ef4444;
  362. font-weight: 500;
  363. padding: 0;
  364. }
  365. .btn-action-delete:hover {
  366. color: #dc2626;
  367. }
  368. .btn-action-delete.is-disabled {
  369. color: #cbd5e1;
  370. }
  371. /* 底部分页精细对齐 */
  372. .pagination-container {
  373. margin-top: 20px;
  374. display: flex;
  375. justify-content: flex-end;
  376. }
  377. ::v-deep .el-pagination.is-background .el-pager li:not(.disabled).active {
  378. background-gradient: linear-gradient(135deg, #1890ff 0%, #0076e4 100%);
  379. background-color: #1890ff;
  380. }
  381. </style>