提交 17aa2717 编写于 作者: X xjh22222228

feat: THEME

上级 f080ea89
<p align="center">
<a href="https://nav3.cn/?g">
<img src="src/assets/icon/logo.png" width="130" />
<img src="src/assets/logo.png" width="130" />
</a>
<br />
<b>发现导航</b>
......@@ -24,7 +24,23 @@
[在线预览](https://nav3.cn/?g)
![Preview](media/poster.png)
![Preview](media/screenshot.png)
## 内置
- [TypeScript](https://www.typescriptlang.org/)
- [Angular v9](https://angular.io/)
- [jQuery](https://jquery.com/)
## 主题
- [sim](https://nav3.cn/#/sim)
- [light](https://nav3.cn/#/light)
......@@ -40,6 +56,7 @@
- [√] 支持自定义引擎搜索。
- [√] 纯静态, 提供自动化部署功能。
- [√] 完全开源,轻松定制化。
- [√] 多款主题。
## 贡献
......@@ -60,7 +77,7 @@ Thank you for your [contribution](https://github.com/xjh22222228/nav/issues), me
## Build Setup
## 构建设置
``` bash
# 下载
git clone --depth=1 https://github.com/xjh22222228/nav.git
......
......@@ -3,12 +3,20 @@
* @author xiejiahe
* @url https://github.com/xjh22222228/nav
*/
import { ISearchEngineProps } from '../src/types'
import { ISearchEngineProps, ThemeType } from '../src/types'
// 主题: light | sim
export const THEME: ThemeType = 'sim'
// 搜索引擎列表, 为空时不显示搜索引擎
// 以下系统内置了一些,需要其他的自行添加
// 自定义引擎 icon 建议使用网络图标 减少入侵
export const SEARCH_ENGINE_LIST: ISearchEngineProps[] = [
{
name: '站内',
icon: 'assets/logo.png',
placeholder: '站内搜索'
},
{
name: '百度',
url: 'https://www.baidu.com/s?wd=',
......@@ -44,6 +52,9 @@ export const SEARCH_ENGINE_LIST: ISearchEngineProps[] = [
}
]
// 网站标题
export const TITLE = '发现导航 - 精选实用导航网站'
// Git 仓库地址, 没有填空字符串
export const GIT_REPO_URL = 'https://github.com/xjh22222228/nav'
......
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import { TONGJI_URL } from '../../config'
import { TONGJI_URL, TITLE, THEME } from '../../config'
import { queryString, setLocation } from '../utils'
@Component({
......@@ -12,6 +12,8 @@ export class AppComponent {
constructor (private router: Router, private activatedRoute: ActivatedRoute) {}
ngOnInit() {
document.title = TITLE
this.goRoute()
this.appendTongji()
......@@ -28,12 +30,15 @@ export class AppComponent {
if (screenWidth < 768) {
this.router.navigate(['/app'], { queryParams })
} else {
this.router.navigate(['/index'], { queryParams })
this.router.navigate(['/' + THEME], { queryParams })
}
}
appendTongji() {
if (document.getElementById('tongji_url')) return
if (
document.getElementById('tongji_url') ||
window.location.hostname === 'localhost'
) return
const script = document.createElement('script')
script.src = TONGJI_URL
......
......@@ -2,13 +2,16 @@ import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { FormsModule } from '@angular/forms'
import { THEME } from '../../config'
// components
import { AppComponent } from './app.component'
// views
import HomeComponent from '../view/index/default/index.component'
import LightComponent from '../view/index/light/index.component'
import SimComponent from '../view/index/sim/index.component'
import WebpComponent from '../view/app/default/app.component'
import { FixbarComponent } from '../components/fixbar/index.component'
import { FooterComponent } from '../components/footer/footer.component'
import { IconGitComponent } from '../components/icon-git/icon-git.component'
import { NoDataComponent } from '../components/no-data/no-data.component'
......@@ -17,8 +20,12 @@ import { SearchEngineComponent } from '../components/search-engine/search-engine
const appRoutes: Routes = [
{
path: 'index',
component: HomeComponent,
path: 'sim',
component: SimComponent,
},
{
path: 'light',
component: LightComponent,
},
{
path: 'app',
......@@ -26,7 +33,7 @@ const appRoutes: Routes = [
},
{
path: '**',
redirectTo: '/index',
redirectTo: '/' + THEME,
},
]
......@@ -34,8 +41,10 @@ const appRoutes: Routes = [
@NgModule({
declarations: [
AppComponent,
HomeComponent,
LightComponent,
SimComponent,
WebpComponent,
FixbarComponent,
FooterComponent,
IconGitComponent,
NoDataComponent,
......
<div class="fixbar">
<div class="wrapper" [class.active]="collapsed">
<img class="icon" src="assets/img/collapse.svg" (click)="collapse()">
</div>
<div class="wrapper">
<img class="icon" src="assets/img/down-arrow.svg" (click)="scrollTop()">
</div>
</div>
.fixbar {
z-index: 9;
position: fixed;
bottom: 30px;
right: 15px;
.wrapper {
margin-top: 10px;
transition: .1s linear;
text-align: center;
&.active {
transform: rotate(-90deg);
}
}
.icon {
width: 20px;
height: 20px;
transform: rotate(180deg);
cursor: pointer;
}
}
import { Component, Output, EventEmitter, Input } from '@angular/core'
@Component({
selector: 'app-fixbar',
templateUrl: './index.component.html',
styleUrls: ['./index.component.scss']
})
export class FixbarComponent {
@Input() collapsed: boolean
@Output() onCollapse = new EventEmitter()
scrollTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
collapse() {
this.onCollapse.emit()
}
}
<footer class="footer">
<footer class="footer" [class]="className" >
<div class="total" [ngStyle]="{marginBottom: !FOOTER_DESC && '5px'}">
共收录 {{ totalWeb }} 个网站
</div>
......
import { Component, OnInit } from '@angular/core'
import { Component, Input } from '@angular/core'
import { FOOTER_DESC } from '../../../config'
import { totalWeb } from '../../utils'
......@@ -7,12 +7,9 @@ import { totalWeb } from '../../utils'
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
export class FooterComponent {
FOOTER_DESC: string = FOOTER_DESC;
totalWeb: number = totalWeb()
constructor() { }
ngOnInit(): void {
}
@Input() className: string
}
.no-result {
margin-top: 80px;
padding: 80px 0;
text-align: center;
.back {
......
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
@Component({
selector: 'app-no-data',
templateUrl: './no-data.component.html',
styleUrls: ['./no-data.component.scss']
})
export class NoDataComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
export class NoDataComponent {
goBack = () => {
history.go(-1);
}
......
......@@ -16,7 +16,7 @@
(keyup)="onKey($event)"
/>
<div class="search-icon" (click)="onSearch()"></div>
<div class="search-icon" (click)="triggerSearch()"></div>
</div>
<div class="engine-main" *ngIf="showEngine">
......
import { Component, OnInit } from '@angular/core'
import { Component, Output, EventEmitter } from '@angular/core'
import { SEARCH_ENGINE_LIST } from '../../../config'
import { getDefaultSearchEngine, setDefaultSearchEngine } from '../../utils'
import { getDefaultSearchEngine, setDefaultSearchEngine, queryString } from '../../utils'
@Component({
selector: 'app-search-engine',
templateUrl: './search-engine.component.html',
styleUrls: ['./search-engine.component.scss']
})
export class SearchEngineComponent implements OnInit {
export class SearchEngineComponent {
SEARCH_ENGINE_LIST = SEARCH_ENGINE_LIST
currentEngine = getDefaultSearchEngine()
showEngine = false
keyword = ''
keyword = queryString().q
constructor() { }
ngOnInit(): void {
}
@Output() onSearch = new EventEmitter<string>()
inputFocus() {
const inputEl = document.getElementById('search-engine-input')
if (inputEl) {
inputEl.focus()
}
inputEl?.focus?.()
}
ngAfterViewInit() {
......@@ -37,6 +32,8 @@ export class SearchEngineComponent implements OnInit {
}
toggleEngine(e?: Event, isShow?: boolean) {
if (this.SEARCH_ENGINE_LIST.length <= 1) return
if (e) {
e.stopPropagation()
}
......@@ -52,13 +49,17 @@ export class SearchEngineComponent implements OnInit {
setDefaultSearchEngine(this.currentEngine)
}
onSearch() {
window.open(this.currentEngine.url + this.keyword)
triggerSearch() {
if (this.currentEngine.url) {
window.open(this.currentEngine.url + this.keyword)
}
this.onSearch.emit(this.keyword)
}
onKey(event: KeyboardEvent) {
if (event.code === 'Enter') {
this.onSearch()
this.triggerSearch()
}
}
}
......@@ -33,8 +33,8 @@
<meta name="renderer" content="webkit">
<meta name="description" content="发现导航 - 精选实用导航网站">
<meta name="keywords" content="导航,前端资源,社区站点,设计师,实用工具,学习资源,运营,网络安全,node.js">
<link rel="icon" href="assets/icon/logo.png">
<link rel ="apple-touch-icon" href="assets/icon/logo.png">
<link rel="icon" href="assets/logo.png">
<link rel ="apple-touch-icon" href="assets/logo.png">
</head>
<body>
......
......@@ -17,7 +17,7 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-overflow-scrolling: touch;
font-family: -apple-system, "PingFang SC", "Hiragino Sans GB", Arial, "Microsoft YaHei", "Helvetica Neue", sans-serif;
font: 14px/1.5 -apple-system,BlinkSystemFont,"Segoe UI",Roboto,Ubuntu,"Helvetica Neue",Helevetica,Arial,"PingFang SC","Hiragino Sans GB","Microsoft YaHei UI","Microsoft YaHei","Source Han Sans CN",sans-serif;
font-size: 14px;
color: #333;
}
......@@ -108,4 +108,18 @@ b {
color: #f50 !important;
}
// 折叠箭头
.down-arrow {
width: 15px;
height: 15px;
align-self: center;
margin-left: 15px;
cursor: pointer;
transition: .1s linear;
&.active {
transform: rotate(-90deg);
}
}
.has-ripple{position:relative;overflow:hidden;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ripple{display:block;position:absolute;pointer-events:none;border-radius:50%;-webkit-transform:scale(0);-o-transform:scale(0);transform:scale(0);background:#fff;opacity:1}.ripple-animate{-webkit-animation:ripple;-o-animation:ripple;animation:ripple}@-webkit-keyframes ripple{100%{opacity:0;-webkit-transform:scale(2);transform:scale(2)}}@-o-keyframes ripple{100%{opacity:0;-o-transform:scale(2);transform:scale(2)}}@keyframes ripple{100%{opacity:0;transform:scale(2)}}
export type ThemeType =
| 'light'
| 'sim'
export interface INavFourProp {
icon?: string | null
name: string
......@@ -29,7 +33,7 @@ export interface INavProps {
export interface ISearchEngineProps {
name: string
url: string
url?: string
icon: string
placeholder?: string
}
......@@ -69,8 +69,12 @@ export function fuzzySearch(navList: INavProps[], keyword: string) {
return searchResultList
}
let total = 0
export function totalWeb(): number {
let total = 0;
if (total) {
return total
}
function r(nav) {
if (!Array.isArray(nav)) return
......@@ -216,3 +220,8 @@ export function getDefaultSearchEngine(): ISearchEngineProps {
export function setDefaultSearchEngine(engine: ISearchEngineProps) {
window.localStorage.setItem('engine', JSON.stringify(engine))
}
export function imgErrorInRemove(e) {
const el = e.currentTarget;
el?.parentNode?.removeChild?.(el)
}
import { annotate } from 'rough-notation'
import { queryString } from './index'
let ANNOTATE_EQUEUE = []
export function initRipple() {
(<any>window).$.ripple('.ripple-btn', {
multi: true,
debug: false,
opacity: .2,
})
}
export function setAnnotate(querySelector = '.top-nav .ripple-btn') {
const elList = document.querySelectorAll(querySelector) || []
if (elList.length === 0) return
ANNOTATE_EQUEUE.forEach(item => item.hide())
ANNOTATE_EQUEUE = []
const { page } = queryString()
const annotation = annotate(elList[page], {
type: 'underline',
color: 'red',
padding: 3,
strokeWidth: 3
})
ANNOTATE_EQUEUE.push(annotation)
annotation.show()
}
......@@ -2,7 +2,7 @@
<header class="header">
<div class="header-top">
<a href="https://github.com/xjh22222228/nav" target="_blank" class="logo">
<img src="assets/icon/logo.png" alt="logo">
<img src="assets/logo.png" alt="logo">
</a>
<div class="open" [class.active]="open" (click)="handleToggleOpen()">
<i></i>
......@@ -34,8 +34,8 @@
</nav>
<ul>
<li *ngFor="let item of nav[page].nav[id].nav">
<h2 class="block-title" *ngIf="item.title || item.subtitle">
{{ item.title || item.subtitle }} x {{ item.nav.length }}
<h2 class="block-title" *ngIf="item.title">
{{ item.title }} x {{ item.nav.length }}
</h2>
<div class="row">
<div class="item-list" *ngFor="let el of item.nav; index as i;">
......
<div class="index-wrapper">
<main class="homepage container">
<input
type="text"
class="search"
placeholder="聚焦搜索"
maxlength="50"
(input)="searchLoading = true; handleSearch()"
[(ngModel)]="searchKeyword"
*ngIf="showInput"
(blur)="showInput = false"
/>
<div
class="fixed-collapse"
(click)="onCollapseAll()"
[class.active]="websiteList[page].nav[id].collapsed"
>
<img
src="assets/img/collapse.svg"
alt=""
>
<img src="assets/img/collapse.svg">
</div>
<nav class="top-nav user-select-none">
......@@ -27,7 +13,8 @@
*ngFor="let item of websiteList; let i = index;"
(click)="handleCilckTopNav(i)"
[class.active]="page === i"
class="ripple-btn">
class="ripple-btn"
>
{{ item.title }}
</a>
</nav>
......@@ -43,27 +30,22 @@
<a class="ripple-btn" *ngIf="item.title">{{ item.title }}</a>
</li>
</ul>
<div class="icon-box">
<a href="javascript:;" (click)="handleOnClickSearch()">
<img src="assets/img/search.svg" alt="" class="icon" draggable="false">
</a>
</div>
</aside>
<div class="main">
<app-loading *ngIf="searchLoading"></app-loading>
<app-search-engine></app-search-engine>
<app-search-engine (onSearch)="onSearch($event)"></app-search-engine>
<ul *ngIf="currentList.length && currentList[0].nav">
<li *ngFor="let item of currentList; let i=index">
<div class="title-wrapper" *ngIf="item.title || item.subtitle">
<div class="title-wrapper" *ngIf="item.title">
<h2 class="block-title">
<span
(click)="onCollapse(item, i)"
class="cursor-pointer"
>
{{ item.title || item.subtitle }} x {{ item.nav.length }}
{{ item.title }} x {{ item.nav.length }}
</span>
<img
......
......@@ -13,28 +13,13 @@
overflow: hidden;
transition: .1s linear;
.search {
z-index: 66666;
position: fixed;
top: 250px;
left: 50%;
width: 600px;
padding: 10px;
border-radius: 10px;
transform: translate(-50%, 0);
background: #e5e5ea;
font-size: 26px;
border: 1px solid #ccc;
box-shadow: 0 4px 8px 1px #ccc;
}
.fixed-collapse {
z-index: 99999;
position: absolute;
bottom: 30px;
right: 30px;
width: 50px;
height: 50px;
width: 39px;
height: 39px;
background: #eee;
border-radius: 50%;
display: flex;
......@@ -82,18 +67,6 @@
height: calc(100% - 52px);
overflow: hidden;
.icon-box {
position: absolute;
bottom: 20px;
left: 0;
right: 0;
text-align: center;
.icon {
width: 20px;
}
}
// 侧边栏分类
$sidebarWidth: 80px;
.sidebar {
......@@ -251,19 +224,6 @@
}
}
.down-arrow {
width: 15px;
height: 15px;
align-self: center;
margin-left: 15px;
cursor: pointer;
transition: .1s linear;
&.active {
transform: rotate(-90deg);
}
}
.block-title {
display: inline-block;
position: relative;
......
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import { INDEX_LANGUAGE, GIT_REPO_URL } from '../../../../config'
import { annotate } from 'rough-notation'
import { INavProps, INavThreeProp } from '../../../types'
import {
debounce,
......@@ -12,9 +11,9 @@ import {
getWebsiteList,
setWebsiteList,
toggleCollapseAll,
imgErrorInRemove
} from '../../../utils'
let ANNOTATE_EQUEUE = []
import { initRipple, setAnnotate } from '../../../utils/ripple'
@Component({
selector: 'app-home',
......@@ -31,7 +30,6 @@ export default class HomeComponent {
page: number = 0
searchKeyword: string = ''
showInput = false
searchLoading = false
language: string[] = INDEX_LANGUAGE
GIT_REPO_URL: string = GIT_REPO_URL
......@@ -51,13 +49,12 @@ export default class HomeComponent {
if (q) {
this.currentList = fuzzySearch(this.websiteList, q)
this.searchLoading = false
} else {
initList()
}
if (tempPage !== page) {
this.setAnnotate()
setAnnotate()
}
setWebsiteList(this.websiteList)
......@@ -66,20 +63,22 @@ export default class HomeComponent {
this.handleSearch = debounce(() => {
if (!this.searchKeyword) {
initList()
this.searchLoading = false
return
}
const params = queryString()
this.router.navigate(['/index'], {
this.router.navigate(['/light'], {
queryParams: {
...params,
q: this.searchKeyword
}
})
}, 1000, true)
}
this.searchLoading = true
}, 1000, false)
onSearch(v) {
this.searchKeyword = v
this.handleSearch()
}
handleOnClickSearch() {
......@@ -94,7 +93,7 @@ export default class HomeComponent {
handleCilckTopNav(index) {
const id = this.websiteList[index].id || 0
this.router.navigate(['/index'], {
this.router.navigate(['/light'], {
queryParams: {
page: index,
id,
......@@ -103,10 +102,10 @@ export default class HomeComponent {
})
}
handleSidebarNav (index) {
handleSidebarNav(index) {
const { page } = queryString()
this.websiteList[page].id = index
this.router.navigate(['/index'], {
this.router.navigate(['/light'], {
queryParams: {
page,
id: index,
......@@ -116,30 +115,8 @@ export default class HomeComponent {
}
ngAfterViewInit () {
this.setAnnotate();
(<any>window).$.ripple('.ripple-btn', {
multi: true,
debug: false,
opacity: .2,
})
}
setAnnotate() {
const elList = document.querySelectorAll('.top-nav .ripple-btn') || []
if (elList.length === 0) return
ANNOTATE_EQUEUE.forEach(item => item.hide())
ANNOTATE_EQUEUE = []
const annotation = annotate(elList[this.page], {
type: 'underline',
color: 'red',
padding: 3,
strokeWidth: 3
})
ANNOTATE_EQUEUE.push(annotation)
annotation.show()
setAnnotate();
initRipple()
}
onCollapse = (item, index) => {
......@@ -154,11 +131,5 @@ export default class HomeComponent {
handleSearch = null
onImgError = onImgError
onSideLogoError(e) {
const el = e.currentTarget;
if (el) {
el?.parentNode?.removeChild(el)
}
}
onSideLogoError = imgErrorInRemove
}
<div class="sim">
<div class="wallpaper">
<h1 class="title">{{ title }}</h1>
<h2 class="description">这里收录多达 <b>{{ totalWeb }}</b> 个优质网站, 助您工作、学习和生活</h2>
<app-search-engine (onSearch)="onSearch($event)"></app-search-engine>
</div>
<nav class="top-nav user-select-none">
<a
*ngFor="let item of websiteList; let i = index;"
[class.active]="page === i"
class="ripple-btn"
(click)="handleCilckTopNav(i)"
>
{{ item.title }}
</a>
</nav>
<div class="wrapper">
<nav class="sidebar" id="sidebar">
<div *ngIf="websiteList[page].nav.length">
<div
*ngFor="let item of websiteList[page].nav; let i = index"
(click)="handleSidebarNav(i)"
[class.active]="id === i"
class="ripple-btn"
>
{{ item.title || websiteList[page].title }}
</div>
</div>
</nav>
<aside class="site-box">
<app-no-data
*ngIf="currentList.length && currentList[0].nav && !currentList[0].nav.length"
>
</app-no-data>
<div *ngFor="let item of currentList; let i=index">
<div class="nav-wrapper">
<div class="title" *ngIf="item.title" (click)="onCollapse(item, i)">
{{ item.title }} x {{ item.nav.length }}
<img
src="assets/img/down-arrow.svg"
class="down-arrow"
[class.active]="item.collapsed"
draggable="false"
/>
</div>
<ul class="ul" *ngIf="!item.collapsed">
<li *ngFor="let ele of item.nav">
<a class="url-box" [href]="ele.url" target="_blank" rel="noopener noreferer">
<div class="left">
<img
*ngIf="ele.icon || item.icon || websiteList[page].nav[id].icon; else icon"
[src]="ele.icon || item.icon || websiteList[page].nav[id].icon"
alt=""
class="icon"
(error)="onImgError($event)"
>
<ng-template #icon>
<span class="icon"></span>
</ng-template>
</div>
<div class="right">
<em class="name" [innerHtml]="ele.name"></em>
<div class="desc" [innerHtml]="ele.desc"></div>
</div>
</a>
<div class="mark" *ngIf="ele.language && ele.language.length > 0">
<div class="button-box">
<a
[href]="ele.language[0]"
class="zh"
target="_blank"
*ngIf="ele.language[0]"
rel="noopener noreferer"
>
{{ language[0] }}
</a>
<a
[href]="ele.language[1]"
class="zh"
target="_blank"
*ngIf="ele.language[1]"
rel="noopener noreferer"
>
{{ language[1] }}
</a>
<a
[href]="ele.language[2]"
class="zh"
target="_blank"
*ngIf="ele.language[2]"
rel="noopener noreferer"
>
{{ language[2] }}
</a>
</div>
</div>
</li>
</ul>
</div>
</div>
</aside>
</div>
</div>
<app-footer className="sim-footer"></app-footer>
<app-fixbar
(onCollapse)="onCollapseAll()"
[collapsed]="websiteList[page].nav[id].collapsed"
>
</app-fixbar>
<div class="sim-bg"></div>
$width: 1200px;
.sim-bg {
z-index: -1;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #f0f2f5;
}
.top-nav {
width: $width;
margin: 10px auto;
padding: 10px;
overflow: none;
white-space: nowrap;
border-bottom: 1px solid #eee;
text-align: center;
a {
display: inline-block;
padding: 10px 15px;
color: #000;
cursor: pointer;
border-radius: 5px;
overflow: hidden;
&:hover {
text-decoration: none;
}
}
}
.sim {
display: flex;
flex-direction: column;
min-height: 100vh;
.wallpaper {
height: 350px;
background: url("../../../assets/img/wallpaper.jpg") no-repeat;
border-radius: 8px;
background-position: top center;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.title {
color: #fff;
margin-bottom: 10px;
font-size: 36px;
}
.description {
text-align: center;
color: #fff;
margin-bottom: 10px;
}
}
.wrapper {
flex: 1;
position: relative;
width: $width;
padding-bottom: 50px;
margin: 0 auto;
.sidebar {
width: 180px;
height: min-content;
padding: 10px 0;
padding-bottom: 0;
text-align: center;
float: left;
background-color: #fff;
box-shadow: 0 0 1px #ccc;
&.fix {
position: fixed;
top: 10px;
}
.ripple-btn {
padding: 10px 0;
margin-bottom: 10px;
cursor: pointer;
border-radius: 5px;
transition: .1s linear;
user-select: none;
font-weight: 500;
&.active {
color: #409eff;
background-color: rgb(231, 241, 253);
}
}
}
.site-box {
width: 990px;
position: relative;
background-color: #fff;
margin-left: 30px;
padding: 15px;
float: right;
.title {
font-size: 18px;
border-bottom: 1px solid #eee;
padding: 10px 0;
color: #3f51b5;
font-weight: 500;
cursor: pointer;
}
ul {
display: flex;
flex-wrap: wrap;
}
li {
position: relative;
width: 225px;
padding: 15px;
margin: 20px 20px 0 0;
transition: .1s ease-out;
border: 1px solid #eee;
overflow: hidden;
border-radius: 3px;
cursor: pointer;
&:nth-child(4n) {
margin-right: 0;
}
&:hover {
box-shadow: 2px 2px 25px -5px rgba(0,0,0,.2);
.mark {
bottom: 0 !important;
}
}
.icon {
display: inline-block;
width: 35px;
height: 35px;
vertical-align: middle;
border-radius: 50%;
pointer-events: none;
border: 1px solid #eee;
background-color: #fff;
}
}
.url-box {
display: flex;
.right {
flex: 1;
margin-left: 10px;
}
.name {
font-size: 16px;
font-weight: 600;
color: #273a52;
}
.desc {
margin-top: 5px;
color: #4c5d73;
}
}
}
}
.mark {
z-index: 28;
position: absolute;
bottom: -50px;
left: 0;
width: 100%;
padding: 10px 0 5px 0px;
background: #fbfbfb;
cursor: auto;
display: flex;
justify-content: center;
align-content: center;
transition: .1s linear;
.button-box {
width: 100%;
text-align: center;
}
a {
display: inline-block;
position: relative;
width: 50px;
font-size: 12px;
padding: 3px 0;
border: none;
background: #2db7f5;
color: #fff;
border-radius: 3px;
overflow: hidden;
&:not(:nth-last-child(1)) {
margin-right: 10px;
}
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #000;
opacity: 0;
}
&:active:after {
opacity: .2;
}
}
.zh {
background: #26a2ff;
}
}
}
::ng-deep .sim-footer {
margin-bottom: 10px;
& > * {
color: #666;
}
}
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import { INDEX_LANGUAGE, GIT_REPO_URL, TITLE } from '../../../../config'
import { INavProps, INavThreeProp } from '../../../types'
import {
debounce,
fuzzySearch,
onImgError,
queryString,
getWebsiteList,
setWebsiteList,
toggleCollapseAll,
totalWeb,
imgErrorInRemove
} from '../../../utils'
import { initRipple, setAnnotate } from '../../../utils/ripple'
let sidebarEl: HTMLElement;
@Component({
selector: 'app-home',
templateUrl: './index.component.html',
styleUrls: ['./index.component.scss']
})
export default class HomeComponent {
constructor (private router: Router, private activatedRoute: ActivatedRoute) {}
websiteList: INavProps[] = getWebsiteList()
currentList: INavThreeProp[] = []
id: number = 0
page: number = 0
searchKeyword: string = ''
language: string[] = INDEX_LANGUAGE
GIT_REPO_URL: string = GIT_REPO_URL
totalWeb: number = totalWeb()
title: string = TITLE
ngOnInit() {
const initList = () => {
this.currentList = this.websiteList[this.page].nav[this.id].nav
}
this.activatedRoute.queryParams.subscribe(() => {
const tempPage = this.page
const { id, page, q } = queryString()
this.searchKeyword = q
this.page = page
this.id = id
if (q) {
this.currentList = fuzzySearch(this.websiteList, q)
} else {
initList()
}
if (tempPage !== page) {
setAnnotate()
}
setWebsiteList(this.websiteList)
})
this.handleSearch = debounce(() => {
if (!this.searchKeyword) {
initList()
return
}
this.currentList = fuzzySearch(this.websiteList, this.searchKeyword)
const params = queryString()
this.router.navigate(['/sim'], {
queryParams: {
...params,
q: this.searchKeyword
}
})
}, 1000, true)
}
onScroll() {
const y = window.scrollY
if (!sidebarEl) {
sidebarEl = document.getElementById('sidebar')
}
if (y >= 438) {
sidebarEl.classList.add('fix')
} else {
sidebarEl.classList.remove('fix')
}
}
ngOnDestroy() {
window.removeEventListener('scroll', this.onScroll)
}
ngAfterViewInit () {
initRipple()
setAnnotate();
window.addEventListener('scroll', this.onScroll)
}
handleSidebarNav(index) {
const { page } = queryString()
this.websiteList[page].id = index
this.router.navigate(['/sim'], {
queryParams: {
page,
id: index,
_: Date.now()
}
})
}
handleCilckTopNav(idx) {
const id = this.websiteList[idx].id || 0
this.router.navigate(['/sim'], {
queryParams: {
page: idx,
id,
_: Date.now()
}
})
}
onCollapse = (item, index) => {
item.collapsed = !item.collapsed
this.websiteList[this.page].nav[this.id].nav[index] = item
setWebsiteList(this.websiteList)
}
onCollapseAll = () => {
toggleCollapseAll(this.websiteList)
}
onSearch(v) {
this.searchKeyword = v
this.handleSearch()
}
handleSearch = null
onImgError = onImgError
onSideLogoError = imgErrorInRemove
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册