本次迭代覆盖MES与WMS核心业务: 1. 新增接驳台托盘传感器读取与AGV对接能力 2. 完善工单排产、备料流程与权限体系拆分 3. 优化看板接口与前端路由、样式 4. 新增操作日志、库存盘点与角色保护逻辑 5. 修复代理地址、BOM保存等已知问题
217 lines
8.0 KiB
Vue
217 lines
8.0 KiB
Vue
<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">
|
||
角色决定账号的菜单与按钮权限;内置角色(超级管理员/生产操作员/质检员)不可删除
|
||
</span>
|
||
</div>
|
||
<el-button v-if="can('sys.role:add')" type="primary" @click="openCreate">新增角色</el-button>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card shadow="never">
|
||
<el-table :data="roles" border size="small" v-loading="loading" empty-text="暂无角色">
|
||
<el-table-column prop="id" label="ID" width="64" align="center" />
|
||
<el-table-column label="名称" min-width="150">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.code === 'SUPER_ADMIN' ? 'danger' : 'primary'" size="small">{{ row.name }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="remark" label="备注" min-width="180">
|
||
<template #default="{ row }">{{ row.remark || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="权限数" width="90" align="center">
|
||
<template #default="{ row }">{{ permCount(row) }} 项</template>
|
||
</el-table-column>
|
||
<el-table-column label="权限明细" min-width="240">
|
||
<template #default="{ row }">
|
||
<el-tooltip :content="permSummary(row)" placement="top" :show-after="300">
|
||
<span style="color:#606266">{{ permSummary(row) }}</span>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button v-if="can('sys.role:edit')" link type="primary" size="small"
|
||
:disabled="row.code === 'SUPER_ADMIN'" @click="openEdit(row)">编辑权限</el-button>
|
||
<el-button v-if="can('sys.role:delete')" link type="danger" size="small"
|
||
:disabled="['SUPER_ADMIN', 'OPERATOR', 'INSPECTOR'].includes(row.code)" @click="removeRole(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
|
||
<el-dialog v-model="dialog.show" :title="dialog.editingId ? '编辑角色权限' : '新增角色'" width="600px"
|
||
append-to-body destroy-on-close>
|
||
<el-form label-width="90px">
|
||
<el-form-item label="角色名称" required>
|
||
<el-input v-model="dialog.name" placeholder="如 库房管理员" />
|
||
</el-form-item>
|
||
<el-form-item label="角色编码" required>
|
||
<el-input v-model="dialog.code" :disabled="!!dialog.editingId" placeholder="如 intro-manager(创建后不可改)" />
|
||
</el-form-item>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="dialog.remark" placeholder="备注(可空)" />
|
||
</el-form-item>
|
||
<el-form-item label="权限设置">
|
||
<div class="perm-tree" v-loading="!perms.length">
|
||
<template v-if="perms.length">
|
||
<div class="perm-tip">勾选菜单=可见该菜单;其下按钮勾选=可用该按钮(勾选互不影响)</div>
|
||
<el-tree ref="treeRef" :data="permTree" show-checkbox node-key="code"
|
||
check-strictly default-expand-all :props="{ label: 'label', children: 'children' }" />
|
||
</template>
|
||
<el-empty v-else description="暂无权限数据" :image-size="60" />
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="dialog.show = false">取消</el-button>
|
||
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
<PageHelp :data="helpRole" page="RoleManage" />
|
||
</template>
|
||
|
||
<script setup>
|
||
defineOptions({ name: 'RoleManage' })
|
||
import { computed, onMounted, reactive, ref } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import request from '../utils/request'
|
||
import { can } from '../utils/perm'
|
||
import PageHelp from '../components/PageHelp.vue'
|
||
import { helpRole } from '../help'
|
||
|
||
const loading = ref(false)
|
||
const roles = ref([])
|
||
const perms = ref([]) // 全部权限定义(MENU + BUTTON)
|
||
const treeRef = ref()
|
||
|
||
const codeName = computed(() => {
|
||
const m = {}
|
||
for (const p of perms.value) m[p.code] = p.name
|
||
return m
|
||
})
|
||
|
||
// MES 无 parentCode:按钮码形如 菜单码:动作,父菜单 = 冒号前段(菜单码不含冒号)
|
||
const permTree = computed(() => {
|
||
const menus = perms.value.filter((p) => p.type === 'MENU')
|
||
const buttons = perms.value.filter((p) => p.type === 'BUTTON')
|
||
const nodes = menus.map((m) => ({
|
||
label: m.name,
|
||
code: m.code,
|
||
children: buttons
|
||
.filter((b) => b.code.startsWith(m.code + ':'))
|
||
.map((b) => ({ label: b.name, code: b.code }))
|
||
}))
|
||
const stray = buttons.filter((b) => !menus.some((m) => b.code.startsWith(m.code + ':')))
|
||
if (stray.length) {
|
||
nodes.push({ label: '通用', global: true, children: stray.map((b) => ({ label: b.name, code: b.code })) })
|
||
}
|
||
return nodes
|
||
})
|
||
|
||
function permCount(row) {
|
||
const codes = row.permissionCodes || []
|
||
if (row.code === 'SUPER_ADMIN') return codes
|
||
return codes.filter((c) => c !== '*').length
|
||
}
|
||
function permSummary(row) {
|
||
const codes = row.permissionCodes || []
|
||
if (codes.includes('*')) return '全部权限'
|
||
return codes.map((c) => codeName.value[c] || c).join('、') || '-'
|
||
}
|
||
|
||
async function loadRoles() {
|
||
loading.value = true
|
||
try {
|
||
roles.value = (await request.get('/roles')) || []
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
async function loadPerms() {
|
||
try {
|
||
perms.value = (await request.get('/permissions')) || []
|
||
} catch { /* 拦截器已提示 */ }
|
||
}
|
||
|
||
/* ---------- 新增 / 编辑 ---------- */
|
||
const dialog = reactive({ show: false, editingId: 0, name: '', code: '', remark: '', checked: [] })
|
||
const saving = ref(false)
|
||
|
||
function openCreate() {
|
||
Object.assign(dialog, { show: true, editingId: 0, name: '', code: '', remark: '', checked: [] })
|
||
}
|
||
function openEdit(row) {
|
||
Object.assign(dialog, {
|
||
show: true, editingId: row.id, name: row.name || '', code: row.code || '',
|
||
remark: row.remark || '', checked: [...(row.permissionCodes || [])]
|
||
})
|
||
// 弹窗打开待树渲染后回显
|
||
setTimeout(() => {
|
||
if (treeRef.value) treeRef.value.setCheckedKeys(dialog.checked)
|
||
}, 60)
|
||
}
|
||
|
||
function treeCheckedCodes() {
|
||
if (!treeRef.value) return dialog.checked
|
||
const keys = treeRef.value.getCheckedKeys() || []
|
||
return [...new Set(keys)]
|
||
}
|
||
|
||
async function save() {
|
||
if (!dialog.name.trim()) return ElMessage.warning('请输入角色名称')
|
||
if (!dialog.editingId && !dialog.code.trim()) return ElMessage.warning('请输入角色编码')
|
||
const payload = {
|
||
id: dialog.editingId,
|
||
name: dialog.name.trim(),
|
||
code: dialog.code.trim(),
|
||
remark: dialog.remark.trim() || undefined,
|
||
permissionCodes: treeCheckedCodes()
|
||
}
|
||
saving.value = true
|
||
try {
|
||
if (dialog.editingId) {
|
||
await request.put('/roles', payload)
|
||
ElMessage.success('保存成功')
|
||
} else {
|
||
await request.post('/roles', payload)
|
||
ElMessage.success('角色创建成功')
|
||
}
|
||
dialog.show = false
|
||
await loadRoles()
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
async function removeRole(row) {
|
||
try {
|
||
await ElMessageBox.confirm(`确定删除角色「${row.name}」?该角色下的用户将受影响。`, '删除确认', {
|
||
type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消'
|
||
})
|
||
} catch {
|
||
return
|
||
}
|
||
try {
|
||
await request.delete(`/roles/${row.id}`)
|
||
ElMessage.success('已删除')
|
||
await loadRoles()
|
||
} catch { /* 拦截器已提示 */ }
|
||
}
|
||
|
||
onMounted(async () => {
|
||
await Promise.all([loadRoles(), loadPerms()])
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.mb12 { margin-bottom: 12px; }
|
||
.perm-tree { border: 1px solid #ebeef5; border-radius: 4px; padding: 12px; max-height: 360px; overflow: auto; }
|
||
.perm-tip { color: #909399; font-size: 12px; margin-bottom: 8px; }
|
||
</style>
|