feat:菜单完成修复
This commit is contained in:
parent
157491c6f4
commit
36a217aec9
@ -46,7 +46,7 @@ export type MenuNode = {
|
||||
// 新增父级菜单
|
||||
export function addParentMenu(data: Omit<RawMenu, "uuid" | "parentId">) {
|
||||
return http({
|
||||
url: "/api/Menu/create-parent",
|
||||
url: "/api/Menu/createParent",
|
||||
method: "POST",
|
||||
data,
|
||||
});
|
||||
@ -54,7 +54,7 @@ export function addParentMenu(data: Omit<RawMenu, "uuid" | "parentId">) {
|
||||
// 编辑父级菜单
|
||||
export function editParentMenu(data: Omit<RawMenu, "parentId">) {
|
||||
return http({
|
||||
url: "/api/Menu/update-parent",
|
||||
url: "/api/Menu/updateParent",
|
||||
method: "PUT",
|
||||
data,
|
||||
});
|
||||
@ -62,7 +62,7 @@ export function editParentMenu(data: Omit<RawMenu, "parentId">) {
|
||||
// 新增子级菜单
|
||||
export function addChildMenu(data:Omit<RawMenu, "uuid">){
|
||||
return http({
|
||||
url:'/api/Menu/create-child',
|
||||
url:'/api/Menu/createChild',
|
||||
method:'POST',
|
||||
data
|
||||
})
|
||||
@ -70,7 +70,7 @@ export function addChildMenu(data:Omit<RawMenu, "uuid">){
|
||||
// 编辑子级菜单
|
||||
export function eidtChildMenu(data:RawMenu){
|
||||
return http({
|
||||
url:'/api/Menu/update-child',
|
||||
url:'/api/Menu/updateChild',
|
||||
method:'PUT',
|
||||
data
|
||||
})
|
||||
@ -85,7 +85,7 @@ export function getAllMenu():Promise<MenuTree[]>{
|
||||
// 递归删除菜单
|
||||
export function deleteMenu(uuid:string){
|
||||
return http({
|
||||
url:`/api/Menu/all/${uuid}`,
|
||||
url:`/api/Menu/delete/${uuid}`,
|
||||
method:'DELETE'
|
||||
})
|
||||
}
|
@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751689452739" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6754" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M510.0032 492.9536c-12.16 0-24.3456-2.3808-35.9424-7.1168L71.7824 300.5952c-11.3408-4.6336-18.816-15.5648-19.072-27.8016s6.784-23.4496 17.92-28.544l400.1536-183.296a100.1728 100.1728 0 0 1 82.7648-0.3072l398.1056 178.8416c11.1616 5.0176 18.2784 16.1792 18.1248 28.416s-7.5776 23.2192-18.8672 27.9296l-404.3264 189.7216a94.26944 94.26944 0 0 1-36.5824 7.3984zM160.64 270.592l336.7424 158.3872a33.0496 33.0496 0 0 0 25.5232-0.1024l338.9952-162.3808-333.5424-149.8112a38.6688 38.6688 0 0 0-32 0.128L160.64 270.592z" fill="#3D8EFF" p-id="6755"></path><path d="M510.1568 722.7648c-14.4896 0-28.9536-3.3536-42.1632-10.0864L68.3264 509.0816c-8.576-4.3776-15.0784-12.2624-16.9472-21.6832a30.7072 30.7072 0 0 1 15.0272-32.8192l189.6704-106.9568c19.5584-11.0336 44.3136-4.1216 55.3472 15.4368l10.2144 18.1248-175.1808 98.7904L495.872 657.92a31.5392 31.5392 0 0 0 28.672-0.0512l354.0224-181.9136-175.9488-100.736 10.3424-18.0736c11.1616-19.4816 35.968-26.2144 55.4496-15.0784l189.7984 108.672c9.7792 5.6064 15.6928 16.1024 15.4368 27.3664s-6.656 21.4784-16.6656 26.624l-404.352 207.7696a92.53376 92.53376 0 0 1-42.4704 10.2656z" fill="#3D8EFF" p-id="6756"></path><path d="M512.2048 975.9744c-4.7872 0-9.5744-1.1264-13.952-3.3536L70.2464 754.5856c-8.576-4.3776-15.0784-12.2624-16.9472-21.7088a30.72512 30.72512 0 0 1 15.0272-32.8192l192.1536-108.3648c18.176-10.24 41.2416-3.8144 51.4816 14.3616l11.6224 20.608-175.1808 98.7904 363.7504 185.2928 368.3584-189.2608-175.9488-100.736 11.7504-20.5312c10.368-18.1248 33.4592-24.3968 51.584-14.0288l192.2816 110.08c9.7792 5.6064 15.6928 16.1024 15.4368 27.3664a30.72 30.72 0 0 1-16.6656 26.624l-432.6656 222.3104a31.0272 31.0272 0 0 1-14.08 3.4048z" fill="#3D8EFF" p-id="6757"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751689452739" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6754" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M510.0032 492.9536c-12.16 0-24.3456-2.3808-35.9424-7.1168L71.7824 300.5952c-11.3408-4.6336-18.816-15.5648-19.072-27.8016s6.784-23.4496 17.92-28.544l400.1536-183.296a100.1728 100.1728 0 0 1 82.7648-0.3072l398.1056 178.8416c11.1616 5.0176 18.2784 16.1792 18.1248 28.416s-7.5776 23.2192-18.8672 27.9296l-404.3264 189.7216a94.26944 94.26944 0 0 1-36.5824 7.3984zM160.64 270.592l336.7424 158.3872a33.0496 33.0496 0 0 0 25.5232-0.1024l338.9952-162.3808-333.5424-149.8112a38.6688 38.6688 0 0 0-32 0.128L160.64 270.592z" p-id="6755"></path><path d="M510.1568 722.7648c-14.4896 0-28.9536-3.3536-42.1632-10.0864L68.3264 509.0816c-8.576-4.3776-15.0784-12.2624-16.9472-21.6832a30.7072 30.7072 0 0 1 15.0272-32.8192l189.6704-106.9568c19.5584-11.0336 44.3136-4.1216 55.3472 15.4368l10.2144 18.1248-175.1808 98.7904L495.872 657.92a31.5392 31.5392 0 0 0 28.672-0.0512l354.0224-181.9136-175.9488-100.736 10.3424-18.0736c11.1616-19.4816 35.968-26.2144 55.4496-15.0784l189.7984 108.672c9.7792 5.6064 15.6928 16.1024 15.4368 27.3664s-6.656 21.4784-16.6656 26.624l-404.352 207.7696a92.53376 92.53376 0 0 1-42.4704 10.2656z" p-id="6756"></path><path d="M512.2048 975.9744c-4.7872 0-9.5744-1.1264-13.952-3.3536L70.2464 754.5856c-8.576-4.3776-15.0784-12.2624-16.9472-21.7088a30.72512 30.72512 0 0 1 15.0272-32.8192l192.1536-108.3648c18.176-10.24 41.2416-3.8144 51.4816 14.3616l11.6224 20.608-175.1808 98.7904 363.7504 185.2928 368.3584-189.2608-175.9488-100.736 11.7504-20.5312c10.368-18.1248 33.4592-24.3968 51.584-14.0288l192.2816 110.08c9.7792 5.6064 15.6928 16.1024 15.4368 27.3664a30.72 30.72 0 0 1-16.6656 26.624l-432.6656 222.3104a31.0272 31.0272 0 0 1-14.08 3.4048z" p-id="6757"></path></svg>
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751689762805" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11546" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M720.213333 269.653333c30.72 0 51.2-20.48 54.613334-47.786666v-170.666667c0-27.306667-20.48-47.786667-51.2-47.786667-27.306667 0-51.2 20.48-51.2 47.786667v170.666667c0 23.893333 20.48 44.373333 47.786666 47.786666zM300.373333 269.653333c30.72 0 51.2-20.48 54.613334-47.786666v-170.666667C351.573333 23.893333 331.093333 0 303.786667 0 273.066667 0 252.586667 20.48 252.586667 47.786667v170.666666c0 27.306667 20.48 47.786667 47.786666 51.2zM409.6 85.333333h201.386667v102.4H409.6z" fill="#FFFFFF" p-id="11547"></path><path d="M890.88 85.333333h-61.44v102.4H887.466667c20.48 0 34.133333 17.066667 34.133333 34.133334v215.04l3.413333 447.146666c0 20.48-17.066667 37.546667-37.546666 37.546667H136.533333c-20.48 0-37.546667-17.066667-37.546666-37.546667L102.4 436.906667V221.866667c0-17.066667 13.653333-34.133333 34.133333-34.133334h58.026667v-102.4H133.12C61.44 85.333333 0 143.36 0 218.453333v672.426667C0 962.56 61.44 1024 133.12 1024h757.76c71.68 0 133.12-61.44 133.12-133.12V218.453333c0-75.093333-61.44-133.12-133.12-133.12z" fill="#FFFFFF" p-id="11548"></path><path d="M682.666667 624.64c27.306667 0 51.2-23.893333 51.2-51.2s-23.893333-51.2-51.2-51.2h-47.786667l81.92-81.92c20.48-20.48 20.48-51.2 0-71.68-20.48-20.48-51.2-20.48-71.68 0L512 498.346667l-133.12-133.12c-20.48-20.48-51.2-20.48-71.68 0-20.48 20.48-20.48 51.2 0 71.68l81.92 81.92H341.333333c-27.306667 0-51.2 23.893333-51.2 51.2s23.893333 51.2 51.2 51.2h119.466667V682.666667H341.333333c-27.306667 0-51.2 23.893333-51.2 51.2S314.026667 785.066667 341.333333 785.066667h119.466667v37.546666c0 27.306667 23.893333 51.2 51.2 51.2s51.2-23.893333 51.2-51.2V785.066667H682.666667c27.306667 0 51.2-23.893333 51.2-51.2S709.973333 682.666667 682.666667 682.666667h-119.466667v-61.44H682.666667z" fill="#FFFFFF" p-id="11549"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751689762805" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11546" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M720.213333 269.653333c30.72 0 51.2-20.48 54.613334-47.786666v-170.666667c0-27.306667-20.48-47.786667-51.2-47.786667-27.306667 0-51.2 20.48-51.2 47.786667v170.666667c0 23.893333 20.48 44.373333 47.786666 47.786666zM300.373333 269.653333c30.72 0 51.2-20.48 54.613334-47.786666v-170.666667C351.573333 23.893333 331.093333 0 303.786667 0 273.066667 0 252.586667 20.48 252.586667 47.786667v170.666666c0 27.306667 20.48 47.786667 47.786666 51.2zM409.6 85.333333h201.386667v102.4H409.6z" p-id="11547"></path><path d="M890.88 85.333333h-61.44v102.4H887.466667c20.48 0 34.133333 17.066667 34.133333 34.133334v215.04l3.413333 447.146666c0 20.48-17.066667 37.546667-37.546666 37.546667H136.533333c-20.48 0-37.546667-17.066667-37.546666-37.546667L102.4 436.906667V221.866667c0-17.066667 13.653333-34.133333 34.133333-34.133334h58.026667v-102.4H133.12C61.44 85.333333 0 143.36 0 218.453333v672.426667C0 962.56 61.44 1024 133.12 1024h757.76c71.68 0 133.12-61.44 133.12-133.12V218.453333c0-75.093333-61.44-133.12-133.12-133.12z" p-id="11548"></path><path d="M682.666667 624.64c27.306667 0 51.2-23.893333 51.2-51.2s-23.893333-51.2-51.2-51.2h-47.786667l81.92-81.92c20.48-20.48 20.48-51.2 0-71.68-20.48-20.48-51.2-20.48-71.68 0L512 498.346667l-133.12-133.12c-20.48-20.48-51.2-20.48-71.68 0-20.48 20.48-20.48 51.2 0 71.68l81.92 81.92H341.333333c-27.306667 0-51.2 23.893333-51.2 51.2s23.893333 51.2 51.2 51.2h119.466667V682.666667H341.333333c-27.306667 0-51.2 23.893333-51.2 51.2S314.026667 785.066667 341.333333 785.066667h119.466667v37.546666c0 27.306667 23.893333 51.2 51.2 51.2s51.2-23.893333 51.2-51.2V785.066667H682.666667c27.306667 0 51.2-23.893333 51.2-51.2S709.973333 682.666667 682.666667 682.666667h-119.466667v-61.44H682.666667z" p-id="11549"></path></svg>
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.0 KiB |
@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751687644150" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5527" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M701.3376 147.2c66.048 0 118.8608 55.5008 121.6512 123.904l0.128 5.7344v444.7232c0 69.12-51.0208 126.464-116.3008 129.5104l-5.4784 0.128H322.6624c-66.048 0-118.8608-55.5008-121.6512-123.904l-0.128-5.7344v-83.5328a32 32 0 0 1 63.8464-3.2768l0.1536 3.2768v83.5328c0 35.328 24.4224 63.3088 54.0416 65.5104l3.7376 0.128h378.6752c30.0288 0 55.6544-26.5472 57.6512-61.2608l0.128-4.3776V276.8384c0-35.328-24.4224-63.3088-54.0416-65.5104l-3.7376-0.128H322.6624c-30.0288 0-55.6544 26.5472-57.6512 61.2608l-0.128 4.3776v211.1488a32 32 0 0 1-63.8208 3.2768l-0.1792-3.2768v-211.1488c0-69.12 51.0208-126.464 116.3008-129.5104l5.4784-0.128h378.6752z" fill="#FFFFFF" p-id="5528"></path><path d="M440.2688 352.512a25.6 25.6 0 0 1 39.2448 32.7168l-2.048 2.4832-56.2176 59.4176a25.6 25.6 0 0 1-32.4096 3.968l-2.4832-1.8176-36.608-30.2592a25.6 25.6 0 0 1 30.0032-41.3696l2.5856 1.8944 18.1504 15.0016 39.7824-42.0352zM440.2688 561.664a25.6 25.6 0 0 1 39.2448 32.7168l-2.048 2.4576-56.2176 59.4432a25.6 25.6 0 0 1-32.4096 3.9424l-2.4832-1.792-36.608-30.2848a25.6 25.6 0 0 1 30.0032-41.344l2.5856 1.8944 18.1504 14.976 39.7824-42.0096zM668.5696 384a25.6 25.6 0 0 1 2.9952 51.0208l-2.9952 0.1792h-138.24a25.6 25.6 0 0 1-2.9696-51.0208l2.9696-0.1792h138.24zM668.5696 583.3728a25.6 25.6 0 0 1 2.9952 51.0208l-2.9952 0.1792h-142.08a25.6 25.6 0 0 1-2.9696-51.0464l2.9696-0.1536h142.08z" fill="#FFFFFF" p-id="5529"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751687644150" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5527" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M701.3376 147.2c66.048 0 118.8608 55.5008 121.6512 123.904l0.128 5.7344v444.7232c0 69.12-51.0208 126.464-116.3008 129.5104l-5.4784 0.128H322.6624c-66.048 0-118.8608-55.5008-121.6512-123.904l-0.128-5.7344v-83.5328a32 32 0 0 1 63.8464-3.2768l0.1536 3.2768v83.5328c0 35.328 24.4224 63.3088 54.0416 65.5104l3.7376 0.128h378.6752c30.0288 0 55.6544-26.5472 57.6512-61.2608l0.128-4.3776V276.8384c0-35.328-24.4224-63.3088-54.0416-65.5104l-3.7376-0.128H322.6624c-30.0288 0-55.6544 26.5472-57.6512 61.2608l-0.128 4.3776v211.1488a32 32 0 0 1-63.8208 3.2768l-0.1792-3.2768v-211.1488c0-69.12 51.0208-126.464 116.3008-129.5104l5.4784-0.128h378.6752z" p-id="5528"></path><path d="M440.2688 352.512a25.6 25.6 0 0 1 39.2448 32.7168l-2.048 2.4832-56.2176 59.4176a25.6 25.6 0 0 1-32.4096 3.968l-2.4832-1.8176-36.608-30.2592a25.6 25.6 0 0 1 30.0032-41.3696l2.5856 1.8944 18.1504 15.0016 39.7824-42.0352zM440.2688 561.664a25.6 25.6 0 0 1 39.2448 32.7168l-2.048 2.4576-56.2176 59.4432a25.6 25.6 0 0 1-32.4096 3.9424l-2.4832-1.792-36.608-30.2848a25.6 25.6 0 0 1 30.0032-41.344l2.5856 1.8944 18.1504 14.976 39.7824-42.0096zM668.5696 384a25.6 25.6 0 0 1 2.9952 51.0208l-2.9952 0.1792h-138.24a25.6 25.6 0 0 1-2.9696-51.0208l2.9696-0.1792h138.24zM668.5696 583.3728a25.6 25.6 0 0 1 2.9952 51.0208l-2.9952 0.1792h-142.08a25.6 25.6 0 0 1-2.9696-51.0464l2.9696-0.1536h142.08z" p-id="5529"></path></svg>
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@ -24,4 +24,7 @@
|
||||
&.right{
|
||||
justify-content: right;
|
||||
}
|
||||
&.between{
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,8 @@ const iconName = computed(() => `#icon-${props.iconClass}`);
|
||||
const svgStyle = computed(() => ({
|
||||
width: typeof props.width === 'number' ? `${props.width}px` : props.width,
|
||||
height: typeof props.height === 'number' ? `${props.height}px` : props.height,
|
||||
fill: props.color
|
||||
fill: props.color + ' !important',
|
||||
'--icon-color': props.color
|
||||
}));
|
||||
</script>
|
||||
|
||||
@ -48,5 +49,12 @@ const svgStyle = computed(() => ({
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
|
||||
:deep(path) {
|
||||
fill: var(--icon-color) !important;
|
||||
}
|
||||
:deep(use) {
|
||||
fill: var(--icon-color) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,4 +1,9 @@
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
||||
import {
|
||||
createRouter,
|
||||
createWebHistory,
|
||||
RouteRecordRaw,
|
||||
RouterView,
|
||||
} from "vue-router";
|
||||
import NProgress from "nprogress"; // progress bar
|
||||
import "nprogress/nprogress.css"; // progress bar style
|
||||
|
||||
@ -36,9 +41,16 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
path: "role", // 匹配 /layout/role
|
||||
name: "roleAuth",
|
||||
component: Auth,
|
||||
path: "role",
|
||||
name: "Role",
|
||||
component: RouterView,
|
||||
children: [
|
||||
{
|
||||
path: "auth", // 匹配 /layout/role
|
||||
name: "roleAuth",
|
||||
component: Auth,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "menu",
|
||||
|
59
src/utils/permission.ts
Normal file
59
src/utils/permission.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { RouteMeta, RouteRecordRaw, RouterView } from "vue-router";
|
||||
import NotFound from "@/views/404/index.vue"
|
||||
interface MenuItem {
|
||||
uuid: string;
|
||||
path: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
menuCode: string;
|
||||
adaptability: string;
|
||||
component: string;
|
||||
sort: number;
|
||||
status: string;
|
||||
query: string;
|
||||
parentId?: string;
|
||||
children?: MenuItem[];
|
||||
}
|
||||
function getComponent(componentPath: string) {
|
||||
// 处理view-router特殊路由
|
||||
if (componentPath === 'view-router') {
|
||||
return RouterView
|
||||
}
|
||||
|
||||
// 动态导入组件,添加错误处理
|
||||
return () => {
|
||||
try {
|
||||
// 尝试动态导入组件
|
||||
const componentPromise = import(`@/views/${componentPath}`)
|
||||
|
||||
// 成功加载则返回组件
|
||||
return componentPromise.catch(() => {
|
||||
console.error(`组件加载失败: @/views/${componentPath}, 回退到404页面`)
|
||||
return NotFound
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('动态导入组件时发生错误:', error)
|
||||
return NotFound
|
||||
}
|
||||
}
|
||||
}
|
||||
export function generateRoutes(menuList: MenuItem[]): RouteRecordRaw[] {
|
||||
return menuList.map((menu) => {
|
||||
const route: RouteRecordRaw = {
|
||||
path: menu.path,
|
||||
name: menu.path.toUpperCase(), //路径转大写
|
||||
meta: {
|
||||
...(menu.query ? JSON.parse(menu.query) : {})
|
||||
},
|
||||
component: getComponent(menu.component),
|
||||
children:[]
|
||||
};
|
||||
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
route.children = generateRoutes(menu.children);
|
||||
}
|
||||
|
||||
return route;
|
||||
});
|
||||
}
|
@ -1,81 +1,82 @@
|
||||
<template>
|
||||
<n-layout class="menu-management">
|
||||
<n-layout-header bordered class="menu-management__header">
|
||||
<n-space justify="space-between" align="center">
|
||||
<n-h2>菜单管理</n-h2>
|
||||
<n-button type="primary" @click="handleAddParent">
|
||||
<template #icon>
|
||||
<n-icon><PlusOutlined /></n-icon>
|
||||
</template>
|
||||
添加父级菜单
|
||||
</n-button>
|
||||
</n-space>
|
||||
<div class="title">菜单管理</div>
|
||||
<n-button type="primary" @click="handleAddParent" size="small">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Add />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加父级菜单
|
||||
</n-button>
|
||||
</n-layout-header>
|
||||
|
||||
<n-layout-content class="menu-management__content">
|
||||
<n-spin :show="loading">
|
||||
<n-tree
|
||||
block-line
|
||||
:data="menuTree"
|
||||
:render-label="renderTreeLabel"
|
||||
:render-switcher-icon="renderSwitcherIcon"
|
||||
:expanded-keys="expandedKeys"
|
||||
@update:expanded-keys="handleExpand"
|
||||
/>
|
||||
<n-tree :data="menuTree" key-field="uuid" label-field="label" children-field="children"
|
||||
:expanded-keys="expandedKeys" :render-label="renderTreeLabel" :render-switcher-icon="renderSwitcherIcon" />
|
||||
</n-spin>
|
||||
</n-layout-content>
|
||||
|
||||
<!-- 父级菜单表单弹窗 -->
|
||||
<n-modal v-model:show="showParentModal" preset="dialog" :title="parentModalTitle">
|
||||
<n-form
|
||||
ref="parentFormRef"
|
||||
:model="parentForm"
|
||||
:rules="parentRules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item label="菜单名称" path="label">
|
||||
<n-input v-model:value="parentForm.label" placeholder="请输入菜单名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单路径" path="path">
|
||||
<n-input v-model:value="parentForm.path" placeholder="请输入菜单路径" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单编码" path="menuCode">
|
||||
<n-input v-model:value="parentForm.menuCode" placeholder="请输入菜单编码" />
|
||||
</n-form-item>
|
||||
<n-form-item label="图标" path="icon">
|
||||
<n-input v-model:value="parentForm.icon" placeholder="请输入图标名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="组件路径" path="component">
|
||||
<n-input v-model:value="parentForm.component" placeholder="请输入组件路径" />
|
||||
</n-form-item>
|
||||
<n-form-item label="适配方式" path="adaptability">
|
||||
<n-select
|
||||
v-model:value="parentForm.adaptability"
|
||||
:options="adaptabilityOptions"
|
||||
placeholder="请选择适配方式"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="parentForm.sort" :min="0" />
|
||||
</n-form-item>
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="parentForm.status">
|
||||
<n-space>
|
||||
<n-radio value="enabled">启用</n-radio>
|
||||
<n-radio value="disabled">禁用</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="查询参数" path="query">
|
||||
<n-input
|
||||
v-model:value="parentForm.query"
|
||||
type="textarea"
|
||||
placeholder="请输入查询参数(JSON格式)"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-modal v-model:show="showParentModal" preset="dialog" style="width:640px" :title="parentModalTitle">
|
||||
<n-card style="width: 600px;">
|
||||
<n-form ref="parentFormRef" :model="parentForm" :rules="parentRules" label-placement="left" label-width="auto"
|
||||
require-mark-placement="right-hanging">
|
||||
<n-form-item label="菜单名称" path="label">
|
||||
<n-input v-model:value="parentForm.label" placeholder="请输入菜单名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单路径" path="path">
|
||||
<n-input v-model:value="parentForm.path" placeholder="请输入菜单路径" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单编码" path="menuCode">
|
||||
<n-input v-model:value="parentForm.menuCode" placeholder="请输入菜单编码" />
|
||||
</n-form-item>
|
||||
<n-form-item label="图标" path="icon">
|
||||
<n-select v-model:value="parentForm.icon" :options="iconOptions" filterable clearable placeholder="请选择图标名称"
|
||||
:render-label="renderLabel" />
|
||||
</n-form-item>
|
||||
<n-form-item label="组件路径" path="component">
|
||||
<template #label>
|
||||
组件路径
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="16">
|
||||
<HelpCircle />
|
||||
</n-icon>
|
||||
</template>
|
||||
/src/views/下的组件,比如/src/views/home/index.vue,就填写home/index.vue
|
||||
</n-tooltip>
|
||||
</template>
|
||||
<n-input-group>
|
||||
<n-input :style="{ width: '60%' }" v-model:value="parentForm.component" placeholder="请输入组件路径"
|
||||
:disabled="menuType === 'list'" />
|
||||
<n-radio-group :style="{ width: '40%' }" v-model:value="menuType">
|
||||
<n-radio-button label="菜单" value="menu" />
|
||||
<n-radio-button label="目录" value="list"></n-radio-button>
|
||||
</n-radio-group>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="适配方式" path="adaptability">
|
||||
<n-select v-model:value="parentForm.adaptability" :options="adaptabilityOptions" placeholder="请选择适配方式" />
|
||||
</n-form-item>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="parentForm.sort" :min="0" />
|
||||
</n-form-item>
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="parentForm.status">
|
||||
<n-space>
|
||||
<n-radio value="enable">启用</n-radio>
|
||||
<n-radio value="disabled">禁用</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="查询参数" path="query">
|
||||
<n-input v-model:value="parentForm.query" type="textarea" placeholder="请输入查询参数(JSON格式)" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<template #action>
|
||||
<n-space justify="end">
|
||||
<n-button @click="showParentModal = false">取消</n-button>
|
||||
@ -85,64 +86,65 @@
|
||||
</n-modal>
|
||||
|
||||
<!-- 子级菜单表单弹窗 -->
|
||||
<n-modal v-model:show="showChildModal" preset="dialog" :title="childModalTitle">
|
||||
<n-form
|
||||
ref="childFormRef"
|
||||
:model="childForm"
|
||||
:rules="childRules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item label="父级菜单" path="parentId">
|
||||
<n-select
|
||||
v-model:value="childForm.parentId"
|
||||
:options="parentMenuOptions"
|
||||
placeholder="请选择父级菜单"
|
||||
:disabled="isEditChild"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单名称" path="label">
|
||||
<n-input v-model:value="childForm.label" placeholder="请输入菜单名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单路径" path="path">
|
||||
<n-input v-model:value="childForm.path" placeholder="请输入菜单路径" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单编码" path="menuCode">
|
||||
<n-input v-model:value="childForm.menuCode" placeholder="请输入菜单编码" />
|
||||
</n-form-item>
|
||||
<n-form-item label="图标" path="icon">
|
||||
<n-input v-model:value="childForm.icon" placeholder="请输入图标名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="组件路径" path="component">
|
||||
<n-input v-model:value="childForm.component" placeholder="请输入组件路径" />
|
||||
</n-form-item>
|
||||
<n-form-item label="适配方式" path="adaptability">
|
||||
<n-select
|
||||
v-model:value="childForm.adaptability"
|
||||
:options="adaptabilityOptions"
|
||||
placeholder="请选择适配方式"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="childForm.sort" :min="0" />
|
||||
</n-form-item>
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="childForm.status">
|
||||
<n-space>
|
||||
<n-radio value="enabled">启用</n-radio>
|
||||
<n-radio value="disabled">禁用</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="查询参数" path="query">
|
||||
<n-input
|
||||
v-model:value="childForm.query"
|
||||
type="textarea"
|
||||
placeholder="请输入查询参数(JSON格式)"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-modal v-model:show="showChildModal" preset="dialog" :title="childModalTitle" style="width:680px">
|
||||
<n-card style="width: 640px;">
|
||||
<n-form ref="childFormRef" :model="childForm" :rules="childRules" label-placement="left" label-width="auto"
|
||||
require-mark-placement="right-hanging">
|
||||
<n-form-item label="父级菜单" path="parentId">
|
||||
<n-select v-model:value="childForm.parentId" :options="parentMenuOptions" placeholder="请选择父级菜单"
|
||||
:disabled="isEditChild" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单名称" path="label">
|
||||
<n-input v-model:value="childForm.label" placeholder="请输入菜单名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单路径" path="path">
|
||||
<n-input v-model:value="childForm.path" placeholder="请输入菜单路径" />
|
||||
</n-form-item>
|
||||
<n-form-item label="菜单编码" path="menuCode">
|
||||
<n-input v-model:value="childForm.menuCode" placeholder="请输入菜单编码" />
|
||||
</n-form-item>
|
||||
<n-form-item label="图标" path="icon">
|
||||
<n-select v-model:value="childForm.icon" :options="iconOptions" filterable clearable placeholder="请选择图标名称"
|
||||
:render-label="renderLabel" />
|
||||
</n-form-item>
|
||||
<n-form-item path="component">
|
||||
<template #label>
|
||||
组件路径
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="16">
|
||||
<HelpCircle />
|
||||
</n-icon>
|
||||
</template>
|
||||
/src/views/下的组件,比如/src/views/home/index.vue,就填写home/index.vue
|
||||
</n-tooltip>
|
||||
</template>
|
||||
<n-input :style="{ width: '60%' }" v-model:value="childForm.component" placeholder="请输入组件路径"
|
||||
:disabled="menuType === 'list'" />
|
||||
<n-radio-group :style="{ width: '40%' }" v-model:value="menuType">
|
||||
<n-radio-button label="菜单" value="menu" />
|
||||
<n-radio-button label="目录" value="list"></n-radio-button>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="适配方式" path="adaptability">
|
||||
<n-select v-model:value="childForm.adaptability" :options="adaptabilityOptions" placeholder="请选择适配方式" />
|
||||
</n-form-item>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="childForm.sort" :min="0" />
|
||||
</n-form-item>
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="childForm.status">
|
||||
<n-space>
|
||||
<n-radio value="enable">启用</n-radio>
|
||||
<n-radio value="disabled">禁用</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="查询参数" path="query">
|
||||
<n-input v-model:value="childForm.query" type="textarea" placeholder="请输入查询参数(JSON格式)" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<template #action>
|
||||
<n-space justify="end">
|
||||
<n-button @click="showChildModal = false">取消</n-button>
|
||||
@ -154,8 +156,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 首先确保已安装 @vicons/ionicons5
|
||||
import { Add, Create, Trash, Document, FolderOpen, Folder } from '@vicons/ionicons5'
|
||||
import { Add, Create, Trash, Document, FolderOpen, Folder, HelpCircle } from '@vicons/ionicons5'
|
||||
import {
|
||||
addParentMenu,
|
||||
editParentMenu,
|
||||
@ -167,7 +168,8 @@ import {
|
||||
type MenuNode,
|
||||
type RawMenu
|
||||
} from '@/api/menu'
|
||||
import { FormInst, FormRules, NButton, NIcon, NSpace, SelectOption, TreeOption } from 'naive-ui'
|
||||
import { FormInst, FormRules, NButton, NIcon, NSpace, SelectOption, TreeOption, useMessage } from 'naive-ui'
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
|
||||
// 状态管理
|
||||
const loading = ref(false)
|
||||
@ -175,8 +177,9 @@ const menuTree = ref<MenuTree[]>([])
|
||||
const expandedKeys = ref<string[]>([])
|
||||
|
||||
// 父级菜单表单相关
|
||||
const showParentModal = ref(false)
|
||||
const parentFormRef = ref<FormInst | null>(null)
|
||||
const showParentModal = ref(false);
|
||||
const parentFormRef = ref<FormInst | null>(null);
|
||||
const menuType = ref<'menu' | 'list'>('menu'); //菜单类型
|
||||
const parentForm = ref<Omit<RawMenu, 'uuid' | 'parentId'>>({
|
||||
path: '',
|
||||
label: '',
|
||||
@ -185,17 +188,61 @@ const parentForm = ref<Omit<RawMenu, 'uuid' | 'parentId'>>({
|
||||
adaptability: 'pc',
|
||||
component: '',
|
||||
sort: 0,
|
||||
status: 'enabled',
|
||||
status: 'enable',
|
||||
query: ''
|
||||
})
|
||||
const parentRules: FormRules = {
|
||||
label: { required: true, message: '请输入菜单名称', trigger: 'blur' },
|
||||
path: { required: true, message: '请输入菜单路径', trigger: 'blur' },
|
||||
menuCode: { required: true, message: '请输入菜单编码', trigger: 'blur' }
|
||||
component: { required: true, message: '请输入组件路径', trigger: 'blur' },
|
||||
}
|
||||
const isEditParent = ref(false)
|
||||
const currentParentId = ref('')
|
||||
const message = useMessage();
|
||||
// 生成ICON
|
||||
// 定义图标选项类型
|
||||
interface IconOption extends SelectOption {
|
||||
value: string
|
||||
label: string
|
||||
icon: string
|
||||
}
|
||||
const svgIcons = ref<string[]>([])
|
||||
// 加载 SVG 图标文件列表
|
||||
const loadSvgIcons = async () => {
|
||||
try {
|
||||
// 使用 import.meta.glob 获取所有 SVG 文件
|
||||
const svgModules = import.meta.glob('/src/assets/icons/*.svg', { eager: true })
|
||||
|
||||
// 提取文件名(不带后缀)
|
||||
svgIcons.value = Object.keys(svgModules).map(path => {
|
||||
const fileName = path.split('/').pop() || ''
|
||||
return fileName.replace('.svg', '')
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载图标失败:', error)
|
||||
svgIcons.value = []
|
||||
}
|
||||
}
|
||||
// 转换为选择器选项
|
||||
const iconOptions = computed<IconOption[]>(() => {
|
||||
return svgIcons.value.map(icon => ({
|
||||
value: icon,
|
||||
label: icon,
|
||||
icon: icon
|
||||
}))
|
||||
})
|
||||
// 自定义渲染选项
|
||||
const renderLabel = (option: IconOption) => {
|
||||
return h('div', { class: 'flex-content between' }, [
|
||||
h('span', { class: 'mr-2' }, option.label),
|
||||
h(SvgIcon, {
|
||||
'icon-class': option.icon,
|
||||
width: '16',
|
||||
height: '16',
|
||||
color: '#4090EF'
|
||||
})
|
||||
])
|
||||
}
|
||||
// 子级菜单表单相关
|
||||
const showChildModal = ref(false)
|
||||
const childFormRef = ref<FormInst | null>(null)
|
||||
@ -208,14 +255,14 @@ const childForm = ref<Omit<RawMenu, 'uuid'>>({
|
||||
adaptability: 'pc',
|
||||
component: '',
|
||||
sort: 0,
|
||||
status: 'enabled',
|
||||
status: 'enable',
|
||||
query: ''
|
||||
})
|
||||
const childRules: FormRules = {
|
||||
parentId: { required: true, message: '请选择父级菜单', trigger: 'blur' },
|
||||
label: { required: true, message: '请输入菜单名称', trigger: 'blur' },
|
||||
path: { required: true, message: '请输入菜单路径', trigger: 'blur' },
|
||||
menuCode: { required: true, message: '请输入菜单编码', trigger: 'blur' }
|
||||
component: { required: true, message: '请输入组件路径', trigger: 'blur' },
|
||||
}
|
||||
const isEditChild = ref(false)
|
||||
const currentChildId = ref('')
|
||||
@ -239,18 +286,42 @@ const parentMenuOptions = computed<SelectOption[]>(() => {
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
const { stop } = watch(menuType, (newVal) => {
|
||||
if (newVal === 'list') {
|
||||
parentForm.value.component = 'view-router';
|
||||
childForm.value.component = 'view-router';
|
||||
} else {
|
||||
parentForm.value.component = '';
|
||||
childForm.value.component = '';
|
||||
}
|
||||
})
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
fetchMenuData()
|
||||
loadSvgIcons();
|
||||
fetchMenuData();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
stop();
|
||||
})
|
||||
|
||||
// 方法
|
||||
const transformMenuData = (menuData: MenuTree[]): TreeOption[] => {
|
||||
return menuData.map(item => ({
|
||||
key: item.uuid,
|
||||
label: item.label,
|
||||
icon: item.icon,
|
||||
children: item.children ? transformMenuData(item.children) : undefined,
|
||||
isLeaf: !item.children || item.children.length === 0,
|
||||
rawData: item // 保留原始数据
|
||||
}))
|
||||
}
|
||||
|
||||
// 获取数据后
|
||||
const fetchMenuData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await getAllMenu()
|
||||
menuTree.value = res
|
||||
// 默认展开所有节点
|
||||
menuTree.value = transformMenuData(res) // 转换数据
|
||||
expandedKeys.value = getAllKeys(res)
|
||||
} catch (error) {
|
||||
console.error('获取菜单数据失败:', error)
|
||||
@ -270,10 +341,6 @@ const getAllKeys = (tree: MenuTree[]): string[] => {
|
||||
return keys
|
||||
}
|
||||
|
||||
const handleExpand = (keys: string[]) => {
|
||||
expandedKeys.value = keys
|
||||
}
|
||||
|
||||
const handleAddParent = () => {
|
||||
isEditParent.value = false
|
||||
parentForm.value = {
|
||||
@ -284,10 +351,11 @@ const handleAddParent = () => {
|
||||
adaptability: 'pc',
|
||||
component: '',
|
||||
sort: 0,
|
||||
status: 'enabled',
|
||||
status: 'enable',
|
||||
query: ''
|
||||
}
|
||||
showParentModal.value = true
|
||||
menuType.value = 'menu';
|
||||
showParentModal.value = true;
|
||||
}
|
||||
|
||||
const handleEditParent = (menu: MenuTree) => {
|
||||
@ -304,6 +372,7 @@ const handleEditParent = (menu: MenuTree) => {
|
||||
status: menu.status,
|
||||
query: menu.query
|
||||
}
|
||||
menuType.value = menu.component === 'view-router' ? 'list' : 'menu';
|
||||
showParentModal.value = true
|
||||
}
|
||||
|
||||
@ -322,7 +391,7 @@ const handleSubmitParent = () => {
|
||||
showParentModal.value = false
|
||||
await fetchMenuData()
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error)
|
||||
message.error(error.message || '操作失败!');
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -339,9 +408,10 @@ const handleAddChild = (parentId: string) => {
|
||||
adaptability: 'pc',
|
||||
component: '',
|
||||
sort: 0,
|
||||
status: 'enabled',
|
||||
status: 'enable',
|
||||
query: ''
|
||||
}
|
||||
menuType.value = 'menu';
|
||||
showChildModal.value = true
|
||||
}
|
||||
|
||||
@ -360,6 +430,7 @@ const handleEditChild = (menu: MenuNode) => {
|
||||
status: menu.status,
|
||||
query: menu.query
|
||||
}
|
||||
menuType.value = menu.component === 'view-router' ? 'list' : 'menu';
|
||||
showChildModal.value = true
|
||||
}
|
||||
|
||||
@ -394,22 +465,22 @@ const handleDelete = async (uuid: string) => {
|
||||
}
|
||||
|
||||
const renderTreeLabel = ({ option }: { option: TreeOption }) => {
|
||||
const menu = option as unknown as MenuTree | MenuNode
|
||||
const menu = option.rawData as MenuNode | MenuTree
|
||||
return h('div', { class: 'menu-management__tree-node' }, [
|
||||
h('span', { class: 'menu-management__tree-label' }, menu.label),
|
||||
h(NSpace, { class: 'menu-management__tree-actions' }, {
|
||||
default: () => [
|
||||
!option.isLeaf
|
||||
!option.isLeaf
|
||||
? h(NButton, {
|
||||
size: 'tiny',
|
||||
tertiary: true,
|
||||
onClick: (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
handleAddChild(menu.uuid)
|
||||
}
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14 }, () => h(Add))
|
||||
})
|
||||
size: 'tiny',
|
||||
tertiary: true,
|
||||
onClick: (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
handleAddChild(menu.uuid)
|
||||
}
|
||||
}, {
|
||||
icon: () => h(NIcon, { size: 14 }, () => h(Add))
|
||||
})
|
||||
: null,
|
||||
h(NButton, {
|
||||
size: 'tiny',
|
||||
@ -443,10 +514,10 @@ const renderTreeLabel = ({ option }: { option: TreeOption }) => {
|
||||
|
||||
// 自定义切换图标渲染
|
||||
const renderSwitcherIcon = ({ expanded, option }: { expanded: boolean; option: TreeOption }) => {
|
||||
return h(NIcon, { size: 16 }, () =>
|
||||
option.isLeaf
|
||||
return h(NIcon, { size: 16 }, () =>
|
||||
option.isLeaf
|
||||
? h(Document)
|
||||
: expanded
|
||||
: expanded
|
||||
? h(FolderOpen)
|
||||
: h(Folder)
|
||||
)
|
||||
@ -460,8 +531,17 @@ const renderSwitcherIcon = ({ expanded, option }: { expanded: boolean; option: T
|
||||
flex-direction: column;
|
||||
|
||||
&__header {
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--n-color);
|
||||
padding: $normolGap;
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: $titleTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
|
Loading…
x
Reference in New Issue
Block a user