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", "name": "shrimp-admin",
"version": "0.3.2", "version": "0.3.5",
"private": true, "private": true,
"description": "Shrimp 框架前端 — 消费 ShrimpServer + ShrimpTable 契约的经典 Vue3 + Element Plus 实现", "description": "Shrimp 框架前端 — 消费 ShrimpServer + ShrimpTable 契约的经典 Vue3 + Element Plus 实现",
"main": "src/index.ts", "main": "src/index.ts",
+9
View File
@@ -123,11 +123,19 @@ export class VueAdmin extends ShrimpClient {
userName = '' userName = ''
config: ShrimpAdminConfig config: ShrimpAdminConfig
langList: { lang: string; name: string }[] langList: { lang: string; name: string }[]
/** 已注册成功的模块路由(moduleKey → IRouteInfo),供 relation 跳转/单项查看解析路由 */
moduleRoutes = new Map<string, IRouteInfo>()
getFieldComponent(_moduleKey: string, type: string, _fieldKey?: string): Component { getFieldComponent(_moduleKey: string, type: string, _fieldKey?: string): Component {
return fieldRegistry.get(type) || fieldRegistry.get('string')! 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 }) state = reactive({ isMobile: false })
private _token: string | null = null private _token: string | null = null
@@ -256,6 +264,7 @@ export class VueAdmin extends ShrimpClient {
props: { moduleKey: info.moduleKey, events: info.events }, props: { moduleKey: info.moduleKey, events: info.events },
meta: { title: info.label } meta: { title: info.label }
}) })
this.moduleRoutes.set(info.moduleKey, info)
} }
initRouter() { initRouter() {
+12 -1
View File
@@ -132,8 +132,18 @@ const {
const searchVisible = ref(false) 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() { function onSearch() {
searchKeyword.value = keyword.value searchKeyword.value = keyword.value
stripJumpPin()
currentPage.value = 1 currentPage.value = 1
refresh() refresh()
} }
@@ -151,7 +161,8 @@ const hasFilter = computed(() => {
}) })
async function handleFilterConfirm(newFilterData: Record<string, any>) { async function handleFilterConfirm(newFilterData: Record<string, any>) {
filterData.value = newFilterData const { _id, ...rest } = newFilterData // 确认筛选 → 丢弃跳转 pin
filterData.value = rest
currentPage.value = 1 currentPage.value = 1
await refresh() await refresh()
} }
+31 -5
View File
@@ -1,7 +1,13 @@
<template> <template>
<template v-if="mode === 'display'"> <template v-if="mode === 'display'">
<template v-if="field.multiple">{{ displayNames.join('、') || '-' }}</template> <template v-if="field.multiple">
<template v-else>{{ displayName || '-' }}</template> <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> </template>
<el-select <el-select
v-else-if="mode === 'filter'" v-else-if="mode === 'filter'"
@@ -44,12 +50,22 @@ const displayName = computed(() => {
if (id == null) return '' if (id == null) return ''
return dict.value[String(id)] ?? String(id) return dict.value[String(id)] ?? String(id)
}) })
const displayNames = computed(() => const displayItems = computed(() =>
(Array.isArray(props.modelValue) ? props.modelValue : []) (Array.isArray(props.modelValue) ? props.modelValue : [])
.map((id: any) => dict.value[String(id)] ?? String(id)) .filter((id: any) => id != null)
.filter(Boolean) .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 的匹配问题 */ /** 统一转为字符串值,解决 ObjectId 与 el-select 的匹配问题 */
const stringValue = computed(() => { const stringValue = computed(() => {
// filter 模式强制 multiple,始终返回数组 // filter 模式强制 multiple,始终返回数组
@@ -105,3 +121,13 @@ onMounted(async () => {
} }
}) })
</script> </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 v-if="!vueAdmin.state.isMobile && nextMenu.length > 0" class="aminui-side">
<div class="adminui-side-scroll"> <div class="adminui-side-scroll">
<el-scrollbar> <el-scrollbar>
<el-menu :default-active="$route.fullPath" router> <el-menu :default-active="$route.path" router>
<navMenu :navMenus="nextMenu"></navMenu> <navMenu :navMenus="nextMenu"></navMenu>
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
+1 -1
View File
@@ -17,7 +17,7 @@
<el-main> <el-main>
<el-scrollbar> <el-scrollbar>
<el-menu <el-menu
:default-active="$route.fullPath" :default-active="$route.path"
@select="select" @select="select"
router router
background-color="#212d3d" background-color="#212d3d"