This commit is contained in:
heixinyouzi 2024-05-23 18:25:49 +08:00
parent 5309d8d763
commit 2c819c7b69
9 changed files with 434 additions and 108 deletions

View File

@ -6,4 +6,27 @@ export function listNav() {
url: '/nav/menu', url: '/nav/menu',
method: 'get' method: 'get'
}); });
}
export function addMenu(data) {
return request({
url: '/nav/menu',
method: 'post',
data
});
}
export function listClass(){
return request({
url: '/nav/class',
method: 'get'
});
}
export function addClass(data) {
return request({
url: '/nav/class',
method: 'post',
data
});
} }

View File

@ -3,7 +3,7 @@ const icons = import.meta.glob('./*.svg', {
}) })
const icon = {} const icon = {}
console.log(icons); // console.log(icons);
for (const i in icons) { for (const i in icons) {
const t = i.split("/") const t = i.split("/")
const name = t[1].split('.')[0] const name = t[1].split('.')[0]

1
src/icon/sciss.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="7" r="3"></circle><circle cx="6" cy="17" r="3"></circle><path d="M8.6 8.6L19 19"></path><path d="M8.6 15.4L19 5"></path></g></svg>

After

Width:  |  Height:  |  Size: 353 B

View File

@ -8,6 +8,15 @@ import { useDialog, useMessage, useNotification } from "naive-ui";
window.$msg = useMessage(); window.$msg = useMessage();
window.$dialog = useDialog(); window.$dialog = useDialog();
window.$note = useNotification(); window.$note = useNotification();
window.$vr = (res, sf, ff) => {
if (res.code == 1) {
window.$msg.success(res.msg)
sf()
} else {
window.$msg.error(res.msg)
ff()
}
}
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -89,6 +89,15 @@ const router = createRouter({
auth: true, auth: true,
}, },
}, },
{
path: "/console/menu",
component: () => import("@/views/console/menu/index.vue"),
meta: {
title: "控制台",
name: "导航管理",
auth: true,
},
},
], ],
}, },
{ {

View File

@ -25,10 +25,15 @@ const options = [
key: "3", key: "3",
icon: () => h(NIcon, { class: "orange" }, { default: () => h(article) }), icon: () => h(NIcon, { class: "orange" }, { default: () => h(article) }),
}, },
{
label: () => h(RouterLink, { to: "/console/menu", class: "menu-item" }, { default: () => "导航管理" }),
key: "4",
icon: () => h(NIcon, { class: "orange" }, { default: () => h(article) }),
},
]; ];
const consoleMenuInfo = { const consoleMenuInfo = {
options, options,
menuList: ["/console/home", "/console/profile", "/console/gallery", "/console/article"], menuList: ["/console/home", "/console/profile", "/console/gallery", "/console/article","/console/menu",],
}; };
export default consoleMenuInfo; export default consoleMenuInfo;

View File

@ -134,3 +134,19 @@ export function getDictValue(dict, ckey, cvalue,rkey) {
}) })
return result; return result;
} }
// 分割数组
export function chunkArrayInGroups(arr, size) {
return Array.from(
{ length: Math.ceil(arr.length / size) },
(_, i) => arr.slice(i * size, i * size + size)
);
}
// 重置对象
export function resetObject(obj) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
obj[key] = null;
}
}
}

View File

