MenuItem.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <template>
  2. <div class="menu-item-wrapper" :class="[`theme-${theme}`]">
  3. <div
  4. class="menu-row"
  5. :class="[
  6. {
  7. 'is-leaf': !hasChildren,
  8. 'is-folder': hasChildren,
  9. 'is-root': level === 0,
  10. 'is-sub': level > 0
  11. },
  12. 'level-' + level
  13. ]"
  14. :style="{ paddingLeft: level * 20 + 15 + 'px' }"
  15. @click="handleClick"
  16. >
  17. <i v-if="node.icon" :class="node.icon" class="node-icon"></i>
  18. <span class="node-label">
  19. <slot name="label" :node="node">
  20. {{ node.label }}
  21. </slot>
  22. </span>
  23. <span
  24. v-if="hasChildren"
  25. class="arrow-icon"
  26. :class="{ 'is-open': isOpen }"
  27. >
  28. <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>
  29. </span>
  30. </div>
  31. <div class="menu-children" v-show="isOpen" v-if="hasChildren">
  32. <MenuItem
  33. v-for="child in node.children"
  34. :key="child.id"
  35. :node="child"
  36. :level="level + 1"
  37. :theme="theme"
  38. @node-click="passEventUp"
  39. >
  40. <template #label="{ node: innerNode }">
  41. <slot name="label" :node="innerNode"></slot>
  42. </template>
  43. </MenuItem>
  44. </div>
  45. </div>
  46. </template>
  47. <script>
  48. export default {
  49. name: 'MenuItem',
  50. props: {
  51. node: {
  52. type: Object,
  53. required: true
  54. },
  55. level: {
  56. type: Number,
  57. default: 0
  58. },
  59. // 可选值:'tech' (科技渐变) | 'dark' (极简暗色)
  60. theme: {
  61. type: String,
  62. default: 'tech'
  63. }
  64. },
  65. data() {
  66. return {
  67. isOpen: true
  68. };
  69. },
  70. computed: {
  71. hasChildren() {
  72. return this.node.children && this.node.children.length > 0;
  73. }
  74. },
  75. methods: {
  76. handleClick() {
  77. if (this.hasChildren) {
  78. this.isOpen = !this.isOpen;
  79. }
  80. this.$emit('node-click', this.node);
  81. },
  82. passEventUp(nodeData) {
  83. this.$emit('node-click', nodeData);
  84. }
  85. }
  86. };
  87. </script>
  88. <style scoped>
  89. .menu-item-wrapper {
  90. color: #fff;
  91. font-size: 14px;
  92. }
  93. /* ================== 公共基础样式 ================== */
  94. .menu-row {
  95. display: flex;
  96. align-items: center;
  97. min-height: 44px;
  98. max-height: 56px;
  99. padding: 12px 20px;
  100. cursor: pointer;
  101. border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  102. background: #081734;
  103. transition: all 0.3s ease;
  104. user-select: none;
  105. }
  106. .node-icon { margin-right: 8px; font-size: 16px; }
  107. .node-label { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  108. .arrow-icon { margin-left: 10px; display: flex; justify-content: center; align-items: center; color: #909399; transform: rotate(180deg); transition: transform 0.3s ease; }
  109. .arrow-icon.is-open { transform: rotate(0deg); }
  110. /* ================== 风格 A: 科技渐变风 (theme-tech) ================== */
  111. /* 1. 最外层父节点 (level: 0) */
  112. .theme-tech.menu-item-wrapper {
  113. background: linear-gradient( 180deg, rgba(5,20,46,0) 0%, #05142E 100%);
  114. }
  115. .theme-tech .menu-row.level-0 {
  116. font-size: 20px;
  117. line-height: 28px;
  118. background: linear-gradient( 180deg, rgba(119,161,255,0) 0%, #77A1FF 100%);
  119. border-radius: 2px 0px 0px 2px;
  120. border: 2px solid rgba(161,190,255,0.7);
  121. font-weight: bold;
  122. position: relative;
  123. z-index: 10;
  124. box-sizing: border-box;
  125. }
  126. /* 2. 第二层子节点 (level: 1) */
  127. .theme-tech .menu-row.level-1 {
  128. background: #0f224350;
  129. font-size: 18px;
  130. line-height: 25px;
  131. color: #ffffff;
  132. }
  133. /* 3. 包含子节点的目录加粗 */
  134. .theme-tech .menu-row.is-folder {
  135. font-weight: bold;
  136. font-size: 18px;
  137. line-height: 25px;
  138. color: #ffffff;
  139. }
  140. .theme-tech .menu-row.level-2 {
  141. font-size: 18px;
  142. line-height: 25px;
  143. color: rgba(255,255,255, 0.8);
  144. }
  145. /* 4. 叶子节点字体颜色 */
  146. .theme-tech .menu-row.is-leaf {
  147. font-size: 18px;
  148. line-height: 25px;
  149. font-weight: normal;
  150. color: #ffffff;
  151. background: rgba(8,23,51,0.9);
  152. color: rgba(255,255,255, 0.5);
  153. }
  154. /* 5. 悬浮交互 */
  155. .theme-tech .menu-row:hover {
  156. background: #3760A9;
  157. color: #ffffff;
  158. }
  159. .theme-tech .menu-row.is-leaf:hover { color: #ffffff; }
  160. /* ================== 风格 B: 极简暗色风 (theme-dark) ================== */
  161. /* 1. 主控中心标题 (顶级菜单 level: 0) */
  162. .theme-dark .menu-row.is-root {
  163. background-color: #112445;
  164. color: #ffffff;
  165. font-weight: bold;
  166. font-size: 20px;
  167. line-height: 28px;
  168. }
  169. /* 2. 其他子菜单 (level > 0) */
  170. .theme-dark .menu-row.is-sub {
  171. background-color: #0b1a37;
  172. color: #ffffff ;
  173. font-size: 18px;
  174. line-height: 25px;
  175. }
  176. /* 3. 叶子节点颜色 */
  177. .theme-dark .menu-row.is-leaf {
  178. color: #6b7280;
  179. font-weight: normal;
  180. }
  181. /* 4. 悬浮交互 */
  182. .theme-dark .menu-row:hover {
  183. background-color: rgba(0, 229, 255, 0.1);
  184. color: #00e5ff;
  185. }
  186. .theme-dark .menu-row.is-leaf:hover { color: #ffffff; }
  187. </style>