Bladeren bron

制作画板ui

bill 2 jaren geleden
bovenliggende
commit
69689c5e36

+ 9 - 1
src/components/base/assets/scss/editor/_menu.scss

@@ -19,9 +19,17 @@
         width: 100%;
         overflow: auto;
     }
-
+    .ui-editor-menu-ul {
+        display: flex;
+        flex-direction: column;
+        min-height: 100%;
+    }
     .ui-editor-menu-item {
         width: var(--editor-menu-width);
         height: var(--editor-menu-width);
+
+        &.bottom {
+            margin-top: auto;
+        }
     }
 }

+ 40 - 45
src/components/base/editor/layout/Menu.vue

@@ -1,62 +1,57 @@
 <template>
-    <div class="ui-editor-menu strengthen-right">
-        <slot v-if="$slots.first" name="first"></slot>
+  <div class="ui-editor-menu strengthen-right">
+    <slot v-if="$slots.first" name="first"></slot>
 
-        <div ref="scrollbar">
-            <ul>
-                <li v-for="item in menu" :key="item" class="ui-editor-menu-item" :class="{ active: $slots.default && item.name == name }" @click="!$slots.default && onMenuClick(item.name)">
-                    <slot v-if="$slots.default" :raw="item" />
-                    <UIMenuItem v-else :text="item.title" :icon="item.icon" :active="item.name == name" />
-                </li>
-            </ul>
-        </div>
+    <ul class="ui-editor-menu-ul">
+      <li v-for="item in menu"
+          :key="item"
+          class="ui-editor-menu-item"
+          :class="{ active: $slots.default && item.name === name, bottom: item.bottom }"
+          @click="!$slots.default && onMenuClick(item.name)"
+      >
+        <slot v-if="$slots.default" :raw="item"/>
+        <UIMenuItem v-else :text="item.title" :icon="item.icon" :active="item.name == name"/>
+      </li>
+    </ul>
 
-        <slot v-if="$slots.attach" name="attach"></slot>
-    </div>
+    <slot v-if="$slots.attach" name="attach"></slot>
+  </div>
 </template>
 <script>
-import { defineComponent, onMounted, watchEffect, ref, computed } from 'vue'
+import {defineComponent, onMounted, watchEffect, ref, computed} from 'vue'
 import Scrollbar from '../../components/scrollbar'
 import UIIcon from '../../components/icon/index.vue'
 import UIMenuItem from '../../components/menu-item'
 
 // 阻止热更新时再次实例化
