191 lines
5.1 KiB
Vue
191 lines
5.1 KiB
Vue
|
|
<script setup lang="ts">
|
|||
|
|
import { api } from '@/utils/request'
|
|||
|
|
import { useUserManageStore, type ScopeRecord } from '@/stores/user-manage'
|
|||
|
|
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
|||
|
|
|
|||
|
|
const props = defineProps<{
|
|||
|
|
open: boolean
|
|||
|
|
userId: number | null
|
|||
|
|
username: string
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
'update:open': [value: boolean]
|
|||
|
|
saved: []
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const store = useUserManageStore()
|
|||
|
|
|
|||
|
|
const loading = ref(false)
|
|||
|
|
const saving = ref(false)
|
|||
|
|
const scopes = ref<ScopeRecord[]>([])
|
|||
|
|
|
|||
|
|
// 基础数据列表
|
|||
|
|
const companies = ref<{ id: number; name: string; label: string }[]>([])
|
|||
|
|
const platforms = ref<{ id: number; developer_id: number }[]>([])
|
|||
|
|
const stores = ref<{ id: number; company_id: number; platform_id: number; name: string; label: string }[]>([])
|
|||
|
|
|
|||
|
|
const scopeTypeOptions = [
|
|||
|
|
{ value: 'company', label: '公司' },
|
|||
|
|
{ value: 'platform', label: '平台' },
|
|||
|
|
{ value: 'store', label: '店铺' },
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
function entityOptions(scopeType: string) {
|
|||
|
|
if (scopeType === 'company') {
|
|||
|
|
return companies.value.map((c) => ({ value: c.id, label: c.label || c.name }))
|
|||
|
|
}
|
|||
|
|
if (scopeType === 'platform') {
|
|||
|
|
return platforms.value.map((p) => ({ value: p.id, label: `平台 #${p.id}` }))
|
|||
|
|
}
|
|||
|
|
if (scopeType === 'store') {
|
|||
|
|
return stores.value.map((s) => ({ value: s.id, label: s.label || s.name }))
|
|||
|
|
}
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function handleClose() {
|
|||
|
|
emit('update:open', false)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function addRow() {
|
|||
|
|
scopes.value.push({ scope_type: 'company', scope_id: 0 })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function removeRow(index: number) {
|
|||
|
|
scopes.value.splice(index, 1)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function handleTypeChange(index: number) {
|
|||
|
|
const row = scopes.value[index]
|
|||
|
|
if (row) row.scope_id = 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadData() {
|
|||
|
|
if (!props.userId) return
|
|||
|
|
loading.value = true
|
|||
|
|
try {
|
|||
|
|
const [scopeData] = await Promise.allSettled([
|
|||
|
|
store.fetchUserDataScope(props.userId),
|
|||
|
|
loadEntities(),
|
|||
|
|
])
|
|||
|
|
if (scopeData.status === 'fulfilled') {
|
|||
|
|
scopes.value = scopeData.value.scopes.map((s) => ({
|
|||
|
|
scope_type: s.scope_type,
|
|||
|
|
scope_id: s.scope_id,
|
|||
|
|
}))
|
|||
|
|
} else {
|
|||
|
|
scopes.value = []
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadEntities() {
|
|||
|
|
try {
|
|||
|
|
const [c, p, s] = await Promise.all([
|
|||
|
|
api.get<typeof companies.value>('/api/v1/companies'),
|
|||
|
|
api.get<typeof platforms.value>('/api/v1/platforms'),
|
|||
|
|
api.get<typeof stores.value>('/api/v1/stores'),
|
|||
|
|
])
|
|||
|
|
companies.value = c
|
|||
|
|
platforms.value = p
|
|||
|
|
stores.value = s
|
|||
|
|
} catch (err: unknown) {
|
|||
|
|
const msg = err instanceof Error ? err.message : '加载基础数据失败'
|
|||
|
|
message.error(msg)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function handleSave() {
|
|||
|
|
if (!props.userId) return
|
|||
|
|
// 过滤掉未选择实体的行,并去重
|
|||
|
|
const seen = new Set<string>()
|
|||
|
|
const validScopes = scopes.value
|
|||
|
|
.filter((s) => {
|
|||
|
|
if (s.scope_id <= 0) return false
|
|||
|
|
const key = `${s.scope_type}:${s.scope_id}`
|
|||
|
|
if (seen.has(key)) return false
|
|||
|
|
seen.add(key)
|
|||
|
|
return true
|
|||
|
|
})
|
|||
|
|
.map(({ scope_type, scope_id }) => ({ scope_type, scope_id }))
|
|||
|
|
saving.value = true
|
|||
|
|
try {
|
|||
|
|
await store.saveUserDataScope(props.userId, validScopes)
|
|||
|
|
message.success('数据范围保存成功')
|
|||
|
|
emit('saved')
|
|||
|
|
handleClose()
|
|||
|
|
} catch (err: unknown) {
|
|||
|
|
const msg = err instanceof Error ? err.message : '保存失败'
|
|||
|
|
message.error(msg)
|
|||
|
|
} finally {
|
|||
|
|
saving.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => props.open,
|
|||
|
|
(val) => {
|
|||
|
|
if (val) {
|
|||
|
|
loadData()
|
|||
|
|
} else {
|
|||
|
|
scopes.value = []
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{ immediate: true },
|
|||
|
|
)
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<a-modal
|
|||
|
|
:open="open"
|
|||
|
|
:title="`数据范围 - ${username}`"
|
|||
|
|
width="640px"
|
|||
|
|
:confirm-loading="saving"
|
|||
|
|
@cancel="handleClose"
|
|||
|
|
@ok="handleSave"
|
|||
|
|
>
|
|||
|
|
<a-spin :spinning="loading">
|
|||
|
|
<div class="mb-3">
|
|||
|
|
<a-button type="dashed" block @click="addRow">
|
|||
|
|
<template #icon><PlusOutlined /></template>
|
|||
|
|
添加数据范围
|
|||
|
|
</a-button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-if="scopes.length === 0 && !loading" class="text-center text-gray-400 py-4">
|
|||
|
|
暂无数据范围配置,该用户将无法访问任何业务数据
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div
|
|||
|
|
v-for="(scope, index) in scopes"
|
|||
|
|
:key="index"
|
|||
|
|
class="flex gap-2 mb-2 items-center"
|
|||
|
|
>
|
|||
|
|
<a-select
|
|||
|
|
:value="scope.scope_type"
|
|||
|
|
:options="scopeTypeOptions"
|
|||
|
|
style="width: 120px"
|
|||
|
|
@change="(val: unknown) => { scope.scope_type = val as ScopeRecord['scope_type']; handleTypeChange(index) }"
|
|||
|
|
/>
|
|||
|
|
<a-select
|
|||
|
|
:value="scope.scope_id || undefined"
|
|||
|
|
:options="entityOptions(scope.scope_type)"
|
|||
|
|
placeholder="选择实体"
|
|||
|
|
show-search
|
|||
|
|
:filter-option="(input: string, option: unknown) =>
|
|||
|
|
((option as { label: string }).label ?? '').toLowerCase().includes(input.toLowerCase())
|
|||
|
|
"
|
|||
|
|
style="flex: 1"
|
|||
|
|
@change="(val: unknown) => { scope.scope_id = (val as number) ?? 0 }"
|
|||
|
|
/>
|
|||
|
|
<a-button type="text" danger size="small" @click="removeRow(index)">
|
|||
|
|
<template #icon><DeleteOutlined /></template>
|
|||
|
|
</a-button>
|
|||
|
|
</div>
|
|||
|
|
</a-spin>
|
|||
|
|
</a-modal>
|
|||
|
|
</template>
|