├── .navauth ├── data ├── component.json ├── search.json ├── tag.json ├── internal.json └── settings.json ├── src ├── view │ ├── system │ │ ├── info │ │ │ ├── index.component.scss │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── component │ │ │ ├── index.component.scss │ │ │ ├── types.ts │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── tag │ │ │ ├── index.component.scss │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── bookmark │ │ │ ├── index.component.scss │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── collect │ │ │ ├── index.component.scss │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── vip-auth │ │ │ ├── index.component.scss │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── search │ │ │ ├── index.component.scss │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── bookmark-export │ │ │ ├── index.component.scss │ │ │ └── index.component.html │ │ ├── index.component.scss │ │ ├── web │ │ │ └── index.component.scss │ │ ├── setting │ │ │ └── index.component.scss │ │ ├── index.component.ts │ │ └── index.component.html │ ├── app │ │ └── default │ │ │ ├── app.component.ts │ │ │ ├── app.component.html │ │ │ └── app.component.scss │ ├── light │ │ ├── index.component.ts │ │ └── index.component.scss │ ├── sim │ │ ├── index.component.ts │ │ └── index.component.scss │ ├── super │ │ └── index.component.ts │ ├── side │ │ ├── index.component.ts │ │ └── index.component.scss │ └── shortcut │ │ ├── index.component.html │ │ ├── index.component.scss │ │ └── index.component.ts ├── components │ ├── image │ │ ├── drawer │ │ │ ├── index.component.scss │ │ │ ├── index.component.ts │ │ │ └── index.component.html │ │ ├── index.component.html │ │ ├── index.component.scss │ │ └── index.component.ts │ ├── calendar │ │ ├── drawer │ │ │ ├── index.component.scss │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── index.component.html │ │ ├── index.component.scss │ │ └── index.component.ts │ ├── off-work │ │ ├── drawer │ │ │ ├── index.component.scss │ │ │ ├── index.component.ts │ │ │ └── index.component.html │ │ ├── index.component.html │ │ ├── index.component.scss │ │ └── index.component.ts │ ├── runtime │ │ ├── drawer │ │ │ ├── index.component.scss │ │ │ ├── index.component.html │ │ │ └── index.component.ts │ │ ├── index.component.html │ │ ├── index.component.ts │ │ └── index.component.scss │ ├── move-web │ │ ├── index.component.scss │ │ └── index.component.html │ ├── footer │ │ ├── footer.component.scss │ │ ├── footer.component.html │ │ ├── footer.component.ts │ │ └── template.ts │ ├── upload │ │ ├── index.component.scss │ │ ├── index.component.html │ │ └── index.component.ts │ ├── login │ │ ├── login.component.scss │ │ ├── login.component.html │ │ └── login.component.ts │ ├── no-data │ │ ├── no-data.component.scss │ │ ├── no-data.component.html │ │ └── no-data.component.ts │ ├── search-engine │ │ ├── index.ts │ │ ├── search-engine.component.scss │ │ ├── search-engine.component.ts │ │ └── search-engine.component.html │ ├── component-group │ │ ├── index.component.scss │ │ ├── index.component.html │ │ └── index.component.ts │ ├── icon-git │ │ ├── icon-git.component.scss │ │ ├── icon-git.component.ts │ │ └── icon-git.component.html │ ├── swiper │ │ ├── index.component.scss │ │ ├── index.component.ts │ │ └── index.component.html │ ├── web-more-menu │ │ ├── index.component.scss │ │ ├── index.component.html │ │ └── index.component.ts │ ├── tag-list │ │ ├── index.component.scss │ │ ├── index.component.html │ │ └── index.component.ts │ ├── create-web │ │ └── index.component.scss │ ├── toolbar-title │ │ ├── index.component.scss │ │ ├── index.component.html │ │ └── index.component.ts │ ├── logo │ │ ├── logo.component.ts │ │ ├── logo.component.html │ │ └── logo.component.scss │ ├── web-list │ │ ├── index.component.html │ │ ├── index.component.scss │ │ └── index.component.ts │ ├── fixbar │ │ ├── index.component.scss │ │ └── index.component.html │ └── card │ │ └── index.component.ts ├── app │ ├── app.component.scss │ ├── app.component.html │ ├── alert-event.ts │ ├── app-routing.module.ts │ └── app.component.ts ├── assets │ ├── fonts │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ ├── Amiko-Regular.ttf │ │ └── iconfont.css │ ├── styles │ │ ├── tailwind.css │ │ ├── nprogress.css │ │ └── dark.scss │ └── img │ │ ├── china.svg │ │ ├── light.svg │ │ ├── bookmark.svg │ │ └── component │ │ └── runtime.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── utils │ ├── mitt.ts │ ├── util.ts │ ├── user.ts │ └── http.ts ├── main.ts ├── types │ └── type.d.ts ├── pipe │ └── safeHtml.pipe.ts ├── locale │ └── index.ts ├── services │ ├── jump.ts │ └── common.ts ├── constants │ └── index.ts ├── store │ └── index.ts └── main.html ├── public └── readme.md ├── .gitattributes ├── .prettierrc.js ├── .eslintignore ├── netlify.toml ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── postcss.config.mjs ├── tsconfig.app.json ├── tsconfig.spec.json ├── .editorconfig ├── tailwind.config.js ├── .github └── workflows │ ├── sync.yml │ └── ci.yml ├── .gitignore ├── nav.config.yaml ├── tsconfig.json ├── scripts └── build.mjs ├── package.json ├── .eslintrc.js └── angular.json /.navauth: -------------------------------------------------------------------------------- 1 | OK -------------------------------------------------------------------------------- /data/component.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/search.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /data/tag.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /data/internal.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /src/view/system/info/index.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/image/drawer/index.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/view/system/component/index.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/calendar/drawer/index.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/off-work/drawer/index.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/runtime/drawer/index.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/readme.md: -------------------------------------------------------------------------------- 1 | https://github.com/xjh22222228/nav 2 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .fetchIng { 2 | height: 100vh; 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-language=TypeScript 2 | *.scss linguist-language=TypeScript -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | }; 5 | -------------------------------------------------------------------------------- /src/components/move-web/index.component.scss: -------------------------------------------------------------------------------- 1 | .act { 2 | margin-top: 30px; 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | data/ 2 | main.ts 3 | polyfills.ts 4 | test.ts 5 | environments/ 6 | assets/ -------------------------------------------------------------------------------- /src/components/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/nav/main/src/assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/assets/fonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/nav/main/src/assets/fonts/iconfont.woff -------------------------------------------------------------------------------- /src/assets/fonts/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/nav/main/src/assets/fonts/iconfont.woff2 -------------------------------------------------------------------------------- /src/components/upload/index.component.scss: -------------------------------------------------------------------------------- 1 | .file { 2 | .file-upload { 3 | display: none; 4 | } 5 | } -------------------------------------------------------------------------------- /src/assets/fonts/Amiko-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/nav/main/src/assets/fonts/Amiko-Regular.ttf -------------------------------------------------------------------------------- /src/utils/mitt.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | import mitt from 'mitt' 3 | 4 | export default mitt() 5 | -------------------------------------------------------------------------------- /src/view/system/tag/index.component.scss: -------------------------------------------------------------------------------- 1 | .add-btn { 2 | margin-bottom: 20px; 3 | margin-right: 20px; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/login/login.component.scss: -------------------------------------------------------------------------------- 1 | .prefix-icon { 2 | width: 20px; 3 | height: 20px; 4 | pointer-events: none; 5 | } 6 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/*" 3 | to = "/index.html" 4 | status = 200 5 | [build.environment] 6 | NODE_VERSION = "20" -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /src/components/no-data/no-data.component.scss: -------------------------------------------------------------------------------- 1 | .no-result { 2 | padding: 80px 0; 3 | text-align: center; 4 | 5 | .back { 6 | margin-top: 30px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/components/search-engine/index.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | 3 | export enum SearchType { 4 | All = 1, 5 | Title, 6 | Desc, 7 | Url, 8 | Current, 9 | Quick, 10 | } 11 | -------------------------------------------------------------------------------- /src/view/system/bookmark/index.component.scss: -------------------------------------------------------------------------------- 1 | .book-wrapper { 2 | text-align: center; 3 | input[type="file"] { 4 | display: none; 5 | } 6 | #file { 7 | cursor: pointer; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/image/index.component.html: -------------------------------------------------------------------------------- 1 |
5 |
{{ component.text }}
6 |
7 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | 6 | platformBrowserDynamic().bootstrapModule(AppModule) 7 | .catch(err => console.error(err)); 8 | -------------------------------------------------------------------------------- /src/view/system/collect/index.component.scss: -------------------------------------------------------------------------------- 1 | .add-btn { 2 | margin-bottom: 20px; 3 | margin-right: 20px; 4 | } 5 | .desc { 6 | width: 300px; 7 | white-space: pre-wrap; 8 | } 9 | .ant-table-wrapper { 10 | overflow: auto; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/component-group/index.component.scss: -------------------------------------------------------------------------------- 1 | .component-group { 2 | padding: 10px; 3 | display: flex; 4 | overflow: hidden; 5 | overflow-x: auto; 6 | user-select: none; 7 | column-gap: 15px; 8 | justify-content: flex-start; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/icon-git/icon-git.component.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 580px) { 2 | .github-link { 3 | display: none; 4 | } 5 | } 6 | 7 | .github-link { 8 | position: fixed; 9 | top: -6px; 10 | right: -6px; 11 | border: 0; 12 | z-index: 10; 13 | } 14 | -------------------------------------------------------------------------------- /src/components/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/no-data/no-data.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /src/components/swiper/index.component.scss: -------------------------------------------------------------------------------- 1 | .swiper { 2 | user-select: none; 3 | ::ng-deep { 4 | .slick-slide, 5 | .slick-track, 6 | .slick-list { 7 | height: auto !important; 8 | } 9 | } 10 | .bgimg { 11 | width: 100%; 12 | object-fit: cover; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/runtime/index.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{ component.title }}
3 |
4 | {{ runDays }} 5 | 6 |
7 | 8 |
9 | -------------------------------------------------------------------------------- /src/view/system/vip-auth/index.component.scss: -------------------------------------------------------------------------------- 1 | .add-btn { 2 | margin-bottom: 20px; 3 | margin-right: 20px; 4 | } 5 | .poster { 6 | width: 1000px; 7 | max-width: 100%; 8 | border-radius: 12px; 9 | } 10 | .desc { 11 | width: 300px; 12 | white-space: pre-wrap; 13 | } 14 | .ant-table-wrapper { 15 | overflow: auto; 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/components/web-more-menu/index.component.scss: -------------------------------------------------------------------------------- 1 | .over-item { 2 | cursor: pointer; 3 | margin: 0; 4 | padding: 7px 16px; 5 | text-align: center; 6 | &.moreActive { 7 | font-weight: bold !important; 8 | color: #08c; 9 | } 10 | } 11 | 12 | .more-btn { 13 | z-index: 11; // 比Github多1 14 | position: relative; 15 | cursor: pointer; 16 | } 17 | -------------------------------------------------------------------------------- /src/types/type.d.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | export {} 6 | 7 | declare global { 8 | const Swiper: any 9 | interface Window { 10 | __FINISHED__: boolean // 记录已取 web 数据 11 | __TITLE__: string | undefined 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/tag-list/index.component.scss: -------------------------------------------------------------------------------- 1 | .tagbox { 2 | display: flex; 3 | flex-wrap: wrap; 4 | gap: 6px; 5 | } 6 | .tag-item { 7 | padding: 0 6px; 8 | border-radius: 2px; 9 | font-size: 12px; 10 | display: flex; 11 | align-items: center; 12 | transition: all 0.1s linear; 13 | color: #fff; 14 | &:hover { 15 | opacity: 0.8; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/view/system/search/index.component.scss: -------------------------------------------------------------------------------- 1 | .add-btn { 2 | margin-bottom: 20px; 3 | margin-right: 20px; 4 | } 5 | 6 | .icon { 7 | width: 30px; 8 | height: 30px; 9 | margin-right: 10px; 10 | object-fit: cover; 11 | } 12 | 13 | ::ng-deep #file { 14 | vertical-align: middle; 15 | margin-left: 10px; 16 | .anticon { 17 | font-size: 20px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/create-web/index.component.scss: -------------------------------------------------------------------------------- 1 | .row { 2 | display: flex; 3 | justify-content: space-around; 4 | } 5 | 6 | .breadcrumb1 { 7 | margin-bottom: 10px; 8 | padding-left: 5px; 9 | font-weight: bold; 10 | .arrow { 11 | margin: 0 8px 0 5px; 12 | } 13 | } 14 | 15 | @media (max-width: 580px) { 16 | ::ng-deep .ant-modal { 17 | top: 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/view/system/bookmark-export/index.component.scss: -------------------------------------------------------------------------------- 1 | .book-wrapper { 2 | text-align: center; 3 | .logo, 4 | p { 5 | cursor: pointer; 6 | } 7 | } 8 | #error-msg { 9 | white-space: pre-line; 10 | background-color: #f2f2f2; 11 | border-radius: 8px; 12 | padding: 0 12px; 13 | } 14 | .err { 15 | opacity: 0; 16 | &.noopacity { 17 | opacity: 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/view/system/index.component.scss: -------------------------------------------------------------------------------- 1 | .system-layout { 2 | min-height: 100vh; 3 | .content { 4 | padding: 30px; 5 | background-color: #fff; 6 | } 7 | .sidebar { 8 | z-index: 3; 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | bottom: 0; 13 | background-color: #fff; 14 | } 15 | .inner-layout { 16 | margin-left: 170px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: 'class', 4 | content: ['./data/**/*.{js,ts,tsx,json}', './src/**/*.{js,ts,tsx,html}'], 5 | theme: { 6 | extend: { 7 | colors: { 8 | background: 'var(--background)', 9 | foreground: 'var(--foreground)', 10 | }, 11 | }, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /src/components/tag-list/index.component.html: -------------------------------------------------------------------------------- 1 |
2 |
7 | {{ tagMap[item.id] && tagMap[item.id].name }} 12 |
13 |
14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "启动发现导航", 7 | "type": "node", 8 | "request": "launch", 9 | "cwd": "${workspaceFolder}", 10 | "runtimeExecutable": "npm", 11 | "runtimeArgs": ["run", "start"], 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/components/calendar/index.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ date }} 4 |
5 |
6 |
{{ day }}
7 |
8 | 第{{ dayOfYear }}天 9 | {{ week }} 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/view/system/bookmark/index.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 | 15 |
16 | -------------------------------------------------------------------------------- /src/components/upload/index.component.html: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /src/components/off-work/index.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ isRest ? component['restTitle'] : component['workTitle'] }} 4 |
5 |
{{ countdownStr }}
6 | 11 | 12 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /src/view/system/component/types.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { ComponentType } from 'src/types' 6 | import { $t } from 'src/locale' 7 | 8 | export const componentTitleMap: Record = { 9 | [ComponentType.Calendar]: $t('_calendar'), 10 | [ComponentType.OffWork]: $t('_offWork'), 11 | [ComponentType.Runtime]: $t('_runtime'), 12 | [ComponentType.Image]: $t('_image'), 13 | } 14 | -------------------------------------------------------------------------------- /src/components/toolbar-title/index.component.scss: -------------------------------------------------------------------------------- 1 | .title { 2 | position: relative; 3 | font-size: 16px; 4 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); 5 | padding: 10px 0; 6 | padding-left: 5px; 7 | color: #3f51b5; 8 | font-weight: 500; 9 | display: flex; 10 | margin-bottom: 15px; 11 | user-select: none; 12 | margin-top: 10px; 13 | 14 | .add-icon { 15 | position: absolute; 16 | top: 50%; 17 | right: 20px; 18 | transform: translateY(-50%); 19 | cursor: pointer; 20 | color: #666; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/view/system/info/index.component.html: -------------------------------------------------------------------------------- 1 |
2 |

TOKEN: {{ token }}

3 |

{{ $t('_devBranch') }}: {{ config.branch }}

4 |

{{ $t('_prevDevTime') }}: {{ date }}

5 |

{{ $t('_curVer') }}:

6 |

7 | {{ $t('_newVer') }}: 8 | 9 | 10 | 11 |

12 |
13 | -------------------------------------------------------------------------------- /src/components/image/index.component.scss: -------------------------------------------------------------------------------- 1 | .cimage { 2 | position: relative; 3 | width: 170px; 4 | height: var(--componentHeight); 5 | max-width: 100%; 6 | max-height: 100%; 7 | border-radius: 12px; 8 | overflow: hidden; 9 | color: #fff; 10 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); 11 | background-size: cover; 12 | .text { 13 | position: absolute; 14 | bottom: 5px; 15 | left: 50%; 16 | transform: translateX(-50%); 17 | font-size: 12px; 18 | width: 100%; 19 | text-align: center; 20 | font-weight: 500; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/pipe/safeHtml.pipe.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Pipe, PipeTransform } from '@angular/core' 6 | import { DomSanitizer } from '@angular/platform-browser' 7 | 8 | @Pipe({ 9 | name: 'safeHtml', 10 | }) 11 | export class SafeHtmlPipe implements PipeTransform { 12 | constructor(private sanitized: DomSanitizer) {} 13 | 14 | transform(value: string): any { 15 | return this.sanitized.bypassSecurityTrustHtml(value) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/no-data/no-data.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | 4 | import { Component, ChangeDetectionStrategy } from '@angular/core' 5 | import { $t } from 'src/locale' 6 | 7 | @Component({ 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | selector: 'app-no-data', 10 | templateUrl: './no-data.component.html', 11 | styleUrls: ['./no-data.component.scss'], 12 | }) 13 | export class NoDataComponent { 14 | $t = $t 15 | 16 | goBack = () => { 17 | history.go(-1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/view/system/web/index.component.scss: -------------------------------------------------------------------------------- 1 | .desc { 2 | width: 300px; 3 | white-space: pre-wrap; 4 | word-break: break-all; 5 | } 6 | #file2 { 7 | position: relative; 8 | cursor: pointer; 9 | .file-upload { 10 | position: absolute; 11 | top: 0; 12 | left: 0; 13 | width: 100%; 14 | height: 100%; 15 | opacity: 0; 16 | cursor: pointer; 17 | } 18 | } 19 | .tip2 { 20 | color: #f50; 21 | font-weight: bold; 22 | margin-top: 10px; 23 | } 24 | .admin { 25 | ::ng-deep .ant-table { 26 | max-height: 600px; 27 | overflow: auto; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: "Upstream Sync" 2 | 3 | on: 4 | schedule: 5 | - cron: "0 */4 * * *" # 每 4 小时运行一次 6 | workflow_dispatch: 7 | inputs: 8 | debug: 9 | description: "Fork Sync Test Mode" 10 | type: string 11 | default: "true" 12 | 13 | jobs: 14 | call-workflow-sync: 15 | uses: aliuq/workflows/.github/workflows/sync.yml@master 16 | with: 17 | ref: main 18 | target_sync_branch: main 19 | upstream_sync_branch: main 20 | upstream_sync_repo: xjh22222228/nav 21 | debug: ${{ inputs.debug }} 22 | secrets: 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /src/components/tag-list/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | import { Component, Input } from '@angular/core' 4 | import { IWebTag } from 'src/types' 5 | import { tagMap } from 'src/store' 6 | import { JumpService } from 'src/services/jump' 7 | 8 | @Component({ 9 | selector: 'tag-list', 10 | templateUrl: './index.component.html', 11 | styleUrls: ['./index.component.scss'], 12 | }) 13 | export class TagListComponent { 14 | @Input() data: IWebTag[] = [] 15 | 16 | tagMap = tagMap 17 | 18 | constructor(public jumpService: JumpService) {} 19 | } 20 | -------------------------------------------------------------------------------- /src/locale/index.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | import english from './english' 3 | import zh_CN from './zh_CN' 4 | import { STORAGE_KEY_MAP } from 'src/constants' 5 | import { settings } from 'src/store' 6 | 7 | const o = { 8 | en: english, 9 | cn: zh_CN, 10 | } 11 | 12 | export function getLocale(): string { 13 | return ( 14 | window.localStorage.getItem(STORAGE_KEY_MAP.language) || settings.language 15 | ) 16 | } 17 | 18 | const l = getLocale() 19 | 20 | export function $t(s: string): string { 21 | if (l === 'zh-CN') { 22 | return o.cn[s] 23 | } 24 | return o.en[s] ?? o.cn[s] 25 | } 26 | 27 | export default o 28 | -------------------------------------------------------------------------------- /src/app/alert-event.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // @ts-nocheck 3 | import { NzMessageService } from 'ng-zorro-antd/message' 4 | import { NzNotificationService } from 'ng-zorro-antd/notification' 5 | import event from 'src/utils/mitt' 6 | 7 | class Alert { 8 | constructor(message: NzMessageService, notification: NzNotificationService) { 9 | event.on('MESSAGE', (props: any) => { 10 | message[props.type](props.content) 11 | }) 12 | 13 | event.on('NOTIFICATION', (props: any) => { 14 | notification.create(props.type, props.title, props.content, props.config) 15 | }) 16 | } 17 | } 18 | 19 | export default Alert 20 | -------------------------------------------------------------------------------- /src/components/component-group/index.component.html: -------------------------------------------------------------------------------- 1 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /src/utils/util.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import navConfig from '../../nav.config.json' 6 | import { internal } from 'src/store' 7 | import { isLogin } from 'src/utils/user' 8 | 9 | // 是否自有部署 10 | export const isSelfDevelop = !!navConfig.address 11 | 12 | export function compilerTemplate(str: string) { 13 | return str 14 | .replaceAll( 15 | '${total}', 16 | String(isLogin ? internal.loginViewCount : internal.userViewCount) 17 | ) 18 | .replaceAll('${hostname}', window.location.hostname) 19 | .replaceAll('${year}', String(new Date().getFullYear())) 20 | } 21 | -------------------------------------------------------------------------------- /src/components/swiper/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, Input, ChangeDetectionStrategy } from '@angular/core' 6 | import { JumpService } from 'src/services/jump' 7 | 8 | @Component({ 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | selector: 'app-swiper', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class SwiperComponent { 15 | @Input() images: any[] = [] 16 | @Input() autoplay = true 17 | @Input() height = 300 18 | 19 | constructor(public jumpService: JumpService) {} 20 | } 21 | -------------------------------------------------------------------------------- /src/components/web-more-menu/index.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/logo/logo.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | import { Component, Input, ChangeDetectionStrategy } from '@angular/core' 5 | 6 | @Component({ 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | selector: 'app-logo', 9 | templateUrl: './logo.component.html', 10 | styleUrls: ['./logo.component.scss'], 11 | }) 12 | export class LogoComponent { 13 | @Input() src: string = '' 14 | @Input() name: string = '' 15 | @Input() size: number = 35 16 | @Input() radius: number = 3 17 | @Input() check: boolean = true 18 | 19 | constructor() {} 20 | 21 | ngOnInit() {} 22 | } 23 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/components/swiper/index.component.html: -------------------------------------------------------------------------------- 1 |
6 | 11 |
17 | 23 |
24 |
25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /src/components/web-more-menu/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, Input, Output, EventEmitter } from '@angular/core' 6 | import { INavProps } from 'src/types' 7 | 8 | @Component({ 9 | selector: 'app-web-more-menu', 10 | templateUrl: './index.component.html', 11 | styleUrls: ['./index.component.scss'], 12 | }) 13 | export class WebMoreMenuComponent { 14 | @Input() index = 0 15 | @Input() data: INavProps[] = [] 16 | @Input() page = 0 17 | @Output() onClick = new EventEmitter() 18 | 19 | ngOnInit() {} 20 | 21 | handleCilck(index: number) { 22 | this.onClick?.emit?.(index) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/icon-git/icon-git.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | import config from '../../../nav.config.json' 4 | import { Component, ChangeDetectionStrategy } from '@angular/core' 5 | import { settings } from 'src/store' 6 | 7 | @Component({ 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | selector: 'app-icon-git', 10 | templateUrl: './icon-git.component.html', 11 | styleUrls: ['./icon-git.component.scss'], 12 | }) 13 | export class IconGitComponent { 14 | gitRepoUrl: string = config.gitRepoUrl.includes('github.com/xjh22222228') 15 | ? 'https://github.com/xjh22222228/nav' 16 | : config.gitRepoUrl 17 | showGithub = settings.showGithub 18 | 19 | constructor() {} 20 | } 21 | -------------------------------------------------------------------------------- /src/components/logo/logo.component.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 |
27 | {{ name ? name[0] : '' }} 28 |
29 |
30 | -------------------------------------------------------------------------------- /src/components/toolbar-title/index.component.html: -------------------------------------------------------------------------------- 1 |
5 | 6 | 10 | 11 | 12 | 13 | {{ dataSource.title }} x {{ dataSource.nav.length }} 14 | 15 | 16 | 17 | 25 | 26 |
27 | -------------------------------------------------------------------------------- /src/components/login/login.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |

{{ $t('_inputTokenMsg') }}

10 | 18 |

19 | {{ $t('_getToken') 20 | }} 24 | {{ $t('_readDoc') }} 26 |

27 |
28 |
29 | -------------------------------------------------------------------------------- /src/components/image/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, Input } from '@angular/core' 6 | import { components } from 'src/store' 7 | import { ComponentType, IComponentProps } from 'src/types' 8 | 9 | @Component({ 10 | selector: 'app-image', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class ImageComponent { 15 | @Input() data!: IComponentProps 16 | 17 | constructor() {} 18 | 19 | get component(): any { 20 | const data = components.find( 21 | (item) => item.type === ComponentType.Image && item.id === this.data.id 22 | ) 23 | return data || {} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/view/app/default/app.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { CommonService } from 'src/services/common' 7 | 8 | @Component({ 9 | selector: 'app-home', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.scss'], 12 | }) 13 | export default class WebpComponent { 14 | open: boolean = false 15 | 16 | constructor(public commonService: CommonService) {} 17 | 18 | ngOnInit() {} 19 | 20 | handleCilckNav(index: number) { 21 | this.commonService.handleCilckTopNav(index) 22 | this.handleToggleOpen() 23 | } 24 | 25 | handleToggleOpen() { 26 | this.open = !this.open 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/calendar/index.component.scss: -------------------------------------------------------------------------------- 1 | .calendar { 2 | width: 130px; 3 | height: var(--componentHeight); 4 | max-width: 100%; 5 | max-height: 100%; 6 | border-radius: 12px; 7 | text-align: center; 8 | overflow: hidden; 9 | color: #fff; 10 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); 11 | display: flex; 12 | flex-direction: column; 13 | .ctop { 14 | font-size: 17px; 15 | height: 40px; 16 | line-height: 0; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | font-weight: 500; 21 | } 22 | .box { 23 | flex: 1; 24 | } 25 | .cday { 26 | font-weight: bold; 27 | font-size: 38px; 28 | line-height: 1; 29 | padding: 24px 0 5px 0; 30 | } 31 | .cdate { 32 | color: rgba(255, 255, 255, 0.7); 33 | font-size: 13px; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/logo/logo.component.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | position: relative; 3 | display: inline-block; 4 | vertical-align: middle; 5 | pointer-events: none; 6 | background-color: #eee; 7 | border-radius: 3px; 8 | object-fit: cover; 9 | transition: all 0.12s linear; 10 | &::after { 11 | content: '' attr(alt); 12 | z-index: 2; 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | width: 100%; 17 | height: 100%; 18 | background-color: #1890ff; 19 | color: #fff; 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: 18px; 24 | } 25 | } 26 | 27 | .circle { 28 | color: #fff; 29 | display: flex; 30 | justify-content: center; 31 | align-items: center; 32 | border-radius: 50%; 33 | background-color: #1890ff; 34 | font-size: 18px; 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /dist.zip 6 | /tmp 7 | /out-tsc 8 | /bazel-out 9 | 10 | # Node 11 | /node_modules 12 | npm-debug.log 13 | yarn-error.log 14 | 15 | # IDEs and editors 16 | .idea/ 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # Visual Studio Code 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # Miscellaneous 33 | /.angular/cache 34 | .sass-cache/ 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | testem.log 39 | /typings 40 | 41 | # System files 42 | .DS_Store 43 | Thumbs.db 44 | 45 | .angular 46 | 47 | # build output 48 | src/index.html 49 | nav.config.json 50 | _upload 51 | test.md 52 | data/collect.json -------------------------------------------------------------------------------- /src/services/jump.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Injectable } from '@angular/core' 6 | import { Router } from '@angular/router' 7 | 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | export class JumpService { 12 | constructor(private router: Router) {} 13 | 14 | goUrl(e: any, url: string | null | undefined) { 15 | e?.stopPropagation?.() 16 | e?.preventDefault?.() 17 | 18 | if (typeof url !== 'string' || !url) { 19 | return 20 | } 21 | 22 | if (url[0] === '@') { 23 | this.router.navigate([url.slice(1)]) 24 | return 25 | } 26 | 27 | const self = url[0] === '!' 28 | if (self) { 29 | window.open(url.slice(1), '_self') 30 | } else { 31 | window.open(url) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | import navConfig from '../../nav.config.json' 5 | 6 | export const DB_PATH = 'data/db.json' 7 | 8 | export const TAG_PATH = 'data/tag.json' 9 | 10 | export const SETTING_PATH = 'data/settings.json' 11 | 12 | export const SEARCH_PATH = 'data/search.json' 13 | 14 | export const COMPONENT_PATH = 'data/component.json' 15 | 16 | export const VERSION = navConfig.version 17 | 18 | export const STORAGE_KEY_MAP = { 19 | token: 'token', 20 | location: 'location', 21 | s_url: 's_url', 22 | isDark: 'isDark', 23 | website: 'WEBSITE_DB', 24 | engine: 'engine', 25 | language: 'language', 26 | total: 'total', 27 | authCode: 'AUTH_CODE', 28 | sideCollapsed: 'SIDE_COLLAPSED', 29 | fixbarOpen: 'FIXBAR_OPEN', 30 | } 31 | -------------------------------------------------------------------------------- /src/components/web-list/index.component.html: -------------------------------------------------------------------------------- 1 |
7 |
16 | 17 |

{{ item.__name__ || item.name }}

18 | {{ item.__desc__ || item.desc }} 19 |
20 | 27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build web 2 | on: 3 | push: 4 | branches: 5 | - dev 6 | - master 7 | - main 8 | - own 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | with: 16 | persist-credentials: false 17 | - name: Set Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 20.x 21 | - name: Install 22 | uses: borales/actions-yarn@v4 23 | with: 24 | cmd: install 25 | # dist/404.html: gh-pages history mode 26 | - name: Build 27 | run: | 28 | npm run build-gh-pages 29 | cp dist/index.html dist/404.html 30 | - name: Deploy 31 | uses: JamesIves/github-pages-deploy-action@v4 32 | with: 33 | token: ${{ secrets.TOKEN }} 34 | branch: gh-pages 35 | folder: dist 36 | -------------------------------------------------------------------------------- /src/components/runtime/drawer/index.component.html: -------------------------------------------------------------------------------- 1 | 9 |
10 | 11 | {{ $t('_runtimeTitle') }} 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 23 | 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /src/components/off-work/index.component.scss: -------------------------------------------------------------------------------- 1 | .offwork { 2 | pointer-events: none; 3 | position: relative; 4 | width: 170px; 5 | height: var(--componentHeight); 6 | max-width: 100%; 7 | max-height: 100%; 8 | border-radius: 12px; 9 | overflow: hidden; 10 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); 11 | display: flex; 12 | align-items: center; 13 | flex-direction: column; 14 | font-weight: bold; 15 | background-color: #fff; 16 | &.rest { 17 | .title { 18 | font-size: 22px; 19 | } 20 | } 21 | .title { 22 | margin-top: 20px; 23 | z-index: 2; 24 | position: relative; 25 | font-size: 14px; 26 | color: gray; 27 | text-align: center; 28 | } 29 | .img { 30 | position: absolute; 31 | left: 50%; 32 | transform: translateX(-50%); 33 | bottom: 0; 34 | width: 100%; 35 | } 36 | .coutdown { 37 | z-index: 2; 38 | position: relative; 39 | font-size: 24px; 40 | color: #666; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/view/system/bookmark-export/index.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | {{ $t('_exportIcons') }} 6 |

耗时 {{ seconds }} 秒

7 |

8 | {{ $t('_processing') }} {{ currentNumber }} / {{ countAll }} 9 |

10 |
11 |
12 |
13 | 19 |

20 | {{ $t('_clickExport') }} 21 |

22 |
23 |
24 | 25 |
26 |

{{ $t('_errorIcons') }}

27 |

28 | 
29 | -------------------------------------------------------------------------------- /nav.config.yaml: -------------------------------------------------------------------------------- 1 | # 发现导航 在未授权的情况下,您可以免费使用,必须公开可见,禁止商业用途。 2 | # LICENSE GPL-3.0 https://github.com/xjh22222228/nav/blob/main/LICENSE 3 | # 配置信息:https://github.com/xjh22222228/nav?tab=readme-ov-file#%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E 4 | # 网站所有内容都是可以在后台系统配置的,不懂的不要擅自修改源代码,出现异常请自行处理 5 | # Fork 用户通常只需要修改 gitRepoUrl ,其他默认处理 6 | # 问题反馈:xjh22222228@gmail.com ,非授权不接收功能建议 7 | # 联系授权:https://official.nav3.cn/pricing 8 | 9 | # 仓库地址 10 | gitRepoUrl: https://github.com/aliuq/nav 11 | 12 | # Gitee | GitHub 13 | provider: GitHub 14 | 15 | # Fork 部署分支 16 | branch: main 17 | 18 | # 只有GitHub pages用户需要设置为true, 其他建议设置为 false 19 | hashMode: false 20 | 21 | # 一旦填写认为你是自有部署,Fork 不要填写 22 | address: '' 23 | 24 | # 自有部署后台登录密码 25 | password: admin 26 | 27 | # 自有部署启动端口 28 | port: 7777 29 | 30 | # 收录通知邮箱 31 | email: '' 32 | 33 | # 自有部署邮件通知系统 34 | mailConfig: 35 | host: smtp.mxhichina.com 36 | port: 465 37 | secure: true 38 | auth: 39 | user: '' 40 | pass: '' 41 | title: 网站收录通知 42 | message: 有用户提交网站收录啦 43 | -------------------------------------------------------------------------------- /src/components/runtime/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, Input } from '@angular/core' 6 | import { components, settings } from 'src/store' 7 | import { ComponentType, IComponentProps } from 'src/types' 8 | 9 | @Component({ 10 | selector: 'app-runtime', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class RuntimeComponent { 15 | @Input() data!: IComponentProps 16 | 17 | runDays = 0 18 | 19 | constructor() { 20 | let now = Date.now() - settings.runtime 21 | now = now < 0 ? 0 : now 22 | this.runDays = Math.floor(now / (1000 * 60 * 60 * 24)) 23 | } 24 | 25 | get component(): any { 26 | const data = components.find( 27 | (item) => item.type === ComponentType.Runtime && item.id === this.data.id 28 | ) 29 | return data || {} 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/runtime/index.component.scss: -------------------------------------------------------------------------------- 1 | .runtime { 2 | pointer-events: none; 3 | position: relative; 4 | width: 300px; 5 | height: var(--componentHeight); 6 | max-width: 100%; 7 | max-height: 100%; 8 | border-radius: 12px; 9 | overflow: hidden; 10 | color: #fff; 11 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); 12 | display: flex; 13 | justify-content: center; 14 | padding: 30px 30px; 15 | flex-direction: column; 16 | background: linear-gradient(135deg, #8bc6ec 0%, #9599e2 100%); 17 | font-weight: bold; 18 | 19 | .title { 20 | z-index: 2; 21 | position: relative; 22 | font-size: 18px; 23 | color: #f9f6f6; 24 | } 25 | .days { 26 | z-index: 2; 27 | position: relative; 28 | margin-top: 10px; 29 | font-size: 48px; 30 | line-height: 1; 31 | color: #d67272; 32 | } 33 | .unit { 34 | font-size: 15px; 35 | } 36 | .img { 37 | position: absolute; 38 | bottom: 30px; 39 | right: 30px; 40 | width: 70px; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/view/light/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { randomBgImg } from 'src/utils' 7 | import { CommonService } from 'src/services/common' 8 | import { JumpService } from 'src/services/jump' 9 | 10 | @Component({ 11 | selector: 'app-light', 12 | templateUrl: './index.component.html', 13 | styleUrls: ['./index.component.scss'], 14 | }) 15 | export default class LightComponent { 16 | constructor( 17 | public commonService: CommonService, 18 | public jumpService: JumpService 19 | ) {} 20 | 21 | ngOnInit() { 22 | randomBgImg() 23 | } 24 | 25 | ngOnDestroy() { 26 | this.commonService.overIndex = Number.MAX_SAFE_INTEGER 27 | } 28 | 29 | ngAfterViewInit() { 30 | if (this.commonService.settings.lightOverType === 'ellipsis') { 31 | this.commonService.getOverIndex('.top-nav .over-item') 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/view/sim/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | 4 | import { Component } from '@angular/core' 5 | import { isLogin } from 'src/utils/user' 6 | import { settings, internal } from 'src/store' 7 | import { CommonService } from 'src/services/common' 8 | 9 | @Component({ 10 | selector: 'app-sim', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export default class SimComponent { 15 | description: string = settings.simThemeDesc.replace( 16 | '${total}', 17 | String(isLogin ? internal.loginViewCount : internal.userViewCount) 18 | ) 19 | 20 | constructor(public commonService: CommonService) {} 21 | 22 | ngOnDestroy() { 23 | this.commonService.overIndex = Number.MAX_SAFE_INTEGER 24 | } 25 | 26 | ngAfterViewInit() { 27 | if (this.commonService.settings.simOverType === 'ellipsis') { 28 | this.commonService.getOverIndex('.top-nav .over-item') 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/view/system/info/index.component.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 3 | // Copyright @ 2018-present xiejiahe. All rights reserved. 4 | // See https://github.com/xjh22222228/nav 5 | 6 | import { Component } from '@angular/core' 7 | import { $t } from 'src/locale' 8 | import { getToken } from 'src/utils/user' 9 | import { VERSION } from 'src/constants' 10 | import { isSelfDevelop } from 'src/utils/util' 11 | import config from '../../../../nav.config.json' 12 | 13 | @Component({ 14 | selector: 'system-info', 15 | templateUrl: './index.component.html', 16 | styleUrls: ['./index.component.scss'], 17 | }) 18 | export default class SystemInfoComponent { 19 | $t = $t 20 | isSelfDevelop = isSelfDevelop 21 | token = getToken() 22 | config = config 23 | date = document.getElementById('META-NAV')?.dataset?.['date'] || $t('_unknow') 24 | currentVersionSrc = `https://img.shields.io/badge/current-v${VERSION}-red.svg?longCache=true&style=flat-square` 25 | 26 | constructor() {} 27 | 28 | ngOnInit() {} 29 | } 30 | -------------------------------------------------------------------------------- /src/view/system/setting/index.component.scss: -------------------------------------------------------------------------------- 1 | #file { 2 | cursor: pointer; 3 | input { 4 | display: none; 5 | } 6 | .logo { 7 | width: 50px; 8 | height: 50px; 9 | } 10 | } 11 | .preview { 12 | width: 300px; 13 | } 14 | .save-tip { 15 | margin-top: 10px; 16 | color: #f50; 17 | font-weight: bold; 18 | } 19 | .divider { 20 | height: 1px; 21 | width: 100%; 22 | margin: 20px 0; 23 | background-color: #eee; 24 | } 25 | .title { 26 | margin-bottom: 20px; 27 | } 28 | .sim-ban { 29 | img { 30 | display: inline-block; 31 | width: 100%; 32 | object-fit: cover; 33 | } 34 | ::ng-deep .anticon { 35 | font-size: 30px; 36 | } 37 | } 38 | .logo-input { 39 | display: block !important; 40 | width: 518px; 41 | margin-top: 5px; 42 | } 43 | .bottom-bar { 44 | z-index: 3; 45 | position: sticky; 46 | bottom: 0; 47 | left: 0; 48 | padding: 10px 0 10px 200px; 49 | background-color: #fff; 50 | box-shadow: 0 0 10px #ccc; 51 | margin-top: 80px; 52 | border-radius: 8px; 53 | display: flex; 54 | align-items: center; 55 | } 56 | -------------------------------------------------------------------------------- /src/components/component-group/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { settings, components } from 'src/store' 7 | import { ComponentType, IComponentProps } from 'src/types' 8 | 9 | @Component({ 10 | selector: 'component-group', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class ComponentGroupComponent { 15 | ComponentType = ComponentType 16 | components: IComponentProps[] = [] 17 | 18 | constructor() { 19 | const c: IComponentProps[] = [] 20 | // 按照系统设置顺序排序显示 21 | components.forEach((item) => { 22 | const has = settings.components.find( 23 | (c) => c.type === item.type && c.id === item.id 24 | ) 25 | if (has) { 26 | c.push(has) 27 | } 28 | }) 29 | this.components = c 30 | } 31 | 32 | trackByItem(i: number, item: any) { 33 | return item.id 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/components/calendar/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, Input } from '@angular/core' 6 | import { getDateTime, getDayOfYear } from 'src/utils' 7 | import { components } from 'src/store' 8 | import { ComponentType, IComponentProps } from 'src/types' 9 | 10 | @Component({ 11 | selector: 'app-calendar', 12 | templateUrl: './index.component.html', 13 | styleUrls: ['./index.component.scss'], 14 | }) 15 | export class CalendarComponent { 16 | @Input() data!: IComponentProps 17 | 18 | date = '' 19 | day = 0 20 | week = '' 21 | dayOfYear = 0 22 | 23 | constructor() { 24 | const date = getDateTime() 25 | this.date = `${date.year}年${date.month}月` 26 | this.day = date.date 27 | this.week = date.dayText 28 | this.dayOfYear = getDayOfYear() 29 | } 30 | 31 | get component(): any { 32 | const data = components.find( 33 | (item) => item.type === ComponentType.Calendar && item.id === this.data.id 34 | ) 35 | return data || {} 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/view/super/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { CommonService } from 'src/services/common' 8 | import { JumpService } from 'src/services/jump' 9 | import event from 'src/utils/mitt' 10 | 11 | @Component({ 12 | selector: 'app-side', 13 | templateUrl: './index.component.html', 14 | styleUrls: ['./index.component.scss'], 15 | }) 16 | export default class SideComponent { 17 | $t = $t 18 | 19 | constructor( 20 | public commonService: CommonService, 21 | public jumpService: JumpService 22 | ) {} 23 | 24 | ngAfterViewInit() { 25 | if (this.commonService.settings.superOverType === 'ellipsis') { 26 | this.commonService.getOverIndex('.topnav .over-item') 27 | } 28 | } 29 | 30 | ngOnDestroy() { 31 | this.commonService.overIndex = Number.MAX_SAFE_INTEGER 32 | } 33 | 34 | openCreateWebModal() { 35 | event.emit('CREATE_WEB', { 36 | threeIndex: this.commonService.selectedIndex, 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "exclude": [ 5 | "**/*.component.html" 6 | ], 7 | "compilerOptions": { 8 | "noImplicitReturns": false, 9 | "allowSyntheticDefaultImports": true, 10 | "resolveJsonModule": true, 11 | "baseUrl": "./", 12 | "outDir": "./dist/out-tsc", 13 | "forceConsistentCasingInFileNames": true, 14 | "strict": true, 15 | "noImplicitOverride": true, 16 | "noPropertyAccessFromIndexSignature": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "sourceMap": true, 19 | "declaration": false, 20 | "downlevelIteration": true, 21 | "experimentalDecorators": true, 22 | "moduleResolution": "node", 23 | "importHelpers": true, 24 | "target": "ES2022", 25 | "module": "ES2022", 26 | "useDefineForClassFields": false, 27 | "lib": [ 28 | "ES2022", 29 | "dom" 30 | ] 31 | }, 32 | "angularCompilerOptions": { 33 | "enableI18nLegacyMessageIdFormat": false, 34 | "strictInjectionParameters": true, 35 | "strictInputAccessModifiers": true, 36 | "strictTemplates": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/toolbar-title/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { 6 | Component, 7 | OnInit, 8 | Input, 9 | Output, 10 | EventEmitter, 11 | ChangeDetectionStrategy, 12 | } from '@angular/core' 13 | import { INavThreeProp, INavProps } from 'src/types' 14 | import { isLogin } from 'src/utils/user' 15 | import { websiteList, settings } from 'src/store' 16 | import event from 'src/utils/mitt' 17 | 18 | @Component({ 19 | changeDetection: ChangeDetectionStrategy.OnPush, 20 | selector: 'app-toolbar-title', 21 | templateUrl: './index.component.html', 22 | styleUrls: ['./index.component.scss'], 23 | }) 24 | export class ToolbarTitleWebComponent implements OnInit { 25 | @Input() index: number = 0 26 | @Input() dataSource!: INavThreeProp 27 | @Output() onCollapse = new EventEmitter() 28 | 29 | isLogin = isLogin 30 | websiteList: INavProps[] = websiteList 31 | settings = settings 32 | 33 | constructor() {} 34 | 35 | ngOnInit() {} 36 | 37 | openCreateWebModal() { 38 | event.emit('CREATE_WEB', { 39 | threeIndex: this.index, 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/calendar/drawer/index.component.html: -------------------------------------------------------------------------------- 1 | 9 |
10 | 11 | {{ $t('_calendarTopColor') }} 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{ $t('_calendarBgColor') }} 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 |
27 | 30 | 33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /src/assets/img/china.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/runtime/drawer/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, EventEmitter, Output } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { FormBuilder, FormGroup } from '@angular/forms' 8 | 9 | @Component({ 10 | selector: 'runtime-drawer', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class RuntimeDrawerComponent { 15 | @Output() ok = new EventEmitter() 16 | 17 | $t = $t 18 | visible = false 19 | validateForm!: FormGroup 20 | index = 0 21 | 22 | constructor(private fb: FormBuilder) { 23 | this.validateForm = this.fb.group({ 24 | title: [''], 25 | }) 26 | } 27 | 28 | open(data: any, idx: number) { 29 | this.index = idx 30 | for (const k in data) { 31 | this.validateForm.get(k)!?.setValue(data[k]) 32 | } 33 | this.visible = true 34 | } 35 | 36 | handleClose() { 37 | this.visible = false 38 | } 39 | 40 | handleSubmit() { 41 | const values = this.validateForm.value 42 | this.ok.emit({ 43 | ...values, 44 | index: this.index, 45 | }) 46 | this.handleClose() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/view/system/vip-auth/index.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 9 |
10 | 19 |
20 |
21 | 22 |
23 |
24 | {{ $t('_bindDomain') }} 25 |
26 | 27 | 36 | 46 |
47 |
48 | -------------------------------------------------------------------------------- /src/components/fixbar/index.component.scss: -------------------------------------------------------------------------------- 1 | .fixbar { 2 | z-index: 9; 3 | position: fixed; 4 | bottom: 60px; 5 | right: 15px; 6 | user-select: none; 7 | &.openFixbar { 8 | .common-show { 9 | visibility: visible !important; 10 | opacity: 1 !important; 11 | } 12 | } 13 | 14 | .wrapper { 15 | width: 40px; 16 | height: 40px; 17 | margin-top: 10px; 18 | transition: 0.1s linear; 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | cursor: pointer; 23 | border-radius: 50%; 24 | background-color: #fff; 25 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); 26 | &:hover { 27 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.15); 28 | transform: scale(1.2); 29 | } 30 | &.common-show { 31 | visibility: hidden; 32 | opacity: 0; 33 | } 34 | } 35 | 36 | img { 37 | width: 25px; 38 | height: 25px; 39 | } 40 | 41 | i { 42 | transition: 0.1s linear; 43 | display: inline-block; 44 | font-size: 20px; 45 | color: #999; 46 | } 47 | 48 | .arrow { 49 | transform: rotate(180deg); 50 | cursor: pointer; 51 | font-weight: bold; 52 | } 53 | 54 | .collapse-icon { 55 | transform: rotate(-270deg); 56 | &.active { 57 | transform: rotate(-360deg); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/web-list/index.component.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 768px) { 2 | .web-list { 3 | &.overflowScroll { 4 | padding: 0; 5 | flex-wrap: nowrap; 6 | overflow: hidden; 7 | overflow-x: auto; 8 | } 9 | gap: 10px !important; 10 | --iconWidth: 50px; 11 | } 12 | } 13 | 14 | .web-list { 15 | margin-top: 15px; 16 | margin-bottom: 15px; 17 | display: flex; 18 | padding: 0 10px; 19 | flex-wrap: wrap; 20 | gap: 25px; 21 | &.large { 22 | .logo { 23 | ::ng-deep { 24 | .common-icon { 25 | width: var(--iconWidth, 70px) !important; 26 | height: var(--iconWidth, 70px) !important; 27 | border-radius: 10px !important; 28 | font-size: 30px; 29 | &::after { 30 | font-size: 30px; 31 | } 32 | } 33 | } 34 | } 35 | .name { 36 | width: 70px; 37 | } 38 | } 39 | 40 | .wrapper { 41 | display: flex; 42 | text-align: center; 43 | cursor: pointer; 44 | align-items: center; 45 | flex-direction: column; 46 | transition: transform 0.1s ease-in; 47 | &:hover { 48 | transform: translateY(-2px); 49 | } 50 | } 51 | 52 | .name { 53 | width: 60px; 54 | margin-top: 3px; 55 | margin-top: 5px; 56 | color: #666; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/calendar/drawer/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, EventEmitter, Output } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { FormBuilder, FormGroup } from '@angular/forms' 8 | 9 | @Component({ 10 | selector: 'calendar-drawer', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class CalendarDrawerComponent { 15 | @Output() ok = new EventEmitter() 16 | 17 | $t = $t 18 | visible = false 19 | validateForm!: FormGroup 20 | index = 0 21 | 22 | constructor(private fb: FormBuilder) { 23 | this.validateForm = this.fb.group({ 24 | topColor: [''], 25 | bgColor: [''], 26 | }) 27 | } 28 | 29 | open(data: any, idx: number) { 30 | this.index = idx 31 | for (const k in data) { 32 | this.validateForm.get(k)!?.setValue(data[k]) 33 | } 34 | this.visible = true 35 | } 36 | 37 | handleClose() { 38 | this.visible = false 39 | } 40 | 41 | handleSubmit() { 42 | const values = this.validateForm.value 43 | this.ok.emit({ 44 | ...values, 45 | index: this.index, 46 | }) 47 | this.handleClose() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/utils/user.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | import localforage from 'localforage' 5 | import { STORAGE_KEY_MAP } from 'src/constants' 6 | 7 | export function getToken() { 8 | return window.localStorage.getItem(STORAGE_KEY_MAP.token) || '' 9 | } 10 | 11 | export function getAuthCode() { 12 | return window.localStorage.getItem(STORAGE_KEY_MAP.authCode) || '' 13 | } 14 | 15 | export function removeAuthCode() { 16 | return window.localStorage.removeItem(STORAGE_KEY_MAP.authCode) 17 | } 18 | 19 | export function setAuthCode(c: string) { 20 | return window.localStorage.setItem(STORAGE_KEY_MAP.authCode, c.trim()) 21 | } 22 | 23 | export function setToken(token: string) { 24 | return window.localStorage.setItem(STORAGE_KEY_MAP.token, token) 25 | } 26 | 27 | export function removeToken() { 28 | return window.localStorage.removeItem(STORAGE_KEY_MAP.token) 29 | } 30 | 31 | export function removeWebsite() { 32 | return localforage.removeItem(STORAGE_KEY_MAP.website) 33 | } 34 | 35 | export function userLogout() { 36 | const code = getAuthCode() 37 | localforage.clear() 38 | window.localStorage.clear() 39 | window.sessionStorage.clear() 40 | setAuthCode(code) 41 | } 42 | 43 | export const isLogin: boolean = !!getToken() 44 | -------------------------------------------------------------------------------- /src/components/off-work/drawer/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, EventEmitter, Output } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { FormBuilder, FormGroup } from '@angular/forms' 8 | 9 | @Component({ 10 | selector: 'offwork-drawer', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class OffWorkDrawerComponent { 15 | @Output() ok = new EventEmitter() 16 | 17 | $t = $t 18 | visible = false 19 | validateForm!: FormGroup 20 | index = 0 21 | 22 | constructor(private fb: FormBuilder) { 23 | this.validateForm = this.fb.group({ 24 | workTitle: [''], 25 | restTitle: [''], 26 | date: [null], 27 | }) 28 | } 29 | 30 | open(data: any, idx: number) { 31 | this.index = idx 32 | for (const k in data) { 33 | this.validateForm.get(k)!?.setValue(data[k]) 34 | } 35 | this.visible = true 36 | } 37 | 38 | handleClose() { 39 | this.visible = false 40 | } 41 | 42 | handleSubmit() { 43 | const values = this.validateForm.value 44 | this.ok.emit({ 45 | ...values, 46 | date: new Date(values.date).getTime(), 47 | index: this.index, 48 | }) 49 | this.handleClose() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/image/drawer/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, EventEmitter, Output } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { FormBuilder, FormGroup } from '@angular/forms' 8 | 9 | @Component({ 10 | selector: 'image-drawer', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class ImageDrawerComponent { 15 | @Output() ok = new EventEmitter() 16 | 17 | $t = $t 18 | visible = false 19 | validateForm!: FormGroup 20 | index = 0 21 | 22 | constructor(private fb: FormBuilder) { 23 | this.validateForm = this.fb.group({ 24 | url: [''], 25 | text: [''], 26 | }) 27 | } 28 | 29 | open(data: any, idx: number) { 30 | this.index = idx 31 | for (const k in data) { 32 | this.validateForm.get(k)!?.setValue(data[k]) 33 | } 34 | this.visible = true 35 | } 36 | 37 | onUploadImage(data: any) { 38 | this.validateForm.get('url')!.setValue(data.cdn) 39 | } 40 | 41 | handleClose() { 42 | this.visible = false 43 | } 44 | 45 | handleSubmit() { 46 | const values = this.validateForm.value 47 | this.ok.emit({ 48 | ...values, 49 | index: this.index, 50 | }) 51 | this.handleClose() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/image/drawer/index.component.html: -------------------------------------------------------------------------------- 1 | 9 |
10 | 11 | {{ $t('_image') }} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {{ $t('_text') }} 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 |
33 | 36 | 39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /src/components/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, Input, ChangeDetectionStrategy } from '@angular/core' 6 | import { settings } from 'src/store' 7 | import { compilerTemplate } from 'src/utils/util' 8 | import event from 'src/utils/mitt' 9 | 10 | @Component({ 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | selector: 'app-footer', 13 | templateUrl: './footer.component.html', 14 | styleUrls: ['./footer.component.scss'], 15 | }) 16 | export class FooterComponent { 17 | @Input() className: string = '' 18 | @Input() content: string = '' 19 | 20 | footerContent: string = '' 21 | 22 | constructor() {} 23 | 24 | ngOnInit() { 25 | this.footerContent = compilerTemplate( 26 | this.content || settings.footerContent 27 | ) 28 | } 29 | 30 | ngOnDestroy() { 31 | const applyWebEls = document.querySelectorAll('#app-footer .applyweb') 32 | applyWebEls.forEach((el) => { 33 | el.removeEventListener('click', this.handleApplyWeb) 34 | }) 35 | } 36 | 37 | handleApplyWeb() { 38 | event.emit('CREATE_WEB') 39 | } 40 | 41 | ngAfterViewInit() { 42 | const applyWebEls = document.querySelectorAll('#app-footer .applyweb') 43 | applyWebEls.forEach((el) => { 44 | el.addEventListener('click', this.handleApplyWeb) 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/assets/fonts/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id 2267418 */ 3 | src: url('iconfont.woff2?t=1723861505767') format('woff2'), 4 | url('iconfont.woff?t=1723861505767') format('woff'), 5 | url('iconfont.ttf?t=1723861505767') format('truetype'); 6 | } 7 | 8 | .iconfont { 9 | font-family: "iconfont" !important; 10 | font-size: 16px; 11 | font-style: normal; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | .iconxiaolian:before { 17 | content: "\e64b"; 18 | } 19 | 20 | .iconxiaolian-02:before { 21 | content: "\e631"; 22 | } 23 | 24 | .icongengduo:before { 25 | content: "\e641"; 26 | } 27 | 28 | .iconweibiaoti14:before { 29 | content: "\e620"; 30 | } 31 | 32 | .iconcopy:before { 33 | content: "\e617"; 34 | } 35 | 36 | .iconfenxiang:before { 37 | content: "\e606"; 38 | } 39 | 40 | .iconunlock:before { 41 | content: "\e63d"; 42 | } 43 | 44 | .iconwinfo-icon-tongbu:before { 45 | content: "\e632"; 46 | } 47 | 48 | .iconchuangjian:before { 49 | content: "\e635"; 50 | } 51 | 52 | .iconweibiaoti25:before { 53 | content: "\e62b"; 54 | } 55 | 56 | .iconjiantouarrow483:before { 57 | content: "\e695"; 58 | } 59 | 60 | .iconsousuo:before { 61 | content: "\e6b9"; 62 | } 63 | 64 | .icondark:before { 65 | content: "\e666"; 66 | } 67 | 68 | .iconA:before { 69 | content: "\e6e5"; 70 | } 71 | 72 | .iconbi:before { 73 | content: "\e742"; 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | 4 | import dbJson from '../../data/db.json' 5 | import searchJson from '../../data/search.json' 6 | import settingsJson from '../../data/settings.json' 7 | import tagJson from '../../data/tag.json' 8 | import internalJson from '../../data/internal.json' 9 | import componentJson from '../../data/component.json' 10 | import { 11 | ISettings, 12 | ISearchEngineProps, 13 | ITagProp, 14 | internalProps, 15 | ITagPropValues, 16 | INavProps, 17 | IComponentProps, 18 | } from 'src/types' 19 | import { isSelfDevelop } from 'src/utils/util' 20 | 21 | export let settings: ISettings = settingsJson as ISettings 22 | 23 | let _tagMap: Record = {} 24 | 25 | export let searchEngineList: ISearchEngineProps[] = isSelfDevelop 26 | ? [] 27 | : searchJson 28 | 29 | export let tagList: Array = isSelfDevelop ? [] : tagJson 30 | 31 | export function getTagMap() { 32 | tagList.forEach((item) => { 33 | if (item.id) { 34 | _tagMap[item.id] = { 35 | ...item, 36 | } 37 | } 38 | }) 39 | return _tagMap 40 | } 41 | getTagMap() 42 | 43 | export let tagMap: ITagProp = _tagMap 44 | 45 | export let internal: internalProps = internalJson 46 | 47 | export let websiteList: INavProps[] = isSelfDevelop 48 | ? [] 49 | : (dbJson as INavProps[]) 50 | 51 | export let components: IComponentProps[] = isSelfDevelop ? [] : componentJson 52 | -------------------------------------------------------------------------------- /scripts/build.mjs: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejia.he. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import fs from 'fs' 6 | import path from 'path' 7 | import dayjs from 'dayjs' 8 | import utc from 'dayjs/plugin/utc.js' 9 | import timezone from 'dayjs/plugin/timezone.js' 10 | import { writeSEO, writeTemplate, spiderWeb } from './util.mjs' 11 | 12 | dayjs.extend(utc) 13 | dayjs.extend(timezone) 14 | dayjs.tz.setDefault('Asia/Shanghai') 15 | 16 | const dbPath = path.join('.', 'data', 'db.json') 17 | const setPath = path.join('.', 'data', 'settings.json') 18 | 19 | const db = JSON.parse(fs.readFileSync(dbPath).toString()) 20 | const settings = JSON.parse(fs.readFileSync(setPath).toString()) 21 | 22 | const seoTemplate = writeSEO(db, { settings }) 23 | const htmlPath = path.join('.', 'src', 'main.html') 24 | const writePath = path.join('.', 'src', 'index.html') 25 | const html = writeTemplate({ 26 | html: fs.readFileSync(htmlPath).toString(), 27 | settings, 28 | seoTemplate, 29 | }) 30 | fs.writeFileSync(writePath, html) 31 | 32 | let errorUrlCount = 0 33 | 34 | process.on('exit', () => { 35 | settings.errorUrlCount = errorUrlCount 36 | fs.writeFileSync(setPath, JSON.stringify(settings), { encoding: 'utf-8' }) 37 | fs.writeFileSync(dbPath, JSON.stringify(db), { encoding: 'utf-8' }) 38 | console.log('All success!') 39 | }) 40 | 41 | const { errorUrlCount: count } = await spiderWeb(db, settings) 42 | errorUrlCount = count 43 | -------------------------------------------------------------------------------- /src/components/off-work/drawer/index.component.html: -------------------------------------------------------------------------------- 1 | 9 |
10 | 11 | {{ $t('_workTitle') }} 12 | 13 | 14 | 15 | 16 | 17 | {{ $t('_restTitle') }} 18 | 19 | 20 | 21 | 22 | 23 | {{ $t('_restTitle') }} 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
32 | 35 | 38 |
39 |
40 |
41 | -------------------------------------------------------------------------------- /src/view/system/index.component.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 3 | // Copyright @ 2018-present xiejiahe. All rights reserved. 4 | // See https://github.com/xjh22222228/nav 5 | 6 | import { Component } from '@angular/core' 7 | import { $t } from 'src/locale' 8 | import { isLogin, userLogout, getAuthCode } from 'src/utils/user' 9 | import { Router } from '@angular/router' 10 | import { VERSION } from 'src/constants' 11 | import { isSelfDevelop } from 'src/utils/util' 12 | 13 | @Component({ 14 | selector: 'app-system', 15 | templateUrl: './index.component.html', 16 | styleUrls: ['./index.component.scss'], 17 | }) 18 | export default class SystemComponent { 19 | isSelfDevelop = isSelfDevelop 20 | $t = $t 21 | isLogin: boolean = isLogin 22 | showLoginModal: boolean = !isLogin 23 | currentMenu: string = '' 24 | date = document.getElementById('META-NAV')?.dataset?.['date'] || '' 25 | currentVersionSrc = `https://img.shields.io/badge/current-v${VERSION}-red.svg?longCache=true&style=flat-square` 26 | isAuthz = !!getAuthCode() 27 | 28 | constructor(private router: Router) { 29 | // 解决暗黑模式部分样式不正确问题,后台没有暗黑 30 | document.documentElement.classList.remove('dark-container', 'dark') 31 | } 32 | 33 | ngOnInit() { 34 | const u = window.location.href.split('/') 35 | this.currentMenu = u[u.length - 1] 36 | } 37 | 38 | goBack() { 39 | this.router.navigate(['/']) 40 | } 41 | 42 | goRoute(to: string, disabled? = false) { 43 | if (disabled) { 44 | return 45 | } 46 | this.router.navigate([to]) 47 | } 48 | 49 | logout() { 50 | userLogout() 51 | this.router.navigate(['/']) 52 | setTimeout(() => { 53 | location.reload() 54 | }, 26) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/assets/styles/nprogress.css: -------------------------------------------------------------------------------- 1 | /* NProgress Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | } 5 | 6 | #nprogress .bar { 7 | background: #29d; 8 | position: fixed; 9 | z-index: 1031; 10 | top: 0; 11 | left: 0; 12 | width: 100%; 13 | height: 2px; 14 | } 15 | 16 | /* Fancy blur effect */ 17 | #nprogress .peg { 18 | display: block; 19 | position: absolute; 20 | right: 0px; 21 | width: 100px; 22 | height: 100%; 23 | box-shadow: 0 0 10px #29d, 0 0 5px #29d; 24 | opacity: 1; 25 | 26 | -webkit-transform: rotate(3deg) translate(0px, -4px); 27 | -ms-transform: rotate(3deg) translate(0px, -4px); 28 | transform: rotate(3deg) translate(0px, -4px); 29 | } 30 | 31 | /* Remove these to get rid of the spinner */ 32 | #nprogress .spinner { 33 | display: block; 34 | position: fixed; 35 | z-index: 1031; 36 | top: 25px; 37 | right: 25px; 38 | } 39 | 40 | #nprogress .spinner-icon { 41 | width: 18px; 42 | height: 18px; 43 | box-sizing: border-box; 44 | border: solid 2px transparent; 45 | border-top-color: #29d; 46 | border-left-color: #29d; 47 | border-radius: 50%; 48 | animation: nprogress-spinner 400ms linear infinite; 49 | } 50 | 51 | .nprogress-custom-parent { 52 | overflow: hidden; 53 | position: relative; 54 | } 55 | 56 | .nprogress-custom-parent #nprogress .spinner, 57 | .nprogress-custom-parent #nprogress .bar { 58 | position: absolute; 59 | } 60 | 61 | @-webkit-keyframes nprogress-spinner { 62 | 0% { 63 | -webkit-transform: rotate(0deg); 64 | } 65 | 100% { 66 | -webkit-transform: rotate(360deg); 67 | } 68 | } 69 | @keyframes nprogress-spinner { 70 | 0% { 71 | transform: rotate(0deg); 72 | } 73 | 100% { 74 | transform: rotate(360deg); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/move-web/index.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 21 | 22 | 23 | 24 | 32 | 37 | 38 | 39 | 40 | 48 | 53 | 54 | 55 | 56 |
57 | 58 |
59 |
60 |
61 | -------------------------------------------------------------------------------- /src/view/system/component/index.component.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | {{ $t('_buildTip') }} 13 |
14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
{{ componentTitleMap[item.type] }}
26 | {{ $t('_moveUp') }} 27 | {{ $t('_moveDown') }} 28 | {{ $t('_edit') }} 29 | 42 |
43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/components/icon-git/icon-git.component.html: -------------------------------------------------------------------------------- 1 | 8 | 49 | 50 | -------------------------------------------------------------------------------- /src/assets/img/light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/view/system/tag/index.component.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | 19 |
20 | {{ $t('_buildTip') }} 21 |
22 | 23 | 24 | 25 | 26 | {{ $t('_tagName') }} 27 | {{ $t('_color') }} 28 | {{ $t('_createAt') }} 29 | {{ $t('_remark') }} 30 | {{ $t('_action') }} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | - {{ data.color }} 45 | 46 | {{ data.createdAt }} 47 | 48 |
{{ data.desc }}
49 | 55 | 56 | 57 | 66 | {{ $t('_del') }} 67 | 68 | 69 | 70 | 71 |
72 | -------------------------------------------------------------------------------- /src/view/side/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { INavProps } from 'src/types' 7 | import { isMobile } from 'src/utils' 8 | import { setWebsiteList } from 'src/utils/web' 9 | import { websiteList } from 'src/store' 10 | import { settings } from 'src/store' 11 | import { $t } from 'src/locale' 12 | import { CommonService } from 'src/services/common' 13 | import { STORAGE_KEY_MAP } from 'src/constants' 14 | import { isSelfDevelop } from 'src/utils/util' 15 | 16 | @Component({ 17 | selector: 'app-side', 18 | templateUrl: './index.component.html', 19 | styleUrls: ['./index.component.scss'], 20 | }) 21 | export default class SideComponent { 22 | $t = $t 23 | websiteList: INavProps[] = websiteList 24 | isCollapsed = isMobile() || settings.sideCollapsed 25 | 26 | constructor(public commonService: CommonService) { 27 | const localCollapsed = localStorage.getItem(STORAGE_KEY_MAP.sideCollapsed) 28 | if (localCollapsed) { 29 | this.isCollapsed = localCollapsed === 'true' 30 | } 31 | } 32 | 33 | get nzXXl(): number { 34 | const cardStyle = this.commonService.settings.sideCardStyle 35 | if (cardStyle === 'original' || cardStyle === 'example') { 36 | return 4 37 | } 38 | return 6 39 | } 40 | 41 | openMenu(item: any, index: number) { 42 | this.websiteList.forEach((data, idx) => { 43 | if (idx === index) { 44 | data.collapsed = !data.collapsed 45 | } else { 46 | data.collapsed = false 47 | } 48 | }) 49 | if (!isSelfDevelop) { 50 | setWebsiteList(this.websiteList) 51 | } 52 | } 53 | 54 | handleCollapsed() { 55 | this.isCollapsed = !this.isCollapsed 56 | localStorage.setItem( 57 | STORAGE_KEY_MAP.sideCollapsed, 58 | String(this.isCollapsed) 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/view/system/bookmark/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { NzNotificationService } from 'ng-zorro-antd/notification' 8 | import { NzMessageService } from 'ng-zorro-antd/message' 9 | import { setWebsiteList } from 'src/utils/web' 10 | import { parseBookmark } from 'src/utils/bookmark' 11 | import { INavProps } from 'src/types' 12 | import { websiteList } from 'src/store' 13 | 14 | @Component({ 15 | selector: 'system-bookmark', 16 | templateUrl: './index.component.html', 17 | styleUrls: ['./index.component.scss'], 18 | }) 19 | export default class SystemBookmarkComponent { 20 | $t = $t 21 | websiteList: INavProps[] = websiteList 22 | 23 | constructor( 24 | private message: NzMessageService, 25 | private notification: NzNotificationService 26 | ) {} 27 | 28 | ngOnInit() {} 29 | 30 | onBookChange(e: any) { 31 | const that = this 32 | const { files } = e.target 33 | if (files.length <= 0) return 34 | const file = files[0] 35 | const fileReader = new FileReader() 36 | fileReader.readAsText(file) 37 | fileReader.onload = function () { 38 | const html = this.result as string 39 | try { 40 | const result = parseBookmark(html) 41 | if (!Array.isArray(result)) { 42 | that.notification.error( 43 | $t('_errorBookTip'), 44 | `${result?.message ?? ''}` 45 | ) 46 | } else { 47 | that.message.success($t('_importSuccess')) 48 | that.websiteList = result 49 | setWebsiteList(that.websiteList) 50 | setTimeout(() => window.location.reload(), 2000) 51 | } 52 | } catch (error: any) { 53 | that.notification.error($t('_errorBookTip'), `${error.message}`) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/view/system/vip-auth/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { NzMessageService } from 'ng-zorro-antd/message' 8 | import { setAuthCode, getAuthCode, removeAuthCode } from 'src/utils/user' 9 | import { getUserInfo, updateUserInfo } from 'src/api' 10 | 11 | @Component({ 12 | selector: 'user-collect', 13 | templateUrl: './index.component.html', 14 | styleUrls: ['./index.component.scss'], 15 | }) 16 | export default class VipAuthComponent { 17 | $t = $t 18 | submitting: boolean = false 19 | isPermission = !!getAuthCode() 20 | authCode = '' 21 | url = '' 22 | 23 | constructor(private message: NzMessageService) {} 24 | 25 | ngOnInit() { 26 | this.getUserInfo() 27 | } 28 | 29 | async getUserInfo(params?: any) { 30 | this.submitting = true 31 | return getUserInfo(params) 32 | .then((res: any) => { 33 | if (typeof res.data?.data?.url === 'string') { 34 | this.isPermission = true 35 | this.url = res.data.data.url 36 | } 37 | return res 38 | }) 39 | .finally(() => { 40 | this.submitting = false 41 | }) 42 | } 43 | 44 | handleSubmitAuthCode() { 45 | if (this.submitting || !this.authCode) { 46 | return 47 | } 48 | 49 | this.getUserInfo({ code: this.authCode }).then(() => { 50 | setAuthCode(this.authCode) 51 | window.location.reload() 52 | }) 53 | } 54 | 55 | handleSave() { 56 | this.submitting = true 57 | updateUserInfo({ 58 | url: this.url, 59 | }) 60 | .then(() => { 61 | this.getUserInfo() 62 | this.message.success(this.$t('_saveSuccess')) 63 | }) 64 | .finally(() => { 65 | this.submitting = false 66 | }) 67 | } 68 | 69 | logoutAuthCode() { 70 | removeAuthCode() 71 | window.location.reload() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/search-engine/search-engine.component.scss: -------------------------------------------------------------------------------- 1 | $width: 500px; 2 | $smallWidth: 300px; 3 | 4 | @media (max-width: 768px) { 5 | .engine-main .item { 6 | width: 100% !important; 7 | margin-right: 0 !important; 8 | } 9 | } 10 | 11 | .search-engine { 12 | position: relative; 13 | display: flex; 14 | justify-content: center; 15 | padding: 10px 0; 16 | &.small { 17 | .input-wrapper { 18 | width: $smallWidth; 19 | } 20 | } 21 | 22 | .input-wrapper { 23 | position: relative; 24 | width: $width; 25 | background: #fff; 26 | border-radius: 5px; 27 | overflow: hidden; 28 | 29 | input { 30 | padding-left: 10px; 31 | padding-right: 10px; 32 | 33 | &:-webkit-autofill-selected { 34 | background-color: transparent !important; 35 | box-shadow: inset 0 0 0 500px transparent !important; 36 | } 37 | } 38 | 39 | .left-icon { 40 | position: relative; 41 | width: 20px; 42 | height: 20px; 43 | background-repeat: no-repeat; 44 | background-size: 20px 20px; 45 | cursor: pointer; 46 | } 47 | 48 | .search-icon { 49 | cursor: pointer; 50 | } 51 | } 52 | 53 | ::ng-deep .removeAddon .ant-input-group-addon { 54 | display: none !important; 55 | } 56 | } 57 | 58 | ::ng-deep.engine-main { 59 | border-radius: 5px; 60 | display: flex; 61 | flex-wrap: wrap; 62 | gap: 15px; 63 | width: $width; 64 | max-width: 90vw; 65 | .item { 66 | width: calc(33.33% - 10px); 67 | padding: 6px; 68 | display: flex; 69 | background: #f6f6f6; 70 | cursor: pointer; 71 | border-radius: 4px; 72 | transition: 0.1s linear; 73 | box-sizing: border-box; 74 | border: 1px solid transparent; 75 | &:hover { 76 | background-color: #eee; 77 | } 78 | .name2 { 79 | margin-left: 10px; 80 | font-size: 15px; 81 | align-self: center; 82 | } 83 | } 84 | } 85 | 86 | @media screen and (max-width: 768px) { 87 | .input-wrapper { 88 | width: 90% !important; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /data/settings.json: -------------------------------------------------------------------------------- 1 | {"favicon":"https://cdn1.bilishare.com/1/icon.png/240","homeUrl":"https://nav3.cn","language":"zh-CN","loading":"loading4","allowCollect":false,"email":"aliuq@bilishare.com","showGithub":false,"showLanguage":false,"showRate":true,"title":"我的导航","description":"发现导航 - 精选实用导航网站","keywords":"导航,前端资源,社区站点,设计师,实用工具,学习资源,运营,网络安全,node.js","theme":"Side","actionUrl":"","appTheme":"App","openSEO":false,"headerContent":"","footerContent":"","baiduStatisticsUrl":"https://hm.baidu.com/hm.js?4582be7af7e7c95ef75351e07c6c32ba","cnzzStatisticsUrl":"","showThemeToggle":true,"lightCardStyle":"standard","lightOverType":"overflow","simThemeImages":[{"src":"https://gcore.jsdelivr.net/gh/xjh22222228/public@gh-pages/nav/banner1.jpg","url":"","width":null,"height":null},{"src":"https://gcore.jsdelivr.net/gh/xjh22222228/public@gh-pages/nav/banner2.jpg","url":"","width":null,"height":null}],"simThemeDesc":"这里收录多达 ${total} 个优质网站, 助您工作、学习和生活","simCardStyle":"standard","simOverType":"overflow","simThemeHeight":0,"simThemeAutoplay":true,"simTitle":"","superCardStyle":"column","superOverType":"overflow","checkUrl":false,"superTitle":"","superImages":[],"lightImages":[],"sideTitle":"","sideCardStyle":"example","sideThemeHeight":0,"sideThemeAutoplay":true,"sideThemeImages":[{"src":"https://gcore.jsdelivr.net/gh/xjh22222228/public@gh-pages/nav/banner2.jpg","url":"","width":null,"height":null},{"src":"https://gcore.jsdelivr.net/gh/xjh22222228/public@gh-pages/nav/banner1.jpg","url":"","width":null,"height":null}],"shortcutTitle":"","shortcutThemeShowWeather":true,"shortcutThemeImages":[{"src":"https://gcore.jsdelivr.net/gh/xjh22222228/public@gh-pages/nav/background.jpg","url":"","width":null,"height":null}],"mirrorList":null,"spiderIcon":"NO","spiderDescription":"NO","spiderTitle":"NO","spiderQty":20,"loadingCode":"","errorUrlCount":0,"runtime":1727034600639,"openSearch":true,"lightDocTitle":"","lightFooterHTML":"","simDocTitle":"","simFooterHTML":"","superFooterHTML":"","superDocTitle":"","sideDocTitle":"","sideFooterHTML":"","sideCollapsed":false,"shortcutDocTitle":"","shortcutDockCount":6,"spiderTimeout":6,"appCardStyle":"retro","appDocTitle":"","gitHubCDN":"gcore.jsdelivr.net"} -------------------------------------------------------------------------------- /src/components/search-engine/search-engine.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | 4 | import { Component, Input } from '@angular/core' 5 | import { 6 | getDefaultSearchEngine, 7 | setDefaultSearchEngine, 8 | queryString, 9 | } from '../../utils' 10 | import { Router } from '@angular/router' 11 | import { searchEngineList } from 'src/store' 12 | import { ISearchEngineProps } from '../../types' 13 | import { SearchType } from './index' 14 | import { $t } from 'src/locale' 15 | 16 | @Component({ 17 | selector: 'app-search-engine', 18 | templateUrl: './search-engine.component.html', 19 | styleUrls: ['./search-engine.component.scss'], 20 | }) 21 | export class SearchEngineComponent { 22 | @Input() size: 'small' | 'default' | 'large' = 'default' 23 | 24 | $t = $t 25 | searchEngineList: ISearchEngineProps[] = searchEngineList 26 | currentEngine: ISearchEngineProps = getDefaultSearchEngine() 27 | SearchType = SearchType 28 | searchTypeValue = SearchType.All 29 | keyword = queryString().q 30 | 31 | constructor(private router: Router) {} 32 | 33 | get searchList() { 34 | return this.searchEngineList.filter((item) => !item.blocked) 35 | } 36 | 37 | inputFocus() { 38 | setTimeout(() => { 39 | document.getElementById('search-engine-input')?.focus?.() 40 | }, 100) 41 | } 42 | 43 | ngAfterViewInit() { 44 | this.inputFocus() 45 | } 46 | 47 | clickEngineItem(index: number) { 48 | document.body.click() 49 | this.currentEngine = this.searchList[index] 50 | this.inputFocus() 51 | setDefaultSearchEngine(this.currentEngine) 52 | } 53 | 54 | triggerSearch() { 55 | if (this.currentEngine.url) { 56 | window.open(this.currentEngine.url + this.keyword) 57 | return 58 | } 59 | 60 | const params = queryString() 61 | this.router.navigate([this.router.url.split('?')[0]], { 62 | queryParams: { 63 | ...params, 64 | q: this.keyword, 65 | type: this.searchTypeValue, 66 | }, 67 | }) 68 | } 69 | 70 | onKey(event: KeyboardEvent) { 71 | if (event.code === 'Enter') { 72 | this.triggerSearch() 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/login/login.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core' 6 | import { NzMessageService } from 'ng-zorro-antd/message' 7 | import { verifyToken, updateFileContent, createBranch } from 'src/api' 8 | import { setToken, removeToken, removeWebsite } from 'src/utils/user' 9 | import { $t } from 'src/locale' 10 | import { isSelfDevelop } from 'src/utils/util' 11 | 12 | @Component({ 13 | selector: 'app-login', 14 | templateUrl: './login.component.html', 15 | styleUrls: ['./login.component.scss'], 16 | }) 17 | export class LoginComponent implements OnInit { 18 | @Input() visible: boolean = false 19 | @Output() onCancel = new EventEmitter() 20 | 21 | $t = $t 22 | isSelfDevelop = isSelfDevelop 23 | token = '' 24 | submiting = false 25 | 26 | constructor(private message: NzMessageService) {} 27 | 28 | ngOnInit() {} 29 | 30 | ngAfterViewInit() { 31 | this.inputFocus() 32 | } 33 | 34 | hanldeCancel() { 35 | this.onCancel.emit() 36 | } 37 | 38 | inputFocus() { 39 | setTimeout(() => { 40 | document.getElementById('loginInput')?.focus?.() 41 | }, 300) 42 | } 43 | 44 | onKey(event: KeyboardEvent) { 45 | if (event.code === 'Enter') { 46 | this.login() 47 | } 48 | } 49 | 50 | login() { 51 | if (!this.token) { 52 | return this.message.error($t('_pleaseInputToken')) 53 | } 54 | const token = this.token.trim() 55 | 56 | this.submiting = true 57 | verifyToken(token) 58 | .then(() => { 59 | setToken(token) 60 | updateFileContent({ 61 | message: 'auth', 62 | path: '.navauth', 63 | content: 'OK', 64 | }) 65 | .then(() => { 66 | createBranch('image').finally(() => { 67 | this.message.success($t('_tokenVerSuc')) 68 | removeWebsite().finally(() => { 69 | window.location.reload() 70 | }) 71 | }) 72 | }) 73 | .catch(() => { 74 | removeToken() 75 | this.submiting = false 76 | }) 77 | }) 78 | .catch(() => { 79 | this.submiting = false 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/view/system/search/index.component.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | 19 |
20 | {{ $t('_buildTip') }} 21 |
22 | 23 | 24 | 25 | {{ $t('_engineName') }} 26 | {{ $t('_engineUrl') }} 27 | {{ $t('_icon') }} 28 | {{ $t('_desc') }} 29 | {{ $t('_isDisable') }} 30 | {{ $t('_action') }} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {{ $t('_moveUp') }} 59 | {{ $t('_moveDown') }} 60 | 70 | {{ $t('_del') }} 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/components/off-work/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, Input } from '@angular/core' 6 | import { components } from 'src/store' 7 | import { ComponentType, IComponentProps } from 'src/types' 8 | 9 | @Component({ 10 | selector: 'app-offwork', 11 | templateUrl: './index.component.html', 12 | styleUrls: ['./index.component.scss'], 13 | }) 14 | export class OffWorkComponent { 15 | @Input() data!: IComponentProps 16 | 17 | countdownStr = '' 18 | isRest = false 19 | timer: any 20 | component: Record = {} 21 | 22 | constructor() { 23 | document.addEventListener( 24 | 'visibilitychange', 25 | this.visibilitychange.bind(this) 26 | ) 27 | } 28 | 29 | ngOnInit() { 30 | this.init() 31 | } 32 | 33 | ngOnDestroy() { 34 | clearTimeout(this.timer) 35 | document.removeEventListener('visibilitychange', this.visibilitychange) 36 | } 37 | 38 | visibilitychange(e: any) { 39 | if (e.target.hidden) { 40 | clearTimeout(this.timer) 41 | } else { 42 | this.init() 43 | } 44 | } 45 | 46 | init() { 47 | const component = components.find( 48 | (item) => item.type === ComponentType.OffWork && item.id === this.data?.id 49 | ) 50 | if (component) { 51 | this.component = component 52 | const now = new Date() 53 | const date = new Date(component['date']) 54 | date.setFullYear(now.getFullYear()) 55 | date.setMonth(now.getMonth()) 56 | date.setDate(now.getDate()) 57 | const diffTime = (date.getTime() - now.getTime()) / 1000 58 | const hours = diffTime / (60 * 60) 59 | const decimal = Math.floor((hours % 1) * 10) / 10 60 | const minutes = Math.floor((diffTime / 60) % 60) 61 | const seconds = Math.floor(diffTime % 60) 62 | if (diffTime <= 0) { 63 | this.isRest = true 64 | return clearTimeout(this.timer) 65 | } else if (hours > 0) { 66 | this.countdownStr = `${Math.floor(hours) + decimal}小时` 67 | } else if (minutes > 0) { 68 | this.countdownStr = `${minutes}分钟` 69 | } else if (seconds >= 0) { 70 | this.countdownStr = `${seconds}秒` 71 | } 72 | this.isRest = false 73 | } 74 | this.timer = setTimeout(() => this.init(), 1000) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main.html: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/view/shortcut/index.component.html: -------------------------------------------------------------------------------- 1 |
6 |
7 |

12 | {{ settings.shortcutTitle || settings.title }} 13 |

14 | 15 |
{{ hours }}:{{ minutes }}:{{ seconds }}
16 |
17 | {{ month }}{{ $t('_shortMonth') }}{{ date }}{{ $t('_shortDay') }} 20 | {{ dayText }} 21 |
22 | 23 | 24 | 25 |
26 | 31 |
32 | 33 |
34 | 39 |
51 | 59 |
60 |
61 |
62 |
63 |
64 | 65 | 66 | 67 | 79 | -------------------------------------------------------------------------------- /src/components/upload/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, EventEmitter, Output } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { NzMessageService } from 'ng-zorro-antd/message' 8 | import { createFile, getCDN } from 'src/api' 9 | 10 | @Component({ 11 | selector: 'app-upload', 12 | templateUrl: './index.component.html', 13 | styleUrls: ['./index.component.scss'], 14 | }) 15 | export class UploadComponent { 16 | @Output() onChange = new EventEmitter() 17 | 18 | $t = $t 19 | uploading: boolean = false 20 | // @ts-ignore 21 | id = `f${Date.now()}${parseInt(Math.random() * 1000000)}` 22 | 23 | constructor(private message: NzMessageService) {} 24 | 25 | onChangeFile(e: any) { 26 | if (this.uploading) { 27 | return 28 | } 29 | 30 | const { files } = e.target 31 | if (files.length <= 0) return 32 | const file = files[0] 33 | 34 | if (!file.type.startsWith('image')) { 35 | return this.message.error($t('_notUpload')) 36 | } 37 | this.onUpload(file).finally(() => { 38 | e.target.value = '' 39 | }) 40 | } 41 | 42 | onUpload(file: File) { 43 | const that = this 44 | return new Promise((resolve, reject) => { 45 | const fileReader = new FileReader() 46 | fileReader.readAsDataURL(file) 47 | fileReader.onerror = reject 48 | fileReader.onload = function () { 49 | that.uploading = true 50 | const iconUrl = this.result as string 51 | const url = iconUrl.split(',')[1] 52 | // fileName 方便自动带上文件后缀 53 | const fileName = file.name.replace(/\s/gi, '') 54 | const path = `nav-${Date.now()}-${fileName}` 55 | 56 | createFile({ 57 | branch: 'image', 58 | message: 'create image', 59 | content: url, 60 | isEncode: false, 61 | path, 62 | }) 63 | .then((res) => { 64 | const params = { 65 | rawPath: path, 66 | cdn: res?.data?.imagePath || getCDN(path), 67 | } 68 | that.onChange.emit(params) 69 | that.message.success($t('_uploadSuccess')) 70 | resolve(params) 71 | }) 72 | .catch(reject) 73 | .finally(() => { 74 | that.uploading = false 75 | }) 76 | } 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nav", 3 | "version": "8.11.0", 4 | "author": "xiejiahe", 5 | "homepage": "https://github.com/xjh22222228/nav", 6 | "scripts": { 7 | "start": "npm run build-start && ng serve --port=7001", 8 | "build-start": "ts-node ./scripts/start.mjs", 9 | "setup": "npm run build-start && ts-node ./scripts/build.mjs", 10 | "build-gh-pages": "npm run setup && ng build --base-href ./ --index src/index.html", 11 | "build": "npm run setup && ng build --index src/index.html", 12 | "watch": "ng build --watch --configuration development", 13 | "update": "git pull && (git remote add upstream https://gitee.com/xiejiahe/nav.git || true) && git fetch upstream main && git merge upstream/main --allow-unrelated-histories --no-edit && git push", 14 | "pull": "git pull" 15 | }, 16 | "private": true, 17 | "dependencies": { 18 | "@angular/animations": "^18.2.4", 19 | "@angular/common": "^18.2.4", 20 | "@angular/compiler": "^18.2.4", 21 | "@angular/core": "^18.2.4", 22 | "@angular/forms": "^18.2.4", 23 | "@angular/platform-browser": "^18.2.4", 24 | "@angular/platform-browser-dynamic": "^18.2.4", 25 | "@angular/router": "^18.2.4", 26 | "@types/file-saver": "^2.0.7", 27 | "@types/qs": "^6.9.16", 28 | "axios": "^1.7.7", 29 | "clipboard": "^2.0.11", 30 | "dayjs": "^1.11.13", 31 | "express": "^4.21.0", 32 | "file-saver": "^2.0.5", 33 | "js-base64": "^3.7.7", 34 | "js-yaml": "^4.1.0", 35 | "localforage": "^1.10.0", 36 | "lz-string": "^1.5.0", 37 | "mitt": "^3.0.1", 38 | "ng-zorro-antd": "^18.1.1", 39 | "nodemailer": "^6.9.15", 40 | "nprogress": "^0.2.0", 41 | "postcss": "^8.4.47", 42 | "qs": "^6.13.0", 43 | "rough-notation": "^0.5.1", 44 | "rxjs": "~7.8.1", 45 | "tailwindcss": "^3.4.11", 46 | "tslib": "^2.7.0", 47 | "zone.js": "~0.15.0" 48 | }, 49 | "devDependencies": { 50 | "@angular-devkit/build-angular": "^18.2.4", 51 | "@angular/cli": "^18.2.4", 52 | "@angular/compiler-cli": "^18.2.4", 53 | "@types/jasmine": "~5.1.4", 54 | "@types/nprogress": "^0.2.3", 55 | "body-parser": "^1.20.3", 56 | "compression": "^1.7.4", 57 | "connect-history-api-fallback": "^2.0.0", 58 | "cors": "^2.8.5", 59 | "info-web": "^0.0.33", 60 | "jasmine-core": "~5.3.0", 61 | "karma": "~6.4.4", 62 | "karma-chrome-launcher": "~3.2.0", 63 | "karma-coverage": "~2.2.1", 64 | "karma-jasmine": "~5.1.0", 65 | "karma-jasmine-html-reporter": "~2.1.0", 66 | "pm2": "^5.4.2", 67 | "ts-node": "~10.9.2", 68 | "typescript": "~5.5.4" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/view/app/default/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 | 8 | 9 |
10 |
11 | 12 | 26 |
27 | 28 | 29 | 30 |
31 | 48 |
49 |

50 | {{ item.title }} x {{ item.nav.length }} 51 |

52 | 53 |
59 |
72 | 79 |
80 |
81 |
82 |
83 | 84 | 85 |
86 | -------------------------------------------------------------------------------- /src/view/system/collect/index.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 13 | 14 | 17 | 18 |
19 | 处理完成所有数据后需要点击一次保存方可生效 20 |
21 | 22 | 23 | 24 | {{ $t('_action') }} 25 | {{ $t('_icon') }} 26 | {{ $t('_webName') }} 27 | {{ $t('_associatedLabels') }} 28 | {{ $t('_webDesc') }} 29 | {{ $t('_webTag') }} 30 | {{ $t('_createAt') }} 31 | 32 | 33 | 34 | 35 | 36 | 39 | 52 | 53 | 54 | 55 | 56 | 57 | {{ data.name }} 58 | 59 | 60 | 61 | 62 | 63 |
{{ data.desc }}
64 | 65 | 66 | {{ data.extra.oneName }} / {{ data.extra.twoName }} / 67 | {{ data.extra.threeName }} 68 | 69 | {{ data.createdAt }} 70 | 71 | 72 |
73 |
74 |
75 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | ecmaVersion: 11, 4 | sourceType: 'module' 5 | }, 6 | env: { 7 | node: true, 8 | es6: true, 9 | mocha: true, 10 | jest: true, 11 | jasmine: true, 12 | }, 13 | rules: { 14 | 'semi': ['error', 'never'], 15 | 'no-var': 2, 16 | 'constructor-super': 2, 17 | 'no-class-assign': 2, 18 | 'for-direction': 2, 19 | 'getter-return': 2, 20 | 'no-async-promise-executor': 2, 21 | 'no-compare-neg-zero': 2, 22 | 'no-cond-assign': 2, 23 | 'no-constant-condition': 2, 24 | 'no-control-regex': 2, 25 | 'no-debugger': 2, 26 | 'no-dupe-args': 2, 27 | 'no-dupe-keys': 2, 28 | 'no-duplicate-case': 2, 29 | 'no-empty-character-class': 2, 30 | 'no-ex-assign': 2, 31 | 'no-extra-boolean-cast': 2, 32 | 'no-extra-semi': 2, 33 | 'no-func-assign': 2, 34 | 'no-inner-declarations': 2, 35 | 'no-invalid-regexp': 2, 36 | 'no-irregular-whitespace': 2, 37 | 'no-misleading-character-class': 2, 38 | 'no-obj-calls': 2, 39 | 'no-prototype-builtins': 2, 40 | 'no-regex-spaces': 2, 41 | 'no-sparse-arrays': 2, 42 | 'no-unexpected-multiline': 2, 43 | 'no-unreachable': 2, 44 | 'no-unsafe-finally': 2, 45 | 'no-unsafe-negation': 2, 46 | 'use-isnan': 2, 47 | 'valid-typeof': 2, 48 | 'no-empty-pattern': 2, 49 | 'no-fallthrough': 2, 50 | 'no-global-assign': 2, 51 | 'no-octal': 2, 52 | 'no-redeclare': 2, 53 | 'no-self-assign': 2, 54 | 'no-unused-labels': 2, 55 | 'no-useless-catch': 2, 56 | 'no-useless-escape': 2, 57 | 'no-with': 2, 58 | 'no-delete-var': 2, 59 | 'no-shadow-restricted-names': 2, 60 | 'no-undef': 2, 61 | 'no-mixed-spaces-and-tabs': 2, 62 | 'no-const-assign': 2, 63 | 'no-dupe-class-members': 2, 64 | 'no-new-symbol': 2, 65 | 'no-this-before-super': 2, 66 | 'require-yield': 2, 67 | 'symbol-description': 2, 68 | 'space-infix-ops': 2, 69 | 'space-before-blocks': 2, 70 | 'no-trailing-spaces': 2, 71 | 'no-new-object': 2, 72 | 'no-multi-assign': 2, 73 | 'no-array-constructor': 2, 74 | 'func-call-spacing': 2, 75 | 'eol-last': 2, 76 | 'no-script-url': 2, 77 | 'no-return-assign': 2, 78 | 'no-useless-return': 2, 79 | 'no-proto': 2, 80 | 'no-new-wrappers': 2, 81 | 'eqeqeq': 2, 82 | 'no-eval': 2, 83 | 'no-extra-label': 2, 84 | 'no-implied-eval': 2, 85 | 'no-multi-spaces': 2, 86 | 'no-multi-str': 2, 87 | 'arrow-spacing': 2 88 | }, 89 | settings: { 90 | // support import modules from TypeScript files in JavaScript files 91 | 'import/resolver': { node: { extensions: ['.js', '.ts'] } }, 92 | }, 93 | } 94 | -------------------------------------------------------------------------------- /src/assets/styles/dark.scss: -------------------------------------------------------------------------------- 1 | // Copyright @ 2018-present xiejiahe. All rights reserved. 2 | // See https://github.com/xjh22222228/nav 3 | 4 | // 5 | .dark-container { 6 | .dark { 7 | &-primary { 8 | color: #58a6ff !important; 9 | font-weight: 600; 10 | } 11 | 12 | &-title { 13 | color: #c9d1d9 !important; 14 | font-weight: 600 !important; 15 | } 16 | 17 | &-white { 18 | color: #fff !important; 19 | &-700 { 20 | color: rgba(255, 255, 255, 0.7) !important; 21 | } 22 | } 23 | 24 | &-shadow { 25 | box-shadow: 0 0 5px rgba(255, 255, 255, 0.1) !important; 26 | } 27 | 28 | &-bg { 29 | background-color: #0d1117 !important; 30 | } 31 | &-hover { 32 | &:hover { 33 | background-color: #21262d !important; 34 | } 35 | } 36 | &-hover-text { 37 | &:hover { 38 | color: #fff !important; 39 | } 40 | } 41 | 42 | &-bg-gary { 43 | background-color: #21262d !important; 44 | } 45 | 46 | &-bg-gary2 { 47 | background-color: rgb(53, 54, 58) !important; 48 | } 49 | 50 | &-bg-deep { 51 | background-color: #000 !important; 52 | } 53 | 54 | &-text { 55 | color: #6e7681 !important; 56 | } 57 | 58 | &-text-active { 59 | color: #c9d1d9 !important; 60 | font-weight: 600; 61 | } 62 | 63 | &-border-color { 64 | border-color: #30363d !important; 65 | 66 | &:after, 67 | &:before { 68 | border-color: transparent !important; 69 | } 70 | } 71 | 72 | &-item-active { 73 | background-color: #21262d !important; 74 | border-color: #21262d !important; 75 | color: #c9d1d9 !important; 76 | font-weight: 600; 77 | } 78 | 79 | &-scrollbar { 80 | &::-webkit-scrollbar { 81 | width: 10px; 82 | height: 10px; 83 | background-color: rgb(39, 39, 39); 84 | } 85 | &::-webkit-scrollbar-thumb { 86 | background-color: rgb(96, 96, 96); 87 | border: 2px solid rgb(39, 39, 39); 88 | border-radius: 100px; 89 | &:hover { 90 | background-color: rgb(136, 136, 136); 91 | } 92 | } 93 | &::-webkit-scrollbar-cornet { 94 | display: block; 95 | } 96 | } 97 | // 全局滚动条细边 98 | &-scrollbar-border { 99 | &::-webkit-scrollbar-thumb { 100 | border: 3px solid rgb(39, 39, 39); 101 | } 102 | } 103 | 104 | &-item-hover:hover { 105 | background-color: #30363d !important; 106 | border: 1px solid #8b949e !important; 107 | } 108 | 109 | // fix-bar 110 | &-action-hover:hover { 111 | background-color: #c9d1d9 !important; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/components/search-engine/search-engine.component.html: -------------------------------------------------------------------------------- 1 |
6 |
10 | 16 | 26 | 27 | 28 |
36 | 37 | 38 |
39 |
44 | 45 | {{ item.name }} 46 |
47 |
48 |
49 |
50 | 51 | 52 | 56 | 57 | 58 | 59 | 63 | 67 | 71 | 75 | 79 | 83 | 84 | 85 |
86 |
87 | -------------------------------------------------------------------------------- /src/view/side/index.component.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 768px) { 2 | .content { 3 | padding-right: 12px !important; 4 | } 5 | .sider { 6 | --menuTitleWidth: 85px; 7 | } 8 | } 9 | 10 | .layout { 11 | min-height: 100vh; 12 | .inner-layout { 13 | .content { 14 | margin-bottom: 2rem; 15 | padding: 10px 70px 0 20px; 16 | display: flex; 17 | flex-direction: column; 18 | } 19 | .search-header { 20 | z-index: 9; 21 | position: relative; 22 | text-align: center; 23 | } 24 | } 25 | 26 | .box { 27 | flex: 1; 28 | transition: 0.1s linear; 29 | } 30 | 31 | .collapse-wrapper { 32 | position: absolute; 33 | right: 0; 34 | top: 0; 35 | padding: 0 15px; 36 | 37 | .collapseIcon { 38 | display: block; 39 | transform: rotate(-270deg); 40 | 41 | &.active { 42 | transform: rotate(-360deg); 43 | } 44 | } 45 | } 46 | 47 | .ant-menu-item { 48 | padding-right: 40px; 49 | padding-left: 38px !important; 50 | } 51 | ::ng-deep { 52 | .ant-menu-title-content { 53 | display: flex; 54 | align-items: center; 55 | } 56 | .ant-menu.ant-menu-inline-collapsed { 57 | & > .ant-menu-submenu > .ant-menu-submenu-title { 58 | padding: 0 !important; 59 | text-align: center; 60 | font-weight: 400; 61 | } 62 | .sideicon { 63 | margin-right: 0; 64 | } 65 | } 66 | 67 | .mobile-search { 68 | .engine-main { 69 | max-width: 95% !important; 70 | } 71 | } 72 | } 73 | } 74 | 75 | .logo { 76 | z-index: 11; 77 | width: 100%; 78 | padding: 10px; 79 | padding-left: 15px; 80 | background-color: #fff; 81 | white-space: nowrap; 82 | overflow: hidden; 83 | text-overflow: ellipsis; 84 | cursor: pointer; 85 | user-select: none; 86 | 87 | .logo-img { 88 | width: 50px; 89 | height: 50px; 90 | pointer-events: none; 91 | } 92 | 93 | .web-title { 94 | font-weight: 500; 95 | font-size: 18px; 96 | margin-left: 10px; 97 | vertical-align: middle; 98 | } 99 | } 100 | 101 | .sider-compnent { 102 | position: sticky; 103 | top: 0; 104 | left: 0; 105 | height: 100vh; 106 | background-color: #fff; 107 | ::ng-deep { 108 | .ant-layout-sider-children { 109 | display: flex; 110 | flex-direction: column; 111 | } 112 | } 113 | } 114 | 115 | .sider { 116 | height: 100vh; 117 | overflow: hidden; 118 | overflow-y: auto; 119 | flex: 1; 120 | padding-bottom: 30px; 121 | .menu-title { 122 | max-width: var(--menuTitleWidth, 130px); 123 | } 124 | } 125 | .sideicon { 126 | width: 20px; 127 | height: 20px; 128 | margin-right: 12px; 129 | object-fit: cover; 130 | &.fixicon { 131 | margin-right: 8px; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/view/system/tag/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { NzMessageService } from 'ng-zorro-antd/message' 8 | import { NzModalService } from 'ng-zorro-antd/modal' 9 | import { ITagPropValues } from 'src/types' 10 | import { updateFileContent } from 'src/api' 11 | import { TAG_PATH } from 'src/constants' 12 | import { tagList } from 'src/store' 13 | import { isSelfDevelop } from 'src/utils/util' 14 | 15 | @Component({ 16 | selector: 'system-tag', 17 | templateUrl: './index.component.html', 18 | styleUrls: ['./index.component.scss'], 19 | }) 20 | export default class SystemTagComponent { 21 | $t = $t 22 | isSelfDevelop = isSelfDevelop 23 | tagList: ITagPropValues[] = tagList 24 | submitting: boolean = false 25 | incrementId = Math.max(...tagList.map((item) => Number(item.id))) + 1 26 | 27 | constructor( 28 | private message: NzMessageService, 29 | private modal: NzModalService 30 | ) {} 31 | 32 | ngOnInit() {} 33 | 34 | onColorChange(e: any, idx: number) { 35 | const color = e.target.value 36 | this.tagList[idx].color = color 37 | } 38 | 39 | handleAdd() { 40 | const isEmpty = this.tagList.some((item) => !item.name.trim()) 41 | if (isEmpty) { 42 | return 43 | } 44 | this.incrementId += 1 45 | this.tagList.unshift({ 46 | id: this.incrementId, 47 | name: '', 48 | createdAt: '', 49 | color: '#f50000', 50 | desc: '', 51 | isInner: false, 52 | }) 53 | } 54 | 55 | handleDelete(idx: number) { 56 | this.tagList.splice(idx, 1) 57 | } 58 | 59 | handleSubmit() { 60 | if (this.submitting) { 61 | return 62 | } 63 | 64 | // 去重 65 | const o: Record = {} 66 | this.tagList.forEach((item: ITagPropValues) => { 67 | if (item.name?.trim?.()) { 68 | o[item.name] = { 69 | ...item, 70 | name: undefined, 71 | } 72 | } 73 | }) 74 | 75 | if (Object.keys(o).length !== this.tagList.length) { 76 | this.message.error($t('_repeatAdd')) 77 | return 78 | } 79 | 80 | this.modal.info({ 81 | nzTitle: $t('_syncDataOut'), 82 | nzOkText: $t('_confirmSync'), 83 | nzContent: $t('_confirmSyncTip'), 84 | nzOnOk: () => { 85 | this.submitting = true 86 | updateFileContent({ 87 | message: 'update tag', 88 | content: JSON.stringify(this.tagList), 89 | path: TAG_PATH, 90 | }) 91 | .then(() => { 92 | this.message.success($t('_saveSuccess')) 93 | }) 94 | .finally(() => { 95 | this.submitting = false 96 | }) 97 | }, 98 | }) 99 | } 100 | 101 | trackByItem(i: number, item: any) { 102 | return item.id 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/view/shortcut/index.component.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 1280px) { 2 | .shortcut { 3 | --padding: 100px; 4 | } 5 | } 6 | 7 | @media (max-width: 968px) { 8 | .shortcut { 9 | --padding: 70px; 10 | } 11 | } 12 | 13 | @media (max-width: 768px) { 14 | .shortcut { 15 | --padding: 0; 16 | --iconWidth: 45px; 17 | --dockHeight: 61px; 18 | .datetime { 19 | font-size: 40px !important; 20 | } 21 | .title { 22 | font-size: 22px !important; 23 | } 24 | .days { 25 | font-size: 16px !important; 26 | } 27 | } 28 | } 29 | 30 | .shortcut { 31 | position: fixed; 32 | top: 0; 33 | left: 0; 34 | right: 0; 35 | bottom: 0; 36 | display: flex; 37 | justify-content: center; 38 | padding-top: 20px; 39 | text-align: center; 40 | background-color: rgba(0, 0, 0, 0.02); 41 | background-repeat: no-repeat; 42 | background-size: cover; 43 | background-position: center; 44 | user-select: none; 45 | transition: all 0.1s linear; 46 | &.hasComponent { 47 | padding-top: 80px; 48 | } 49 | .box2 { 50 | width: 100%; 51 | display: flex; 52 | flex-direction: column; 53 | } 54 | .flex1 { 55 | flex: 1; 56 | padding: 0 var(--padding, 200px); 57 | margin-bottom: 20px; 58 | overflow: hidden; 59 | overflow-y: auto; 60 | &::-webkit-scrollbar { 61 | display: none; 62 | } 63 | } 64 | .dock { 65 | padding-bottom: 10px; 66 | .dock-box { 67 | background-color: rgba(255, 255, 255, 0.4); 68 | border-radius: 16px; 69 | height: var(--dockHeight, 66px); 70 | padding: 0 8px; 71 | padding-bottom: 8px; 72 | box-shadow: rgba(0, 0, 0, 0.25) 0px 0px 12px 0px; 73 | display: inline-flex; 74 | justify-content: center; 75 | align-items: flex-end; 76 | gap: 16px; 77 | max-width: 100%; 78 | &::-webkit-scrollbar { 79 | display: none; 80 | } 81 | } 82 | .dock-item { 83 | cursor: pointer; 84 | } 85 | ::ng-deep { 86 | .icon { 87 | background-color: transparent; 88 | } 89 | .common-icon { 90 | width: var(--iconWidth, 50px); 91 | height: var(--iconWidth, 50px); 92 | } 93 | } 94 | } 95 | 96 | .title { 97 | font-size: 30px; 98 | font-weight: bold; 99 | } 100 | 101 | ::ng-deep { 102 | .name { 103 | color: #fff !important; 104 | } 105 | .web-list { 106 | gap: 40px; 107 | } 108 | } 109 | .datetime { 110 | font-size: 60px; 111 | color: #fff; 112 | line-height: 1; 113 | font-family: text; 114 | font-weight: bold; 115 | } 116 | .days { 117 | font-size: 20px; 118 | font-weight: bold; 119 | color: rgba(249, 250, 251, 0.85); 120 | margin-bottom: 15px; 121 | margin-top: 5px; 122 | font-family: text; 123 | } 124 | .margin { 125 | margin: 0 10px; 126 | } 127 | } 128 | 129 | .tianqi { 130 | position: fixed; 131 | top: 0; 132 | right: 100px; 133 | } 134 | -------------------------------------------------------------------------------- /src/components/card/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { 6 | Component, 7 | OnInit, 8 | Input, 9 | ChangeDetectionStrategy, 10 | } from '@angular/core' 11 | import { isLogin } from 'src/utils/user' 12 | import { copyText, getTextContent } from 'src/utils' 13 | import { setWebsiteList, deleteByWeb } from 'src/utils/web' 14 | import { INavProps, IWebProps, ICardType } from 'src/types' 15 | import { $t } from 'src/locale' 16 | import { settings, websiteList } from 'src/store' 17 | import { JumpService } from 'src/services/jump' 18 | import event from 'src/utils/mitt' 19 | 20 | @Component({ 21 | changeDetection: ChangeDetectionStrategy.OnPush, 22 | selector: 'app-card', 23 | templateUrl: './index.component.html', 24 | styleUrls: ['./index.component.scss'], 25 | }) 26 | export class CardComponent implements OnInit { 27 | @Input() searchKeyword: string = '' 28 | @Input() dataSource: IWebProps | Record = {} 29 | @Input() indexs: Array = [] 30 | @Input() cardStyle: ICardType = 'standard' 31 | 32 | $t = $t 33 | settings = settings 34 | websiteList: INavProps[] = websiteList 35 | isLogin: boolean = isLogin 36 | copyUrlDone = false 37 | copyPathDone = false 38 | 39 | constructor(public jumpService: JumpService) {} 40 | 41 | ngOnInit(): void {} 42 | 43 | async copyUrl(e: Event, type: number) { 44 | const w = this.dataSource 45 | const { origin, hash, pathname } = window.location 46 | const pathUrl = `${origin}${pathname}${hash}?q=${ 47 | w.name 48 | }&url=${encodeURIComponent(w.url)}` 49 | const isDone = await copyText(e, type === 1 ? pathUrl : w.url) 50 | 51 | if (type === 1) { 52 | this.copyPathDone = isDone 53 | } else { 54 | this.copyUrlDone = isDone 55 | } 56 | } 57 | 58 | copyMouseout() { 59 | this.copyUrlDone = false 60 | this.copyPathDone = false 61 | } 62 | 63 | openEditWebMoal() { 64 | event.emit('CREATE_WEB', { 65 | detail: this.dataSource, 66 | }) 67 | } 68 | 69 | onRateChange(n: number) { 70 | this.dataSource.rate = n 71 | setWebsiteList(this.websiteList) 72 | } 73 | 74 | confirmDel() { 75 | deleteByWeb({ 76 | ...(this.dataSource as IWebProps), 77 | name: getTextContent(this.dataSource.name), 78 | desc: getTextContent(this.dataSource.desc), 79 | }) 80 | } 81 | 82 | openMoveWebModal() { 83 | event.emit('MOVE_WEB', { 84 | indexs: this.indexs, 85 | data: [this.dataSource], 86 | }) 87 | } 88 | 89 | get html() { 90 | return this.dataSource.desc.slice(1) 91 | } 92 | 93 | get getRate() { 94 | if (!this.dataSource.rate) { 95 | return null 96 | } 97 | const rate = Number(this.dataSource.rate) 98 | // 0分不显示 99 | if (!rate) { 100 | return null 101 | } 102 | return rate.toFixed(1) + '分' 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/utils/http.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | 4 | import axios from 'axios' 5 | import NProgress from 'nprogress' 6 | import config from '../../nav.config.json' 7 | import event from './mitt' 8 | import { settings } from 'src/store' 9 | import { getToken, getAuthCode } from '../utils/user' 10 | import { isLogin } from 'src/utils/user' 11 | 12 | const httpInstance = axios.create({ 13 | timeout: 60000 * 3, 14 | baseURL: 15 | config.address || 16 | (config.provider === 'Gitee' 17 | ? 'https://gitee.com/api/v5' 18 | : 'https://api.github.com'), 19 | }) 20 | 21 | function startLoad() { 22 | NProgress.start() 23 | } 24 | 25 | function stopLoad() { 26 | NProgress.done() 27 | } 28 | 29 | httpInstance.interceptors.request.use( 30 | function (config) { 31 | const token = getToken() 32 | if (token) { 33 | config.headers['Authorization'] = `token ${token}` 34 | } 35 | startLoad() 36 | return config 37 | }, 38 | function (error) { 39 | stopLoad() 40 | return Promise.reject(error) 41 | } 42 | ) 43 | 44 | httpInstance.interceptors.response.use( 45 | function (res) { 46 | stopLoad() 47 | return res 48 | }, 49 | function (error) { 50 | const status = 51 | error.status || error.response?.data?.status || error.code || '' 52 | const errorMsg = error.response?.data?.message || error.message || '' 53 | event.emit('NOTIFICATION', { 54 | type: 'error', 55 | title: 'Error:' + status, 56 | content: errorMsg, 57 | }) 58 | stopLoad() 59 | return Promise.reject(error) 60 | } 61 | ) 62 | 63 | const httpNavInstance = axios.create({ 64 | timeout: 15000, 65 | baseURL: 'https://api.nav3.cn', 66 | // baseURL: 'http://localhost:3007', 67 | }) 68 | 69 | httpNavInstance.interceptors.request.use( 70 | function (conf) { 71 | const code = getAuthCode() 72 | if (code) { 73 | conf.headers['Authorization'] = code 74 | } 75 | conf.data = { 76 | code, 77 | hostname: location.hostname, 78 | href: location.href, 79 | isLogin, 80 | ...config, 81 | ...conf.data, 82 | email: settings.email, 83 | language: settings.language, 84 | } 85 | startLoad() 86 | 87 | return conf 88 | }, 89 | function (error) { 90 | stopLoad() 91 | return Promise.reject(error) 92 | } 93 | ) 94 | 95 | httpNavInstance.interceptors.response.use( 96 | function (res) { 97 | stopLoad() 98 | return res 99 | }, 100 | function (error) { 101 | const status = 102 | error.status || error.response?.data?.status || error.code || '' 103 | const errorMsg = error.response?.data?.message || error.message || '' 104 | event.emit('NOTIFICATION', { 105 | type: 'error', 106 | title: 'Error:' + status, 107 | content: errorMsg, 108 | }) 109 | stopLoad() 110 | return Promise.reject(error) 111 | } 112 | ) 113 | 114 | export const httpNav = httpNavInstance 115 | 116 | export default httpInstance 117 | -------------------------------------------------------------------------------- /src/components/footer/template.ts: -------------------------------------------------------------------------------- 1 | const t: Record = { 2 | footTemplate1: ` 3 |
4 |
8 |
9 |
联系方式
10 |
11 | 问题反馈:xjh22222228@gmail.com 12 |
13 |
14 | 微信授权:xjh22222228 15 |
16 |
17 | 18 |
19 |
网站信息
20 |
共收录 $\{total} 个网站
21 |
22 | 申请收录 23 |
24 |
25 | 26 |
27 |
技术支持
28 |
29 | 30 | Nav 31 | 32 |
33 | 41 |
42 |
43 |
44 | `, 45 | 46 | footTemplate2: ` 47 |
48 |
共收录$\{total}个网站
49 |
Copyright © 2018-$\{year} $\{hostname}, All Rights Reserved
50 |
51 | `, 52 | 53 | footTemplate3: ` 54 |
57 |
58 | 62 | @ $\{year} $\{hostname}, $\{total}. 63 |
64 | 65 |
68 | Author 74 | Nav 80 | Boomb 86 | Beautiful window 92 | Tomato work 98 |
99 |
100 | `, 101 | } 102 | 103 | export default t 104 | -------------------------------------------------------------------------------- /src/view/app/default/app.component.scss: -------------------------------------------------------------------------------- 1 | $bg-color: #fbfbfb; 2 | 3 | .app-page { 4 | padding: 45px 0 0 0; 5 | min-height: 100vh; 6 | background: #f6f6f6; 7 | overflow: hidden; 8 | 9 | .header { 10 | z-index: 9999; 11 | position: fixed; 12 | top: 0; 13 | left: 0; 14 | right: 0; 15 | text-align: center; 16 | background: $bg-color; 17 | box-shadow: 0 0 3px #ccc; 18 | 19 | .header-top { 20 | position: relative; 21 | height: 45px; 22 | border-bottom: 1px solid #eee; 23 | background: $bg-color; 24 | 25 | .logo { 26 | display: inline-block; 27 | width: 35px; 28 | height: 35px; 29 | margin-top: 4px; 30 | pointer-events: none; 31 | } 32 | } 33 | 34 | .open { 35 | position: absolute; 36 | top: 9px; 37 | left: 15px; 38 | cursor: pointer; 39 | 40 | i { 41 | display: block; 42 | margin-top: 6px; 43 | height: 2px; 44 | width: 25px; 45 | background: #999; 46 | transform-origin: right center; 47 | transition: 0.1s linear; 48 | } 49 | 50 | &.active { 51 | i:nth-child(1) { 52 | transform: rotate(-45deg); 53 | } 54 | 55 | i:nth-child(2) { 56 | opacity: 0; 57 | } 58 | 59 | i:nth-child(3) { 60 | transform: translateY(2px) rotate(45deg); 61 | } 62 | } 63 | } 64 | 65 | .nav-open { 66 | display: none; 67 | box-shadow: 1px 1px 5px #ccc; 68 | overflow: hidden; 69 | background: $bg-color; 70 | transition: 0.1s linear; 71 | &.active { 72 | display: block; 73 | } 74 | } 75 | 76 | .nav-title { 77 | display: inline-block; 78 | width: 50%; 79 | font-size: 16px; 80 | padding: 8px 0; 81 | color: #666; 82 | cursor: pointer; 83 | &:hover { 84 | background: rgba(0, 0, 0, 0.05); 85 | } 86 | &:active, 87 | &.active { 88 | background: rgba(0, 0, 0, 0.05); 89 | } 90 | } 91 | } 92 | 93 | .wrapper { 94 | margin: 0 0 60px 0; 95 | 96 | .children-nav { 97 | background: $bg-color; 98 | margin: 0 0 15px 0; 99 | padding: 10px 10px 5px; 100 | white-space: nowrap; 101 | overflow: auto; 102 | box-shadow: 0 0 3px #ccc; 103 | 104 | .tag { 105 | position: relative; 106 | display: inline-block; 107 | font-size: 14px; 108 | padding: 3px 5px; 109 | margin: 0 3px 5px 0; 110 | transition: background 0.1s linear; 111 | color: #333; 112 | } 113 | 114 | .active::after { 115 | content: ''; 116 | position: absolute; 117 | bottom: 5px; 118 | left: 50%; 119 | width: 50%; 120 | transform: translateX(-50%); 121 | height: 3px; 122 | border-radius: 2px; 123 | background: #20a0ff; 124 | } 125 | } 126 | 127 | .block-title { 128 | padding: 10px 0 10px 15px; 129 | border-bottom: 1px solid #eee; 130 | color: #3f51b5; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/components/fixbar/index.component.html: -------------------------------------------------------------------------------- 1 |
2 |
9 | 10 |
11 | 12 |
    13 |
  • 18 | {{ themeItem.name }} 19 |
  • 20 |
21 |
22 | 23 |
31 | 32 |
33 | 34 |
41 | 42 |
43 | 44 |
51 | 55 |
56 | 57 |
63 | 64 | 65 |
66 | 67 |
72 | 77 | 82 |
83 | 84 |
88 | 94 |
95 | 96 |
101 | 105 |
106 |
107 | -------------------------------------------------------------------------------- /src/view/system/search/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { NzMessageService } from 'ng-zorro-antd/message' 8 | import { ISearchEngineProps } from 'src/types' 9 | import { updateFileContent } from 'src/api' 10 | import { NzModalService } from 'ng-zorro-antd/modal' 11 | import { SEARCH_PATH } from 'src/constants' 12 | import { searchEngineList } from 'src/store' 13 | 14 | @Component({ 15 | selector: 'system-tag', 16 | templateUrl: './index.component.html', 17 | styleUrls: ['./index.component.scss'], 18 | }) 19 | export default class SystemSearchComponent { 20 | $t = $t 21 | searchList: ISearchEngineProps[] = searchEngineList 22 | submitting: boolean = false 23 | 24 | constructor( 25 | private message: NzMessageService, 26 | private modal: NzModalService 27 | ) {} 28 | 29 | handleAdd() { 30 | const isEmpty = this.searchList.some((item) => !item.name.trim()) 31 | if (isEmpty) { 32 | return 33 | } 34 | this.searchList.unshift({ 35 | name: '', 36 | url: '', 37 | icon: '', 38 | placeholder: '', 39 | blocked: false, 40 | isInner: false, 41 | }) 42 | } 43 | 44 | handleDelete(idx: number) { 45 | this.searchList.splice(idx, 1) 46 | } 47 | 48 | // 上移 49 | moveUp(index: number): void { 50 | if (index === 0) { 51 | return 52 | } 53 | const current = this.searchList[index] 54 | const prev = this.searchList[index - 1] 55 | this.searchList[index - 1] = current 56 | this.searchList[index] = prev 57 | } 58 | 59 | // 下移 60 | moveDown(index: number): void { 61 | if (index === this.searchList.length - 1) { 62 | return 63 | } 64 | const current = this.searchList[index] 65 | const next = this.searchList[index + 1] 66 | this.searchList[index + 1] = current 67 | this.searchList[index] = next 68 | } 69 | 70 | handleSubmit() { 71 | if (this.submitting) { 72 | return 73 | } 74 | 75 | this.modal.info({ 76 | nzTitle: $t('_syncDataOut'), 77 | nzOkText: $t('_confirmSync'), 78 | nzContent: $t('_confirmSyncTip'), 79 | nzOnOk: () => { 80 | const o = {} 81 | this.searchList.forEach((item) => { 82 | if (item.name.trim()) { 83 | // @ts-ignore 84 | o[item.name] = null 85 | } 86 | }) 87 | 88 | if (Object.keys(o).length !== this.searchList.length) { 89 | this.message.error($t('_repeatAdd')) 90 | return 91 | } 92 | 93 | this.submitting = true 94 | updateFileContent({ 95 | message: 'update search', 96 | content: JSON.stringify(this.searchList), 97 | path: SEARCH_PATH, 98 | }) 99 | .then(() => { 100 | this.message.success($t('_saveSuccess')) 101 | }) 102 | .finally(() => { 103 | this.submitting = false 104 | }) 105 | }, 106 | }) 107 | } 108 | 109 | trackByItem(a: any, item: any) { 110 | return item.name 111 | } 112 | 113 | onChangeUpload(path: any, idx: number) { 114 | this.searchList[idx].icon = path.cdn 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/assets/img/bookmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/component/runtime.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "nav2": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "allowedCommonJsDependencies": [ 21 | "axios", 22 | "qs", 23 | "nprogress", 24 | "clipboard", 25 | "file-saver", 26 | "localforage" 27 | ], 28 | "outputPath": "dist", 29 | "index": "src/main.html", 30 | "main": "src/main.ts", 31 | "polyfills": ["zone.js"], 32 | "tsConfig": "tsconfig.app.json", 33 | "inlineStyleLanguage": "scss", 34 | "assets": [ 35 | "src/assets", 36 | { 37 | "glob": "**/*", 38 | "input": "public", 39 | "output": "/" 40 | } 41 | ], 42 | "styles": ["src/styles.scss"], 43 | "scripts": [] 44 | }, 45 | "configurations": { 46 | "production": { 47 | "budgets": [ 48 | { 49 | "type": "initial", 50 | "maximumWarning": "5mb", 51 | "maximumError": "500mb" 52 | }, 53 | { 54 | "type": "anyComponentStyle", 55 | "maximumWarning": "10kb", 56 | "maximumError": "10kb" 57 | } 58 | ], 59 | "outputHashing": "all" 60 | }, 61 | "development": { 62 | "buildOptimizer": false, 63 | "optimization": false, 64 | "vendorChunk": true, 65 | "extractLicenses": false, 66 | "sourceMap": true, 67 | "namedChunks": true 68 | } 69 | }, 70 | "defaultConfiguration": "production" 71 | }, 72 | "serve": { 73 | "builder": "@angular-devkit/build-angular:dev-server", 74 | "configurations": { 75 | "production": { 76 | "buildTarget": "nav2:build:production" 77 | }, 78 | "development": { 79 | "buildTarget": "nav2:build:development" 80 | } 81 | }, 82 | "defaultConfiguration": "development" 83 | }, 84 | "extract-i18n": { 85 | "builder": "@angular-devkit/build-angular:extract-i18n", 86 | "options": { 87 | "buildTarget": "nav2:build" 88 | } 89 | }, 90 | "test": { 91 | "builder": "@angular-devkit/build-angular:karma", 92 | "options": { 93 | "polyfills": ["zone.js", "zone.js/testing"], 94 | "tsConfig": "tsconfig.spec.json", 95 | "inlineStyleLanguage": "scss", 96 | "assets": ["src/favicon.ico", "src/assets"], 97 | "styles": ["src/styles.scss"], 98 | "scripts": [] 99 | } 100 | } 101 | } 102 | } 103 | }, 104 | "cli": { 105 | "analytics": "e700bfbb-1203-4812-bab2-d4a7fa5f752e" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/services/common.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Injectable } from '@angular/core' 6 | import { Router, ActivatedRoute } from '@angular/router' 7 | import { websiteList, settings } from 'src/store' 8 | import { 9 | queryString, 10 | fuzzySearch, 11 | matchCurrentList, 12 | getOverIndex, 13 | } from 'src/utils' 14 | import { setWebsiteList, toggleCollapseAll } from 'src/utils/web' 15 | import { INavProps, INavThreeProp } from 'src/types' 16 | import { isLogin } from 'src/utils/user' 17 | import { isSelfDevelop } from 'src/utils/util' 18 | import event from 'src/utils/mitt' 19 | 20 | @Injectable({ 21 | providedIn: 'root', 22 | }) 23 | export class CommonService { 24 | isLogin = isLogin 25 | settings = settings 26 | websiteList: INavProps[] = websiteList 27 | currentList: INavThreeProp[] = [] 28 | id = 0 29 | page = 0 30 | sliceMax = 0 31 | selectedIndex = 0 // 第三级菜单选中 32 | searchKeyword = '' 33 | overIndex = Number.MAX_SAFE_INTEGER 34 | title: string = settings.title.trim().split(/\s/)[0] 35 | 36 | constructor(private router: Router, private activatedRoute: ActivatedRoute) { 37 | const init = () => { 38 | this.activatedRoute.queryParams.subscribe(() => { 39 | const { id, page, q } = queryString() 40 | this.page = page 41 | this.id = id 42 | this.searchKeyword = q 43 | this.handleCheckThree(0) 44 | this.sliceMax = 0 45 | 46 | if (q) { 47 | this.currentList = fuzzySearch(websiteList, q) 48 | } else { 49 | this.currentList = matchCurrentList() 50 | } 51 | setTimeout(() => { 52 | this.sliceMax = Number.MAX_SAFE_INTEGER 53 | }, 100) 54 | }) 55 | } 56 | if (window.__FINISHED__) { 57 | init() 58 | } else { 59 | event.on('WEB_FINISH', () => { 60 | init() 61 | }) 62 | } 63 | } 64 | 65 | handleCilckTopNav(index: number) { 66 | const id = websiteList[index].id || 0 67 | this.router.navigate([this.router.url.split('?')[0]], { 68 | queryParams: { 69 | page: index, 70 | id, 71 | _: Date.now(), 72 | }, 73 | }) 74 | } 75 | handleSidebarNav(index: number, pageIndex?: number) { 76 | const { page } = queryString() 77 | websiteList[pageIndex ?? page].id = index 78 | this.router.navigate([this.router.url.split('?')[0]], { 79 | queryParams: { 80 | page: pageIndex ?? page, 81 | id: index, 82 | _: Date.now(), 83 | }, 84 | }) 85 | } 86 | 87 | handleCheckThree(index: number) { 88 | this.selectedIndex = index 89 | } 90 | 91 | onCollapseAll = (e?: Event) => { 92 | e?.stopPropagation() 93 | toggleCollapseAll(websiteList) 94 | } 95 | 96 | trackByItem(a: any, item: any) { 97 | return item.title 98 | } 99 | 100 | trackByItemWeb(a: any, item: any) { 101 | return item.id 102 | } 103 | 104 | get collapsed() { 105 | try { 106 | return !!websiteList[this.page].nav[this.id].collapsed 107 | } catch (error) { 108 | return false 109 | } 110 | } 111 | 112 | onCollapse = (item: any, index: number) => { 113 | item.collapsed = !item.collapsed 114 | this.websiteList[this.page].nav[this.id].nav[index] = item 115 | if (!isSelfDevelop) { 116 | setWebsiteList(this.websiteList) 117 | } 118 | } 119 | 120 | getOverIndex(selector: string) { 121 | queueMicrotask(() => { 122 | const overIndex = getOverIndex(selector) 123 | if (this.overIndex === overIndex) { 124 | return 125 | } 126 | this.overIndex = overIndex 127 | }) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/view/light/index.component.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 1280px) { 2 | .index-wrapper { 3 | --width: 800px; 4 | --rightWidth: 200px; 5 | } 6 | .index-wrapper.noimg { 7 | --width: 1000px; 8 | } 9 | } 10 | 11 | @media (max-width: 1080px) { 12 | .index-wrapper { 13 | --width: 800px !important; 14 | } 15 | .right { 16 | display: none; 17 | } 18 | } 19 | 20 | @media (max-width: 1000px) { 21 | .light-box { 22 | width: 100% !important; 23 | } 24 | } 25 | 26 | @media (max-width: 810px) { 27 | .index-wrapper { 28 | --top: 0; 29 | } 30 | } 31 | 32 | .index-wrapper { 33 | display: flex; 34 | flex-direction: column; 35 | height: 100vh; 36 | padding-top: var(--top, 15px); 37 | overflow: hidden; 38 | .light-box { 39 | display: flex; 40 | justify-content: center; 41 | height: 100%; 42 | margin: 0 auto; 43 | column-gap: 20px; 44 | overflow: hidden; 45 | } 46 | .right { 47 | width: var(--rightWidth, 230px); 48 | min-width: var(--rightWidth, 230px); 49 | max-height: calc(100vh - 50px); 50 | overflow: hidden; 51 | overflow-y: auto; 52 | .adsimg { 53 | display: block; 54 | width: 100%; 55 | border-radius: 4px; 56 | } 57 | .aditem { 58 | position: relative; 59 | margin-bottom: 20px; 60 | border-radius: 4px; 61 | overflow: hidden; 62 | } 63 | } 64 | } 65 | 66 | .homepage { 67 | width: var(--width, 1000px); 68 | height: 100%; 69 | position: relative; 70 | background: #f9f9f9; 71 | border-radius: 5px; 72 | overflow: hidden; 73 | transition: 0.1s linear; 74 | border: 1px solid transparent; 75 | 76 | .top-nav { 77 | padding: 10px 0; 78 | overflow: none; 79 | overflow-x: auto; 80 | white-space: nowrap; 81 | border-bottom: 1px solid #eee; 82 | text-align: center; 83 | user-select: none; 84 | 85 | ::ng-deep .more-btn, 86 | .ripple-btn { 87 | height: 40px; 88 | position: relative; 89 | padding: 0 15px; 90 | color: #000; 91 | cursor: pointer; 92 | border-radius: 5px; 93 | overflow: hidden; 94 | display: inline-flex; 95 | align-items: center; 96 | &.active::after { 97 | content: ''; 98 | position: absolute; 99 | bottom: 0; 100 | left: 20%; 101 | right: 20%; 102 | height: 3px; 103 | border-radius: 2px; 104 | background-color: rgb(16, 142, 233); 105 | } 106 | } 107 | } 108 | } 109 | 110 | .index-section { 111 | position: relative; 112 | height: calc(100% - 52px); 113 | overflow: hidden; 114 | 115 | // 侧边栏分类 116 | $sidebarWidth: 80px; 117 | .sidebar { 118 | position: relative; 119 | width: $sidebarWidth; 120 | height: calc(100% - 25px); 121 | text-align: center; 122 | border-right: 1px solid #eee; 123 | overflow-y: auto; 124 | 125 | .tag { 126 | position: relative; 127 | cursor: pointer; 128 | padding: 11px 0; 129 | &.active { 130 | color: #1890ff; 131 | &::after, 132 | &::before { 133 | content: ''; 134 | position: absolute; 135 | left: 0; 136 | width: 100%; 137 | } 138 | &:after { 139 | top: 0; 140 | border-top: 1px dashed #ccc; 141 | } 142 | &:before { 143 | content: ''; 144 | bottom: 0; 145 | border-bottom: 1px dashed #ccc; 146 | } 147 | } 148 | } 149 | } 150 | 151 | .main { 152 | width: calc(100% - #{$sidebarWidth}); 153 | position: absolute; 154 | top: 0; 155 | left: $sidebarWidth; 156 | bottom: 30px; 157 | padding-bottom: 50px; 158 | overflow: hidden; 159 | overflow-y: auto; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/components/web-list/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, Input } from '@angular/core' 6 | import { websiteList, settings } from 'src/store' 7 | import { IWebProps, INavProps, TopType } from 'src/types' 8 | import { queryString, fuzzySearch, isMobile, getDefaultTheme } from 'src/utils' 9 | import { isLogin } from 'src/utils/user' 10 | import { ActivatedRoute, Router } from '@angular/router' 11 | import { CommonService } from 'src/services/common' 12 | import { JumpService } from 'src/services/jump' 13 | import event from 'src/utils/mitt' 14 | 15 | let DEFAULT_WEBSITE: Array = [] 16 | 17 | @Component({ 18 | selector: 'app-web-list', 19 | templateUrl: './index.component.html', 20 | styleUrls: ['./index.component.scss'], 21 | }) 22 | export class WebListComponent { 23 | @Input() type: 'dock' | '' = '' 24 | @Input() dockCount = 4 25 | @Input() size: 'large' | '' = '' 26 | @Input() max: number = 110 27 | @Input() search = true 28 | @Input() overflow = false 29 | 30 | websiteList: INavProps[] = websiteList 31 | dataList: IWebProps[] = [] 32 | 33 | constructor( 34 | private router: Router, 35 | public jumpService: JumpService, 36 | private activatedRoute: ActivatedRoute, 37 | public commonService: CommonService 38 | ) {} 39 | 40 | ngOnInit() { 41 | const init = () => { 42 | this.getTopWeb() 43 | this.activatedRoute.queryParams.subscribe(() => { 44 | const { q } = queryString() 45 | 46 | if (this.search && q.trim()) { 47 | const result = fuzzySearch(this.websiteList, q) 48 | if (result.length === 0) { 49 | this.dataList = [] 50 | } else { 51 | this.dataList = result[0].nav.slice(0, this.max) 52 | } 53 | } else { 54 | this.dataList = DEFAULT_WEBSITE 55 | } 56 | }) 57 | } 58 | if (window.__FINISHED__) { 59 | init() 60 | } else { 61 | event.on('WEB_FINISH', () => { 62 | init() 63 | }) 64 | } 65 | } 66 | 67 | // 获取置顶WEB 68 | getTopWeb() { 69 | let path = this.router.url.split('?')[0].replace('/', '') 70 | if (!path) { 71 | path = getDefaultTheme() 72 | } 73 | path = path[0].toUpperCase() + path.slice(1) 74 | const dataList: IWebProps[] = [] 75 | const max = this.max 76 | let dockList: IWebProps[] = [] 77 | 78 | function r(nav: any) { 79 | if (!Array.isArray(nav)) return 80 | 81 | for (let i = 0; i < nav.length; i++) { 82 | if (dataList.length > max) { 83 | break 84 | } 85 | 86 | const item = nav[i] 87 | if (item.url) { 88 | if (item.top && (isLogin || !item.ownVisible)) { 89 | const isMatch = (item.topTypes || []).some( 90 | (v: number) => path === TopType[v] 91 | ) 92 | if (isMatch) { 93 | dataList.push(item) 94 | } 95 | } 96 | } else { 97 | r(item.nav) 98 | } 99 | } 100 | } 101 | r(websiteList) 102 | 103 | // @ts-ignore 104 | this.dataList = dataList.sort((a: any, b: any) => { 105 | const aIdx = a.index == null || a.index === '' ? 100000 : Number(a.index) 106 | const bIdx = b.index == null || b.index === '' ? 100000 : Number(b.index) 107 | return aIdx - bIdx 108 | }) 109 | if (this.type === 'dock') { 110 | const dockCount = isMobile() ? 5 : this.dockCount 111 | dockList = this.dataList.slice(0, dockCount) 112 | event.emit('DOCK_LIST', dockList) 113 | this.dataList = this.dataList.slice(dockCount) 114 | } 115 | DEFAULT_WEBSITE = this.dataList 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/view/system/index.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
    5 |
  • 6 |
      7 |
    • 12 | {{ $t('_websiteMang') }} 13 |
    • 14 |
    • 19 | {{ $t('_systemSet') }} 20 |
    • 21 |
    • 26 | {{ $t('_tagSettings') }} 27 |
    • 28 |
    • 33 | {{ $t('_bookmarkImport') }} 34 |
    • 35 |
    • 40 | {{ $t('_searchEngines') }} 41 |
    • 42 |
    • 48 | {{ $t('_components') }} 49 |
    • 50 |
    • 56 | {{ $t('_bookmarkExport') }} 57 |
    • 58 |
    • 64 | {{ $t('_userCollect') }} 65 |
    • 66 | 67 |
    • 72 | {{ $t('_vipAuth') }} 73 |
    • 74 |
    • 79 | {{ $t('_webInfo') }} 80 |
    • 81 |
    82 |
  • 83 |
84 |
85 | 86 | 87 |
88 | 91 | 92 | 95 | 96 | 97 | 101 | 102 | 103 | {{ date }} 104 |
105 | 106 | 107 |
108 |
109 |
110 |
111 | 112 | 113 | -------------------------------------------------------------------------------- /src/view/system/component/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component, ViewChild } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { NzMessageService } from 'ng-zorro-antd/message' 8 | import { NzModalService } from 'ng-zorro-antd/modal' 9 | import { updateFileContent } from 'src/api' 10 | import { COMPONENT_PATH } from 'src/constants' 11 | import { components } from 'src/store' 12 | import { ComponentType, IComponentProps } from 'src/types' 13 | import { CalendarDrawerComponent } from 'src/components/calendar/drawer/index.component' 14 | import { RuntimeDrawerComponent } from 'src/components/runtime/drawer/index.component' 15 | import { OffWorkDrawerComponent } from 'src/components/off-work/drawer/index.component' 16 | import { ImageDrawerComponent } from 'src/components/image/drawer/index.component' 17 | import { componentTitleMap } from './types' 18 | import { isSelfDevelop } from 'src/utils/util' 19 | 20 | @Component({ 21 | selector: 'system-component', 22 | templateUrl: './index.component.html', 23 | styleUrls: ['./index.component.scss'], 24 | }) 25 | export default class SystemComponentComponent { 26 | @ViewChild('calendar') calendarChild!: CalendarDrawerComponent 27 | @ViewChild('runtime') runtimeChild!: RuntimeDrawerComponent 28 | @ViewChild('offwork') offworkChild!: OffWorkDrawerComponent 29 | @ViewChild('image') imageChild!: ImageDrawerComponent 30 | 31 | $t = $t 32 | isSelfDevelop = isSelfDevelop 33 | componentTitleMap = componentTitleMap 34 | ComponentType = ComponentType 35 | components = components 36 | submitting: boolean = false 37 | 38 | constructor( 39 | private message: NzMessageService, 40 | private modal: NzModalService 41 | ) {} 42 | 43 | ngOnInit() {} 44 | 45 | // 上移 46 | moveUp(index: number): void { 47 | if (index === 0) { 48 | return 49 | } 50 | const current = this.components[index] 51 | const prev = this.components[index - 1] 52 | this.components[index - 1] = current 53 | this.components[index] = prev 54 | } 55 | 56 | // 下移 57 | moveDown(index: number): void { 58 | if (index === this.components.length - 1) { 59 | return 60 | } 61 | const current = this.components[index] 62 | const next = this.components[index + 1] 63 | this.components[index + 1] = current 64 | this.components[index] = next 65 | } 66 | 67 | handleEdit(data: any, idx: number) { 68 | const type = data.type 69 | const types: Record = { 70 | 1: this.calendarChild, 71 | 2: this.offworkChild, 72 | 3: this.runtimeChild, 73 | 4: this.imageChild, 74 | } 75 | types[type]?.open(data, idx) 76 | } 77 | 78 | onAdd(data: IComponentProps) { 79 | let max = Math.max(...this.components.map((item) => item.id)) 80 | max = max <= 0 ? 1 : max + 1 81 | this.components.push({ 82 | ...data, 83 | id: max, 84 | }) 85 | } 86 | 87 | onDelete(idx: number) {} 88 | 89 | handleOk(data: any) { 90 | const { index, ...values } = data 91 | this.components[index] = { 92 | ...this.components[index], 93 | ...values, 94 | } 95 | } 96 | 97 | handleSubmit() { 98 | if (this.submitting) { 99 | return 100 | } 101 | 102 | this.modal.info({ 103 | nzTitle: $t('_syncDataOut'), 104 | nzOkText: $t('_confirmSync'), 105 | nzContent: $t('_confirmSyncTip'), 106 | nzOnOk: () => { 107 | this.submitting = true 108 | updateFileContent({ 109 | message: 'update component', 110 | content: JSON.stringify(this.components), 111 | path: COMPONENT_PATH, 112 | }) 113 | .then(() => { 114 | this.message.success($t('_saveSuccess')) 115 | }) 116 | .finally(() => { 117 | this.submitting = false 118 | }) 119 | }, 120 | }) 121 | } 122 | 123 | trackByItem(i: number, item: any) { 124 | return item.id 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/view/system/collect/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { $t } from 'src/locale' 7 | import { NzMessageService } from 'ng-zorro-antd/message' 8 | import { NzNotificationService } from 'ng-zorro-antd/notification' 9 | import { NzModalService } from 'ng-zorro-antd/modal' 10 | import { websiteList, tagMap } from 'src/store' 11 | import { setAuthCode, getAuthCode } from 'src/utils/user' 12 | import { getUserCollect, delUserCollect, updateFileContent } from 'src/api' 13 | import { DB_PATH } from 'src/constants' 14 | import { isSelfDevelop } from 'src/utils/util' 15 | import event from 'src/utils/mitt' 16 | 17 | @Component({ 18 | selector: 'user-collect', 19 | templateUrl: './index.component.html', 20 | styleUrls: ['./index.component.scss'], 21 | }) 22 | export default class CollectComponent { 23 | $t = $t 24 | isSelfDevelop = isSelfDevelop 25 | submitting: boolean = false 26 | isPermission = !!getAuthCode() 27 | dataList: Array = [] 28 | authCode = '' 29 | tagMap = tagMap 30 | 31 | constructor( 32 | private message: NzMessageService, 33 | private modal: NzModalService, 34 | private notification: NzNotificationService 35 | ) {} 36 | 37 | ngOnInit() { 38 | this.getUserCollect() 39 | } 40 | 41 | handleDelete(idx: number) { 42 | this.submitting = true 43 | delUserCollect({ 44 | data: this.dataList[idx], 45 | }) 46 | .then((res) => { 47 | this.dataList = res.data?.data || [] 48 | }) 49 | .finally(() => { 50 | this.submitting = false 51 | }) 52 | } 53 | 54 | getUserCollect() { 55 | this.submitting = true 56 | getUserCollect() 57 | .then((res: any) => { 58 | this.isPermission = true 59 | this.dataList = res.data?.data || [] 60 | }) 61 | .finally(() => { 62 | this.submitting = false 63 | }) 64 | } 65 | 66 | handleSubmitAuthCode() { 67 | if (this.submitting || !this.authCode) { 68 | return 69 | } 70 | 71 | setAuthCode(this.authCode) 72 | this.getUserCollect() 73 | } 74 | 75 | handleConfirmGet(data: any, idx: number) { 76 | const that = this 77 | let oneIndex = 0 78 | let twoIndex = 0 79 | let threeIndex = 0 80 | try { 81 | oneIndex = websiteList.findIndex( 82 | (item) => item.title === data.extra.oneName 83 | ) 84 | twoIndex = websiteList[oneIndex].nav.findIndex( 85 | (item) => item.title === data.extra.twoName 86 | ) 87 | threeIndex = websiteList[oneIndex].nav[twoIndex].nav.findIndex( 88 | (item) => item.title === data.extra.threeName 89 | ) 90 | } catch (error) { 91 | this.notification.error($t('_error'), $t('_classNoMatch')) 92 | } 93 | 94 | try { 95 | event.emit('CREATE_WEB', { 96 | detail: data, 97 | oneIndex, 98 | twoIndex, 99 | threeIndex, 100 | isMove: true, 101 | }) 102 | event.emit('SET_CREATE_WEB', { 103 | detail: null, 104 | callback() { 105 | that.handleDelete(idx) 106 | }, 107 | }) 108 | } catch (error: any) { 109 | this.notification.error($t('_error'), error.message) 110 | } 111 | } 112 | 113 | handleSubmit() { 114 | if (this.submitting) { 115 | return 116 | } 117 | 118 | this.modal.info({ 119 | nzTitle: $t('_syncDataOut'), 120 | nzOkText: $t('_confirmSync'), 121 | nzContent: $t('_confirmSyncTip'), 122 | nzOnOk: () => { 123 | this.submitting = true 124 | updateFileContent({ 125 | message: 'update db', 126 | content: JSON.stringify(websiteList), 127 | path: DB_PATH, 128 | }) 129 | .then(() => { 130 | this.message.success($t('_syncSuccessTip')) 131 | }) 132 | .finally(() => { 133 | this.submitting = false 134 | }) 135 | }, 136 | }) 137 | } 138 | 139 | trackByItem(i: number, item: any) { 140 | return item.id 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { NgModule } from '@angular/core' 6 | import { RouterModule, Routes } from '@angular/router' 7 | import config from '../../nav.config.json' 8 | import LightComponent from '../view/light/index.component' 9 | import SuperComponent from '../view/super/index.component' 10 | import SimComponent from '../view/sim/index.component' 11 | import SystemComponent from '../view/system/index.component' 12 | import SystemInfoComponent from '../view/system/info/index.component' 13 | import SystemBookmarkComponent from '../view/system/bookmark/index.component' 14 | import SystemBookmarkExportComponent from '../view/system/bookmark-export/index.component' 15 | import SystemTagComponent from '../view/system/tag/index.component' 16 | import SystemSearchComponent from '../view/system/search/index.component' 17 | import SystemSettingComponent from '../view/system/setting/index.component' 18 | import SystemWebComponent from '../view/system/web/index.component' 19 | import SystemComponentComponent from '../view/system/component/index.component' 20 | import SideComponent from '../view/side/index.component' 21 | import ShortcutComponent from '../view/shortcut/index.component' 22 | import CollectComponent from '../view/system/collect/index.component' 23 | import WebpComponent from '../view/app/default/app.component' 24 | import VipAuthComponent from '../view/system/vip-auth/index.component' 25 | import { isSelfDevelop } from 'src/utils/util' 26 | import { getDefaultTheme } from 'src/utils' 27 | 28 | export const routes: Routes = [ 29 | { 30 | path: 'sim', 31 | component: SimComponent, 32 | data: {}, 33 | }, 34 | { 35 | path: 'super', 36 | component: SuperComponent, 37 | data: {}, 38 | }, 39 | { 40 | path: 'side', 41 | component: SideComponent, 42 | data: {}, 43 | }, 44 | { 45 | path: 'shortcut', 46 | component: ShortcutComponent, 47 | data: {}, 48 | }, 49 | 50 | { 51 | path: 'light', 52 | component: LightComponent, 53 | data: { 54 | renderLinear: true, 55 | data: {}, 56 | }, 57 | }, 58 | { 59 | path: 'app', 60 | component: WebpComponent, 61 | data: {}, 62 | }, 63 | { 64 | path: 'system', 65 | component: SystemComponent, 66 | children: [ 67 | { 68 | path: 'info', 69 | component: SystemInfoComponent, 70 | }, 71 | { 72 | path: 'bookmark', 73 | component: SystemBookmarkComponent, 74 | }, 75 | { 76 | path: 'bookmarkExport', 77 | component: SystemBookmarkExportComponent, 78 | }, 79 | { 80 | path: 'collect', 81 | component: CollectComponent, 82 | }, 83 | { 84 | path: 'vip', 85 | component: VipAuthComponent, 86 | }, 87 | { 88 | path: 'tag', 89 | component: SystemTagComponent, 90 | }, 91 | { 92 | path: 'search', 93 | component: SystemSearchComponent, 94 | }, 95 | { 96 | path: 'setting', 97 | component: SystemSettingComponent, 98 | }, 99 | { 100 | path: 'component', 101 | component: SystemComponentComponent, 102 | }, 103 | { 104 | path: 'web', 105 | component: SystemWebComponent, 106 | }, 107 | { 108 | path: '**', 109 | redirectTo: '/system/web', 110 | }, 111 | ], 112 | }, 113 | ] 114 | 115 | // 自有部署异步 116 | if (!isSelfDevelop) { 117 | const defaultTheme = getDefaultTheme().toLowerCase() 118 | const hasDefault = routes.find((item) => item.path === defaultTheme) 119 | if (hasDefault) { 120 | routes.push({ 121 | ...hasDefault, 122 | path: '**', 123 | }) 124 | } else { 125 | routes.push({ 126 | path: '**', 127 | redirectTo: '/' + defaultTheme, 128 | }) 129 | } 130 | } 131 | 132 | @NgModule({ 133 | imports: [ 134 | RouterModule.forRoot(routes, { 135 | useHash: config.hashMode, 136 | }), 137 | ], 138 | exports: [RouterModule], 139 | }) 140 | export class AppRoutingModule {} 141 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | // See https://github.com/xjh22222228/nav 4 | 5 | import { Component } from '@angular/core' 6 | import { Router, ActivatedRoute, NavigationEnd } from '@angular/router' 7 | import { queryString, setLocation, isMobile, getDefaultTheme } from '../utils' 8 | import { en_US, NzI18nService, zh_CN } from 'ng-zorro-antd/i18n' 9 | import { getLocale } from 'src/locale' 10 | import { settings } from 'src/store' 11 | import { verifyToken, getContentes } from 'src/api' 12 | import { getToken, userLogout, isLogin } from 'src/utils/user' 13 | import { NzMessageService } from 'ng-zorro-antd/message' 14 | import { NzNotificationService } from 'ng-zorro-antd/notification' 15 | import { fetchWeb } from 'src/utils/web' 16 | import { isSelfDevelop } from 'src/utils/util' 17 | import { routes } from './app-routing.module' 18 | import Alert from './alert-event' 19 | import event from 'src/utils/mitt' 20 | 21 | @Component({ 22 | selector: 'app-xiejiahe', 23 | templateUrl: './app.component.html', 24 | styleUrls: ['./app.component.scss'], 25 | }) 26 | export class AppComponent { 27 | isLogin: boolean = isLogin 28 | fetchIng = true 29 | 30 | constructor( 31 | private router: Router, 32 | private activatedRoute: ActivatedRoute, 33 | private i18n: NzI18nService, 34 | private message: NzMessageService, 35 | private notification: NzNotificationService 36 | ) { 37 | new Alert(message, notification) 38 | 39 | this.router.events.subscribe((event) => { 40 | if (event instanceof NavigationEnd) { 41 | this.updateDocumentTitle() 42 | } 43 | }) 44 | } 45 | 46 | updateDocumentTitle() { 47 | const url = this.router.url.split('?')[0].slice(1) 48 | const theme = (url === '' ? settings.theme : url).toLowerCase() 49 | const title = settings[`${theme}DocTitle`] 50 | document.title = title || window.__TITLE__ || settings.title 51 | } 52 | 53 | ngOnInit() { 54 | this.goRoute() 55 | this.activatedRoute.queryParams.subscribe(setLocation) 56 | 57 | if (getLocale() === 'zh-CN') { 58 | this.i18n.setLocale(zh_CN) 59 | } else { 60 | this.i18n.setLocale(en_US) 61 | } 62 | 63 | const token = getToken() 64 | if (token) { 65 | verifyToken(token) 66 | .then((res) => { 67 | const data = res.data || {} 68 | if (!settings.email && data.email) { 69 | settings.email = data.email 70 | } 71 | event.emit('GITHUB_USER_INFO', data) 72 | }) 73 | .catch(() => { 74 | userLogout() 75 | setTimeout(() => { 76 | location.reload() 77 | }, 1000) 78 | }) 79 | } 80 | 81 | if (isSelfDevelop) { 82 | getContentes().then(() => { 83 | // 处理默认主题 84 | const currentRoutes = this.router.config 85 | const defaultTheme = getDefaultTheme().toLowerCase() 86 | const hasDefault = routes.find((item) => item.path === defaultTheme) 87 | const isHome = this.router.url.split('?')[0] === '/' 88 | if (hasDefault) { 89 | this.router.resetConfig([ 90 | ...currentRoutes, 91 | { 92 | ...hasDefault, 93 | path: '**', 94 | }, 95 | ]) 96 | } 97 | if (isHome) { 98 | this.router.navigate([defaultTheme]) 99 | } 100 | this.updateDocumentTitle() 101 | this.fetchIng = false 102 | event.emit('WEB_FINISH') 103 | window.__FINISHED__ = true 104 | }) 105 | } else { 106 | fetchWeb().finally(() => { 107 | this.fetchIng = false 108 | }) 109 | } 110 | } 111 | 112 | goRoute() { 113 | // is App 114 | if (settings.appTheme !== 'Current' && isMobile()) { 115 | const url = (this.router.url.split('?')[0] || '').toLowerCase() 116 | const { page, id, q } = queryString() 117 | const queryParams = { page, id, q } 118 | const path = '/' + String(settings.appTheme).toLowerCase() 119 | 120 | if (!url.includes(path)) { 121 | this.router.navigate([path], { queryParams }) 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/view/shortcut/index.component.ts: -------------------------------------------------------------------------------- 1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。 2 | // Copyright @ 2018-present xiejiahe. All rights reserved. 3 | 4 | import { Component } from '@angular/core' 5 | import { isDark as isDarkFn, getDateTime, isMobile } from 'src/utils' 6 | import { settings } from 'src/store' 7 | import { IWebProps } from 'src/types' 8 | import { JumpService } from 'src/services/jump' 9 | import { $t } from 'src/locale' 10 | import event from 'src/utils/mitt' 11 | 12 | @Component({ 13 | selector: 'app-shortcut', 14 | templateUrl: './index.component.html', 15 | styleUrls: ['./index.component.scss'], 16 | }) 17 | export default class ShortcutComponent { 18 | $t = $t 19 | settings = settings 20 | isMobile = isMobile() 21 | isDark: boolean = isDarkFn() 22 | shortcutThemeImage = settings.shortcutThemeImages?.[0]?.['src'] 23 | timer: any = null 24 | month = 0 25 | date = 0 26 | hours = '' 27 | minutes = '' 28 | seconds = '' 29 | dayText = '' 30 | dockList: IWebProps[] = [] 31 | iconSize: number = 0 32 | frameLoad = false 33 | 34 | constructor(public jumpService: JumpService) { 35 | event.on('EVENT_DARK', (isDark: any) => { 36 | this.isDark = isDark 37 | }) 38 | event.on('DOCK_LIST', (dockList: any) => { 39 | this.dockList = dockList 40 | }) 41 | this.getDateTime() 42 | } 43 | 44 | ngOnInit() { 45 | document.addEventListener('visibilitychange', (e: any) => { 46 | const hide = e.target.hidden 47 | if (hide) { 48 | clearTimeout(this.timer) 49 | } else { 50 | this.getDateTime() 51 | } 52 | }) 53 | } 54 | 55 | handleMouseLeave(e: any) { 56 | try { 57 | const imgs = e.currentTarget.querySelectorAll('.common-icon') 58 | if (this.iconSize !== 0) { 59 | imgs.forEach((el: HTMLImageElement) => { 60 | el.style.width = `${this.iconSize}px` 61 | el.style.height = `${this.iconSize}px` 62 | }) 63 | } 64 | } catch (error) {} 65 | } 66 | 67 | handleMouseOver(e: any) { 68 | if (this.isMobile) { 69 | return 70 | } 71 | 72 | try { 73 | const imgs = e.currentTarget.querySelectorAll('.common-icon') 74 | if (!imgs.length) { 75 | return 76 | } 77 | 78 | const nodeName = e.target.nodeName 79 | if (nodeName === 'APP-LOGO' || nodeName === 'div') { 80 | if (this.iconSize === 0) { 81 | this.iconSize = imgs[0].clientWidth 82 | } 83 | const index = Number(e.target.dataset.index) 84 | imgs.forEach((el: HTMLImageElement) => { 85 | el.style.width = `${this.iconSize}px` 86 | el.style.height = `${this.iconSize}px` 87 | }) 88 | const largeSize = this.iconSize * 1.4 89 | imgs[index].style.width = `${largeSize}px` 90 | imgs[index].style.height = `${largeSize}px` 91 | const middleSize = this.iconSize * 1.2 92 | const smallSize = this.iconSize * 1.04 93 | if (imgs[index - 1]) { 94 | imgs[index - 1].style.width = `${middleSize}px` 95 | imgs[index - 1].style.height = `${middleSize}px` 96 | } 97 | if (imgs[index - 2]) { 98 | imgs[index - 2].style.width = `${smallSize}px` 99 | imgs[index - 2].style.height = `${smallSize}px` 100 | } 101 | if (imgs[index + 1]) { 102 | imgs[index + 1].style.width = `${middleSize}px` 103 | imgs[index + 1].style.height = `${middleSize}px` 104 | } 105 | if (imgs[index + 2]) { 106 | imgs[index + 2].style.width = `${smallSize}px` 107 | imgs[index + 2].style.height = `${smallSize}px` 108 | } 109 | } 110 | } catch (error) {} 111 | } 112 | 113 | getDateTime() { 114 | this.timer = setTimeout(() => { 115 | this.getDateTime() 116 | }, 1000) 117 | const { hours, minutes, seconds, month, date, dayText } = getDateTime() 118 | this.hours = hours 119 | this.minutes = minutes 120 | this.seconds = seconds 121 | this.month = month 122 | this.date = date 123 | this.dayText = dayText 124 | } 125 | 126 | ngOnDestroy() { 127 | clearTimeout(this.timer) 128 | } 129 | 130 | trackByItemWeb(a: any, item: any) { 131 | return item.id 132 | } 133 | 134 | iframeLoad() { 135 | this.frameLoad = true 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/view/sim/index.component.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 1280px) { 2 | .sim { 3 | --width: 1000px !important; 4 | } 5 | } 6 | @media (max-width: 1080px) { 7 | .sim { 8 | --width: 900px !important; 9 | .bootstrap { 10 | --titleSize: 32px; 11 | --textSize: 14px; 12 | } 13 | } 14 | } 15 | @media (max-width: 980px) { 16 | .sim { 17 | --width: 768px !important; 18 | .bootstrap { 19 | --titleSize: 26px; 20 | --textSize: 12px; 21 | } 22 | } 23 | } 24 | @media (max-width: 800px) { 25 | .bar { 26 | display: none; 27 | } 28 | 29 | .wrapper { 30 | flex-direction: column; 31 | } 32 | .top-nav { 33 | z-index: 9; 34 | background-color: #fff; 35 | position: sticky; 36 | top: 0; 37 | left: 0; 38 | user-select: none; 39 | } 40 | .sidebar { 41 | position: relative !important; 42 | top: 0 !important; 43 | width: 100% !important; 44 | } 45 | .sim { 46 | --width: 100% !important; 47 | } 48 | } 49 | 50 | @media (max-width: 768px) { 51 | .search-sm { 52 | display: block; 53 | } 54 | } 55 | 56 | .sim-bg { 57 | z-index: -1; 58 | position: fixed; 59 | top: 0; 60 | left: 0; 61 | right: 0; 62 | bottom: 0; 63 | background: #f0f2f5; 64 | } 65 | 66 | .top-nav { 67 | max-width: var(--width); 68 | width: 100%; 69 | margin: 10px auto; 70 | padding: 10px; 71 | overflow: none; 72 | overflow-x: auto; 73 | white-space: nowrap; 74 | border-bottom: 1px solid #eee; 75 | text-align: center; 76 | 77 | ::ng-deep .more-btn, 78 | .over-item { 79 | position: relative; 80 | display: inline-block; 81 | padding: 10px 15px; 82 | color: #666; 83 | cursor: pointer; 84 | overflow: hidden; 85 | &.active::after { 86 | content: ''; 87 | position: absolute; 88 | bottom: 0; 89 | left: 20%; 90 | right: 20%; 91 | height: 3px; 92 | border-radius: 2px; 93 | background-color: rgb(16, 142, 233); 94 | } 95 | } 96 | .over-item { 97 | &:hover { 98 | color: #000; 99 | } 100 | } 101 | } 102 | 103 | .sim { 104 | --width: 1200px; 105 | display: flex; 106 | flex-direction: column; 107 | min-height: 100vh; 108 | 109 | .wallpaper { 110 | position: relative; 111 | background-color: #fff; 112 | .bar { 113 | z-index: 10; 114 | position: absolute; 115 | max-width: 600px; 116 | top: 50%; 117 | left: 50%; 118 | transform: translate(-50%, -50%); 119 | } 120 | 121 | .title { 122 | color: #fff; 123 | margin-bottom: 10px; 124 | font-size: var(--titleSize, 36px); 125 | text-align: center; 126 | line-height: 1.3; 127 | user-select: none; 128 | pointer-events: none; 129 | white-space: nowrap; 130 | } 131 | 132 | .description { 133 | text-align: center; 134 | color: #fff; 135 | margin-bottom: 10px; 136 | font-weight: bold; 137 | font-size: var(--textSize, 16px); 138 | white-space: pre-wrap; 139 | user-select: none; 140 | pointer-events: none; 141 | } 142 | } 143 | 144 | .sim-component { 145 | width: var(--width); 146 | margin: 0 auto; 147 | } 148 | 149 | .wrapper { 150 | flex: 1; 151 | position: relative; 152 | width: var(--width); 153 | padding-bottom: 50px; 154 | margin: 0 auto; 155 | display: flex; 156 | column-gap: 30px; 157 | row-gap: 20px; 158 | 159 | .sidebar { 160 | position: sticky; 161 | top: 20px; 162 | left: 0; 163 | width: 180px; 164 | height: min-content; 165 | padding: 10px 0; 166 | text-align: center; 167 | background-color: #fff; 168 | box-shadow: 0 0 1px #ccc; 169 | max-height: calc(100vh - 50px); 170 | overflow-y: auto; 171 | border-radius: 4px; 172 | .ripple-btn { 173 | padding: 14px 0; 174 | cursor: pointer; 175 | border-radius: 5px; 176 | transition: 0.1s linear; 177 | user-select: none; 178 | font-weight: 500; 179 | &:hover { 180 | background-color: rgba(0, 0, 0, 0.03); 181 | } 182 | &.active { 183 | color: #409eff; 184 | background-color: rgb(231, 241, 253); 185 | } 186 | } 187 | } 188 | 189 | .site-box { 190 | flex: 1; 191 | position: relative; 192 | background-color: #f9f9f9; 193 | padding: 15px; 194 | border-radius: 4px; 195 | } 196 | } 197 | } 198 | --------------------------------------------------------------------------------