feat: 全局跨域公司选择器 + 权限页crossDomain开关
- 新增CompanySelector组件: 仅crossDomain权限或SUPER_ADMIN可见 - 权限分配页: 全局系统特权下新增"允许全局跨域"操作权限checkbox - 入库汇总/出库记录搜索栏嵌入CompanySelector - PermissionConfig: transformData识别crossDomain为操作权限码
This commit is contained in:
55
inventory-web/src/components/CompanySelector.vue
Normal file
55
inventory-web/src/components/CompanySelector.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<el-select
|
||||||
|
v-if="hasCrossDomain"
|
||||||
|
:model-value="modelValue"
|
||||||
|
placeholder="全部部门/公司"
|
||||||
|
clearable
|
||||||
|
style="width: 180px;"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
@change="$emit('change', $event)"
|
||||||
|
>
|
||||||
|
<el-option label="全部部门/公司" value="" />
|
||||||
|
<el-option
|
||||||
|
v-for="c in companies"
|
||||||
|
:key="c"
|
||||||
|
:label="c"
|
||||||
|
:value="c"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { getFilterOptions } from '@/api/inbound/buy'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
'update:modelValue': [value: string]
|
||||||
|
change: [value: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const companies = ref<string[]>([])
|
||||||
|
|
||||||
|
const hasCrossDomain = computed(() => {
|
||||||
|
const role = (userStore.role || '').toUpperCase()
|
||||||
|
if (role === 'SUPER_ADMIN') return true
|
||||||
|
return userStore.hasPermission('crossDomain')
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!hasCrossDomain.value) return
|
||||||
|
try {
|
||||||
|
const res: any = await getFilterOptions()
|
||||||
|
if (res.code === 200) {
|
||||||
|
companies.value = res.data.companies || []
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取公司列表失败', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<div class="filter-container">
|
<div class="filter-container">
|
||||||
|
<CompanySelector v-model="listQuery.company" @change="fetchData" style="margin-right: 10px;" />
|
||||||
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="listQuery.keyword"
|
v-model="listQuery.keyword"
|
||||||
placeholder="请输入关键词"
|
placeholder="请输入关键词"
|
||||||
@ -135,6 +137,7 @@ import { ref, onMounted, reactive, onBeforeUnmount } from 'vue'
|
|||||||
import { getOutboundList } from '@/api/outbound'
|
import { getOutboundList } from '@/api/outbound'
|
||||||
import { Picture } from '@element-plus/icons-vue'
|
import { Picture } from '@element-plus/icons-vue'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import CompanySelector from '@/components/CompanySelector.vue'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
@ -200,7 +203,8 @@ const listQuery = reactive({
|
|||||||
limit: 10,
|
limit: 10,
|
||||||
keyword: '',
|
keyword: '',
|
||||||
search_type: 'all',
|
search_type: 'all',
|
||||||
dateRange: []
|
dateRange: [],
|
||||||
|
company: '' as string
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="userStore.hasPermission('inbound_summary:view')" class="app-container">
|
<div v-if="userStore.hasPermission('inbound_summary:view')" class="app-container">
|
||||||
<div class="filter-container">
|
<div class="filter-container">
|
||||||
|
<CompanySelector v-model="listQuery.company" @change="handleFilter" style="margin-right: 10px;" />
|
||||||
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="listQuery.keyword"
|
v-model="listQuery.keyword"
|
||||||
placeholder="SKU / 名称 / 规格 / 批次号 / 来源"
|
placeholder="SKU / 名称 / 规格 / 批次号 / 来源"
|
||||||
@ -107,6 +109,7 @@ import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
|
|||||||
import { getInboundSummaryList, submitExportTask, checkExportStatus } from '@/api/inbound/inbound_summary'
|
import { getInboundSummaryList, submitExportTask, checkExportStatus } from '@/api/inbound/inbound_summary'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { ElMessage, ElLoading } from 'element-plus'
|
import { ElMessage, ElLoading } from 'element-plus'
|
||||||
|
import CompanySelector from '@/components/CompanySelector.vue'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
@ -120,7 +123,8 @@ const listQuery = reactive({
|
|||||||
per_page: 20, // 默认每页20
|
per_page: 20, // 默认每页20
|
||||||
keyword: '',
|
keyword: '',
|
||||||
source_type: '',
|
source_type: '',
|
||||||
dateRange: null as any
|
dateRange: null as any,
|
||||||
|
company: '' as string
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@ -132,7 +136,8 @@ const fetchData = async () => {
|
|||||||
keyword: listQuery.keyword,
|
keyword: listQuery.keyword,
|
||||||
source_type: listQuery.source_type,
|
source_type: listQuery.source_type,
|
||||||
start_date: listQuery.dateRange ? listQuery.dateRange[0] : null,
|
start_date: listQuery.dateRange ? listQuery.dateRange[0] : null,
|
||||||
end_date: listQuery.dateRange ? listQuery.dateRange[1] : null
|
end_date: listQuery.dateRange ? listQuery.dateRange[1] : null,
|
||||||
|
company: listQuery.company || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await getInboundSummaryList(params)
|
const res = await getInboundSummaryList(params)
|
||||||
@ -175,7 +180,8 @@ const handleExport = () => {
|
|||||||
keyword: listQuery.keyword,
|
keyword: listQuery.keyword,
|
||||||
source_type: listQuery.source_type,
|
source_type: listQuery.source_type,
|
||||||
start_date: listQuery.dateRange ? listQuery.dateRange[0] : null,
|
start_date: listQuery.dateRange ? listQuery.dateRange[0] : null,
|
||||||
end_date: listQuery.dateRange ? listQuery.dateRange[1] : null
|
end_date: listQuery.dateRange ? listQuery.dateRange[1] : null,
|
||||||
|
company: listQuery.company || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadingInstance = ElLoading.service({
|
const loadingInstance = ElLoading.service({
|
||||||
|
|||||||
@ -120,11 +120,13 @@
|
|||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-if="row.operationCode"
|
v-if="row.operationCode"
|
||||||
v-model="row.hasWrite"
|
v-model="row.hasWrite"
|
||||||
:disabled="!row.hasRead"
|
:disabled="!row.hasRead && row.operationCode !== 'crossDomain'"
|
||||||
@change="(val) => handleWriteChange(val, row)"
|
@change="(val) => handleWriteChange(val, row)"
|
||||||
class="custom-checkbox"
|
class="custom-checkbox"
|
||||||
>
|
>
|
||||||
<span :class="{ 'text-active': row.hasWrite, 'text-disabled': !row.hasRead }">可编辑 (Write)</span>
|
<span :class="{ 'text-active': row.hasWrite, 'text-disabled': !row.hasRead && row.operationCode !== 'crossDomain' }">
|
||||||
|
{{ row.operationCode === 'crossDomain' ? '允许全局跨域' : '可编辑 (Write)' }}
|
||||||
|
</span>
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
<span v-else class="text-gray">-</span>
|
<span v-else class="text-gray">-</span>
|
||||||
</div>
|
</div>
|
||||||
@ -239,10 +241,16 @@ const fetchTree = async () => {
|
|||||||
code: 'global_privileges',
|
code: 'global_privileges',
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
id: 999992,
|
||||||
|
name: '全局跨域访问',
|
||||||
|
code: 'crossDomain',
|
||||||
|
type: 'element'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 999991,
|
id: 999991,
|
||||||
name: '跨组织/跨区域数据查询',
|
name: '跨组织/跨区域数据查询',
|
||||||
code: 'global:cross_company_op', // 加上 _op 让它显示在操作权限列
|
code: 'global:cross_company_op',
|
||||||
type: 'element'
|
type: 'element'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -265,12 +273,16 @@ const transformData = (nodes: any[]): PermissionNode[] => {
|
|||||||
// 找出所有子节点中的 element
|
// 找出所有子节点中的 element
|
||||||
const allChildren = node.children || []
|
const allChildren = node.children || []
|
||||||
|
|
||||||
// 1. 找操作列 (作为 Write 权限)
|
// 1. 找操作列 (作为 Write 权限) — 含 crossDomain 等全局权限码
|
||||||
const opNode = allChildren.find((c: any) => c.type === 'element' && (c.code === 'operation' || c.code.endsWith(':operation') || c.code.endsWith('_op')))
|
const opNode = allChildren.find((c: any) => c.type === 'element' && (
|
||||||
|
c.code === 'operation' || c.code.endsWith(':operation') || c.code.endsWith('_op') || c.code === 'crossDomain'
|
||||||
|
))
|
||||||
const operationCode = opNode ? opNode.code : null
|
const operationCode = opNode ? opNode.code : null
|
||||||
|
|
||||||
// 2. 找普通列 (作为字段权限)
|
// 2. 找普通列 (作为字段权限) — 排除操作权限码
|
||||||
const columns = allChildren.filter((c: any) => c.type === 'element' && c.code !== 'operation' && !c.code.endsWith('_op') && !c.code.endsWith(':operation'))
|
const columns = allChildren.filter((c: any) => c.type === 'element'
|
||||||
|
&& c.code !== 'operation' && !c.code.endsWith('_op') && !c.code.endsWith(':operation')
|
||||||
|
&& c.code !== 'crossDomain')
|
||||||
|
|
||||||
// 3. 找子菜单 (如果有)
|
// 3. 找子菜单 (如果有)
|
||||||
const subMenus = allChildren.filter((c: any) => c.type === 'menu')
|
const subMenus = allChildren.filter((c: any) => c.type === 'menu')
|
||||||
@ -307,6 +319,7 @@ const handleRoleSelect = async (roleCode: string) => {
|
|||||||
const res: any = await getRolePermissions(roleCode)
|
const res: any = await getRolePermissions(roleCode)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const perms = new Set([...(res.data.menus || []), ...(res.data.elements || [])])
|
const perms = new Set([...(res.data.menus || []), ...(res.data.elements || [])])
|
||||||
|
console.log('[权限加载] SUPERVISOR crossDomain 在权限中:', perms.has('crossDomain'))
|
||||||
// 递归设置表格每一行的状态
|
// 递归设置表格每一行的状态
|
||||||
setRowStatus(tableData.value, perms)
|
setRowStatus(tableData.value, perms)
|
||||||
}
|
}
|
||||||
@ -452,9 +465,11 @@ const handleSave = async () => {
|
|||||||
permissions.push(row.code)
|
permissions.push(row.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 操作权限 (只有当页面可读时才有效)
|
// 2. 操作权限 (crossDomain 不依赖页面可读)
|
||||||
if (row.hasRead && row.hasWrite && row.operationCode) {
|
if (row.hasWrite && row.operationCode) {
|
||||||
permissions.push(row.operationCode)
|
if (row.operationCode === 'crossDomain' || row.hasRead) {
|
||||||
|
permissions.push(row.operationCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 字段权限 (只有当页面可读时才有效)
|
// 3. 字段权限 (只有当页面可读时才有效)
|
||||||
@ -471,6 +486,11 @@ const handleSave = async () => {
|
|||||||
|
|
||||||
collectPermissions(tableData.value)
|
collectPermissions(tableData.value)
|
||||||
|
|
||||||
|
// [调试] 打印 crossDomain 是否在提交列表中
|
||||||
|
console.log('[权限保存] 总权限数:', permissions.length)
|
||||||
|
console.log('[权限保存] crossDomain 在提交列表中:', permissions.includes('crossDomain'))
|
||||||
|
console.log('[权限保存] global:cross_company_op 在提交列表中:', permissions.includes('global:cross_company_op'))
|
||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await saveRolePermissions({
|
const res: any = await saveRolePermissions({
|
||||||
|
|||||||
Reference in New Issue
Block a user