提交 d93ca5d2 authored 作者: lidongxu's avatar lidongxu

feat(components/commonmenu): 常用菜单开发完毕

在主页实现了常用菜单组件的开发和使用_菜单数据是路由路径保存到 LocalStorage 使用
上级 f60d157b
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
"pinia": "2.1.7", "pinia": "2.1.7",
"splitpanes": "3.1.5", "splitpanes": "3.1.5",
"vue": "3.4.31", "vue": "3.4.31",
"vue-count-to": "^1.0.13",
"vue-cropper": "1.1.1", "vue-cropper": "1.1.1",
"vue-router": "4.4.0", "vue-router": "4.4.0",
"vuedraggable": "4.1.0" "vuedraggable": "4.1.0"
......
<template>
<el-card>
<div slot="header"
class="el-card__header">
<span>
常用菜单
</span>
<el-button class="custom"
type="primary"
link
@click="visible = true">
自定义
</el-button>
</div>
<div class="el-card__body">
<div v-for="o in commonMenuList"
class="item">
<el-button type="primary"
plain
@click="$router.push(o.path)">
{{ o.label }}
</el-button>
</div>
<div v-if="commonMenuList.length === 0">
<p class="none">
暂无常用菜单,立即
<a @click.prevent="visible = true">
自定义
</a>
</p>
</div>
</div>
<el-dialog title="编辑常用菜单"
v-model="visible">
<el-transfer ref="myTransfer"
v-model="selectList"
:data="menuList"
filterable
filter-placeholder="菜单名称"
@left-check-change="leftChange"
@right-check-change="rightChange"></el-transfer>
</el-dialog>
</el-card>
</template>
<script setup>
import usePermissionStore from '@/store/modules/permission'
import { routesFlat } from '@/utils'
const { proxy } = getCurrentInstance();
const visible = ref(false)
// 左侧菜单
const menuList = computed(() => {
return routesFlat(usePermissionStore().routes).map(obj => {
return {
key: obj.path,
path: obj.path,
label: obj.title.join('/')
}
})
})
// 右侧菜单
const selectList = ref(proxy.$cache.local.getJSONArray('common_ment'))
// 展示常用菜单
const commonMenuList = computed(() => selectList.value.map((path) => {
return menuList.value.find(o => o.key === path)
}))
// 监听选中菜单改变
watch(selectList, (val) => {
proxy.$cache.local.setJSON('common_ment', val)
}, {
deep: true
})
const leftChange = (val) => {
selectList.value.push(...val)
}
const rightChange = (val) => {
selectList.value.splice(selectList.value.findIndex(o => o === val[0]), 1)
}
</script>
<style scoped
lang="scss">
.el-card__header {
.custom {
float: right;
padding: 3px 0;
}
}
.el-card__body {
padding-left: 15px !important;
.item {
display: inline-block;
margin-right: 20px;
}
}
.none {
font-size: 14px;
a {
color: #337ab7;
}
}
/* 穿梭框样式重写 */
::v-deep(.el-dialog__body) {
display: flex;
justify-content: center;
/* 隐藏头部列表数量统计 */
.el-transfer-panel__header {
display: none;
}
.el-transfer {
display: flex;
align-items: center;
gap: 50px;
width: 100%;
/* 隐藏中间按钮 */
.el-transfer__buttons {
display: none;
}
.el-transfer-panel {
flex: 1;
.el-transfer-panel__body {
height: 500px;
display: flex;
flex-direction: column;
.el-transfer-panel__list {
flex: 1;
}
}
}
.el-transfer-panel:nth-last-of-type(1) {
.el-checkbox__inner {
background-color: #409eff;
}
.el-checkbox__inner::after {
transform: rotate(45deg) scaleY(1);
height: 6px;
width: 3px;
left: 4px;
box-sizing: content-box;
content: "";
border: 1px solid #fff;
border-left: 0;
border-top: 0;
position: absolute;
top: 1px;
transition: transform .15s ease-in .05s;
transform-origin: center;
}
}
.el-transfer-panel {
width: 300px;
}
}
}
</style>
<template> <template>
<div :class="{ 'show': show }" class="header-search"> <div :class="{ 'show': show }"
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" /> class="header-search">
<el-select <svg-icon class-name="search-icon"
ref="headerSearchSelectRef" icon-class="search"
@click.stop="click" />
<el-select ref="headerSearchSelectRef"
v-model="search" v-model="search"
:remote-method="querySearch" :remote-method="querySearch"
filterable filterable
...@@ -10,17 +12,19 @@ ...@@ -10,17 +12,19 @@
remote remote
placeholder="Search" placeholder="Search"
class="header-search-select" class="header-search-select"
@change="change" @change="change">
> <el-option v-for="option in options"
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" /> :key="option.item.path"
:value="option.item"
:label="option.item.title.join(' > ')" />
</el-select> </el-select>
</div> </div>
</template> </template>
<script setup> <script setup>
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi' import { routesFlat } from '@/utils'
import { isHttp } from '@/utils/validate'
import usePermissionStore from '@/store/modules/permission' import usePermissionStore from '@/store/modules/permission'
const search = ref(''); const search = ref('');
...@@ -80,43 +84,7 @@ function initFuse(list) { ...@@ -80,43 +84,7 @@ function initFuse(list) {
}] }]
}) })
} }
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
function generateRoutes(routes, basePath = '', prefixTitle = []) {
let res = []
for (const r of routes) {
// skip hidden router
if (r.hidden) { continue }
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle]
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title]
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
}
}
if (r.query) {
data.query = r.query
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
}
function querySearch(query) { function querySearch(query) {
if (query !== '') { if (query !== '') {
options.value = fuse.value.search(query) options.value = fuse.value.search(query)
...@@ -126,11 +94,11 @@ function querySearch(query) { ...@@ -126,11 +94,11 @@ function querySearch(query) {
} }
onMounted(() => { onMounted(() => {
searchPool.value = generateRoutes(routes.value); searchPool.value = routesFlat(routes.value);
}) })
watchEffect(() => { watchEffect(() => {
searchPool.value = generateRoutes(routes.value) searchPool.value = routesFlat(routes.value)
}) })
watch(show, (value) => { watch(show, (value) => {
...@@ -146,8 +114,9 @@ watch(searchPool, (list) => { ...@@ -146,8 +114,9 @@ watch(searchPool, (list) => {
}) })
</script> </script>
<style lang='scss' scoped> <style lang='scss'
.header-search { scoped>
.header-search {
font-size: 0 !important; font-size: 0 !important;
.search-icon { .search-icon {
...@@ -183,5 +152,5 @@ watch(searchPool, (list) => { ...@@ -183,5 +152,5 @@ watch(searchPool, (list) => {
margin-left: 10px; margin-left: 10px;
} }
} }
} }
</style> </style>
\ No newline at end of file
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App'
import store from './store'
import router from './router'
import directive from './directive' // directive
import './permission' // permission control
import plugins from './plugins' // plugins
import Cookies from 'js-cookie' /****************** 组件 ******************/
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css' import 'element-plus/theme-chalk/dark/css-vars.css'
import locale from 'element-plus/es/locale/lang/zh-cn' import locale from 'element-plus/es/locale/lang/zh-cn'
import '@/assets/styles/index.scss'
import '@/assets/styles/index.scss' // global css /****************** 插件 ******************/
import Cookies from 'js-cookie'
import App from './App'
import store from './store'
import router from './router'
import directive from './directive' // directive
// 注册指令
import plugins from './plugins' // plugins
import { download } from '@/utils/request' import { download } from '@/utils/request'
import 'virtual:svg-icons-register' // svg图标
// svg图标
import 'virtual:svg-icons-register'
import SvgIcon from '@/components/SvgIcon' import SvgIcon from '@/components/SvgIcon'
import elementIcons from '@/components/SvgIcon/svgicon' import elementIcons from '@/components/SvgIcon/svgicon'
import './permission' // permission control
import { useDict } from '@/utils/dict' import { useDict } from '@/utils/dict'
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi' import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
...@@ -42,6 +36,8 @@ import ImageUpload from "@/components/ImageUpload" ...@@ -42,6 +36,8 @@ import ImageUpload from "@/components/ImageUpload"
import ImagePreview from "@/components/ImagePreview" import ImagePreview from "@/components/ImagePreview"
// 字典标签组件 // 字典标签组件
import DictTag from '@/components/DictTag' import DictTag from '@/components/DictTag'
// 常用菜单
import CommonMenu from '@/components/CommonMenu'
const app = createApp(App) const app = createApp(App)
...@@ -63,6 +59,7 @@ app.component('ImageUpload', ImageUpload) ...@@ -63,6 +59,7 @@ app.component('ImageUpload', ImageUpload)
app.component('ImagePreview', ImagePreview) app.component('ImagePreview', ImagePreview)
app.component('RightToolbar', RightToolbar) app.component('RightToolbar', RightToolbar)
app.component('Editor', Editor) app.component('Editor', Editor)
app.component('CommonMenu', CommonMenu)
app.use(router) app.use(router)
app.use(store) app.use(store)
......
...@@ -28,6 +28,9 @@ const sessionCache = { ...@@ -28,6 +28,9 @@ const sessionCache = {
} }
return null return null
}, },
getJSONArray (key) {
return sessionCache.getJSON(key) || []
},
remove (key) { remove (key) {
sessionStorage.removeItem(key); sessionStorage.removeItem(key);
} }
...@@ -62,6 +65,9 @@ const localCache = { ...@@ -62,6 +65,9 @@ const localCache = {
} }
return null return null
}, },
getJSONArray (key) {
return localCache.getJSON(key) || []
},
remove (key) { remove (key) {
localStorage.removeItem(key); localStorage.removeItem(key);
} }
......
...@@ -12,8 +12,8 @@ const usePermissionStore = defineStore( ...@@ -12,8 +12,8 @@ const usePermissionStore = defineStore(
'permission', 'permission',
{ {
state: () => ({ state: () => ({
routes: [], routes: [], // 总路由(嵌套关系)
addRoutes: [], addRoutes: [], // 动态路由
defaultRoutes: [], defaultRoutes: [],
topbarRouters: [], topbarRouters: [],
sidebarRouters: [] sidebarRouters: []
......
import { parseTime } from './ruoyi' import { parseTime } from './ruoyi'
export * from './jsencrypt.js' export * from './jsencrypt.js'
export * from './ruoyi' export * from './ruoyi'
export * from './route.js'
/** /**
* 表格时间格式化 * 表格时间格式化
......
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
import { isHttp } from '@/utils/validate'
import { getNormalPath } from '@/utils'
export function routesFlat(routes, basePath = '', prefixTitle = []) {
let res = []
for (const r of routes) {
// skip hidden router
if (r.hidden) { continue }
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle]
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title]
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
}
}
if (r.query) {
data.query = r.query
}
// recursive child routes
if (r.children) {
const tempRoutes = routesFlat(r.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
}
\ No newline at end of file
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论