├── .editorconfig ├── .env.development ├── .env.production ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── ----.md │ └── bug-report-.md └── workflows │ ├── deploy.yml │ └── test.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .vscode ├── extensions.json └── settings.json ├── .yarnrc ├── README.md ├── commitlint.config.js ├── global.d.ts ├── index.html ├── jest.config.js ├── license ├── package.json ├── prettier.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── api │ ├── model │ │ └── mofish.ts │ └── mofish.ts ├── assets │ ├── image │ │ ├── github.png │ │ ├── heart.png │ │ └── mofish.gif │ └── style │ │ ├── _font.scss │ │ ├── _reset.scss │ │ ├── _unite.scss │ │ ├── _zindex.scss │ │ ├── index.scss │ │ └── variable.scss ├── components │ ├── abstract │ │ ├── button │ │ │ ├── __tests__ │ │ │ │ └── button.test.ts │ │ │ ├── button.ts │ │ │ ├── button.vue │ │ │ └── types.ts │ │ └── toast │ │ │ ├── __tests__ │ │ │ └── toast.test.ts │ │ │ ├── toast.ts │ │ │ ├── toast.vue │ │ │ └── types.ts │ ├── container.vue │ ├── heart.vue │ ├── mofish-list.vue │ ├── todolist.vue │ └── v-footer.vue ├── main.ts ├── pages │ ├── index.vue │ ├── mofish.vue │ └── todolist.vue ├── router │ └── index.ts ├── store │ └── index.ts └── utils │ ├── index.ts │ └── request │ ├── base.ts │ ├── index.ts │ └── mofish.ts ├── stylelint.config.js ├── tsconfig.json ├── vite.config.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # base url 2 | VITE_LOCAL_URL = '/api' 3 | VITE_REALITY_URL = 'https://xxxxxxxxxxx' 4 | VITE_BASE_URL = '/api' 5 | 6 | # mofish url 7 | VITE_MOFISH_LOCAL_URL = '/mofish' 8 | VITE_MOFISH_REALITY_URL = 'https://api.tophub.fun' 9 | VITE_MOFISH_BASE_URL = '/mofish' 10 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # base url 2 | VITE_LOCAL_URL = '/api' 3 | VITE_REALITY_URL = 'https://xxxxxxxxxxx' 4 | VITE_BASE_URL = 'https://xxxxxxxxxxx' 5 | 6 | # mofish url 7 | VITE_MOFISH_LOCAL_URL = '/mofish' 8 | VITE_MOFISH_REALITY_URL = 'https://api.tophub.fun' 9 | VITE_MOFISH_BASE_URL = 'https://api.tophub.fun' 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage/ 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | static/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # parcel-bundler cache (https://parceljs.org/) 64 | .cache 65 | 66 | # next.js build output 67 | .next 68 | 69 | # nuxt.js build output 70 | .nuxt 71 | 72 | # Nuxt generate 73 | dist 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless 80 | 81 | # IDE / Editor 82 | .idea 83 | 84 | # Service worker 85 | sw.* 86 | 87 | # macOS 88 | .DS_Store 89 | 90 | # Vim swap files 91 | *.swp 92 | 93 | # coverage 94 | coverage/ 95 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | // 使用 eslint-plugin-vue 解决 Vue3 defineProps、defineEmits、no-undef 规则警告 7 | // https://eslint.vuejs.org/user-guide/#usage 8 | 'vue/setup-compiler-macros': true 9 | }, 10 | parser: 'vue-eslint-parser', 11 | extends: [ 12 | 'plugin:prettier/recommended', 13 | 'plugin:vue/vue3-recommended', 14 | 'plugin:@typescript-eslint/recommended', 15 | 'prettier' 16 | ], 17 | parserOptions: { 18 | ecmaVersion: 'latest', 19 | parser: '@typescript-eslint/parser', 20 | sourceType: 'module' 21 | }, 22 | plugins: ['prettier', '@typescript-eslint'], 23 | rules: { 24 | 'prettier/prettier': 'error', 25 | 'vue/no-v-html': 'off', 26 | 'vue/multi-word-component-names': 'off', 27 | 'vue/no-multiple-template-root': 'off', 28 | 'vue/component-definition-name-casing': ['warn', 'kebab-case'], 29 | 'no-debugger': 'off', 30 | 'no-console': 'off', 31 | '@typescript-eslint/no-explicit-any': ['off'] 32 | }, 33 | globals: { defineOptions: 'writable' } 34 | } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 博文模板 3 | about: 自己写博客的模板 4 | 5 | --- 6 | 7 | ![banner](#width-full) 8 | 9 | > 描述 10 | 11 | ### 标题 12 | 13 |

14 | 15 |

