1. 将bj_power_wms_client的前端代码、配置及网关逻辑全部迁移到bj_power_wms主项目 2. 删除原库房客户端相关的所有文件与配置 3. 更新README与部署文档,说明合并后的服务架构 4. 新增Web配置项支持自动打开浏览器开关,统一服务端口为8890
240 lines
8.9 KiB
Vue
240 lines
8.9 KiB
Vue
<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>
|