index.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <template>
  2. <div class="page-container">
  3. <!-- 搜索区域 -->
  4. <el-card class="search-card">
  5. <el-form :model="queryForm" inline>
  6. <el-form-item label="手机号">
  7. <el-input v-model="queryForm.phone" placeholder="请输入手机号" clearable style="width:180px" />
  8. </el-form-item>
  9. <el-form-item label="昵称">
  10. <el-input v-model="queryForm.nickname" placeholder="请输入昵称" clearable style="width:180px" />
  11. </el-form-item>
  12. <el-form-item label="用户类型">
  13. <el-select v-model="queryForm.userType" placeholder="全部" clearable style="width:120px">
  14. <el-option
  15. v-for="item in userCategoryOptions"
  16. :key="item.code"
  17. :label="item.name"
  18. :value="item.code"
  19. />
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item label="状态">
  23. <el-select v-model="queryForm.status" placeholder="全部" clearable style="width:100px">
  24. <el-option label="正常" :value="1" />
  25. <el-option label="禁用" :value="0" />
  26. </el-select>
  27. </el-form-item>
  28. <el-form-item>
  29. <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
  30. <el-button :icon="Refresh" @click="handleReset">重置</el-button>
  31. </el-form-item>
  32. </el-form>
  33. </el-card>
  34. <!-- 表格区域 -->
  35. <el-card class="table-card">
  36. <template #header>
  37. <div class="card-header">
  38. <span class="title">账号管理列表</span>
  39. <el-button type="primary" :icon="Plus" @click="handleAdd">新增用户</el-button>
  40. </div>
  41. </template>
  42. <el-table :data="tableData" v-loading="loading" border stripe>
  43. <el-table-column type="index" label="#" width="55" align="center" />
  44. <el-table-column prop="phone" label="手机号" width="130" />
  45. <el-table-column prop="nickname" label="昵称" />
  46. <el-table-column prop="userType" label="用户类型" width="100" align="center">
  47. <template #default="{ row }">
  48. <el-tag
  49. :type="userTypeTagType(row.userType)"
  50. size="small"
  51. >
  52. {{ userTypeText(row.userType) }}
  53. </el-tag>
  54. </template>
  55. </el-table-column>
  56. <el-table-column prop="status" label="状态" width="80" align="center">
  57. <template #default="{ row }">
  58. <el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
  59. {{ row.status === 1 ? '正常' : '禁用' }}
  60. </el-tag>
  61. </template>
  62. </el-table-column>
  63. <el-table-column prop="s" label="绑定设备" width="170" />
  64. <el-table-column prop="s" label="子用户" width="170" />
  65. <el-table-column prop="createTime" label="注册时间" width="170" />
  66. <el-table-column label="操作" width="240" align="center" fixed="right">
  67. <template #default="{ row }">
  68. <el-button size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
  69. <el-button
  70. size="small"
  71. :type="row.status === 1 ? 'warning' : 'success'"
  72. link
  73. @click="handleToggleStatus(row)"
  74. >
  75. {{ row.status === 1 ? '禁用' : '启用' }}
  76. </el-button>
  77. <!-- <el-button size="small" type="info" link @click="handleViewProfiles(row)">子用户</el-button> -->
  78. <el-button size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
  79. </template>
  80. </el-table-column>
  81. </el-table>
  82. <!-- 分页 -->
  83. <el-pagination
  84. v-model:current-page="queryForm.pageNum"
  85. v-model:page-size="queryForm.pageSize"
  86. :page-sizes="[10, 20, 50]"
  87. :total="total"
  88. layout="total, sizes, prev, pager, next, jumper"
  89. class="pagination"
  90. @size-change="loadData"
  91. @current-change="loadData"
  92. />
  93. </el-card>
  94. <!-- 新增/编辑弹窗 -->
  95. <el-dialog
  96. v-model="dialogVisible"
  97. :title="dialogTitle"
  98. width="500px"
  99. destroy-on-close
  100. >
  101. <el-form :model="formData" :rules="formRules" ref="formRef" label-width="90px">
  102. <el-form-item label="手机号" prop="phone">
  103. <el-input v-model="formData.phone" placeholder="请输入手机号" :disabled="!!formData.id" />
  104. </el-form-item>
  105. <el-form-item label="昵称" prop="nickname">
  106. <el-input v-model="formData.nickname" placeholder="请输入昵称" />
  107. </el-form-item>
  108. <el-form-item label="用户类型" prop="userType">
  109. <el-select v-model="formData.userType" placeholder="请选择" style="width:100%">
  110. <el-option
  111. v-for="item in userCategoryOptions"
  112. :key="item.code"
  113. :label="item.name"
  114. :value="item.code"
  115. />
  116. </el-select>
  117. </el-form-item>
  118. <el-form-item label="状态" prop="status">
  119. <el-radio-group v-model="formData.status">
  120. <el-radio :label="1">正常</el-radio>
  121. <el-radio :label="0">禁用</el-radio>
  122. </el-radio-group>
  123. </el-form-item>
  124. </el-form>
  125. <template #footer>
  126. <el-button @click="dialogVisible = false">取消</el-button>
  127. <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
  128. </template>
  129. </el-dialog>
  130. </div>
  131. </template>
  132. <script setup>
  133. import { ref, reactive, onMounted } from 'vue'
  134. import { useRouter } from 'vue-router'
  135. import { ElMessage, ElMessageBox } from 'element-plus'
  136. import { Search, Refresh, Plus } from '@element-plus/icons-vue'
  137. import {
  138. getAppUserPage,
  139. addAppUser,
  140. updateAppUser,
  141. deleteAppUser,
  142. updateAppUserStatus
  143. } from '@/api/user'
  144. import { getUserCategoryList } from '@/api/userCategory'
  145. const router = useRouter()
  146. /** 用户类别选项(从后台 user_category 动态加载) */
  147. const userCategoryOptions = ref([])
  148. /** 用户类型文本 */
  149. function userTypeText(type) {
  150. if (type == null) return '-'
  151. const item = userCategoryOptions.value.find(c => c.code === type)
  152. return item ? item.name : '-'
  153. }
  154. /** 用户类型标签颜色 */
  155. function userTypeTagType(type) {
  156. if (type == null) return 'info'
  157. const item = userCategoryOptions.value.find(c => c.code === type)
  158. return item?.tagType || 'primary'
  159. }
  160. // 查询表单
  161. const queryForm = reactive({
  162. phone: '',
  163. nickname: '',
  164. userType: null,
  165. status: null,
  166. pageNum: 1,
  167. pageSize: 10
  168. })
  169. const tableData = ref([])
  170. const total = ref(0)
  171. const loading = ref(false)
  172. // 弹窗
  173. const dialogVisible = ref(false)
  174. const dialogTitle = ref('')
  175. const submitLoading = ref(false)
  176. const formRef = ref(null)
  177. const formData = reactive({
  178. id: null,
  179. phone: '',
  180. nickname: '',
  181. userType: 1,
  182. status: 1
  183. })
  184. const formRules = {
  185. phone: [
  186. { required: true, message: '请输入手机号', trigger: 'blur' },
  187. { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
  188. ],
  189. userType: [{ required: true, message: '请选择用户类型', trigger: 'change' }]
  190. }
  191. // 加载数据
  192. async function loadData() {
  193. loading.value = true
  194. try {
  195. const res = await getAppUserPage(queryForm)
  196. tableData.value = res.data?.records || []
  197. total.value = res.data?.total || 0
  198. } finally {
  199. loading.value = false
  200. }
  201. }
  202. function handleSearch() {
  203. queryForm.pageNum = 1
  204. loadData()
  205. }
  206. function handleReset() {
  207. queryForm.phone = ''
  208. queryForm.nickname = ''
  209. queryForm.userType = null
  210. queryForm.status = null
  211. queryForm.pageNum = 1
  212. loadData()
  213. }
  214. // 新增
  215. function handleAdd() {
  216. Object.assign(formData, { id: null, phone: '', nickname: '', userType: 1, status: 1 })
  217. dialogTitle.value = '新增账号'
  218. dialogVisible.value = true
  219. }
  220. // 编辑
  221. function handleEdit(row) {
  222. Object.assign(formData, { id: row.id, phone: row.phone, nickname: row.nickname, userType: row.userType, status: row.status })
  223. dialogTitle.value = '编辑账号'
  224. dialogVisible.value = true
  225. }
  226. // 提交
  227. async function handleSubmit() {
  228. await formRef.value.validate()
  229. submitLoading.value = true
  230. try {
  231. if (formData.id) {
  232. await updateAppUser({ ...formData })
  233. } else {
  234. await addAppUser({ ...formData })
  235. }
  236. ElMessage.success('操作成功')
  237. dialogVisible.value = false
  238. loadData()
  239. } finally {
  240. submitLoading.value = false
  241. }
  242. }
  243. // 禁用/启用
  244. async function handleToggleStatus(row) {
  245. const newStatus = row.status === 1 ? 0 : 1
  246. const action = newStatus === 0 ? '禁用' : '启用'
  247. await ElMessageBox.confirm(`确认${action}该用户吗?`, '提示', { type: 'warning' })
  248. await updateAppUserStatus(row.id, newStatus)
  249. ElMessage.success(`已${action}`)
  250. loadData()
  251. }
  252. // 查看子用户
  253. function handleViewProfiles(row) {
  254. router.push({ path: '/user/profile', query: { userId: row.id } })
  255. }
  256. // 删除
  257. async function handleDelete(row) {
  258. await ElMessageBox.confirm('确认删除该用户吗?删除后不可恢复!', '提示', { type: 'warning' })
  259. await deleteAppUser(row.id)
  260. ElMessage.success('删除成功')
  261. loadData()
  262. }
  263. async function loadUserCategories() {
  264. try {
  265. const res = await getUserCategoryList()
  266. userCategoryOptions.value = res.data || []
  267. } catch (e) {
  268. userCategoryOptions.value = []
  269. }
  270. }
  271. onMounted(() => {
  272. loadUserCategories()
  273. loadData()
  274. })
  275. </script>
  276. <style lang="scss" scoped>
  277. .page-container {
  278. display: flex;
  279. flex-direction: column;
  280. gap: 16px;
  281. }
  282. .search-card {
  283. :deep(.el-card__body) {
  284. padding: 16px 20px 4px;
  285. }
  286. }
  287. .table-card {
  288. .card-header {
  289. display: flex;
  290. align-items: center;
  291. justify-content: space-between;
  292. .title {
  293. font-weight: 600;
  294. font-size: 15px;
  295. }
  296. }
  297. }
  298. .pagination {
  299. margin-top: 16px;
  300. justify-content: flex-end;
  301. }
  302. </style>