├── .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 | 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 |
10 | 11 | {{ $t('_topColor') }} 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | {{ $t('_bgColor') }} 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |
32 |
33 |
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 |
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 |
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 |
10 | 11 | {{ $t('_topColor') }} 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | {{ $t('_image') }} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | {{ $t('_bgColor') }} 39 | 40 | 41 | 42 |
43 | 44 | 45 | 46 |
47 |
48 | 49 | 50 | {{ $t('_title') }} 51 | 52 | 53 | 54 | 55 | 56 | 57 | {{ $t('_dateColor') }} 58 | 59 | 60 | 61 | 62 | 63 | 64 | {{ $t('_timeColor') }} 65 | 66 | 67 | 68 | 69 | 70 | 71 | {{ $t('_date') }} 72 | 73 | 74 | 75 | 76 |
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 |
10 | 11 | {{ 12 | $t('_categoryName') 13 | }} 14 | 15 | 16 | 17 | 18 | 19 | 20 | {{ 21 | $t('_onlyOwnVisible') 22 | }} 23 | 24 | 25 | 26 | 27 | 28 | 29 | {{ $t('_iconAddr') }} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 45 |
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 |
2 | 7 |
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 |
10 |
11 |
15 | 16 | 17 | {{ $t('_title') }} 18 | 19 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 休息天数 39 | 40 | 41 | 42 | 43 | 44 | {{ $t('_date') }} 45 | 46 | 47 | 48 | 49 | 50 |
51 |
52 | 53 | 56 |
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 | {{ $t('_rest') }} 17 | {{ items[0].dateStr }} 19 | - {{ items[0].afterDay }} 23 |
24 |
25 |
26 |
27 |
28 | {{ item.title }} {{ item.dateStr 29 | }}{{ $t('_rest') }} 30 |
31 |
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 |
10 | 11 | HTML 12 | 13 | 19 | 20 | 21 | 22 | 23 | Width 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{ $t('_bgColor') }} 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 |
39 |
40 |
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 | 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 |
10 | 11 | {{ $t('_image') }} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | {{ $t('_text') }} 27 | 28 | 29 | 30 | 31 | 32 | 33 | URL 34 | 35 | 36 | 37 | 38 |
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 | 2 | 3 | 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 | 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 |
10 | 11 | Types 12 | 13 | 17 | 18 | 19 | 20 | 21 | {{ $t('_bgColor') }} 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 | Count 34 | 35 | 36 | 37 | 38 |
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 |
11 |
18 | {{ newsTypeMap[type] }} 19 |
20 |
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 |
10 | 11 | {{ $t('_workTitle') }} 12 | 13 | 14 | 15 | 16 | 17 | {{ $t('_restTitle') }} 18 | 19 | 20 | 21 | 22 | 23 | {{ $t('_workHours') }} 24 | 25 | 26 | 27 | 28 | 29 | {{ $t('_breakTime') }} 30 | 31 | 32 | 33 | 34 |
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 |
9 | 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 |
10 | 11 | {{ $t('_runtimeTitle') }} 12 | 13 | 14 | 15 | 16 |
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 |
2 |
8 | 9 |
10 |
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 |
6 | 11 |
17 | 18 |
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 |
6 |
10 | 14 | 15 | 16 | 20 | 21 | 22 | {{ dataSource.title 23 | }}{{ dataSource.nav.length < 10 ? '' : ' x ' + dataSource.nav.length }} 24 | 25 | 26 | 27 | 34 | 41 | 48 | 49 |
50 | 51 | 59 | 60 |
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 |
6 |
7 | 13 |
14 |
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 | 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 | 90 | 91 | 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 |
3 |
4 | 5 |
6 | 7 | 8 | 9 |
10 |
11 | 12 | 26 |
27 | 28 | 29 | 30 |
31 | 48 |
49 | 53 | 54 |
66 | 72 |
73 |
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 |
27 | 32 |
33 | 34 |
35 | 40 |
52 | 60 |
61 |
62 |
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 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
{{ componentTitleMap[item.type] }}
40 | {{ 41 | $t('_moveUp') 42 | }} 43 | {{ $t('_moveDown') }} 44 | {{ $t('_edit') }} 45 | 58 |
59 |
60 |
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 |
2 | 3 | {{ $t('_address') }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ $t('_password') }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | Hash Mode 18 | 19 | 20 | 21 | 22 | 23 | 24 | {{ $t('_apiPass') }} 25 | 26 | 27 | 28 | 29 | 30 | 31 | Email 32 | 33 | 38 | 39 | 40 | 41 | 49 |
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 | --------------------------------------------------------------------------------