Compare commits

...
3 Commits
Author SHA1 Message Date
wc24 0b75f8c6f2 fix(filter): 用户搜索/改筛选时清掉 relation 跳转的隐形 _id pin
跳转用 ?filter={_id} 锁单条,但 _id 不在筛选抽屉里、且抽屉 confirm 会把它原样带回,
导致用户再搜索/筛选时被看不见的 _id 一直 AND 着、结果莫名为空。
onSearch + handleFilterConfirm 现在都剥掉 _id(重置本就清空)。
2026-06-29 20:54:19 +08:00
wc24 417318921c fix(menu): 菜单高亮用 $route.path 而非 fullPath
default-active 原绑 $route.fullPath(含 query),地址栏带 ?filter=… 时与菜单项
index('/'+name,纯路径)对不上 → 菜单不高亮/不跟随切换。改用 $route.path。
layout(桌面) + sideM(移动) 两处同改。
2026-06-29 20:48:58 +08:00
wc24 f7ef29a928 feat(relation): 显示名可点 → 跳目标模块并过滤到该 _id(单行)
- VueAdmin: 记录 moduleKey→IRouteInfo(moduleRoutes),加 routeForModule(判已注册+非子模块)
- FieldRelation.display: 目标可达时名字渲成链接,点击 router.push 到目标模块
  + ?filter={_id},落成过滤到单条的列表(框架无详情页,单项查看即此形态)
- 子模块目标/无权限目标不可点(routeForModule 返回 undefined)
2026-06-29 20:38:21 +08:00
6 changed files with 55 additions and 9 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "shrimp-admin",
"version": "0.3.2",
"version": "0.3.5",
"private": true,
"description": "Shrimp 框架前端 — 消费 ShrimpServer + ShrimpTable 契约的经典 Vue3 + Element Plus 实现",
"main": "src/index.ts",
+9
View File
@@ -123,11 +123,19 @@ export class VueAdmin extends ShrimpClient {
userName = ''
config: ShrimpAdminConfig
langList: { lang: string; name: string }[]
/** 已注册成功的模块路由(moduleKey → IRouteInfo),供 relation 跳转/单项查看解析路由 */
moduleRoutes = new Map<string, IRouteInfo>()
getFieldComponent(_moduleKey: string, type: string, _fieldKey?: string): Component {
return fieldRegistry.get(type) || fieldRegistry.get('string')!
}
/** 目标模块能否作为「单项查看」跳转目标:已按权限注册路由 且 非子模块。返回其路由信息或 undefined(不可跳) */
routeForModule(moduleKey: string): IRouteInfo | undefined {
if (this.moduleInfo.find(m => m.moduleKey === moduleKey)?.parent) return undefined
return this.moduleRoutes.get(moduleKey)
}
state = reactive({ isMobile: false })
private _token: string | null = null
@@ -256,6 +264,7 @@ export class VueAdmin extends ShrimpClient {
props: { moduleKey: info.moduleKey, events: info.events },
meta: { title: info.label }
})
this.moduleRoutes.set(info.moduleKey, info)
}
initRouter() {
+12 -1
View File
@@ -132,8 +132,18 @@ const {
const searchVisible = ref(false)
// relation 跳转用 ?filter={_id} 锁定单条;_id 是隐形 pin(不在筛选抽屉里),
// 用户一旦搜索/改筛选就清掉它,避免被看不见的 _id 一直 AND 着导致结果莫名为空
function stripJumpPin() {
if ('_id' in filterData.value) {
const { _id, ...rest } = filterData.value
filterData.value = rest
}
}
function onSearch() {
searchKeyword.value = keyword.value
stripJumpPin()
currentPage.value = 1
refresh()
}
@@ -151,7 +161,8 @@ const hasFilter = computed(() => {
})
async function handleFilterConfirm(newFilterData: Record<string, any>) {
filterData.value = newFilterData
const { _id, ...rest } = newFilterData // 确认筛选 → 丢弃跳转 pin
filterData.value = rest
currentPage.value = 1
await refresh()
}
+31 -5
View File
@@ -1,7 +1,13 @@
<template>
<template v-if="mode === 'display'">
<template v-if="field.multiple">{{ displayNames.join('、') || '-' }}</template>
<template v-else>{{ displayName || '-' }}</template>
<template v-if="field.multiple">
<span v-if="!displayItems.length">-</span>
<template v-for="(it, i) in displayItems" :key="i"><a v-if="jumpable" class="rel-link" @click.stop="goto(it.id)">{{ it.name }}</a><span v-else>{{ it.name }}</span><span v-if="i < displayItems.length - 1">、</span></template>
</template>
<template v-else>
<a v-if="jumpable && modelValue != null" class="rel-link" @click.stop="goto(modelValue)">{{ displayName }}</a>
<span v-else>{{ displayName || '-' }}</span>
</template>
</template>
<el-select
v-else-if="mode === 'filter'"
@@ -44,12 +50,22 @@ const displayName = computed(() => {
if (id == null) return ''
return dict.value[String(id)] ?? String(id)
})
const displayNames = computed(() =>
const displayItems = computed(() =>
(Array.isArray(props.modelValue) ? props.modelValue : [])
.map((id: any) => dict.value[String(id)] ?? String(id))
.filter(Boolean)
.filter((id: any) => id != null)
.map((id: any) => ({ id, name: dict.value[String(id)] ?? String(id) }))
)
// 单项查看跳转:目标模块可达(已注册路由)且非子模块时,名字可点 → 跳目标模块并过滤到该 _id(单行)
const jumpable = computed(() =>
props.mode === 'display' && !!props.field.relation && !!vueAdmin.routeForModule(props.field.relation.moduleKey)
)
function goto(id: any) {
const r = props.field.relation && vueAdmin.routeForModule(props.field.relation.moduleKey)
if (!r || id == null) return
vueAdmin.router.push({ name: r.name, query: { filter: JSON.stringify({ _id: String(id) }) } })
}
/** 统一转为字符串值,解决 ObjectId 与 el-select 的匹配问题 */
const stringValue = computed(() => {
// filter 模式强制 multiple,始终返回数组
@@ -105,3 +121,13 @@ onMounted(async () => {
}
})
</script>
<style scoped>
.rel-link {
color: var(--el-color-primary);
cursor: pointer;
}
.rel-link:hover {
text-decoration: underline;
}
</style>
+1 -1
View File
@@ -24,7 +24,7 @@
<div v-if="!vueAdmin.state.isMobile && nextMenu.length > 0" class="aminui-side">
<div class="adminui-side-scroll">
<el-scrollbar>
<el-menu :default-active="$route.fullPath" router>
<el-menu :default-active="$route.path" router>
<navMenu :navMenus="nextMenu"></navMenu>
</el-menu>
</el-scrollbar>
+1 -1
View File
@@ -17,7 +17,7 @@
<el-main>
<el-scrollbar>
<el-menu
:default-active="$route.fullPath"
:default-active="$route.path"
@select="select"
router
background-color="#212d3d"