Files
bj_power/bj_power_wms/frontend/src/pages/UserManage.vue
T
SunYF 2544c6141d refactor: 合并原库房客户端到主WMS服务,退役8891端口
1.  将bj_power_wms_client的前端代码、配置及网关逻辑全部迁移到bj_power_wms主项目
2.  删除原库房客户端相关的所有文件与配置
3.  更新README与部署文档,说明合并后的服务架构
4.  新增Web配置项支持自动打开浏览器开关,统一服务端口为8890
2026-09-07 15:55:30 +08:00

240 lines
8.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
defineOptions({ name: 'UserManage' })
import { onMounted, reactive, ref, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '../utils/request'
import { fmtTime } from '../utils/format'
import { can } from '../utils/perm'
// 角色数据源:实时取自角色表(含自定义角色),code/name 与 RoleManage 同源
const roles = ref([])
const roleOptions = computed(() => roles.value.map((r) => ({ value: r.id, label: `${r.name}${r.code}` })))
const roleOfId = computed(() => {
const m = {}
for (const r of roles.value) m[r.id] = r
return m
})
async function loadRoles() {
try {
const data = await request.get('/roles/options')
roles.value = data?.list || []
} catch { /* 无权限时不阻断用户页 */ }
}
const list = ref([])
const total = ref(0)
const loading = ref(false)
const page = reactive({ current: 1, size: 20 })
// 后端真分页(created_at desc 最新置顶),禁止前端全量拉取后本地分页
async function loadUsers() {
loading.value = true
try {
const data = await request.get('/user/list', {
params: { page: page.current, pageSize: page.size }
})
list.value = data?.list || []
total.value = data?.total || 0
} finally {
loading.value = false
}
}
function onPage() {
loadUsers()
}
function roleName(row) {
const r = roleOfId.value[row.roleId]
if (r) return r.name
return row.role || '-'
}
function roleTagType(row) {
const r = roleOfId.value[row.roleId]
if (r && r.code === 'admin') return 'danger'
if (row.role === 'admin') return 'danger'
if (row.role === 'inspector') return 'warning'
return 'primary'
}
/* ---------- 新增 / 编辑 弹窗 ---------- */
const dialogVisible = ref(false)
const editingId = ref(0)
const saving = ref(false)
const form = reactive({ username: '', realName: '', roleId: 0, dept: '', password: '' })
const pwdAllNum = computed(() => /^[\d]+$/.test(form.password))
function resetForm() {
editingId.value = 0
Object.assign(form, { username: '', realName: '', roleId: 0, dept: '', password: '' })
}
function openCreate() {
resetForm()
dialogVisible.value = true
}
function openEdit(row) {
resetForm()
editingId.value = row.id
form.username = row.username
form.realName = row.realName || ''
form.roleId = row.roleId || 0
form.dept = row.dept || ''
form.password = '' // 编辑时不填 = 不改密码
dialogVisible.value = true
}
async function save() {
if (!form.username.trim()) return ElMessage.warning('请输入用户名')
if (!editingId.value && !form.password) return ElMessage.warning('请输入初始密码')
if (form.password && form.password.length < 6) return ElMessage.warning('密码长度至少 6 位')
if (!form.roleId) return ElMessage.warning('请选择角色')
saving.value = true
try {
if (editingId.value) {
const payload = {
id: editingId.value,
realName: form.realName.trim() || undefined,
roleId: form.roleId,
dept: form.dept.trim() || undefined,
isActive: undefined
}
if (form.password) payload.password = form.password // 填写则重置密码
await request.post('/user/update', payload)
ElMessage.success('保存成功')
} else {
await request.post('/user/create', {
username: form.username.trim(),
realName: form.realName.trim() || undefined,
roleId: form.roleId,
dept: form.dept.trim() || undefined,
password: form.password
})
ElMessage.success('账号创建成功')
}
dialogVisible.value = false
await loadUsers()
} finally {
saving.value = false
}
}
async function toggleActive(row) {
try {
await request.post('/user/update', { id: row.id, isActive: !row.isActive })
ElMessage.success(row.isActive ? '已停用' : '已启用')
await loadUsers()
} catch { /* 拦截器已提示 */ }
}
async function removeUser(row) {
try {
await ElMessageBox.confirm(`确定删除账号「${row.username}」?该操作不可恢复。`, '删除确认', {
type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消'
})
} catch {
return
}
try {
await request.post('/user/delete', { id: row.id })
ElMessage.success('已删除')
await loadUsers()
} catch { /* 拦截器已提示 */ }
}
onMounted(() => {
loadRoles()
loadUsers()
})
</script>
<template>
<div>
<el-card shadow="never" class="mb12">
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<b>账号管理</b>
<span style="margin-left:8px;color:#909399;font-size:12px">角色决定菜单与功能权限系统内置 admin 账号不可改</span>
</div>
<el-button v-if="can('user:create')" type="primary" @click="openCreate">新增账号</el-button>
</div>
</el-card>
<el-card shadow="never">
<el-table :data="list" border size="small" v-loading="loading" empty-text="暂无账号">
<el-table-column prop="id" label="ID" width="64" align="center" />
<el-table-column prop="username" label="用户名" min-width="120" />
<el-table-column prop="realName" label="姓名" min-width="110">
<template #default="{ row }">{{ row.realName || '-' }}</template>
</el-table-column>
<el-table-column label="角色" width="140" align="center">
<template #default="{ row }">
<el-tag :type="roleTagType(row)" size="small">{{ roleName(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="dept" label="部门" min-width="100">
<template #default="{ row }">{{ row.dept || '-' }}</template>
</el-table-column>
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
<el-switch :model-value="row.isActive"
:disabled="row.username === 'admin' || !can('user:edit')" @change="toggleActive(row)" />
</template>
</el-table-column>
<el-table-column label="最近登录" width="150" align="center">
<template #default="{ row }">{{ row.lastLoginAt ? fmtTime(row.lastLoginAt) : '从未' }}</template>
</el-table-column>
<el-table-column label="创建时间" width="155" align="center">
<template #default="{ row }">{{ row.createdAt ? fmtTime(row.createdAt) : '-' }}</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="can('user:edit')" link type="primary" size="small" :disabled="row.username === 'admin'"
@click="openEdit(row)">编辑</el-button>
<el-button v-if="can('user:delete')" link type="danger" size="small" :disabled="row.username === 'admin'"
@click="removeUser(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="page.current" v-model:page-size="page.size" :page-sizes="[10, 20, 50]"
:total="total" background layout="total, sizes, prev, pager, next, jumper" style="margin-top:12px"
@size-change="() => { page.current = 1; loadUsers() }" @current-change="onPage" />
</el-card>
<el-dialog v-model="dialogVisible" :title="editingId ? '编辑账号' : '新增账号'" width="520px"
append-to-body destroy-on-close @closed="resetForm">
<el-form label-width="90px">
<el-form-item label="用户名" required>
<el-input v-model="form.username" :disabled="!!editingId" placeholder="登录名创建后不可改" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="form.realName" placeholder="显示名可空" />
</el-form-item>
<el-form-item label="角色" required>
<el-select v-model="form.roleId" style="width:100%" placeholder="请选择角色">
<el-option v-for="o in roleOptions" :key="o.value" :value="o.value" :label="o.label" />
</el-select>
</el-form-item>
<el-form-item label="部门">
<el-input v-model="form.dept" placeholder="部门可空" />
</el-form-item>
<el-form-item :label="editingId ? '重置密码' : '初始密码'" required>
<el-input v-model="form.password" type="password" show-password
:placeholder="editingId ? '不填则保持原密码(至少6位)' : '至少 6 位'" />
<el-alert v-if="pwdAllNum" type="warning" :closable="false" style="margin-top:6px"
title="密码为纯数字安全性较低建议包含字母与数字组合" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.mb12 { margin-bottom: 12px; }
</style>