DEV Community

Leo
Leo

Posted on

用户管理模块技术文档 (Vue 3 + Element Plus)

1. 核心架构设计

  • API 层:纯粹的请求定义。
  • Hook 层 (核心):管理所有状态(List, Loading, Submitting)和逻辑(CRUD 请求)。
  • 列表页 (容器):调度中心,负责触发 Hook 中的方法。
  • 弹窗页 (展示):纯 UI 组件,通过 emit 传递数据。

2. API 定义 (src/api/user.js)

export const UserAPI = {
  fetchList: () => Promise.resolve([{ id: 1, name: 'Gemini', role: 'Admin' }]),
  create: (data) => Promise.resolve({ success: true }),
  update: (id, data) => Promise.resolve({ success: true }),
  delete: (id) => Promise.resolve({ success: true })
}

Enter fullscreen mode Exit fullscreen mode

3. 业务逻辑 Hook (src/hooks/useUserManagement.js)

所有的 API 调用和状态都在这里! 弹窗和列表页都共享这个 Hook。

import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { UserAPI } from '@/api/user'

export function useUserManagement() {
  const list = ref([])
  const loading = ref(false)
  const submitting = ref(false) // 新增:全局提交状态

  // 1. 获取列表
  const loadList = async () => {
    loading.value = true
    try {
      list.value = await UserAPI.fetchList()
    } finally {
      loading.value = false
    }
  }

  // 2. 统一保存入口 (新增或编辑)
  const saveUser = async (id, formData) => {
    submitting.value = true
    try {
      if (id) {
        await UserAPI.update(id, formData)
      } else {
        await UserAPI.create(formData)
      }
      ElMessage.success('保存成功')
      await loadList() // 自动刷新列表
      return true
    } catch (error) {
      return false
    } finally {
      submitting.value = false
    }
  }

  // 3. 删除
  const removeUser = async (id) => {
    try {
      await ElMessageBox.confirm('确定删除吗?', '警告', { type: 'warning' })
      await UserAPI.delete(id)
      ElMessage.success('删除成功')
      await loadList()
    } catch (e) {}
  }

  return { list, loading, submitting, loadList, saveUser, removeUser }
}

Enter fullscreen mode Exit fullscreen mode

4. 纯 UI 弹窗组件 (src/views/user/UserEditDialog.vue)

变化:不直接引用 UserAPI,而是通过 emit('save') 发送信号。

<template>
  <el-dialog v-model="visible" :title="isEdit ? '编辑用户' : '新增用户'" width="450px">
    <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
      <el-form-item label="用户名" prop="name">
        <el-input v-model="form.name" />
      </el-form-item>
      <el-form-item label="角色" prop="role">
        <el-select v-model="form.role" class="w-full">
          <el-option label="管理员" value="Admin" />
          <el-option label="普通用户" value="User" />
        </el-select>
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" :loading="loading" @click="handleConfirm">确定</el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, computed } from 'vue'
import { useCloned } from '@vueuse/core'

defineProps({ loading: Boolean })
const emit = defineEmits(['save'])

const visible = ref(false)
const formRef = ref(null)
const sourceData = ref({ name: '', role: 'User' })
const { cloned: form, sync } = useCloned(sourceData, { manual: true })

const isEdit = computed(() => !!sourceData.value.id)
const rules = { name: [{ required: true, message: '必填', trigger: 'blur' }] }

const open = (row) => {
  sourceData.value = row ? { ...row } : { name: '', role: 'User' }
  sync()
  visible.value = true
}

const close = () => { visible.value = false }

const handleConfirm = async () => {
  await formRef.value.validate()
  // 将数据传给父组件处理
  emit('save', { id: sourceData.value.id, data: form.value })
}

defineExpose({ open, close })
</script>

Enter fullscreen mode Exit fullscreen mode

5. 用户列表主页面 (src/views/user/index.vue)

变化:负责业务逻辑的调度。

<template>
  <div class="p-6">
    <div class="flex justify-between mb-4">
      <h2 class="text-lg font-bold">用户管理</h2>
      <el-button type="primary" @click="dialogRef.open()">新增</el-button>
    </div>

    <el-table :data="list" v-loading="loading" border>
      <el-table-column prop="name" label="姓名" />
      <el-table-column label="操作">
        <template #default="{ row }">
          <el-button link type="primary" @click="dialogRef.open(row)">编辑</el-button>
          <el-button link type="danger" @click="removeUser(row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <UserEditDialog 
      ref="dialogRef" 
      :loading="submitting" 
      @save="onSave" 
    />
  </div>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import { useUserManagement } from '@/hooks/useUserManagement'
import UserEditDialog from './UserEditDialog.vue'

const { list, loading, submitting, loadList, saveUser, removeUser } = useUserManagement()
const dialogRef = ref(null)

// 处理保存逻辑
const onSave = async ({ id, data }) => {
  const isOk = await saveUser(id, data)
  if (isOk) dialogRef.value.close()
}

onMounted(loadList)
</script>

Enter fullscreen mode Exit fullscreen mode

总结:

  1. 逻辑高度集中:所有跟 User 相关的增删改查动作都在 useUserManagement.js 里。如果 API 地址变了或业务规则变了(比如保存前要弹两次确认),你只需要改 Hook。
  2. 组件极致精简UserEditDialog.vue 现在是一个纯粹的表单组件。你可以把它直接拿去给其他页面用,只需要在外部监听 @save 即可。
  3. 状态同步成本低:因为 saveUser 内部执行了 await loadList(),所以页面不需要手动去处理“保存后如何刷新表格”的琐事,状态自动同步。

Top comments (0)