@ -0,0 +1,347 @@
<template>
<div>
<div class="mb-8 mt-8 relative">
<h1 class="text-center text-lg">导航管理</h1>
<div class="absolute top-0 right-[5%]">
<!-- <n-button class="mr-4" type="primary" @click="addClass">增加分类</n-button> -->
<n-button type="primary" @click="addMenu">增加导航</n-button>
</div>
</div>
<div class="table w-[95%] mx-auto">
<n-data-table :columns="columns" striped :single-line="false" :paginate-single-page="false"
:pagination="pagination" :data="menuList">
<template #empty>
<div>
<span>尚无导航</span><span class="text-pp-200 hover:underline" @click="add">去增加导航 ></span>
</div>
</template>
</n-data-table>
</div>
<n-drawer v-model:show="mshow" :width="702" :mask-closable="false">
<n-drawer-content title="新增导航" closable>
<n-form class="mb-20 px-4" ref="menuForm" :model="menuValue" :rules="menuRules" label-placement="left"
label-width="100">
<n-form-item-row label="导航名称" path="menuName">
<n-input v-model:value="menuValue.menuName" placeholder="请输入导航名称" />
</n-form-item-row>
<n-form-item-row label="导航类别" path="">
<n-select v-model:value="menuValue.menuClassId" placeholder="请选择导航类别" :options="classList"
@update:value="changeClass" />
</n-form-item-row>
<n-form-item-row label="导航链接" path="menuLink">
<n-input v-model:value="menuValue.menuLink" placeholder="请输入导航链接" />
</n-form-item-row>
<n-form-item-row label="导航图标" path="menuIcon">
<n-input v-model:value="menuValue.menuIcon" placeholder="请输入导航图标" />
</n-form-item-row>
<n-form-item-row label="导航描述" path="menuDesc">
<n-input type="textarea" v-model:value="menuValue.menuDesc" placeholder="请输入导航描述" />
</n-form-item-row>
<div class="px-[10%] ">
<n-button type="primary" block secondary strong @click="submitAddMenu">
增加导航
</n-button>
</div>
</n-form>
<n-divider class="mt-4" title-placement="left">
<n-icon size="20" color="#8A2BE2">
<icon-sciss></icon-sciss>
</n-icon>
<span class="ml-2 text-[20px]">增加类别</span>
</n-divider>
<n-form class="px-4" :model="classValue" ref="classForm" :rules="classRules" label-placement="left"
label-width="100">
<n-form-item label="分类名称" path="menuClass">
<n-input v-model:value="classValue.menuClass" placeholder="请输入分类名称" />
</n-form-item>
<n-form-item label="分类id" path="">
<n-input-number v-model:value="classValue.menuClassId" placeholder="请输入分类id" />
</n-form-item>
<div class="px-[10%]">
<n-button type="primary" block secondary strong @click="submitAddClass">创建分类</n-button>
</div>
</n-form>
</n-drawer-content>
</n-drawer>
<n-modal v-model:show="showModal" preset="dialog" title="删除" content="是否永久删除该文章?" positive-text="确认"
negative-text="取消" @positive-click="submitCallback" @negative-click="cancelCallback" />
</div>
</template>
<script setup>
import { resetObject } from '@/util/index.js';
import { onMounted, reactive, ref } from 'vue';
const menuValue = reactive({
menuName: null,
menuClassId: null,
menuLink: null,
menuIcon: null,
menuClass: null,
menuDesc: null,
})
const classValue = reactive({
menuClass: null,
menuClassId: null,
})
const mshow = ref(false)
const showModal = ref(false)
const menuList = ref([])
const classList = ref([])
const pagination = ref({
pageSize: 10,
})
const menuForm = ref(null)
const classForm = ref(null)
const dtitle = ref('');
const opId = ref(-1);
const curId = ref(-1);
const delId = ref(-1);
const show = ref('')
const columns = createColumns({
view: (row) => {
dshow.value = true
dtitle.value = '文章预览'
opId.value = row.id
show.value = 'view'
},
edit: (row) => {
dshow.value = true
dtitle.value = '编辑文章'
curId.value = row.id
show.value = 'edit'
},
remove: (row) => {
showModal.value = true
delId.value = row.id
}
})
const menuRules = {
menuName: {
required: true,
message: '请输入导航名称',
trigger: 'blur',
},
menuClassId: {
required: true,
message: '请选择导航类别',
trigger: 'change',
},
menuLink: {
required: true,
message: '请输入导航链接',
trigger: 'blur',
},
menuIcon: {
required: true,
message: '请输入导航图标',
trigger: 'blur',
},
}
const classRules = {
menuClass: {
required: true,
message: '请输入分类名称',
trigger: 'blur',
},
menuClassId: {
required: true,
message: '请输入分类id',
trigger: 'blur',
},
}
function createColumns({ edit, remove, view }) {
return [
{
title: '序号',
key: 'key',
align: 'center',
width: "80",
render: (_, index) => {
return `${index + 1}`
}
},
{
title: "导航名称",
align: 'center',
width: "300",
key: "menuName"
},
{
title: "导航分类",
align: 'center',
width: "200",
key: "menuClass"
},
{
title: "导航简介",
align: 'center',
key: "menuDesc",
ellipsis: {
tooltip: true
}
},
{
title: "操作",
align: 'center',
key: "actions",
width: "200",
render(row) {
return h(
'div',
[
h(NButton,
{
type: "primary",
text: true,
size: "small",
onClick: () => view(row)
},
{ default: () => "预览" }
),
h(NButton,
{
type: "primary",
text: true,
size: "small",
style: "margin-left: 10px",
onClick: () => edit(row)
},
{ default: () => "编辑" }
),
h(NButton,
{
type: "primary",
text: true,
style: "margin-left: 10px",
size: "small",
onClick: () => remove(row)
},
{ default: () => "删除" }
),
]
);
}
}
];
};
function addMenu() {
mshow.value = true
}
onMounted(() => {
getClass()
getList()
})
async function getList() {
//
const res = await $http.nav.listNav()
menuList.value = res.data
}
async function getClass() {
const res = await $http.nav.listClass()
if (res.code == 1) {
res.data.forEach(i => {
i.value = i.menuClassId
i.label = i.menuClass
});
classList.value = res.data
}
}
async function submitCallback() {
const res = await $http.art.deleteArti(delId.value)
if (res.code == 1) $msg.success(res.msg)
else $msg.error(res.msg)
showModal.value = false
delId.value = -1
getList()
}
function cancelCallback() {
showModal.value = false
delId.value = -1
}
async function submitAddMenu() {
if (menuValue.menuClassId == null || !menuValue.menuClassId) {
$msg.error('请选择导航类别')
return
}
menuForm.value?.validate(async errors => {
if (!errors) {
const res = await $http.nav.addMenu(menuValue)
if (res.code == 1) {
$msg.success(res.msg);
resetObject(menuValue)
getList()
} else {
$msg.error(res.msg);
}
} else {
$msg.error(errors[0][0].message)
}
});
}
function submitAddClass() {
console.log(classValue);
if (!classValue.menuClassId) {
$msg.error('请输入分类id')
return
}
classForm.value.validate(async errors => {
if (!errors) {
const res = await $http.nav.addClass(classValue)
if (res.code == 1) {
$msg.success(res.msg);
resetObject(classValue)
getClass()
} else {
$msg.error(res.msg);
}
} else {
$msg.error(errors[0][0].message)
}
});
}
function changeClass() {
menuValue.menuClass = classList.value.find(item => item.menuClassId == menuValue.menuClassId).menuClass
console.log(menuValue);
}
</script>
<style scoped lang="less">
:deep(.n-divider__line) {
background-color: @purple !important;
height: 2px !important;
}
:deep(.n-input-number) {
width: 100%;
}
</style>

