完成导航栏管理

dev
truthhun 12 months ago
parent 073f1495cb
commit 416973ee44

@ -12,7 +12,7 @@ type Navigation struct {
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;comment:链接名称;"`
Href string `form:"href" json:"href,omitempty" gorm:"column:href;type:varchar(255);size:255;comment:跳转链接;"`
Target string `form:"target" json:"target,omitempty" gorm:"column:target;type:varchar(16);size:16;comment:打开方式;"`
Color string `form:"color" json:"color,omitempty" gorm:"column:color;type:varchar(16);size:16;comment:链接颜色;"`
Color string `form:"color" json:"color,omitempty" gorm:"column:color;type:varchar(32);size:32;comment:链接颜色;"`
Sort int `form:"sort" json:"sort,omitempty" gorm:"column:sort;type:int(11);size:11;default:0;comment:排序,值越大越靠前;"`
Enable *bool `form:"enable" json:"enable,omitempty" gorm:"column:enable;type:int(11);size:11;default:0;comment:是否启用;"`
ParentId int64 `form:"parent_id" json:"parent_id,omitempty" gorm:"column:parent_id;type:int(11);size:11;default:0;comment:上级id;"`
@ -123,10 +123,26 @@ func (m *DBModel) GetNavigationList(opt *OptionGetNavigationList) (navigationLis
}
// DeleteNavigation 删除数据
// 连同子数据一起删除
func (m *DBModel) DeleteNavigation(ids []int64) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&Navigation{}).Error
if err != nil {
m.logger.Error("DeleteNavigation", zap.Error(err))
return
}
var children []Navigation
m.db.Select("id").Where("parent_id in (?)", ids).Find(&children)
if len(children) > 0 {
var childrenIds []int64
for _, child := range children {
childrenIds = append(childrenIds, child.Id)
}
err = m.DeleteNavigation(childrenIds)
if err != nil {
m.logger.Error("DeleteNavigation", zap.Error(err))
return
}
}
return
}

@ -6,20 +6,21 @@
label-width="80px"
:model="navigation"
>
<!-- -->
<el-form-item label="上级导航">
<el-cascader
<el-select
v-model="navigation.parent_id"
:options="trees"
:filterable="true"
:props="{
checkStrictly: true,
expandTrigger: 'hover',
label: 'title',
value: 'id',
}"
clearable
placeholder="请选择上级分类"
></el-cascader>
:clearable="true"
placeholder="请选择上级导航"
>
<el-option
v-for="item in trees"
:key="item.id"
:label="item.title"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item
label="名称"
@ -35,13 +36,6 @@
<el-form-item
label="地址"
prop="href"
:rules="[
{
required: true,
trigger: 'blur',
message: ' https://mnt.ltd',
},
]"
>
<el-input
v-model="navigation.href"
@ -97,7 +91,7 @@
<el-option
v-for="item in [
{ label: '', value: '_self' },
{ label: '', value: '_blank' },
{ label: '', value: '_blank' },
]"
:key="item.value"
:label="item.label"

@ -0,0 +1,51 @@
<template>
<el-menu-item
:class="hiddenXS ? 'hidden-xs-only':''"
:key="'nav-' + navigation.id"
:index="`nav-${navigation.id}`"
>
<template v-if="navigation.href">
<!-- https:// 或者 http:// -->
<template
v-if="
navigation.href.indexOf('https://') === 0 ||
navigation.href.indexOf('http://') === 0
"
>
<a
:href="navigation.href"
:style="`color:${navigation.color}`"
:target="navigation.target"
class="el-link el-link--default"
>{{ navigation.title }}</a
>
</template>
<nuxt-link
v-else
:style="`color:${navigation.color}`"
:to="navigation.href"
:target="navigation.target"
class="el-link el-link--default"
>{{ navigation.title }}</nuxt-link
>
</template>
<template v-else>
<span :style="`color:${navigation.color}`">{{ navigation.title }}</span>
</template>
</el-menu-item>
</template>
<script>
export default {
name: 'NavigationLink',
props: {
navigation: {
type: Object,
default: () => {},
},
hiddenXS: {
type: Boolean,
default: false,
},
},
}
</script>

