feat(密钥管理): 按数据权限分组隔离与筛选
- 后台密钥列表改为按 biz_shop_manage_group 分组:主管只看自己带的分组成员,超管看全量可按 group_id 筛选 - 行数据补「所属分组」名称(批量查询);列表接口带回分组筛选项 - 修复超管筛选不生效:前端筛选参数统一 snake_case(camel 被后端静默忽略) - 列表列/筛选项文案由「所属管理员」改为「分组」
This commit is contained in:
@@ -19,8 +19,8 @@ export interface AdminUserSecretModule {
|
||||
export interface AdminUserSecretRow {
|
||||
userId: number
|
||||
username: string
|
||||
createdById: number | null
|
||||
createdByUsername: string
|
||||
/** 所属数据权限分组名(可能多个)。 */
|
||||
groups: string[]
|
||||
similarAsin: AdminUserSecretModule
|
||||
appearancePatent: AdminUserSecretModule
|
||||
proxy: AdminUserSecretModule
|
||||
@@ -34,26 +34,31 @@ export interface AdminUserSecretPage {
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
/** 分组筛选项(超管=全部;主管=自己带的分组)。 */
|
||||
groupOptions?: GroupOption[]
|
||||
}
|
||||
|
||||
/** 管理员下拉项(超管筛选用)。 */
|
||||
export interface AdminOption {
|
||||
export interface GroupOption {
|
||||
id: number
|
||||
username: string
|
||||
groupName: string
|
||||
}
|
||||
|
||||
export interface UserSecretQuery {
|
||||
keyword?: string
|
||||
checkStatus?: string
|
||||
/** 按创建人筛选(仅超管生效)。 */
|
||||
createdById?: number
|
||||
/** 按数据权限分组筛选(仅超管生效);后端参数为 snake_case。 */
|
||||
groupId?: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
/** 分页查询用户密钥(一行一用户):GET /api/admin/user-secrets */
|
||||
/** 分页查询用户密钥(一行一用户):GET /api/admin/user-secrets(筛选参数必须 snake_case,camel 会被后端静默忽略)。 */
|
||||
export async function fetchUserSecretList(params: UserSecretQuery): Promise<AdminUserSecretPage> {
|
||||
const { data } = await http.get('/api/admin/user-secrets', { params })
|
||||
const query: Record<string, string | number> = { page: params.page, pageSize: params.pageSize }
|
||||
if (params.keyword) query.keyword = params.keyword
|
||||
if (params.checkStatus) query.checkStatus = params.checkStatus
|
||||
if (params.groupId) query.group_id = params.groupId
|
||||
const { data } = await http.get('/api/admin/user-secrets', { params: query })
|
||||
return unwrap<AdminUserSecretPage>(data)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,15 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
import OldPagination from '@/components/OldPagination.vue'
|
||||
import { useAdminSessionStore } from '@/stores/admin-session'
|
||||
import { fetchUserList } from '@/api/users'
|
||||
import {
|
||||
checkUserSecret,
|
||||
deleteUserSecret,
|
||||
fetchUserSecretList,
|
||||
type AdminUserSecretModule,
|
||||
type AdminUserSecretRow,
|
||||
type GroupOption,
|
||||
} from '@/api/user-secrets'
|
||||
|
||||
const session = useAdminSessionStore()
|
||||
const loading = ref(false)
|
||||
const rows = ref<AdminUserSecretRow[]>([])
|
||||
const total = ref(0)
|
||||
@@ -21,9 +19,9 @@ const page = ref(1)
|
||||
const pageSize = ref(15)
|
||||
const keyword = ref('')
|
||||
const statusFilter = ref('')
|
||||
const createdByIdFilter = ref<number | null>(null)
|
||||
/** 管理员下拉(仅超管可见/加载)。 */
|
||||
const adminOptions = ref<Array<{ id: number; username: string }>>([])
|
||||
const groupFilter = ref<number | null>(null)
|
||||
/** 分组下拉(超管=全部;主管=自己带的分组,由列表接口带回)。 */
|
||||
const groupOptions = ref<GroupOption[]>([])
|
||||
/** 正在检测的行 userId,用于按钮 loading 态。 */
|
||||
const checkingId = ref<number | null>(null)
|
||||
|
||||
@@ -91,28 +89,19 @@ function rowStatusTooltip(row: AdminUserSecretRow) {
|
||||
return parts.join(';') || '暂无检测记录'
|
||||
}
|
||||
|
||||
async function loadAdminOptions() {
|
||||
if (!session.isSuperAdmin) return
|
||||
try {
|
||||
const result = await fetchUserList({ page: 1, pageSize: 999, role: 'admin' })
|
||||
adminOptions.value = result.admins || []
|
||||
} catch (error) {
|
||||
console.warn('[user-secrets] 管理员下拉加载失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await fetchUserSecretList({
|
||||
keyword: keyword.value.trim() || undefined,
|
||||
checkStatus: statusFilter.value || undefined,
|
||||
createdById: createdByIdFilter.value || undefined,
|
||||
groupId: groupFilter.value || undefined,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
})
|
||||
rows.value = result?.items || []
|
||||
total.value = Number(result?.total || 0)
|
||||
groupOptions.value = result?.groupOptions || []
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '加载失败')
|
||||
} finally {
|
||||
@@ -128,7 +117,7 @@ function search() {
|
||||
function reset() {
|
||||
keyword.value = ''
|
||||
statusFilter.value = ''
|
||||
createdByIdFilter.value = null
|
||||
groupFilter.value = null
|
||||
page.value = 1
|
||||
load()
|
||||
}
|
||||
@@ -183,10 +172,7 @@ function changeSize(size: number) {
|
||||
load()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadAdminOptions()
|
||||
load()
|
||||
})
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -207,11 +193,11 @@ onMounted(() => {
|
||||
<option v-for="option in STATUS_OPTIONS" :key="option.value" :value="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" v-if="session.isSuperAdmin" style="min-width: 170px">
|
||||
<label>所属管理员</label>
|
||||
<select v-model="createdByIdFilter">
|
||||
<option :value="null">全部管理员</option>
|
||||
<option v-for="admin in adminOptions" :key="admin.id" :value="admin.id">{{ admin.username }}</option>
|
||||
<div class="form-group" v-if="groupOptions.length" style="min-width: 170px">
|
||||
<label>分组</label>
|
||||
<select v-model="groupFilter">
|
||||
<option :value="null">全部分组</option>
|
||||
<option v-for="group in groupOptions" :key="group.id" :value="group.id">{{ group.groupName }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -229,7 +215,7 @@ onMounted(() => {
|
||||
<tr>
|
||||
<th style="width: 58px">序号</th>
|
||||
<th style="width: 260px">用户</th>
|
||||
<th style="width: 130px">所属管理员</th>
|
||||
<th style="width: 130px">分组</th>
|
||||
<th style="width: 230px">货源查询密钥</th>
|
||||
<th style="width: 230px">外观专利密钥</th>
|
||||
<th style="width: 230px">代理设置</th>
|
||||
@@ -245,7 +231,7 @@ onMounted(() => {
|
||||
<span class="user-name">{{ row.username || '—' }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="creator-name">{{ row.createdByUsername || (row.createdById ? `UID ${row.createdById}` : '—') }}</span>
|
||||
<span class="creator-name">{{ row.groups?.length ? row.groups.join('、') : '—' }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="module-cell">
|
||||
@@ -296,7 +282,7 @@ onMounted(() => {
|
||||
<td colspan="8" class="empty-tip">加载中...</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="8" class="empty-tip">{{ keyword || statusFilter || createdByIdFilter ? '暂无匹配记录' : '暂无用户密钥记录' }}</td>
|
||||
<td colspan="8" class="empty-tip">{{ keyword || statusFilter || groupFilter ? '暂无匹配记录' : '暂无用户密钥记录' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user