feat(需求): ①账户域菜单/分组补筛选(名称/组长/类型) + 分组管理移除「数据权限分组」摘要卡 ②店铺密钥列表补筛选(备注/紫鸟账号) ③店铺数据记录两级分页常显 ④全站分组筛选下拉支持模糊搜索(el-select filterable,样式对齐蓝白42px)
This commit is contained in:
@@ -7,8 +7,7 @@ import { ElMessage } from 'element-plus'
|
||||
import { useAdminSessionStore } from '@/stores/admin-session'
|
||||
import { openAdminConfirm } from '@/components/admin-confirm'
|
||||
import { deleteShopGroup, fetchShopGroups } from './shop-group-api'
|
||||
import { shopGroupSummary, type ShopGroupItem } from './shop-group-model'
|
||||
import { dedupeGroupSummaryOf } from '@/pages/asin/dedupe-total-model'
|
||||
import type { ShopGroupItem } from './shop-group-model'
|
||||
import GroupEditorDialog from './GroupEditorDialog.vue'
|
||||
|
||||
const session = useAdminSessionStore()
|
||||
@@ -16,17 +15,21 @@ const loading = ref(false)
|
||||
const rows = ref<ShopGroupItem[]>([])
|
||||
const editorVisible = ref(false)
|
||||
const editingGroup = ref<ShopGroupItem | null>(null)
|
||||
const summary = computed(() => shopGroupSummary(rows.value))
|
||||
const isSuperAdmin = computed(() => session.isSuperAdmin)
|
||||
const currentUserId = computed(() => session.user?.id ?? null)
|
||||
/** 顶部摘要 chips(对齐 admin.js renderShopManageGroupSummary;与去重汇总同构)。 */
|
||||
const groupChips = computed(() =>
|
||||
dedupeGroupSummaryOf(
|
||||
rows.value.map((g) => ({ id: g.id, name: g.name, leaderUserId: g.leaderUserId })),
|
||||
currentUserId.value,
|
||||
session.user?.role || '',
|
||||
),
|
||||
)
|
||||
/** 筛选:分组名称/组长 模糊过滤(用户要求账户域子菜单补筛选条件)。 */
|
||||
const filterKeyword = ref('')
|
||||
const filterLeader = ref('')
|
||||
const filteredRows = computed(() => {
|
||||
const kw = (filterKeyword.value || '').trim().toLowerCase()
|
||||
const ldr = (filterLeader.value || '').trim().toLowerCase()
|
||||
if (!kw && !ldr) return rows.value
|
||||
return rows.value.filter((g) => {
|
||||
const nameOk = !kw || (g.name || '').toLowerCase().includes(kw)
|
||||
const leaderOk = !ldr || (g.leaderUsername || '').toLowerCase().includes(ldr)
|
||||
return nameOk && leaderOk
|
||||
})
|
||||
})
|
||||
|
||||
function canEditOf(group: ShopGroupItem): boolean {
|
||||
if (isSuperAdmin.value) return true
|
||||
@@ -72,32 +75,23 @@ onMounted(loadGroups)
|
||||
|
||||
<template>
|
||||
<div class="groups-view">
|
||||
<section class="form-box">
|
||||
<div class="dedupe-group-access">
|
||||
<div class="dedupe-group-access-main">
|
||||
<span class="dedupe-group-access-title">数据权限分组</span>
|
||||
<div class="dedupe-group-summary">
|
||||
<template v-if="groupChips.kind === 'all'">
|
||||
<span class="dedupe-group-summary-chip">全部分组 · {{ groupChips.count }} 个</span>
|
||||
</template>
|
||||
<span v-else-if="groupChips.kind === 'none'" class="no-group">未加入分组</span>
|
||||
<template v-else>
|
||||
<span v-for="chip in groupChips.chips" :key="chip.name" class="dedupe-group-summary-chip" :title="chip.name">
|
||||
{{ chip.name }} · {{ chip.relation }}
|
||||
</span>
|
||||
<span v-if="groupChips.extra > 0" class="extra-groups">另 {{ groupChips.extra }} 个</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel-box">
|
||||
<div class="groups-head">
|
||||
<h3>分组列表</h3>
|
||||
<button class="btn" type="button" @click="openCreate">新建分组</button>
|
||||
</div>
|
||||
|
||||
<div class="form-row groups-filter-row">
|
||||
<div class="form-group" style="min-width: 200px">
|
||||
<label>分组名称</label>
|
||||
<input v-model="filterKeyword" type="text" placeholder="输入分组名称" @keyup.enter="filterKeyword = filterKeyword" />
|
||||
</div>
|
||||
<div class="form-group" style="min-width: 180px">
|
||||
<label>组长</label>
|
||||
<input v-model="filterLeader" type="text" placeholder="输入组长用户名" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -113,8 +107,8 @@ onMounted(loadGroups)
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-if="rows.length">
|
||||
<tr v-for="(group, index) in rows" :key="group.id">
|
||||
<template v-if="filteredRows.length">
|
||||
<tr v-for="(group, index) in filteredRows" :key="group.id">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ group.name }}</td>
|
||||
<td>{{ group.leaderUsername || '-' }}</td>
|
||||
@@ -140,7 +134,7 @@ onMounted(loadGroups)
|
||||
<td colspan="8" class="empty-tip">加载中...</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="8" class="empty-tip">暂无分组</td>
|
||||
<td colspan="8" class="empty-tip">{{ filterKeyword || filterLeader ? '暂无匹配分组' : '暂无分组' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -182,53 +176,53 @@ h3 {
|
||||
color: #24384d;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.dedupe-group-access {
|
||||
.groups-filter-row {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.groups-filter-row .form-group {
|
||||
flex: 1 1 200px;
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 14px 18px;
|
||||
}
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
margin-bottom: 0;
|
||||
padding: 0 0 6px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.dedupe-group-access-main {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.dedupe-group-access-title {
|
||||
flex: 0 0 auto;
|
||||
font-size: 14px;
|
||||
.form-group label {
|
||||
color: #5b6f83;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
color: #24384d;
|
||||
}
|
||||
.dedupe-group-summary {
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
color: #5b6f83;
|
||||
font-size: 13px;
|
||||
min-height: 42px;
|
||||
padding: 8px 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;
|
||||
}
|
||||
.dedupe-group-summary-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 220px;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid #d8e3ee;
|
||||
border-radius: 6px;
|
||||
background: #f8f9fc;
|
||||
color: #5b6f83;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
.form-group input:hover,
|
||||
.form-group select:hover {
|
||||
border-color: #9fb7cd;
|
||||
}
|
||||
.no-group,
|
||||
.extra-groups {
|
||||
color: #8293a5;
|
||||
.form-group input:focus,
|
||||
.form-group select:focus {
|
||||
background: #ffffff;
|
||||
border-color: #5f85ad;
|
||||
box-shadow: 0 0 0 3px rgba(95, 133, 173, 0.16);
|
||||
}
|
||||
.groups-head {
|
||||
display: flex;
|
||||
|
||||
@@ -29,6 +29,18 @@ interface MenuRow extends MenuManageNode {
|
||||
|
||||
const loading = ref(false)
|
||||
const rows = ref<MenuManageNode[]>([])
|
||||
/** 筛选:菜单名称/类型(用户要求账户域子菜单补筛选条件)。 */
|
||||
const filterName = ref('')
|
||||
const filterType = ref('')
|
||||
const filteredMenuRows = computed<MenuRow[]>(() => {
|
||||
const kw = (filterName.value || '').trim().toLowerCase()
|
||||
const type = filterType.value || ''
|
||||
return menuRows.value.filter((row) => {
|
||||
const nameOk = !kw || (row.name || '').toLowerCase().includes(kw)
|
||||
const typeOk = !type || row.menuType === type
|
||||
return nameOk && typeOk
|
||||
})
|
||||
})
|
||||
/** 展平视图:前序 + depth。 */
|
||||
const menuRows = computed<MenuRow[]>(() => {
|
||||
const out: MenuRow[] = []
|
||||
@@ -197,6 +209,21 @@ onMounted(loadMenus)
|
||||
</div>
|
||||
<p class="columns-drag-tip">拖动每行左侧的手柄可调整同级菜单的显示顺序,松开后自动保存。</p>
|
||||
|
||||
<div class="form-row menus-filter-row">
|
||||
<div class="form-group" style="min-width: 200px">
|
||||
<label>菜单名称</label>
|
||||
<input v-model="filterName" type="text" placeholder="输入菜单名称" />
|
||||
</div>
|
||||
<div class="form-group" style="min-width: 160px">
|
||||
<label>菜单类型</label>
|
||||
<select v-model="filterType">
|
||||
<option value="">全部</option>
|
||||
<option value="admin">后台</option>
|
||||
<option value="app">软件</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll columns-table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -211,9 +238,9 @@ onMounted(loadMenus)
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-if="menuRows.length">
|
||||
<template v-if="filteredMenuRows.length">
|
||||
<tr
|
||||
v-for="row in menuRows"
|
||||
v-for="row in filteredMenuRows"
|
||||
:key="row.id"
|
||||
:class="{ 'is-column-dragging': dragState?.id === row.id }"
|
||||
@dragover="onDragOver($event, row)"
|
||||
@@ -255,7 +282,7 @@ onMounted(loadMenus)
|
||||
<td colspan="7" class="empty-tip">加载中...</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="7" class="empty-tip">暂无菜单,请先在上方新增</td>
|
||||
<td colspan="7" class="empty-tip">{{ filterName || filterType ? '暂无匹配菜单' : '暂无菜单,请先在上方新增' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -327,6 +354,51 @@ h3 {
|
||||
font-size: 13px;
|
||||
color: #5b6f83;
|
||||
}
|
||||
.menus-filter-row {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 14px 18px;
|
||||
}
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.form-group label {
|
||||
color: #5b6f83;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
min-width: 0;
|
||||
min-height: 42px;
|
||||
padding: 8px 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;
|
||||
}
|
||||
.form-group input:hover,
|
||||
.form-group select:hover {
|
||||
border-color: #9fb7cd;
|
||||
}
|
||||
.form-group input:focus,
|
||||
.form-group select: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;
|
||||
|
||||
Reference in New Issue
Block a user