16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Bug report ' 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ### 问题类型 / question type 8 | 9 | ### 项目版本信息 / Project version information 10 | 11 | ### 问题重现 / Problem recurrence 12 | 13 | ### 功能请求 / Feature request 14 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: 'deploy' 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | name: deploy 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Setup Node.js 16 | uses: actions/setup-node@main 17 | with: 18 | node-version: '16' 19 | - name: Install dependencies 20 | run: npm install 21 | - name: Build 22 | run: npm run build 23 | - name: Deploy to GitHub Pages 24 | uses: JamesIves/github-pages-deploy-action@3.7.1 25 | with: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | BRANCH: gh-pages # The branch the action should deploy to. 28 | FOLDER: dist # The folder the action should deploy. 29 | CLEAN: true # Automatically remove deleted files from the deploy branch 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 'test' 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | name: test 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup Node.js 17 | uses: actions/setup-node@main 18 | with: 19 | node-version: '16' 20 | 21 | - name: Install dependencies 22 | run: npm install 23 | 24 | - name: Run linter 25 | run: npm run test:coverage 26 | 27 | - name: Upload coverage file 28 | uses: codecov/codecov-action@v1 29 | with: 30 | token: ${{ secrets.CODECOV_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | 7 | # jest 8 | coverage 9 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit $1 5 | 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.detectIndentation": false, 3 | "editor.tabSize": 4 4 | } 5 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npm.taobao.org" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BLOG 2 | 3 | [![vue](https://img.shields.io/badge/MADE%20WITH-VUE3.2-42a97a?style=for-the-badge&labelColor=35495d)](https://vuejs.org) 4 |   5 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/shaobeichen/blog/deploy?label=deploy&style=for-the-badge)](https://github.com/shaobeichen/blog/actions/workflows/action.yml) 6 |   7 | [![coverage](https://img.shields.io/codecov/c/github/shaobeichen/blog/master.svg?label=coverage&style=for-the-badge)](https://codecov.io/gh/shaobeichen/blog) 8 |   9 | [![GitHub stars](https://img.shields.io/github/stars/shaobeichen/blog.svg?style=for-the-badge)](https://github.com/shaobeichen/blog/stargazers) 10 |   11 | 12 | ## 介绍 13 | 14 | 个人主页,Vue3.2 + Vite + TypeScript + Pinia + Setup 写法 15 | 16 | ## 特性 17 | 18 | - 有专门用来练习 Vue3.2 的 TodoList 案例 19 | - 可以快速入门 Vue3.2 + Vite2 + TypeScript 20 | - 有 Vue3 + Setup 组件写法 21 | - 有 Vue3.2 命令式组件写法,组件同时支持 Vue2.x Options API 调用 22 | - 有 jest + TypeScript 入门写法,写出第一个 Vue 组件测试用例,查看覆盖率 23 | - 支持 eslint、prettier、stylelint、commitlint、husky 等规范 24 | - 支持 GitHub Actions 自动部署项目 25 | 26 | ## 用法 27 | 28 | 1.安装 yarn 29 | 30 | ``` 31 | npm i -g yarn 32 | ``` 33 | 34 | 2.安装依赖 35 | 36 | ``` 37 | yarn 38 | ``` 39 | 40 | 3.运行项目 41 | 42 | ``` 43 | yarn dev 44 | ``` 45 | 46 | ## 产出文章 47 | 48 | > 做项目后产出了几篇文章,用于记录,帮助大家踩坑。 49 | 50 | - [Vue3+Vite+Scss 项目踩坑记录(一)](https://juejin.cn/post/7115375597370474533) 51 | - [Vue3+Vite+Scss 项目踩坑记录(二)](https://juejin.cn/post/7116310360986304525) 52 | - [Vue3+Vite+Scss 项目踩坑记录(三)](https://juejin.cn/post/7117296242660474893) 53 | - [Vue3+Vite+Scss 项目踩坑记录(四)](https://juejin.cn/post/7118400090296827911) 54 | 55 | ## 参考链接 56 | 57 | Vue3 58 | 59 | - [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) 60 | - [element-plus](https://github.com/element-plus/element-plus/tree/dev/packages/components) 61 | - [vant](https://github.com/youzan/vant) 62 | - [vue3-music](https://github.com/SmallRuralDog/vue3-music/blob/master/src/views/playlist/PlayList.vue) 63 | - [vue3.2-vite-template](https://github.com/BoyYangzai/vue3.2-vite-template/blob/main/src/components/Message/Message.ts) 64 | - [Vue3 组件如何支持 Options API](https://github.com/vueComponent/ant-design-vue/issues/2810) 65 | - [从零搭建后台系统(Vue3.0+ElementPlus+TS+Vite)(一)](https://juejin.cn/post/7038485798143918116) 66 | 67 | jest 68 | 69 | - [jest 中文网](https://jestjs.io/zh-Hans/docs/getting-started) 70 | - [Vue Test Utils 2](https://test-utils.vuejs.org/guide/): 用于 Vue3 组件单元测试 71 | - [如何运行 Jest 单元测试](https://developer.aliyun.com/article/975177) 72 | - [vant toast test](https://github.com/youzan/vant/blob/dev/packages/vant/src/toast/test/index.spec.ts) 73 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-case': [2, 'always', 'lower-case'], 5 | 'type-empty': [2, 'never'], 6 | 'type-enum': [ 7 | 2, 8 | 'always', 9 | [ 10 | 'build', 11 | 'chore', 12 | 'ci', 13 | 'docs', 14 | 'feat', 15 | 'fix', 16 | 'merge', 17 | 'perf', 18 | 'refactor', 19 | 'revert', 20 | 'setup', 21 | 'style', 22 | 'test' 23 | ] 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { defineComponent } from 'vue' 3 | const Component: ReturnType 4 | export default Component 5 | } 6 | 7 | // 环境变量类型定义 8 | interface ImportMetaEnv { 9 | VITE_LOCAL_URL: string 10 | VITE_REALITY_URL: string 11 | VITE_BASE_URL: string 12 | VITE_MOFISH_LOCAL_URL: string 13 | VITE_MOFISH_REALITY_URL: string 14 | VITE_MOFISH_BASE_URL: string 15 | } 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 少北晨的个人网站 - Shao beichen's Personal Website 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | 3 | module.exports = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'jsdom', 6 | testEnvironmentOptions: { 7 | customExportConditions: ['node', 'node-addons'] 8 | }, 9 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'vue'], 10 | collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx,vue}'], 11 | transform: { 12 | '^.+\\.ts$': 'ts-jest', 13 | '^.+\\.js$': 'babel-jest', 14 | '^.+\\.vue$': '@vue/vue3-jest' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "version": "2.0.0", 4 | "license": "ISC", 5 | "scripts": { 6 | "dev": "vite --mode development", 7 | "dev:prod": "vite --mode production", 8 | "build": "vite build --mode production", 9 | "serve": "vite preview", 10 | "lint:js": "eslint --ext \".ts,.js,.vue\" --ignore-path .eslintignore .", 11 | "lint:style": "stylelint \"**/*.{vue,scss,sass,css}\" --ignore-path .eslintignore", 12 | "lint": "yarn lint:js && yarn lint:style", 13 | "test": "jest", 14 | "test:coverage": "jest --coverage --maxWorkers 2" 15 | }, 16 | "lint-staged": { 17 | "*.{ts,js,vue}": [ 18 | "eslint", 19 | "prettier" 20 | ], 21 | "*.{scss,sass,css,vue}": [ 22 | "stylelint", 23 | "prettier" 24 | ] 25 | }, 26 | "dependencies": { 27 | "axios": "^0.27.2", 28 | "pinia": "^2.0.14", 29 | "vue": "^3.2.37", 30 | "vue-router": "^4.0.0" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "^7.18.6", 34 | "@babel/eslint-parser": "^7.18.2", 35 | "@commitlint/cli": "^17.0.3", 36 | "@commitlint/config-conventional": "^17.0.3", 37 | "@types/jest": "^27.0.0", 38 | "@typescript-eslint/eslint-plugin": "^5.30.0", 39 | "@typescript-eslint/parser": "^5.30.0", 40 | "@vitejs/plugin-vue": "^2.3.3", 41 | "@vue/compiler-sfc": "^3.2.37", 42 | "@vue/test-utils": "^2.0.2", 43 | "@vue/vue3-jest": "27", 44 | "@vuedx/typescript-plugin-vue": "^0.7.5", 45 | "babel-core": "^7.0.0-bridge.0", 46 | "babel-jest": "^27.0.0", 47 | "cross-env": "^7.0.3", 48 | "eslint": "^8.18.0", 49 | "eslint-config-prettier": "^8.5.0", 50 | "eslint-plugin-prettier": "^4.2.1", 51 | "eslint-plugin-vue": "^9.1.1", 52 | "husky": "^8.0.1", 53 | "jest": "^27.0.0", 54 | "jest-environment-jsdom": "^27.0.0", 55 | "lint-staged": "^13.0.3", 56 | "prettier": "^2.7.1", 57 | "sass": "^1.53.0", 58 | "stylelint": "13.9.0", 59 | "stylelint-config-prettier": "^8.0.2", 60 | "stylelint-config-standard": "^20.0.0", 61 | "ts-jest": "^27.0.0", 62 | "typescript": "^4.1.3", 63 | "unplugin-vue-define-options": "^0.6.2", 64 | "vite": "^2.0.1", 65 | "vite-plugin-eslint": "^1.6.1", 66 | "vue-eslint-parser": "^9.0.3" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // 在项目根目录创建文件 .prettier.js 2 | // 以下配置视自己情况而定,并步是每个都需要的 3 | module.exports = { 4 | tabWidth: 2, // 使用2个空格缩进 5 | semi: false, // 代码结尾是否加分号 6 | trailingComma: 'none', // 代码末尾不需要逗号 7 | singleQuote: true, // 是否使用单引号 8 | printWidth: 100, // 超过多少字符强制换行 9 | arrowParens: 'avoid', // 单个参数的箭头函数不加括号 x => x 10 | bracketSpacing: true, // 对象大括号内两边是否加空格 { a:0 } 11 | 12 | endOfLine: 'auto', // 文件换行格式 LF/CRLF 13 | useTabs: false, // 不使用缩进符,而使用空格 14 | quoteProps: 'as-needed', // 对象的key仅在必要时用引号 15 | jsxSingleQuote: false, // jsx不使用单引号,而使用双引号 16 | jsxBracketSameLine: false, // jsx标签的反尖括号需要换行 17 | rangeStart: 0, // 每个文件格式化的范围是文件的全部内容 18 | rangeEnd: Infinity, // 结尾 19 | requirePragma: false, // 不需要写文件开头的 @prettier 20 | insertPragma: false, // 不需要自动在文件开头插入 @prettier 21 | proseWrap: 'preserve', // 使用默认的折行标准 22 | htmlWhitespaceSensitivity: 'css' // 根据显示样式决定html要不要折行 23 | } 24 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaobeichen/blog/64f8bda418f456b34962eb5cc412db3cc7aaaeec/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/api/model/mofish.ts: -------------------------------------------------------------------------------- 1 | export interface GetMoFishZhiHuList { 2 | id: number 3 | page: number 4 | type: string 5 | } 6 | -------------------------------------------------------------------------------- /src/api/mofish.ts: -------------------------------------------------------------------------------- 1 | import { mofish } from '@/utils/request' 2 | import { GetMoFishZhiHuList } from './model/mofish' 3 | 4 | interface Res { 5 | Code: number 6 | Message: string 7 | Data: { 8 | data: [] 9 | page: number 10 | } 11 | } 12 | 13 | export function getMoFishZhiHuList(params: GetMoFishZhiHuList): Promise { 14 | return mofish.get('/mofish/v2/GetAllInfoGzip', { params }) 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/image/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaobeichen/blog/64f8bda418f456b34962eb5cc412db3cc7aaaeec/src/assets/image/github.png -------------------------------------------------------------------------------- /src/assets/image/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaobeichen/blog/64f8bda418f456b34962eb5cc412db3cc7aaaeec/src/assets/image/heart.png -------------------------------------------------------------------------------- /src/assets/image/mofish.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaobeichen/blog/64f8bda418f456b34962eb5cc412db3cc7aaaeec/src/assets/image/mofish.gif -------------------------------------------------------------------------------- /src/assets/style/_font.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Font 4 | * 5 | */ 6 | 7 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800;900&display=swap'); 8 | 9 | body { 10 | font-family: 'Poppins', 'PingFang SC', 'Microsoft YaHei', 'Arial', 'simsun', sans-serif; 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/style/_reset.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable at-rule-no-unknown */ 2 | 3 | // Reboot 4 | // 5 | // Normalization of HTML elements, manually forked from Normalize.css to remove 6 | // styles targeting irrelevant browsers while applying new styles. 7 | // 8 | // Normalize is licensed MIT. https://github.com/necolas/normalize.css 9 | 10 | // HTML & Body reset 11 | html, 12 | body { 13 | width: 100%; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | // remove the clear button of a text input control in IE10+ 19 | input::-ms-clear, 20 | input::-ms-reveal { 21 | display: none; 22 | } 23 | 24 | // Document 25 | // 26 | // 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`. 27 | // 2. Change the default font family in all browsers. 28 | // 3. Correct the line height in all browsers. 29 | // 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS. 30 | // 5. Setting $viewport causes scrollbars to overlap content in IE11 and Edge, so 31 | // we force a non-overlapping, non-auto-hiding scrollbar to counteract. 32 | // 6. Change the default tap highlight to be completely transparent in iOS. 33 | 34 | *, 35 | *::before, 36 | *::after { 37 | box-sizing: border-box; // 1 38 | } 39 | 40 | html { 41 | -webkit-text-size-adjust: 100%; // 4 42 | -ms-text-size-adjust: 100%; // 4 43 | -ms-overflow-style: scrollbar; // 5 44 | -webkit-tap-highlight-color: transparent; // 6 45 | } 46 | 47 | // Body 48 | // 49 | // 1. remove the margin in all browsers. 50 | // 2. As a best practice, apply a default `body-background`. 51 | 52 | body { 53 | font-size: 16px; 54 | line-height: 1.4; 55 | -webkit-font-smoothing: antialiased; 56 | 57 | // eslint-disable-next-line 58 | word-break: break-all; 59 | word-break: break-word; 60 | } 61 | 62 | // Render the `main` element consistently in IE. 63 | 64 | main { 65 | display: block; 66 | } 67 | 68 | // Suppress the focus outline on elements that cannot be accessed via keyboard. 69 | // This prevents an unwanted focus outline from appearing around elements that 70 | // might still respond to pointer events. 71 | // 72 | // Credit: https://github.com/suitcss/base 73 | [tabindex='-1']:focus { 74 | outline: none !important; 75 | } 76 | 77 | // Content grouping 78 | // 79 | // 1. Add the correct box sizing in Firefox. 80 | // 2. Show the overflow in Edge and IE. 81 | 82 | hr { 83 | box-sizing: content-box; // 1 84 | height: 0; // 1 85 | overflow: visible; // 2 86 | } 87 | 88 | // 89 | // Typography 90 | // 91 | 92 | // remove top margins from headings 93 | // 94 | // By default, `

