feat(需求): ①用户管理补「角色」筛选(前端+后端 role 参数) ②软件/数字人版本页补版本号搜索 ③全站 el-select 统一 filterable(分组/所属管理员/角色等大下拉可模糊搜索)
This commit is contained in:
@@ -13,6 +13,8 @@ export interface UserListParams {
|
||||
keyword?: string
|
||||
createdById?: number | null
|
||||
adminId?: number | null
|
||||
/** 角色过滤(''=全部)。 */
|
||||
role?: string
|
||||
}
|
||||
|
||||
/** 序列化到 GET /api/admin/users 的查询参数(Java snake_case)。 */
|
||||
@@ -23,6 +25,7 @@ export interface UserListQuery {
|
||||
search?: string
|
||||
created_by_id?: number
|
||||
admin_id?: number
|
||||
role?: string
|
||||
}
|
||||
|
||||
/** 用户列表项即 AdminUser(与 Java AdminUserItemVo 字段对齐)。 */
|
||||
@@ -67,6 +70,7 @@ export function toUserListQuery(params: UserListParams): UserListQuery {
|
||||
if (params.keyword) query.username = params.keyword
|
||||
if (params.createdById != null) query.created_by_id = params.createdById
|
||||
if (params.adminId != null) query.admin_id = params.adminId
|
||||
if (params.role) query.role = params.role
|
||||
return query
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ async function create(): Promise<void> {
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="operatorSuper" label="角色">
|
||||
<el-select v-model="form.role" style="width: 200px">
|
||||
<el-select filterable v-model="form.role" style="width: 200px">
|
||||
<el-option v-for="opt in ROLE_CHOICES" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -295,12 +295,12 @@ onMounted(loadMenus)
|
||||
<el-input v-model="menuForm.name" placeholder="如:用户管理" />
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单类型">
|
||||
<el-select v-model="menuForm.menuType" style="width: 100%" @change="onMenuTypeChange">
|
||||
<el-select filterable v-model="menuForm.menuType" style="width: 100%" @change="onMenuTypeChange">
|
||||
<el-option v-for="opt in MENU_TYPE_OPTIONS" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="父菜单">
|
||||
<el-select v-model="menuForm.parentId" clearable placeholder="无(一级菜单)" style="width: 100%">
|
||||
<el-select filterable v-model="menuForm.parentId" clearable placeholder="无(一级菜单)" style="width: 100%">
|
||||
<el-option
|
||||
v-for="node in parentOptions"
|
||||
:key="node.id"
|
||||
|
||||
@@ -139,10 +139,17 @@ onMounted(loadUsers)
|
||||
</div>
|
||||
<div v-if="isSuperAdmin" class="form-group" style="min-width: 160px">
|
||||
<label>所属管理员</label>
|
||||
<select v-model="filters.createdByAdminId">
|
||||
<option value="">全部</option>
|
||||
<option v-for="admin in admins" :key="admin.id" :value="admin.id">{{ admin.username }}</option>
|
||||
</select>
|
||||
<el-select v-model="filters.createdByAdminId" class="admin-filter" filterable clearable placeholder="全部" @change="search">
|
||||
<el-option v-for="admin in admins" :key="admin.id" :label="admin.username" :value="admin.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-group" style="min-width: 140px">
|
||||
<label>角色</label>
|
||||
<el-select v-model="filters.role" class="admin-filter" filterable clearable placeholder="全部角色" @change="search">
|
||||
<el-option label="超级管理员" value="super_admin" />
|
||||
<el-option label="管理员" value="admin" />
|
||||
<el-option label="普通账号" value="normal" />
|
||||
</el-select>
|
||||
</div>
|
||||
<button class="btn" type="button" @click="search">查询</button>
|
||||
</div>
|
||||
|
||||
@@ -7,18 +7,20 @@ export interface UserFilterState {
|
||||
keyword: string
|
||||
/** 按创建管理员过滤(可选)。 */
|
||||
createdByAdminId: number | null
|
||||
/** 角色过滤(''=全部;super_admin/admin/normal)。 */
|
||||
role: string
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export function createUserFilterState(): UserFilterState {
|
||||
return { keyword: '', createdByAdminId: null, page: USER_PAGE_MIN_PAGE, pageSize: USER_PAGE_DEFAULT_SIZE }
|
||||
return { keyword: '', createdByAdminId: null, role: '', page: USER_PAGE_MIN_PAGE, pageSize: USER_PAGE_DEFAULT_SIZE }
|
||||
}
|
||||
|
||||
/** 修改筛选条件时重置回第一页(避免停在可能越界的旧页)。 */
|
||||
export function withFilterReset(
|
||||
state: UserFilterState,
|
||||
next: Partial<Pick<UserFilterState, 'keyword' | 'createdByAdminId'>>,
|
||||
next: Partial<Pick<UserFilterState, 'keyword' | 'createdByAdminId' | 'role'>>,
|
||||
): UserFilterState {
|
||||
return { ...state, ...next, page: USER_PAGE_MIN_PAGE }
|
||||
}
|
||||
@@ -36,10 +38,13 @@ export function isFilterActive(state: UserFilterState): boolean {
|
||||
/** 页面状态 → 查询适配层参数(空关键字不下发)。 */
|
||||
export function toUserListParams(state: UserFilterState): UserListParams {
|
||||
const keyword = state.keyword.trim()
|
||||
return {
|
||||
const params: UserListParams = {
|
||||
page: state.page,
|
||||
pageSize: state.pageSize,
|
||||
keyword: keyword || undefined,
|
||||
createdById: state.createdByAdminId,
|
||||
}
|
||||
const role = (state.role || '').trim()
|
||||
if (role) params.role = role
|
||||
return params
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ onMounted(() => {
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="国家">
|
||||
<el-select v-model="createCountry" placeholder="请选择国家" clearable style="width: 100%">
|
||||
<el-select filterable v-model="createCountry" placeholder="请选择国家" clearable style="width: 100%">
|
||||
<el-option v-for="code in QUERY_ASIN_COUNTRIES" :key="code" :label="asinCountryLabel(code)" :value="code" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -461,7 +461,7 @@ onMounted(() => {
|
||||
<el-dialog v-model="importVisible" :title="importMode === 'add' ? '导入添加查询 ASIN' : '删除导入查询 ASIN'" width="520px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="分组(Excel 未提供时的兜底,可选)">
|
||||
<el-select v-model="importGroupId" placeholder="可不选" clearable style="width: 100%">
|
||||
<el-select filterable v-model="importGroupId" placeholder="可不选" clearable style="width: 100%">
|
||||
<el-option v-for="group in groups" :key="group.id" :label="group.groupName" :value="group.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -473,7 +473,7 @@ onMounted(() => {
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="国家">
|
||||
<el-select v-model="createCountry" placeholder="请选择国家" clearable style="width: 100%">
|
||||
<el-select filterable v-model="createCountry" placeholder="请选择国家" clearable style="width: 100%">
|
||||
<el-option v-for="code in COUNTRIES" :key="code" :label="asinCountryLabel(code)" :value="code" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -514,7 +514,7 @@ onMounted(() => {
|
||||
<el-dialog v-model="importVisible" :title="importMode === 'add' ? '导入最低价 ASIN' : '删除导入最低价 ASIN'" width="520px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="分组(必选)">
|
||||
<el-select v-model="importGroupId" placeholder="请选择分组" style="width: 100%">
|
||||
<el-select filterable v-model="importGroupId" placeholder="请选择分组" style="width: 100%">
|
||||
<el-option v-for="group in groups" :key="group.id" :label="group.groupName" :value="group.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -29,11 +29,18 @@ const items = ref<DigitalHumanVersion[]>([])
|
||||
/** 分页(对齐 admin.js:6311 pageSize=20 + 数字页码 + 跳转;Java 列表接口不分页,前端分页呈现)。 */
|
||||
const pageSize = 20
|
||||
const page = ref(1)
|
||||
/** 版本号搜索(客户端过滤)。 */
|
||||
const versionKeyword = ref('')
|
||||
const filteredItems = computed(() => {
|
||||
const kw = (versionKeyword.value || '').trim().toLowerCase()
|
||||
if (!kw) return items.value
|
||||
return items.value.filter((item) => `${item.version}`.toLowerCase().includes(kw))
|
||||
})
|
||||
const pagedItems = computed(() => {
|
||||
const start = (page.value - 1) * pageSize
|
||||
return items.value.slice(start, start + pageSize)
|
||||
return filteredItems.value.slice(start, start + pageSize)
|
||||
})
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(items.value.length / pageSize)))
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(filteredItems.value.length / pageSize)))
|
||||
const jumpPage = ref('')
|
||||
|
||||
const uploadVisible = ref(false)
|
||||
@@ -191,8 +198,11 @@ onMounted(load)
|
||||
<section class="panel-box">
|
||||
<div class="dh-head">
|
||||
<h3>版本列表</h3>
|
||||
<div class="dh-head-tools">
|
||||
<input v-model="versionKeyword" type="text" placeholder="搜索版本号" class="dh-keyword" />
|
||||
<button class="btn" type="button" @click="uploadVisible = true">上传新版本</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll digital-human-version-table-scroll">
|
||||
<table>
|
||||
@@ -242,13 +252,13 @@ onMounted(load)
|
||||
<td colspan="8" class="empty-tip">加载中...</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="8" class="empty-tip">暂无版本记录</td>
|
||||
<td colspan="8" class="empty-tip">{{ versionKeyword ? '暂无匹配版本' : '暂无版本记录' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="items.length > pageSize" class="pagination">
|
||||
<div v-if="filteredItems.length > pageSize" class="pagination">
|
||||
<button type="button" :disabled="page <= 1" @click="changePage(page - 1)">上一页</button>
|
||||
<span class="page-nums">
|
||||
<template v-for="n in totalPages" :key="n">
|
||||
@@ -322,6 +332,33 @@ h3 {
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.dh-head-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.dh-keyword {
|
||||
width: 200px;
|
||||
min-height: 42px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #cbd9e6;
|
||||
border-radius: 9px;
|
||||
background: #f8fbfd;
|
||||
color: #24384d;
|
||||
font-size: 13.5px;
|
||||
font-family: inherit;
|
||||
color-scheme: light;
|
||||
outline: none;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
|
||||
}
|
||||
.dh-keyword:hover {
|
||||
border-color: #9fb7cd;
|
||||
}
|
||||
.dh-keyword:focus {
|
||||
background: #ffffff;
|
||||
border-color: #5f85ad;
|
||||
box-shadow: 0 0 0 3px rgba(95, 133, 173, 0.16);
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
/** 软件版本管理页 · 像素复刻旧版 admin.html panel-version(自绘:面板头+上传新版本、版本列表、全量无分页同旧版)。
|
||||
* script 逻辑沿用现有 Vue 实现(上传弹窗成功留驻含链接)。 */
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { fetchSoftwareVersions, uploadSoftwareVersion } from './version-api.ts'
|
||||
import type { SoftwareVersionItem } from './version-dto.ts'
|
||||
@@ -10,6 +10,13 @@ import { isNonEmptyVersion } from './version-dto.ts'
|
||||
|
||||
const loading = ref(false)
|
||||
const items = ref<SoftwareVersionItem[]>([])
|
||||
/** 版本号搜索(客户端过滤)。 */
|
||||
const versionKeyword = ref('')
|
||||
const filteredItems = computed(() => {
|
||||
const kw = (versionKeyword.value || '').trim().toLowerCase()
|
||||
if (!kw) return items.value
|
||||
return items.value.filter((item) => `${item.version}`.toLowerCase().includes(kw))
|
||||
})
|
||||
|
||||
const uploadVisible = ref(false)
|
||||
const uploading = ref(false)
|
||||
@@ -83,8 +90,11 @@ onMounted(load)
|
||||
<section class="panel-box">
|
||||
<div class="version-head">
|
||||
<h3>版本列表</h3>
|
||||
<div class="version-head-tools">
|
||||
<input v-model="versionKeyword" type="text" placeholder="搜索版本号" class="version-keyword" />
|
||||
<button class="btn" type="button" @click="openUpload">上传新版本</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll version-table-scroll">
|
||||
<table>
|
||||
@@ -97,8 +107,8 @@ onMounted(load)
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-if="items.length">
|
||||
<tr v-for="row in items" :key="row.version + row.id">
|
||||
<template v-if="filteredItems.length">
|
||||
<tr v-for="row in filteredItems" :key="row.version + row.id">
|
||||
<td>{{ row.version }}</td>
|
||||
<td>
|
||||
<a v-if="row.fileUrl" class="link-cell" :href="row.fileUrl" target="_blank" rel="noopener" :title="row.fileUrl">{{ row.fileUrl }}</a>
|
||||
@@ -115,7 +125,7 @@ onMounted(load)
|
||||
<td colspan="4" class="empty-tip">加载中...</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="4" class="empty-tip">暂无版本记录</td>
|
||||
<td colspan="4" class="empty-tip">{{ versionKeyword ? '暂无匹配版本' : '暂无版本记录' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -174,6 +184,33 @@ h3 {
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.version-head-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.version-keyword {
|
||||
width: 200px;
|
||||
min-height: 42px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #cbd9e6;
|
||||
border-radius: 9px;
|
||||
background: #f8fbfd;
|
||||
color: #24384d;
|
||||
font-size: 13.5px;
|
||||
font-family: inherit;
|
||||
color-scheme: light;
|
||||
outline: none;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
|
||||
}
|
||||
.version-keyword:hover {
|
||||
border-color: #9fb7cd;
|
||||
}
|
||||
.version-keyword:focus {
|
||||
background: #ffffff;
|
||||
border-color: #5f85ad;
|
||||
box-shadow: 0 0 0 3px rgba(95, 133, 173, 0.16);
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -291,7 +291,7 @@ onMounted(() => {
|
||||
<el-dialog v-model="dialogVisible" :title="editingId == null ? '新增店铺' : '编辑店铺'" width="600px">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="所属分组" required>
|
||||
<el-select v-model="form.groupId" placeholder="选择分组">
|
||||
<el-select filterable v-model="form.groupId" placeholder="选择分组">
|
||||
<el-option v-for="group in groupOptions" :key="group.id" :label="group.groupName" :value="group.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
Reference in New Issue
Block a user