fix(security): 角色资源树回显防权限放大 + 401 刷新防死循环
- assign-resources-form:只保存叶子键 getCheckedKeys(true),回显 setCheckedKeys(leafOnly) - http.ts:_retry 标记只重放一次 + 排除 login/refresh Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
23e228783c
commit
7ae220581a
@ -65,9 +65,12 @@ export class HttpClient {
|
|||||||
return Promise.reject(new ApiError(body.code, body.message))
|
return Promise.reject(new ApiError(body.code, body.message))
|
||||||
},
|
},
|
||||||
async (error: AxiosError) => {
|
async (error: AxiosError) => {
|
||||||
const config = error.config
|
const config = error.config as (AxiosRequestConfig & { _retry?: boolean }) | undefined
|
||||||
// 401 自动刷新并重放一次
|
const url = config?.url ?? ''
|
||||||
if (error.response?.status === 401 && config) {
|
const isAuthEndpoint = url.includes('/auth/login') || url.includes('/auth/refresh')
|
||||||
|
// 401 自动刷新并重放一次(登录/刷新接口自身 401 不重放,_retry 防死循环)
|
||||||
|
if (error.response?.status === 401 && config && !config._retry && !isAuthEndpoint) {
|
||||||
|
config._retry = true
|
||||||
try {
|
try {
|
||||||
const token = await this.refresh()
|
const token = await this.refresh()
|
||||||
config.headers = config.headers ?? {}
|
config.headers = config.headers ?? {}
|
||||||
|
|||||||
@ -7,13 +7,12 @@
|
|||||||
node-key="code"
|
node-key="code"
|
||||||
show-checkbox
|
show-checkbox
|
||||||
:props="{ label: 'name', children: 'children' }"
|
:props="{ label: 'name', children: 'children' }"
|
||||||
:default-checked-keys="assigned ?? []"
|
|
||||||
default-expand-all
|
default-expand-all
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
import { useRequest } from '@/composables/useRequest'
|
import { useRequest } from '@/composables/useRequest'
|
||||||
import { listResources } from '@/api/resource'
|
import { listResources } from '@/api/resource'
|
||||||
import { listAssignedResources } from '@/api/role'
|
import { listAssignedResources } from '@/api/role'
|
||||||
@ -25,8 +24,8 @@ defineOptions({ name: 'AssignResourcesForm' })
|
|||||||
const props = defineProps<{ roleCode: string }>()
|
const props = defineProps<{ roleCode: string }>()
|
||||||
|
|
||||||
interface TreeApi {
|
interface TreeApi {
|
||||||
getCheckedKeys: () => (string | number)[]
|
getCheckedKeys: (leafOnly?: boolean) => (string | number)[]
|
||||||
getHalfCheckedKeys: () => (string | number)[]
|
setCheckedKeys: (keys: (string | number)[], leafOnly?: boolean) => void
|
||||||
}
|
}
|
||||||
const treeRef = ref<TreeApi>()
|
const treeRef = ref<TreeApi>()
|
||||||
|
|
||||||
@ -36,10 +35,18 @@ const { data: assigned } = useRequest((signal: AbortSignal) => listAssignedResou
|
|||||||
const tree = computed(() => buildTree(resourceList.value ?? []) as TreeNode<Resource>[])
|
const tree = computed(() => buildTree(resourceList.value ?? []) as TreeNode<Resource>[])
|
||||||
const ready = computed(() => resourceList.value !== undefined && assigned.value !== undefined)
|
const ready = computed(() => resourceList.value !== undefined && assigned.value !== undefined)
|
||||||
|
|
||||||
|
// 回显:仅叶子节点勾选,父节点由叶子自动推导,避免 default-checked-keys 级联全选
|
||||||
|
watch(ready, async (r) => {
|
||||||
|
if (r) {
|
||||||
|
await nextTick()
|
||||||
|
treeRef.value?.setCheckedKeys(assigned.value ?? [], true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function submit(): { resourceCodes: string[] } {
|
function submit(): { resourceCodes: string[] } {
|
||||||
const checked = treeRef.value?.getCheckedKeys() ?? []
|
// 只保存叶子节点:半选父节点由叶子推导,避免回显级联造成权限放大
|
||||||
const half = treeRef.value?.getHalfCheckedKeys() ?? []
|
const checked = treeRef.value?.getCheckedKeys(true) ?? []
|
||||||
return { resourceCodes: [...new Set([...checked, ...half].map(String))] }
|
return { resourceCodes: [...new Set(checked.map(String))] }
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ submit })
|
defineExpose({ submit })
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -68,15 +68,13 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
rules: [
|
rules: ['primary', 'success', 'warning', 'danger', 'info'].flatMap((color) => [
|
||||||
...['primary', 'success', 'warning', 'danger', 'info'].flatMap((color) => [
|
|
||||||
[`bg-${color}-light-3`, { 'background-color': `var(--wm-color-${color}-light-3)` }],
|
[`bg-${color}-light-3`, { 'background-color': `var(--wm-color-${color}-light-3)` }],
|
||||||
[`bg-${color}-light-5`, { 'background-color': `var(--wm-color-${color}-light-5)` }],
|
[`bg-${color}-light-5`, { 'background-color': `var(--wm-color-${color}-light-5)` }],
|
||||||
[`bg-${color}-light-7`, { 'background-color': `var(--wm-color-${color}-light-7)` }],
|
[`bg-${color}-light-7`, { 'background-color': `var(--wm-color-${color}-light-7)` }],
|
||||||
[`bg-${color}-light-9`, { 'background-color': `var(--wm-color-${color}-light-9)` }],
|
[`bg-${color}-light-9`, { 'background-color': `var(--wm-color-${color}-light-9)` }],
|
||||||
[`text-${color}-dark-2`, { color: `var(--wm-color-${color}-dark-2)` }],
|
[`text-${color}-dark-2`, { color: `var(--wm-color-${color}-dark-2)` }],
|
||||||
]),
|
]),
|
||||||
],
|
|
||||||
|
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
// 布局
|
// 布局
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user