`-`

` all receive top and bottom margins. We nuke the top 95 | // margin for easier control within type scales as it avoids margin collapsing. 96 | h1, 97 | h2, 98 | h3, 99 | h4, 100 | h5, 101 | h6 { 102 | margin-top: 0; 103 | margin-bottom: 0.5em; 104 | font-weight: 500; 105 | } 106 | 107 | // Reset margins on paragraphs 108 | // 109 | // Similarly, the top margin on `

`s get reset. 110 | p { 111 | margin-top: 0; 112 | margin-bottom: 0; 113 | } 114 | 115 | // Abbreviations 116 | // 117 | // 1. remove the bottom border in Firefox 39-. 118 | // 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 119 | // 3. Add explicit cursor to indicate changed behavior. 120 | // 4. Duplicate behavior to the data-* attribute for our tooltip plugin 121 | 122 | abbr[title], 123 | abbr[data-original-title] { 124 | // 4 125 | text-decoration: underline; // 2 126 | text-decoration: underline dotted; // 2 127 | border-bottom: 0; // 1 128 | cursor: help; // 3 129 | } 130 | 131 | address { 132 | margin-bottom: 1em; 133 | font-style: normal; 134 | line-height: inherit; 135 | } 136 | 137 | input[type='text'], 138 | input[type='password'], 139 | input[type='number'], 140 | textarea { 141 | -webkit-appearance: none; 142 | -moz-appearance: textfield; 143 | } 144 | 145 | ol, 146 | ul, 147 | dl, 148 | dt, 149 | dd { 150 | margin: 0; 151 | padding: 0; 152 | } 153 | 154 | ul, 155 | ol { 156 | list-style-type: none; 157 | } 158 | 159 | dt { 160 | font-weight: 500; 161 | } 162 | 163 | dd { 164 | margin: 0; 165 | } 166 | 167 | dfn { 168 | font-style: italic; // Add the correct font style in Android 4.3- 169 | } 170 | 171 | b, 172 | strong { 173 | font-weight: bolder; // Add the correct font weight in Chrome, Edge, and Safari 174 | } 175 | 176 | small { 177 | font-size: 80%; // Add the correct font size in all browsers 178 | } 179 | 180 | // 181 | // Prevent `sub` and `sup` elements from affecting the line height in 182 | // all browsers. 183 | // 184 | 185 | sub, 186 | sup { 187 | position: relative; 188 | font-size: 75%; 189 | line-height: 0; 190 | vertical-align: baseline; 191 | } 192 | 193 | sub { 194 | bottom: -0.25em; 195 | } 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | // 201 | // Links 202 | // 203 | 204 | a { 205 | background-color: transparent; // remove the gray background on active links in IE 10. 206 | outline: none; 207 | cursor: pointer; 208 | transition: color 0.3s; 209 | text-decoration: none; 210 | -webkit-text-decoration-skip: objects; // remove gaps in links underline in iOS 8+ and Safari 8+. 211 | 212 | &:active, 213 | &:hover { 214 | outline: 0; 215 | } 216 | 217 | // https://github.com/ant-design/ant-design/issues/22503 218 | &:focus { 219 | outline: 0; 220 | } 221 | 222 | &[disabled] { 223 | cursor: not-allowed; 224 | pointer-events: none; 225 | } 226 | } 227 | 228 | // 229 | // Code 230 | // 231 | 232 | pre, 233 | code, 234 | kbd, 235 | samp { 236 | font-size: 1em; // Correct the odd `em` font sizing in all browsers. 237 | } 238 | 239 | pre { 240 | // remove browser default top margin 241 | margin-top: 0; 242 | // Reset browser default of `1em` to use `em`s 243 | margin-bottom: 1em; 244 | // Don't allow content to break outside 245 | overflow: auto; 246 | } 247 | 248 | // 249 | // Figures 250 | // 251 | figure { 252 | // Apply a consistent margin strategy (matches our type styles). 253 | margin: 0 0 1em; 254 | } 255 | 256 | // 257 | // Images and content 258 | // 259 | 260 | img { 261 | vertical-align: middle; 262 | border-style: none; // remove the border on images inside links in IE 10-. 263 | } 264 | 265 | svg:not(:root) { 266 | overflow: hidden; // Hide the overflow in IE 267 | } 268 | 269 | // Avoid 300ms click delay on touch devices that support the `touch-action` CSS property. 270 | // 271 | // In particular, unlike most other browsers, IE11+Edge on Windows 10 on touch devices and IE Mobile 10-11 272 | // DON'T remove the click delay when `` is present. 273 | // However, they DO support emoving the click delay via `touch-action: manipulation`. 274 | // See: 275 | // * https://getbootstrap.com/docs/4.0/content/reboot/#click-delay-optimization-for-touch 276 | // * http://caniuse.com/#feat=css-touch-action 277 | // * https://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay 278 | 279 | a, 280 | area, 281 | button, 282 | [role='button'], 283 | input:not([type='range']), 284 | label, 285 | select, 286 | summary, 287 | textarea { 288 | touch-action: manipulation; 289 | } 290 | 291 | // 292 | // Tables 293 | // 294 | 295 | table { 296 | border-collapse: collapse; // Prevent double borders 297 | } 298 | 299 | caption { 300 | padding-top: 0.75em; 301 | padding-bottom: 0.3em; 302 | text-align: left; 303 | caption-side: bottom; 304 | } 305 | 306 | th { 307 | // Matches default `` alignment by inheriting from the ``, or the 308 | // closest parent with a set `text-align`. 309 | text-align: inherit; 310 | } 311 | 312 | // 313 | // Forms 314 | // 315 | 316 | input, 317 | button, 318 | select, 319 | optgroup, 320 | textarea { 321 | margin: 0; // remove the margin in Firefox and Safari 322 | padding: 0; 323 | color: inherit; 324 | font-size: inherit; 325 | font-family: inherit; 326 | line-height: inherit; 327 | border-radius: 0; 328 | &:focus { 329 | outline: 0; 330 | } 331 | } 332 | 333 | input { 334 | -webkit-appearance: none; 335 | word-break: normal; 336 | box-shadow: none; 337 | 338 | // placeholder 339 | &::-webkit-input-placeholder, 340 | &::-moz-placeholder, 341 | &::-ms-input-placeholder { 342 | opacity: 1; 343 | } 344 | 345 | &::-webkit-search-cancel-button { 346 | display: none; 347 | } 348 | 349 | &::-webkit-credentials-auto-fill-button { 350 | display: none !important; 351 | visibility: hidden; 352 | pointer-events: none; 353 | position: absolute; 354 | right: 0; 355 | } 356 | 357 | &::-webkit-outer-spin-button, 358 | &::-webkit-inner-spin-button { 359 | -webkit-appearance: none; 360 | } 361 | } 362 | 363 | button, 364 | input { 365 | overflow: visible; // Show the overflow in Edge 366 | } 367 | 368 | button, 369 | select { 370 | text-transform: none; // remove the inheritance of text transform in Firefox 371 | } 372 | 373 | // 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 374 | // controls in Android 4. 375 | // 2. Correct the inability to style clickable types in iOS and Safari. 376 | button, 377 | html [type="button"], /* 1 */ 378 | [type="reset"], 379 | [type="submit"] { 380 | -webkit-appearance: button; // 2 381 | } 382 | 383 | // remove inner border and padding from Firefox, but don't restore the outline like Normalize. 384 | button::-moz-focus-inner, 385 | [type='button']::-moz-focus-inner, 386 | [type='reset']::-moz-focus-inner, 387 | [type='submit']::-moz-focus-inner { 388 | padding: 0; 389 | border-style: none; 390 | } 391 | 392 | input[type='radio'], 393 | input[type='checkbox'] { 394 | box-sizing: border-box; // 1. Add the correct box sizing in IE 10- 395 | padding: 0; // 2. remove the padding in IE 10- 396 | border: 0; 397 | } 398 | 399 | input[type='date'], 400 | input[type='time'], 401 | input[type='datetime-local'], 402 | input[type='month'] { 403 | // remove the default appearance of temporal inputs to avoid a Mobile Safari 404 | // bug where setting a custom line-height prevents text from being vertically 405 | // centered within the input. 406 | // See https://bugs.webkit.org/show_bug.cgi?id=139848 407 | // and https://github.com/twbs/bootstrap/issues/11266 408 | -webkit-appearance: listbox; 409 | } 410 | 411 | textarea { 412 | overflow: auto; // remove the default vertical scrollbar in IE. 413 | // Textareas should really only resize vertically so they don't break their (horizontal) containers. 414 | resize: none; 415 | } 416 | 417 | fieldset { 418 | // Browsers set a default `min-width: min-content;` on fieldsets, 419 | // unlike e.g. `

