feat(B批收尾): B1门禁可信/B7-1菜单环防护/B2空值守卫/B7-2分页/B7-3字段清洗/B3空态/B4索引key/B5-3 comment三集合选A新建schema(漂移清零)/B6清理+admin配置+changelog

This commit is contained in:
12914
2026-09-12 14:14:42 +08:00
parent e408cd0a3e
commit c80c5977f7
79 changed files with 18538 additions and 16777 deletions
+184 -186
View File
@@ -1,186 +1,184 @@
<template>
<view>
<uni-nav-menu :active="value" activeKey="value" :activeTextColor="activeTextColor" :uniqueOpened="uniqueOpened"
@select="onSelect">
<uni-menu-sidebar :data="userMenu"></uni-menu-sidebar>
<uni-menu-sidebar :data="staticMenu"></uni-menu-sidebar>
</uni-nav-menu>
</view>
</template>
<script>
import {
mapActions
} from 'vuex'
import {
buildMenus
} from './util.js'
export default {
data() {
return {
menus: [],
userMenu: [],
famliy: [],
};
},
mixins: [uniCloud.mixinDatacom],
props: {
// 当前激活菜单的 url
value: {
type: String,
default: ''
},
// 当前激活菜单的文字颜色
activeTextColor: {
type: String,
default: '#42B983'
},
// 是否只保持一个子菜单的展开
uniqueOpened: {
type: Boolean,
default: false
},
staticMenu: {
type: Array,
default () {
return []
}
}
},
watch: {
localdata: {
handler(newval) {
if (this.hasLocalData(newval)) {
this.userMenu = newval
}
},
immediate: true
},
// TODO 暂时无需监听,需要看后面会出现什么问题
// #ifdef H5
menus: {
immediate: true,
handler(newVal,oldVal) {
const item = this.menus.find(m => m.value === this.$route.path)
// 设置面包屑
if(item){
this.getMenuAncestor(item.menu_id, newVal)
item && this.setRoutes && this.setRoutes(this.famliy)
}
}
},
// #endif
$route: {
immediate: false,
handler(val, old) {
if (val.fullPath !== old.fullPath) {
this.famliy = []
const menu = this.menus.find(m => m.value === val.path)
const menu_id = menu && menu.menu_id
this.getMenuAncestor(menu_id, this.menus)
this.setRoutes && this.setRoutes(this.famliy)
}
}
}
},
created() {
if (this.hasLocalData(this.localdata)) return
// #ifndef H5
this.load()
// #endif
},
// computed:{
// userMenu() {
// return this.getUserMenu(this.menus)
// }
// },
methods: {
...mapActions({
setRoutes: 'app/setRoutes'
}),
getUserMenu(menuList) {
const {
permission,
role
} = uniCloud.getCurrentUserInfo()
// 标记叶子节点
menuList.map(item => {
if (!menuList.some(subMenuItem => subMenuItem.parent_id === item.menu_id)) {
item.isLeafNode = true
}
})
// 删除无权限访问的菜单
if (!role.includes('admin')) {
menuList = menuList.filter(item => {
if (item.isLeafNode) {
if (item.permission && item.permission.length) {
return item.permission.some(item => permission.indexOf(item) > -1)
}
return false
}
return true
})
}
return buildMenus(menuList)
},
onSelect(menu) {
this.famliy = []
this.getMenuAncestor(menu.menu_id, this.menus)
this.emit(menu)
},
emit(menu) {
this.$emit('select', menu, this.famliy)
this.$emit('input', menu.value)
},
hasLocalData(value) {
return Array.isArray(value) && value.length > 0
},
load() {
if (this.mixinDatacomLoading == true) {
return
}
this.mixinDatacomLoading = true
this.mixinDatacomGet().then((res) => {
this.mixinDatacomLoading = false
const {
data,
count
} = res.result
this.menus = data
this.userMenu = this.getUserMenu(this.menus)
}).catch((err) => {
this.mixinDatacomLoading = false
this.mixinDatacomErrorMessage = err
})
},
getMenuAncestor(menuId, menus) {
menus.forEach(item => {
if (item.menu_id === menuId) {
const route = {
name: item.text
}
const path = item.value
if (path) {
route.to = {
path
}
}
this.famliy.unshift(route)
const parent_id = item.parent_id
if (parent_id) {
this.getMenuAncestor(parent_id, menus)
}
}
})
// return famliy
}
},
}
</script>
<style>
</style>
<template>
<view>
<uni-nav-menu :active="value" activeKey="value" :activeTextColor="activeTextColor" :uniqueOpened="uniqueOpened"
@select="onSelect">
<uni-menu-sidebar :data="userMenu"></uni-menu-sidebar>
<uni-menu-sidebar :data="staticMenu"></uni-menu-sidebar>
</uni-nav-menu>
</view>
</template>
<script>
import {
mapActions
} from 'vuex'
import {
buildMenus
} from './util.js'
export default {
data() {
return {
menus: [],
userMenu: [],
famliy: [],
};
},
mixins: [uniCloud.mixinDatacom],
props: {
// 当前激活菜单的 url
value: {
type: String,
default: ''
},
// 当前激活菜单的文字颜色
activeTextColor: {
type: String,
default: '#42B983'
},
// 是否只保持一个子菜单的展开
uniqueOpened: {
type: Boolean,
default: false
},
staticMenu: {
type: Array,
default () {
return []
}
}
},
watch: {
localdata: {
handler(newval) {
if (this.hasLocalData(newval)) {
this.userMenu = newval
}
},
immediate: true
},
// TODO 暂时无需监听,需要看后面会出现什么问题
// #ifdef H5
menus: {
immediate: true,
handler(newVal,oldVal) {
const item = this.menus.find(m => m.value === this.$route.path)
// 设置面包屑
if(item){
this.getMenuAncestor(item.menu_id, newVal)
item && this.setRoutes && this.setRoutes(this.famliy)
}
}
},
// #endif
$route: {
immediate: false,
handler(val, old) {
if (val.fullPath !== old.fullPath) {
this.famliy = []
const menu = this.menus.find(m => m.value === val.path)
const menu_id = menu && menu.menu_id
this.getMenuAncestor(menu_id, this.menus)
this.setRoutes && this.setRoutes(this.famliy)
}
}
}
},
created() {
if (this.hasLocalData(this.localdata)) return
this.load()
},
// computed:{
// userMenu() {
// return this.getUserMenu(this.menus)
// }
// },
methods: {
...mapActions({
setRoutes: 'app/setRoutes'
}),
getUserMenu(menuList) {
const userInfo = uniCloud.getCurrentUserInfo() || {}
const permission = Array.isArray(userInfo.permission) ? userInfo.permission : []
const role = Array.isArray(userInfo.role) ? userInfo.role : []
menuList = Array.isArray(menuList) ? menuList : []
// 标记叶子节点
menuList.map(item => {
if (!menuList.some(subMenuItem => subMenuItem.parent_id === item.menu_id)) {
item.isLeafNode = true
}
})
// 删除无权限访问的菜单
if (!role.includes('admin')) {
menuList = menuList.filter(item => {
if (item.isLeafNode) {
if (Array.isArray(item.permission) && item.permission.length) {
return item.permission.some(item => permission.indexOf(item) > -1)
}
return false
}
return true
})
}
return buildMenus(menuList)
},
onSelect(menu) {
this.famliy = []
this.getMenuAncestor(menu.menu_id, this.menus)
this.emit(menu)
},
emit(menu) {
this.$emit('select', menu, this.famliy)
this.$emit('input', menu.value)
},
hasLocalData(value) {
return Array.isArray(value) && value.length > 0
},
load() {
if (this.mixinDatacomLoading == true) {
return
}
this.mixinDatacomLoading = true
this.mixinDatacomGet().then((res) => {
this.mixinDatacomLoading = false
const {
data,
count
} = res.result
this.menus = data
this.userMenu = this.getUserMenu(this.menus)
}).catch((err) => {
this.mixinDatacomLoading = false
this.mixinDatacomErrorMessage = err && err.message ? err.message : String(err || '请求服务失败')
})
},
getMenuAncestor(menuId, menus) {
menus.forEach(item => {
if (item.menu_id === menuId) {
const route = {
name: item.text
}
const path = item.value
if (path) {
route.to = {
path
}
}
this.famliy.unshift(route)
const parent_id = item.parent_id
if (parent_id) {
this.getMenuAncestor(parent_id, menus)
}
}
})
// return famliy
}
},
}
</script>
<style>
</style>
+116 -67
View File
@@ -1,67 +1,116 @@
function buildMenu(menu, menuList, menuIds) {
let nextLayer = []
for (let i = menu.length - 1; i > -1; i--) {
const currentMenu = menu[i]
const subMenu = menuList.filter(item => {
if (item.parent_id === currentMenu.menu_id) {
menuIds.push(item.menu_id)
return true
}
})
nextLayer = nextLayer.concat(subMenu)
currentMenu.children = subMenu
}
if (nextLayer.length) {
buildMenu(nextLayer, menuList, menuIds)
}
}
function getParentIds(menuItem, menuList) {
const parentArr = []
let currentItem = menuItem
while (currentItem && currentItem.parent_id) {
parentArr.push(currentItem.parent_id)
currentItem = menuList.find(item => item.menu_id === currentItem.parent_id)
}
return parentArr
}
function buildMenus(menuList, trim = true) {
// 保证父子级顺序
menuList = menuList.sort(function(a, b) {
const parentIdsA = getParentIds(a, menuList)
const parentIdsB = getParentIds(b, menuList)
if (parentIdsA.includes(b.menu_id)) {
return 1
}
return parentIdsA.length - parentIdsB.length || a.sort - b.sort
})
// 删除无subMenu且非子节点的菜单项
if (trim) {
for (let i = menuList.length - 1; i > -1; i--) {
const currentMenu = menuList[i]
const subMenu = menuList.filter(subMenuItem => subMenuItem.parent_id === currentMenu.menu_id)
if (!currentMenu.isLeafNode && !subMenu.length) {
menuList.splice(i, 1)
}
}
}
const menuIds = []
const menu = menuList.filter(item => {
if (!item.parent_id) {
menuIds.push(item.menu_id)
return true
}
})
buildMenu(menu, menuList, menuIds)
// 包含所有无效菜单
if (!trim && menuIds.length !== menuList.length) {
menu.push(...menuList.filter(item => !menuIds.includes(item.menu_id)))
}
return menu
}
export {
buildMenu,
buildMenus
}
function buildMenu(menu, menuList, menuIds, visited) {
visited = visited || new Set()
let nextLayer = []
for (let i = menu.length - 1; i > -1; i--) {
const currentMenu = menu[i]
if (visited.has(currentMenu.menu_id)) continue
visited.add(currentMenu.menu_id)
const subMenu = menuList.filter(item => {
if (item.parent_id === currentMenu.menu_id) {
if (visited.has(item.menu_id)) return false
menuIds.push(item.menu_id)
return true
}
})
nextLayer = nextLayer.concat(subMenu)
currentMenu.children = subMenu
}
if (nextLayer.length) {
buildMenu(nextLayer, menuList, menuIds, visited)
}
}
function getParentIds(menuItem, menuList) {
const parentArr = []
const visited = new Set()
let currentItem = menuItem
while (currentItem && currentItem.parent_id) {
if (visited.has(currentItem.parent_id)) {
// B7-1: 数据成环(A→B、B→A)时立即返回已收集链,不再死循环
parentArr.push(currentItem.parent_id)
break
}
visited.add(currentItem.parent_id)
parentArr.push(currentItem.parent_id)
currentItem = menuList.find(item => item.menu_id === currentItem.parent_id)
}
return parentArr
}
// B7-1: 判断把 menuId 的 parent 改为 newParentId 是否会成环
function wouldCreateCycle(menuList, menuId, newParentId) {
if (!newParentId) return false
if (newParentId === menuId) return true
const visited = new Set()
let current = newParentId
while (current) {
if (current === menuId) return true
if (visited.has(current)) return true
visited.add(current)
const item = menuList.find(m => m.menu_id === current)
current = item && item.parent_id
}
return false
}
function buildMenus(menuList, trim = true) {
// B7-1: sort 比较器不再每次重算 getParentIds —— 记忆化一次算完(链式 O(n))
const byId = new Map()
for (const item of menuList) byId.set(item.menu_id, item)
const parentMap = new Map()
function ancestors(id) {
if (parentMap.has(id)) return parentMap.get(id)
const chain = []
const visited = new Set()
let cur = byId.get(id)
while (cur && cur.parent_id) {
if (visited.has(cur.parent_id)) { chain.push(cur.parent_id); break }
visited.add(cur.parent_id)
chain.push(cur.parent_id)
if (parentMap.has(cur.parent_id)) { chain.push(...parentMap.get(cur.parent_id)); break }
cur = byId.get(cur.parent_id)
}
parentMap.set(id, chain)
return chain
}
for (const item of menuList) ancestors(item.menu_id)
// 保证父子级顺序
menuList = menuList.sort(function(a, b) {
const parentIdsA = parentMap.get(a.menu_id) || []
const parentIdsB = parentMap.get(b.menu_id) || []
if (parentIdsA.includes(b.menu_id)) {
return 1
}
return parentIdsA.length - parentIdsB.length || a.sort - b.sort
})
// 删除无subMenu且非子节点的菜单项
if (trim) {
for (let i = menuList.length - 1; i > -1; i--) {
const currentMenu = menuList[i]
const subMenu = menuList.filter(subMenuItem => subMenuItem.parent_id === currentMenu.menu_id)
if (!currentMenu.isLeafNode && !subMenu.length) {
menuList.splice(i, 1)
}
}
}
const menuIds = []
const menu = menuList.filter(item => {
if (!item.parent_id) {
menuIds.push(item.menu_id)
return true
}
})
buildMenu(menu, menuList, menuIds, new Set())
// 包含所有无效菜单
if (!trim && menuIds.length !== menuList.length) {
menu.push(...menuList.filter(item => !menuIds.includes(item.menu_id)))
}
return menu
}
export {
buildMenu,
buildMenus,
getParentIds,
wouldCreateCycle
}