View File

@ -1,37 +1,28 @@
<template> <template>
<div class="box flex flex-wrap pl-[2%] mt-8"> <div class="box flex flex-wrap pl-[2%] mt-8">
<div class="nav-card w-[300px] mx-[2%] mb-6 p-2 bg-[#fafbfc]" v-for="(item, index) in navList" :key="index"> <n-card title="" style="margin-bottom: 16px" v-for="(ii,iii) in navList">
<div class="t" @click="expand(index)"> <n-tabs default-value="0" justify-content="space-evenly" type="line">
<n-divider title-placement="center"> <n-tab-pane class="flex flex-wrap" :name="index+''" :tab="item.menuClass" v-for="(item, index) in ii">
<div class="flex items-center font-bold"> <div class="card mr-4 mb-4 rounded-md py-4 flex justify-center items-center w-[320px] bg-[#f2e8fc]" @click="link(it.menuLink)" v-for="(it, idx) in item.children" :key="index">
{{ item.menuClass }} <div class="left mr-3">
<n-icon size="24" class="tiao mx-2"> <img width="40" height="40" class="rounded-full bg-pp-400" :src="it.menuIcon" alt="">
<icon-more /> </div>
</n-icon> <div class="right w-[224px]">
<div class="text-[20px] text-pp-700 font-bold">{{ it.menuName }}</div>
<div class="truncate">{{ it.menuDesc }}</div>
</div>
</div> </div>
</n-divider> </n-tab-pane>
</div> </n-tabs>
<div class="flex flex-wrap pt-3" v-show="item.exp"> </n-card>
<div class="w-[45%] ml-[3%] mb-2" v-for="(it, idx) in item.children" :key="index">
<!-- <n-popover trigger="hover"> -->
<!-- <template #trigger> -->
<n-button class="w-[100%] text-[12px] text-ellipsis hover:bg-gradient-to-r hover:from-pp-100 hover:to-pp-400 font-bold" ghost color="#B172EC" @click="link(it.menuLink)">
<!-- <n-button class="w-[100%] text-[12px] text-ellipsis" ghost color="#ff69b4" @click="link(it.menuLink)"> -->
{{ it.menuName }}
</n-button>
<!-- </template> -->
<!-- <span class="text-[12px]">{{ it.menuName }}</span> -->
<!-- </n-popover> -->
</div>
</div>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { chunkArrayInGroups } from '@/util/index.js';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
//mark import //mark import
//mark data //mark data
@ -54,23 +45,13 @@ function formatNav(list) {
}) })
}) })
console.log(result); console.log(result);
return result
return chunkArrayInGroups(result, 4)
} }
function expand(index) {
console.log(index, active.value);
if (active.value == index) {
navList.value[index].exp = !navList.value[index].exp
} else {
navList.value.forEach(i => {
i.exp = false
})
navList.value[index].exp = true
}
active.value = index
}
function link(url) { function link(url) {
window.open(url, "_blank") window.open(url, "_blank")
} }
@ -78,82 +59,17 @@ function link(url) {
onMounted(async () => { onMounted(async () => {
const res = await $http.nav.listNav() const res = await $http.nav.listNav()
navList.value = formatNav(res.data) navList.value = formatNav(res.data)
console.log(navList.value);
}) })
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.nav-card {
box-shadow: @ps;
border-radius: 4px;
flex-grow: 1;
}
.box { .box {
align-items: start; align-items: start;
} }
@keyframes bounce { :deep(.n-collapse-item__header) {
background-color: red;
0%,
20%,
50%,
80%,
100% {
transform: translateY(0);
}
40% {
transform: translateY(-3px);
}
60% {
transform: translateY(-1px);
}
}
.tiao {
animation: bounce 1s infinite;
/* 播放名为bounce的动画循环无限次每次动画时长1秒 */
}
:deep(.n-divider) {
margin: 0px;
}
:deep(.n-button__content) {
display: block;
width: 100%;
text-align: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: bold;
}
:deep(.n-button):hover {
// background-color: @purple !important;
border: none !important;
color: white !important;
animation: swing .8s ease-in-out 3;
}
@keyframes swing {
0%,
20%,
50%,
80%,
100% {
transform: translateX(0);
}
40% {
transform: translateX(-3px);
}
60% {
transform: translateX(3px);
}
} }
</style> </style>