`s, which have `min-width: 0;` by default. 420 | // So we reset that to ensure fieldsets behave more like a standard block element. 421 | // See https://github.com/twbs/bootstrap/issues/12359 422 | // and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements 423 | min-width: 0; 424 | margin: 0; 425 | // Reset the default outline behavior of fieldsets so they don't affect page layout. 426 | padding: 0; 427 | border: 0; 428 | } 429 | 430 | // 1. Correct the text wrapping in Edge and IE. 431 | // 2. Correct the color inheritance from `fieldset` elements in IE. 432 | legend { 433 | display: block; 434 | width: 100%; 435 | max-width: 100%; // 1 436 | margin-bottom: 0.5em; 437 | padding: 0; 438 | color: inherit; // 2 439 | font-size: 1.5em; 440 | line-height: inherit; 441 | white-space: normal; // 1 442 | } 443 | 444 | progress { 445 | vertical-align: baseline; // Add the correct vertical alignment in Chrome, Firefox, and Opera. 446 | } 447 | 448 | // Correct the cursor style of incement and decement buttons in Chrome. 449 | [type='number']::-webkit-inner-spin-button, 450 | [type='number']::-webkit-outer-spin-button { 451 | height: auto; 452 | } 453 | 454 | [type='search'] { 455 | // This overrides the extra rounded corners on search inputs in iOS so that our 456 | // `.form-control` class can properly style them. Note that this cannot simply 457 | // be added to `.form-control` as it's not specific enough. For details, see 458 | // https://github.com/twbs/bootstrap/issues/11586. 459 | outline-offset: -2px; // 2. Correct the outline style in Safari. 460 | -webkit-appearance: none; 461 | } 462 | 463 | // 464 | // remove the inner padding and cancel buttons in Chrome and Safari on macOS. 465 | // 466 | 467 | [type='search']::-webkit-search-cancel-button, 468 | [type='search']::-webkit-search-decoration { 469 | -webkit-appearance: none; 470 | } 471 | 472 | // 473 | // 1. Correct the inability to style clickable types in iOS and Safari. 474 | // 2. Change font properties to `inherit` in Safari. 475 | // 476 | 477 | ::-webkit-file-upload-button { 478 | font: inherit; // 2 479 | -webkit-appearance: button; // 1 480 | } 481 | 482 | // 483 | // Correct element displays 484 | // 485 | 486 | output { 487 | display: inline-block; 488 | } 489 | 490 | summary { 491 | display: list-item; // Add the correct display in all browsers 492 | } 493 | 494 | template { 495 | display: none; // Add the correct display in IE 496 | } 497 | 498 | // Always hide an element with the `hidden` HTML attribute (from PureCSS). 499 | // Needed for proper display in IE 10-. 500 | [hidden] { 501 | display: none !important; 502 | } 503 | -------------------------------------------------------------------------------- /src/assets/style/_unite.scss: -------------------------------------------------------------------------------- 1 | .text-ellipsis { 2 | white-space: nowrap; 3 | text-overflow: ellipsis; 4 | overflow: hidden; 5 | } 6 | .text-ellipsis-2 { 7 | display: -webkit-box; 8 | overflow: hidden; 9 | -webkit-line-clamp: 2; 10 | -webkit-box-orient: vertical; 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/style/_zindex.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Z-Index 4 | * 5 | */ 6 | 7 | $zindex-dropdown: 100; 8 | 9 | $zindex-fixed: 201; 10 | 11 | $zindex-sticky: 501; 12 | 13 | $zindex-sidebar: 601; 14 | 15 | $zindex-modal: 2001; 16 | 17 | $zindex-message: 2101; 18 | 19 | $zindex-toast: 2201; 20 | 21 | $zindex-loading: 3001; 22 | -------------------------------------------------------------------------------- /src/assets/style/index.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * index 4 | * 5 | */ 6 | 7 | @import './reset'; 8 | 9 | @import './font'; 10 | 11 | @import './unite'; 12 | -------------------------------------------------------------------------------- /src/assets/style/variable.scss: -------------------------------------------------------------------------------- 1 | @import './zindex'; 2 | -------------------------------------------------------------------------------- /src/components/abstract/button/__tests__/button.test.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | 3 | import UIButton from '../button.vue' 4 | 5 | test('type为success', () => { 6 | const wrapper = mount(UIButton, { 7 | props: { 8 | type: 'success' 9 | } 10 | }) 11 | expect(wrapper.classes()).toContain('button--success') 12 | }) 13 | -------------------------------------------------------------------------------- /src/components/abstract/button/button.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import UIButton from './button.vue' 3 | 4 | const Button = { 5 | install(app: App) { 6 | app.component('v-button', UIButton) 7 | } 8 | } 9 | 10 | export default Button 11 | export { Button } 12 | -------------------------------------------------------------------------------- /src/components/abstract/button/button.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 38 | 39 | 89 | -------------------------------------------------------------------------------- /src/components/abstract/button/types.ts: -------------------------------------------------------------------------------- 1 | import type { ExtractPropTypes } from 'vue' 2 | 3 | export const buttonTypes = ['default', 'primary', 'success'] 4 | 5 | export const buttonSizes = ['normal', 'large', 'small'] 6 | 7 | export const buttonProps = { 8 | type: { 9 | type: String, 10 | values: buttonTypes, 11 | default: 'default' 12 | }, 13 | size: { 14 | type: String, 15 | values: buttonSizes, 16 | default: 'normal' 17 | }, 18 | disabled: Boolean 19 | } 20 | 21 | export type ButtonProps = ExtractPropTypes 22 | -------------------------------------------------------------------------------- /src/components/abstract/toast/__tests__/toast.test.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import { sleep } from '../../../../utils/index' 3 | 4 | import UIToast from '../toast.vue' 5 | import Toast from '../toast' 6 | 7 | test('创建一个弹窗,持续3秒', () => { 8 | const wrapper = mount(UIToast, { 9 | props: { 10 | message: '提示成功', 11 | duration: 3000 12 | } 13 | }) 14 | // expect(wrapper.find('.toast')).toBeTruthy() 15 | expect(wrapper.text()).toContain('提示成功') 16 | }) 17 | 18 | test('命令式调用,创建一个弹窗', async () => { 19 | Toast('提示成功') 20 | await sleep() 21 | expect(document.body.querySelector('.toast')).toBeTruthy() 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/abstract/toast/toast.ts: -------------------------------------------------------------------------------- 1 | import { render, createVNode, type App } from 'vue' 2 | import UIToast from './toast.vue' 3 | import { ToastProps } from './types' 4 | 5 | const Toast = (props: string | ToastProps) => { 6 | const mergeProps: ToastProps = { 7 | message: '', 8 | duration: 2000, 9 | closeToast: () => ({}) 10 | } 11 | 12 | if (typeof props === 'string') mergeProps.message = props 13 | else Object.assign(mergeProps, props) 14 | 15 | const mountNode = document.createElement('div') 16 | const ToastBox = createVNode(UIToast, { 17 | ...mergeProps, 18 | closeToast: () => { 19 | mountNode.parentNode?.removeChild(mountNode) 20 | } 21 | }) 22 | render(ToastBox, mountNode) 23 | document.body.appendChild(mountNode) 24 | } 25 | 26 | Toast.install = (app: App) => { 27 | app.component('v-toast', UIToast) 28 | app.provide('$toast', Toast) 29 | app.config.globalProperties.$toast = Toast 30 | } 31 | 32 | declare module '@vue/runtime-core' { 33 | interface ComponentCustomProperties { 34 | $toast: (props: string | ToastProps) => void 35 | } 36 | } 37 | 38 | export default Toast 39 | export { Toast } 40 | -------------------------------------------------------------------------------- /src/components/abstract/toast/toast.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | 21 | 51 | -------------------------------------------------------------------------------- /src/components/abstract/toast/types.ts: -------------------------------------------------------------------------------- 1 | import { type ExtractPropTypes } from 'vue' 2 | 3 | export const toastProps = { 4 | message: { 5 | type: String, 6 | default: '' 7 | }, 8 | duration: { 9 | type: Number, 10 | default: 2000 11 | }, 12 | closeToast: { 13 | type: Function, 14 | default: () => ({}) 15 | } 16 | } 17 | 18 | export type ToastProps = ExtractPropTypes 19 | -------------------------------------------------------------------------------- /src/components/container.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 40 | 41 | 68 | -------------------------------------------------------------------------------- /src/components/heart.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | 58 | -------------------------------------------------------------------------------- /src/components/mofish-list.vue: -------------------------------------------------------------------------------- 1 | 25 | 44 | 45 | 125 | -------------------------------------------------------------------------------- /src/components/todolist.vue: -------------------------------------------------------------------------------- 1 | 32 | 99 | 100 | 172 | -------------------------------------------------------------------------------- /src/components/v-footer.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | 23 | 42 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { createPinia } from 'pinia' 3 | import router from './router/index' 4 | import App from './App.vue' 5 | 6 | import toast from '@/components/abstract/toast/toast' 7 | import button from '@/components/abstract/button/button' 8 | 9 | const pinia = createPinia() 10 | const app = createApp(App) 11 | 12 | app.use(pinia) 13 | app.use(router) 14 | app.use(toast) 15 | app.use(button) 16 | 17 | app.mount('#app') 18 | -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | 15 | 28 | -------------------------------------------------------------------------------- /src/pages/mofish.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 39 | -------------------------------------------------------------------------------- /src/pages/todolist.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router' 2 | 3 | import index from '../pages/index.vue' 4 | import todolist from '../pages/todolist.vue' 5 | import mofish from '../pages/mofish.vue' 6 | 7 | const routes = [ 8 | { path: '/', component: index }, 9 | { path: '/todolist', component: todolist }, 10 | { path: '/mofish', component: mofish } 11 | ] 12 | 13 | const router = createRouter({ 14 | history: createWebHashHistory(), 15 | routes, 16 | scrollBehavior() { 17 | return { left: 0, top: 0 } 18 | } 19 | }) 20 | 21 | export default router 22 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useHeartStore = defineStore('heart', { 4 | state: () => { 5 | return { 6 | count: 0 7 | } 8 | }, 9 | actions: { 10 | addCount() { 11 | this.count++ 12 | } 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 延迟函数 3 | * @param delay 延迟时间 4 | * @returns 5 | */ 6 | export function sleep(delay = 0): Promise { 7 | return new Promise(resolve => setTimeout(resolve, delay)) 8 | } 9 | 10 | /** 11 | * 防抖函数 12 | * @param func 函数 13 | * @param wait 延迟执行毫秒数 14 | * @returns 15 | */ 16 | export function debounce(func: () => void, wait = 0): () => void { 17 | let timeout: null | NodeJS.Timeout = null 18 | return function (this: any, ...args: any) { 19 | if (timeout) clearTimeout(timeout) 20 | timeout = setTimeout(() => { 21 | func.apply(this, args) 22 | }, wait) 23 | } 24 | } 25 | 26 | /** 27 | * 节流函数 28 | * @param func 函数 29 | * @param wait 延迟执行毫秒数 30 | */ 31 | export function throttle(func: () => void, wait = 0): () => void { 32 | let previous = 0 33 | return function (this: any, ...args: any) { 34 | const now = Date.now() 35 | if (now - previous > wait) { 36 | func.apply(this, args) 37 | previous = now 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/request/base.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const { VITE_BASE_URL } = import.meta.env 3 | 4 | // create an axios instance 5 | const service = axios.create({ 6 | baseURL: VITE_BASE_URL, 7 | timeout: 5 * 60 * 1000 8 | }) 9 | 10 | /** 11 | * request interceptor, used to preset token 12 | */ 13 | service.interceptors.request.use( 14 | config => { 15 | // Do something before request is sent 16 | // const token = store.getters.accessToken 17 | // if (token) { 18 | // config.headers.Authorization = `Bearer ${token}` 19 | // } 20 | return config 21 | }, 22 | error => { 23 | console.error('send api failed: ', error) 24 | Promise.reject(error) 25 | } 26 | ) 27 | 28 | // response interceptor 29 | service.interceptors.response.use( 30 | ({ status, data }) => { 31 | if (status === 401) { 32 | // TODO 跳转登录页 33 | } 34 | 35 | return data 36 | }, 37 | error => { 38 | console.error('api request failed: ', error) 39 | return Promise.reject(error) 40 | } 41 | ) 42 | 43 | export default service 44 | -------------------------------------------------------------------------------- /src/utils/request/index.ts: -------------------------------------------------------------------------------- 1 | import base from './base' 2 | 3 | import mofish from './mofish' 4 | 5 | export default base 6 | export { base, mofish } 7 | -------------------------------------------------------------------------------- /src/utils/request/mofish.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const { VITE_MOFISH_BASE_URL, VITE_MOFISH_LOCAL_URL } = import.meta.env 3 | 4 | // create an axios instance 5 | const service = axios.create({ 6 | baseURL: VITE_MOFISH_BASE_URL, 7 | timeout: 5 * 60 * 1000 8 | }) 9 | 10 | /** 11 | * request interceptor, used to preset token 12 | */ 13 | service.interceptors.request.use( 14 | request => { 15 | request.url = request.url?.replace(VITE_MOFISH_LOCAL_URL, '') 16 | return request 17 | }, 18 | error => { 19 | console.error('send api failed: ', error) 20 | Promise.reject(error) 21 | } 22 | ) 23 | 24 | // response interceptor 25 | service.interceptors.response.use( 26 | ({ data }) => { 27 | return data 28 | }, 29 | error => { 30 | console.error('api request failed: ', error) 31 | return Promise.reject(error) 32 | } 33 | ) 34 | 35 | export default service 36 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['stylelint-config-standard', 'stylelint-config-prettier'], 3 | // add your custom config here 4 | // https://stylelint.io/user-guide/configuration 5 | rules: { 6 | indentation: 2, 7 | 'unit-no-unknown': null, 8 | 'property-no-unknown': null, 9 | 'at-rule-no-unknown': null, 10 | 'no-descending-specificity': null, 11 | 'selector-pseudo-element-no-unknown': null, 12 | 'font-family-no-missing-generic-family-keyword': null 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "target": "esnext", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "lib": ["esnext", "dom"], 13 | "types": ["jest", "vite/client", "unplugin-vue-define-options"], 14 | "plugins": [ 15 | { 16 | "name": "@vuedx/typescript-plugin-vue" 17 | } 18 | ], 19 | "paths": { 20 | "@/*": ["src/*"] 21 | } 22 | }, 23 | "vueCompilerOptions": { 24 | "target": 2 25 | }, 26 | "include": ["global.d.ts", "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 27 | } 28 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import path from 'path' 4 | 5 | import eslintPlugin from 'vite-plugin-eslint' 6 | import defineOptions from 'unplugin-vue-define-options/vite' 7 | 8 | // https://vitejs.dev/config/ 9 | export default ({ mode }) => { 10 | const { VITE_MOFISH_REALITY_URL, VITE_MOFISH_LOCAL_URL } = loadEnv(mode, process.cwd()) 11 | 12 | return defineConfig({ 13 | base: './', 14 | logLevel: 'warn', 15 | resolve: { 16 | alias: { 17 | '@': path.resolve(__dirname, 'src') 18 | } 19 | }, 20 | server: { 21 | open: true, 22 | proxy: { 23 | [VITE_MOFISH_LOCAL_URL]: { 24 | target: VITE_MOFISH_REALITY_URL, 25 | changeOrigin: true, 26 | rewrite: path => path.replace(VITE_MOFISH_LOCAL_URL, '') 27 | } 28 | } 29 | }, 30 | plugins: [ 31 | vue(), 32 | defineOptions(), 33 | eslintPlugin({ 34 | include: ['src/**/*.ts', 'src/**/*.js', 'src/**/*.vue', 'src/*.ts', 'src/*.js', 'src/*.vue'] 35 | }) 36 | ], 37 | css: { 38 | preprocessorOptions: { 39 | scss: { 40 | additionalData: `@import "./src/assets/style/variable.scss";` 41 | } 42 | } 43 | } 44 | }) 45 | } 46 | --------------------------------------------------------------------------------