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

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

在主页实现了常用菜单组件的开发和使用_菜单数据是路由路径保存到 LocalStorage 使用
上级 f60d157b
......@@ -32,6 +32,7 @@
"pinia": "2.1.7",
"splitpanes": "3.1.5",
"vue": "3.4.31",
"vue-count-to": "^1.0.13",
"vue-cropper": "1.1.1",
"vue-router": "4.4.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>
<div :class="{ 'show': show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select
ref="headerSearchSelectRef"
<div :class="{ 'show': show }"
class="header-search">
<svg-icon class-name="search-icon"
icon-class="search"
@click.stop="click" />
<el-select ref="headerSearchSelectRef"
v-model="search"
:remote-method="querySearch"
filterable
......@@ -10,17 +12,19 @@
remote
placeholder="Search"
class="header-search-select"
@change="change"
>
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
@change="change">
<el-option v-for="option in options"
:key="option.item.path"
:value="option.item"
:label="option.item.title.join(' > ')" />
</el-select>
</div>
</template>
<script setup>
import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi'
import { isHttp } from '@/utils/validate'
import { routesFlat } from '@/utils'
import usePermissionStore from '@/store/modules/permission'
const search = ref('');
......@@ -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) {
if (query !== '') {
options.value = fuse.value.search(query)
......@@ -126,11 +94,11 @@ function querySearch(query) {
}
onMounted(() => {
searchPool.value = generateRoutes(routes.value);
searchPool.value = routesFlat(routes.value);
})
watchEffect(() => {
searchPool.value = generateRoutes(routes.value)
searchPool.value = routesFlat(routes.value)
})
watch(show, (value) => {
......@@ -146,42 +114,43 @@ watch(searchPool, (list) => {
})
</script>
<style lang='scss' scoped>
.header-search {
font-size: 0 !important;
<style lang='scss'
scoped>
.header-search {
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
:deep(.el-input__inner) {
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
display: inline-block;
vertical-align: middle;
:deep(.el-input__inner) {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
}
}
}
</style>
\ No newline at end of file
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 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import locale from 'element-plus/es/locale/lang/zh-cn'
import '@/assets/styles/index.scss'
import '@/assets/styles/index.scss' // global css
import App from './App'
import store from './store'
import router from './router'
import directive from './directive' // directive
// 注册指令
import plugins from './plugins' // plugins
/****************** 插件 ******************/
import Cookies from 'js-cookie'
import { download } from '@/utils/request'
// svg图标
import 'virtual:svg-icons-register'
import 'virtual:svg-icons-register' // svg图标
import SvgIcon from '@/components/SvgIcon'
import elementIcons from '@/components/SvgIcon/svgicon'
import './permission' // permission control
import { useDict } from '@/utils/dict'
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
......@@ -42,6 +36,8 @@ import ImageUpload from "@/components/ImageUpload"
import ImagePreview from "@/components/ImagePreview"
// 字典标签组件
import DictTag from '@/components/DictTag'
// 常用菜单
import CommonMenu from '@/components/CommonMenu'
const app = createApp(App)
......@@ -63,6 +59,7 @@ app.component('ImageUpload', ImageUpload)
app.component('ImagePreview', ImagePreview)
app.component('RightToolbar', RightToolbar)
app.component('Editor', Editor)
app.component('CommonMenu', CommonMenu)
app.use(router)
app.use(store)
......
......@@ -28,6 +28,9 @@ const sessionCache = {
}
return null
},
getJSONArray (key) {
return sessionCache.getJSON(key) || []
},
remove (key) {
sessionStorage.removeItem(key);
}
......@@ -62,6 +65,9 @@ const localCache = {
}
return null
},
getJSONArray (key) {
return localCache.getJSON(key) || []
},
remove (key) {
localStorage.removeItem(key);
}
......
......@@ -12,8 +12,8 @@ const usePermissionStore = defineStore(
'permission',
{
state: () => ({
routes: [],
addRoutes: [],
routes: [], // 总路由(嵌套关系)
addRoutes: [], // 动态路由
defaultRoutes: [],
topbarRouters: [],
sidebarRouters: []
......
import { parseTime } from './ruoyi'
export * from './jsencrypt.js'
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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论