提交 4754ee72 编写于 作者: X xjh22222228

feat: Add del

上级 c5a53fd7
......@@ -101,55 +101,6 @@ server {
所有可配置位于文件 `nav.config.ts`
## 更新数据
只需要关注根目录 `data` 文件夹, 如果你使用了上面教程提供的自动化部署服务,那么当更新数据后大概5分钟即可看到。
```js
{
title: '工具',
icon: 'https://example/favicon.ico',
nav: [
{
title: '网站',
collapsed: false, // 默认展开,设置 false 折叠
showSideIcon: false, // 右侧边图标,如果设置Icon则默认显示
nav: [
{
name: '发现导航',
desc: '发现导航 - 精选实用导航网站',
url: 'https://nav3.cn',
}
]
}
]
}
```
## 图标
图标是支持继承的,每一级的 `icon` 字段都是可选,如果当前没有就会继承上一级Icon,
```js
{
title: 'Example',
icon: 'https://example/icon',
nav: [
{
showSideIcon: false, // 如果这层设置 icon 图标会默认在右侧边栏显示ICON, 设置 false 关闭显示
title: 'Example',
// icon: 'https://favicon.ico',
nav: [
{
// icon: 'https://favicon.ico',
},
]
}
]
}
```
......
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import config from '../../nav.config'
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import config from '../../nav.config'
import { queryString, setLocation } from '../utils'
@Component({
......
......@@ -17,6 +17,7 @@ import { NzEmptyModule } from 'ng-zorro-antd/empty'
import { NzButtonModule } from 'ng-zorro-antd/button'
import { ReactiveFormsModule } from '@angular/forms'
import { NzAvatarModule } from 'ng-zorro-antd/avatar'
import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'
// components
import { AppComponent } from './app.component'
......@@ -38,7 +39,8 @@ import { registerLocaleData } from '@angular/common';
import zh from '@angular/common/locales/zh';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { LogoComponent } from '../components/logo/logo.component'
import { LogoComponent } from '../components/logo/logo.component';
import { DelComponent } from '../components/del/del.component'
registerLocaleData(zh);
......@@ -75,7 +77,8 @@ const appRoutes: Routes = [
NoDataComponent,
SearchEngineComponent,
CreateComponent,
LogoComponent
LogoComponent,
DelComponent
],
imports: [
NzModalModule,
......@@ -89,6 +92,7 @@ const appRoutes: Routes = [
NzButtonModule,
ReactiveFormsModule,
NzAvatarModule,
NzPopconfirmModule,
BrowserModule,
FormsModule,
RouterModule.forRoot(
......
......@@ -97,7 +97,7 @@
<nz-form-item *ngIf="radioType === '6'">
<nz-form-label [nzSpan]="4">网站描述</nz-form-label>
<nz-form-control [nzSpan]="20">
<input formControlName="description" nz-input placeholder="发现导航, 精选实用导航网站" />
<input formControlName="desc" nz-input placeholder="发现导航, 精选实用导航网站" />
</nz-form-control>
</nz-form-item>
</form>
......
......@@ -44,7 +44,7 @@ export class CreateComponent implements OnInit {
threeSelect: [null, [Validators.required]],
url: [null, [Validators.required]],
icon: [null],
description: [''],
desc: [''],
});
}
......@@ -120,7 +120,7 @@ export class CreateComponent implements OnInit {
this.validateForm.controls[i].updateValueAndValidity();
}
let { title, icon, oneSelect, twoSelect, threeSelect, url, description } = this.validateForm.value
let { title, icon, oneSelect, twoSelect, threeSelect, url, desc } = this.validateForm.value
if (!title) return
title = title.trim()
......@@ -198,7 +198,7 @@ export class CreateComponent implements OnInit {
name: title,
icon,
url,
desc: description
desc
})
break
}
......
<div class="container-slot">
<a
*ngIf="isLogin"
nz-popconfirm
nzPopconfirmPlacement="rightTop"
nzPopconfirmTitle="确定要删除吗?"
nzPopconfirmPlacement="bottom"
(nzOnConfirm)="confirmDel($event)"
>
<i class="iconfont iconchuangjian del" [style]="delIconStyle"></i>
</a>
<ng-content></ng-content>
</div>
.container-slot {
position: relative;
height: 100%;
&:hover {
.del {
display: inline-block;
}
}
.del {
z-index: 9;
display: none;
transform: rotate(45deg);
position: absolute;
top: 0px;
right: 0px;
color: #f50;
transition: .1s linear;
&:hover {
transform: rotate(45deg) scale(1.2);
}
}
}
\ No newline at end of file
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DelComponent } from './del.component';
describe('DelComponent', () => {
let component: DelComponent;
let fixture: ComponentFixture<DelComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DelComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, Input } from '@angular/core'
import { NzMessageService } from 'ng-zorro-antd/message'
import { getToken } from '../../utils/user'
import { setWebsiteList, queryString } from '../../utils'
import { websiteList } from '../../store'
import { Router } from '@angular/router'
import { setAnnotate } from '../../utils/ripple'
@Component({
selector: 'app-del',
templateUrl: './del.component.html',
styleUrls: ['./del.component.scss']
})
export class DelComponent implements OnInit {
@Input() delIconStyle: string
@Input() oIdx: number
@Input() twoIdx: number
@Input() threeIdx: number
@Input() fourIdx: number
websiteList = websiteList
isLogin: boolean = !!getToken()
constructor(private message: NzMessageService, private router: Router) {}
ngOnInit(): void {
}
confirmDel() {
const { page, id } = queryString()
// 删除网站
if (
this.oIdx >= 0 &&
this.twoIdx >= 0 &&
this.threeIdx >= 0 &&
this.fourIdx >= 0
) {
this.websiteList[this.oIdx].nav[this.twoIdx].nav[this.threeIdx].nav.splice(this.fourIdx, 1)
} else if (
this.oIdx >= 0 &&
this.twoIdx >= 0 &&
this.threeIdx >= 0
) {
// 删除三级分类
this.websiteList[this.oIdx].nav[this.twoIdx].nav.splice(this.threeIdx, 1)
} else if (
this.oIdx >= 0 &&
this.twoIdx >= 0
) {
// 删除二级分类
if (this.websiteList[this.oIdx]?.nav?.length <= 1) {
return this.message.error('至少保留一项, 请先添加再删除!')
}
this.router.navigate([this.router.url.split('?')[0]], {
queryParams: {
page,
id: id > 0 ? id - 1 : 0,
}
})
delete this.websiteList[this.oIdx].id
this.websiteList[this.oIdx].nav.splice(this.twoIdx, 1)
} else if (this.oIdx >= 0) {
// 删除一级分类
if (this.websiteList.length === 1) {
this.message.error('至少保留一项, 请先添加再删除!')
return
}
this.router.navigate([this.router.url.split('?')[0]], {
queryParams: {
page: page > 0 ? page - 1 : 0,
id: 0
}
})
this.websiteList.splice(this.oIdx, 1)
setTimeout(() => {
setAnnotate()
}, 100)
}
setWebsiteList(this.websiteList)
}
}
.mark {
z-index: 28;
position: absolute;
bottom: -50px;
bottom: -100px;
left: 0;
width: 100%;
padding: 10px 0 5px 0px;
......
......@@ -71,6 +71,10 @@ export function fuzzySearch(navList: INavProps[], keyword: string) {
f()
if (searchResultList[0].nav.length <= 0) {
return[]
}
return searchResultList
}
......
......@@ -2,6 +2,7 @@
import { annotate } from 'rough-notation'
import { queryString } from './index'
import { websiteList } from '../store'
let ANNOTATE_EQUEUE = []
......@@ -21,6 +22,8 @@ export function setAnnotate(querySelector = '.top-nav .ripple-btn') {
ANNOTATE_EQUEUE = []
const { page } = queryString()
if (page >= websiteList.length || page < 0) return
const annotation = annotate(elList[page], {
type: 'underline',
color: '#f9826c',
......
......@@ -2,7 +2,7 @@
<main class="homepage dark-border-color">
<app-fixbar
(onCollapse)="onCollapseAll()"
[collapsed]="websiteList[page].nav[id].collapsed"
[collapsed]="collapsed()"
selector=".main"
[randomBg]="true"
>
......@@ -16,13 +16,15 @@
[class.dark-text-active]="page === i"
class="ripple-btn dark-text"
>
{{ item.title }}
<app-del delIconStyle="top: -8px;right: -16px;" [oIdx]="i">
{{ item.title }}
</app-del>
</a>
</nav>
<section class="index-section user-select-none dark-bg">
<aside class="sidebar dark-bg dark-border-color" id="sidebar">
<ul>
<ul *ngIf="websiteList[page]">
<li
class="tag dark-text"
[class.active]="id === i"
......@@ -30,7 +32,9 @@
(click)="handleSidebarNav(i)"
*ngFor="let item of websiteList[page].nav; let i = index"
>
<div class="ripple-btn" *ngIf="item.title">{{ item.title }}</div>
<app-del [oIdx]="page" [twoIdx]="i">
<div class="ripple-btn">{{ item.title }}</div>
</app-del>
</li>
</ul>
</aside>
......@@ -38,48 +42,51 @@
<div class="main dark-scrollbar">
<app-search-engine (onSearch)="onSearch($event)"></app-search-engine>
<ul *ngIf="currentList.length && currentList[0].nav.length; else noData">
<ul *ngIf="currentList.length > 0; else noData">
<li *ngFor="let item of currentList; let i=index">
<div class="title-wrapper dark-border-color" *ngIf="item.title">
<h2 class="block-title">
<span
(click)="onCollapse(item, i)"
class="cursor-pointer dark-primary"
>
{{ item.title }} x {{ item.nav.length }}
</span>
<i
class="iconfont iconjiantouarrow483 down-arrow"
[class.active]="item.collapsed"
(click)="onCollapse(item, i)"
>
</i>
</h2>
<img
*ngIf="item.icon && item.showSideIcon !== false"
[src]="item.icon"
class="side-logo"
(error)="onSideLogoError($event)"
/>
<app-del delIconStyle="right: -20px;" [oIdx]="page" [twoIdx]="id" [threeIdx]="i">
<h2 class="block-title">
<app-logo
[src]="item.icon"
[name]="item.title"
>
</app-logo>
<span
(click)="onCollapse(item, i)"
class="cursor-pointer dark-primary"
style="align-self: center; margin-left: 10px;"
>
{{ item.title }} x {{ item.nav.length }}
</span>
<i
class="iconfont iconjiantouarrow483 down-arrow"
[class.active]="item.collapsed"
(click)="onCollapse(item, i)"
>
</i>
</h2>
</app-del>
</div>
<div class="row" *ngIf="!item.collapsed">
<div class="click-btn dark-border-color" *ngFor="let el of item.nav">
<a [href]="el.url" target="_blank" rel="noopener noreferer">
<div class="top">
<app-logo
[src]="el.icon || item.icon || websiteList[page].nav[id].icon"
[name]="el.name"
>
</app-logo>
<em class="name dark-title" [innerHtml]="el.name" [title]="el.name"></em>
</div>
<div class="desc dark-border-color dark-text" [innerHtml]="el.desc"></div>
</a>
<app-multiple-site [dataSource]="el"></app-multiple-site>
<div class="click-btn dark-border-color" *ngFor="let el of item.nav; let idx=index">
<app-del [oIdx]="page" [twoIdx]="id" [threeIdx]="i" [fourIdx]="idx">
<a [href]="el.url" target="_blank" rel="noopener noreferer">
<div class="top">
<app-logo
[src]="el.icon || item.icon || websiteList[page].nav[id].icon"
[name]="el.name"
>
</app-logo>
<em class="name dark-title" [innerHtml]="el.name" [title]="el.name"></em>
</div>
<div class="desc dark-border-color dark-text" [innerHtml]="el.desc"></div>
</a>
<app-multiple-site [dataSource]="el"></app-multiple-site>
</app-del>
</div>
</div>
</li>
......
......@@ -103,12 +103,6 @@
padding-bottom: 50px;
overflow-y: auto;
.side-logo {
width: 30px;
height: 30px;
border-radius: 50%;
}
.name {
display: inline-block;
font-size: 15px;
......
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import config from '../../../../nav.config'
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import config from '../../../../nav.config'
import { INavProps, INavThreeProp } from '../../../types'
import {
debounce,
......@@ -11,7 +11,6 @@ import {
queryString,
setWebsiteList,
toggleCollapseAll,
imgErrorInRemove
} from '../../../utils'
import { initRipple, setAnnotate } from '../../../utils/ripple'
import { websiteList } from '../../../store'
......@@ -37,7 +36,15 @@ export default class HomeComponent {
randomBgImg()
const initList = () => {
this.currentList = this.websiteList[this.page].nav[this.id].nav
try {
if (this.websiteList[this.page] && this.websiteList[this.page]?.nav?.length > 0) {
this.currentList = this.websiteList[this.page].nav[this.id].nav
} else {
this.currentList = []
}
} catch (error) {
this.currentList = []
}
}
this.activatedRoute.queryParams.subscribe(() => {
......@@ -67,7 +74,7 @@ export default class HomeComponent {
}
const params = queryString()
this.router.navigate(['/light'], {
this.router.navigate([this.router.url.split('?')[0]], {
queryParams: {
...params,
q: this.searchKeyword
......@@ -76,24 +83,22 @@ export default class HomeComponent {
}, 1000, true)
}
collapsed() {
try {
return websiteList[this.page].nav[this.id].collapsed
} catch (error) {
return false
}
}
onSearch(v) {
this.searchKeyword = v
this.handleSearch()
}
handleOnClickSearch() {
this.showInput = true
setTimeout(() => {
const searchEl = document.querySelector('.search') as HTMLInputElement
if (searchEl) {
searchEl.focus()
}
}, 0)
}
handleCilckTopNav(index) {
const id = this.websiteList[index].id || 0
this.router.navigate(['/light'], {
this.router.navigate([this.router.url.split('?')[0]], {
queryParams: {
page: index,
id,
......@@ -105,7 +110,7 @@ export default class HomeComponent {
handleSidebarNav(index) {
const { page } = queryString()
this.websiteList[page].id = index
this.router.navigate(['/light'], {
this.router.navigate([this.router.url.split('?')[0]], {
queryParams: {
page,
id: index,
......@@ -130,5 +135,4 @@ export default class HomeComponent {
}
handleSearch = null
onSideLogoError = imgErrorInRemove
}
......@@ -13,13 +13,15 @@
class="ripple-btn dark-text"
(click)="handleCilckTopNav(i)"
>
{{ item.title }}
<app-del delIconStyle="top: -8px;right: -16px;" [oIdx]="i">
{{ item.title }}
</app-del>
</a>
</nav>
<div class="wrapper">
<nav class="sidebar dark-bg" id="sidebar">
<div *ngIf="websiteList[page].nav.length">
<div *ngIf="websiteList[page]?.nav?.length > 0">
<div
*ngFor="let item of websiteList[page].nav; let i = index"
(click)="handleSidebarNav(i)"
......@@ -27,7 +29,9 @@
[class.dark-item-active]="id === i"
class="ripple-btn dark-text"
>
{{ item.title || websiteList[page].title }}
<app-del delIconStyle="top: -10px;" [oIdx]="page" [twoIdx]="i">
{{ item.title }}
</app-del>
</div>
</div>
</nav>
......@@ -35,38 +39,48 @@
<aside class="site-box dark-bg">
<div *ngFor="let item of currentList; let i=index">
<div class="nav-wrapper">
<div
class="title dark-primary dark-border-color"
*ngIf="item.title"
(click)="onCollapse(item, i)"
>
{{ item.title }} x {{ item.nav.length }}
<app-del [oIdx]="page" [twoIdx]="id" [threeIdx]="i">
<div
class="title dark-primary dark-border-color"
*ngIf="item.title"
(click)="onCollapse(item, i)"
>
<app-logo
[src]="item.icon"
[name]="item.title"
>
</app-logo>
<span style="margin-left: 10px;align-self: center;">{{ item.title }} x {{ item.nav.length }}</span>
<i class="iconfont iconjiantouarrow483 down-arrow" [class.active]="item.collapsed"></i>
</div>
<i class="iconfont iconjiantouarrow483 down-arrow" [class.active]="item.collapsed"></i>
</div>
</app-del>
<ul class="ul" *ngIf="!item.collapsed">
<li *ngFor="let ele of item.nav" class="dark-border-color">
<a
class="url-box"
[href]="ele.url"
target="_blank"
rel="noopener noreferer"
>
<div class="box-wrapper">
<div class="left">
<app-logo
[src]="ele.icon || item.icon || websiteList[page].nav[id].icon"
[name]="ele.name"
>
</app-logo>
<app-del [oIdx]="page" [twoIdx]="id" [threeIdx]="i" [fourIdx]="idx">
<a
class="url-box"
[href]="ele.url"
target="_blank"
rel="noopener noreferer"
>
<div class="box-wrapper">
<div class="left">
<app-logo
[src]="ele.icon || item.icon || websiteList[page].nav[id].icon"
[name]="ele.name"
>
</app-logo>
</div>
<div class="right dark-title" [innerHtml]="ele.name" [title]="ele.name"></div>
</div>
<div class="right dark-title" [innerHtml]="ele.name" [title]="ele.name"></div>
</div>
<div class="desc dark-text" [innerHtml]="ele.desc"></div>
</a>
<div class="desc dark-text" [innerHtml]="ele.desc"></div>
</a>
</app-del>
<app-multiple-site [dataSource]="ele"></app-multiple-site>
</li>
......@@ -82,7 +96,7 @@
<app-footer className="sim-footer"></app-footer>
<app-fixbar
(onCollapse)="onCollapseAll()"
[collapsed]="websiteList[page].nav[id].collapsed"
[collapsed]="collapsed()"
>
</app-fixbar>
......
......@@ -11,7 +11,6 @@ import {
setWebsiteList,
toggleCollapseAll,
totalWeb,
imgErrorInRemove
} from '../../../utils'
import { initRipple, setAnnotate } from '../../../utils/ripple'
import { websiteList } from '../../../store'
......@@ -40,8 +39,15 @@ export default class HomeComponent {
ngOnInit() {
const initList = () => {
this.currentList = this.websiteList[this.page].nav[this.id].nav
try {
if (this.websiteList[this.page] && this.websiteList[this.page]?.nav?.length > 0) {
this.currentList = this.websiteList[this.page].nav[this.id].nav
} else {
this.currentList = []
}
} catch (error) {
this.currentList = []
}
}
this.activatedRoute.queryParams.subscribe(() => {
......@@ -74,7 +80,7 @@ export default class HomeComponent {
this.currentList = fuzzySearch(this.websiteList, this.searchKeyword)
const params = queryString()
this.router.navigate(['/sim'], {
this.router.navigate([this.router.url.split('?')[0]], {
queryParams: {
...params,
q: this.searchKeyword
......@@ -110,7 +116,7 @@ export default class HomeComponent {
handleSidebarNav(index) {
const { page } = queryString()
this.websiteList[page].id = index
this.router.navigate(['/sim'], {
this.router.navigate([this.router.url.split('?')[0]], {
queryParams: {
page,
id: index,
......@@ -121,7 +127,7 @@ export default class HomeComponent {
handleCilckTopNav(idx) {
const id = this.websiteList[idx].id || 0
this.router.navigate(['/sim'], {
this.router.navigate([this.router.url.split('?')[0]], {
queryParams: {
page: idx,
id,
......@@ -140,11 +146,18 @@ export default class HomeComponent {
toggleCollapseAll(this.websiteList)
}
collapsed() {
try {
return websiteList[this.page].nav[this.id].collapsed
} catch (error) {
return false
}
}
onSearch(v) {
this.searchKeyword = v
this.handleSearch()
}
handleSearch = null
onSideLogoError = imgErrorInRemove
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册