├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .npmrc
├── .postcssrc.json
├── .prettierrc.json
├── LICENSE
├── README.md
├── README_EN.md
├── angular.json
├── data
├── component.json
├── db.json
├── internal.json
├── search.json
├── settings.json
└── tag.json
├── nav.config.yaml
├── netlify.toml
├── ngsw-config.json
├── package.json
├── pnpm-lock.yaml
├── public
├── icons
│ ├── icon-192x192.png
│ └── icon-512x512.png
├── manifest.webmanifest
└── readme.md
├── scripts
├── build.ts
├── db.ts
├── loading.ts
├── start.ts
└── utils.ts
├── src
├── api
│ └── index.ts
├── app
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.config.ts
│ └── app.routes.ts
├── assets
│ ├── fonts
│ │ ├── Amiko-Regular.ttf
│ │ ├── iconfont.css
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── iconfont.woff2
│ ├── img
│ │ ├── china.svg
│ │ ├── light.svg
│ │ ├── sync.svg
│ │ ├── theme.svg
│ │ └── ua.svg
│ └── styles
│ │ ├── dark.scss
│ │ ├── nprogress.css
│ │ └── tailwind.css
├── components
│ ├── breadcrumb
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── calendar
│ │ ├── drawer
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── card
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── class-tabs
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── component-group
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── countdown
│ │ ├── drawer
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── create-web
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── delete-modal
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── edit-class
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── fixbar
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── footer
│ │ ├── footer.component.html
│ │ ├── footer.component.scss
│ │ ├── footer.component.ts
│ │ └── template.ts
│ ├── holiday
│ │ ├── drawer
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── html
│ │ ├── drawer
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── icon-git
│ │ ├── icon-git.component.html
│ │ ├── icon-git.component.scss
│ │ └── icon-git.component.ts
│ ├── image
│ │ ├── drawer
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── loading
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── login
│ │ ├── login.component.html
│ │ ├── login.component.scss
│ │ └── login.component.ts
│ ├── logo
│ │ ├── logo.component.html
│ │ ├── logo.component.scss
│ │ └── logo.component.ts
│ ├── move-web
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── news
│ │ ├── drawer
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ ├── index.component.ts
│ │ └── types.ts
│ ├── no-data
│ │ ├── no-data.component.html
│ │ ├── no-data.component.scss
│ │ └── no-data.component.ts
│ ├── off-work
│ │ ├── drawer
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── phone-class
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── runtime
│ │ ├── drawer
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── search
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ ├── index.component.ts
│ │ └── index.ts
│ ├── side-images
│ │ ├── image
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── swiper
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ ├── index.component.ts
│ │ └── swiper-item
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ ├── tag-list
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── toolbar-title
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── upload-file
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── upload-image
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ ├── web-list
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
│ └── web-more-menu
│ │ ├── index.component.html
│ │ ├── index.component.scss
│ │ └── index.component.ts
├── constants
│ ├── index.ts
│ └── symbol.ts
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── locale
│ ├── english.ts
│ ├── index.ts
│ └── zh_CN.ts
├── main.html
├── main.ts
├── pipe
│ └── safeHtml.pipe.ts
├── services
│ ├── common.ts
│ └── jump.ts
├── store
│ └── index.ts
├── styles.scss
├── types
│ ├── index.ts
│ └── type.d.ts
├── utils
│ ├── bookmark.ts
│ ├── http.ts
│ ├── index.ts
│ ├── mitt.ts
│ ├── pureUtils.ts
│ ├── sw.ts
│ ├── user.ts
│ ├── utils.ts
│ └── web.ts
└── view
│ ├── light
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── mobile
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── shortcut
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── side
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── sim
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── super
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ └── system
│ ├── auth
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── bookmark-export
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── bookmark
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── collect
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── component
│ ├── index.component.html
│ ├── index.component.scss
│ ├── index.component.ts
│ └── types.ts
│ ├── config
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── index.component.html
│ ├── index.component.scss
│ ├── index.component.ts
│ ├── info
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── search
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── setting
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ ├── tag
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
│ └── web
│ ├── index.component.html
│ ├── index.component.scss
│ └── index.component.ts
├── tailwind.config.js
├── tsconfig.app.json
├── tsconfig.json
└── vercel.json
/.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 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | data/
2 | main.ts
3 | polyfills.ts
4 | test.ts
5 | environments/
6 | assets/
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build web
2 | on:
3 | push:
4 | branches:
5 | - master
6 | - main
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3
13 | with:
14 | persist-credentials: false
15 | - name: Set Node.js
16 | uses: actions/setup-node@v3
17 | with:
18 | node-version: 22.x
19 | - name: Install pnpm
20 | uses: pnpm/action-setup@v4
21 | with:
22 | version: 10
23 | - name: Install
24 | run: |
25 | pnpm install
26 | # dist/404.html: gh-pages history mode
27 | - name: Build
28 | run: |
29 | npm run build-gh-pages
30 | cp dist/browser/index.html dist/browser/404.html
31 | - name: Deploy
32 | uses: JamesIves/github-pages-deploy-action@v4
33 | with:
34 | token: ${{ secrets.TOKEN }}
35 | branch: gh-pages
36 | folder: dist/browser
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode
25 | .history/*
26 |
27 | # Miscellaneous
28 | /.angular/cache
29 | .sass-cache/
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | testem.log
34 | /typings
35 |
36 | # System files
37 | .DS_Store
38 | Thumbs.db
39 |
40 | # build output
41 | src/index.html
42 | nav.config.json
43 | _upload
44 | test.md
45 | data/collect.json
46 | data/serverdb.json
47 | data/news.json
48 | data/backup.json
49 | /.dockerignore
50 | /Dockerfile
51 | .next
52 |
53 | log/
54 | logs/
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
--------------------------------------------------------------------------------
/.postcssrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": {
3 | "@tailwindcss/postcss": {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/data/component.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/data/component.json
--------------------------------------------------------------------------------
/data/db.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/data/db.json
--------------------------------------------------------------------------------
/data/internal.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/data/search.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/data/search.json
--------------------------------------------------------------------------------
/data/settings.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/data/tag.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/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 | # 免责声明:使用即表示已阅读 https://official.nav3.cn/disclaimers
9 |
10 | # 仓库地址,填写 Fork 后的仓库地址,支持 Gitee / GitHub / GitLab
11 | # 作者 Gitee 仓库地址 https://gitee.com/xiejiahe/nav
12 | # 作者 GitLab 仓库地址 https://gitlab.com/xjh22222228/nav?projectId=68880543, projectId 是项目 ID,可在仓库页面中找到
13 | gitRepoUrl: https://github.com/xjh22222228/nav
14 |
15 | # Fork 部署分支
16 | branch: main
17 |
18 | # 默认Fork仓库
19 | # 仓库必须自己提前创建,如果打算长期使用本软件,建议每个人都创建一个新的仓库作为图片存储
20 | # e.g 'https://github.com/xjh22222228/image?branch=main'
21 | # gitlab e.g 'https://gitlab.com/xjh22222228/image?branch=main&projectId=1001'
22 | imageRepoUrl: ''
23 |
24 | # 只有GitHub pages用户需要设置为true, 其他建议设置为 false
25 | hashMode: true
26 |
27 | # 一旦填写认为你是自有部署,Fork 不要填写
28 | address: ''
29 |
30 | # 自有部署后台登录密码
31 | password: admin
32 |
33 | # 自有部署启动端口
34 | port: 7777
35 |
36 | # 收录通知邮箱
37 | email: ''
38 |
39 | # 自有部署邮件通知系统
40 | mailConfig:
41 | host: smtp.mxhichina.com
42 | port: 465
43 | secure: true
44 | auth:
45 | user: ''
46 | pass: ''
47 | title: 网站收录通知
48 | message: 有用户提交网站收录啦
49 |
50 | # 自有部署
51 | XFAPIPassword: ''
52 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [[redirects]]
2 | from = "/*"
3 | to = "/index.html"
4 | status = 200
5 | [build.environment]
6 | NODE_VERSION = "22"
7 | [build]
8 | command = "npm run build"
9 | publish = "dist/browser"
10 | [[headers]]
11 | for = "/api/*"
12 | [headers.values]
13 | Access-Control-Allow-Origin = "*"
14 | [[headers]]
15 | for = "/manifest.webmanifest"
16 | [headers.values]
17 | Content-Type = "application/manifest+json"
--------------------------------------------------------------------------------
/ngsw-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json",
3 | "index": "/index.html",
4 | "assetGroups": [
5 | {
6 | "name": "app",
7 | "installMode": "prefetch",
8 | "resources": {
9 | "files": [
10 | "/favicon.ico",
11 | "/index.csr.html",
12 | "/index.html",
13 | "/manifest.webmanifest",
14 | "/*.css",
15 | "/*.js"
16 | ]
17 | }
18 | },
19 | {
20 | "name": "assets",
21 | "installMode": "lazy",
22 | "updateMode": "prefetch",
23 | "resources": {
24 | "files": [
25 | "/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
26 | ]
27 | }
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nav",
3 | "version": "17.0.0",
4 | "author": "xiejiahe",
5 | "homepage": "https://github.com/xjh22222228/nav",
6 | "scripts": {
7 | "format": "prettier --write \"**/*.{ts,cjs,mjs}\"",
8 | "start": "npm run init && ng serve --host 0.0.0.0 --port=7001 --live-reload=false",
9 | "start:reload": "npm run init && ng serve --host 0.0.0.0 --port=7001",
10 | "init": "tsx ./scripts/start.ts",
11 | "setup": "npm run init && tsx ./scripts/build.ts",
12 | "build-gh-pages": "npm run setup && ng build --base-href ./ --index src/index.html",
13 | "build": "npm run setup && ng build --index src/index.html",
14 | "watch": "ng build --watch --configuration development",
15 | "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",
16 | "pull": "git pull"
17 | },
18 | "lint-staged": {
19 | "*.{js,jsx,ts,tsx,json,css,scss,md}": "npm run format"
20 | },
21 | "type": "module",
22 | "private": true,
23 | "dependencies": {
24 | "@angular/animations": "^19.2.11",
25 | "@angular/common": "^19.2.11",
26 | "@angular/compiler": "^19.2.11",
27 | "@angular/core": "^19.2.11",
28 | "@angular/forms": "^19.2.11",
29 | "@angular/platform-browser": "^19.2.11",
30 | "@angular/platform-browser-dynamic": "^19.2.11",
31 | "@angular/router": "^19.2.11",
32 | "@angular/service-worker": "^19.2.11",
33 | "axios": "^1.9.0",
34 | "chrome-finder": "^1.0.7",
35 | "cron": "^4.3.0",
36 | "dayjs": "^1.11.13",
37 | "express": "^5.1.0",
38 | "file-saver": "^2.0.5",
39 | "js-base64": "^3.7.7",
40 | "js-yaml": "^4.1.0",
41 | "localforage": "^1.10.0",
42 | "lz-string": "^1.5.0",
43 | "mitt": "^3.0.1",
44 | "ng-zorro-antd": "^19.2.2",
45 | "nprogress": "^0.2.0",
46 | "puppeteer": "^24.8.2",
47 | "qs": "^6.14.0",
48 | "rough-notation": "^0.5.1",
49 | "rxjs": "~7.8.2",
50 | "sharp": "^0.34.1",
51 | "zone.js": "~0.15.0"
52 | },
53 | "devDependencies": {
54 | "@angular-devkit/build-angular": "^19.2.12",
55 | "@angular/cli": "^19.2.12",
56 | "@angular/compiler-cli": "^19.2.11",
57 | "@tailwindcss/postcss": "^4.1.7",
58 | "@types/compression": "^1.7.5",
59 | "@types/file-saver": "^2.0.7",
60 | "@types/jasmine": "~5.1.8",
61 | "@types/js-yaml": "^4.0.9",
62 | "@types/nodemailer": "^6.4.17",
63 | "@types/nprogress": "^0.2.3",
64 | "@types/qs": "^6.14.0",
65 | "body-parser": "^2.2.0",
66 | "compression": "^1.8.0",
67 | "connect-history-api-fallback": "^2.0.0",
68 | "cors": "^2.8.5",
69 | "info-web": "^0.0.46",
70 | "jasmine-core": "~5.7.1",
71 | "lint-staged": "^16.0.0",
72 | "nodemailer": "^7.0.3",
73 | "pm2": "^6.0.6",
74 | "postcss": "^8.5.3",
75 | "prettier": "^3.5.3",
76 | "tailwindcss": "^4.1.7",
77 | "tslib": "^2.8.1",
78 | "tsx": "^4.19.4",
79 | "typescript": "~5.8.3"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/public/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/public/icons/icon-192x192.png
--------------------------------------------------------------------------------
/public/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/public/icons/icon-512x512.png
--------------------------------------------------------------------------------
/public/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "发现导航",
3 | "short_name": "发现导航",
4 | "theme_color": "#ffffff",
5 | "background_color": "#ffffff",
6 | "display": "standalone",
7 | "scope": "./",
8 | "start_url": "./",
9 | "icons": [
10 | {
11 | "src": "icons/icon-192x192.png",
12 | "sizes": "192x192",
13 | "type": "image/png",
14 | "purpose": "maskable any"
15 | },
16 | {
17 | "src": "icons/icon-512x512.png",
18 | "sizes": "512x512",
19 | "type": "image/png",
20 | "purpose": "maskable any"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/public/readme.md:
--------------------------------------------------------------------------------
1 | https://github.com/xjh22222228/nav
2 |
--------------------------------------------------------------------------------
/scripts/build.ts:
--------------------------------------------------------------------------------
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 dayjs from 'dayjs'
7 | import utc from 'dayjs/plugin/utc.js'
8 | import timezone from 'dayjs/plugin/timezone.js'
9 | import {
10 | writeSEO,
11 | writeTemplate,
12 | spiderWebs,
13 | PATHS,
14 | getConfig,
15 | fileWriteStream,
16 | writePWA,
17 | } from './utils'
18 | import type { INavProps, ISettings } from '../src/types/index'
19 |
20 | dayjs.extend(utc)
21 | dayjs.extend(timezone)
22 | dayjs.tz.setDefault('Asia/Shanghai')
23 |
24 | const config = getConfig()
25 |
26 | const handleFileOperation = (operation: () => any): any => {
27 | try {
28 | return operation()
29 | } catch (error) {
30 | console.error(`File operation failed: ${(error as Error).message}`)
31 | return null
32 | }
33 | }
34 |
35 | const db: INavProps[] = handleFileOperation(() =>
36 | JSON.parse(fs.readFileSync(PATHS.db, 'utf-8')),
37 | )
38 | const settings: ISettings = handleFileOperation(() =>
39 | JSON.parse(fs.readFileSync(PATHS.settings, 'utf-8')),
40 | )
41 |
42 | const seoTemplate = writeSEO(db, { settings })
43 | const html = writeTemplate({
44 | html: fs.readFileSync(PATHS.html.main, 'utf-8'),
45 | settings,
46 | seoTemplate,
47 | })
48 |
49 | handleFileOperation(() => fs.writeFileSync(PATHS.html.write, html))
50 | handleFileOperation(() =>
51 | writePWA(
52 | {
53 | ...settings,
54 | pwaIcon: '',
55 | },
56 | PATHS.manifestPublic,
57 | ),
58 | )
59 |
60 | let errorUrlCount = 0
61 |
62 | process.on('exit', async () => {
63 | settings.errorUrlCount = errorUrlCount
64 | fs.writeFileSync(PATHS.settings, JSON.stringify(settings))
65 | await fileWriteStream(PATHS.db, config.address ? [] : db)
66 |
67 | if (config.address) {
68 | await fileWriteStream(PATHS.serverdb, db)
69 | }
70 | console.log('All success!')
71 | })
72 |
73 | const { errorUrlCount: count } = await spiderWebs(db, settings)
74 | errorUrlCount = count
75 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | .fetchIng {
2 | height: 100vh;
3 | }
4 |
--------------------------------------------------------------------------------
/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'
2 | import { registerLocaleData } from '@angular/common'
3 | import zh from '@angular/common/locales/zh'
4 | import { provideRouter, withHashLocation } from '@angular/router'
5 | import { provideNzIcons } from 'ng-zorro-antd/icon'
6 | import { IconDefinition } from '@ant-design/icons-angular'
7 | import { routes } from './app.routes'
8 | import {
9 | CheckOutline,
10 | CopyOutline,
11 | ShareAltOutline,
12 | EllipsisOutline,
13 | LoadingOutline,
14 | UploadOutline,
15 | MinusOutline,
16 | PlusOutline,
17 | StopOutline,
18 | MenuFoldOutline,
19 | MenuUnfoldOutline,
20 | DoubleRightOutline,
21 | DoubleLeftOutline,
22 | } from '@ant-design/icons-angular/icons'
23 | import { provideAnimations } from '@angular/platform-browser/animations'
24 | import { NZ_I18N } from 'ng-zorro-antd/i18n'
25 | import { zh_CN } from 'ng-zorro-antd/i18n'
26 | import config from '../../nav.config.json'
27 | import { provideServiceWorker } from '@angular/service-worker'
28 | import { isMobile } from 'src/utils'
29 | import { unregisterServiceWorkers } from 'src/utils/sw'
30 |
31 | registerLocaleData(zh)
32 |
33 | const icons: IconDefinition[] = [
34 | CheckOutline,
35 | CopyOutline,
36 | ShareAltOutline,
37 | EllipsisOutline,
38 | LoadingOutline,
39 | UploadOutline,
40 | MinusOutline,
41 | PlusOutline,
42 | StopOutline,
43 | MenuFoldOutline,
44 | MenuUnfoldOutline,
45 | DoubleRightOutline,
46 | DoubleLeftOutline,
47 | ]
48 |
49 | const isPhone = isMobile()
50 | const isHashMode = window.__HASH_MODE__ ?? config.hashMode
51 | const pwaEnable =
52 | window.__PWA_ENABLE__ && location.protocol === 'https:' ? isPhone : false
53 |
54 | if (!pwaEnable && isPhone) {
55 | unregisterServiceWorkers()
56 | }
57 |
58 | export const appConfig: ApplicationConfig = {
59 | providers: [
60 | provideNzIcons(icons),
61 | provideZoneChangeDetection({ eventCoalescing: true }),
62 | isHashMode
63 | ? provideRouter(routes, withHashLocation())
64 | : provideRouter(routes),
65 | provideAnimations(),
66 | { provide: NZ_I18N, useValue: zh_CN },
67 | provideServiceWorker('ngsw-worker.js', {
68 | enabled: pwaEnable,
69 | registrationStrategy: 'registerWhenStable:30000',
70 | }),
71 | ],
72 | }
73 |
--------------------------------------------------------------------------------
/src/assets/fonts/Amiko-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/assets/fonts/Amiko-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "iconfont"; /* Project id 2267418 */
3 | src: url('iconfont.woff2?t=1743153395328') format('woff2'),
4 | url('iconfont.woff?t=1743153395328') format('woff'),
5 | url('iconfont.ttf?t=1743153395328') 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 | .iconyinru:before {
17 | content: "\e625";
18 | }
19 |
20 | .iconxiaolian:before {
21 | content: "\e64b";
22 | }
23 |
24 | .iconxiaolian-02:before {
25 | content: "\e631";
26 | }
27 |
28 | .icongengduo:before {
29 | content: "\e641";
30 | }
31 |
32 | .iconweibiaoti14:before {
33 | content: "\e620";
34 | }
35 |
36 | .iconcopy:before {
37 | content: "\e617";
38 | }
39 |
40 | .iconfenxiang:before {
41 | content: "\e606";
42 | }
43 |
44 | .iconunlock:before {
45 | content: "\e63d";
46 | }
47 |
48 | .iconwinfo-icon-tongbu:before {
49 | content: "\e632";
50 | }
51 |
52 | .iconchuangjian:before {
53 | content: "\e635";
54 | }
55 |
56 | .iconweibiaoti25:before {
57 | content: "\e62b";
58 | }
59 |
60 | .iconjiantouarrow483:before {
61 | content: "\e695";
62 | }
63 |
64 | .iconsousuo:before {
65 | content: "\e6b9";
66 | }
67 |
68 | .icondark:before {
69 | content: "\e666";
70 | }
71 |
72 | .iconA:before {
73 | content: "\e6e5";
74 | }
75 |
76 | .iconbi:before {
77 | content: "\e742";
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/assets/fonts/iconfont.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/assets/fonts/iconfont.woff
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/assets/fonts/iconfont.woff2
--------------------------------------------------------------------------------
/src/assets/img/china.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/img/light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/img/sync.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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: #12171e !important;
30 | }
31 |
32 | &-bg-gary {
33 | background-color: #21262d !important;
34 | }
35 |
36 | &-bg-gary2 {
37 | background-color: rgb(53, 54, 58) !important;
38 | }
39 |
40 | &-bg-deep {
41 | background-color: #000 !important;
42 | }
43 |
44 | &-text {
45 | color: #969ca4 !important;
46 | }
47 |
48 | &-text-active {
49 | color: #c9d1d9 !important;
50 | font-weight: 600;
51 | }
52 |
53 | &-border-color {
54 | border-color: #30363d !important;
55 |
56 | &:after,
57 | &:before {
58 | border-color: transparent !important;
59 | }
60 | }
61 |
62 | &-border-color2 {
63 | border-color: rgba(255, 255, 255, 0.05) !important;
64 | }
65 |
66 | &-item-active {
67 | background-color: #21262d !important;
68 | border-color: #21262d !important;
69 | color: #c9d1d9 !important;
70 | font-weight: 600;
71 | }
72 |
73 | @media (any-hover: hover) {
74 | &-hover {
75 | &:hover:not(.active) {
76 | background-color: #21262d !important;
77 | }
78 | }
79 | &-hover-text {
80 | &:hover {
81 | color: #fff !important;
82 | }
83 | }
84 | &-hover-bg {
85 | &:hover {
86 | background-color: #30363d !important;
87 | }
88 | }
89 |
90 | &-item-hover:hover {
91 | background-color: #30363d !important;
92 | border: 1px solid #8b949e !important;
93 | }
94 |
95 | // fix-bar
96 | &-action-hover:hover {
97 | background-color: #c9d1d9 !important;
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/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/assets/styles/tailwind.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 |
--------------------------------------------------------------------------------
/src/components/breadcrumb/index.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{ text
4 | }}/
8 |
9 |
--------------------------------------------------------------------------------
/src/components/breadcrumb/index.component.scss:
--------------------------------------------------------------------------------
1 | .breadcrumb1 {
2 | font-size: 12px;
3 | color: #666;
4 | .slash {
5 | margin: 0 4px;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/breadcrumb/index.component.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 | import { Component, ChangeDetectionStrategy, Input } from '@angular/core'
5 | import { CommonModule } from '@angular/common'
6 | import type { IWebProps } from 'src/types'
7 |
8 | @Component({
9 | standalone: true,
10 | imports: [CommonModule],
11 | changeDetection: ChangeDetectionStrategy.OnPush,
12 | selector: 'app-breadcrumb',
13 | templateUrl: './index.component.html',
14 | styleUrls: ['./index.component.scss'],
15 | })
16 | export class BreadcrumbComponent {
17 | @Input() data!: IWebProps
18 |
19 | constructor() {}
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/calendar/drawer/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
34 |
35 |
36 |
37 |
40 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/components/calendar/drawer/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/calendar/drawer/index.component.scss
--------------------------------------------------------------------------------
/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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
7 | import { $t } from 'src/locale'
8 | import { FormBuilder, FormGroup } from '@angular/forms'
9 | import { NzDrawerModule } from 'ng-zorro-antd/drawer'
10 | import { NzFormModule } from 'ng-zorro-antd/form'
11 | import { NzButtonModule } from 'ng-zorro-antd/button'
12 | import { NzInputModule } from 'ng-zorro-antd/input'
13 | import { NzColorPickerModule } from 'ng-zorro-antd/color-picker'
14 |
15 | @Component({
16 | standalone: true,
17 | imports: [
18 | FormsModule,
19 | ReactiveFormsModule,
20 | NzDrawerModule,
21 | NzFormModule,
22 | NzButtonModule,
23 | NzInputModule,
24 | NzColorPickerModule,
25 | ],
26 | selector: 'calendar-drawer',
27 | templateUrl: './index.component.html',
28 | styleUrls: ['./index.component.scss'],
29 | })
30 | export class CalendarDrawerComponent {
31 | @Output() ok = new EventEmitter()
32 |
33 | $t = $t
34 | visible = false
35 | validateForm!: FormGroup
36 | index = 0
37 |
38 | constructor(private fb: FormBuilder) {
39 | this.validateForm = this.fb.group({
40 | topColor: [''],
41 | bgColor: [''],
42 | })
43 | }
44 |
45 | open(data: any, idx: number) {
46 | this.index = idx
47 | for (const k in data) {
48 | this.validateForm.get(k)!?.setValue(data[k])
49 | }
50 | this.visible = true
51 | }
52 |
53 | handleClose() {
54 | this.visible = false
55 | }
56 |
57 | handleSubmit() {
58 | const values = this.validateForm.value
59 | this.ok.emit({
60 | ...values,
61 | index: this.index,
62 | })
63 | this.handleClose()
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/calendar/index.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ date }}
4 |
5 |
6 |
{{ day }}
7 |
8 | {{ dayOfYear }}
9 | {{ week }}
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/calendar/index.component.scss:
--------------------------------------------------------------------------------
1 | .calendar {
2 | width: 130px;
3 | height: var(--componentHeight);
4 | max-height: 100%;
5 | border-radius: 12px;
6 | text-align: center;
7 | overflow: hidden;
8 | color: #fff;
9 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
10 | display: flex;
11 | flex-direction: column;
12 | .ctop {
13 | font-size: 17px;
14 | height: 40px;
15 | line-height: 0;
16 | display: flex;
17 | align-items: center;
18 | justify-content: center;
19 | font-weight: 500;
20 | }
21 | .box {
22 | flex: 1;
23 | }
24 | .cday {
25 | font-weight: bold;
26 | font-size: 40px;
27 | line-height: 1;
28 | padding: 24px 0 5px 0;
29 | }
30 | .cdate {
31 | color: rgba(255, 255, 255, 0.7);
32 | font-size: 13px;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/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 type { IComponentItemProps } from 'src/types'
8 | import { $t } from 'src/locale'
9 | import { component } from 'src/store'
10 |
11 | @Component({
12 | standalone: true,
13 | selector: 'app-calendar',
14 | templateUrl: './index.component.html',
15 | styleUrls: ['./index.component.scss'],
16 | })
17 | export class CalendarComponent {
18 | @Input() data!: IComponentItemProps
19 |
20 | readonly component = component()
21 | date = ''
22 | day = ''
23 | week = ''
24 | dayOfYear = ''
25 |
26 | constructor() {
27 | const date = getDateTime()
28 | this.date = $t('_calendarDate', { year: date.year, month: date.month })
29 | this.day = date.zeroDate
30 | this.week = date.dayText
31 | this.dayOfYear = $t('_dayOfYear', { day: getDayOfYear() })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/class-tabs/index.component.html:
--------------------------------------------------------------------------------
1 | 1" class="class-tabs dark-bg-gary2" #parent>
2 |
3 |
10 | {{ tab.title }}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/class-tabs/index.component.scss:
--------------------------------------------------------------------------------
1 | .class-tabs {
2 | --height: 28px;
3 | position: relative;
4 | padding: 4px;
5 | border-radius: 8px;
6 | background-color: #ededee;
7 | display: inline-flex;
8 | font-size: 14px;
9 | overflow: hidden;
10 | overflow-x: auto;
11 | max-width: 100%;
12 | white-space: nowrap;
13 | color: #93959a;
14 | user-select: none;
15 | transform: translateZ(0);
16 | .anchor {
17 | position: absolute;
18 | height: var(--height);
19 | background-color: #1890ff !important;
20 | color: #fff;
21 | transition: left 0.15s linear;
22 | border-radius: 6px;
23 | }
24 | .tab {
25 | z-index: 2;
26 | position: relative;
27 | padding: 0 8px;
28 | height: var(--height);
29 | display: flex;
30 | align-items: center;
31 | justify-content: center;
32 | transition: all 0.1s linear;
33 | cursor: pointer;
34 | border-radius: 6px;
35 | &:hover:not(.active) {
36 | background-color: rgba(0, 0, 0, 0.055);
37 | }
38 | &.active {
39 | color: #fff;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/component-group/index.component.html:
--------------------------------------------------------------------------------
1 | 0"
4 | [class.directionCol]="direction === 'column'"
5 | [class.over]="isOver"
6 | [class.showAll]="isShowAll"
7 | [class.!overflow-x-scroll]="isMobile"
8 | >
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/component-group/index.component.scss:
--------------------------------------------------------------------------------
1 | @keyframes zoomInDown {
2 | from {
3 | opacity: 0;
4 | }
5 |
6 | 100% {
7 | opacity: 1;
8 | }
9 | }
10 |
11 | @keyframes arrow {
12 | 0% {
13 | transform: translateX(0);
14 | }
15 | 11% {
16 | transform: translateX(4px);
17 | }
18 | 21% {
19 | transform: translateX(-2px);
20 | }
21 | 31% {
22 | transform: translateX(4px);
23 | }
24 | 41% {
25 | transform: translateX(-2px);
26 | }
27 | 51% {
28 | transform: translateX(0);
29 | }
30 | }
31 |
32 | .component-group {
33 | position: relative;
34 | padding: 10px;
35 | display: flex;
36 | overflow: hidden;
37 | user-select: none;
38 | gap: 15px;
39 | justify-content: flex-start;
40 | max-width: 100%;
41 | &.over {
42 | padding-bottom: 30px;
43 | margin-bottom: 15px;
44 | }
45 | &.showAll {
46 | flex-wrap: wrap;
47 | .arrow {
48 | transform: translateX(-50%) rotate(-90deg) !important;
49 | }
50 | .arrowicon {
51 | animation: none;
52 | }
53 | }
54 | &.directionCol {
55 | flex-wrap: wrap;
56 | justify-content: center;
57 | overflow-y: auto;
58 | ::ng-deep {
59 | > .ng-star-inserted,
60 | .citems {
61 | max-width: 100%;
62 | }
63 | }
64 | }
65 | .arrow {
66 | display: inline-block;
67 | position: absolute;
68 | bottom: 0;
69 | left: 50%;
70 | transform: translate(-50%, 0) rotate(90deg);
71 | cursor: pointer;
72 | transition: all 0.3s;
73 | }
74 | .arrowicon {
75 | font-size: 18px;
76 | animation: arrow 3s ease-in-out infinite;
77 | color: #f38181;
78 | }
79 | ::ng-deep {
80 | > .ng-star-inserted {
81 | animation-duration: 0.5s;
82 | animation-fill-mode: both;
83 | animation-name: zoomInDown;
84 | }
85 | @for $i from 2 through 20 {
86 | > .ng-star-inserted:nth-child(#{$i}) {
87 | animation-delay: $i * 0.05s;
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/components/countdown/drawer/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
77 |
78 |
79 |
80 |
83 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/components/countdown/drawer/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/countdown/drawer/index.component.scss
--------------------------------------------------------------------------------
/src/components/countdown/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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
7 | import { $t } from 'src/locale'
8 | import { FormBuilder, FormGroup } from '@angular/forms'
9 | import { NzDrawerModule } from 'ng-zorro-antd/drawer'
10 | import { NzFormModule } from 'ng-zorro-antd/form'
11 | import { NzButtonModule } from 'ng-zorro-antd/button'
12 | import { NzInputModule } from 'ng-zorro-antd/input'
13 | import { UploadImageComponent } from 'src/components/upload-image/index.component'
14 | import { NzColorPickerModule } from 'ng-zorro-antd/color-picker'
15 | import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'
16 | import dayjs from 'dayjs'
17 |
18 | @Component({
19 | standalone: true,
20 | imports: [
21 | NzDatePickerModule,
22 | FormsModule,
23 | ReactiveFormsModule,
24 | NzDrawerModule,
25 | NzFormModule,
26 | NzButtonModule,
27 | NzInputModule,
28 | UploadImageComponent,
29 | NzColorPickerModule,
30 | ],
31 | selector: 'countdown-drawer',
32 | templateUrl: './index.component.html',
33 | styleUrls: ['./index.component.scss'],
34 | })
35 | export class CountdownDrawerComponent {
36 | @Output() ok = new EventEmitter()
37 |
38 | $t = $t
39 | visible = false
40 | validateForm!: FormGroup
41 | index = 0
42 |
43 | constructor(private fb: FormBuilder) {
44 | this.validateForm = this.fb.group({
45 | topColor: [''],
46 | bgColor: [''],
47 | title: [''],
48 | url: [''],
49 | dateColor: [''],
50 | dayColor: [''],
51 | date: [null],
52 | })
53 | }
54 |
55 | open(data: any, idx: number) {
56 | this.index = idx
57 | for (const k in data) {
58 | this.validateForm.get(k)!?.setValue(data[k])
59 | }
60 | this.visible = true
61 | }
62 |
63 | onUploadImage(data: any) {
64 | this.validateForm.get('url')!.setValue(data.cdn)
65 | }
66 |
67 | handleClose() {
68 | this.visible = false
69 | }
70 |
71 | handleSubmit() {
72 | const values = this.validateForm.value
73 | this.ok.emit({
74 | ...values,
75 | date: dayjs(values.date).format('YYYY-MM-DD'),
76 | index: this.index,
77 | })
78 | this.handleClose()
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/components/countdown/index.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ data['title'] }}
4 |
5 |
10 |
11 | {{ countdownData['dayStr'] }}
12 |
13 |
14 | - {{ countdownData['dateStr'] }} -
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/countdown/index.component.scss:
--------------------------------------------------------------------------------
1 | .holiday {
2 | position: relative;
3 | width: 160px;
4 | height: var(--componentHeight);
5 | max-height: 100%;
6 | border-radius: 12px;
7 | overflow: hidden;
8 | color: #fff;
9 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
10 | display: flex;
11 | flex-direction: column;
12 | text-align: center;
13 | .top {
14 | padding: 6px 10px;
15 | font-weight: 500;
16 | }
17 | .box {
18 | position: relative;
19 | flex: 1;
20 | background-size: cover;
21 | }
22 | .days {
23 | font-weight: bold;
24 | font-size: 50px;
25 | margin-top: 16px;
26 | }
27 | .date {
28 | position: absolute;
29 | bottom: 5px;
30 | left: 0;
31 | width: 100%;
32 | font-weight: 500;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/countdown/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 type { IComponentItemProps } from 'src/types'
7 | import dayjs from 'dayjs'
8 | import { component } from 'src/store'
9 |
10 | interface IProps {
11 | dateStr: string
12 | dayStr: number
13 | }
14 |
15 | @Component({
16 | standalone: true,
17 | selector: 'app-countdown',
18 | templateUrl: './index.component.html',
19 | styleUrls: ['./index.component.scss'],
20 | })
21 | export class CountdownComponent {
22 | @Input() data!: IComponentItemProps
23 |
24 | readonly component = component()
25 | countdownData = {} as IProps
26 |
27 | constructor() {}
28 |
29 | ngOnInit() {
30 | this.init()
31 | }
32 |
33 | ngOnChanges() {
34 | this.init()
35 | }
36 |
37 | private init() {
38 | const payload = {} as IProps
39 | if (this.data['date']) {
40 | payload.dateStr = dayjs(this.data['date']).format('YYYY.MM.DD')
41 | payload.dayStr = dayjs(
42 | dayjs(this.data['date']).format('YYYY-MM-DD'),
43 | ).diff(dayjs().format('YYYY-MM-DD'), 'day')
44 | payload.dayStr = payload.dayStr < 0 ? 0 : payload.dayStr
45 | payload.dayStr = payload.dayStr > 9999 ? 9999 : payload.dayStr
46 | }
47 | this.countdownData = payload
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/create-web/index.component.scss:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | justify-content: space-around;
4 | }
5 |
6 | @media (max-width: 580px) {
7 | ::ng-deep .ant-modal {
8 | top: 0;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/delete-modal/index.component.html:
--------------------------------------------------------------------------------
1 |
7 |
8 | ID: {{ ids }}
9 |
10 |
11 |
该项目引用自 ID {{ ids }}
12 |
15 |
16 |
17 |
18 |
19 |
22 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/components/delete-modal/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/delete-modal/index.component.scss
--------------------------------------------------------------------------------
/src/components/delete-modal/index.component.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 | import { Component } from '@angular/core'
5 | import { CommonModule } from '@angular/common'
6 | import { FormsModule } from '@angular/forms'
7 | import type { IWebProps, INavProps } from 'src/types'
8 | import { NzButtonModule } from 'ng-zorro-antd/button'
9 | import { NzModalModule } from 'ng-zorro-antd/modal'
10 | import { NzMessageService } from 'ng-zorro-antd/message'
11 | import event from 'src/utils/mitt'
12 | import { $t } from 'src/locale'
13 | import { NzCheckboxModule } from 'ng-zorro-antd/checkbox'
14 | import { deleteWebByIds, deleteClassByIds } from 'src/utils/web'
15 |
16 | interface Props {
17 | ids: number[]
18 | data?: IWebProps | INavProps
19 | isClass?: boolean
20 | onOk?: () => void
21 | onComplete?: () => void
22 | }
23 |
24 | @Component({
25 | standalone: true,
26 | imports: [
27 | CommonModule,
28 | NzModalModule,
29 | NzButtonModule,
30 | NzCheckboxModule,
31 | FormsModule,
32 | ],
33 | selector: 'app-delete-modal',
34 | templateUrl: './index.component.html',
35 | styleUrls: ['./index.component.scss'],
36 | })
37 | export class DeleteModalComponent {
38 | readonly $t = $t
39 |
40 | submitting = false
41 | showModal = false
42 | ids: number[] = []
43 | isClass = false
44 | data?: IWebProps | INavProps
45 | isChecked = false
46 | onOk?: () => void
47 | onComplete?: () => void
48 |
49 | constructor(private message: NzMessageService) {
50 | event.on('DELETE_MODAL', (props: unknown) => {
51 | const p = props as Props
52 | this.ids = p.ids
53 | this.isClass = p.isClass || false
54 | this.data = p.data
55 | this.showModal = true
56 | this.onOk = p.onOk
57 | this.onComplete = p.onComplete
58 | })
59 | }
60 |
61 | handleCancel() {
62 | this.showModal = false
63 | this.isChecked = false
64 | }
65 |
66 | async handleOk() {
67 | let isDelRid = false
68 | if (this.data?.rId && this.isChecked) {
69 | isDelRid = true
70 | }
71 |
72 | const ok = await (this.isClass
73 | ? deleteClassByIds(this.ids, isDelRid)
74 | : deleteWebByIds(this.ids, isDelRid))
75 | if (ok) {
76 | this.onOk?.()
77 | this.message.success($t('_delSuccess'))
78 | } else {
79 | this.message.error('Delete failed')
80 | }
81 |
82 | this.onComplete?.()
83 | this.handleCancel()
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/edit-class/index.component.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/components/edit-class/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/edit-class/index.component.scss
--------------------------------------------------------------------------------
/src/components/fixbar/index.component.scss:
--------------------------------------------------------------------------------
1 | @media (min-width: 768px) {
2 | .wrapper {
3 | &:hover {
4 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.15);
5 | transform: scale(1.2);
6 | }
7 | }
8 | }
9 |
10 | @keyframes fadeIn {
11 | 0% {
12 | opacity: 0;
13 | }
14 | 100% {
15 | opacity: 1;
16 | }
17 | }
18 |
19 | .fixbar {
20 | z-index: 30;
21 | position: fixed;
22 | bottom: 20px;
23 | right: 15px;
24 | user-select: none;
25 | &.openFixbar {
26 | .common-show {
27 | visibility: visible !important;
28 | opacity: 1 !important;
29 | }
30 | .scrolltop {
31 | opacity: 1 !important;
32 | visibility: visible !important;
33 | }
34 | }
35 |
36 | .wrapper {
37 | width: 40px;
38 | height: 40px;
39 | margin-top: 10px;
40 | transition: 0.1s linear;
41 | display: flex;
42 | justify-content: center;
43 | align-items: center;
44 | cursor: pointer;
45 | border-radius: 50%;
46 | background-color: #fff;
47 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
48 | transition: all 0.1s linear;
49 | &.common-show {
50 | visibility: hidden;
51 | opacity: 0;
52 | }
53 | }
54 |
55 | .scrolltop {
56 | visibility: hidden;
57 | opacity: 0;
58 | &.showTop {
59 | visibility: visible;
60 | animation: fadeIn 0.5s linear forwards;
61 | }
62 | }
63 |
64 | img {
65 | width: 25px;
66 | height: 25px;
67 | }
68 |
69 | i {
70 | transition: 0.1s linear;
71 | display: inline-block;
72 | font-size: 20px;
73 | color: #999;
74 | }
75 |
76 | .arrow {
77 | transform: rotate(180deg);
78 | cursor: pointer;
79 | font-weight: bold;
80 | }
81 |
82 | .collapse-icon {
83 | transform: rotate(-270deg);
84 | &.active {
85 | transform: rotate(-360deg);
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/components/footer/footer.component.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/components/footer/footer.component.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | text-align: center;
3 | }
4 |
--------------------------------------------------------------------------------
/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 { CommonModule } from '@angular/common'
7 | import { settings } from 'src/store'
8 | import { compilerTemplate } from 'src/utils/utils'
9 | import { SafeHtmlPipe } from 'src/pipe/safeHtml.pipe'
10 | import event from 'src/utils/mitt'
11 |
12 | @Component({
13 | standalone: true,
14 | imports: [CommonModule, SafeHtmlPipe],
15 | changeDetection: ChangeDetectionStrategy.OnPush,
16 | selector: 'app-footer',
17 | templateUrl: './footer.component.html',
18 | styleUrls: ['./footer.component.scss'],
19 | })
20 | export class FooterComponent {
21 | @Input() className: string = ''
22 | @Input() content: string = ''
23 |
24 | footerContent: string = ''
25 |
26 | constructor() {}
27 |
28 | ngOnInit() {
29 | this.footerContent = compilerTemplate(
30 | this.content || settings().footerContent,
31 | )
32 | }
33 |
34 | ngOnDestroy() {
35 | const applyWebEls = document.querySelectorAll('#app-footer .applyweb')
36 | applyWebEls.forEach((el) => {
37 | el.removeEventListener('click', this.handleApplyWeb)
38 | })
39 | }
40 |
41 | handleApplyWeb() {
42 | event.emit('CREATE_WEB', {})
43 | }
44 |
45 | ngAfterViewInit() {
46 | const applyWebEls = document.querySelectorAll('#app-footer .applyweb')
47 | applyWebEls.forEach((el) => {
48 | el.addEventListener('click', this.handleApplyWeb)
49 | })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/holiday/drawer/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
57 |
58 |
59 |
60 |
63 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/components/holiday/drawer/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/holiday/drawer/index.component.scss
--------------------------------------------------------------------------------
/src/components/holiday/index.component.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | {{
9 | items[0].isToday || items[0].isRest
10 | ? $t('_dayIs')
11 | : $t('_distance') + items[0].title
12 | }}
13 |
14 |
{{ items[0].diffStr }}
15 |
16 | 0">{{ $t('_rest') }}
17 | {{ items[0].dateStr }}
19 | - {{ items[0].afterDay }}
23 |
24 |
25 |
26 |
27 |
28 | {{ item.title }} {{ item.dateStr
29 | }} 0">{{ $t('_rest') }}
30 |
31 |
0">
32 | {{ item.diffDay }}{{ $t('_day') }}
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/components/holiday/index.component.scss:
--------------------------------------------------------------------------------
1 | .holiday {
2 | position: relative;
3 | width: 320px;
4 | height: var(--componentHeight);
5 | max-height: 100%;
6 | border-radius: 12px;
7 | overflow: hidden;
8 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
9 | display: flex;
10 | background-color: #fff;
11 | color: #666;
12 | .title {
13 | font-weight: 500;
14 | font-size: 16px;
15 | }
16 | .left {
17 | width: 140px;
18 | padding: 12px 0 12px 12px;
19 | display: flex;
20 | flex-direction: column;
21 | .days {
22 | font-size: 46px;
23 | text-align: center;
24 | font-weight: bold;
25 | color: rgba(0, 0, 0, 0.85);
26 | flex: 1;
27 | display: flex;
28 | align-items: center;
29 | justify-content: center;
30 | }
31 | &.today {
32 | .days {
33 | font-size: 30px;
34 | }
35 | }
36 | }
37 | .tag {
38 | background-color: rgb(223, 252, 234);
39 | color: rgb(85, 170, 111);
40 | font-size: 10px;
41 | font-weight: 500;
42 | padding: 1px 2px;
43 | border-radius: 2px;
44 | display: inline-flex;
45 | align-items: center;
46 | justify-content: center;
47 | margin-right: 3px;
48 | margin-left: 2px;
49 | }
50 | .right {
51 | padding: 12px 12px 12px 0;
52 | flex: 1;
53 | padding-left: 12px;
54 | .items {
55 | display: flex;
56 | align-items: center;
57 | justify-content: space-between;
58 | padding: 4px 0;
59 | color: rgba(0, 0, 0, 0.8);
60 | &:not(:nth-last-child(1)) {
61 | margin-bottom: 3px;
62 | border-bottom: 1px solid #eee;
63 | }
64 | }
65 | }
66 | .cleft {
67 | position: relative;
68 | white-space: nowrap;
69 | display: flex;
70 | align-items: center;
71 | .tag {
72 | position: absolute;
73 | top: 50%;
74 | left: -5px;
75 | transform: translate(-100%, -50%);
76 | }
77 | }
78 | .cright {
79 | font-weight: 500;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/holiday/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 { CommonModule } from '@angular/common'
7 | import type { IComponentItemProps } from 'src/types'
8 | import { $t } from 'src/locale'
9 | import dayjs from 'dayjs'
10 | import { component } from 'src/store'
11 |
12 | @Component({
13 | standalone: true,
14 | imports: [CommonModule],
15 | selector: 'app-holiday',
16 | templateUrl: './index.component.html',
17 | styleUrls: ['./index.component.scss'],
18 | })
19 | export class HolidayComponent {
20 | @Input() data!: IComponentItemProps
21 | items: any[] = []
22 |
23 | readonly component = component()
24 | readonly $t = $t
25 |
26 | constructor() {}
27 |
28 | ngOnChanges() {
29 | this.init()
30 | }
31 |
32 | private init() {
33 | let items: any = {}
34 | const now = dayjs(dayjs().format('YYYY-MM-DD'))
35 | if (this.data['items']) {
36 | items = [...this.data['items']]
37 | .filter((item: any) => {
38 | item.date = dayjs(item.date).format('YYYY-MM-DD')
39 | let date = dayjs(item.date)
40 | if (item.day > 0) {
41 | date = date.add(item.day - 1, 'day')
42 | }
43 | if (date.isBefore(now)) {
44 | return false
45 | }
46 | return true
47 | })
48 | .slice(0, 4)
49 | .map((item: any) => {
50 | item.dateStr = dayjs(item.date).format('MM.DD')
51 | item.diffDay = dayjs(dayjs(item.date).format('YYYY-MM-DD')).diff(
52 | now,
53 | 'day',
54 | )
55 | item.diffDay = item.diffDay < 0 ? 0 : item.diffDay
56 | item.diffDay = item.diffDay > 999 ? 999 : item.diffDay
57 | item.diffStr = item.diffDay
58 | if (item.day > 0) {
59 | item.afterDay = dayjs(item.date)
60 | .add(item.day - 1, 'day')
61 | .format('MM.DD')
62 | if (item.afterDay === item.dateStr) {
63 | item.afterDay = null
64 | }
65 | }
66 | item.isToday = item.dateStr === dayjs().format('MM.DD')
67 | if (item.diffDay <= 0) {
68 | if (item.isToday) {
69 | item.diffStr = item.title
70 | } else {
71 | item.isRest = true
72 | item.diffStr = '休息日'
73 | }
74 | }
75 |
76 | return item
77 | })
78 | }
79 | this.items = items
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/html/drawer/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
41 |
42 |
43 |
44 |
47 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/components/html/drawer/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/html/drawer/index.component.scss
--------------------------------------------------------------------------------
/src/components/html/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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
7 | import { $t } from 'src/locale'
8 | import { FormBuilder, FormGroup } from '@angular/forms'
9 | import { NzDrawerModule } from 'ng-zorro-antd/drawer'
10 | import { NzFormModule } from 'ng-zorro-antd/form'
11 | import { NzButtonModule } from 'ng-zorro-antd/button'
12 | import { NzInputModule } from 'ng-zorro-antd/input'
13 | import { NzSliderModule } from 'ng-zorro-antd/slider'
14 | import { NzColorPickerModule } from 'ng-zorro-antd/color-picker'
15 |
16 | @Component({
17 | standalone: true,
18 | imports: [
19 | FormsModule,
20 | ReactiveFormsModule,
21 | NzDrawerModule,
22 | NzFormModule,
23 | NzButtonModule,
24 | NzInputModule,
25 | NzSliderModule,
26 | NzColorPickerModule,
27 | ],
28 | selector: 'html-drawer',
29 | templateUrl: './index.component.html',
30 | styleUrls: ['./index.component.scss'],
31 | })
32 | export class HTMLDrawerComponent {
33 | @Output() ok = new EventEmitter()
34 |
35 | readonly $t = $t
36 | visible = false
37 | validateForm!: FormGroup
38 | index = 0
39 |
40 | constructor(private fb: FormBuilder) {
41 | this.validateForm = this.fb.group({
42 | html: [''],
43 | width: [0],
44 | bgColor: [''],
45 | })
46 | }
47 |
48 | open(data: any, idx: number) {
49 | this.index = idx
50 | for (const k in data) {
51 | this.validateForm.get(k)!?.setValue(data[k])
52 | }
53 | this.visible = true
54 | }
55 |
56 | handleClose() {
57 | this.visible = false
58 | }
59 |
60 | handleSubmit() {
61 | const values = this.validateForm.value
62 | this.ok.emit({
63 | ...values,
64 | index: this.index,
65 | })
66 | this.handleClose()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/html/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/components/html/index.component.scss:
--------------------------------------------------------------------------------
1 | .html {
2 | position: relative;
3 | height: var(--componentHeight) !important;
4 | max-height: 100%;
5 | border-radius: 12px;
6 | overflow: hidden;
7 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
8 | overflow-wrap: break-word;
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/html/index.component.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 |
5 | import { Component, Input, ViewChild, ElementRef } from '@angular/core'
6 | import type { IComponentItemProps } from 'src/types'
7 | import { SafeHtmlPipe } from 'src/pipe/safeHtml.pipe'
8 | import { parseHtmlWithContent, parseLoadingWithContent } from 'src/utils/utils'
9 | import { component } from 'src/store'
10 |
11 | @Component({
12 | standalone: true,
13 | imports: [SafeHtmlPipe],
14 | selector: 'app-html',
15 | templateUrl: './index.component.html',
16 | styleUrls: ['./index.component.scss'],
17 | })
18 | export class HTMLComponent {
19 | @Input() data!: IComponentItemProps
20 | @ViewChild('root', { static: false }) root!: ElementRef
21 |
22 | private parseDescriptionTimer: any
23 | readonly component = component()
24 | html = ''
25 |
26 | constructor() {}
27 |
28 | ngOnChanges() {
29 | this.init()
30 | this.parseDescription()
31 | }
32 |
33 | ngOnDestroy() {
34 | clearTimeout(this.parseDescriptionTimer)
35 | }
36 |
37 | private init() {
38 | this.html = parseLoadingWithContent(`!${this.data['html']}`)
39 | }
40 |
41 | private parseDescription() {
42 | clearTimeout(this.parseDescriptionTimer)
43 | this.parseDescriptionTimer = setTimeout(() => {
44 | parseHtmlWithContent(this.root?.nativeElement, `!${this.html}`)
45 | }, 300)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/icon-git/icon-git.component.html:
--------------------------------------------------------------------------------
1 |
8 |
49 |
50 |
--------------------------------------------------------------------------------
/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/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 { CommonModule } from '@angular/common'
6 | import { settings } from 'src/store'
7 | import { isSelfDevelop } from 'src/utils/utils'
8 |
9 | @Component({
10 | standalone: true,
11 | imports: [CommonModule],
12 | changeDetection: ChangeDetectionStrategy.OnPush,
13 | selector: 'app-icon-git',
14 | templateUrl: './icon-git.component.html',
15 | styleUrls: ['./icon-git.component.scss'],
16 | })
17 | export class IconGitComponent {
18 | gitRepoUrl: string = config.gitRepoUrl
19 | showGithub = !isSelfDevelop && settings().showGithub
20 |
21 | constructor() {}
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/image/drawer/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
39 |
40 |
41 |
42 |
45 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/image/drawer/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/image/drawer/index.component.scss
--------------------------------------------------------------------------------
/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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
7 | import { $t } from 'src/locale'
8 | import { FormBuilder, FormGroup } from '@angular/forms'
9 | import { NzDrawerModule } from 'ng-zorro-antd/drawer'
10 | import { NzFormModule } from 'ng-zorro-antd/form'
11 | import { NzButtonModule } from 'ng-zorro-antd/button'
12 | import { NzInputModule } from 'ng-zorro-antd/input'
13 | import { UploadImageComponent } from 'src/components/upload-image/index.component'
14 |
15 | @Component({
16 | standalone: true,
17 | imports: [
18 | UploadImageComponent,
19 | FormsModule,
20 | ReactiveFormsModule,
21 | NzDrawerModule,
22 | NzFormModule,
23 | NzButtonModule,
24 | NzInputModule,
25 | ],
26 | selector: 'image-drawer',
27 | templateUrl: './index.component.html',
28 | styleUrls: ['./index.component.scss'],
29 | })
30 | export class ImageDrawerComponent {
31 | @Output() ok = new EventEmitter()
32 |
33 | $t = $t
34 | visible = false
35 | validateForm!: FormGroup
36 | index = 0
37 |
38 | constructor(private fb: FormBuilder) {
39 | this.validateForm = this.fb.group({
40 | url: [''],
41 | text: [''],
42 | go: [''],
43 | })
44 | }
45 |
46 | open(data: any, idx: number) {
47 | this.index = idx
48 | for (const k in data) {
49 | this.validateForm.get(k)!?.setValue(data[k])
50 | }
51 | this.visible = true
52 | }
53 |
54 | onUploadImage(data: any) {
55 | this.validateForm.get('url')!.setValue(data.cdn)
56 | }
57 |
58 | handleClose() {
59 | this.visible = false
60 | }
61 |
62 | handleSubmit() {
63 | const values = this.validateForm.value
64 | this.ok.emit({
65 | ...values,
66 | index: this.index,
67 | })
68 | this.handleClose()
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/image/index.component.html:
--------------------------------------------------------------------------------
1 |
8 |
{{ data['text'] }}
9 |
10 |
--------------------------------------------------------------------------------
/src/components/image/index.component.scss:
--------------------------------------------------------------------------------
1 | .cimage {
2 | position: relative;
3 | width: 170px;
4 | height: var(--componentHeight);
5 | max-height: 100%;
6 | border-radius: 12px;
7 | overflow: hidden;
8 | color: #fff;
9 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
10 | background-size: cover;
11 | .text {
12 | position: absolute;
13 | bottom: 5px;
14 | left: 0;
15 | width: 100%;
16 | font-size: 12px;
17 | text-align: center;
18 | font-weight: 500;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/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 type { IComponentItemProps } from 'src/types'
7 | import { JumpService } from 'src/services/jump'
8 | import { component } from 'src/store'
9 |
10 | @Component({
11 | standalone: true,
12 | selector: 'app-image',
13 | templateUrl: './index.component.html',
14 | styleUrls: ['./index.component.scss'],
15 | })
16 | export class ImageComponent {
17 | @Input() data!: IComponentItemProps
18 |
19 | readonly component = component()
20 |
21 | constructor(public jumpService: JumpService) {}
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/loading/index.component.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/loading/index.component.scss:
--------------------------------------------------------------------------------
1 | .cc1 {
2 | z-index: 2;
3 | position: absolute;
4 | top: 50%;
5 | left: 50%;
6 | transform: translate(-50%, -50%);
7 | width: 15px;
8 | height: 15px;
9 | animation: my-rotatex1 2s linear infinite;
10 | }
11 | .cc2 {
12 | stroke-dasharray: 90, 150;
13 | stroke-dashoffset: 0;
14 | stroke-width: 5;
15 | stroke: #6d8ba7;
16 | stroke-linecap: round;
17 | animation: loading-dashx2 1.5s ease-in-out infinite;
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/loading/index.component.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 |
5 | import { Component, ChangeDetectionStrategy } from '@angular/core'
6 |
7 | @Component({
8 | standalone: true,
9 | changeDetection: ChangeDetectionStrategy.OnPush,
10 | imports: [],
11 | selector: 'app-loading',
12 | templateUrl: './index.component.html',
13 | styleUrls: ['./index.component.scss'],
14 | })
15 | export class LoadingComponent {
16 | constructor() {}
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/login/login.component.html:
--------------------------------------------------------------------------------
1 |
8 |
9 | {{ $t('_inputTokenMsg') }}
10 |
18 |
19 |
26 |
27 |
28 | {{ $t('_getToken')
29 | }}
33 | {{ $t('_readDoc') }}
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/components/login/login.component.scss:
--------------------------------------------------------------------------------
1 | .prefix-icon {
2 | width: 20px;
3 | height: 20px;
4 | pointer-events: none;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/logo/logo.component.html:
--------------------------------------------------------------------------------
1 |
13 | {{ firstLetter }}
14 |
15 |
16 |
17 |
33 |
34 |
46 | {{ firstLetter }}
47 |
48 |
49 |
--------------------------------------------------------------------------------
/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 | font-size: 18px;
33 | }
34 |
35 | .common-icon {
36 | transition: all 0.1s linear;
37 | }
38 |
--------------------------------------------------------------------------------
/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 | import { CommonModule } from '@angular/common'
6 | import { randomColor, getTextContent } from 'src/utils'
7 |
8 | @Component({
9 | standalone: true,
10 | imports: [CommonModule],
11 | changeDetection: ChangeDetectionStrategy.OnPush,
12 | selector: 'app-logo',
13 | templateUrl: './logo.component.html',
14 | styleUrls: ['./logo.component.scss'],
15 | })
16 | export class LogoComponent {
17 | @Input() src: string = ''
18 | @Input() name: string = ''
19 | @Input() size: number = 35
20 | @Input() radius: number = 3
21 |
22 | backgroundColor: string = '#1890ff'
23 | firstLetter: string = ''
24 | isError: boolean = false
25 |
26 | constructor() {}
27 |
28 | ngOnInit() {
29 | if (!this.src) {
30 | this.generateColor()
31 | this.getFirstLetter()
32 | }
33 | }
34 |
35 | private generateColor() {
36 | this.backgroundColor = `linear-gradient(45deg, #fff, ${randomColor()} 41%)`
37 | }
38 |
39 | private getFirstLetter() {
40 | if (this.name) {
41 | this.firstLetter = getTextContent(this.name)[0].toUpperCase()
42 | }
43 | }
44 |
45 | onError() {
46 | this.isError = true
47 | this.generateColor()
48 | this.getFirstLetter()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/move-web/index.component.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
17 |
22 |
23 |
24 |
25 | 2"
32 | >
33 |
38 |
39 |
40 |
41 |
49 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/components/move-web/index.component.scss:
--------------------------------------------------------------------------------
1 | .act {
2 | margin-top: 30px;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/news/drawer/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
39 |
40 |
41 |
42 |
45 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/news/drawer/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/news/drawer/index.component.scss
--------------------------------------------------------------------------------
/src/components/news/index.component.html:
--------------------------------------------------------------------------------
1 |
5 |
21 |
22 |
23 |
24 |
25 | {{ $t('_noNews') }}
26 |
27 |
28 |
33 | {{ i + 1 }}{{ item.text }}{{ item.hot }}
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/news/index.component.scss:
--------------------------------------------------------------------------------
1 | .news {
2 | position: relative;
3 | width: 310px;
4 | height: var(--componentHeight) !important;
5 | max-height: 100%;
6 | border-radius: 12px;
7 | overflow: hidden;
8 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
9 | background: linear-gradient(100deg, #2a2d38, #535a68);
10 | color: #fff;
11 | padding: 10px 10px 0 10px;
12 | font-size: 12px;
13 | display: flex;
14 | flex-direction: column;
15 | .tabs {
16 | display: flex;
17 | column-gap: 4px;
18 | white-space: nowrap;
19 | overflow: hidden;
20 | overflow-x: auto;
21 | }
22 | .tab {
23 | border-radius: 4px;
24 | padding: 2px 4px;
25 | cursor: pointer;
26 | transition: all 0.2s linear;
27 | &.active {
28 | background-color: #fff2;
29 | }
30 | }
31 | .content-box {
32 | position: relative;
33 | flex: 1;
34 | overflow: hidden;
35 | overflow-y: auto;
36 | padding: 10px 0;
37 | color: #f1f1f1;
38 | }
39 | .nodata {
40 | position: absolute;
41 | top: 50%;
42 | left: 50%;
43 | transform: translate(-50%, -50%);
44 | color: #aeaeae;
45 | }
46 | .content {
47 | cursor: pointer;
48 | display: flex;
49 | align-items: center;
50 | &:not(:last-child) {
51 | margin-bottom: 4px;
52 | }
53 | &:hover {
54 | opacity: 0.9;
55 | }
56 | &:nth-child(1) .n {
57 | color: #fff;
58 | }
59 | &:nth-child(2) .n {
60 | color: rgba(255, 255, 255, 0.9);
61 | }
62 | &:nth-child(3) .n {
63 | color: rgba(255, 255, 255, 0.8);
64 | }
65 |
66 | .mid {
67 | flex: 1;
68 | }
69 | }
70 | .n {
71 | color: #969c9c;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/news/types.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 |
5 | import { $t } from 'src/locale'
6 | import { NewsType } from 'src/types'
7 |
8 | export const newsTypeMap = {
9 | [NewsType.Weibo]: $t('_weibo'),
10 | [NewsType.V2ex]: 'V2EX',
11 | [NewsType.Douyin]: $t('_douyin'),
12 | [NewsType.Bilibili]: $t('_bilibili'),
13 | [NewsType.Juejin]: $t('_juejin'),
14 | [NewsType.Baidu]: $t('_baidu'),
15 | [NewsType.GitHub]: 'GitHub',
16 | [NewsType.Pojie52]: $t('_52'),
17 | [NewsType.Xiaohongshu]: $t('_hongshu'),
18 | [NewsType.Toutiao]: $t('_toutiao'),
19 | [NewsType.Douban]: $t('_douban'),
20 | [NewsType.HackerNews]: 'Hacker News',
21 | [NewsType.Zhihu]: $t('_zhihu'),
22 | [NewsType.ZhihuDaily]: $t('_zhihuDaily'),
23 | } as const
24 |
--------------------------------------------------------------------------------
/src/components/no-data/no-data.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | import { NzButtonModule } from 'ng-zorro-antd/button'
7 | import { NzEmptyModule } from 'ng-zorro-antd/empty'
8 |
9 | @Component({
10 | standalone: true,
11 | imports: [NzButtonModule, NzEmptyModule],
12 | changeDetection: ChangeDetectionStrategy.OnPush,
13 | selector: 'app-no-data',
14 | templateUrl: './no-data.component.html',
15 | styleUrls: ['./no-data.component.scss'],
16 | })
17 | export class NoDataComponent {
18 | $t = $t
19 |
20 | goBack = () => {
21 | history.go(-1)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/off-work/drawer/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
35 |
36 |
37 |
38 |
41 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/components/off-work/drawer/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/off-work/drawer/index.component.scss
--------------------------------------------------------------------------------
/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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
7 | import { $t } from 'src/locale'
8 | import { FormBuilder, FormGroup } from '@angular/forms'
9 | import { NzMessageService } from 'ng-zorro-antd/message'
10 | import { NzDrawerModule } from 'ng-zorro-antd/drawer'
11 | import { NzFormModule } from 'ng-zorro-antd/form'
12 | import { NzButtonModule } from 'ng-zorro-antd/button'
13 | import { NzInputModule } from 'ng-zorro-antd/input'
14 | import { NzTimePickerModule } from 'ng-zorro-antd/time-picker'
15 |
16 | @Component({
17 | standalone: true,
18 | imports: [
19 | NzTimePickerModule,
20 | FormsModule,
21 | ReactiveFormsModule,
22 | NzDrawerModule,
23 | NzFormModule,
24 | NzButtonModule,
25 | NzInputModule,
26 | ],
27 | selector: 'offwork-drawer',
28 | templateUrl: './index.component.html',
29 | styleUrls: ['./index.component.scss'],
30 | })
31 | export class OffWorkDrawerComponent {
32 | @Output() ok = new EventEmitter()
33 |
34 | $t = $t
35 | visible = false
36 | validateForm!: FormGroup
37 | index = 0
38 |
39 | constructor(
40 | private fb: FormBuilder,
41 | private message: NzMessageService,
42 | ) {
43 | this.validateForm = this.fb.group({
44 | workTitle: [''],
45 | restTitle: [''],
46 | startDate: [null],
47 | date: [null],
48 | })
49 | }
50 |
51 | open(data: any, idx: number) {
52 | this.index = idx
53 | for (const k in data) {
54 | this.validateForm.get(k)!?.setValue(data[k])
55 | }
56 | this.visible = true
57 | }
58 |
59 | handleClose() {
60 | this.visible = false
61 | }
62 |
63 | handleSubmit(): any {
64 | const values = this.validateForm.value
65 | const startDate = new Date(values.startDate).getTime()
66 | const date = new Date(values.date).getTime()
67 | if (startDate >= date) {
68 | return this.message.error('休息时间需要比工作时间大')
69 | }
70 | this.ok.emit({
71 | ...values,
72 | startDate,
73 | date,
74 | index: this.index,
75 | })
76 | this.handleClose()
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/off-work/index.component.html:
--------------------------------------------------------------------------------
1 |
6 |
7 | {{ isRest ? data['restTitle'] : data['workTitle'] }}
8 |
9 |
{{ countdownStr }}
10 |

15 |
16 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/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-height: 100%;
7 | border-radius: 12px;
8 | overflow: hidden;
9 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
10 | display: flex;
11 | align-items: center;
12 | flex-direction: column;
13 | font-weight: bold;
14 | background-color: #fff;
15 | &.rest {
16 | .title {
17 | font-size: 22px;
18 | color: #666;
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: 0;
32 | bottom: 0;
33 | width: 100%;
34 | }
35 | .coutdown {
36 | z-index: 2;
37 | position: relative;
38 | font-size: 24px;
39 | color: #666;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/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 { CommonModule } from '@angular/common'
7 | import type { IComponentItemProps } from 'src/types'
8 | import { $t } from 'src/locale'
9 | import { component } from 'src/store'
10 |
11 | @Component({
12 | standalone: true,
13 | imports: [CommonModule],
14 | selector: 'app-offwork',
15 | templateUrl: './index.component.html',
16 | styleUrls: ['./index.component.scss'],
17 | })
18 | export class OffWorkComponent {
19 | @Input() data!: IComponentItemProps
20 |
21 | readonly component = component()
22 | private timer: any
23 | countdownStr = ''
24 | isRest = false
25 |
26 | constructor() {
27 | document.addEventListener(
28 | 'visibilitychange',
29 | this.visibilitychange.bind(this),
30 | )
31 | }
32 |
33 | ngOnChanges() {
34 | clearTimeout(this.timer)
35 | this.init()
36 | }
37 |
38 | ngOnDestroy() {
39 | clearTimeout(this.timer)
40 | document.removeEventListener('visibilitychange', this.visibilitychange)
41 | }
42 |
43 | private visibilitychange(e: any) {
44 | if (e.target.hidden) {
45 | clearTimeout(this.timer)
46 | } else {
47 | this.init()
48 | }
49 | }
50 |
51 | private init() {
52 | if (this.data) {
53 | const now = new Date()
54 | const nowTime = now.getTime()
55 | const startDate = new Date(this.data['startDate'] as number)
56 | startDate.setFullYear(now.getFullYear())
57 | startDate.setMonth(now.getMonth())
58 | startDate.setDate(now.getDate())
59 | const startTime = startDate.getTime()
60 | const date = new Date(this.data['date'] as number)
61 | date.setFullYear(now.getFullYear())
62 | date.setMonth(now.getMonth())
63 | date.setDate(now.getDate())
64 | const dateTime = date.getTime()
65 | const diffTime = (dateTime - nowTime) / 1000
66 | const hours = diffTime / (60 * 60)
67 | const decimal = Math.floor((hours % 1) * 10) / 10
68 | const minutes = Math.floor((diffTime / 60) % 60)
69 | const seconds = Math.floor(diffTime % 60)
70 | const hoursDecimal = Math.floor(hours) + decimal
71 |
72 | if (nowTime >= startTime && nowTime <= dateTime) {
73 | if (hoursDecimal >= 1) {
74 | this.countdownStr = $t('_hours', { num: hoursDecimal })
75 | } else if (minutes > 0) {
76 | this.countdownStr = $t('_minutes', { num: minutes })
77 | } else if (seconds >= 0) {
78 | this.countdownStr = $t('_seconds', { num: seconds })
79 | }
80 | } else {
81 | this.isRest = true
82 | return clearTimeout(this.timer)
83 | }
84 | this.isRest = false
85 | }
86 | this.timer = setTimeout(() => this.init(), 1000)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/components/phone-class/index.component.html:
--------------------------------------------------------------------------------
1 | 1
7 | "
8 | >
9 |
20 | {{ item.title }}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/phone-class/index.component.scss:
--------------------------------------------------------------------------------
1 | .phone-nav {
2 | display: flex;
3 | align-items: center;
4 | background-color: #e1e1e1;
5 | border-radius: 50px;
6 | padding: 4px;
7 | overflow: hidden;
8 | overflow-x: auto;
9 | white-space: nowrap;
10 | .nav-item {
11 | padding: 4px 12px;
12 | cursor: pointer;
13 | border-radius: 50px;
14 | color: #666;
15 | &.active {
16 | background-color: #1e80ff !important;
17 | color: #fff !important;
18 | }
19 | &:hover {
20 | background: rgba(0, 0, 0, 0.03);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/phone-class/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 | ViewChild,
8 | ElementRef,
9 | ViewChildren,
10 | QueryList,
11 | } from '@angular/core'
12 | import { CommonModule } from '@angular/common'
13 | import { CommonService } from 'src/services/common'
14 | import type { INavTwoProp } from 'src/types'
15 | import { scrollIntoViewLeft } from 'src/utils'
16 |
17 | @Component({
18 | standalone: true,
19 | imports: [CommonModule],
20 | selector: 'app-phone-class',
21 | templateUrl: './index.component.html',
22 | styleUrls: ['./index.component.scss'],
23 | })
24 | export class PhoneClassComponent {
25 | @ViewChild('parent') parentRef!: ElementRef
26 | @ViewChildren('item') itemsRef!: QueryList
27 |
28 | constructor(public commonService: CommonService) {}
29 |
30 | ngAfterViewInit() {
31 | if (!this.parentRef) {
32 | return
33 | }
34 | scrollIntoViewLeft(
35 | this.parentRef.nativeElement,
36 | this.itemsRef.toArray()[this.commonService.twoIndex].nativeElement,
37 | { behavior: 'auto' },
38 | )
39 | }
40 |
41 | handleClickTwo(e: any, data: INavTwoProp) {
42 | this.commonService.handleClickClass(data.id)
43 | scrollIntoViewLeft(this.parentRef.nativeElement, e.target)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/runtime/drawer/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
17 |
18 |
19 |
20 |
23 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/runtime/drawer/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/runtime/drawer/index.component.scss
--------------------------------------------------------------------------------
/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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
7 | import { $t } from 'src/locale'
8 | import { FormBuilder, FormGroup } from '@angular/forms'
9 | import { NzDrawerModule } from 'ng-zorro-antd/drawer'
10 | import { NzFormModule } from 'ng-zorro-antd/form'
11 | import { NzButtonModule } from 'ng-zorro-antd/button'
12 | import { NzInputModule } from 'ng-zorro-antd/input'
13 |
14 | @Component({
15 | standalone: true,
16 | imports: [
17 | FormsModule,
18 | ReactiveFormsModule,
19 | NzDrawerModule,
20 | NzFormModule,
21 | NzButtonModule,
22 | NzInputModule,
23 | ],
24 | selector: 'runtime-drawer',
25 | templateUrl: './index.component.html',
26 | styleUrls: ['./index.component.scss'],
27 | })
28 | export class RuntimeDrawerComponent {
29 | @Output() ok = new EventEmitter()
30 |
31 | $t = $t
32 | visible = false
33 | validateForm!: FormGroup
34 | index = 0
35 |
36 | constructor(private fb: FormBuilder) {
37 | this.validateForm = this.fb.group({
38 | title: [''],
39 | })
40 | }
41 |
42 | open(data: any, idx: number) {
43 | this.index = idx
44 | for (const k in data) {
45 | this.validateForm.get(k)!?.setValue(data[k])
46 | }
47 | this.visible = true
48 | }
49 |
50 | handleClose() {
51 | this.visible = false
52 | }
53 |
54 | handleSubmit() {
55 | const values = this.validateForm.value
56 | this.ok.emit({
57 | ...values,
58 | index: this.index,
59 | })
60 | this.handleClose()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/runtime/index.component.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ data['title'] }}
3 |
4 | {{ runDays }}
5 | {{ unit }}
6 |
7 |

11 |
12 |
--------------------------------------------------------------------------------
/src/components/runtime/index.component.scss:
--------------------------------------------------------------------------------
1 | .runtime {
2 | pointer-events: none;
3 | position: relative;
4 | width: 230px;
5 | height: var(--componentHeight);
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 | display: flex;
12 | justify-content: center;
13 | padding: 30px 30px;
14 | flex-direction: column;
15 | background: linear-gradient(135deg, #8bc6ec 0%, #9599e2 100%);
16 | font-weight: bold;
17 |
18 | .title {
19 | z-index: 2;
20 | position: relative;
21 | font-size: 18px;
22 | color: #f9f6f6;
23 | }
24 | .days {
25 | z-index: 2;
26 | position: relative;
27 | margin-top: 10px;
28 | font-size: 48px;
29 | line-height: 1;
30 | color: #d67272;
31 | }
32 | .day {
33 | vertical-align: text-bottom;
34 | }
35 | .unit {
36 | font-size: 15px;
37 | margin-left: 2px;
38 | margin-bottom: 2px;
39 | font-weight: 500;
40 | }
41 | .img {
42 | position: absolute;
43 | bottom: 30px;
44 | right: 30px;
45 | width: 70px;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/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 { settings } from 'src/store'
7 | import type { IComponentItemProps } from 'src/types'
8 | import { $t } from 'src/locale'
9 | import { component } from 'src/store'
10 |
11 | @Component({
12 | standalone: true,
13 | selector: 'app-runtime',
14 | templateUrl: './index.component.html',
15 | styleUrls: ['./index.component.scss'],
16 | })
17 | export class RuntimeComponent {
18 | @Input() data!: IComponentItemProps
19 |
20 | readonly component = component()
21 | runDays = 0
22 | unit = ''
23 |
24 | constructor() {
25 | let now = Date.now() - settings().runtime
26 | now = now < 0 ? 0 : now
27 | const diffYear = Math.floor(now / (1000 * 60 * 60 * 24 * 365))
28 | if (diffYear > 0) {
29 | this.runDays = diffYear
30 | this.unit = $t('_year')
31 | } else {
32 | this.runDays = Math.floor(now / (1000 * 60 * 60 * 24))
33 | this.unit = $t('_day')
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/search/index.component.scss:
--------------------------------------------------------------------------------
1 | .search-engine {
2 | position: relative;
3 | display: flex;
4 | justify-content: center;
5 | flex-direction: column;
6 | align-items: center;
7 | padding: 10px 0;
8 | .input-wrapper {
9 | position: relative;
10 | width: 650px;
11 | max-width: 95%;
12 | background: #fff;
13 | border-radius: 5px;
14 | overflow: hidden;
15 |
16 | input {
17 | padding-left: 10px;
18 | padding-right: 10px;
19 |
20 | &:-webkit-autofill-selected {
21 | background-color: transparent !important;
22 | box-shadow: inset 0 0 0 500px transparent !important;
23 | }
24 | }
25 |
26 | .left-icon {
27 | position: relative;
28 | width: 20px;
29 | height: 20px;
30 | background-repeat: no-repeat;
31 | background-size: 20px 20px;
32 | cursor: pointer;
33 | }
34 |
35 | .search-icon {
36 | cursor: pointer;
37 | }
38 | }
39 |
40 | ::ng-deep .removeAddon .ant-input-group-addon {
41 | display: none !important;
42 | }
43 | }
44 |
45 | ::ng-deep.engine-main {
46 | border-radius: 5px;
47 | display: flex;
48 | flex-wrap: wrap;
49 | gap: 10px;
50 | max-width: 631px;
51 | .item {
52 | width: 200px;
53 | padding: 6px;
54 | display: flex;
55 | background: #f6f6f6;
56 | cursor: pointer;
57 | border-radius: 4px;
58 | transition: 0.1s linear;
59 | box-sizing: border-box;
60 | border: 1px solid transparent;
61 | &:hover {
62 | background-color: #eee;
63 | }
64 | .name2 {
65 | margin-left: 10px;
66 | font-size: 15px;
67 | align-self: center;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/search/index.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 |
3 | export enum SearchType {
4 | All = 1,
5 | Title,
6 | Desc,
7 | Url,
8 | Current,
9 | Quick,
10 | Id,
11 | Tag,
12 | Class,
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/side-images/image/index.component.html:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/components/side-images/image/index.component.scss:
--------------------------------------------------------------------------------
1 | .adsimg {
2 | display: block;
3 | width: 100%;
4 | border-radius: 4px;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/side-images/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, ViewChild, ElementRef } from '@angular/core'
6 | import { CommonModule } from '@angular/common'
7 | import type { ImageProps } from 'src/types'
8 | import { parseHtmlWithContent, parseLoadingWithContent } from 'src/utils/utils'
9 | import { SafeHtmlPipe } from 'src/pipe/safeHtml.pipe'
10 | import { CODE_SYMBOL } from 'src/constants/symbol'
11 |
12 | @Component({
13 | standalone: true,
14 | imports: [CommonModule, SafeHtmlPipe],
15 | selector: 'app-side-image',
16 | templateUrl: './index.component.html',
17 | styleUrls: ['./index.component.scss'],
18 | })
19 | export class SideImageComponent {
20 | @Input() data: ImageProps = {} as ImageProps
21 | @ViewChild('root', { static: false }) root!: ElementRef
22 |
23 | isCode = false
24 | html = ''
25 |
26 | constructor() {}
27 |
28 | ngOnInit() {
29 | this.isCode = this.data.url[0] === CODE_SYMBOL
30 | if (this.isCode) {
31 | this.html = parseLoadingWithContent(this.data.url)
32 | }
33 | }
34 |
35 | ngAfterViewInit() {
36 | this.parseDescription()
37 | }
38 |
39 | private parseDescription() {
40 | if (this.isCode) {
41 | parseHtmlWithContent(this.root?.nativeElement, this.data.url)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/side-images/index.component.html:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/components/side-images/index.component.scss:
--------------------------------------------------------------------------------
1 | .sideimg {
2 | max-width: 100%;
3 | width: 230px;
4 | max-height: calc(100vh - 50px);
5 | overflow: hidden;
6 | overflow-y: auto;
7 | .adsimg {
8 | display: block;
9 | width: 100%;
10 | border-radius: 4px;
11 | }
12 | .aditem {
13 | position: relative;
14 | margin-bottom: 20px;
15 | border-radius: 4px;
16 | overflow: hidden;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/side-images/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 { CommonModule } from '@angular/common'
7 | import type { ImageProps } from 'src/types'
8 | import { JumpService } from 'src/services/jump'
9 | import { SideImageComponent } from './image/index.component'
10 |
11 | @Component({
12 | standalone: true,
13 | imports: [CommonModule, SideImageComponent],
14 | selector: 'app-side-images',
15 | templateUrl: './index.component.html',
16 | styleUrls: ['./index.component.scss'],
17 | })
18 | export class SideImagesComponent {
19 | @Input() data: ImageProps[] = []
20 |
21 | constructor(public jumpService: JumpService) {}
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/swiper/index.component.html:
--------------------------------------------------------------------------------
1 | 0"
5 | >
6 |
1"
10 | >
11 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/swiper/index.component.scss:
--------------------------------------------------------------------------------
1 | .swiper {
2 | user-select: none;
3 | overflow: hidden;
4 | ::ng-deep {
5 | .slick-slide,
6 | .slick-track,
7 | .slick-list {
8 | height: auto !important;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/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 { CommonModule } from '@angular/common'
7 | import { JumpService } from 'src/services/jump'
8 | import { NzCarouselModule } from 'ng-zorro-antd/carousel'
9 | import type { ImageProps } from 'src/types'
10 | import { SwiperItemComponent } from './swiper-item/index.component'
11 |
12 | @Component({
13 | standalone: true,
14 | imports: [NzCarouselModule, CommonModule, SwiperItemComponent],
15 | changeDetection: ChangeDetectionStrategy.OnPush,
16 | selector: 'app-swiper',
17 | templateUrl: './index.component.html',
18 | styleUrls: ['./index.component.scss'],
19 | })
20 | export class SwiperComponent {
21 | @Input() images: ImageProps[] = []
22 | @Input() autoplay = true
23 | @Input() height = 300
24 |
25 | constructor(public jumpService: JumpService) {}
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/swiper/swiper-item/index.component.html:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/swiper/swiper-item/index.component.scss:
--------------------------------------------------------------------------------
1 | .bgimg {
2 | width: 100%;
3 | object-fit: cover;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/swiper/swiper-item/index.component.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 |
5 | import { Component, Input, ViewChild, ElementRef } from '@angular/core'
6 | import { CommonModule } from '@angular/common'
7 | import type { ImageProps } from 'src/types'
8 | import { parseHtmlWithContent, parseLoadingWithContent } from 'src/utils/utils'
9 | import { SafeHtmlPipe } from 'src/pipe/safeHtml.pipe'
10 | import { CODE_SYMBOL } from 'src/constants/symbol'
11 |
12 | @Component({
13 | standalone: true,
14 | imports: [CommonModule, SafeHtmlPipe],
15 | selector: 'app-swiper-item',
16 | templateUrl: './index.component.html',
17 | styleUrls: ['./index.component.scss'],
18 | })
19 | export class SwiperItemComponent {
20 | @Input() data: ImageProps = {} as ImageProps
21 | @Input() height!: number
22 | @ViewChild('root', { static: false }) root!: ElementRef
23 |
24 | isCode = false
25 | html = ''
26 |
27 | constructor() {}
28 |
29 | ngOnInit() {
30 | this.isCode = this.data.url[0] === CODE_SYMBOL
31 | if (this.isCode) {
32 | this.html = parseLoadingWithContent(this.data.url)
33 | }
34 | }
35 |
36 | ngAfterViewInit() {
37 | this.parseDescription()
38 | }
39 |
40 | private parseDescription() {
41 | if (this.isCode) {
42 | parseHtmlWithContent(this.root?.nativeElement, this.data.url)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/tag-list/index.component.html:
--------------------------------------------------------------------------------
1 |
2 |
9 | {{ tagMap[item.id] && tagMap[item.id].name }}
10 |
11 |
12 |
13 |
21 |
29 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/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 | min-height: 15px;
15 | &:hover {
16 | opacity: 0.8;
17 | }
18 | }
19 | .tag-action {
20 | display: none;
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/tag-list/index.component.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 | import { Component, Input, EventEmitter, Output } from '@angular/core'
5 | import { CommonModule } from '@angular/common'
6 | import type { IWebTag } from 'src/types'
7 | import { tagMap, settings } from 'src/store'
8 | import { JumpService } from 'src/services/jump'
9 | import { isLogin, getPermissions } from 'src/utils/user'
10 | import { NzIconModule } from 'ng-zorro-antd/icon'
11 | import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'
12 | import { $t } from 'src/locale'
13 |
14 | @Component({
15 | standalone: true,
16 | imports: [CommonModule, NzIconModule, NzPopconfirmModule],
17 | selector: 'tag-list',
18 | templateUrl: './index.component.html',
19 | styleUrls: ['./index.component.scss'],
20 | })
21 | export class TagListComponent {
22 | @Input() data?: IWebTag[] | undefined = []
23 | @Input() action: boolean = false
24 | @Output() onDelete = new EventEmitter()
25 | @Output() onMove = new EventEmitter()
26 | @Output() onEdit = new EventEmitter()
27 |
28 | readonly $t = $t
29 | readonly isLogin = isLogin
30 | readonly tagMap = tagMap()
31 | readonly permissions = getPermissions(settings())
32 |
33 | constructor(public jumpService: JumpService) {}
34 |
35 | private handleClick(e: any) {
36 | e.stopPropagation()
37 | e.preventDefault()
38 | }
39 |
40 | openEditWebMoal(e: any) {
41 | this.handleClick(e)
42 | this.onEdit.emit()
43 | }
44 |
45 | confirmDel(e: any) {
46 | this.handleClick(e)
47 | this.onDelete.emit()
48 | }
49 |
50 | openMoveWebModal(e: any) {
51 | this.handleClick(e)
52 | this.onMove.emit()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/toolbar-title/index.component.html:
--------------------------------------------------------------------------------
1 |
61 |
--------------------------------------------------------------------------------
/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 | justify-content: space-between;
11 | margin-bottom: 15px;
12 | user-select: none;
13 | margin-top: 10px;
14 | .edit-icon {
15 | display: none;
16 | vertical-align: middle;
17 | margin: 0 5px;
18 | }
19 | &:hover {
20 | .edit-icon {
21 | display: inline-block;
22 | }
23 | }
24 | .add-icon {
25 | position: relative;
26 | z-index: 6;
27 | cursor: pointer;
28 | color: #666;
29 | right: 10px;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/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 { Component, Input, Output, EventEmitter } from '@angular/core'
6 | import { CommonModule } from '@angular/common'
7 | import type { INavThreeProp, INavProps } from 'src/types'
8 | import { isLogin, getPermissions } from 'src/utils/user'
9 | import { navs, settings } from 'src/store'
10 | import { NzIconModule } from 'ng-zorro-antd/icon'
11 | import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'
12 | import { $t } from 'src/locale'
13 | import { isSelfDevelop } from 'src/utils/utils'
14 | import { CommonService } from 'src/services/common'
15 | import event from 'src/utils/mitt'
16 |
17 | @Component({
18 | standalone: true,
19 | imports: [CommonModule, NzIconModule, NzPopconfirmModule],
20 | selector: 'app-toolbar-title',
21 | templateUrl: './index.component.html',
22 | styleUrls: ['./index.component.scss'],
23 | })
24 | export class ToolbarTitleWebComponent {
25 | @Input() dataSource!: INavThreeProp
26 | @Output() onCollapse = new EventEmitter()
27 |
28 | readonly $t = $t
29 | readonly isLogin = isLogin
30 | readonly navs: INavProps[] = navs()
31 | readonly permissions = getPermissions(settings())
32 |
33 | constructor(public commonService: CommonService) {}
34 |
35 | openCreateWebModal() {
36 | event.emit('CREATE_WEB', {
37 | parentId: this.dataSource.id,
38 | })
39 | }
40 |
41 | openMoveModal(e: Event, data: INavThreeProp) {
42 | this.stopPropagation(e)
43 | event.emit('MOVE_WEB', {
44 | id: data.id,
45 | data: [data],
46 | level: 3,
47 | })
48 | }
49 |
50 | async handleDelete(e: Event, id: number) {
51 | this.stopPropagation(e)
52 | event.emit('DELETE_MODAL', {
53 | isClass: true,
54 | ids: [id],
55 | data: this.dataSource,
56 | onOk: () => {
57 | if (!isSelfDevelop) {
58 | event.emit('WEB_REFRESH')
59 | }
60 | },
61 | })
62 | }
63 |
64 | stopPropagation(e: Event) {
65 | e.stopPropagation()
66 | e.preventDefault()
67 | }
68 |
69 | handleEditName(e: Event, data: INavThreeProp) {
70 | this.stopPropagation(e)
71 | event.emit('EDIT_CLASS_OPEN', { ...data })
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/upload-file/index.component.html:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/components/upload-file/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/upload-file/index.component.scss
--------------------------------------------------------------------------------
/src/components/upload-file/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 { CommonModule } from '@angular/common'
7 | import { $t } from 'src/locale'
8 | import { NzMessageService } from 'ng-zorro-antd/message'
9 | import { createFile } from 'src/api'
10 | import { NzIconModule } from 'ng-zorro-antd/icon'
11 |
12 | @Component({
13 | standalone: true,
14 | imports: [CommonModule, NzIconModule],
15 | selector: 'app-upload-file',
16 | templateUrl: './index.component.html',
17 | styleUrls: ['./index.component.scss'],
18 | })
19 | export class UploadFileComponent {
20 | @Output() onChange = new EventEmitter()
21 |
22 | readonly $t = $t
23 | uploading: boolean = false
24 | // @ts-ignore
25 | id = `f${Date.now()}${parseInt(Math.random() * 1000000)}`
26 |
27 | constructor(private message: NzMessageService) {}
28 |
29 | onChangeFile(e: any): any {
30 | if (this.uploading) {
31 | return
32 | }
33 |
34 | const { files } = e.target
35 | if (files.length <= 0) return
36 | const file = files[0]
37 |
38 | this.onUpload(file).finally(() => {
39 | e.target.value = ''
40 | })
41 | }
42 |
43 | onUpload(file: File) {
44 | const that = this
45 | return new Promise((resolve, reject) => {
46 | const fileReader = new FileReader()
47 | fileReader.readAsDataURL(file)
48 | fileReader.onerror = reject
49 | fileReader.onload = function () {
50 | that.uploading = true
51 | const iconUrl = this.result as string
52 | const url = iconUrl.split(',')[1]
53 | const path = file.name
54 |
55 | createFile({
56 | message: `create ${path}`,
57 | content: url,
58 | path,
59 | })
60 | .then((res) => {
61 | const params = {
62 | cdn: res?.data.filePath,
63 | }
64 | window.open(params.cdn)
65 | that.onChange.emit(params)
66 | that.message.success($t('_uploadSuccess'))
67 | resolve(params)
68 | })
69 | .catch(reject)
70 | .finally(() => {
71 | that.uploading = false
72 | })
73 | }
74 | })
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/upload-image/index.component.html:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/components/upload-image/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/components/upload-image/index.component.scss
--------------------------------------------------------------------------------
/src/components/upload-image/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, Input } from '@angular/core'
6 | import { CommonModule } from '@angular/common'
7 | import { $t } from 'src/locale'
8 | import { NzMessageService } from 'ng-zorro-antd/message'
9 | import { createImageFile, getCDN, getImageRepo } from 'src/api'
10 | import { NzIconModule } from 'ng-zorro-antd/icon'
11 | import { isSelfDevelop } from 'src/utils/utils'
12 |
13 | @Component({
14 | standalone: true,
15 | imports: [CommonModule, NzIconModule],
16 | selector: 'app-upload-image',
17 | templateUrl: './index.component.html',
18 | styleUrls: ['./index.component.scss'],
19 | })
20 | export class UploadImageComponent {
21 | @Input() accept = 'image/*'
22 | @Output() onChange = new EventEmitter()
23 |
24 | readonly $t = $t
25 | uploading: boolean = false
26 | // @ts-ignore
27 | id = `f${Date.now()}${parseInt(Math.random() * 1000000)}`
28 |
29 | constructor(private message: NzMessageService) {}
30 |
31 | onChangeFile(e: any): any {
32 | if (this.uploading) {
33 | return
34 | }
35 |
36 | const { files } = e.target
37 | if (files.length <= 0) return
38 | const file = files[0]
39 |
40 | if (!file.type.startsWith('image')) {
41 | return this.message.error($t('_notUpload'))
42 | }
43 | this.onUpload(file).finally(() => {
44 | e.target.value = ''
45 | })
46 | }
47 |
48 | onUpload(file: File) {
49 | const that = this
50 | return new Promise((resolve, reject) => {
51 | const fileReader = new FileReader()
52 | fileReader.readAsDataURL(file)
53 | fileReader.onerror = reject
54 | fileReader.onload = function () {
55 | that.uploading = true
56 | const iconUrl = this.result as string
57 | const url = iconUrl.split(',')[1]
58 | const mime = `.${file.name.split('.').at(-1) || 'png'}`
59 | const path = `${Date.now()}${mime}`
60 |
61 | createImageFile({
62 | branch: getImageRepo().branch,
63 | message: 'create image',
64 | content: url,
65 | isEncode: false,
66 | path,
67 | })
68 | .then((res) => {
69 | const params = {
70 | cdn: isSelfDevelop ? res?.data?.fullImagePath : getCDN(path),
71 | }
72 | that.onChange.emit(params)
73 | that.message.success($t('_uploadSuccess'))
74 | resolve(params)
75 | })
76 | .catch(reject)
77 | .finally(() => {
78 | that.uploading = false
79 | })
80 | }
81 | })
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/components/web-list/index.component.html:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/components/web-list/index.component.scss:
--------------------------------------------------------------------------------
1 | @media (max-width: 768px) {
2 | .web-list {
3 | &.overflowScroll .wrapper {
4 | padding: 0;
5 | flex-wrap: nowrap;
6 | overflow: hidden;
7 | overflow-x: auto;
8 | }
9 | }
10 | }
11 |
12 | .web-list {
13 | margin-top: 15px;
14 | margin-bottom: 15px;
15 | display: flex;
16 | padding: 0 10px;
17 | flex-wrap: wrap;
18 | .wrapper {
19 | display: flex;
20 | flex-wrap: wrap;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/web-more-menu/index.component.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | -
19 | {{ item.title }}
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/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/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 { CommonModule } from '@angular/common'
7 | import type { INavProps } from 'src/types'
8 | import { NzDropDownModule } from 'ng-zorro-antd/dropdown'
9 |
10 | @Component({
11 | standalone: true,
12 | imports: [CommonModule, NzDropDownModule],
13 | selector: 'app-web-more-menu',
14 | templateUrl: './index.component.html',
15 | styleUrls: ['./index.component.scss'],
16 | })
17 | export class WebMoreMenuComponent {
18 | @Input() index = 0
19 | @Input() data: INavProps[] = []
20 | @Input() page = 0
21 | @Output() onClick = new EventEmitter()
22 |
23 | ngOnInit() {}
24 |
25 | handleCilck(id: number) {
26 | this.onClick?.emit?.(id)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/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 | DATE_TIME: 's_url',
22 | IS_DARK: 'isDark',
23 | WEBSITE: 'WEBSITE_DB',
24 | SEARCH_ENGINE: 'engine',
25 | LANGUAGE: 'language',
26 | AUTH_CODE: 'AUTH_CODE',
27 | SIDE_COLLAPSED: 'SIDE_COLLAPSED',
28 | FIXBAR_OPEN: 'FIXBAR_OPEN',
29 | SYSTEM_COLLAPSED: 'SYSTEM_COLLAPSED',
30 | NEWS: 'NEWS',
31 | NEWS_DATE: 'NEWS_DATE',
32 | COMPONENT_COLLAPSED: 'COMPONENT_COLLAPSED',
33 | IMAGE_TOKEN: 'IMAGE_TOKEN',
34 | }
35 |
--------------------------------------------------------------------------------
/src/constants/symbol.ts:
--------------------------------------------------------------------------------
1 | export const CODE_SYMBOL = '!'
2 | export const SELF_SYMBOL = '^'
3 | export const ROUTER_SYMBOL = '@'
4 |
5 | export const DEFAULT_SORT_INDEX = 100000
6 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | }
4 |
--------------------------------------------------------------------------------
/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/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 localStorage.getItem(STORAGE_KEY_MAP.LANGUAGE) || settings().language
14 | }
15 |
16 | const l = getLocale()
17 |
18 | export function $t(s: string, map?: Record): string {
19 | function replaceStr(s: string, map?: Record) {
20 | if (map) {
21 | for (const k in map) {
22 | s = s.replaceAll(`{${k}}`, map[k])
23 | }
24 | }
25 | return s
26 | }
27 | if (l === 'zh-CN') {
28 | return replaceStr(o.cn[s], map)
29 | }
30 | return replaceStr(o.en[s] ?? o.cn[s], map)
31 | }
32 |
33 | export function isZhCN(): boolean {
34 | return l === 'zh-CN'
35 | }
36 |
37 | export default o
38 |
--------------------------------------------------------------------------------
/src/main.html:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser'
2 | import { appConfig } from './app/app.config'
3 | import { AppComponent } from './app/app.component'
4 |
5 | bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err))
6 |
--------------------------------------------------------------------------------
/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 | standalone: true,
10 | name: 'safeHtml',
11 | })
12 | export class SafeHtmlPipe implements PipeTransform {
13 | constructor(private sanitized: DomSanitizer) {}
14 |
15 | transform(value: string): any {
16 | return this.sanitized.bypassSecurityTrustHtml(value)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/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 | import { CODE_SYMBOL, SELF_SYMBOL, ROUTER_SYMBOL } from 'src/constants/symbol'
8 | import event from 'src/utils/mitt'
9 |
10 | @Injectable({
11 | providedIn: 'root',
12 | })
13 | export class JumpService {
14 | constructor(private router: Router) {}
15 |
16 | goUrl(e: any, url: string | null | undefined) {
17 | e?.stopPropagation?.()
18 | e?.preventDefault?.()
19 |
20 | if (typeof url !== 'string' || !url) {
21 | return
22 | }
23 | const firstSymbol = url[0]
24 |
25 | // Code
26 | if (firstSymbol === CODE_SYMBOL) {
27 | return
28 | }
29 |
30 | if (url === '@apply') {
31 | event.emit('CREATE_WEB')
32 | return
33 | }
34 |
35 | if (firstSymbol === ROUTER_SYMBOL) {
36 | this.router.navigate([url.slice(1)])
37 | return
38 | }
39 |
40 | const self = firstSymbol === SELF_SYMBOL
41 | if (self) {
42 | window.open(url.slice(1), '_self')
43 | } else {
44 | window.open(url)
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 | import { signal, computed } from '@angular/core'
5 | import dbJson from '../../data/db.json'
6 | import searchJson from '../../data/search.json'
7 | import settingsJson from '../../data/settings.json'
8 | import tagJson from '../../data/tag.json'
9 | import internalJson from '../../data/internal.json'
10 | import componentJson from '../../data/component.json'
11 | import type {
12 | ISettings,
13 | ISearchProps,
14 | ITagProp,
15 | InternalProps,
16 | ITagPropValues,
17 | INavProps,
18 | IComponentProps,
19 | } from 'src/types'
20 | import { isSelfDevelop } from 'src/utils/utils'
21 |
22 | export const settings = signal(settingsJson as ISettings)
23 |
24 | export const search = signal(
25 | isSelfDevelop ? ({} as ISearchProps) : searchJson,
26 | )
27 |
28 | export const tagList = signal>(
29 | isSelfDevelop ? [] : tagJson,
30 | )
31 |
32 | export const tagMap = computed(() => {
33 | const map: ITagProp = {}
34 | tagList().forEach((item) => {
35 | if (item.id) {
36 | map[item.id] = {
37 | ...item,
38 | }
39 | }
40 | })
41 | return map
42 | })
43 |
44 | export const internal = signal(internalJson)
45 |
46 | export const navs = signal(
47 | isSelfDevelop ? [] : (dbJson as INavProps[]),
48 | )
49 |
50 | export const component = signal(
51 | isSelfDevelop ? { zoom: 1, components: [] } : componentJson,
52 | )
53 |
--------------------------------------------------------------------------------
/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 | var __HASH_MODE__: boolean | undefined
10 | var __ADDRESS__: string | undefined
11 | var __FINISHED__: boolean // 记录已取 web 数据
12 | var __TITLE__: string = undefined
13 | var __PWA_ENABLE__: boolean | undefined
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/mitt.ts:
--------------------------------------------------------------------------------
1 | // 开源项目,未经作者同意,不得以抄袭/复制代码/修改源代码版权信息。
2 | // Copyright @ 2018-present xiejiahe. All rights reserved.
3 | // See https://github.com/xjh22222228/nav
4 | import mitt from 'mitt'
5 |
6 | export default mitt()
7 |
--------------------------------------------------------------------------------
/src/utils/sw.ts:
--------------------------------------------------------------------------------
1 | export async function unregisterServiceWorkers(): Promise {
2 | try {
3 | // 卸载所有Service Worker
4 | if ('serviceWorker' in navigator) {
5 | const registrations = await navigator.serviceWorker.getRegistrations()
6 |
7 | console.log(`找到 ${registrations.length} 个Service Worker注册`)
8 |
9 | for (const registration of registrations) {
10 | await registration.unregister()
11 | console.log('成功卸载Service Worker:', registration.scope)
12 | }
13 | }
14 |
15 | // 清理所有缓存
16 | if ('caches' in window) {
17 | const cacheNames = await caches.keys()
18 |
19 | console.log(`找到 ${cacheNames.length} 个缓存`)
20 |
21 | for (const cacheName of cacheNames) {
22 | await caches.delete(cacheName)
23 | console.log('成功删除缓存:', cacheName)
24 | }
25 | }
26 |
27 | return true
28 | } catch (error) {
29 | console.error('清理Service Worker失败:', error)
30 | return false
31 | }
32 | }
33 |
34 | export function isPwaMode(): boolean {
35 | const isStandalone = window.matchMedia('(display-mode: standalone)').matches
36 | const isFullscreen = window.matchMedia('(display-mode: fullscreen)').matches
37 | const isMinimalUI = window.matchMedia('(display-mode: minimal-ui)').matches
38 | const isIOSStandalone =
39 | 'standalone' in navigator && (navigator.standalone as boolean)
40 | return isStandalone || isFullscreen || isMinimalUI || isIOSStandalone
41 | }
42 |
--------------------------------------------------------------------------------
/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 | import { ActionType } from 'src/types'
7 | import type { ISettings } from 'src/types'
8 |
9 | export function getToken() {
10 | return globalThis.localStorage?.getItem(STORAGE_KEY_MAP.TOKEN) || ''
11 | }
12 |
13 | export function getAuthCode() {
14 | return globalThis.localStorage.getItem(STORAGE_KEY_MAP.AUTH_CODE) || ''
15 | }
16 |
17 | export function removeAuthCode() {
18 | return globalThis.localStorage.removeItem(STORAGE_KEY_MAP.AUTH_CODE)
19 | }
20 |
21 | export function setAuthCode(c: string) {
22 | return globalThis.localStorage.setItem(STORAGE_KEY_MAP.AUTH_CODE, c.trim())
23 | }
24 |
25 | export function setToken(token: string) {
26 | return globalThis.localStorage.setItem(STORAGE_KEY_MAP.TOKEN, token)
27 | }
28 |
29 | export function removeToken() {
30 | return globalThis.localStorage.removeItem(STORAGE_KEY_MAP.TOKEN)
31 | }
32 |
33 | export function getImageToken() {
34 | return globalThis.localStorage?.getItem(STORAGE_KEY_MAP.IMAGE_TOKEN) || ''
35 | }
36 |
37 | export function setImageToken(token: string) {
38 | return globalThis.localStorage.setItem(STORAGE_KEY_MAP.IMAGE_TOKEN, token)
39 | }
40 |
41 | export function removeWebsite() {
42 | return localforage.removeItem(STORAGE_KEY_MAP.WEBSITE)
43 | }
44 |
45 | export function userLogout() {
46 | const removeKeys = [
47 | STORAGE_KEY_MAP.TOKEN,
48 | STORAGE_KEY_MAP.IMAGE_TOKEN,
49 | STORAGE_KEY_MAP.WEBSITE,
50 | ]
51 | localforage.clear()
52 | Array.from({ length: globalThis.localStorage.length }, (_, i) => {
53 | return globalThis.localStorage.key(i)
54 | }).forEach((key) => {
55 | if (key && removeKeys.includes(key)) {
56 | globalThis.localStorage.removeItem(key)
57 | }
58 | })
59 | Array.from({ length: globalThis.sessionStorage.length }, (_, i) => {
60 | return globalThis.sessionStorage.key(i)
61 | }).forEach((key) => {
62 | if (key && removeKeys.includes(key)) {
63 | globalThis.sessionStorage.removeItem(key)
64 | }
65 | })
66 | }
67 |
68 | export const isLogin: boolean = !!getToken()
69 |
70 | let create: null | boolean = null
71 | let edit: null | boolean = null
72 | let del: null | boolean = null
73 |
74 | export function getPermissions(settings: ISettings) {
75 | if (create == null) {
76 | create = settings.userActions.includes(ActionType.Create)
77 | edit = settings.userActions.includes(ActionType.Edit)
78 | del = settings.userActions.includes(ActionType.Delete)
79 | }
80 |
81 | const c = create || false
82 | const e = edit || false
83 | const d = del || false
84 |
85 | return {
86 | create: c,
87 | edit: e,
88 | del: d,
89 | ok: c || e || d,
90 | } as const
91 | }
92 |
--------------------------------------------------------------------------------
/src/utils/utils.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 | import { CODE_SYMBOL } from 'src/constants/symbol'
9 |
10 | export const isSelfDevelop = !!navConfig.address
11 |
12 | interface TemplateData {
13 | total: number
14 | hostname: string
15 | year: number
16 | }
17 |
18 | export function compilerTemplate(str: string): string {
19 | const { loginViewCount, userViewCount } = internal()
20 | const data: TemplateData = {
21 | total: isLogin ? loginViewCount : userViewCount,
22 | hostname: window.location.hostname,
23 | year: new Date().getFullYear(),
24 | }
25 |
26 | return Object.entries(data).reduce(
27 | (result, [key, value]) => result.replaceAll(`\${${key}}`, String(value)),
28 | str,
29 | )
30 | }
31 |
32 | const DARK_THEME = {
33 | cssUrl: navConfig.zorroDark,
34 | cssId: 'dark-css',
35 | classes: ['dark-container', 'dark'],
36 | } as const
37 |
38 | export function addDark(): void {
39 | const darkNode = document.getElementById(DARK_THEME.cssId)
40 | if (darkNode) return
41 |
42 | const link = document.createElement('link')
43 | link.rel = 'stylesheet'
44 | link.href = DARK_THEME.cssUrl
45 | link.id = DARK_THEME.cssId
46 | document.body.appendChild(link)
47 | document.documentElement.classList.add(...DARK_THEME.classes)
48 | }
49 |
50 | export function removeDark(): void {
51 | const darkNode = document.getElementById(DARK_THEME.cssId)
52 | document.documentElement.classList.remove(...DARK_THEME.classes)
53 | darkNode?.parentNode?.removeChild(darkNode)
54 | }
55 |
56 | export function parseHtmlWithContent(node: HTMLElement, str: string) {
57 | if (str[0] === CODE_SYMBOL) {
58 | if (!node) return
59 | const s = node.querySelectorAll('script')
60 | s.forEach((script) => {
61 | script.parentNode?.removeChild(script)
62 | })
63 |
64 | const parser = new DOMParser()
65 | const doc = parser.parseFromString(str, 'text/html')
66 | const scripts = doc.querySelectorAll('script')
67 | scripts.forEach((script) => {
68 | const newScript: any = document.createElement('script')
69 | const text = script.textContent?.trim() || ''
70 | const attributes = script.attributes
71 | for (let i = 0; i < attributes.length; i++) {
72 | const attr = attributes[i]
73 | newScript[attr.name] = attr.value
74 | }
75 | if (text) {
76 | newScript.textContent = `{${text}}`
77 | }
78 | node.appendChild(newScript)
79 | })
80 | }
81 | }
82 |
83 | export function parseLoadingWithContent(str: string): string {
84 | if (str[0] !== '!') {
85 | return str
86 | }
87 | const loadingHtml = `
88 |
89 |
92 |
93 | `
94 | str = str.slice(1)
95 | if (str.includes('#loadingx1')) {
96 | return str.replaceAll('#loadingx1', '') + loadingHtml
97 | }
98 | return str
99 | }
100 |
101 | export function getTempId() {
102 | return -Date.now()
103 | }
104 |
--------------------------------------------------------------------------------
/src/view/light/index.component.scss:
--------------------------------------------------------------------------------
1 | .index-wrapper {
2 | display: flex;
3 | flex-direction: column;
4 | height: 100vh;
5 | padding-top: 0;
6 | overflow: hidden;
7 | .light-box {
8 | display: flex;
9 | justify-content: center;
10 | height: 100%;
11 | margin: 0 auto;
12 | column-gap: 20px;
13 | overflow: hidden;
14 | }
15 | .component-box {
16 | max-width: 320px;
17 | overflow: hidden;
18 | overflow-y: auto;
19 | }
20 | }
21 |
22 | .homepage {
23 | height: 100%;
24 | position: relative;
25 | background: #f9f9f9;
26 | border-radius: 5px;
27 | overflow: hidden;
28 | transition: 0.1s linear;
29 | border: 1px solid transparent;
30 |
31 | .top-nav {
32 | padding: 10px 0;
33 | overflow: none;
34 | overflow-x: auto;
35 | white-space: nowrap;
36 | border-bottom: 1px solid #eee;
37 | text-align: center;
38 | user-select: none;
39 |
40 | ::ng-deep .more-btn,
41 | .ripple-btn {
42 | height: 40px;
43 | position: relative;
44 | padding: 0 15px;
45 | color: #000;
46 | cursor: pointer;
47 | border-radius: 5px;
48 | overflow: hidden;
49 | display: inline-flex;
50 | align-items: center;
51 | &.active::after {
52 | content: '';
53 | position: absolute;
54 | bottom: 0;
55 | left: 20%;
56 | right: 20%;
57 | height: 3px;
58 | border-radius: 2px;
59 | background-color: rgb(16, 142, 233);
60 | }
61 | }
62 | }
63 | }
64 |
65 | .index-section {
66 | position: relative;
67 | height: calc(100% - 52px);
68 | overflow: hidden;
69 | display: flex;
70 |
71 | // 侧边栏分类
72 | $sidebarWidth: 80px;
73 | .sidebar {
74 | position: relative;
75 | width: $sidebarWidth;
76 | min-width: $sidebarWidth;
77 | height: 98%;
78 | text-align: center;
79 | border-right: 1px solid #eee;
80 | overflow: hidden;
81 | overflow-y: auto;
82 |
83 | .tag {
84 | position: relative;
85 | cursor: pointer;
86 | padding: 11px 1px;
87 | overflow-wrap: break-word;
88 | &.active {
89 | color: #1890ff;
90 | &::after,
91 | &::before {
92 | content: '';
93 | position: absolute;
94 | left: 0;
95 | width: 100%;
96 | }
97 | &:after {
98 | top: 0;
99 | border-top: 1px dashed #ccc;
100 | }
101 | &:before {
102 | content: '';
103 | bottom: 0;
104 | border-bottom: 1px dashed #ccc;
105 | }
106 | }
107 | }
108 | }
109 |
110 | .main {
111 | flex: 1;
112 | padding-bottom: 50px;
113 | overflow: hidden;
114 | overflow-y: auto;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/view/mobile/index.component.html:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
30 |
31 |
48 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/view/mobile/index.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: 999;
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 | }
128 |
--------------------------------------------------------------------------------
/src/view/mobile/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 { CommonModule } from '@angular/common'
7 | import { CommonService } from 'src/services/common'
8 | import { SearchComponent } from 'src/components/search/index.component'
9 | import { CardComponent } from 'src/components/card/index.component'
10 | import { FooterComponent } from 'src/components/footer/footer.component'
11 | import { ToolbarTitleWebComponent } from 'src/components/toolbar-title/index.component'
12 |
13 | @Component({
14 | standalone: true,
15 | imports: [
16 | CommonModule,
17 | SearchComponent,
18 | CardComponent,
19 | FooterComponent,
20 | ToolbarTitleWebComponent,
21 | ],
22 | selector: 'app-mobile',
23 | templateUrl: './index.component.html',
24 | styleUrls: ['./index.component.scss'],
25 | })
26 | export default class MobileComponent {
27 | open: boolean = false
28 |
29 | constructor(public commonService: CommonService) {}
30 |
31 | handleCilckNav(id: number) {
32 | this.commonService.handleClickClass(id)
33 | this.handleToggleOpen()
34 | }
35 |
36 | handleToggleOpen() {
37 | this.open = !this.open
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/view/shortcut/index.component.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
13 | {{ settings.shortcutTitle }}
14 |
15 |
16 |
{{ hours }}:{{ minutes }}:{{ seconds }}
17 |
18 | {{ month }}{{ $t('_shortMonth') }}{{ date }}{{ $t('_shortDay') }}
21 | {{ dayText }}
22 |
23 |
24 |
25 |
26 |
33 |
34 |
63 |
64 |
65 |
66 |
67 |
68 |
80 |
--------------------------------------------------------------------------------
/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 | ::ng-deep {
28 | .web-list {
29 | .common-icon {
30 | width: 50px !important;
31 | height: 50px !important;
32 | min-width: 50px !important;
33 | min-height: 50px !important;
34 | }
35 | .name {
36 | width: 75px !important;
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
43 | .shortcut {
44 | position: fixed;
45 | top: 0;
46 | left: 0;
47 | right: 0;
48 | bottom: 0;
49 | display: flex;
50 | justify-content: center;
51 | padding-top: 20px;
52 | text-align: center;
53 | background-color: rgba(0, 0, 0, 0.02);
54 | background-repeat: no-repeat;
55 | background-size: cover;
56 | background-position: center;
57 | user-select: none;
58 | transition: all 0.1s linear;
59 | &.hasComponent {
60 | padding-top: 80px;
61 | }
62 | .box2 {
63 | width: 100%;
64 | display: flex;
65 | flex-direction: column;
66 | }
67 | .flex1 {
68 | flex: 1;
69 | padding: 0 var(--padding, 200px);
70 | margin-bottom: 20px;
71 | overflow: hidden;
72 | overflow-y: auto;
73 | &::-webkit-scrollbar {
74 | display: none;
75 | }
76 | }
77 | .dock {
78 | padding-bottom: 10px;
79 | .dock-box {
80 | background-color: rgba(255, 255, 255, 0.4);
81 | border-radius: 16px;
82 | height: var(--dockHeight, 66px);
83 | padding: 0 8px;
84 | padding-bottom: 8px;
85 | box-shadow: rgba(0, 0, 0, 0.25) 0px 0px 12px 0px;
86 | display: inline-flex;
87 | justify-content: center;
88 | align-items: flex-end;
89 | gap: 16px;
90 | max-width: 100%;
91 | &::-webkit-scrollbar {
92 | display: none;
93 | }
94 | }
95 | .dock-item {
96 | cursor: pointer;
97 | }
98 | ::ng-deep {
99 | .icon {
100 | background-color: transparent;
101 | }
102 | .common-icon {
103 | width: var(--iconWidth, 50px);
104 | height: var(--iconWidth, 50px);
105 | }
106 | }
107 | }
108 |
109 | .title {
110 | font-size: 30px;
111 | font-weight: bold;
112 | }
113 |
114 | ::ng-deep {
115 | .name {
116 | color: #fff !important;
117 | }
118 | }
119 | .datetime {
120 | font-size: 60px;
121 | color: #fff;
122 | line-height: 1;
123 | font-family: text;
124 | font-weight: bold;
125 | }
126 | .days {
127 | font-size: 20px;
128 | font-weight: bold;
129 | color: rgba(249, 250, 251, 0.85);
130 | margin-bottom: 15px;
131 | margin-top: 5px;
132 | font-family: text;
133 | }
134 | .margin {
135 | margin: 0 10px;
136 | }
137 | }
138 |
139 | .tianqi {
140 | position: fixed;
141 | top: 0;
142 | right: 100px;
143 | }
144 |
--------------------------------------------------------------------------------
/src/view/sim/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 | ViewChild,
8 | ElementRef,
9 | ViewChildren,
10 | QueryList,
11 | } from '@angular/core'
12 | import { CommonModule } from '@angular/common'
13 | import { settings } from 'src/store'
14 | import { compilerTemplate } from 'src/utils/utils'
15 | import { scrollIntoViewLeft } from 'src/utils'
16 | import { CommonService } from 'src/services/common'
17 | import { ComponentGroupComponent } from 'src/components/component-group/index.component'
18 | import { WebMoreMenuComponent } from 'src/components/web-more-menu/index.component'
19 | import { SearchComponent } from 'src/components/search/index.component'
20 | import { NzSpinModule } from 'ng-zorro-antd/spin'
21 | import { NzToolTipModule } from 'ng-zorro-antd/tooltip'
22 | import { CardComponent } from 'src/components/card/index.component'
23 | import { NoDataComponent } from 'src/components/no-data/no-data.component'
24 | import { FooterComponent } from 'src/components/footer/footer.component'
25 | import { FixbarComponent } from 'src/components/fixbar/index.component'
26 | import { SwiperComponent } from 'src/components/swiper/index.component'
27 | import { SafeHtmlPipe } from 'src/pipe/safeHtml.pipe'
28 | import { ToolbarTitleWebComponent } from 'src/components/toolbar-title/index.component'
29 | import { ClassTabsComponent } from 'src/components/class-tabs/index.component'
30 | import { PhoneClassComponent } from 'src/components/phone-class/index.component'
31 | import type { INavProps } from 'src/types'
32 |
33 | @Component({
34 | standalone: true,
35 | imports: [
36 | CommonModule,
37 | ToolbarTitleWebComponent,
38 | ComponentGroupComponent,
39 | WebMoreMenuComponent,
40 | SearchComponent,
41 | NzSpinModule,
42 | NzToolTipModule,
43 | CardComponent,
44 | NoDataComponent,
45 | FooterComponent,
46 | FixbarComponent,
47 | SwiperComponent,
48 | SafeHtmlPipe,
49 | ClassTabsComponent,
50 | PhoneClassComponent,
51 | ],
52 | selector: 'app-sim',
53 | templateUrl: './index.component.html',
54 | styleUrls: ['./index.component.scss'],
55 | })
56 | export default class SimComponent {
57 | @ViewChild('parent') parentRef!: ElementRef
58 | @ViewChildren('item') itemsRef!: QueryList
59 |
60 | readonly description: string = compilerTemplate(settings().simThemeDesc)
61 |
62 | constructor(public commonService: CommonService) {}
63 |
64 | ngOnDestroy() {
65 | this.commonService.setOverIndex()
66 | }
67 |
68 | get isEllipsis() {
69 | return this.commonService.settings().simOverType === 'ellipsis'
70 | }
71 |
72 | ngAfterViewInit() {
73 | if (this.isEllipsis) {
74 | this.commonService.getOverIndex('.top-nav .over-item')
75 | } else {
76 | scrollIntoViewLeft(
77 | this.parentRef.nativeElement,
78 | this.itemsRef.toArray()[this.commonService.oneIndex].nativeElement,
79 | {
80 | behavior: 'auto',
81 | },
82 | )
83 | }
84 | }
85 |
86 | handleClickTop(e: any, data: INavProps) {
87 | this.commonService.handleClickClass(data.id)
88 | if (!this.isEllipsis) {
89 | scrollIntoViewLeft(this.parentRef.nativeElement, e.target)
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/view/system/auth/index.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
23 | {{ $t('_bindDomain') }}
24 |
25 |
26 |
27 |
35 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/view/system/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 |
--------------------------------------------------------------------------------
/src/view/system/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 { FormsModule } from '@angular/forms'
7 | import { CommonModule } from '@angular/common'
8 | import { $t } from 'src/locale'
9 | import { NzMessageService } from 'ng-zorro-antd/message'
10 | import { setAuthCode, getAuthCode, removeAuthCode } from 'src/utils/user'
11 | import { getUserInfo, updateUserInfo } from 'src/api'
12 | import { NzInputModule } from 'ng-zorro-antd/input'
13 | import { NzButtonModule } from 'ng-zorro-antd/button'
14 | import { NzSpinModule } from 'ng-zorro-antd/spin'
15 |
16 | @Component({
17 | standalone: true,
18 | imports: [
19 | CommonModule,
20 | FormsModule,
21 | NzInputModule,
22 | NzButtonModule,
23 | NzSpinModule,
24 | ],
25 | selector: 'auth',
26 | templateUrl: './index.component.html',
27 | styleUrls: ['./index.component.scss'],
28 | })
29 | export default class AuthComponent {
30 | readonly $t = $t
31 | submitting: boolean = false
32 | isPermission = !!getAuthCode()
33 | authCode = ''
34 | url = ''
35 |
36 | constructor(private message: NzMessageService) {}
37 |
38 | ngOnInit() {
39 | if (this.isPermission) {
40 | this.getUserInfo()
41 | }
42 | }
43 |
44 | private async getUserInfo(params?: any) {
45 | this.submitting = true
46 | return getUserInfo(params)
47 | .then((res: any) => {
48 | if (typeof res.data?.data?.url === 'string') {
49 | this.isPermission = true
50 | this.url = res.data.data.url
51 | }
52 | return res
53 | })
54 | .finally(() => {
55 | this.submitting = false
56 | })
57 | }
58 |
59 | handleSubmitAuthCode() {
60 | if (this.submitting || !this.authCode) {
61 | return
62 | }
63 |
64 | this.getUserInfo({ code: this.authCode }).then(() => {
65 | setAuthCode(this.authCode)
66 | window.location.reload()
67 | })
68 | }
69 |
70 | handleSave() {
71 | this.submitting = true
72 | updateUserInfo({
73 | url: this.url,
74 | })
75 | .then(() => {
76 | this.getUserInfo()
77 | this.message.success(this.$t('_saveSuccess'))
78 | })
79 | .finally(() => {
80 | this.submitting = false
81 | })
82 | }
83 |
84 | logoutAuthCode() {
85 | removeAuthCode()
86 | window.location.reload()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/view/system/bookmark-export/index.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
--------------------------------------------------------------------------------
/src/view/system/bookmark-export/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/view/system/bookmark-export/index.component.scss
--------------------------------------------------------------------------------
/src/view/system/bookmark-export/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 { CommonModule } from '@angular/common'
7 | import { FormsModule } from '@angular/forms'
8 | import { $t } from 'src/locale'
9 | import { NzNotificationService } from 'ng-zorro-antd/notification'
10 | import { NzButtonModule } from 'ng-zorro-antd/button'
11 | import type { INavProps } from 'src/types'
12 | import { navs } from 'src/store'
13 | import { bookmarksExport } from 'src/api'
14 | import { saveAs } from 'file-saver'
15 | import { getAuthCode } from 'src/utils/user'
16 | import { NzSwitchModule } from 'ng-zorro-antd/switch'
17 | import { NzSpinModule } from 'ng-zorro-antd/spin'
18 | import LZString from 'lz-string'
19 |
20 | @Component({
21 | standalone: true,
22 | imports: [
23 | CommonModule,
24 | NzSwitchModule,
25 | NzSpinModule,
26 | FormsModule,
27 | NzButtonModule,
28 | ],
29 | selector: 'system-bookmark-export',
30 | templateUrl: './index.component.html',
31 | styleUrls: ['./index.component.scss'],
32 | })
33 | export default class SystemBookmarkExportComponent {
34 | readonly $t = $t
35 | submitting = false
36 |
37 | constructor(private notification: NzNotificationService) {}
38 |
39 | async bookmarksExport(): Promise {
40 | if (!getAuthCode()) {
41 | return this.notification.error('Error', 'Bad credentials')
42 | }
43 |
44 | if (this.submitting) {
45 | return
46 | }
47 | const navsData: INavProps = JSON.parse(JSON.stringify(navs()))
48 |
49 | function removeAttrs(data: any) {
50 | if (!Array.isArray(data)) {
51 | return
52 | }
53 | data.forEach((item) => {
54 | if (item.title) {
55 | for (const k in item) {
56 | const keys = ['title', 'nav', 'icon']
57 | if (!keys.includes(k)) {
58 | delete item[k]
59 | }
60 | }
61 | }
62 | if (item.url) {
63 | for (const k in item) {
64 | const keys = ['url', 'icon', 'name']
65 | if (!keys.includes(k)) {
66 | delete item[k]
67 | }
68 | }
69 | }
70 | if (Array.isArray(item.nav)) {
71 | removeAttrs(item.nav)
72 | }
73 | })
74 | }
75 | removeAttrs(navsData)
76 | this.submitting = true
77 | bookmarksExport({ data: LZString.compress(JSON.stringify(navsData)) })
78 | .then((res) => {
79 | const fileName = 'bookmarks.html'
80 | const blob = new Blob([res.data.data], {
81 | type: 'text/html;charset=utf-8',
82 | })
83 | saveAs(blob, fileName)
84 | this.notification.success('OK', fileName, {
85 | nzDuration: 0,
86 | })
87 | })
88 | .finally(() => {
89 | this.submitting = false
90 | })
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/view/system/bookmark/index.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/src/view/system/bookmark/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/view/system/bookmark/index.component.scss
--------------------------------------------------------------------------------
/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, ViewChild, ElementRef } 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 { NzButtonModule } from 'ng-zorro-antd/button'
10 | import { setNavs } from 'src/utils/web'
11 | import { parseBookmark } from 'src/utils/bookmark'
12 | import { NzInputModule } from 'ng-zorro-antd/input'
13 |
14 | @Component({
15 | standalone: true,
16 | imports: [NzInputModule, NzButtonModule],
17 | selector: 'system-bookmark',
18 | templateUrl: './index.component.html',
19 | styleUrls: ['./index.component.scss'],
20 | })
21 | export default class SystemBookmarkComponent {
22 | @ViewChild('file', { static: false }) file!: ElementRef
23 |
24 | readonly $t = $t
25 |
26 | constructor(
27 | private message: NzMessageService,
28 | private notification: NzNotificationService,
29 | ) {}
30 |
31 | ngOnInit() {}
32 |
33 | onClickFile() {
34 | this.file.nativeElement.click()
35 | }
36 |
37 | onBookChange(e: any) {
38 | const that = this
39 | const { files } = e.target
40 | if (files.length <= 0) return
41 | const file = files[0]
42 | const fileReader = new FileReader()
43 | fileReader.readAsText(file)
44 | fileReader.onload = function () {
45 | const html = this.result as string
46 | try {
47 | const result = parseBookmark(html)
48 | if (!Array.isArray(result)) {
49 | that.notification.error(
50 | $t('_errorBookTip'),
51 | `${result?.message ?? ''}`,
52 | )
53 | } else {
54 | that.message.success($t('_importSuccess'))
55 | setNavs(result)
56 | setTimeout(() => window.location.reload(), 2000)
57 | }
58 | } catch (error: any) {
59 | that.notification.error($t('_errorBookTip'), `${error.message}`)
60 | }
61 | e.target.value = ''
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/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/view/system/component/index.component.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 | Size:
14 |
22 |
23 |
24 |
25 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/view/system/component/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/view/system/component/index.component.scss
--------------------------------------------------------------------------------
/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 | [ComponentType.Countdown]: $t('_countdown'),
14 | [ComponentType.HTML]: 'HTML',
15 | [ComponentType.Holiday]: $t('_holiday'),
16 | [ComponentType.News]: $t('_news'),
17 | }
18 |
--------------------------------------------------------------------------------
/src/view/system/config/index.component.html:
--------------------------------------------------------------------------------
1 |
50 |
--------------------------------------------------------------------------------
/src/view/system/config/index.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xjh22222228/nav/2c7d24c69b2631c2073bb79ea74ea4fcfa80b2ae/src/view/system/config/index.component.scss
--------------------------------------------------------------------------------
/src/view/system/index.component.scss:
--------------------------------------------------------------------------------
1 | .system-layout {
2 | min-height: 100vh;
3 | .topbar {
4 | z-index: 3;
5 | position: sticky;
6 | background-color: #fff;
7 | top: 0;
8 | margin-bottom: 10px;
9 | padding: 15px 15px 0;
10 | box-shadow: 0 5px 5px rgba(0, 0, 0, 0.05);
11 | }
12 | .content {
13 | padding: 0 15px 15px;
14 | background-color: #fff;
15 | height: 100vh;
16 | overflow: hidden;
17 | overflow-y: auto;
18 | ::ng-deep.ant-table-content {
19 | overflow: auto;
20 | }
21 | }
22 | .sidebar {
23 | position: sticky;
24 | top: 0;
25 | background-color: #fff;
26 | &.hide {
27 | width: 0 !important;
28 | min-width: 0 !important;
29 | flex: 0 !important;
30 | }
31 | }
32 | .inner-layout {
33 | margin-left: 15px;
34 | }
35 | .fixed-center {
36 | position: fixed;
37 | top: 50%;
38 | left: 50%;
39 | transform: translate(-50%, -50%);
40 | z-index: 10;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/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/view/system/info/index.component.scss:
--------------------------------------------------------------------------------
1 | p {
2 | margin-bottom: 10px;
3 | }
4 |
--------------------------------------------------------------------------------
/src/view/system/info/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 { CommonModule } from '@angular/common'
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/utils'
11 | import config from '../../../../nav.config.json'
12 |
13 | @Component({
14 | standalone: true,
15 | imports: [CommonModule],
16 | selector: 'system-info',
17 | templateUrl: './index.component.html',
18 | styleUrls: ['./index.component.scss'],
19 | })
20 | export default class SystemInfoComponent {
21 | readonly $t = $t
22 | readonly isSelfDevelop = isSelfDevelop
23 | readonly token = getToken()
24 | readonly config = config
25 | readonly date = config.datetime
26 | readonly currentVersionSrc = `https://img.shields.io/badge/current-v${VERSION}-red.svg?longCache=true&style=flat-square`
27 |
28 | constructor() {}
29 | }
30 |
--------------------------------------------------------------------------------
/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/view/system/setting/index.component.scss:
--------------------------------------------------------------------------------
1 | #file {
2 | cursor: pointer;
3 | input {
4 | display: none;
5 | }
6 | }
7 | .preview {
8 | width: 300px;
9 | }
10 | .save-tip {
11 | margin-top: 10px;
12 | color: #f50;
13 | font-weight: bold;
14 | }
15 | .divider {
16 | height: 1px;
17 | width: 100%;
18 | margin: 20px 0;
19 | background-color: #eee;
20 | }
21 | .title {
22 | margin-bottom: 20px;
23 | }
24 | .sim-ban {
25 | img {
26 | display: inline-block;
27 | width: 100%;
28 | object-fit: cover;
29 | }
30 | ::ng-deep .anticon {
31 | font-size: 30px;
32 | }
33 | }
34 | .logo-input {
35 | display: block !important;
36 | margin-top: 5px;
37 | }
38 | .bottom-bar {
39 | z-index: 3;
40 | position: fixed;
41 | bottom: 0;
42 | left: 0;
43 | width: 100%;
44 | padding: 10px 0 10px 200px;
45 | background-color: #fff;
46 | box-shadow: 0 0 10px #ccc;
47 | margin-top: 80px;
48 | border-radius: 8px;
49 | display: flex;
50 | align-items: center;
51 | }
52 |
--------------------------------------------------------------------------------
/src/view/system/tag/index.component.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
18 |
19 |
20 |
21 |
22 | {{ $t('_noPublic') }} |
23 | {{ $t('_tagName') }} |
24 | {{ $t('_color') }} |
25 | {{ $t('_sort') }} |
26 | {{ $t('_remark') }} |
27 | {{ $t('_action') }} |
28 |
29 |
30 |
31 |
32 |
33 |
34 | |
35 |
36 |
37 | |
38 |
39 |
44 | - {{ data.color }}
45 | |
46 |
47 |
48 | |
49 |
50 | {{ data.desc }}
51 |
57 | |
58 |
59 | {{
60 | $t('_moveUp')
61 | }}
62 | {{ $t('_moveDown') }}
63 |
72 | {{ $t('_del') }}
73 |
74 | |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/view/system/tag/index.component.scss:
--------------------------------------------------------------------------------
1 | .add-btn {
2 | margin-bottom: 20px;
3 | margin-right: 20px;
4 | }
5 |
--------------------------------------------------------------------------------
/src/view/system/web/index.component.scss:
--------------------------------------------------------------------------------
1 | .desc {
2 | width: 300px;
3 | white-space: pre-wrap;
4 | word-break: break-all;
5 | }
6 | .file-upload {
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | width: 100%;
11 | height: 100%;
12 | opacity: 0;
13 | cursor: pointer;
14 | }
15 | .tip2 {
16 | color: #f50;
17 | font-weight: bold;
18 | margin-top: 10px;
19 | }
20 | .admin {
21 | ::ng-deep .ant-table {
22 | max-height: 600px;
23 | overflow: auto;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "compileOnSave": false,
5 | "exclude": ["**/*.component.html"],
6 | "compilerOptions": {
7 | "baseUrl": "./",
8 | "paths": {
9 | "src/*": ["src/*"]
10 | },
11 | "outDir": "./dist/out-tsc",
12 | "strict": true,
13 | "noImplicitOverride": true,
14 | "noPropertyAccessFromIndexSignature": true,
15 | "noImplicitReturns": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "skipLibCheck": true,
18 | "isolatedModules": true,
19 | "esModuleInterop": true,
20 | "experimentalDecorators": true,
21 | "moduleResolution": "bundler",
22 | "importHelpers": true,
23 | "target": "ES2022",
24 | "module": "ES2022"
25 | },
26 | "angularCompilerOptions": {
27 | "enableI18nLegacyMessageIdFormat": false,
28 | "strictInjectionParameters": true,
29 | "strictInputAccessModifiers": true,
30 | "strictTemplates": true
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "headers": [
3 | {
4 | "source": "/manifest.webmanifest",
5 | "headers": [
6 | {
7 | "key": "Content-Type",
8 | "value": "application/manifest+json"
9 | }
10 | ]
11 | }
12 | ],
13 | "rewrites": [
14 | {
15 | "source": "/:path*",
16 | "destination": "/index.html"
17 | }
18 | ],
19 | "builds": [
20 | {
21 | "src": "package.json",
22 | "use": "@vercel/static-build",
23 | "config": {
24 | "distDir": "dist/browser"
25 | }
26 | }
27 | ],
28 | "outputDirectory": "dist/browser"
29 | }
30 |
--------------------------------------------------------------------------------