-let __init__ = false
 
 export default defineComponent({
-    name: 'ui-editor-menu',
-    props: {
-        menu: {
-            type: Array,
-            default: [],
-        },
-        name: {
-            type: String,
-        },
+  name: 'ui-editor-menu',
+  props: {
+    menu: {
+      type: Array,
+      default: [],
     },
-    setup(props, ctx) {
-        const scrollbar = ref(null)
-
-        const onMenuClick = name => {
-            // routerName.value = name
-            ctx.emit('menu-click', name)
-        }
-        onMounted(() => {
-            if (__init__ == false) {
-                __init__ = true
-                new Scrollbar(scrollbar.value)
-            }
-        })
-
-        return {
-            scrollbar,
-            onMenuClick,
-        }
-    },
-    components: {
-        UIIcon,
-        UIMenuItem,
+    name: {
+      type: String,
     },
+  },
+  setup(props, ctx) {
+    const scrollbar = ref(null)
+
+    const onMenuClick = name => {
+      // routerName.value = name
+      ctx.emit('menu-click', name)
+    }
+    return {
+      scrollbar,
+      onMenuClick,
+    }
+  },
+  components: {
+    UIIcon,
+    UIMenuItem,
+  },
 })
 
 // export default {

+ 18 - 0
src/components/graphic-action/index.vue

@@ -0,0 +1,18 @@
+<template>
+  <div class="graphic-action">
+    <slot />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.graphic-action {
+  position: absolute;
+  z-index: 1;
+  height: 64px;
+  border-radius: 32px;
+  background-color: var(--editor-menu-back);
+  padding: 4px 16px;
+  display: flex;
+  align-items: center;
+}
+</style>

+ 9 - 8
src/components/main-panel/index.vue

@@ -1,16 +1,17 @@
 <template>
   <UiEditorLayout class="layout" :class="layoutClass">
     <UiEditorHead class="header">
-      <div
-        class="menu"
-        :class="{ abs: isFull }"
-        @click="customMap.sysView = isFull ? 'auto' : 'full'"
-      >
-        <ui-icon :type="isFull ? 'menu' : 'close'" ctrl />
-      </div>
-
+<!--      <div-->
+<!--        class="menu"-->
+<!--        :class="{ abs: isFull }"-->
+<!--        @click="customMap.sysView = isFull ? 'auto' : 'full'"-->
+<!--      >-->
+<!--        <ui-icon :type="isFull ? 'menu' : 'close'" ctrl />-->
+<!--      </div>-->
+      <slot name="header" />
     </UiEditorHead>
     <slot/>
+
     <Menu
       :menu="menus"
       :active-key="activeMenuKey"

+ 6 - 1
src/graphic/CanvasStyle/default.js

@@ -55,6 +55,10 @@ const Point = {
   radius: 4,
 }
 
+const RoadPoint = {
+  ...Point
+}
+
 const CurvePoint = {
   ...Point
 }
@@ -121,5 +125,6 @@ export default {
   Text,
   Font: CanvasFont,
   Measure,
-  Element
+  Element,
+  RoadPoint
 }

+ 5 - 0
src/graphic/CanvasStyle/focus.js

@@ -23,6 +23,10 @@ const Point = {
   strokeStyle: "rgba(245, 255, 255, 1)",
 }
 
+const RoadPoint = {
+  ...Point,
+}
+
 const CurvePoint = {
   ...def.CurvePoint,
   ...Point
@@ -49,6 +53,7 @@ export default {
   Road,
   Tag,
   Point,
+  RoadPoint,
   CurvePoint,
   ControlPoint,
   CurveRoad,

+ 5 - 0
src/graphic/CanvasStyle/select.js

@@ -33,6 +33,10 @@ const Point = {
   strokeStyle: "rgba(245, 255, 255, 1)",
 }
 
+const RoadPoint = {
+  ...Point
+}
+
 const CurvePoint = {
   ...def.CurvePoint,
   ...Point
@@ -47,6 +51,7 @@ export default {
   Road,
   Tag,
   Point,
+  RoadPoint,
   CurvePoint,
   ControlPoint,
   CurveRoad,

+ 5 - 0
src/graphic/Renderer/Draw.js

@@ -269,7 +269,9 @@ export default class Draw {
 
     ctx.save();
     ctx.beginPath();
+    console.log(vector)
     help.setVectorStyle(ctx, vector);
+
     ctx.arc(
       pt.x,
       pt.y,
@@ -334,6 +336,9 @@ export default class Draw {
     }
   }
 
+  drawRoadPoint(vector) {
+    this.drawPoint(vector)
+  }
   drawPoint(vector) {
     const pt = coordinate.getScreenXY({ x: vector.x, y: vector.y });
 

+ 1 - 1
src/hook/useGraphic.ts

@@ -8,7 +8,7 @@ export type VectorTypeT = typeof VectorType
 
 const newsletter = ref<{
   selectUI?: UITypeT[keyof UITypeT]
-  focusVector?: { geoType: VectorTypeT[keyof VectorTypeT] }
+  focusVector?: { type: VectorTypeT[keyof VectorTypeT], vectorId: string }
 }>({ selectUI: null, focusVector: null });
 
 export const setCanvas = async (canvas: HTMLCanvasElement) => {

+ 98 - 0
src/views/graphic/childMenus.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="graphic-child-menus">
+    <div class="header">
+      <ui-icon type="left" class="icon" ctrl @click="$emit('quit')" />
+      <p>{{ title }}</p>
+    </div>
+    <div class="menu-list">
+      <div
+        v-for="menu in menus"
+        :key="menu.key"
+        class="menu"
+        :class="{active: uiType.current === menu.key}"
+        @click="uiType.change(menu.key as any)"
+      >
+        <ui-icon type="menu" class="icon" />
+        <p>{{ menu.text }}</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { MenusRaw, findMainMenuByExtend } from "@/views/graphic/menus";
+import UiIcon from "@/components/base/components/icon/index.vue";
+import { uiType } from '@/hook/useGraphic'
+import {computed} from "vue";
+
+const props = defineProps<{ menus: MenusRaw }>();
+const title = computed(() => findMainMenuByExtend(props.menus)?.text)
+
+defineEmits<{ (e: "quit") }>();
+
+</script>
+
+<style lang="scss" scoped>
+.graphic-child-menus {
+  background-color: var(--editor-menu-back);
+  position: absolute;
+  top: calc(var(--editor-head-height) + var(--header-top));
+  bottom: 0;
+  left: calc(var(--editor-menu-left) + var(--editor-menu-width));
+  padding: 16px;
+  overflow-y: auto;
+
+  .menu-list {
+    display: grid;
+    grid-template-columns: repeat(3, 80px);
+    grid-gap: 16px;
+  }
+}
+
+.menu {
+  display: flex;
+  flex-direction: column;
+  cursor: pointer;
+  height: 100px;
+  transition: color .3s ease;
+
+  &:hover,
+  &.active {
+    color: var(--color-main-hover);
+  }
+
+  &.active {
+    background-color: rgba(255, 255, 255, 0.06);
+  }
+
+  .icon {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex: 1;
+    font-size: 40px;
+    text-align: center;
+    background: #383838;
+  }
+  p {
+    padding: 4px;
+    font-size: 12px;
+    text-align: center;
+  }
+}
+
+.header {
+  margin-bottom: 10px;
+  padding: 5px 0;
+  text-align: center;
+  font-size: 16px;
+  position: relative;
+
+  .icon {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    left: 0;
+  }
+}
+</style>

+ 36 - 0
src/views/graphic/container.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="draw-layout">
+    <div class="canvas-layout">
+      <canvas ref="drawCanvasRef" class="draw-canvas" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {onMounted, ref} from "vue";
+import {setCanvas} from "@/hook/useGraphic";
+
+const drawCanvasRef = ref<HTMLCanvasElement>();
+const setCanvasSize = () => {
+  drawCanvasRef.value.width = drawCanvasRef.value.offsetWidth;
+  drawCanvasRef.value.height = drawCanvasRef.value.offsetHeight;
+};
+
+onMounted(() => {
+  setCanvasSize();
+  setCanvas(drawCanvasRef.value);
+});
+</script>
+
+<style scoped lang="scss">
+.draw-layout {
+  width  : 100%;
+  height : 100%;
+  display: flex;
+}
+
+.draw-canvas {
+  width : 100%;
+  height: 100%;
+}
+</style>

+ 72 - 0
src/views/graphic/header.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="graphic-header">
+    <div class="title">
+      <ui-icon type="close" />
+      <p>现场绘图</p>
+    </div>
+
+    <div class="actions">
+      <ui-icon
+        class="action"
+        v-for="menu in menus"
+        :key="menu.key"
+        type="close"
+        ctrl
+      />
+    </div>
+
+    <div class="table">
+      <ui-button width="100px" type="primary">制表</ui-button>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import UiIcon from "@/components/base/components/icon/index.vue";
+import UiButton from "@/components/base/components/button/index.vue";
+import { headActionMenuRaw, generateByMenus } from './menus'
+
+const menus = generateByMenus(
+  menu => ({...menu}),
+  headActionMenuRaw
+)
+
+</script>
+
+<style scoped lang="scss">
+.graphic-header {
+  display: flex;
+  position: relative;
+  width: 100%;
+  height: 100%;
+  align-items: center;
+  justify-content: center;
+}
+
+.actions .action {
+  font-size: 20px;
+  margin: 0 15px;
+}
+
+.table,
+.title {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.title {
+  left: 0;
+  display: flex;
+  align-items: center;
+  p {
+    margin-left: 10px;
+  }
+}
+
+.table {
+  right: 0;
+}
+
+
+
+</style>

+ 52 - 36
src/views/graphic/index.vue

@@ -1,48 +1,64 @@
 <template>
-  <MainPanel :menus="menus" :active-menu-key="uiType.current">
-    <div class="draw-layout">
-      <div class="canvas-layout">+
-        <canvas ref="drawCanvasRef" class="draw-canvas" />
-      </div>
-    </div>
+  <MainPanel :menus="menus as any" :active-menu-key="activeMenuKey">
+    <template v-slot:header>
+      <Header />
+    </template>
+    <Container />
+    <ChildMenus v-if="extendMenus" :menus="extendMenus" @quit="extendMenus = null" />
+
+    <GraphicAction class="full-action">
+      <ui-icon
+        :type="isFull ? 'lessen' : 'full'"
+        ctrl
+        @click="customMap.sysView = isFull ? 'auto' : 'full'"
+      />
+    </GraphicAction>
   </MainPanel>
 </template>
 
 <script lang="ts" setup>
 import MainPanel from '@/components/main-panel/index.vue'
-import {computed, onMounted, ref} from "vue";
-import { setCanvas, UIType, uiType } from "@/hook/useGraphic";
+import ChildMenus from "@/views/graphic/childMenus.vue";
+import Header from './header.vue'
+import Container from './container.vue'
+import GraphicAction from '@/components/graphic-action/index.vue'
+import UiIcon from "@/components/base/components/icon/index.vue";
 
-const drawCanvasRef = ref<HTMLCanvasElement>();
-const setCanvasSize = () => {
-  drawCanvasRef.value.width = drawCanvasRef.value.offsetWidth;
-  drawCanvasRef.value.height = drawCanvasRef.value.offsetHeight;
-};
+import { computed, ref } from "vue";
+import { uiType, currentVector } from "@/hook/useGraphic";
+import { customMap } from '@/hook'
+import { generateByMenus, findMainMenuByExtend, MenusRaw, UITypeExtend } from './menus'
 
-const menusRaw = ref([
-  { key: UIType.Road, text: "直路" },
-  { key: UIType.CurveRoad, text: "弯路" },
-  { key: UIType.Tag, text: "标注" },
-  { key: UIType.MeasureLine, text: "测量线" },
-  { key: UIType.Img, text: "背景图片" },
-]);
-const menus = computed(() =>
-  menusRaw.value.map((menu) => ({
-    title: menu.text,
-    name: menu.key,
-    isRoute: false,
-    icon: 'menu',
-    onClick: () => uiType.change(menu.key as any)
-  }))
+const isFull = computed(() => customMap.sysView === 'full' )
+const extendMenus = ref<MenusRaw>();
+const menus = generateByMenus((mainMenuRaw) => ({
+  title: mainMenuRaw.text,
+  name: mainMenuRaw.key,
+  isRoute: false,
+  icon: 'menu',
+  bottom:  mainMenuRaw.key === UITypeExtend.photo,
+  onClick: () => {
+    if (mainMenuRaw.extend) {
+      extendMenus.value = mainMenuRaw.extend
+    } else {
+      uiType.change(mainMenuRaw.key as any)
+    }
+  }
+}))
+const activeMenuKey = computed(() =>
+  extendMenus.value
+    ? findMainMenuByExtend(extendMenus.value)?.key
+    : uiType.current
 )
 
-
-onMounted(() => {
-  setCanvasSize();
-  setCanvas(drawCanvasRef.value);
-});
 </script>
 
-<style lang="scss">
-@import url("./style.scss");
-</style>
+<style lang="scss" scoped>
+.full-action {
+  right: 24px;
+  bottom: 26px;
+  width: 64px;
+  font-size: 22px;
+  justify-content: center;
+}
+</style>

+ 118 - 0
src/views/graphic/menus.ts

@@ -0,0 +1,118 @@
+import {UIType} from "@/hook/useGraphic";
+import { toRaw } from 'vue'
+
+export const UITypeExtend = {
+  structure: "structure",
+  template: "template",
+  photo: "photo",
+  setup: "setup"
+}
+
+export type MenuRaw = {
+  key: string, text: string,
+  icon?: string,
+  children?: MenusRaw
+  extend?: MenusRaw
+}
+export type MenusRaw = Array<MenuRaw>
+
+
+export const structureMenusRaw = [
+  { key: UIType.CurveRoad, text: "支路" },
+  { key: UIType.CurveRoad, text: "窄路" },
+  { key: UIType.CurveRoad, text: "路肩" },
+  { key: UIType.CurveRoad, text: "斑马线" },
+  { key: UIType.CurveRoad, text: "桥" },
+  { key: UIType.CurveRoad, text: "隧道" },
+  { key: UIType.CurveRoad, text: "人行道" },
+  { key: UIType.CurveRoad, text: "施工路段" },
+  { key: UIType.CurveRoad, text: "下坡" },
+  { key: UIType.CurveRoad, text: "上坡" },
+  { key: UIType.CurveRoad, text: "路边水沟" },
+  { key: UIType.CurveRoad, text: "道路与铁..." },
+  { key: UIType.CurveRoad, text: "消火栓井" },
+  { key: UIType.CurveRoad, text: "雨水口" },
+  { key: UIType.CurveRoad, text: "路面凹坑" },
+  { key: UIType.CurveRoad, text: "路面凸出..." },
+  { key: UIType.CurveRoad, text: "路面积水" },
+]
+
+export const templateMenusRaw = [
+  { key: UIType.CurveRoad, text: "s型弯路" },
+  { key: UIType.CurveRoad, text: "丁字路口" },
+  { key: UIType.CurveRoad, text: "五岔路口" },
+  { key: UIType.CurveRoad, text: "出口匝道" },
+  { key: UIType.CurveRoad, text: "十字路口" },
+  { key: UIType.CurveRoad, text: "国道(路肩)" },
+  { key: UIType.CurveRoad, text: "室内路段" },
+  { key: UIType.CurveRoad, text: "弯道" },
+  { key: UIType.CurveRoad, text: "急转弯道" },
+  { key: UIType.CurveRoad, text: "六岔路口" },
+  { key: UIType.CurveRoad, text: "宽变窄路段" },
+  { key: UIType.CurveRoad, text: "直角弯道" },
+  { key: UIType.CurveRoad, text: "进口砸到" },
+  { key: UIType.CurveRoad, text: "高速收费站" },
+  { key: UIType.CurveRoad, text: "高速港湾" },
+  { key: UIType.CurveRoad, text: "高速路段" }
+]
+
+export const mainMenusRaw: MenusRaw = [
+  { key: UIType.Road, text: "画线" },
+  {
+    key: UIType.Road,
+    text: "道路",
+    children: [
+      { key: UIType.Road, text: "画线" },
+      { key: UIType.Road, text: "画线" },
+      { key: UIType.Road, text: "画线" },
+      { key: UIType.Road, text: "画线" },
+      { key: UIType.Road, text: "画线" },
+      { key: UIType.Road, text: "画线" },
+      { key: UITypeExtend.structure, text: "道路结构", extend: structureMenusRaw },
+      { key: UITypeExtend.template, text: "道路模板", extend: templateMenusRaw },
+    ]
+  },
+  { key: UIType.CurveRoad, text: "弯路" },
+  { key: UIType.CurveRoad, text: "图例" },
+  { key: UIType.MeasureLine, text: "测量" },
+  { key: UIType.Tag, text: "文字" },
+  { key: UIType.MeasureLine, text: "放大镜" },
+  { key: UITypeExtend.photo, text: "照片库" },
+  { key: UITypeExtend.setup, text: "设置" },
+];
+
+export const headActionMenuRaw = [
+  { key: UIType.Road, text: "画线" },
+  { key: UIType.Road, text: "画线" },
+  { key: UIType.Road, text: "画线" },
+  { key: UIType.Road, text: "画线" },
+  { key: UIType.Road, text: "画线" }
+]
+
+
+
+export const isUITypeExtend = (key: string) =>
+  Object.values(UITypeExtend).some(v => v === key)
+
+type GenerateResult<T extends {}> = Array<T & { children: GenerateResult<T> }>;
+export const generateByMenus = <T>(
+  generateFn: (men: MenuRaw) => T,
+  mainMenus: MenusRaw = mainMenusRaw
+): GenerateResult<T>  => {
+  return mainMenus.map(mainMenu => ({
+    ...generateFn(mainMenu),
+    children: mainMenu.children ? generateByMenus(generateFn, mainMenu.children) : []
+  }))
+}
+export const findMainMenuByExtend = (extend: MenusRaw, mainMenus = mainMenusRaw) => {
+  for (const mainMenu of mainMenus) {
+    if (toRaw(mainMenu.extend) === toRaw(extend)) {
+      return mainMenu
+    } else if (mainMenu.children) {
+      const childMainMenu = findMainMenuByExtend(extend, mainMenu.children)
+      if (childMainMenu) {
+        return childMainMenu;
+      }
+    }
+  }
+}

+ 0 - 36
src/views/graphic/style.scss

@@ -1,36 +0,0 @@
-.draw-layout {
-  width  : 100%;
-  height : 100%;
-  display: flex;
-}
-
-.draw-slide {
-  flex        : 0 0 100px;
-  padding     : 10px 0;
-  border-right: 1px solid #ccc;
-
-  >div {
-    font-size  : 14px;
-    text-align : center;
-    cursor     : pointer;
-    line-height: 30px;
-
-    &:hover {
-      color: #1890ff;
-    }
-
-    &.active {
-      background-color: #e6f7ff;
-    }
-  }
-}
-
-
-.draw-layout {
-  flex: 1;
-}
-
-.draw-canvas {
-  width : 100%;
-  height: 100%;
-}

+ 0 - 7
src/views/sys/menu/index.vue

@@ -17,13 +17,6 @@
         </template>
       </Item>
     </template>
-    <template #attach>
-      <div class="logo">
-        <a :href="void 0" target="_blank">
-          <img src="@/assets/images/Logo_4DVison.png" />
-        </a>
-      </div>
-    </template>
   </ui-editor-menu>
 </template>
 

+ 9 - 9
src/views/sys/menu/item/index.vue

@@ -27,20 +27,20 @@
       </template>
     </menu-child-item>
   </ui-menu-item>
-  <div
+  <ui-menu-item
     v-else
     v-for="(raw, index) in attrs.menu"
-    class="menu-children-item menu-item"
-    :data-route-name="raw.name"
-    :class="{ active: active.includes(raw.name) }"
+    :text="raw.title"
+    :icon="raw.icon"
+    :active="active.includes(raw.name)"
     :ref="attrs.children[index].menuRef"
-    @mouseenter="enterHandler(index)"
-    @mouseleave="leaveHandler(index)"
+    :data-route-name="raw.name"
+    class="menu-item child-menu-item"
+    @enter="enterHandler(index)"
+    @leave="leaveHandler(index)"
     @click="selectHandler(index, raw)"
   >
     <slot name="attach" :raw="raw"></slot>
-    <ui-icon :type="raw.icon" size="18px" :svg="raw.svg" />
-    <span>{{ raw.title }}</span>
     <menu-child-item
       v-bind="getItemProps(index)"
       @enter="enterHandler(index)"
@@ -51,7 +51,7 @@
         <slot name="attach" :raw="raw" :active="active"></slot>
       </template>
     </menu-child-item>
-  </div>
+  </ui-menu-item>
 </template>
 
 <script setup lang="ts">

+ 4 - 2
src/views/sys/menu/item/item.vue

@@ -53,14 +53,16 @@ const emit = defineEmits<{
 .menu-children {
   backdrop-filter: blur(4px);
   background-color: var(--editor-menu-back);
-  transform: translateY(calc(-50% + 30px)) scaleY(0);
+  transform: scaleY(0);
   opacity: 0;
   transform-origin: center top;
   transition: transform 0.3s ease, opacity 0.3s ease;
+  height: calc(100% - var(--editor-head-height));
+  top: var(--editor-head-height) !important;
 }
 
 .menu-children.show {
-  transform: translateY(calc(-50% + 30px)) scaleY(1);
+  transform: scaleY(1);
   opacity: 1;
 }
 </style>

+ 6 - 0
src/views/sys/menu/item/style.scss

@@ -9,6 +9,12 @@
   }
 }
 
+.child-menu-item {
+  height: 80px;
+  width: 80px;
+  font-size: 12px;
+}
+
 .menu-children-item {
   width: 240px;
   height: 60px;

+ 1 - 0
src/views/sys/menu/menu.ts

@@ -22,6 +22,7 @@ export type MenuAtom<T extends ModeFlag = any, K extends Excess = any> = {
   name: RouteNameRaw<T>[keyof RouteNameRaw<T>] | keyof K;
   isRoute: boolean;
   icon: string;
+  bottom?: boolean
   onClick?: () => void;
   children?: MenuRaw<T>;
 };