@ -17,19 +17,10 @@
<el-menu-item index="/" class="hidden-xs-only">
<nuxt-link to="/"></nuxt-link>
</el-menu-item>
<el-menu-item
v-for="(item, index) in categoryTrees"
:key="'c-' + item.id"
:index="`/category/${item.id}`"
v-show="$route.path === '/' && index < 6"
class="hidden-xs-only"
>
<nuxt-link :to="`/category/${item.id}`">{{ item.title }}</nuxt-link>
</el-menu-item>
<el-submenu
index="channel"
class="hidden-xs-only"
v-show="$route.path !== '/'"
v-show="$route.path !== '/' || navigations.length > 0"
>
<template slot="title"></template>
<el-menu-item
@ -45,9 +36,47 @@
>
</el-menu-item>
</el-submenu>
<template v-if="navigations.length === 0">
<el-menu-item
v-for="(item, index) in categoryTrees"
:key="'c-' + item.id"
:index="`/category/${item.id}`"
v-show="$route.path === '/' && index < 6"
class="hidden-xs-only"
>
<nuxt-link :to="`/category/${item.id}`">{{
item.title
}}</nuxt-link>
</el-menu-item>
</template>
<template v-else>
<template v-for="item in navigations">
<el-submenu
:key="'nav-' + item.id"
:index="`nav-${item.id}`"
v-if="item.children && item.children.length > 0"
class="hidden-xs-only"
>
<template slot="title">{{ item.title }}</template>
<NavigationLink
:hiddenXS="true"
v-for="child in item.children || []"
:key="'child-' + child.id"
:navigation="child"
/>
</el-submenu>
<NavigationLink
v-else
:navigation="item"
:key="'nav-' + item.id"
:hiddenXS="true"
/>
</template>
</template>
<el-menu-item
index="searchbox"
class="nav-searchbox hidden-xs-only"
:class="navigations.length <= 2 ? 'nav-searchbox-large' : ''"
v-show="$route.path !== '/'"
>
<el-input
@ -359,6 +388,23 @@
</ul>
</el-collapse-item>
</el-collapse>
<el-menu :default-active="$route.path" class="el-menu-mobile">
<template v-for="item in navigations">
<el-submenu
:key="'nav-' + item.id"
:index="`nav-${item.id}`"
v-if="item.children && item.children.length > 0"
>
<template slot="title">{{ item.title }}</template>
<NavigationLink
v-for="child in item.children || []"
:key="'child-' + child.id"
:navigation="child"
/>
</el-submenu>
<NavigationLink v-else :navigation="item" :key="'nav-' + item.id" />
</template>
</el-menu>
</el-drawer>
</el-container>
</template>
@ -367,6 +413,7 @@ import { mapGetters, mapActions } from 'vuex'
import UserAvatar from '~/components/UserAvatar.vue'
import FormUserinfo from '~/components/FormUserinfo.vue'
import { listFriendlink } from '~/api/friendlink'
import { listNavigation } from '~/api/navigation'
import { categoryToTrees, requireLogin } from '~/utils/utils'
import { getSignedToday, signToday } from '~/api/user'
@ -386,6 +433,7 @@ export default {
sign: { id: 0 },
activeCollapse: 'categories',
loading: false,
navigations: [],
}
},
head() {
@ -411,21 +459,17 @@ export default {
},
async created() {
this.loading = true
const [res] = await Promise.all([
listFriendlink({
enable: true,
field: ['id', 'title', 'link'],
}),
await Promise.all([
this.getCategories(),
this.getSettings(),
this.listNavigation(),
this.listFriendlink(),
])
if (res.status === 200) {
this.friendlinks = res.data.friendlink
}
this.categoryTrees = categoryToTrees(this.categories).filter(
(item) => item.enable
)
this.loopUpdate()
this.loading = false
if (requireLogin(this.settings, this.user, this.$route, this.permissions)) {
@ -451,6 +495,15 @@ export default {
this.sign = res.data || this.sign
}
},
async listFriendlink() {
const res = await listFriendlink({
enable: true,
field: ['id', 'title', 'link'],
})
if (res.status === 200) {
this.friendlinks = res.data.friendlink
}
},
async signToday() {
const res = await signToday()
if (res.status === 200) {
@ -466,6 +519,14 @@ export default {
this.$message.error(res.message || res.data.message)
}
},
async listNavigation() {
const res = await listNavigation({ page: 1, size: 10000 })
if (res.status === 200) {
let navigations = res.data.navigation || []
navigations = categoryToTrees(navigations).filter((item) => item.enable)
this.navigations = navigations
}
},
onSearch() {
if (!this.search.wd) return
this.menuDrawerVisible = false
@ -610,11 +671,16 @@ export default {
.nav-searchbox {
padding: 0 25px !important;
top: -2px;
&.nav-searchbox-large {
.el-input {
width: 300px;
}
}
&.is-active {
border-color: transparent;
}
.el-input {
width: 360px;
width: 200px;
}
}
a {
@ -622,7 +688,8 @@ export default {
height: 60px;
line-height: 60px;
display: inline-block;
padding: 0 20px;
// padding: 0 20px;
padding: 0 15px;
}
.el-menu-item {
padding: 0;
@ -732,6 +799,24 @@ export default {
padding: 1px 20px;
}
.el-menu-mobile {
margin-top: 15px;
margin-left: -15px;
border-right: 0;
& > li {
padding-left: 0;
}
.el-submenu__title {
height: 32px;
line-height: 32px;
font-size: 15px;
}
.el-menu-item {
height: 32px;
line-height: 32px;
}
}
// =======================
// 移动端样式
// =======================

@ -127,10 +127,15 @@ export default {
let navigations = res.data.navigation || []
navigations.map((item) => {
item.title_html = genLinkHTML(item.title, item.href)
if (item.color) {
// 增加链接颜色
item.title_html = item.title_html.replace(
'<a',
`<a style="color:${item.color}" `
)
}
})
console.log(navigations)
let trees = categoryToTrees(navigations, false)
this.navigations = trees
this.total = res.data.total
@ -179,7 +184,7 @@ export default {
},
batchDelete() {
this.$confirm(
`您确定要删除选中的【${this.selectedRow.length}条】导航吗?删除之后不可恢复!`,
`您确定要删除选中的【${this.selectedRow.length}条】导航吗?会连带着子导航一起删除,删除之后不可恢复!`,
'',
{
confirmButtonText: '',
@ -201,7 +206,7 @@ export default {
},
deleteRow(row) {
this.$confirm(
`您确定要删除导航【${row.title}】吗?删除之后不可恢复!`,
`您确定要删除导航【${row.title}】吗?会连带着子导航一起删除,删除之后不可恢复!`,
'',
{
confirmButtonText: '',

Loading…
Cancel
Save