feat: add user management CRUD and platform_user table

V24 migration creates platform_user table. Backend UserAdminController provides list/create/update/toggleStatus. Frontend UserManagementView enables admin to add/edit/disable users. Replaces hardcoded auth with database-backed user lifecycle.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-27 08:37:02 +08:00
parent 118790486a
commit 8c167d4909
5 changed files with 273 additions and 0 deletions
@@ -461,6 +461,20 @@ export function createSkuMapping(contractLineId, body) { return axios.post(`/api
export function updateSkuMapping(id, body) { return axios.put(`/api/v1/integration/sku-mappings/${id}`, body); }
export function deleteSkuMapping(id) { return axios.delete(`/api/v1/integration/sku-mappings/${id}`); }
// —— M11-F14 用户管理 ————————————————————————————
export function listUsers() {
return axios.get('/api/v1/admin/users');
}
export function createUser(body) {
return axios.post('/api/v1/admin/users', body);
}
export function updateUser(id, body) {
return axios.put(`/api/v1/admin/users/${id}`, body);
}
export function patchUserStatus(id, body) {
return axios.patch(`/api/v1/admin/users/${id}/status`, body);
}
export function listStakeholders(projectId) {
return axios.get(`/api/v1/projects/${projectId}/stakeholders`);
}
@@ -0,0 +1,166 @@
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { apiErrorMessage } from '../utils/apiErrorMessage'
import { listUsers, createUser, updateUser, patchUserStatus } from '../api/platform'
const loading = ref(false)
const users = ref([])
const dialogVisible = ref(false)
const dialogTitle = ref('')
const editingId = ref(null)
const form = ref({ username: '', displayName: '', password: '', role: 'SALES' })
const roles = [
{ value: 'SYS_ADMIN', label: '系统管理员' },
{ value: 'SALES', label: '商务经理' },
{ value: 'DELIVERY', label: '交付工程师' },
{ value: 'LICENSE_OPS', label: '授权运营' },
]
onMounted(loadData)
async function loadData() {
loading.value = true
try {
const { data } = await listUsers()
users.value = data
} catch (e) {
ElMessage.error(apiErrorMessage(e, '加载用户列表失败'))
} finally {
loading.value = false
}
}
function openCreate() {
editingId.value = null
dialogTitle.value = '新建用户'
form.value = { username: '', displayName: '', password: '', role: 'SALES' }
dialogVisible.value = true
}
function openEdit(row) {
editingId.value = row.id
dialogTitle.value = '编辑用户'
form.value = {
username: row.username,
displayName: row.displayName || '',
password: '',
role: row.role || 'SALES',
}
dialogVisible.value = true
}
async function handleSave() {
const body = { ...form.value }
if (!editingId.value) {
if (!body.password || body.password.length < 6) {
ElMessage.error('密码至少6位')
return
}
try {
await createUser(body)
ElMessage.success('用户创建成功')
dialogVisible.value = false
await loadData()
} catch (e) {
ElMessage.error(apiErrorMessage(e, '创建失败'))
}
} else {
if (body.password && body.password.length < 6) {
ElMessage.error('密码至少6位')
return
}
try {
await updateUser(editingId.value, body)
ElMessage.success('用户更新成功')
dialogVisible.value = false
await loadData()
} catch (e) {
ElMessage.error(apiErrorMessage(e, '更新失败'))
}
}
}
async function handleToggleStatus(row) {
const newStatus = row.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE'
const label = newStatus === 'ACTIVE' ? '启用' : '禁用'
try {
await patchUserStatus(row.id, { status: newStatus })
ElMessage.success(`用户已${label}`)
await loadData()
} catch (e) {
ElMessage.error(apiErrorMessage(e, `${label}失败`))
}
}
function statusTagType(status) {
return status === 'ACTIVE' ? 'success' : 'danger'
}
function statusLabel(status) {
return status === 'ACTIVE' ? '正常' : '已禁用'
}
</script>
<template>
<div>
<h2>用户管理</h2>
<el-card shadow="never" style="margin-top: 16px">
<template #header>
<el-button type="primary" @click="openCreate">新建用户</el-button>
</template>
<el-table v-loading="loading" :data="users" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="用户名" min-width="140" />
<el-table-column prop="displayName" label="显示名" min-width="160" />
<el-table-column label="角色" width="140">
<template #default="{ row }">
<el-tag size="small">{{ row.role }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="statusTagType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" width="180" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="openEdit(row)">编辑</el-button>
<el-button
:type="row.status === 'ACTIVE' ? 'warning' : 'success'"
link
@click="handleToggleStatus(row)"
>
{{ row.status === 'ACTIVE' ? '禁用' : '启用' }}
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="480px" @closed="dialogVisible = false">
<el-form label-width="100px">
<el-form-item label="用户名" required>
<el-input v-model="form.username" :disabled="!!editingId" maxlength="64" placeholder="登录名" />
</el-form-item>
<el-form-item label="显示名">
<el-input v-model="form.displayName" maxlength="128" placeholder="选填,默认同用户名" />
</el-form-item>
<el-form-item label="角色" required>
<el-select v-model="form.role" style="width: 100%">
<el-option v-for="r in roles" :key="r.value" :label="r.label" :value="r.value" />
</el-select>
</el-form-item>
<el-form-item :label="editingId ? '新密码' : '密码'" :required="!editingId">
<el-input v-model="form.password" type="password" show-password :placeholder="editingId ? '留空则不修改' : '至少6位'" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</template>
</el-dialog>
</div>
</template>