init
10
.env.development
Normal file
@ -0,0 +1,10 @@
|
||||
# 开发环境
|
||||
|
||||
VITE_APP_TITLE = fast-vue3
|
||||
# 接口请求地址,会设置到 axios 的 baseURL 参数上
|
||||
VITE_APP_API_BASEURL = /api
|
||||
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
|
||||
VITE_APP_DEBUG_TOOL = vconsole
|
||||
|
||||
# 是否开启代理
|
||||
VITE_OPEN_PROXY0 = true
|
16
.env.production
Normal file
@ -0,0 +1,16 @@
|
||||
# 生产环境
|
||||
NODE_ENV = production
|
||||
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = fast-vue3
|
||||
# 接口请求地址,会设置到 axios 的 baseURL 参数上
|
||||
VITE_APP_API_BASEURL = /
|
||||
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
|
||||
VITE_APP_DEBUG_TOOL = vconsole
|
||||
|
||||
# 是否在打包时生成 sourcemap
|
||||
VITE_BUILD_SOURCEMAP = false
|
||||
# 是否在打包时删除 console 代码
|
||||
VITE_BUILD_DROP_CONSOLE = false
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS = gzip,brotli
|
16
.env.test
Normal file
@ -0,0 +1,16 @@
|
||||
# 测试环境
|
||||
NODE_ENV = production
|
||||
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = fast-vue3
|
||||
# 接口请求地址,会设置到 axios 的 baseURL 参数上
|
||||
VITE_APP_API_BASEURL = /
|
||||
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
|
||||
VITE_APP_DEBUG_TOOL = vconsole
|
||||
|
||||
# 是否在打包时生成 sourcemap
|
||||
VITE_BUILD_SOURCEMAP = true
|
||||
# 是否在打包时删除 console 代码
|
||||
VITE_BUILD_DROP_CONSOLE = true
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS =
|
7
.eslintignore
Normal file
@ -0,0 +1,7 @@
|
||||
# eslint 忽略检查
|
||||
node_modules
|
||||
dist
|
||||
!.prettierrc.js
|
||||
/src/assets/fonts
|
||||
/src/assets/icons
|
||||
/src/assets/images
|
81
.eslintrc.js
Normal file
@ -0,0 +1,81 @@
|
||||
// @ts-check
|
||||
const { defineConfig } = require('eslint-define-config')
|
||||
module.exports = defineConfig({
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
jsxPragma: 'React',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:jest/recommended',
|
||||
],
|
||||
rules: {
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'prettier/prettier': ['error', { endOfLine: 'off' }],
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'vue/custom-event-name-casing': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'space-before-function-paren': 'off',
|
||||
|
||||
'vue/attributes-order': 'off',
|
||||
'vue/one-component-per-file': 'off',
|
||||
'vue/html-closing-bracket-newline': 'off',
|
||||
'vue/max-attributes-per-line': 'off',
|
||||
'vue/multiline-html-element-content-newline': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/attribute-hyphenation': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/require-explicit-emits': 'off',
|
||||
'vue/html-self-closing': [
|
||||
'error',
|
||||
{
|
||||
html: {
|
||||
void: 'always',
|
||||
normal: 'never',
|
||||
component: 'always',
|
||||
},
|
||||
svg: 'always',
|
||||
math: 'always',
|
||||
},
|
||||
],
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
})
|
27
.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
|
||||
.local
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.eslintcache
|
||||
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
pnpm-lock.yaml*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
# .vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
1
.pnpm-debug.log
Normal file
@ -0,0 +1 @@
|
||||
{}
|
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
22
.prettierrc.js
Normal file
@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
tabWidth: 2,
|
||||
jsxSingleQuote: true,
|
||||
jsxBracketSameLine: true,
|
||||
printWidth: 100,
|
||||
singleQuote: true,
|
||||
semi: false,
|
||||
overrides: [
|
||||
{
|
||||
files: '*.json',
|
||||
options: {
|
||||
printWidth: 200,
|
||||
},
|
||||
},
|
||||
],
|
||||
arrowParens: 'always',
|
||||
endOfLine: 'auto',
|
||||
vueIndentScriptAndStyle: true,
|
||||
trailingComma: 'all',
|
||||
proseWrap: 'never',
|
||||
htmlWhitespaceSensitivity: 'strict',
|
||||
}
|
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["johnsoncodehk.volar"]
|
||||
}
|
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
144
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
{
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"volar.tsPlugin": true,
|
||||
"volar.tsPluginStatus": false,
|
||||
"npm.packageManager": "pnpm",
|
||||
"editor.tabSize": 2,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.eol": "\n",
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/*.log": true,
|
||||
"**/*.log*": true,
|
||||
"**/bower_components": true,
|
||||
"**/dist": true,
|
||||
"**/elehukouben": true,
|
||||
"**/.git": true,
|
||||
"**/.gitignore": true,
|
||||
"**/.svn": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/.idea": true,
|
||||
"**/.vscode": false,
|
||||
"**/yarn.lock": true,
|
||||
"**/tmp": true,
|
||||
"out": true,
|
||||
"dist": true,
|
||||
"node_modules": true,
|
||||
"CHANGELOG.md": true,
|
||||
"examples": true,
|
||||
"res": true,
|
||||
"screenshots": true,
|
||||
"yarn-error.log": true,
|
||||
"**/.yarn": true
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/.cache": true,
|
||||
"**/.editorconfig": true,
|
||||
"**/.eslintcache": true,
|
||||
"**/bower_components": true,
|
||||
"**/.idea": true,
|
||||
"**/tmp": true,
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/.vscode/**": true,
|
||||
"**/node_modules/**": true,
|
||||
"**/tmp/**": true,
|
||||
"**/bower_components/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/yarn.lock": true
|
||||
},
|
||||
"stylelint.enable": true,
|
||||
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
|
||||
"path-intellisense.mappings": {
|
||||
"/@/": "${workspaceRoot}/src"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[less]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.fixAll.stylelint": true
|
||||
},
|
||||
"editor.defaultFormatter": "johnsoncodehk.volar"
|
||||
},
|
||||
"i18n-ally.localesPaths": ["src/locales/lang"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
|
||||
"i18n-ally.enabledParsers": ["ts"],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"cSpell.words": [
|
||||
"vben",
|
||||
"windi",
|
||||
"browserslist",
|
||||
"tailwindcss",
|
||||
"esnext",
|
||||
"antv",
|
||||
"tinymce",
|
||||
"qrcode",
|
||||
"sider",
|
||||
"pinia",
|
||||
"sider",
|
||||
"nprogress",
|
||||
"INTLIFY",
|
||||
"stylelint",
|
||||
"esno",
|
||||
"vitejs",
|
||||
"sortablejs",
|
||||
"mockjs",
|
||||
"codemirror",
|
||||
"iconify",
|
||||
"commitlint",
|
||||
"vditor",
|
||||
"echarts",
|
||||
"cropperjs",
|
||||
"logicflow",
|
||||
"vueuse",
|
||||
"zxcvbn",
|
||||
"lintstagedrc",
|
||||
"brotli",
|
||||
"tailwindcss",
|
||||
"sider",
|
||||
"pnpm",
|
||||
"antd",
|
||||
"lint-staged"
|
||||
],
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
47
.vscode/vue.code-snippets
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"store新建页面": {
|
||||
"scope": "typescript",
|
||||
"prefix": "store",
|
||||
"body": [
|
||||
"import { defineStore } from 'pinia'",
|
||||
"import { piniaStore } from '@store'",
|
||||
"",
|
||||
"export const use$1Store = defineStore(",
|
||||
" '${1/(.*)/${1:/camelcase}/}',",
|
||||
" {",
|
||||
" state: () => ({}),",
|
||||
" getters: {},",
|
||||
" actions: {}",
|
||||
" }",
|
||||
")",
|
||||
"",
|
||||
"export function use$1OutsideStore() {",
|
||||
" return use$1Store(piniaStore)",
|
||||
"}"
|
||||
],
|
||||
"description": "store page"
|
||||
},
|
||||
"vue新建页面": {
|
||||
"scope": "vue",
|
||||
"prefix": "page",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <div>",
|
||||
" $3",
|
||||
" </div>",
|
||||
"</template>",
|
||||
"",
|
||||
"<script setup name='$1'>",
|
||||
"// const { proxy } = getCurrentInstance()",
|
||||
"// const router = useRouter()",
|
||||
"// const route = useRoute()",
|
||||
" $2",
|
||||
"</script>",
|
||||
"",
|
||||
"<style lang='less' scoped>",
|
||||
" $4",
|
||||
"</style>"
|
||||
],
|
||||
"description": "vue page"
|
||||
}
|
||||
}
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-present, Fast-vue3
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
496
README-zh_CN.md
Normal file
@ -0,0 +1,496 @@
|
||||
<p align="center">
|
||||
<img src="https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/fast-vue3.svg" width="340" />
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/-Vue3-34495e?logo=vue.j" />
|
||||
<img src="https://img.shields.io/badge/-Vite2.7-646cff?logo=vite&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-TypeScript-blue?logo=typescript&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-Pinia-yellow?logo=picpay&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-ESLint-4b32c3?logo=eslint&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-pnpm-F69220?logo=pnpm&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-Axios-008fc7?logo=axios.js&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-Prettier-ef9421?logo=Prettier&logoColor=white" alt="Prettier">
|
||||
<img src="https://img.shields.io/badge/-Less-1D365D?logo=less&logoColor=white" alt="Less">
|
||||
<img src="https://img.shields.io/badge/-Tailwind%20CSS-06B6D4?logo=Tailwind%20CSS&logoColor=white" alt="Taiwind">
|
||||
<img src="" alt="">
|
||||
<p>
|
||||
|
||||
|
||||
一个开箱即用,快速搭建大型应用的Vue3+Vite2+TypeScript+...模板框架。集成了各类插件,并进行了模块化和按需加载的优化,可以放心使用。 [更新文档在此](https://github.com/tobe-fe-dalao/fast-vue3/blob/main/docs/update.md)
|
||||
|
||||
[English](./README.md) | 简体中文 | [日本語](./README.ja-JP.md)
|
||||
|
||||
|
||||
# 功能亮点
|
||||
这里简单介绍一些核心部分,安装部分不再细讲,建议大家直接阅读官方文档或[可视化仓库](https://github1s.com/tobe-fe-dalao/fast-vue3)
|
||||
|
||||
## 🪂大厂协作-代码规范
|
||||
🪁 目前多数大厂团队一般使用[husky](https://github.com/typicode/husky)和 [lint-staged](https://github.com/okonet/lint-staged) 来约束代码规范,
|
||||
- 通过`pre-commit`实现lint检查、单元测试、代码格式化等。
|
||||
- 结合VsCode编辑器(保存时自动执行格式化:editor.formatOnSave: true)
|
||||
- 配合Git hooks钩子(commit前或提交前执行:pre-commit => npm run lint:lint-staged)
|
||||
- IDE 配置(`.editorconfig`)、ESLint 配置(`.eslintrc.js` 和 `.eslintignore`)、StyleLint 配置(`.stylelintrc` 和 `.stylelintignore`),详细请看对应的配置文件。
|
||||
|
||||
🔌关闭代码规范
|
||||
将 `src/` 目录分别加入 `.eslintignore` 和 `.stylelintignore` 进行忽略即可。
|
||||
|
||||
|
||||
## 目录结构
|
||||
|
||||
以下是系统的目录结构
|
||||
|
||||
```
|
||||
├── config
|
||||
│ ├── vite // vite配置
|
||||
│ ├── constant // 系统常量
|
||||
| └── themeConfig // 主题配置
|
||||
├── docs // 文档相关
|
||||
├── mock // mock数据
|
||||
├── plop-tpls // plop模板
|
||||
├── src
|
||||
│ ├── api // api请求
|
||||
│ ├── assets // 静态文件
|
||||
│ ├── components // 业务通用组件
|
||||
│ ├── page // 业务页面
|
||||
│ ├── router // 路由文件
|
||||
│ ├── store // 状态管理
|
||||
│ ├── utils // 工具类
|
||||
│ ├── App.vue // vue模板入口
|
||||
│ ├── main.ts // vue模板js
|
||||
├── .d.ts // 类型定义
|
||||
├── tailwind.config.js // tailwind全局配置
|
||||
├── tsconfig.json // ts配置
|
||||
└── vite.config.ts // vite全局配置
|
||||
```
|
||||
|
||||
## 💕支持JSX语法
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.3"
|
||||
...
|
||||
}
|
||||
```
|
||||
## 🎸UI组件按需加载,自动导入
|
||||
```typescript
|
||||
//模块化写法
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
export const AutoRegistryComponents = () => {
|
||||
return Components({
|
||||
extensions: ['vue', 'md'],
|
||||
deep: true,
|
||||
dts: 'src/components.d.ts',
|
||||
directoryAsNamespace: false,
|
||||
globalNamespaces: [],
|
||||
directives: true,
|
||||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||
exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
|
||||
resolvers: [
|
||||
IconsResolver({
|
||||
componentPrefix: '',
|
||||
}),
|
||||
ArcoResolver({ importStyle: 'less' }),//根据你需要增加UI框架
|
||||
VueUseComponentsResolver(),//默认使用VueUse组件
|
||||
],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 🧩Vite插件模块化
|
||||
为了方便管理插件,将所有的`config`统一放入`config/vite/plugins`里面,未来还会有更多插件直接分文件夹管理十分干净。
|
||||
值得一提的是,`Fast-Vue3`增加了统一环境变量管理,来区分动态开启某些插件。
|
||||
```typescript
|
||||
// vite/plugins/index.ts
|
||||
/**
|
||||
* @name createVitePlugins
|
||||
* @description 封装plugins数组统一调用
|
||||
*/
|
||||
import type { Plugin } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import { ConfigSvgIconsPlugin } from './svgIcons';
|
||||
import { AutoRegistryComponents } from './component';
|
||||
import { AutoImportDeps } from './autoImport';
|
||||
import { ConfigMockPlugin } from './mock';
|
||||
import { ConfigVisualizerConfig } from './visualizer';
|
||||
import { ConfigCompressPlugin } from './compress';
|
||||
import { ConfigPagesPlugin } from './pages'
|
||||
import { ConfigMarkDownPlugin } from './markdown'
|
||||
import { ConfigRestartPlugin } from './restart'
|
||||
|
||||
export function createVitePlugins(isBuild: boolean) {
|
||||
const vitePlugins: (Plugin | Plugin[])[] = [
|
||||
// vue支持
|
||||
vue(),
|
||||
// JSX支持
|
||||
vueJsx(),
|
||||
// 自动按需引入组件
|
||||
AutoRegistryComponents(),
|
||||
// 自动按需引入依赖
|
||||
AutoImportDeps(),
|
||||
// 自动生成路由
|
||||
ConfigPagesPlugin(),
|
||||
// 开启.gz压缩 rollup-plugin-gzip
|
||||
ConfigCompressPlugin(),
|
||||
//支持markdown
|
||||
ConfigMarkDownPlugin(),
|
||||
// 监听配置文件改动重启
|
||||
ConfigRestartPlugin(),
|
||||
];
|
||||
// vite-plugin-svg-icons
|
||||
vitePlugins.push(ConfigSvgIconsPlugin(isBuild));
|
||||
// vite-plugin-mock
|
||||
vitePlugins.push(ConfigMockPlugin(isBuild));
|
||||
// rollup-plugin-visualizer
|
||||
vitePlugins.push(ConfigVisualizerConfig());
|
||||
return vitePlugins;
|
||||
}
|
||||
```
|
||||
而`vite.config.ts`便干净多了
|
||||
```typescript
|
||||
import { createVitePlugins } from './config/vite/plugins'
|
||||
...
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, './src'),
|
||||
'@config': path.resolve(__dirname, './config'),
|
||||
"@components": path.resolve(__dirname, './src/components'),
|
||||
'@utils': path.resolve(__dirname, './src/utils'),
|
||||
'@api': path.resolve(__dirname, './src/api'),
|
||||
}
|
||||
},
|
||||
// plugins
|
||||
plugins: createVitePlugins(isBuild)
|
||||
}
|
||||
...
|
||||
```
|
||||
## 📱支持`Pinia` ,下一代`Vuex5`
|
||||
创建文件`src/store/index.ts`
|
||||
```typescript
|
||||
// 支持模块化,配合plop可以通过命令行一键生成
|
||||
import { createPinia } from 'pinia';
|
||||
import { useAppStore } from './modules/app';
|
||||
import { useUserStore } from './modules/user';
|
||||
const pinia = createPinia();
|
||||
export { useAppStore, useUserStore };
|
||||
export default pinia;
|
||||
```
|
||||
创建文件`src/store/modules/user/index.ts`
|
||||
|
||||
```typescript
|
||||
import { defineStore } from 'pinia'
|
||||
import piniaStore from '@/store'
|
||||
export const useUserStore = defineStore(
|
||||
// 唯一ID
|
||||
'user',
|
||||
{
|
||||
state: () => ({}),
|
||||
getters: {},
|
||||
actions: {}
|
||||
}
|
||||
)
|
||||
```
|
||||
## 🤖 支持`Plop`自动生成文件
|
||||
⚙️ 代码文件自动生成,提供三种预设模板`pages`,`components`,`store`,也可以根据自己需要设计更多自动生成脚本。一般后端同学惯用此形式,十分高效。
|
||||
|
||||
```shell
|
||||
# 安装plop
|
||||
pnpm add plop
|
||||
```
|
||||
根目录创建`plopfile.ts`
|
||||
```typescript
|
||||
import { NodePlopAPI } from 'plop';
|
||||
export default function (plop: NodePlopAPI) {
|
||||
plop.setWelcomeMessage('请选择需要创建的模式:')
|
||||
plop.setGenerator('page', require('./plop-tpls/page/prompt'))
|
||||
plop.setGenerator('component', require('./plop-tpls/component/prompt'))
|
||||
plop.setGenerator('store', require('./plop-tpls/store/prompt'))
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
# 启动命令
|
||||
pnpm run plop
|
||||
```
|
||||
![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a6756aebd4d6407e8545eed41b6e5864~tplv-k3u1fbpfcp-watermark.image?)
|
||||
|
||||
|
||||
## 🖼️ 支持`SVG`图标
|
||||
随着浏览器兼容性的提升,SVG的性能逐渐凸显,很多大厂团队都在创建自己的SVG管理库,后面工具库会有推荐。
|
||||
```shell
|
||||
# 安装svg依赖
|
||||
pnpm add vite-plugin-svg-icons
|
||||
```
|
||||
配置`vite.config.ts`
|
||||
```typescript
|
||||
import viteSvgIcons from 'vite-plugin-svg-icons';
|
||||
export default defineConfig({
|
||||
plugins:[
|
||||
...
|
||||
viteSvgIcons({
|
||||
// 指定需要缓存的图标文件夹
|
||||
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
|
||||
// 指定symbolId格式
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
}),
|
||||
]
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
已封装一个简单的`SvgIcon`组件,可以直接读取文件下的`svg`,可以根据文件夹目录自动查找文件。
|
||||
|
||||
```html
|
||||
<template>
|
||||
<svg aria-hidden="true" class="svg-icon-spin" :class="calsses">
|
||||
<use :xlink:href="symbolId" :fill="color" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'icon',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
})
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
|
||||
const calsses = computed(() => {
|
||||
return {
|
||||
[`sdms-size-${props.size}`]: props.size,
|
||||
}
|
||||
})
|
||||
const fontSize = reactive({ default: '32px', small: '20px', large: '48px' })
|
||||
</script>
|
||||
```
|
||||
## 📦支持`axios(ts版)`
|
||||
已封装了主流的拦截器,请求调用等方法,区分了模块`index.ts`/`status.ts`/`type.ts`
|
||||
```typescript
|
||||
//封装src/api/user/index.ts
|
||||
import request from '@utils/http/axios'
|
||||
import { IResponse } from '@utils/http/axios/type'
|
||||
import { ReqAuth, ReqParams, ResResult } from './type';
|
||||
enum URL {
|
||||
login = '/v1/user/login',
|
||||
permission = '/v1/user/permission',
|
||||
userProfile = 'mock/api/userProfile'
|
||||
}
|
||||
const getUserProfile = async () => request<ReqAuth>({ url: URL.userProfile });
|
||||
const login = async (data: ReqParams) => request({ url: URL.login, data });
|
||||
const permission = async () => request<ReqAuth>({ url: URL.permission });
|
||||
export default { getUserProfile, login, permission };
|
||||
```
|
||||
```typescript
|
||||
//调用
|
||||
import userApi from "@api/user"
|
||||
// setup模式下组件可以直接引用
|
||||
const res = await userApi.profile()
|
||||
```
|
||||
## 👽 自动生成`router`,过滤`components`组件
|
||||
支持`vue-router4.0`的模块化,通过检索pages文件夹可自动生成路由,并支持动态路由
|
||||
|
||||
```typescript
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
||||
import routes from 'virtual:generated-pages'
|
||||
|
||||
console.log(routes,'打印生成自动生成的路由')
|
||||
//导入生成的路由数据
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router
|
||||
```
|
||||
## 🧬支持Mock数据
|
||||
使用`vite-plugin-mock`插件,支持自动区分和启停的环境配置
|
||||
|
||||
```javascript
|
||||
// vite config
|
||||
viteMockServe({
|
||||
ignore: /^\_/,
|
||||
mockPath: 'mock',
|
||||
localEnabled: !isBuild,
|
||||
prodEnabled: false,
|
||||
// https://github.com/anncwb/vite-plugin-mock/issues/9
|
||||
injectCode: `
|
||||
import { setupProdMockServer } from '../mock/_createProdMockServer';
|
||||
setupProdMockServer();
|
||||
`
|
||||
})
|
||||
```
|
||||
根目录下创建 `_createProductionServer.ts`文件,非`_`开头文件会被自动加载成mock文件
|
||||
|
||||
```typescript
|
||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
||||
// 批量加载
|
||||
const modules = import.meta.globEager('./mock/*.ts');
|
||||
|
||||
const mockModules: Array<string> = [];
|
||||
Object.keys(modules).forEach((key) => {
|
||||
if (key.includes('/_')) {
|
||||
return;
|
||||
}
|
||||
mockModules.push(...modules[key].default);
|
||||
});
|
||||
export function setupProdMockServer() {
|
||||
createProdMockServer(mockModules);
|
||||
}
|
||||
```
|
||||
## 🎎Proxy代理
|
||||
```typescript
|
||||
// vite config
|
||||
import proxy from '@config/vite/proxy';
|
||||
export default defineConfig({
|
||||
...
|
||||
server: {
|
||||
hmr: { overlay: false }, // 禁用或配置 HMR 连接 设置 server.hmr.overlay 为 false 可以禁用服务器错误遮罩层
|
||||
// 服务配置
|
||||
port: VITE_PORT, // 类型: number 指定服务器端口;
|
||||
open: false, // 类型: boolean | string在服务器启动时自动在浏览器中打开应用程序;
|
||||
cors: false, // 类型: boolean | CorsOptions 为开发服务器配置 CORS。默认启用并允许任何源
|
||||
host: '0.0.0.0', // IP配置,支持从IP启动
|
||||
proxy,
|
||||
}
|
||||
...
|
||||
})
|
||||
```
|
||||
```typescript
|
||||
// proxy.ts
|
||||
import {
|
||||
API_BASE_URL,
|
||||
API_TARGET_URL,
|
||||
MOCK_API_BASE_URL,
|
||||
MOCK_API_TARGET_URL,
|
||||
} from '@config/constant';
|
||||
import { ProxyOptions } from 'vite';
|
||||
type ProxyTargetList = Record<string, ProxyOptions>;
|
||||
|
||||
const init: ProxyTargetList = {
|
||||
// test
|
||||
[API_BASE_URL]: {
|
||||
target: API_TARGET_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(new RegExp(`^${API_BASE_URL}`), ''),
|
||||
},
|
||||
// mock
|
||||
[MOCK_API_BASE_URL]: {
|
||||
target: MOCK_API_TARGET_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(new RegExp(`^${MOCK_API_BASE_URL}`), '/api'),
|
||||
},
|
||||
};
|
||||
|
||||
export default init;
|
||||
|
||||
```
|
||||
|
||||
## 🎉 其他
|
||||
|
||||
- 🏗 支持`vw/vh`移动端布局兼容,也可以使用`plop`自己配置生成文件
|
||||
- 还有更多新功能增在`commiting`,如果你有更好的方案欢迎`PR`
|
||||
|
||||
|
||||
# 使用
|
||||
一键三连: Star 或 Fork 或 [可视化仓库](https://github1s.com/tobe-fe-dalao/fast-vue3)
|
||||
|
||||
```shell
|
||||
# 拉取仓库代码
|
||||
git clone https://github.com/tobe-fe-dalao/fast-vue3.git
|
||||
|
||||
# 进入项目文件夹
|
||||
cd fast-vue3
|
||||
|
||||
# 安装项目依赖
|
||||
pnpm install
|
||||
|
||||
# 运行
|
||||
pnpm run dev
|
||||
```
|
||||
如果不报错,恭喜你点火成功。否则,请看下面常见问题。
|
||||
|
||||
如果你已经了解本模板,建议你拉取 `template` 分支进行项目开发,该分支不含任何示例代码。
|
||||
|
||||
```
|
||||
# clone template 分支
|
||||
git clone -b template https://github.com/tobe-fe-dalao/fast-vue3.git
|
||||
```
|
||||
|
||||
# 工具库
|
||||
学会使用适当的工具库,让`coding`事半功倍。尤其是开源的工具库,值得每个人学习,因为这本身就是你应该达到的层次。这里推荐一些大厂常用的类库,因为我喜新...,以下工具均可直接引入。
|
||||
|
||||
## JS库
|
||||
- [pnpm](https://pnpm.io/),一个依赖包全局管理的工具,老板再也不用担心我的C盘不够用。Vite官方推荐,字节官方前端团队大规模项目考验
|
||||
|
||||
![image-20220110125758056](https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/image-20220110125758056.png)
|
||||
- [mitt 全局事件监听库](https://github.com/developit/mitt),Vue3官方推荐
|
||||
- [Hammer](http://hammerjs.github.io/),可以识别由触摸、鼠标和指针事件做出的手势,只有 7.34kb
|
||||
- [outils](https://github.com/proYang/outils),开发中常用的函数集,也可以使用`lodash`
|
||||
|
||||
- [tailwindcss](https://tailwindcss.com/),艾玛香的一塌糊涂,一行css不写,3分钟出一个页面。不适合初中级前端,建议还是先踏实学基础再用框架。
|
||||
|
||||
![tailwindcss-1](https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/tailwindcss-1.gif)
|
||||
|
||||
![tailwindcss-2](https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/tailwindcss-2.gif)
|
||||
|
||||
- [Vue I18n](https://vue-i18n.intlify.dev/) 是 Vue.js 的国际化插件,如果你想做开源框架,国际化首选插件。
|
||||
|
||||
- [ViteSSG](https://github.com/antfu/vite-ssg),SEO优化,这个项目有点意思,大家可以玩玩这个方案,之前我都是通过服务端渲染搞SEO,后来了解到这个可以直接在Vue3的服务器上生成。
|
||||
|
||||
- [Vitest](https://github.com/vitest-dev/vitest),基于Vite的单元测试工具,目前迭代比较快,尤大金牌赞助。可以持续关注,不建议使用在小项目中。
|
||||
|
||||
![image-20220110125605172](https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/image-20220110125605172.png)
|
||||
|
||||
|
||||
# UI库
|
||||
- [arco-design](https://github.com/arco-design/arco-design),字节团队新出的UI框架,配置层面更为灵活,`fast-vue3`使用的就是这个,不喜欢的小伙伴可以移除
|
||||
- [semi-design](https://github.com/DouyinFE/semi-design),抖音前端出的框架,面向经常撕逼UI和FE,可以尝鲜玩玩
|
||||
- [nutui](https://github.com/jdf2e/nutui),京东前端团队出的UI框架,已升级到3.X,个人认为颜值最高并接受反驳
|
||||
- [naive-ui](https://github.com/TuSimple/naive-ui),尤大推荐,TypeScript语法,主题可调,这家公司挺厉害
|
||||
- 暂时就这些吧,困了,回头再补
|
||||
|
||||
# 资料
|
||||
- 官方配置文档入口[vite](https://vitejs.cn/config/)、[pinia](https://pinia.vuejs.org/introduction.html)、[vue-router4](https://next.router.vuejs.org/zh/introduction.html)、[plop](https://github.com/plopjs/plop)...
|
||||
- 更详细的配置手册:https://juejin.cn/post/7036745610954801166
|
||||
- vu3写组件实践案例:https://juejin.cn/post/7052717075168493598
|
||||
|
||||
# 贡献者
|
||||
|
||||
这个项目的存在要感谢所有做出贡献的人。
|
||||
|
||||
并感谢我们所有的支持者! 🙏
|
||||
|
||||
<a href="https://github.com/study-vue3/fast-vue3/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=study-vue3/fast-vue3" />
|
||||
</a>
|
||||
|
||||
# 最后
|
||||
|
||||
- 欢迎加群[前端水友群](https://link.juejin.cn?target=https%3A%2F%2Fp3-juejin.byteimg.com%2Ftos-cn-i-k3u1fbpfcp%2Ff2747d1a5fcf4d9894e997b140b8a0d8~tplv-k3u1fbpfcp-zoom-1.image "https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f2747d1a5fcf4d9894e997b140b8a0d8~tplv-k3u1fbpfcp-zoom-1.image"),划水,大家一起划水,现在粉丝群甚少讨论技术,那么我们就一起水吧。欢迎关注我的公众号[扫地盲僧](https://link.juejin.cn?target=https%3A%2F%2Fp3-juejin.byteimg.com%2Ftos-cn-i-k3u1fbpfcp%2Fa08fd56556654baa86975b2a5ba6a8f0~tplv-k3u1fbpfcp-watermark.image%2522 "https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a08fd56556654baa86975b2a5ba6a8f0~tplv-k3u1fbpfcp-watermark.image%22")。
|
||||
|
||||
- 前沿技术,各类体验、互动相关的技术,各类译文、研报的提前透视。
|
||||
- 白嫖,承诺发布的所有付费资源,粉丝群统统免费白嫖,不然大家谁有时间跟你玩,嘿嘿。
|
||||
|
||||
|
||||
<p>
|
||||
<img width="360" src="https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/varqun.jpg">
|
||||
</p>
|
||||
<p>
|
||||
<img width="360" src="https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/扫地盲僧公众号.png">
|
||||
</p>
|
481
README.ja-JP.md
Normal file
@ -0,0 +1,481 @@
|
||||
<p align="center">
|
||||
<img src="https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/fast-vue3.svg" width="340" />
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/-Vue3-34495e?logo=vue.j" />
|
||||
<img src="https://img.shields.io/badge/-Vite2.7-646cff?logo=vite&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-TypeScript-blue?logo=typescript&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-Pinia-yellow?logo=picpay&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-ESLint-4b32c3?logo=eslint&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-pnpm-F69220?logo=pnpm&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-Axios-008fc7?logo=axios.js&logoColor=white" />
|
||||
<img src="https://img.shields.io/badge/-Prettier-ef9421?logo=Prettier&logoColor=white" alt="Prettier">
|
||||
<img src="https://img.shields.io/badge/-Less-1D365D?logo=less&logoColor=white" alt="Less">
|
||||
<img src="https://img.shields.io/badge/-Tailwind%20CSS-06B6D4?logo=Tailwind%20CSS&logoColor=white" alt="Taiwind">
|
||||
<img src="" alt="">
|
||||
<p>
|
||||
すぐに使えるVue3 + Vite2 + TypeScriptなど。 大規模なアプリケーションを迅速に構築するためのテンプレートフレームワーク。 さまざまなプラグインが統合され、モジュール化とリードオンデマンド用に最適化されているため、自信を持って使用できます。 [ドキュメントを更新するには、ここをクリックしてください](https://github.com/tobe-fe-dalao/fast-vue3/blob/main/docs/update.md)
|
||||
|
||||
[English](./README.md) | [简体中文](./README-zh_CN.md) | 日本語
|
||||
|
||||
# 特徴
|
||||
ここでは、いくつかのコアパーツの簡単な紹介を示しますが、インストールパーツについては詳しく説明しません。 公式ドキュメントまたは[ビジュアルウェアハウス](https://github1s.com/tobe-fe-dalao/fast-vue3)を直接読むことをお勧めします。
|
||||
|
||||
## 🪂技術巨人のコラボレーション-コード仕様
|
||||
🪁 現在、多くのハイテク巨人チームは、一般的に [husky](https://github.com/typicode/husky) と [lint-staged](https://github.com/okonet/lint-staged)を使用してコード仕様を制約しています。
|
||||
- `pre-commit`を介して、lintチェック、単体テスト、コードフォーマットなどを実装します。
|
||||
- VsCodeと組み合わせる(保存時に自動的にフォーマットする:editor.formatOnSave:true)
|
||||
- Gitフックと組み合わせる(コミット前に実行:pre-commit => npm run lint:lint-staged)
|
||||
- IDE構成( `.editorconfig`)、ESLint構成(` .eslintrc.js`和 `.eslintignore`)、StyleLint構成(` .stylelintrc`和 `.stylelintignore`)、詳細については、対応する構成ファイルを参照してください.
|
||||
|
||||
🔌 コード仕様を閉じる
|
||||
`.eslintignore` と ` .stylelintignore`をそれぞれ `src /`ディレクトリに追加して無視します.
|
||||
|
||||
|
||||
## ディレクトリ構造
|
||||
|
||||
システムのディレクトリ構造は次のとおりです
|
||||
|
||||
```
|
||||
├── config
|
||||
│ ├── vite // vite 構成
|
||||
│ ├── constant // システム定数
|
||||
| └── themeConfig // theme 構成
|
||||
├── docs // ドキュメント関連
|
||||
├── mock // モックデータ
|
||||
├── plop-tpls // plopテンプレート
|
||||
├── src
|
||||
│ ├── api // APIリクエスト
|
||||
│ ├── assets // 静的ファイル
|
||||
│ ├── components // コンポーネント
|
||||
│ ├── page // ページ
|
||||
│ ├── router // ルーティングファイル
|
||||
│ ├── store // 状態管理
|
||||
│ ├── utils // ツール
|
||||
│ ├── App.vue // vue テンプレート エントリ
|
||||
│ ├── main.ts // vue テンプレート js
|
||||
├── .d.ts // タイプ定義
|
||||
├── tailwind.config.js // tailwind グローバル構成
|
||||
├── tsconfig.json // ts 構成
|
||||
└── vite.config.ts // vite グローバル構成
|
||||
```
|
||||
|
||||
## 💕JSX構文をサポートする
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.3"
|
||||
...
|
||||
}
|
||||
```
|
||||
## 🎸 UIコンポーネントはオンデマンドで読み込まれ、自動的にインポートされます
|
||||
```typescript
|
||||
// モジュラーライティング
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
export const AutoRegistryComponents = () => {
|
||||
return Components({
|
||||
extensions: ['vue', 'md'],
|
||||
deep: true,
|
||||
dts: 'src/components.d.ts',
|
||||
directoryAsNamespace: false,
|
||||
globalNamespaces: [],
|
||||
directives: true,
|
||||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||
exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
|
||||
resolvers: [
|
||||
IconsResolver({
|
||||
componentPrefix: '',
|
||||
}),
|
||||
ArcoResolver({ importStyle: 'less' }),// 必要に応じてUIフレームワークを追加します
|
||||
VueUseComponentsResolver(),// VueUseコンポーネントがデフォルトで使用されます
|
||||
],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 🧩Viteプラグインのモジュール性
|
||||
プラグインの管理を容易にするために、すべての `config`を` config / vite / plugins`に入れてください。 将来的には、非常にクリーンに管理するために、フォルダーに直接分割されたプラグインが増える予定です。
|
||||
`Fast-Vue3`は、特定のプラグインの動的なオープンを区別するために、統合された環境変数管理を追加することは言及する価値があります。
|
||||
```typescript
|
||||
// vite/plugins/index.ts
|
||||
/**
|
||||
* @name createVitePlugins
|
||||
* @description Encapsulate the plugins array to call uniformly
|
||||
*/
|
||||
import type { Plugin } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import { ConfigSvgIconsPlugin } from './svgIcons';
|
||||
import { AutoRegistryComponents } from './component';
|
||||
import { AutoImportDeps } from './autoImport';
|
||||
import { ConfigMockPlugin } from './mock';
|
||||
import { ConfigVisualizerConfig } from './visualizer';
|
||||
import { ConfigCompressPlugin } from './compress';
|
||||
import { ConfigPagesPlugin } from './pages'
|
||||
import { ConfigMarkDownPlugin } from './markdown'
|
||||
import { ConfigRestartPlugin } from './restart'
|
||||
|
||||
export function createVitePlugins(isBuild: boolean) {
|
||||
const vitePlugins: (Plugin | Plugin[])[] = [
|
||||
// vueサポート
|
||||
vue(),
|
||||
// JSXサポート
|
||||
vueJsx(),
|
||||
// コンポーネントをオンデマンドで自動的にインポート
|
||||
AutoRegistryComponents(),
|
||||
// 必要に応じて依存関係を自動的にインポートします
|
||||
AutoImportDeps(),
|
||||
// ルートを自動的に生成する
|
||||
ConfigPagesPlugin(),
|
||||
// .gz圧縮を有効にする rollup-plugin-gzip
|
||||
ConfigCompressPlugin(),
|
||||
// markdownサポート
|
||||
ConfigMarkDownPlugin(),
|
||||
// 構成ファイルの変更を監視して再起動します
|
||||
ConfigRestartPlugin(),
|
||||
];
|
||||
// vite-plugin-svg-icons
|
||||
vitePlugins.push(ConfigSvgIconsPlugin(isBuild));
|
||||
// vite-plugin-mock
|
||||
vitePlugins.push(ConfigMockPlugin(isBuild));
|
||||
// rollup-plugin-visualizer
|
||||
vitePlugins.push(ConfigVisualizerConfig());
|
||||
return vitePlugins;
|
||||
}
|
||||
```
|
||||
`vite.config.ts` is much cleaner
|
||||
```typescript
|
||||
import { createVitePlugins } from './config/vite/plugins'
|
||||
...
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, './src'),
|
||||
'@config': path.resolve(__dirname, './config'),
|
||||
"@components": path.resolve(__dirname, './src/components'),
|
||||
'@utils': path.resolve(__dirname, './src/utils'),
|
||||
'@api': path.resolve(__dirname, './src/api'),
|
||||
}
|
||||
},
|
||||
// plugins
|
||||
plugins: createVitePlugins(isBuild)
|
||||
}
|
||||
...
|
||||
```
|
||||
## 📱 Support for `Pinia`, the next generation of `Vuex5`
|
||||
ファイルを作成する `src/store/index.ts`
|
||||
```typescript
|
||||
// モジュール化をサポートし、plopを使用してコマンドラインからワンクリックで生成できます
|
||||
import { createPinia } from 'pinia';
|
||||
import { useAppStore } from './modules/app';
|
||||
import { useUserStore } from './modules/user';
|
||||
const pinia = createPinia();
|
||||
export { useAppStore, useUserStore };
|
||||
export default pinia;
|
||||
```
|
||||
ファイルを作成する `src/store/modules/user/index.ts`
|
||||
|
||||
```typescript
|
||||
import { defineStore } from 'pinia'
|
||||
import piniaStore from '@/store'
|
||||
export const useUserStore = defineStore(
|
||||
// unique id
|
||||
'user',
|
||||
{
|
||||
state: () => ({}),
|
||||
getters: {},
|
||||
actions: {}
|
||||
}
|
||||
)
|
||||
```
|
||||
## 🤖 ファイルを自動的に生成するための `Plop`をサポート
|
||||
⚙️ コードファイルは自動的に生成され、3つのプリセットテンプレート `pages`、` components`、 `store`を提供します。また、必要に応じて、より多くの自動生成スクリプトを設計することもできます。 通常、バックエンドエンジニアはこのフォームを使用します。これは非常に効率的です。。
|
||||
|
||||
```shell
|
||||
# install plop
|
||||
pnpm add plop
|
||||
```
|
||||
ルートディレクトリ `plopfile.ts`に作成します
|
||||
```typescript
|
||||
import { NodePlopAPI } from 'plop';
|
||||
export default function (plop: NodePlopAPI) {
|
||||
plop.setWelcomeMessage('Please select the pattern you want to create')
|
||||
plop.setGenerator('page', require('./plop-tpls/page/prompt'))
|
||||
plop.setGenerator('component', require('./plop-tpls/component/prompt'))
|
||||
plop.setGenerator('store', require('./plop-tpls/store/prompt'))
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
# start command
|
||||
pnpm run plop
|
||||
```
|
||||
![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a6756aebd4d6407e8545eed41b6e5864~tplv-k3u1fbpfcp-watermark.image?)
|
||||
|
||||
|
||||
## 🖼️ `SVG`アイコンのサポート
|
||||
ブラウザの互換性の向上に伴い、SVGのパフォーマンスは徐々に顕著になりました。 多くの技術大手チームが独自のSVG管理ライブラリを作成しており、ツールライブラリは後で推奨されます。
|
||||
```shell
|
||||
# svg依存関係をインストールします
|
||||
pnpm add vite-plugin-svg-icons
|
||||
```
|
||||
設定 `vite.config.ts`
|
||||
```typescript
|
||||
import viteSvgIcons from 'vite-plugin-svg-icons';
|
||||
export default defineConfig({
|
||||
plugins:[
|
||||
...
|
||||
viteSvgIcons({
|
||||
// キャッシュする必要のあるアイコンフォルダを指定します
|
||||
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
|
||||
// symbolId形式を指定します
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
}),
|
||||
]
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
単純な `SvgIcon`コンポーネントがカプセル化されており、ファイルの下の` svg`を直接読み取ることができ、フォルダーディレクトリに従ってファイルを自動的に見つけることができます。
|
||||
|
||||
```html
|
||||
<template>
|
||||
<svg aria-hidden="true" class="svg-icon-spin" :class="calsses">
|
||||
<use :xlink:href="symbolId" :fill="color" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'icon',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
})
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
|
||||
const calsses = computed(() => {
|
||||
return {
|
||||
[`sdms-size-${props.size}`]: props.size,
|
||||
}
|
||||
})
|
||||
const fontSize = reactive({ default: '32px', small: '20px', large: '48px' })
|
||||
</script>
|
||||
```
|
||||
## 📦 サポート `axios (ts version)`
|
||||
主流のインターセプター、リクエスト呼び出し、その他のメソッドをカプセル化し、モジュール `index.ts` /` status.ts` / `type.ts`を区別しています。
|
||||
```typescript
|
||||
// カプセル化 src/api/user/index.ts
|
||||
import request from '@utils/http/axios'
|
||||
import { IResponse } from '@utils/http/axios/type'
|
||||
import { ReqAuth, ReqParams, ResResult } from './type';
|
||||
enum URL {
|
||||
login = '/v1/user/login',
|
||||
userProfile = 'mock/api/userProfile'
|
||||
}
|
||||
const getUserProfile = async () => request<ReqAuth>({ url: URL.userProfile });
|
||||
const login = async (data: ReqParams) => request({ url: URL.login, data });
|
||||
export default { getUserProfile, login };
|
||||
```
|
||||
```typescript
|
||||
// 移行
|
||||
import userApi from "@api/user"
|
||||
// コンポーネントは、セットアップモードで直接参照できます
|
||||
const res = await userApi.profile()
|
||||
```
|
||||
## 👽 Automatically generate `router`, filter `components` components
|
||||
`vue-router4.0`のモジュール化をサポートし、pagesフォルダーを取得してルートを自動的に生成し、動的ルートをサポートします
|
||||
|
||||
```typescript
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
||||
import routes from 'virtual:generated-pages'
|
||||
|
||||
console.log(routes,'print generate auto-generated routes')
|
||||
// 生成されたルーティングデータをインポートする
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router
|
||||
```
|
||||
## 🧬 サポート Mock data
|
||||
`vite-plugin-mock`プラグインを使用して、自動識別と開始-停止環境構成をサポートします
|
||||
|
||||
```javascript
|
||||
// vite config
|
||||
viteMockServe({
|
||||
ignore: /^\_/,
|
||||
mockPath: 'mock',
|
||||
localEnabled: !isBuild,
|
||||
prodEnabled: false,
|
||||
// https://github.com/anncwb/vite-plugin-mock/issues/9
|
||||
injectCode: `
|
||||
import { setupProdMockServer } from '../mock/_createProdMockServer';
|
||||
setupProdMockServer();
|
||||
`
|
||||
})
|
||||
```
|
||||
ルートディレクトリに `_createProductionServer.ts`ファイルを作成します。`_`で始まらないファイルは自動的にモックファイルにロードされます
|
||||
|
||||
```typescript
|
||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
||||
// Bulk load
|
||||
const modules = import.meta.globEager('./mock/*.ts');
|
||||
|
||||
const mockModules: Array<string> = [];
|
||||
Object.keys(modules).forEach((key) => {
|
||||
if (key.includes('/_')) {
|
||||
return;
|
||||
}
|
||||
mockModules.push(...modules[key].default);
|
||||
});
|
||||
export function setupProdMockServer() {
|
||||
createProdMockServer(mockModules);
|
||||
}
|
||||
```
|
||||
## 🎎 プロキシー
|
||||
```typescript
|
||||
// vite 構成
|
||||
import proxy from '@config/vite/proxy';
|
||||
export default defineConfig({
|
||||
...
|
||||
server: {
|
||||
hmr: { overlay: false }, // HMR接続を無効または構成し、server.hmr.overlayをfalseに設定して、サーバーエラーマスキングレイヤーを「無効」にします
|
||||
// サービス構成
|
||||
port: VITE_PORT, // type:number サーバーポートを指定します;
|
||||
open: false, // type:boolean | string サーバーの起動時にブラウザでアプリケーションを自動的に開きます;
|
||||
cors: false, // type:boolean | CorsOptionsは、開発サーバーのCORSを構成します.デフォルトで有効になっており、任意のオリジンを許可します
|
||||
host: '0.0.0.0', // IP構成、IPからの起動をサポート
|
||||
proxy,
|
||||
}
|
||||
...
|
||||
})
|
||||
```
|
||||
```typescript
|
||||
// proxy.ts
|
||||
import {
|
||||
API_BASE_URL,
|
||||
API_TARGET_URL,
|
||||
MOCK_API_BASE_URL,
|
||||
MOCK_API_TARGET_URL,
|
||||
} from '@config/constant';
|
||||
import { ProxyOptions } from 'vite';
|
||||
type ProxyTargetList = Record<string, ProxyOptions>;
|
||||
|
||||
const init: ProxyTargetList = {
|
||||
// test
|
||||
[API_BASE_URL]: {
|
||||
target: API_TARGET_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(new RegExp(`^${API_BASE_URL}`), ''),
|
||||
},
|
||||
// mock
|
||||
[MOCK_API_BASE_URL]: {
|
||||
target: MOCK_API_TARGET_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(new RegExp(`^${MOCK_API_BASE_URL}`), '/api'),
|
||||
},
|
||||
};
|
||||
|
||||
export default init;
|
||||
|
||||
```
|
||||
|
||||
## 🎉 そのた
|
||||
|
||||
- 🏗 `vw / vh`モバイル端末レイアウトの互換性をサポートします。`plop`を使用して、生成されたファイルを自分で構成することもできます
|
||||
- `commiting`にはさらに多くの新機能が追加されています。より良い解決策がある場合は、` PR`を歓迎します。
|
||||
|
||||
|
||||
# 使用する
|
||||
スターとフォロー:スターまたはフォークまたは[ビジュアルウェアハウス](https://github1s.com/tobe-fe-dalao/fast-vue3)
|
||||
|
||||
```shell
|
||||
# リポジトリコードをプルする
|
||||
git clone https://github.com/tobe-fe-dalao/fast-vue3.git
|
||||
|
||||
# プロジェクトフォルダに入る
|
||||
cd fast-vue3
|
||||
|
||||
# プロジェクトの依存関係をインストールする
|
||||
pnpm install
|
||||
|
||||
# ラン
|
||||
pnpm run dev
|
||||
```
|
||||
エラーが報告されない場合は、点火に成功しました。それ以外の場合は、以下のFAQを参照してください.
|
||||
|
||||
このテンプレートをすでに知っている場合は、プロジェクト開発のために `テンプレート`ブランチをプルすることをお勧めします。このブランチには、サンプルコードは含まれていません。
|
||||
|
||||
```
|
||||
# テンプレートブランチのクローン
|
||||
git clone -b template https://github.com/tobe-fe-dalao/fast-vue3.git
|
||||
```
|
||||
|
||||
# ツール ライブラリ
|
||||
適切なツールライブラリを使用して、 `コーディング`がより少ないリソースでより多くのことを実行できるようにする方法を学びます。 特にオープンソースツールライブラリは、到達すべきレベルであるため、誰もが学ぶ価値があります。 新しいものが好きなので、主要なメーカーで一般的に使用されているクラスライブラリをいくつか示します...次のツールを直接インポートできます。
|
||||
|
||||
## JS ライブラリ
|
||||
- [pnpm](https://pnpm.io/),パッケージのグローバル管理に依存するツールである上司は、私のCドライブが十分でないことを心配する必要がなくなりました。 Vite公式推奨、ByteDance公式フロントエンドチーム大規模プロジェクトテスト
|
||||
|
||||
![image-20220110125758056](https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/image-20220110125758056.png)
|
||||
- [mitt Global event listener library](https://github.com/developit/mitt),Vue3の公式推奨
|
||||
- [Hammer](http://hammerjs.github.io/),タッチ、マウス、ポインターのイベントによって行われたジェスチャを認識できます。わずか7.34kb
|
||||
- [outils](https://github.com/proYang/outils),開発で一般的に使用される関数のセットで、 `lodash`を使用することもできます
|
||||
|
||||
- [tailwindcss](https://tailwindcss.com/),ああ、私のイエス様、CSSの行を書かなくても、ページは3分で作成されます。 ジュニアおよび中間のフロントエンドには適していません。 最初に基本を学び、次にフレームワークを使用することをお勧めします。
|
||||
|
||||
![tailwindcss-1](https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/tailwindcss-1.gif)
|
||||
|
||||
![tailwindcss-2](https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/tailwindcss-2.gif)
|
||||
|
||||
- [Vue I18n](https://vue-i18n.intlify.dev/),Vue.jsの国際化プラグイン。 オープンソースフレームワークを作成したい場合は、国際化に適したプラグインです。
|
||||
|
||||
- [ViteSSG](https://github.com/antfu/vite-ssg),SEO最適化、このプロジェクトは興味深いです。このソリューションで遊ぶことができます。以前はサーバー側のレンダリングでSEOを実行していましたが、後でVue3サーバーで直接生成できることを学びました。
|
||||
|
||||
- [Vitest](https://github.com/vitest-dev/vitest),Viteに基づく単体テストツールである反復は、現在比較的高速であり、EvanYouが後援しています。 引き続き注意を払うことができますが、小さなプロジェクトで使用することはお勧めしません。
|
||||
|
||||
![image-20220110125605172](https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/image-20220110125605172.png)
|
||||
|
||||
|
||||
# UI ライブラリ
|
||||
- [arco-design](https://github.com/arco-design/arco-design),Byte Danceチームの新しいUIフレームワークには、より柔軟な構成レベルがあります。 `fast-vue3`はそれを使用します。 気に入らない場合は削除できます。
|
||||
- [semi-design](https://github.com/DouyinFE/semi-design),Douyinのフロントエンドのフレームワークは、UIとFEを絶えず引き裂くことを目的としており、試してみることができます。
|
||||
- [nutui](https://github.com/jdf2e/nutui),JDのフロントエンドチームによって開発されたUIフレームワークが3.Xにアップグレードされました。 個人的には見た目が一番高く、反論も受けられると思います。
|
||||
- [naive-ui](https://github.com/TuSimple/naive-ui),Evan Youが推奨する、TypeScript構文、調整可能なテーマ、この会社は非常に強力です。
|
||||
- 今のところこれですべてです。後で補います。
|
||||
|
||||
# 参考
|
||||
- 公式の構成ドキュメントエントリ[vite](https://vitejs.cn/config/)、[pinia](https://pinia.vuejs.org/introduction.html)、[vue-router4](https://next.router.vuejs.org/zh/introduction.html)、[plop](https://github.com/plopjs/plop)...
|
||||
- より詳細な構成マニュアル:https://juejin.cn/post/7036745610954801166
|
||||
- vu3ライティングコンポーネントの練習ケース:https://juejin.cn/post/7052717075168493598
|
||||
|
||||
# 最後
|
||||
|
||||
- グループへようこそ [前端水友群](https://link.juejin.cn?target=https%3A%2F%2Fp3-juejin.byteimg.com%2Ftos-cn-i-k3u1fbpfcp%2Ff2747d1a5fcf4d9894e997b140b8a0d8~tplv-k3u1fbpfcp-zoom-1.image "https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f2747d1a5fcf4d9894e997b140b8a0d8~tplv-k3u1fbpfcp-zoom-1.image"),さぼる、一緒にさぼるしましょう。今ではファングループがテクノロジーについて話し合うことはめったにないので、一緒にさぼるしましょう。 Wechat公開番号に注目することを歓迎します [扫地盲僧](https://link.juejin.cn?target=https%3A%2F%2Fp3-juejin.byteimg.com%2Ftos-cn-i-k3u1fbpfcp%2Fa08fd56556654baa86975b2a5ba6a8f0~tplv-k3u1fbpfcp-watermark.image%2522 "https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a08fd56556654baa86975b2a5ba6a8f0~tplv-k3u1fbpfcp-watermark.image%22")。
|
||||
|
||||
- 最先端のテクノロジー、さまざまな経験や相互作用に関連するテクノロジー、さまざまな翻訳や調査レポートの高度な視点。
|
||||
- 無料で使用でき、すべての有料リソースがリリースされることが約束されており、すべてのファングループが無料で使用できます。それ以外の場合は、あなたと遊ぶ時間があります。本当に面白い。
|
||||
|
||||
|
||||
<p>
|
||||
<img width="360" src="https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/varqun.jpg">
|
||||
</p>
|
||||
<p>
|
||||
<img width="360" src="https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/扫地盲僧公众号.png">
|
||||
</p>
|
37
config/constant.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @name Config
|
||||
* @description 项目配置
|
||||
*/
|
||||
|
||||
// 应用名
|
||||
export const APP_TITLE = 'Fast-Vue3';
|
||||
|
||||
// 本地服务端口
|
||||
export const VITE_PORT = 3000;
|
||||
|
||||
// prefix
|
||||
export const API_PREFIX = '/api';
|
||||
|
||||
// serve
|
||||
export const API_BASE_URL = '/api';
|
||||
export const API_TARGET_URL = 'http://localhost:3000';
|
||||
|
||||
// mock
|
||||
export const MOCK_API_BASE_URL = '/mock/api';
|
||||
export const MOCK_API_TARGET_URL = 'http://localhost:3000';
|
||||
|
||||
// iconfontUrl
|
||||
export const ICONFONTURL = '//at.alicdn.com/t/font_3004192_9jmc1z9neiw.js'; // 去色版
|
||||
|
||||
// 包依赖分析
|
||||
export const ANALYSIS = true;
|
||||
|
||||
// 是否支持Md渲染
|
||||
export const MARKDOWN = true;
|
||||
|
||||
// 代码压缩
|
||||
export const COMPRESSION = true;
|
||||
|
||||
|
||||
// 删除 console
|
||||
export const VITE_DROP_CONSOLE = true;
|
10
config/themeConfig.ts
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
// import vitePluginForArco from '@arco-plugins/vite-vue'
|
||||
// 很遗憾他们目前只有react的,vue版还没卷出来
|
||||
export const generateModifyVars = (dark = false) => {
|
||||
// const modifyVars = vitePluginForArco({ dark });
|
||||
return {
|
||||
// ...modifyVars,
|
||||
// 'primary-color': '#ff6900'
|
||||
}
|
||||
}
|
12
config/vite/plugins/autoImport.ts
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
/**
|
||||
* @name AutoImportDeps
|
||||
* @description 按需加载,自动引入
|
||||
*/
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
export const AutoImportDeps = () => {
|
||||
return AutoImport({
|
||||
dts: 'src/auto-imports.d.ts',
|
||||
imports: ['vue', 'pinia', 'vue-router', '@vueuse/core'],
|
||||
})
|
||||
}
|
29
config/vite/plugins/component.ts
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
/**
|
||||
* @name AutoRegistryComponents
|
||||
* @description 按需加载,自动引入组件
|
||||
*/
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import IconsResolver from 'unplugin-icons/resolver'
|
||||
import { ArcoResolver, VueUseComponentsResolver } from 'unplugin-vue-components/resolvers'
|
||||
export const AutoRegistryComponents = () => {
|
||||
return Components({
|
||||
// dirs: ['src/components'],
|
||||
extensions: ['vue', 'md'],
|
||||
deep: true,
|
||||
dts: 'src/components.d.ts',
|
||||
directoryAsNamespace: false,
|
||||
globalNamespaces: [],
|
||||
directives: true,
|
||||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||
exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
|
||||
resolvers: [
|
||||
IconsResolver({
|
||||
componentPrefix: '',
|
||||
}),
|
||||
ArcoResolver({ importStyle: 'less' }),
|
||||
VueUseComponentsResolver(),
|
||||
],
|
||||
})
|
||||
|
||||
}
|
17
config/vite/plugins/compress.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @name ConfigCompressPlugin
|
||||
* @description 开启.gz压缩
|
||||
*/
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
import { COMPRESSION } from '../../constant';
|
||||
|
||||
export const ConfigCompressPlugin = () => {
|
||||
if (COMPRESSION) {
|
||||
return viteCompression({
|
||||
ext: '.gz',
|
||||
verbose: true,
|
||||
deleteOriginFile: false,
|
||||
})
|
||||
}
|
||||
return [];
|
||||
}
|
45
config/vite/plugins/index.ts
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @name createVitePlugins
|
||||
* @description 封装plugins数组统一调用
|
||||
*/
|
||||
import type { Plugin } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import { ConfigSvgIconsPlugin } from './svgIcons'
|
||||
import { AutoRegistryComponents } from './component'
|
||||
import { AutoImportDeps } from './autoImport'
|
||||
import { ConfigMockPlugin } from './mock'
|
||||
import { ConfigVisualizerConfig } from './visualizer'
|
||||
import { ConfigCompressPlugin } from './compress'
|
||||
import { ConfigPagesPlugin } from './pages'
|
||||
import { ConfigRestartPlugin } from './restart'
|
||||
|
||||
export function createVitePlugins(isBuild: boolean) {
|
||||
const vitePlugins: (Plugin | Plugin[])[] = [
|
||||
// vue支持
|
||||
vue(),
|
||||
// JSX支持
|
||||
vueJsx(),
|
||||
// 自动按需引入组件
|
||||
AutoRegistryComponents(),
|
||||
// 自动按需引入依赖
|
||||
AutoImportDeps(),
|
||||
// 自动生成路由
|
||||
ConfigPagesPlugin(),
|
||||
// 开启.gz压缩 rollup-plugin-gzip
|
||||
ConfigCompressPlugin(),
|
||||
// 监听配置文件改动重启
|
||||
ConfigRestartPlugin(),
|
||||
]
|
||||
|
||||
// vite-plugin-svg-icons
|
||||
vitePlugins.push(ConfigSvgIconsPlugin(isBuild))
|
||||
|
||||
// vite-plugin-mock
|
||||
vitePlugins.push(ConfigMockPlugin(isBuild))
|
||||
|
||||
// rollup-plugin-visualizer
|
||||
vitePlugins.push(ConfigVisualizerConfig())
|
||||
|
||||
return vitePlugins
|
||||
}
|
18
config/vite/plugins/mock.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @name ConfigMockPlugin
|
||||
* @description 引入mockjs,本地模拟接口
|
||||
*/
|
||||
import { viteMockServe } from 'vite-plugin-mock'
|
||||
export const ConfigMockPlugin = (isBuild: boolean) => {
|
||||
return viteMockServe({
|
||||
ignore: /^\_/,
|
||||
mockPath: 'mock',
|
||||
localEnabled: !isBuild,
|
||||
prodEnabled: false, //实际开发请关闭,会影响打包体积
|
||||
// https://github.com/anncwb/vite-plugin-mock/issues/9
|
||||
injectCode: `
|
||||
import { setupProdMockServer } from '../mock/_createProdMockServer';
|
||||
setupProdMockServer();
|
||||
`,
|
||||
})
|
||||
}
|
13
config/vite/plugins/pages.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @name ConfigPagesPlugin
|
||||
* @description 动态生成路由
|
||||
*/
|
||||
import Pages from 'vite-plugin-pages'
|
||||
export const ConfigPagesPlugin = () => {
|
||||
return Pages({
|
||||
pagesDir: [{ dir: 'src/pages', baseRoute: '' }],
|
||||
extensions: ['vue', 'md'],
|
||||
exclude: ['**/components/*.vue'],
|
||||
nuxtStyle: true,
|
||||
})
|
||||
}
|
13
config/vite/plugins/restart.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @name ConfigRestartPlugin
|
||||
* @description 监听配置文件修改自动重启Vite
|
||||
*/
|
||||
import ViteRestart from 'vite-plugin-restart'
|
||||
export const ConfigRestartPlugin = () => {
|
||||
return ViteRestart({
|
||||
restart: [
|
||||
'*.config.[jt]s',
|
||||
'**/config/*.[jt]s'
|
||||
]
|
||||
})
|
||||
}
|
16
config/vite/plugins/svgIcons.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @name SvgIconsPlugin
|
||||
* @description 加载SVG文件,自动引入
|
||||
*/
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import path from 'path'
|
||||
|
||||
export const ConfigSvgIconsPlugin = (isBuild: boolean) => {
|
||||
return createSvgIconsPlugin({
|
||||
// 指定需要缓存的图标文件夹
|
||||
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
|
||||
// 指定symbolId格式
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
svgoOptions: isBuild,
|
||||
})
|
||||
}
|
14
config/vite/plugins/visualizer.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import visualizer from 'rollup-plugin-visualizer';
|
||||
import { ANALYSIS } from '../../constant';
|
||||
|
||||
export function ConfigVisualizerConfig() {
|
||||
if (ANALYSIS) {
|
||||
return visualizer({
|
||||
filename: './node_modules/.cache/visualizer/stats.html',
|
||||
open: true,
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}
|
25
config/vite/proxy.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {
|
||||
API_BASE_URL,
|
||||
API_TARGET_URL,
|
||||
MOCK_API_BASE_URL,
|
||||
MOCK_API_TARGET_URL,
|
||||
} from '../../config/constant';
|
||||
import { ProxyOptions } from 'vite';
|
||||
type ProxyTargetList = Record<string, ProxyOptions>;
|
||||
|
||||
const init: ProxyTargetList = {
|
||||
// test
|
||||
[API_BASE_URL]: {
|
||||
target: API_TARGET_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(new RegExp(`^${API_BASE_URL}`), ''),
|
||||
},
|
||||
// mock
|
||||
[MOCK_API_BASE_URL]: {
|
||||
target: MOCK_API_TARGET_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(new RegExp(`^${MOCK_API_BASE_URL}`), '/api'),
|
||||
},
|
||||
};
|
||||
|
||||
export default init;
|
15
docs/update.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Fast-Vue3版本更新
|
||||
## V0.1.1-2022/01/28
|
||||
- 🚃 咱的mock模拟的是真实登录流程,请访问`login`路由
|
||||
- 🥵 修复好几卡车的bug
|
||||
- 🎸 搞了一个好看的logo,svg的~
|
||||
- 😈 重写axios封装,目前进度80%,敬请期待~
|
||||
- 🐯 过年了,代码不写了,祝群里的水友们新年发发发~
|
||||
## V0.1.0-2022/01/26
|
||||
- 🎉 增加vite-plugin模块化配置,根据环境变量按需打包
|
||||
- 📱 增加mock支持,并开启区分环境
|
||||
- 🧩 统一管理全局变量`constant.ts`
|
||||
- 🎎 调整了store的自动生成,以模块化的方式`npm run plop`
|
||||
- 🧬 重写了文档,方便快速上手
|
||||
- 🍡 改写了axios,支持到处request或`get`,`post`
|
||||
- 🎸 此次改版将更加符合大型项目的结构,下个版本会重点通过mock,解决更加复杂的问题,例如登录,权限,鉴权,nav-menu...等。
|
16
index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="[--scroll-mt:9.875rem] lg:[--scroll-mt:6.3125rem] js-focus-visible dark" data-js-focus-visible>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>GrasscutterTools</title>
|
||||
</head>
|
||||
|
||||
<body class="antialiased text-slate-500 dark:text-slate-400 bg-white">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
14
mock/_createProdMockServer.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
||||
// 批量加载
|
||||
const modules = import.meta.globEager('./mock/*.ts');
|
||||
|
||||
const mockModules: Array<string> = [];
|
||||
Object.keys(modules).forEach((key) => {
|
||||
if (key.includes('/_')) {
|
||||
return;
|
||||
}
|
||||
mockModules.push(...modules[key].default);
|
||||
});
|
||||
export function setupProdMockServer() {
|
||||
createProdMockServer(mockModules);
|
||||
}
|
114
mock/user.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { MockMethod } from "vite-plugin-mock"
|
||||
import {
|
||||
successResult,
|
||||
errorResult,
|
||||
pageSuccessResult,
|
||||
requestParams,
|
||||
getRequestToken
|
||||
} from "@/utils/result"
|
||||
import { isLogin, getToken, TokenPrefix } from '@/utils/auth'
|
||||
|
||||
export function createFakeUserList() {
|
||||
return [
|
||||
{
|
||||
user_id: '3306',
|
||||
user_name: 'blindmonk',
|
||||
real_name: '扫地盲僧',
|
||||
avatar: 'https://api.multiavatar.com/blindmonk.svg',
|
||||
desc: '达摩深寺扫地僧,盲崖盘坐思人生',
|
||||
password: 'blindmonk',
|
||||
token: 'P1DeqWBao0HTU47Q',
|
||||
organization: '某大型公司CTO',
|
||||
location: '中国',
|
||||
email: '896226896@qq.com',
|
||||
auths: [],
|
||||
is_admin: 1,
|
||||
dev_languages: 'JavaScript/Vue/React/Node/PHP',
|
||||
blog_github: 'https://github.com/MaleWeb',
|
||||
blog_juejin: 'https://juejin.cn/user/3016715636842622',
|
||||
blog_zhihu: 'https://www.zhihu.com/people/blind_monk',
|
||||
role: 'admin'
|
||||
|
||||
}, {
|
||||
user_id: '80',
|
||||
user_name: 'test',
|
||||
real_name: '盲僧水友',
|
||||
avatar: 'https://api.multiavatar.com/test.svg',
|
||||
desc: '欢迎加入扫地盲僧水友群',
|
||||
password: 'test',
|
||||
token: 'yg8bE8nZwiCL4nQg',
|
||||
organization: '某大型公司CTO',
|
||||
location: '中国',
|
||||
email: '8888@china.com',
|
||||
auths: [],
|
||||
is_admin: 0,
|
||||
dev_languages: 'JavaScript/Vue/React/Node/PHP',
|
||||
blog_github: 'https://github.com/MaleWeb',
|
||||
blog_juejin: 'https://juejin.cn/user/3016715636842622',
|
||||
blog_zhihu: 'https://www.zhihu.com/people/blind_monk',
|
||||
role: 'user',
|
||||
}
|
||||
]
|
||||
}
|
||||
export default [
|
||||
{
|
||||
url: '/user/profile',
|
||||
timeout: 200,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
const token = getRequestToken(request);
|
||||
if (!token) return errorResult('Invalid token')
|
||||
const checkUser = createFakeUserList().find((item) => `${TokenPrefix}${item.token}` === token);
|
||||
if (!checkUser) {
|
||||
return errorResult('未获得相应的用户信息');
|
||||
}
|
||||
return successResult(checkUser);
|
||||
}
|
||||
},
|
||||
{
|
||||
url: '/user/login',
|
||||
timeout: 200,
|
||||
method: 'post',
|
||||
response: (request: requestParams) => {
|
||||
const { username, password } = request?.body;
|
||||
const checkUser = createFakeUserList().find(
|
||||
(item) => item.user_name === username && item.password === password
|
||||
)
|
||||
if (!checkUser) {
|
||||
return errorResult('不存在该用户');
|
||||
}
|
||||
return successResult({ token: checkUser.token })
|
||||
}
|
||||
},
|
||||
{
|
||||
url: '/user/logout',
|
||||
timeout: 200,
|
||||
method: 'post',
|
||||
response: (request: requestParams) => {
|
||||
console.dir(request)
|
||||
const token = getRequestToken(request);
|
||||
if (!token) return errorResult('token缺失!');
|
||||
const checkUser = createFakeUserList().find((item) => `${TokenPrefix}${item.token}` === token);
|
||||
if (!checkUser) {
|
||||
return errorResult('token缺失!');
|
||||
}
|
||||
return successResult('Token 已失效');
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/text',
|
||||
method: 'post',
|
||||
rawResponse: async (req, res) => {
|
||||
let reqbody = ''
|
||||
await new Promise((resolve) => {
|
||||
req.on('data', (chunk) => {
|
||||
reqbody += chunk
|
||||
})
|
||||
req.on('end', () => resolve(undefined))
|
||||
})
|
||||
res.setHeader('Content-Type', 'text/plain')
|
||||
res.statusCode = 200
|
||||
res.end(`hello, ${reqbody}`)
|
||||
},
|
||||
},
|
||||
] as MockMethod[]
|
82
package.json
Normal file
@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "grasscutter-tools",
|
||||
"version": "0.1.1",
|
||||
"author": "wmn1525",
|
||||
"scripts": {
|
||||
"dev": "vite --mode development",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"build:dev": "vite build --mode development",
|
||||
"build:pro": "vite build --mode production",
|
||||
"serve": "vite preview",
|
||||
"plop": "plop",
|
||||
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
|
||||
"prettier": "prettier --write .",
|
||||
"prepare": "husky install",
|
||||
"deps": "yarn upgrade-interactive --latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arco-design/web-vue": "^2.25.2",
|
||||
"@vueuse/components": "^8.3.1",
|
||||
"@vueuse/core": "^8.3.1",
|
||||
"axios": "^0.27.2",
|
||||
"mockjs": "^1.1.0",
|
||||
"naive-ui": "^2.28.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.13",
|
||||
"plop": "^3.1.0",
|
||||
"qs": "^6.10.3",
|
||||
"vue": "^3.2.33",
|
||||
"vue-router": "^4.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.29",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||
"@typescript-eslint/parser": "^5.21.0",
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
||||
"autoprefixer": "^10.4.5",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-define-config": "^1.4.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^8.7.1",
|
||||
"husky": "^7.0.4",
|
||||
"import": "^0.0.6",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^10.2.0",
|
||||
"lint-staged": "^12.4.1",
|
||||
"postcss": "^8.4.12",
|
||||
"postcss-px-to-viewport": "^1.1.1",
|
||||
"prettier": "^2.6.2",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"typescript": "^4.6.3",
|
||||
"unplugin-auto-import": "^0.7.1",
|
||||
"unplugin-icons": "^0.14.1",
|
||||
"unplugin-vue-components": "^0.19.3",
|
||||
"vite": "^2.9.6",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-md": "^0.13.0",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-pages": "^0.23.0",
|
||||
"vite-plugin-restart": "^0.1.1",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "^0.34.10"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,vue,ts,tsx}": [
|
||||
"yarn lint",
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
4
packages/create-fast-vue3/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
.husky
|
||||
pnpm-lock.yaml
|
||||
yarn-error.log
|
1
packages/create-fast-vue3/.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
pnpm-lock.yaml
|
7
packages/create-fast-vue3/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none"
|
||||
}
|
9
packages/create-fast-vue3/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# create-fast-vue3
|
||||
|
||||
a scanffold to create vue3 project that use fast-vue3 template
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
npm init fast-vue3
|
||||
```
|
186
packages/create-fast-vue3/index.js
Normal file
@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import minimist from 'minimist'
|
||||
import prompts from 'prompts'
|
||||
import { red, green, bold } from 'kolorist'
|
||||
import { postOrderDirectoryTraverse } from './utils/directoryTraverse'
|
||||
import getCommand from './utils/getCommand'
|
||||
import clone from 'git-clone/promise'
|
||||
import ora from 'ora'
|
||||
|
||||
async function loading(fn, message, ...args) {
|
||||
const spinner = ora(message)
|
||||
spinner.start()
|
||||
|
||||
try {
|
||||
const result = await fn(...args)
|
||||
spinner.succeed()
|
||||
return result
|
||||
} catch(error) {
|
||||
console.log(error)
|
||||
spinner.fail('Request failed, refetch...')
|
||||
}
|
||||
}
|
||||
|
||||
function changePackageInfo(root, packageName) {
|
||||
const pkgJSONPath = path.join(root, 'package.json')
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgJSONPath))
|
||||
pkg.name = packageName
|
||||
pkg.version = '0.0.0'
|
||||
delete pkg.author
|
||||
fs.writeFileSync(pkgJSONPath, JSON.stringify(pkg, null, 2) + '\n')
|
||||
}
|
||||
|
||||
function removeDir(root, dir) {
|
||||
const deleteFolderRecursive = function(path) {
|
||||
if (fs.existsSync(path)) {
|
||||
fs.readdirSync(path).forEach(function(file) {
|
||||
let curPath = path + "/" + file
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteFolderRecursive(curPath)
|
||||
} else {
|
||||
fs.unlinkSync(curPath)
|
||||
}
|
||||
})
|
||||
fs.rmdirSync(path)
|
||||
}
|
||||
}
|
||||
|
||||
deleteFolderRecursive(path.join(root, dir))
|
||||
}
|
||||
|
||||
function isValidPackageName(projectName) {
|
||||
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName)
|
||||
}
|
||||
|
||||
function toValidPackageName(projectName) {
|
||||
return String(projectName)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/^[._]/, '')
|
||||
.replace(/[^a-z0-9-~]+/g, '-')
|
||||
}
|
||||
|
||||
function canSafelyOverwrite(dir) {
|
||||
return !fs.existsSync(dir) || fs.readdirSync(dir).length === 0
|
||||
}
|
||||
|
||||
function emptyDir(dir) {
|
||||
postOrderDirectoryTraverse(
|
||||
dir,
|
||||
(dir) => fs.rmdirSync(dir),
|
||||
(file) => fs.unlinkSync(file)
|
||||
)
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const downloadUrl = 'https://gitee.com/maleweb/fast-vue3.git'
|
||||
const cwd = process.cwd()
|
||||
const argv = minimist(process.argv.slice(2))
|
||||
|
||||
let targetDir = argv._[0]
|
||||
const defaultProjectName = !targetDir ? 'fast-vue3-demo' : targetDir
|
||||
|
||||
const forceOverwrite = argv.force
|
||||
|
||||
let result = {}
|
||||
|
||||
try {
|
||||
result = await prompts(
|
||||
[
|
||||
{
|
||||
name: 'template',
|
||||
type: 'select',
|
||||
message: 'Choice a Template:',
|
||||
choices: [
|
||||
{ title: 'template-pc', description: 'This will generate template for web scene', value: 'web' },
|
||||
{ title: 'template-mobile', description: 'This will generate template for mobile scene', value: 'mobile' }
|
||||
],
|
||||
initial: 0
|
||||
},
|
||||
{
|
||||
name: 'projectName',
|
||||
type: targetDir ? null : 'text',
|
||||
message: 'Project name:',
|
||||
initial: defaultProjectName,
|
||||
onState: (state) => (targetDir = String(state.value).trim() || defaultProjectName)
|
||||
},
|
||||
{
|
||||
name: 'shouldOverwrite',
|
||||
type: () => (canSafelyOverwrite(String(targetDir)) || forceOverwrite ? null : 'confirm'),
|
||||
message: () => {
|
||||
const dirForPrompt =
|
||||
targetDir === '.' ? 'Current directory' : `Target directory "${targetDir}"`
|
||||
return `${dirForPrompt} is not empty. Remove existing files and continue?`
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'overwriteChecker',
|
||||
type: (prev, values = {}) => {
|
||||
if (values.shouldOverwrite === false) {
|
||||
throw new Error(red('✖') + ' Operation cancelled')
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'packageName',
|
||||
type: () => (isValidPackageName(targetDir) ? null : 'text'),
|
||||
message: 'Package name:',
|
||||
initial: () => toValidPackageName(targetDir),
|
||||
validate: (dir) => isValidPackageName(dir) || 'Invalid package.json name'
|
||||
}
|
||||
],
|
||||
{
|
||||
onCancel: () => {
|
||||
throw new Error(red('✖') + ' Operation cancelled')
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (cancelled) {
|
||||
console.log(cancelled.message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const { packageName = toValidPackageName(defaultProjectName), shouldOverwrite, template } = result
|
||||
const root = path.join(cwd, String(targetDir))
|
||||
|
||||
if (shouldOverwrite) {
|
||||
emptyDir(root)
|
||||
}
|
||||
|
||||
const templates = {
|
||||
'web': 'main',
|
||||
'mobile': 'mobile-template'
|
||||
}
|
||||
|
||||
console.log(`\nScaffolding project in ${root}...`)
|
||||
|
||||
await loading(clone, 'waiting download template', downloadUrl, root, { checkout: templates[template] })
|
||||
|
||||
removeDir(root, "packages")
|
||||
removeDir(root, ".git")
|
||||
changePackageInfo(root, packageName)
|
||||
|
||||
const packageManager = /pnpm/.test(process.env.npm_execpath)
|
||||
? 'pnpm'
|
||||
: /yarn/.test(process.env.npm_execpath)
|
||||
? 'yarn'
|
||||
: 'npm'
|
||||
|
||||
console.log(`\nDone. Now run:\n`)
|
||||
if (root !== cwd) {
|
||||
console.log(` ${bold(green(`cd ${path.relative(cwd, root)}`))}`)
|
||||
}
|
||||
console.log(` ${bold(green(getCommand(packageManager, 'install')))}`)
|
||||
console.log(` ${bold(green(getCommand(packageManager, 'dev')))}`)
|
||||
console.log()
|
||||
}
|
||||
|
||||
init().catch((e) => {
|
||||
console.error(e)
|
||||
})
|
12789
packages/create-fast-vue3/outfile.cjs
Normal file
45
packages/create-fast-vue3/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "create-fast-vue3",
|
||||
"type": "module",
|
||||
"version": "0.0.13",
|
||||
"description": "a easy way to create vue3 project based of fast-vue3 template",
|
||||
"bin": {
|
||||
"create-fast-vue3": "outfile.cjs"
|
||||
},
|
||||
"files": [
|
||||
"outfile.cjs"
|
||||
],
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"build": "esbuild --bundle index.js --format=cjs --platform=node --outfile=outfile.cjs"
|
||||
},
|
||||
"keywords": [
|
||||
"vue3",
|
||||
"vite",
|
||||
"fast-vue3"
|
||||
],
|
||||
"homepage": "https://github.com/study-vue3/fast-vue3/tree/main/packages/create-fast-vue3#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/study-vue3/fast-vue3.git",
|
||||
"directory": "packages/create-fast-vue3"
|
||||
},
|
||||
"author": "liulei",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.14.14",
|
||||
"git-clone": "^0.2.0",
|
||||
"husky": "^7.0.4",
|
||||
"kolorist": "^1.5.1",
|
||||
"lint-staged": "^12.3.2",
|
||||
"minimist": "^1.2.5",
|
||||
"ora": "^5.0",
|
||||
"prettier": "^2.5.1",
|
||||
"prompts": "^2.4.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,json}": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
14
packages/create-fast-vue3/utils/directoryTraverse.js
Normal file
@ -0,0 +1,14 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export function postOrderDirectoryTraverse(dir, dirCallback, fileCallback) {
|
||||
for (const filename of fs.readdirSync(dir)) {
|
||||
const fullpath = path.resolve(dir, filename)
|
||||
if (fs.lstatSync(fullpath).isDirectory()) {
|
||||
postOrderDirectoryTraverse(fullpath, dirCallback, fileCallback)
|
||||
dirCallback(fullpath)
|
||||
continue
|
||||
}
|
||||
fileCallback(fullpath)
|
||||
}
|
||||
}
|
7
packages/create-fast-vue3/utils/getCommand.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default function getCommand(packageManager, scriptName) {
|
||||
if (scriptName === 'install') {
|
||||
return packageManager === 'yarn' ? 'yarn' : `${packageManager} install`
|
||||
}
|
||||
|
||||
return packageManager === 'npm' ? `npm run ${scriptName}` : `${packageManager} ${scriptName}`
|
||||
}
|
6
packages/juejin-maths-game/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
*.log
|
||||
node_modules/
|
||||
.idea
|
||||
yarn.lock
|
||||
.env
|
77
packages/juejin-maths-game/README.md
Normal file
@ -0,0 +1,77 @@
|
||||
# 掘金游戏“数字谜题”算法助手
|
||||
> 非外挂哦,只是最快速找到算法,仍需要根据算法手动完成游戏
|
||||
|
||||
某个夜黑风高的晚上,某程序员摸鱼时无意间发现到了JJ的新游戏,玩了一会不得不佩服JJ运营和技术的头脑,太烧脑了。
|
||||
模式上采用`共建`方式,既能收集题目又能调动参与,复杂的解体再加上变态的陷阱路线,简直SaoD不能再Sao。
|
||||
|
||||
基于某位掘友的算法,进行进一步改良和加工,仓库地址[juejin-maths-game](https://github.com/study-vue3/fast-vue3)
|
||||
|
||||
感谢[wangscaler](https://github.com/wangscaler)提供的可视化地址:[http://math.wangscaler.com/](http://math.wangscaler.com/)
|
||||
|
||||
# 加强版
|
||||
<a>
|
||||
<img src="./statics/maths.png" width="460">
|
||||
</a>
|
||||
|
||||
- 🎉 双模双待,自动读取关卡的数字,运算符,目标数字。
|
||||
- 🧩 答案萃取,增加了对上百种结果的过滤和萃取,更加接近答案值。
|
||||
- 🪂 钉钉PUSH,可以推到钉钉,手机电脑两步走
|
||||
- 🧬 优化了Maths库ing,针对`.`合并运算符进行解析处理
|
||||
- 🤡 仅供学习,欢迎一起完善`Game`的自动寻路功能
|
||||
|
||||
|
||||
# 模式
|
||||
>有两种模式分别是手动和自动,针对不同的使用场景可自由切换,默认为自动模式
|
||||
|
||||
`手动模式`,需要自己根据游戏关卡展示的`数字`,`结果`,`字符`输入到`命令`,然后运行,此模式不需要`COOKIE和UID`
|
||||
```javascript
|
||||
# src/index.js
|
||||
handleRunning([1,2,3,4], ['/', '*', '-'], 10)
|
||||
```
|
||||
|
||||
`自动模式`,根据用户的信息自动登录游戏查询到关卡数据,并且直接运行解析算法,给出正确结果。如果通关,再次执行自动模式命令即可。
|
||||
```javascript
|
||||
# src/index.js
|
||||
autoRunning()
|
||||
|
||||
```
|
||||
|
||||
# 使用
|
||||
|
||||
`.env` 不要fork,不上上传你的数据,本地跑就可以
|
||||
```
|
||||
# 用户cookie
|
||||
COOKIE=
|
||||
# 用户ID,随便url可查
|
||||
USERID=
|
||||
# 钉钉机器人
|
||||
DINGTALK_WEBHOOK=
|
||||
# 钉钉机器人密钥
|
||||
DINGTALK_SECRET=
|
||||
```
|
||||
|
||||
```sh
|
||||
# clone
|
||||
$ git clone https://github.com/study-vue3/fast-vue3.git
|
||||
|
||||
# pnpm/yarn/npm均可安装
|
||||
$ pnpm install
|
||||
|
||||
# 运行
|
||||
$ npm run start
|
||||
```
|
||||
|
||||
<a>
|
||||
<img src="./statics/maths-code.png" width="360">
|
||||
</a>
|
||||
|
||||
# 最后
|
||||
- 欢迎加群前端水友群,大家一起划水,因为讨论技术和装X的都被T出去了,那么我们就一起水吧。 加微信,备注来源和姓名,拉进入群。
|
||||
- 前沿技术,各类体验、互动相关的技术,各类译文、研报的提前透视。
|
||||
|
||||
<p>
|
||||
<img width="360" src="https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/varqun.jpg">
|
||||
</p>
|
||||
<p>
|
||||
<img width="360" src="https://cdn.jsdelivr.net/gh/MaleWeb/picture/images/techblog/扫地盲僧公众号.png">
|
||||
</p>
|
23
packages/juejin-maths-game/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "juejin-maths-game",
|
||||
"version": "1.0.0",
|
||||
"description": "掘金数字谜题游戏算法解析",
|
||||
"main": "./src/index",
|
||||
"homepage": "https://github.com/study-vue3/fast-vue3/tree/main/packages/juejin-maths-game",
|
||||
"scripts": {
|
||||
"start": "node ./src/index",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "仅供学习交流",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "^3.2.3",
|
||||
"axios": "^0.21.1",
|
||||
"colors": "^1.4.0",
|
||||
"dayjs": "^1.10.6",
|
||||
"dotenv": "^16.0.0",
|
||||
"got": "^11.8.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"node-schedule": "^2.1.0"
|
||||
}
|
||||
}
|
10
packages/juejin-maths-game/src/config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const path = require('path')
|
||||
require('dotenv').config({ path: path.join(__dirname, '../.env') })
|
||||
module.exports = {
|
||||
COOKIE: process.env.COOKIE || '',
|
||||
// 自动玩游戏需要此参数,在掘金首页打开控制台输入这行代码`window.__NUXT__.state.auth.user.id`就可以得到
|
||||
USERID: process.env.USERID || '',
|
||||
DINGTALK_WEBHOOK: process.env.DINGTALK_WEBHOOK || '',
|
||||
DINGTALK_SECRET: process.env.DINGTALK_SECRET || '',
|
||||
RESULT_COUNT: process.env.RESULT_COUNT || ''
|
||||
}
|
33
packages/juejin-maths-game/src/index.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { COOKIE, USERID, RESULT_COUNT } = require('./config')
|
||||
const { Game } = require('./lib/Game')
|
||||
const { Maths } = require('./lib/Maths')
|
||||
const { operatorArr } = require('./lib/utils')
|
||||
const message = require('./lib/message')
|
||||
|
||||
//自动模式
|
||||
const autoRunning = async (needNum) => {
|
||||
//1,拿到关卡数据
|
||||
const res = new Game(USERID, COOKIE)
|
||||
const resList = await res.openGame()
|
||||
|
||||
//2,解析关卡数据
|
||||
const { nums, options } = operatorArr(resList.map)
|
||||
const target = resList.target;
|
||||
|
||||
//3, 执行算法解析
|
||||
const getMatch = new Maths(nums, options, target)
|
||||
|
||||
//5,前五个答案
|
||||
getMatch.run(needNum)
|
||||
}
|
||||
//手动模式
|
||||
const handleRunning = async (nums, options, target) => {
|
||||
const getMatch = new Maths(nums, options, target)
|
||||
getMatch.run(5)
|
||||
}
|
||||
autoRunning(5)
|
||||
|
||||
// handleRunning([1, 4, 5], ['+', '*'], 21)
|
||||
|
||||
|
||||
|
76
packages/juejin-maths-game/src/lib/DIngtalkBot.js
Normal file
@ -0,0 +1,76 @@
|
||||
const axios = require('axios')
|
||||
const crypto = require('crypto')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
const defaultOptions = {
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'hello~',
|
||||
},
|
||||
}
|
||||
|
||||
class DingtalkBot {
|
||||
constructor(options = {}) {
|
||||
this.text = ''
|
||||
|
||||
this.webhook = options.webhook
|
||||
this.secret = options.secret
|
||||
const timestamp = new Date().getTime()
|
||||
const sign = this.signFn(this.secret, `${timestamp}\n${this.secret}`)
|
||||
this.allWebhookUrl = `${this.webhook}×tamp=${timestamp}&sign=${sign}`
|
||||
}
|
||||
|
||||
signFn(secret, content) {
|
||||
// 加签
|
||||
const str = crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(content)
|
||||
.digest()
|
||||
.toString('base64')
|
||||
return encodeURIComponent(str)
|
||||
}
|
||||
|
||||
send(data = defaultOptions) {
|
||||
let p
|
||||
// 没有这两个参数则静默失败
|
||||
if (!this.webhook || !this.secret) {
|
||||
p = Promise.resolve({
|
||||
errcode: -1,
|
||||
errmsg: 'webhook和secret不能为空',
|
||||
})
|
||||
} else {
|
||||
p = axios({
|
||||
url: this.allWebhookUrl,
|
||||
method: 'POST',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
},
|
||||
}).then((res) => {
|
||||
return res.data
|
||||
})
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
sendMessage(msg) {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.text += `- ${dayjs().format('HH:mm:ss')} ${msg}\n`
|
||||
this.timer = setTimeout(() => {
|
||||
this.send({
|
||||
msgtype: 'markdown',
|
||||
markdown: {
|
||||
title: 'JueJinLog',
|
||||
text: this.text,
|
||||
}
|
||||
}).then(() => {
|
||||
this.text = ''
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DingtalkBot
|
163
packages/juejin-maths-game/src/lib/Game.js
Normal file
@ -0,0 +1,163 @@
|
||||
const got = require('got')
|
||||
|
||||
const GET_TOKEN_URL = 'https://juejin.cn/get/token'
|
||||
const HEADER = {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67'
|
||||
}
|
||||
|
||||
const HOST_BASE = 'https://juejin-game.bytedance.com/game/num-puzz'
|
||||
const GET_USER = 'https://api.juejin.cn/user_api/v1/user/get'
|
||||
const START_GAME_URL = HOST_BASE + '/ugc/start?'
|
||||
const LOGIN_GAME_URL = HOST_BASE + '/user/login?'
|
||||
const OVER_GAME_URL = HOST_BASE + '/game/over?'
|
||||
|
||||
class Game {
|
||||
#uid
|
||||
#username
|
||||
#cookie
|
||||
#authorization
|
||||
|
||||
constructor(uid, cookie) {
|
||||
this.#uid = uid
|
||||
this.#cookie = cookie
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 获取authorization授权
|
||||
* @returns
|
||||
*/
|
||||
#getToken = () => {
|
||||
const cookie = this.#cookie
|
||||
return got.post(GET_TOKEN_URL, {
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
options => {
|
||||
Object.assign(options.headers, {
|
||||
...HEADER,
|
||||
cookie
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 获取用户信息
|
||||
* @returns
|
||||
*/
|
||||
#getInfo = () => {
|
||||
const URL = GET_USER
|
||||
const cookie = this.#cookie
|
||||
return got.get(URL, {
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
options => {
|
||||
Object.assign(options.headers, {
|
||||
...HEADER,
|
||||
cookie
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 登录游戏
|
||||
* @returns
|
||||
*/
|
||||
#loginGame = () => {
|
||||
const URL = LOGIN_GAME_URL + `uid=${this.#uid}&time=` + new Date().getTime()
|
||||
const body = { name: this.#username }
|
||||
const authorization = this.#authorization
|
||||
return got.post(URL, {
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
options => {
|
||||
Object.assign(options.headers, {
|
||||
...HEADER,
|
||||
authorization: authorization
|
||||
})
|
||||
}
|
||||
]
|
||||
},
|
||||
json: body
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 开始游戏
|
||||
* @param {Number} name 角色id
|
||||
*/
|
||||
#startGame = () => {
|
||||
const URL = START_GAME_URL + `uid=${this.#uid}&time=` + new Date().getTime()
|
||||
const body = {}
|
||||
const authorization = this.#authorization
|
||||
return got.post(URL, {
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
options => {
|
||||
Object.assign(options.headers, {
|
||||
...HEADER,
|
||||
authorization: authorization
|
||||
})
|
||||
}
|
||||
]
|
||||
},
|
||||
json: body
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 结束游戏
|
||||
*/
|
||||
outGame = () => {
|
||||
const URL = OVER_GAME_URL + `uid=${this.#uid}&time=` + new Date().getTime()
|
||||
const body = { isButton: 1 }
|
||||
const authorization = this.#authorization
|
||||
return got.post(URL, {
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
options => {
|
||||
Object.assign(options.headers, {
|
||||
...HEADER,
|
||||
authorization: authorization
|
||||
})
|
||||
}
|
||||
]
|
||||
},
|
||||
json: body
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @desc 启动游戏
|
||||
* @returns {Boolean} 是否启动成功
|
||||
*/
|
||||
openGame = async () => {
|
||||
// 1.获取授权
|
||||
let res = await this.#getToken().json()
|
||||
this.#authorization = 'Bearer ' + res.data
|
||||
|
||||
// 2.获取用户名
|
||||
res = await this.#getInfo().json()
|
||||
this.#username = res.data.user_name
|
||||
|
||||
// 3.登录游戏
|
||||
// res = await this.#loginGame().json()
|
||||
|
||||
|
||||
// 4.开始游戏,获得关卡数据
|
||||
res = await this.#startGame().json()
|
||||
|
||||
|
||||
// 5.游戏启动成功返回游戏信息
|
||||
return res.code === 0 ? res.data : undefined
|
||||
}
|
||||
}
|
||||
|
||||
exports.Game = Game
|
136
packages/juejin-maths-game/src/lib/Maths.js
Normal file
@ -0,0 +1,136 @@
|
||||
const colors = require('colors');
|
||||
colors.setTheme({
|
||||
silly: 'rainbow',
|
||||
input: 'grey',
|
||||
verbose: 'cyan',
|
||||
prompt: 'grey',
|
||||
info: 'green',
|
||||
data: 'grey',
|
||||
help: 'cyan',
|
||||
warn: 'yellow',
|
||||
debug: 'blue',
|
||||
error: 'red'
|
||||
});
|
||||
class Maths {
|
||||
//解析算法部分-升级改造版 源地址:https://juejin.cn/post/7067862481800003591
|
||||
constructor(nums, options, target) {
|
||||
this.nums = nums;
|
||||
this.options = options;
|
||||
this.target = target;
|
||||
this.cache = {}
|
||||
this.type = 2;//第二代
|
||||
this.needNum = 5;//正确解的数量
|
||||
}
|
||||
calc(a, option, b) {
|
||||
let res;
|
||||
let formula;
|
||||
switch (option) {
|
||||
case '+':
|
||||
res = a.value + b.value;
|
||||
formula = a.formula + '+' + b.formula;
|
||||
break;
|
||||
case '-':
|
||||
res = a.value - b.value;
|
||||
formula = a.formula + '-' + b.formula;
|
||||
break;
|
||||
case '*':
|
||||
res = a.value * b.value;
|
||||
formula = a.formula + '*' + b.formula;
|
||||
break;
|
||||
case '/':
|
||||
res = a.value / b.value;
|
||||
formula = a.formula + '/' + b.formula;
|
||||
break;
|
||||
case '.':
|
||||
res = Number([a.value, b.value].join(""));
|
||||
formula = a.formula + '.' + b.formula;
|
||||
break;
|
||||
}
|
||||
|
||||
if (res < 0 || res % 1 !== 0) {
|
||||
res = NaN;
|
||||
}
|
||||
return {
|
||||
value: res,
|
||||
formula: '(' + formula + ')',
|
||||
};;
|
||||
};
|
||||
calcSum = () => {
|
||||
let cha = this.nums.length - 1 - this.options.length;
|
||||
this.cache = {};
|
||||
if (cha) {
|
||||
this.options.push(...'.'.repeat(cha))
|
||||
}
|
||||
this.nowNum = 0
|
||||
this.options.sort()
|
||||
let len = this.nums.length
|
||||
this.nums.sort()
|
||||
for (let i = 0; i < len; i++) {
|
||||
let num = this.nums[i]
|
||||
this.nums[i] = {
|
||||
value: num,
|
||||
formula: '' + num,
|
||||
}
|
||||
}
|
||||
this.calcLoop(this.nums, this.options, this.target)
|
||||
this.cache = null;
|
||||
};
|
||||
calcLoop = (nums, options, target) => {
|
||||
let cache = this.cache
|
||||
if (nums.length === 1 && options.length == 0 && nums[0].value == target) {
|
||||
console.log(colors.warn(`算法破解:${nums[0].formula}`));
|
||||
this.nowNum++;
|
||||
if (this.nowNum >= this.needNum) {
|
||||
console.log(colors.info('扫地盲僧第三代算法--运行结束--!'))
|
||||
return true
|
||||
}
|
||||
return// nums[0]
|
||||
}
|
||||
|
||||
let nums2 = nums.map((v) => v.value)
|
||||
nums2.sort()
|
||||
let key = nums2.join() + '|' + options.join();
|
||||
if (cache[key]) {
|
||||
return
|
||||
}
|
||||
cache[key] = true
|
||||
for (let i of '*+-/.') {
|
||||
let index = options.indexOf(i);
|
||||
if (index >= 0) {
|
||||
let newOptions = [...options];
|
||||
newOptions.splice(index, 1);
|
||||
let len = nums.length
|
||||
for (let j = 0; j < len - 1; j++) {
|
||||
let newNums = [...nums];
|
||||
newNums.splice(j, 1);
|
||||
for (let k = j + 1; k < len; k++) {
|
||||
let newNums2 = [...newNums];
|
||||
//newNums2.splice(newNums2.indexOf(nums[k]), 1);
|
||||
let newNum = this.calc(nums[j], i, nums[k])
|
||||
if (isNaN(newNum.value)) {
|
||||
}
|
||||
else {
|
||||
newNums2[k - 1] = newNum//[newNum.formula, ...newNums2]
|
||||
if (this.calcLoop(newNums2, newOptions, target)) return true;
|
||||
}
|
||||
if (i != '+' && i != '*') {
|
||||
let newNum = this.calc(nums[k], i, nums[j]);
|
||||
if (isNaN(newNum.value)) {
|
||||
}
|
||||
else {
|
||||
newNums2[k - 1] = newNum
|
||||
if (this.calcLoop(newNums2, newOptions, target)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
run = (needNum = 5) => {
|
||||
this.needNum = needNum;
|
||||
this.calcSum(this.nums, this.options, this.target)
|
||||
|
||||
}
|
||||
}
|
||||
exports.Maths = Maths
|
9
packages/juejin-maths-game/src/lib/message.js
Normal file
@ -0,0 +1,9 @@
|
||||
const DingtalkBot = require('./DingtalkBot')
|
||||
const config = require('../config')
|
||||
const DingRobot = new DingtalkBot({
|
||||
webhook: config.DINGTALK_WEBHOOK, // Webhook地址
|
||||
secret: config.DINGTALK_SECRET, // 安全设置:加签的secret
|
||||
})
|
||||
module.exports = function message(msg) {
|
||||
DingRobot.sendMessage(msg)
|
||||
}
|
35
packages/juejin-maths-game/src/lib/utils.js
Normal file
@ -0,0 +1,35 @@
|
||||
module.exports = {
|
||||
//解析运算符
|
||||
operatorArr(arr) {
|
||||
let nums = [];
|
||||
let options = [];
|
||||
arr.forEach(item => {
|
||||
item.forEach(key => {
|
||||
//整数为数字
|
||||
if (key % 1 === 0) {
|
||||
nums.push(key)
|
||||
} else {
|
||||
//运算符匹配
|
||||
switch (key) {
|
||||
case 0.3:
|
||||
options.push('+')
|
||||
break;
|
||||
case 0.4:
|
||||
options.push('-')
|
||||
break;
|
||||
case 0.5:
|
||||
options.push('*')
|
||||
break;
|
||||
case 0.6:
|
||||
options.push('/')
|
||||
break;
|
||||
case 0.4:
|
||||
options.push('-')
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
return { nums, options }
|
||||
},
|
||||
}
|
BIN
packages/juejin-maths-game/statics/maths-code.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
packages/juejin-maths-game/statics/maths.png
Normal file
After Width: | Height: | Size: 992 KiB |
30
packages/juejin-maths-vue/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# maths-juejin
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
|
||||
### THANKS
|
||||
|
||||
- 1、[数字谜题解](https://juejin.cn/post/7067862481800003591).
|
||||
|
||||
- 2、[vue-juejin-mining](https://github.com/642134542/vue-juejin-mining).
|
5
packages/juejin-maths-vue/babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
27342
packages/juejin-maths-vue/package-lock.json
generated
Normal file
47
packages/juejin-maths-vue/package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "maths-juejin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.24.0",
|
||||
"core-js": "^3.6.5",
|
||||
"element-ui": "^2.15.6",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"less-loader": "^6.0.0",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"less": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
BIN
packages/juejin-maths-vue/public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
17
packages/juejin-maths-vue/public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
26
packages/juejin-maths-vue/src/App.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<home/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import home from './components/home';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
home,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
</style>
|
22
packages/juejin-maths-vue/src/api/juejin.js
Normal file
@ -0,0 +1,22 @@
|
||||
import request from "@/utils/request.js";
|
||||
|
||||
/* 开始 */
|
||||
/**
|
||||
*
|
||||
* @param {*} params {}
|
||||
* @param {*} uid
|
||||
* @param {*} time
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export function start(params, uid, time) {
|
||||
return request({
|
||||
url: `/game/num-puzz/ugc/start?uid=${uid}&time=${time}`,
|
||||
method: "post",
|
||||
headers: {
|
||||
"Allow-Control-Allow-Origin": "*",
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
},
|
||||
data: params,
|
||||
});
|
||||
}
|
377
packages/juejin-maths-vue/src/components/home.vue
Normal file
@ -0,0 +1,377 @@
|
||||
<template>
|
||||
<el-container class="home-container">
|
||||
<el-aside width="500px">
|
||||
<el-form ref="form" :model="form" :rules="formRule" label-width="80px" size="mini">
|
||||
<el-form-item label="UID" prop="uid">
|
||||
<el-input v-model="form.uid" @blur="saveLocal"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="TOKEN" prop="token">
|
||||
<el-input v-model="form.token" @blur="saveLocal"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row class="margin-top-20">
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" @click="submitForm('start')">自动开始</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" @click="submitForm('command')">发起指令</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="form"
|
||||
:rules="formRule"
|
||||
label-width="80px"
|
||||
size="mini"
|
||||
class="margin-top-20"
|
||||
>
|
||||
<el-form-item label="数字" prop="manualNumber">
|
||||
<el-input v-model="manualForm.manualNumber"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作" prop="manualOptions">
|
||||
<el-input v-model="manualForm.manualOptions"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标值" prop="manualTarget">
|
||||
<el-input v-model="manualForm.manualTarget"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row class="margin-top-20">
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" @click="submitForm('manual')">手动开始</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" @click="submitForm('test')">测试案例</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-aside>
|
||||
<el-main class="home-main">
|
||||
<div class="left-text log-basic">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<p>
|
||||
数字:<span class="is-bold">{{ mathInfo.nums }}</span>
|
||||
</p>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<p>
|
||||
操作字符:<span class="is-bold">{{ mathInfo.options }}</span>
|
||||
</p>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="border-bottom"></div>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<p>
|
||||
目标:<span class="is-bold">{{ mathInfo.target }}</span>
|
||||
</p>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<p>
|
||||
游戏共建者:<span class="is-bold">{{ author }}</span>
|
||||
</p>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="border-bottom"></div>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<p>
|
||||
当前关卡:<span class="is-bold">{{ round }}</span>
|
||||
</p>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<p>
|
||||
剩余BUG:<span class="is-bold">{{ bug }}</span>
|
||||
</p>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="border-bottom"></div>
|
||||
</div>
|
||||
<div class="log-container left-text">
|
||||
<h3 class="left-text">日志:</h3>
|
||||
<el-button class="btn-clear" type="primary" size="mini" @click="clearLog">清空</el-button>
|
||||
<div class="log-list">
|
||||
<p class="log-item" v-for="(item, index) in logData" :key="index">
|
||||
<label>{{ index + 1 }}:</label> {{ item.msg }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { start } from '../api/juejin'
|
||||
export default {
|
||||
name: 'home',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
manualForm: {
|
||||
manualNumber: '',
|
||||
manualOptions: '',
|
||||
manualTarget: '',
|
||||
},
|
||||
form: {
|
||||
uid: '',
|
||||
token: '',
|
||||
},
|
||||
answer: {
|
||||
values: '',
|
||||
formula: '',
|
||||
},
|
||||
formRule: {
|
||||
uid: [{ required: true, message: '请输入uid', trigger: 'blur' }],
|
||||
token: [{ required: true, message: '请输入token', trigger: 'blur' }],
|
||||
},
|
||||
logData: [],
|
||||
author: 'wangscaler', // 当前作者
|
||||
bug: 0,
|
||||
round: 0,
|
||||
cache: {},
|
||||
type: 2, //第二代
|
||||
needNum: 5, //正确解的数量
|
||||
result: '',
|
||||
nowNum: 0,
|
||||
mathInfo: {
|
||||
nums: [],
|
||||
options: [],
|
||||
target: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getLocal()
|
||||
},
|
||||
methods: {
|
||||
operatorArr(arr) {
|
||||
let nums = []
|
||||
let options = []
|
||||
arr.forEach((item) => {
|
||||
item.forEach((key) => {
|
||||
//整数为数字
|
||||
if (key % 1 === 0) {
|
||||
nums.push(key)
|
||||
} else {
|
||||
//运算符匹配
|
||||
switch (key) {
|
||||
case 0.3:
|
||||
options.push('+')
|
||||
break
|
||||
case 0.4:
|
||||
options.push('-')
|
||||
break
|
||||
case 0.5:
|
||||
options.push('*')
|
||||
break
|
||||
case 0.6:
|
||||
options.push('/')
|
||||
break
|
||||
case 0.4:
|
||||
options.push('-')
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return { nums, options }
|
||||
},
|
||||
getLocal() {
|
||||
this.form.uid = localStorage.getItem('uid')
|
||||
this.form.token = localStorage.getItem('token')
|
||||
// this.saveLocal();
|
||||
},
|
||||
// 保存本地
|
||||
saveLocal() {
|
||||
localStorage.setItem('uid', this.form.uid)
|
||||
localStorage.setItem('token', this.form.token)
|
||||
if (this.form.uid && this.form.token) {
|
||||
this.getRecord()
|
||||
}
|
||||
},
|
||||
submitForm(funName) {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
this[funName]()
|
||||
} else {
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
calc(a, option, b) {
|
||||
let res
|
||||
let formula
|
||||
switch (option) {
|
||||
case '+':
|
||||
res = a.value + b.value
|
||||
formula = a.formula + '+' + b.formula
|
||||
break
|
||||
case '-':
|
||||
res = a.value - b.value
|
||||
formula = a.formula + '-' + b.formula
|
||||
break
|
||||
case '*':
|
||||
res = a.value * b.value
|
||||
formula = a.formula + '*' + b.formula
|
||||
break
|
||||
case '/':
|
||||
res = a.value / b.value
|
||||
formula = a.formula + '/' + b.formula
|
||||
break
|
||||
case '.':
|
||||
res = Number([a.value, b.value].join(''))
|
||||
formula = a.formula + '.' + b.formula
|
||||
break
|
||||
}
|
||||
|
||||
if (res < 0 || res % 1 !== 0) {
|
||||
res = NaN
|
||||
}
|
||||
return {
|
||||
value: res,
|
||||
formula: '(' + formula + ')',
|
||||
}
|
||||
},
|
||||
calcSum(nums, options, target) {
|
||||
let cha = nums.length - 1 - options.length
|
||||
if (cha) {
|
||||
options.push(...'.'.repeat(cha))
|
||||
}
|
||||
this.nowNum = 0
|
||||
options.sort()
|
||||
let len = nums.length
|
||||
nums.sort()
|
||||
for (let i = 0; i < len; i++) {
|
||||
let num = nums[i]
|
||||
nums[i] = {
|
||||
value: num,
|
||||
formula: '' + num,
|
||||
}
|
||||
}
|
||||
this.calcLoop(nums, options, target)
|
||||
this.cache = {}
|
||||
},
|
||||
calcLoop(nums, options, target) {
|
||||
if (nums.length === 1 && options.length == 0 && nums[0].value == target) {
|
||||
this.logData.unshift({
|
||||
msg: '答案:' + nums[0].formula,
|
||||
})
|
||||
this.nowNum++
|
||||
if (this.nowNum >= this.needNum) {
|
||||
this.logData.unshift({
|
||||
msg: '扫地盲僧第三代算法--运行结束--!',
|
||||
})
|
||||
return true
|
||||
}
|
||||
return // nums[0]
|
||||
}
|
||||
let nums2 = nums.map((v) => v.value)
|
||||
nums2.sort()
|
||||
let key = nums2.join() + '|' + options.join()
|
||||
if (this.cache[key]) {
|
||||
return
|
||||
}
|
||||
this.cache[key] = true
|
||||
for (let i of '*+-/.') {
|
||||
let index = options.indexOf(i)
|
||||
if (index >= 0) {
|
||||
let newOptions = [...options]
|
||||
newOptions.splice(index, 1)
|
||||
let len = nums.length
|
||||
for (let j = 0; j < len - 1; j++) {
|
||||
let newNums = [...nums]
|
||||
newNums.splice(j, 1)
|
||||
for (let k = j + 1; k < len; k++) {
|
||||
let newNums2 = [...newNums]
|
||||
//newNums2.splice(newNums2.indexOf(nums[k]), 1);
|
||||
let newNum = this.calc(nums[j], i, nums[k])
|
||||
if (isNaN(newNum.value)) {
|
||||
} else {
|
||||
newNums2[k - 1] = newNum //[newNum.formula, ...newNums2]
|
||||
if (this.calcLoop(newNums2, newOptions, target)) return true
|
||||
}
|
||||
if (i != '+' && i != '*') {
|
||||
let newNum = this.calc(nums[k], i, nums[j])
|
||||
if (isNaN(newNum.value)) {
|
||||
} else {
|
||||
newNums2[k - 1] = newNum
|
||||
if (this.calcLoop(newNums2, newOptions, target)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
start() {
|
||||
this.clearLog()
|
||||
const time = new Date().getTime()
|
||||
const params = {}
|
||||
start(params, this.form.uid, time, this.form.token).then(
|
||||
(res) => {
|
||||
const { data } = res
|
||||
this.logData.unshift({
|
||||
msg: '成功获取游戏数据',
|
||||
})
|
||||
this.author = data.author
|
||||
this.bug = data.bug
|
||||
this.round = data.round
|
||||
this.mathInfo.target = data.target
|
||||
this.manualForm.manualTarget = data.target
|
||||
const { nums, options } = this.operatorArr(data.map)
|
||||
this.mathInfo.nums = nums
|
||||
this.manualForm.manualNumber = nums
|
||||
this.mathInfo.options = options
|
||||
this.manualForm.manualOptions = options
|
||||
},
|
||||
() => {},
|
||||
)
|
||||
},
|
||||
test() {
|
||||
this.clearLog()
|
||||
this.manualForm.manualNumber = '4,14,24,34,44,54'
|
||||
this.manualForm.manualOptions = '+/*-+'
|
||||
this.manualForm.manualTarget = 24
|
||||
this.mathInfo.nums = '4,14,24,34,44,54'
|
||||
this.mathInfo.options = '+/*-+'
|
||||
this.mathInfo.target = 24
|
||||
},
|
||||
manual() {
|
||||
this.clearLog()
|
||||
let manualNums = []
|
||||
let manualOptions = []
|
||||
manualNums = String(this.manualForm.manualNumber).trim().split(',')
|
||||
var numArr = []
|
||||
var optionsArr = []
|
||||
for (var i = 0; i < manualNums.length; i++) {
|
||||
numArr.push(Number(manualNums[i]))
|
||||
}
|
||||
this.mathInfo.nums = numArr
|
||||
manualOptions = String(this.manualForm.manualOptions).trim().split('')
|
||||
for (var i = 0; i < manualOptions.length; i++) {
|
||||
var currChar = manualOptions[i]
|
||||
if (currChar === '+' || currChar === '-' || currChar === '*' || currChar === '/') {
|
||||
optionsArr.push(currChar)
|
||||
}
|
||||
}
|
||||
this.mathInfo.options = optionsArr
|
||||
this.mathInfo.target = Number(this.manualForm.manualTarget)
|
||||
this.command()
|
||||
},
|
||||
// 发出指令
|
||||
command() {
|
||||
this.clearLog()
|
||||
let nums = JSON.parse(JSON.stringify(this.mathInfo.nums))
|
||||
let options = JSON.parse(JSON.stringify(this.mathInfo.options))
|
||||
let target = JSON.parse(JSON.stringify(this.mathInfo.target))
|
||||
console.log(nums)
|
||||
this.calcSum(nums, options, target)
|
||||
},
|
||||
clearLog() {
|
||||
this.logData = []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
91
packages/juejin-maths-vue/src/less/home.less
Normal file
@ -0,0 +1,91 @@
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
.home-container {
|
||||
height: 100%;
|
||||
.el-aside {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
.el-main {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.home-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #ccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.log-basic {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
.is-bold {
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.el-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.border-bottom {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
margin-top: 10px;
|
||||
background-color: #aaa;
|
||||
}
|
||||
}
|
||||
.log-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
.btn-clear {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
.log-list {
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
overflow: auto;
|
||||
p {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.log-item {
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
width: 110px;
|
||||
}
|
||||
.margin-top-20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.el-dialog__header {
|
||||
text-align: left;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 15px;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
text-align: center;
|
||||
}
|
||||
.el-message-box__btns {
|
||||
content: " ";
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
.el-button--primary {
|
||||
float: left;
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
120
packages/juejin-maths-vue/src/less/index.less
Normal file
@ -0,0 +1,120 @@
|
||||
@txt-color: #fff;
|
||||
|
||||
html, body, div, span, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
abbr, address, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, samp,
|
||||
small, strong, sub, sup, var,
|
||||
b, i,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend, button,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, figcaption, figure,
|
||||
footer, header, menu, nav, section, summary,
|
||||
time, mark, audio, video {
|
||||
font-family: 'PingFang SC', 'Microsoft Yahei', 'Hiragino Sans GB', Arial, Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft Yahei', Arial, Helvetica, sans-serif;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: @txt-color;
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @txt-color;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.clearfix:after,
|
||||
.clearfix:before {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.center-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.left-text {
|
||||
text-align: left;
|
||||
}
|
||||
.right-text {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.txt-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
button[disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
/*滚动条轨道 内阴影+圆角*/
|
||||
::-webkit-scrollbar-track {
|
||||
//-webkit-box-shadow: inset 0 0 6px @light-border;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/*滑块 内阴影+圆角*/
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px; /*滚动条的圆角*/
|
||||
//-webkit-box-shadow: inset 0 0 6px @light-border;
|
||||
background: rgba(44, 129, 224, 0.2);
|
||||
&:hover {
|
||||
background: rgba(44, 129, 224, 0.5);
|
||||
}
|
||||
}
|
14
packages/juejin-maths-vue/src/main.js
Normal file
@ -0,0 +1,14 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import ElementUI from 'element-ui';
|
||||
import 'element-ui/lib/theme-chalk/index.css';
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(ElementUI);
|
||||
|
||||
import './less/index.less';
|
||||
import './less/home.less';
|
||||
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
29
packages/juejin-maths-vue/src/utils/request.js
Normal file
@ -0,0 +1,29 @@
|
||||
import axios from "axios";
|
||||
|
||||
const service = axios.create({
|
||||
timeout: 1000 * 60 * 5, // request timeout
|
||||
});
|
||||
|
||||
const tokenHeader = "authorization"; // token自定义头部名称
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
config.headers[tokenHeader] = token;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
service.interceptors.response.use(
|
||||
(res) => res.data,
|
||||
(err) => {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
export default service;
|
14
packages/juejin-maths-vue/src/utils/running.env.js
Normal file
@ -0,0 +1,14 @@
|
||||
// let type = process.argv.slice(2)[0];
|
||||
|
||||
// if (type === "--test") {
|
||||
// process.env.VUE_APP_BASE_API = "http://121.36.28.248:8195/api/v1/";
|
||||
// }
|
||||
// if (type == "--prod") {
|
||||
// process.env.VUE_APP_BASE_API = "http://zytech.org.cn/api/v1/";
|
||||
// }
|
||||
|
||||
"use strict";
|
||||
module.exports = {
|
||||
PROD_BASE_URL: '"https://juejin-game.bytedance.com"',
|
||||
TEST_BASE_URL: 'https://api.juejin.cn/user_api/v1/user/get'
|
||||
};
|
36
packages/juejin-maths-vue/vue.config.js
Normal file
@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* You will need to set publicPath if you plan to deploy your site under a sub path,
|
||||
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
|
||||
* then publicPath should be set to "/bar/".
|
||||
* In most cases please use '/' !!!
|
||||
* Detail: https://cli.vuejs.org/config/#publicpath
|
||||
*/
|
||||
publicPath: "./",
|
||||
outputDir: "dist",
|
||||
assetsDir: "static",
|
||||
lintOnSave: process.env.NODE_ENV === "development",
|
||||
productionSourceMap: false,
|
||||
devServer: {
|
||||
port: 9550,
|
||||
open: true,
|
||||
overlay: {
|
||||
warnings: false,
|
||||
errors: true,
|
||||
},
|
||||
proxy: {
|
||||
// change xxx-api/login => mock/login
|
||||
// detail: https://cli.vuejs.org/config/#devserver-proxy
|
||||
"/game": {
|
||||
ws: false,
|
||||
target: "https://juejin-game.bytedance.com",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/user": {
|
||||
ws: false,
|
||||
target: "https://api.juejin.cn/user_api/v1/user/get",
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
12
plop-tpls/component/index.hbs
Normal file
@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Your content -->
|
||||
</div>
|
||||
</template>
|
||||
<script setup{{#if isGlobal}} name="{{ properCase name }}" {{/if}}>
|
||||
// const { proxy } = getCurrentInstance()
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
63
plop-tpls/component/prompt.js
Normal file
@ -0,0 +1,63 @@
|
||||
const fs = require('fs')
|
||||
|
||||
function getFolder(path) {
|
||||
let components = []
|
||||
const files = fs.readdirSync(path)
|
||||
files.forEach(function(item) {
|
||||
let stat = fs.lstatSync(path + '/' + item)
|
||||
if (stat.isDirectory() === true && item != 'components') {
|
||||
components.push(path + '/' + item)
|
||||
components.push.apply(components, getFolder(path + '/' + item))
|
||||
}
|
||||
})
|
||||
return components
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: '创建组件',
|
||||
prompts: [
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'isGlobal',
|
||||
message: '是否为全局组件',
|
||||
default: false
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'path',
|
||||
message: '请选择组件创建目录',
|
||||
choices: getFolder('src/pages'),
|
||||
when: answers => {
|
||||
return !answers.isGlobal
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: '请输入组件名称',
|
||||
validate: v => {
|
||||
if (!v || v.trim === '') {
|
||||
return '组件名称不能为空'
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
actions: data => {
|
||||
let path = ''
|
||||
if (data.isGlobal) {
|
||||
path = 'src/components/{{properCase name}}/index.vue'
|
||||
} else {
|
||||
path = `${data.path}/components/{{properCase name}}/index.vue`
|
||||
}
|
||||
const actions = [
|
||||
{
|
||||
type: 'add',
|
||||
path: path,
|
||||
templateFile: 'plop-tpls/component/index.hbs'
|
||||
}
|
||||
]
|
||||
return actions
|
||||
}
|
||||
}
|
15
plop-tpls/page/index.hbs
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Your content -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="{{ properCase componentName }}">
|
||||
// const { proxy } = getCurrentInstance()
|
||||
// const router = useRouter()
|
||||
// const route = useRoute()
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
53
plop-tpls/page/prompt.js
Normal file
@ -0,0 +1,53 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
function getFolder(path) {
|
||||
let components = []
|
||||
const files = fs.readdirSync(path)
|
||||
files.forEach(function (item) {
|
||||
let stat = fs.lstatSync(path + '/' + item)
|
||||
if (stat.isDirectory() === true && item != 'components') {
|
||||
components.push(path + '/' + item)
|
||||
components.push.apply(components, getFolder(path + '/' + item))
|
||||
}
|
||||
})
|
||||
return components
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: '创建页面',
|
||||
prompts: [
|
||||
{
|
||||
type: 'list',
|
||||
name: 'path',
|
||||
message: '请选择页面创建目录',
|
||||
choices: getFolder('src/pages')
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: '请输入文件名',
|
||||
validate: v => {
|
||||
if (!v || v.trim === '') {
|
||||
return '文件名不能为空'
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
actions: data => {
|
||||
let relativePath = path.relative('src/pages', data.path)
|
||||
const actions = [
|
||||
{
|
||||
type: 'add',
|
||||
path: `${data.path}/{{dotCase name}}.vue`,
|
||||
templateFile: 'plop-tpls/page/index.hbs',
|
||||
data: {
|
||||
componentName: `${relativePath} ${data.name}`
|
||||
}
|
||||
}
|
||||
]
|
||||
return actions
|
||||
}
|
||||
}
|
11
plop-tpls/store/index.hbs
Normal file
@ -0,0 +1,11 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import piniaStore from '@/store/index'
|
||||
|
||||
export const use{{ properCase name }}Store = defineStore('{{ camelCase name }}', {
|
||||
state: () => ({}),
|
||||
getters: {},
|
||||
actions: {}
|
||||
})
|
||||
export function use{{ properCase name }}OutsideStore() {
|
||||
return use{{ properCase name }}Store(piniaStore)
|
||||
}
|
50
plop-tpls/store/prompt.js
Normal file
@ -0,0 +1,50 @@
|
||||
const fs = require('fs')
|
||||
function getFolder(path) {
|
||||
let components = []
|
||||
const files = fs.readdirSync(path)
|
||||
files.forEach(function (item) {
|
||||
let stat = fs.lstatSync(path + '/' + item)
|
||||
if (stat.isDirectory() === true && item != 'components') {
|
||||
components.push(path + '/' + item)
|
||||
components.push.apply(components, getFolder(path + '/' + item))
|
||||
}
|
||||
})
|
||||
return components
|
||||
}
|
||||
module.exports = {
|
||||
description: '创建全局模块化状态',
|
||||
prompts: [
|
||||
{
|
||||
type: 'list',
|
||||
name: 'path',
|
||||
message: '请选择页面创建目录',
|
||||
choices: getFolder('src/store')
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: '请输入模块名称',
|
||||
validate: v => {
|
||||
if (!v || v.trim === '') {
|
||||
return '模块名称不能为空'
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
actions: (data) => {
|
||||
const actions = [
|
||||
{
|
||||
type: 'add',
|
||||
path: `${data.path}/{{camelCase name}}/index.ts`,
|
||||
templateFile: 'plop-tpls/store/index.hbs'
|
||||
},
|
||||
{
|
||||
type: 'add',
|
||||
path: `${data.path}/{{camelCase name}}/types.ts`,
|
||||
}
|
||||
]
|
||||
return actions
|
||||
}
|
||||
}
|
6
plopfile.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = function (plop) {
|
||||
plop.setWelcomeMessage('请选择需要创建的模式:')
|
||||
plop.setGenerator('page', require('./plop-tpls/page/prompt'))
|
||||
plop.setGenerator('component', require('./plop-tpls/component/prompt'))
|
||||
plop.setGenerator('store', require('./plop-tpls/store/prompt'))
|
||||
}
|
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- 'packages/*'
|
28
postcss.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
// 'postcss-px-to-viewport': {
|
||||
// // 需要转换的单位,默认为 px
|
||||
// unitToConvert: 'px',
|
||||
// // 视窗的宽度,对应的是我们设计稿的宽度
|
||||
// viewportWidth: 750,
|
||||
// // 指定 px 转换为视窗单位值的小数位数(很多时候无法整除)
|
||||
// unitPrecision: 3,
|
||||
// // 能转化为 vw 的属性列表
|
||||
// propList: ['*'],
|
||||
// // 指定需要转换成的视窗单位,建议使用 vw
|
||||
// viewportUnit: 'vw',
|
||||
// // 字体使用的视口单位
|
||||
// fontViewportUnit: 'vw',
|
||||
// // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
|
||||
// selectorBlackList: [
|
||||
// '.ignore'
|
||||
// ],
|
||||
// // 小于或等于 1px 不转换为视窗单位,你也可以设置为你想要的值
|
||||
// minPixelValue: 1,
|
||||
// // 允许在媒体查询中转换 px
|
||||
// mediaQuery: false
|
||||
// }
|
||||
},
|
||||
}
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
19
src/.pnpm-debug.log
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"0 debug pnpm:scope": {
|
||||
"selected": 1
|
||||
},
|
||||
"1 error pnpm": {
|
||||
"errno": 1,
|
||||
"code": "ELIFECYCLE",
|
||||
"pkgid": "vvpt@0.0.3",
|
||||
"stage": "lint",
|
||||
"script": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
|
||||
"pkgname": "vvpt",
|
||||
"err": {
|
||||
"name": "pnpm",
|
||||
"message": "vvpt@0.0.3 lint: `eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx`\nExit status 1",
|
||||
"code": "ELIFECYCLE",
|
||||
"stack": "pnpm: vvpt@0.0.3 lint: `eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx`\nExit status 1\n at EventEmitter.<anonymous> (C:\\Users\\MaLe\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\pnpm@6.26.1\\node_modules\\pnpm\\dist\\pnpm.cjs:105378:20)\n at EventEmitter.emit (node:events:390:28)\n at ChildProcess.<anonymous> (C:\\Users\\MaLe\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\pnpm@6.26.1\\node_modules\\pnpm\\dist\\pnpm.cjs:91965:18)\n at ChildProcess.emit (node:events:390:28)\n at maybeClose (node:internal/child_process:1064:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:301:5)"
|
||||
}
|
||||
}
|
||||
}
|
41
src/App.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<n-message-provider>
|
||||
<router-view></router-view>
|
||||
</n-message-provider>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import { useAppStore } from './store/modules/app'
|
||||
// const appStore = useAppStore()
|
||||
|
||||
|
||||
// provide('reload', reload)
|
||||
// function reload() {
|
||||
// isRouterAlive.value = false
|
||||
// nextTick(() => (isRouterAlive.value = true))
|
||||
// }
|
||||
|
||||
// const isRouterAlive = ref(true)
|
||||
|
||||
// watch(
|
||||
// () => appStore.title,
|
||||
// () => {
|
||||
// const title: string = appStore.title
|
||||
// document.title = title
|
||||
// ? `${title} - ${import.meta.env.VITE_APP_TITLE}`
|
||||
// : import.meta.env.VITE_APP_TITLE
|
||||
// },
|
||||
// {
|
||||
// immediate: true,
|
||||
// }
|
||||
// )
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
background-color: var(--color-bg-1);
|
||||
}
|
||||
</style>
|
24
src/api/user/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
// 权限问题后期增加
|
||||
import { get, post } from '@utils/http/axios'
|
||||
import { IResponse } from '@utils/http/axios/type'
|
||||
import { ReqAuth, ReqParams, ResResult } from './types';
|
||||
import { UserState } from '@/store/modules/user/types';
|
||||
// import axios from 'axios';
|
||||
enum URL {
|
||||
login = '/user/login',
|
||||
logout = '/user/logout',
|
||||
profile = '/user/profile'
|
||||
}
|
||||
interface LoginRes {
|
||||
token: string
|
||||
}
|
||||
|
||||
export interface LoginData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const getUserProfile = async () => get<UserState>({ url: URL.profile });
|
||||
const login = async (data: LoginData) => post<any>({ url: URL.login, data });
|
||||
const logout = async () => post<LoginRes>({ url: URL.logout });
|
||||
export { getUserProfile, logout, login };
|
26
src/api/user/types.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export interface ReqParams {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface ReqAuth {
|
||||
auths: string[];
|
||||
modules: string[];
|
||||
is_admin?: 0 | 1;
|
||||
}
|
||||
|
||||
// export interface ResResult {
|
||||
// data?: ResResultData;
|
||||
// status: string | '';
|
||||
// headers: object
|
||||
// }
|
||||
export interface ResResult {
|
||||
|
||||
}
|
||||
export interface ResResultData {
|
||||
code?: number;
|
||||
result?: any;
|
||||
message: string;
|
||||
status: string
|
||||
}
|
||||
|
BIN
src/assets/fonts/Blimone-ExtraBold.woff
Normal file
BIN
src/assets/fonts/Blimone-ExtraLight.woff
Normal file
BIN
src/assets/fonts/Blimone-Light.woff
Normal file
BIN
src/assets/fonts/Blimone-Regular.woff
Normal file
4
src/assets/icons/svg/github.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 16 16" fill="" aria-hidden="true">
|
||||
<path
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
|
||||
</svg>
|
After Width: | Height: | Size: 653 B |
83
src/assets/icons/svg/logo.svg
Normal file
@ -0,0 +1,83 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 340 250" preserveAspectRatio="xMidYMid meet"
|
||||
color-interpolation-filters="sRGB">
|
||||
<g fill="#FFFFFF">
|
||||
<g class="tp-name">
|
||||
<rect fill="#FFFFFF" fill-opacity="0" stroke-width="2" x="0" y="0" width="25.599482540267125"
|
||||
height="34.760180512542604" class="image-rect"></rect>
|
||||
<svg x="0" y="0" width="25.599482540267125" height="34.760180512542604" filtersec="colorsb4636060174"
|
||||
class="image-svg-svg primary"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-0.005272185895591974 -1.4179225793498063e-8 89.71527099609375 121.82000732421875">
|
||||
<title>资源 46</title>
|
||||
<path
|
||||
d="M79.29 15.71a10.46 10.46 0 0 1 .08-1.59c-.05.52-.08 1.05-.08 1.59zM15.71 31.42A15.71 15.71 0 0 1 0 15.71v90.55a15.56 15.56 0 0 0 31.12 0V76.69H17.54a15.71 15.71 0 0 1 0-31.42h13.58V31.42z"
|
||||
fill="#00d5ff"></path>
|
||||
<path d="M61.4 76.69a15.71 15.71 0 1 0 0-31.42H31.12v31.42z" fill="#ffd100"></path>
|
||||
<path d="M1.83 61a15.71 15.71 0 0 0 15.71 15.69h13.58V45.27H17.54A15.71 15.71 0 0 0 1.83 61z"
|
||||
fill="#45ae17"></path>
|
||||
<path
|
||||
d="M15.56.15a15.56 15.56 0 0 1 15.56 15.56v15.71H74A15.71 15.71 0 0 0 74 0H15.71A15.71 15.71 0 0 0 .08 14.12 15.56 15.56 0 0 1 15.56.15z"
|
||||
fill="#f0f"></path>
|
||||
<path
|
||||
d="M15.71 31.42h15.41V15.71a15.56 15.56 0 0 0-31-1.59A7 7 0 0 0 0 15.71a15.71 15.71 0 0 0 15.71 15.71z"
|
||||
fill="#2300fd"></path>
|
||||
</svg></svg>
|
||||
<g transform="translate(29, 2.6853819794734743)">
|
||||
<g data-gra="path-name" fill="#FFFFFF" transform="scale(1)">
|
||||
<path
|
||||
d="M11.28-20.39Q15.43-20.39 17.44-18.34Q19.46-16.28 19.46-12.09L19.46-12.09L19.46-2.16Q19.46-1.06 18.81-0.43Q18.15 0.20 17.01 0.20L17.01 0.20Q15.96 0.20 15.28-0.45Q14.61-1.10 14.61-2.16L14.61-2.16L14.61-3.05Q13.92-1.47 12.44-0.57Q10.95 0.33 9.00 0.33L9.00 0.33Q7.00 0.33 5.37-0.49Q3.74-1.30 2.81-2.73Q1.87-4.15 1.87-5.90L1.87-5.90Q1.87-8.10 2.99-9.36Q4.11-10.62 6.64-11.19Q9.16-11.76 13.60-11.76L13.60-11.76L14.61-11.76L14.61-12.70Q14.61-14.69 13.76-15.61Q12.90-16.53 10.99-16.53L10.99-16.53Q9.81-16.53 8.59-16.18Q7.37-15.83 5.70-15.18L5.70-15.18Q4.64-14.65 4.15-14.65L4.15-14.65Q3.42-14.65 2.95-15.18Q2.48-15.71 2.48-16.57L2.48-16.57Q2.48-17.26 2.83-17.77Q3.18-18.28 3.99-18.72L3.99-18.72Q5.41-19.50 7.39-19.95Q9.36-20.39 11.28-20.39L11.28-20.39ZM10.01-3.34Q12.05-3.34 13.33-4.70Q14.61-6.07 14.61-8.22L14.61-8.22L14.61-9.08L13.88-9.08Q11.15-9.08 9.65-8.83Q8.14-8.59 7.49-7.98Q6.84-7.37 6.84-6.31L6.84-6.31Q6.84-5.01 7.75-4.17Q8.67-3.34 10.01-3.34L10.01-3.34Z"
|
||||
transform="translate(-1.8724559023066483, 29.063772048846673)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(50, 2.6853819794734743)">
|
||||
<g data-gra="path-name" fill="#FFFFFF" transform="scale(1)">
|
||||
<path
|
||||
d="M9.85 0.33Q5.41 0.33 2.69-1.42L2.69-1.42Q1.38-2.20 1.38-3.62L1.38-3.62Q1.38-4.44 1.83-4.95Q2.28-5.45 2.93-5.45L2.93-5.45Q3.62-5.45 4.84-4.80L4.84-4.80Q6.15-4.19 7.23-3.87Q8.30-3.54 9.97-3.54L9.97-3.54Q11.68-3.54 12.64-4.11Q13.60-4.68 13.60-5.70L13.60-5.70Q13.60-6.39 13.21-6.80Q12.82-7.20 11.82-7.55Q10.83-7.90 8.79-8.34L8.79-8.34Q5.17-9.08 3.60-10.42Q2.04-11.76 2.04-14.12L2.04-14.12Q2.04-15.92 3.09-17.36Q4.15-18.81 6.00-19.60Q7.86-20.39 10.22-20.39L10.22-20.39Q11.93-20.39 13.51-19.97Q15.10-19.54 16.36-18.72L16.36-18.72Q17.67-17.91 17.67-16.57L17.67-16.57Q17.67-15.75 17.22-15.20Q16.77-14.65 16.12-14.65L16.12-14.65Q15.67-14.65 15.24-14.84Q14.82-15.02 14.17-15.39L14.17-15.39Q12.99-16.00 12.13-16.30Q11.28-16.61 10.01-16.61L10.01-16.61Q8.51-16.61 7.63-16.00Q6.76-15.39 6.76-14.33L6.76-14.33Q6.76-13.31 7.65-12.78Q8.55-12.25 11.11-11.72L11.11-11.72Q13.84-11.15 15.39-10.42Q16.93-9.69 17.61-8.59Q18.28-7.49 18.28-5.78L18.28-5.78Q18.28-3.01 15.98-1.34Q13.68 0.33 9.85 0.33L9.85 0.33Z"
|
||||
transform="translate(-1.383989145183175, 29.063772048846673)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(70, 2.6853819794734743)">
|
||||
<g data-gra="path-name" fill="#FFFFFF" transform="scale(1)">
|
||||
<path
|
||||
d="M12.99-3.54Q15.02-3.42 15.02-1.71L15.02-1.71Q15.02-0.73 14.23-0.22Q13.43 0.28 11.97 0.20L11.97 0.20L10.87 0.12Q4.03-0.37 4.03-7.20L4.03-7.20L4.03-16.08L1.99-16.08Q0.90-16.08 0.31-16.57Q-0.28-17.06-0.28-17.99L-0.28-17.99Q-0.28-18.93 0.31-19.42Q0.90-19.91 1.99-19.91L1.99-19.91L4.03-19.91L4.03-23.65Q4.03-24.75 4.72-25.40Q5.41-26.05 6.59-26.05L6.59-26.05Q7.73-26.05 8.43-25.40Q9.12-24.75 9.12-23.65L9.12-23.65L9.12-19.91L12.58-19.91Q13.68-19.91 14.27-19.42Q14.86-18.93 14.86-17.99L14.86-17.99Q14.86-17.06 14.27-16.57Q13.68-16.08 12.58-16.08L12.58-16.08L9.12-16.08L9.12-6.84Q9.12-3.83 11.89-3.62L11.89-3.62L12.99-3.54Z"
|
||||
transform="translate(0.28493894165535955, 29.063772048846673)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(89, 2.6853819794734743)">
|
||||
<g data-gra="path-name" fill="#FFFFFF" transform="scale(1)">
|
||||
<path
|
||||
d="M4.64-8.87Q3.46-8.87 2.87-9.40Q2.28-9.93 2.28-10.87L2.28-10.87Q2.28-11.80 2.87-12.35Q3.46-12.90 4.64-12.90L4.64-12.90L13.03-12.90Q14.21-12.90 14.80-12.35Q15.39-11.80 15.39-10.87L15.39-10.87Q15.39-9.93 14.80-9.40Q14.21-8.87 13.03-8.87L13.03-8.87L4.64-8.87Z"
|
||||
transform="translate(-2.2795115332428764, 29.063772048846673)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(106, 2.6853819794734743)">
|
||||
<g data-gra="path-name" fill="#FFFFFF" transform="scale(1)">
|
||||
<path
|
||||
d="M23.98-27.56Q24.63-28.94 26.13-28.94L26.13-28.94Q27.15-28.94 27.96-28.29Q28.78-27.64 28.78-26.66L28.78-26.66Q28.78-26.21 28.53-25.64L28.53-25.64L17.26-1.38Q16.89-0.61 16.14-0.18Q15.39 0.24 14.53 0.24L14.53 0.24Q13.68 0.24 12.92-0.18Q12.17-0.61 11.80-1.38L11.80-1.38L0.57-25.64Q0.33-26.21 0.33-26.62L0.33-26.62Q0.33-27.64 1.16-28.29Q1.99-28.94 3.05-28.94L3.05-28.94Q3.74-28.94 4.34-28.60Q4.93-28.25 5.25-27.56L5.25-27.56L14.61-6.76L23.98-27.56Z"
|
||||
transform="translate(-0.3256445047489823, 29.063772048846673)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(138, 2.6853819794734743)">
|
||||
<g data-gra="path-name" fill="#FFFFFF" transform="scale(1)">
|
||||
<path
|
||||
d="M18.36-20.31Q19.54-20.31 20.23-19.66Q20.92-19.01 20.92-17.91L20.92-17.91L20.92-2.12Q20.92-1.06 20.21-0.41Q19.50 0.24 18.36 0.24L18.36 0.24Q17.26 0.24 16.61-0.37Q15.96-0.98 15.96-2.04L15.96-2.04L15.96-3.05Q15.02-1.42 13.43-0.55Q11.85 0.33 9.85 0.33L9.85 0.33Q2.52 0.33 2.52-7.90L2.52-7.90L2.52-17.91Q2.52-19.01 3.22-19.66Q3.91-20.31 5.05-20.31L5.05-20.31Q6.23-20.31 6.92-19.66Q7.61-19.01 7.61-17.91L7.61-17.91L7.61-7.86Q7.61-5.74 8.47-4.72Q9.32-3.70 11.15-3.70L11.15-3.70Q13.27-3.70 14.55-5.11Q15.83-6.51 15.83-8.83L15.83-8.83L15.83-17.91Q15.83-19.01 16.53-19.66Q17.22-20.31 18.36-20.31L18.36-20.31Z"
|
||||
transform="translate(-2.523744911804613, 29.063772048846673)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(160, 2.6853819794734743)">
|
||||
<g data-gra="path-name" fill="#FFFFFF" transform="scale(1)">
|
||||
<path
|
||||
d="M18.40-5.45Q19.09-5.45 19.52-4.93Q19.95-4.40 19.95-3.50L19.95-3.50Q19.95-2.24 18.44-1.38L18.44-1.38Q17.06-0.61 15.31-0.14Q13.55 0.33 11.97 0.33L11.97 0.33Q7.16 0.33 4.36-2.44Q1.55-5.21 1.55-10.01L1.55-10.01Q1.55-13.07 2.77-15.43Q3.99-17.79 6.21-19.09Q8.43-20.39 11.23-20.39L11.23-20.39Q13.92-20.39 15.92-19.21Q17.91-18.03 19.01-15.88Q20.11-13.72 20.11-10.79L20.11-10.79Q20.11-9.04 18.56-9.04L18.56-9.04L6.55-9.04Q6.80-6.23 8.14-4.91Q9.48-3.58 12.05-3.58L12.05-3.58Q13.35-3.58 14.35-3.91Q15.35-4.23 16.61-4.80L16.61-4.80Q17.83-5.45 18.40-5.45L18.40-5.45ZM11.36-16.81Q9.28-16.81 8.04-15.51Q6.80-14.21 6.55-11.76L6.55-11.76L15.75-11.76Q15.67-14.25 14.53-15.53Q13.39-16.81 11.36-16.81L11.36-16.81Z"
|
||||
transform="translate(-1.546811397557666, 29.063772048846673)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(182, 2.6853819794734743)">
|
||||
<g data-gra="path-name" fill="#FFFFFF" transform="scale(1)">
|
||||
<path
|
||||
d="M16.61-14.74Q19.17-14.08 20.54-12.27Q21.90-10.46 21.90-7.77L21.90-7.77Q21.90-4.07 19.17-1.87Q16.45 0.33 11.80 0.33L11.80 0.33Q9.16 0.33 6.72-0.49Q4.27-1.30 2.56-2.77L2.56-2.77Q1.63-3.58 1.63-4.68L1.63-4.68Q1.63-5.58 2.14-6.21Q2.65-6.84 3.34-6.84L3.34-6.84Q3.74-6.84 4.07-6.70Q4.40-6.55 4.97-6.23L4.97-6.23Q6.68-5.09 8.22-4.48Q9.77-3.87 11.60-3.87L11.60-3.87Q14.29-3.87 15.59-4.95Q16.89-6.02 16.89-8.22L16.89-8.22Q16.89-10.38 15.53-11.42Q14.17-12.46 11.32-12.46L11.32-12.46L9.00-12.46Q8.06-12.46 7.55-13.09Q7.04-13.72 7.04-14.53L7.04-14.53Q7.04-15.39 7.55-16.00Q8.06-16.61 9.00-16.61L9.00-16.61L10.62-16.61Q16.20-16.61 16.20-20.80L16.20-20.80Q16.20-22.75 15.02-23.81Q13.84-24.87 11.76-24.87L11.76-24.87Q8.79-24.87 5.41-22.51L5.41-22.51Q4.84-22.18 4.52-22.04Q4.19-21.90 3.79-21.90L3.79-21.90Q3.09-21.90 2.58-22.53Q2.08-23.16 2.08-24.06L2.08-24.06Q2.08-24.67 2.30-25.09Q2.52-25.52 3.01-25.97L3.01-25.97Q4.72-27.39 7.10-28.23Q9.48-29.06 11.97-29.06L11.97-29.06Q16.20-29.06 18.70-26.99Q21.21-24.91 21.21-21.45L21.21-21.45Q21.17-19.09 19.97-17.32Q18.77-15.55 16.61-14.74L16.61-14.74Z"
|
||||
transform="translate(-1.6282225237449115, 29.063772048846673)"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<!---->
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.8 KiB |
4
src/assets/icons/svg/marks.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="45" height="36" fill="rgb(217, 217, 217)">
|
||||
<path
|
||||
d="M13.415.001C6.07 5.185.887 13.681.887 23.041c0 7.632 4.608 12.096 9.936 12.096 5.04 0 8.784-4.032 8.784-8.784 0-4.752-3.312-8.208-7.632-8.208-.864 0-2.016.144-2.304.288.72-4.896 5.328-10.656 9.936-13.536L13.415.001zm24.768 0c-7.2 5.184-12.384 13.68-12.384 23.04 0 7.632 4.608 12.096 9.936 12.096 4.896 0 8.784-4.032 8.784-8.784 0-4.752-3.456-8.208-7.776-8.208-.864 0-1.872.144-2.16.288.72-4.896 5.184-10.656 9.792-13.536L38.183.001z" />
|
||||
</svg>
|
After Width: | Height: | Size: 519 B |
12
src/assets/icons/svg/test.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 16 16" fill="" aria-hidden="true">
|
||||
<path
|
||||
d="M79.29 15.71a10.46 10.46 0 0 1 .08-1.59c-.05.52-.08 1.05-.08 1.59zM15.71 31.42A15.71 15.71 0 0 1 0 15.71v90.55a15.56 15.56 0 0 0 31.12 0V76.69H17.54a15.71 15.71 0 0 1 0-31.42h13.58V31.42z"
|
||||
fill="#00d5ff"></path>
|
||||
<path d="M61.4 76.69a15.71 15.71 0 1 0 0-31.42H31.12v31.42z" fill="#ffd100"></path>
|
||||
<path d="M1.83 61a15.71 15.71 0 0 0 15.71 15.69h13.58V45.27H17.54A15.71 15.71 0 0 0 1.83 61z" fill="#45ae17"></path>
|
||||
<path
|
||||
d="M15.56.15a15.56 15.56 0 0 1 15.56 15.56v15.71H74A15.71 15.71 0 0 0 74 0H15.71A15.71 15.71 0 0 0 .08 14.12 15.56 15.56 0 0 1 15.56.15z"
|
||||
fill="#f0f"></path>
|
||||
<path d="M15.71 31.42h15.41V15.71a15.56 15.56 0 0 0-31-1.59A7 7 0 0 0 0 15.71a15.71 15.71 0 0 0 15.71 15.71z"
|
||||
fill="#2300fd"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 838 B |
5
src/assets/icons/svg/ts.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg fill="none" height="26" viewBox="0 0 27 26" width="27" xmlns="http://www.w3.org/2000/svg">
|
||||
<path clip-rule="evenodd"
|
||||
d="m.98608 0h24.32332c.5446 0 .9861.436522.9861.975v24.05c0 .5385-.4415.975-.9861.975h-24.32332c-.544597 0-.98608-.4365-.98608-.975v-24.05c0-.538478.441483-.975.98608-.975zm13.63142 13.8324v-2.1324h-9.35841v2.1324h3.34111v9.4946h2.6598v-9.4946zm1.0604 9.2439c.4289.2162.9362.3784 1.5218.4865.5857.1081 1.2029.1622 1.8518.1622.6324 0 1.2331-.0595 1.8023-.1784.5691-.1189 1.0681-.3149 1.497-.5879s.7685-.6297 1.0187-1.0703.3753-.9852.3753-1.6339c0-.4703-.0715-.8824-.2145-1.2365-.1429-.3541-.3491-.669-.6186-.9447-.2694-.2757-.5925-.523-.9692-.7419s-.8014-.4257-1.2743-.6203c-.3465-.1406-.6572-.2771-.9321-.4095-.275-.1324-.5087-.2676-.7011-.4054-.1925-.1379-.3409-.2838-.4454-.4379-.1045-.154-.1567-.3284-.1567-.523 0-.1784.0467-.3392.1402-.4824.0935-.1433.2254-.2663.3959-.369s.3794-.1824.6269-.2392c.2474-.0567.5224-.0851.8248-.0851.22 0 .4523.0162.697.0486.2447.0325.4908.0825.7382.15.2475.0676.4881.1527.7218.2555.2337.1027.4495.2216.6475.3567v-2.4244c-.4015-.1514-.84-.2636-1.3157-.3365-.4756-.073-1.0214-.1095-1.6373-.1095-.6268 0-1.2207.0662-1.7816.1987-.5609.1324-1.0544.3392-1.4806.6203s-.763.6392-1.0104 1.0743c-.2475.4352-.3712.9555-.3712 1.5609 0 .7731.2268 1.4326.6805 1.9785.4537.546 1.1424 1.0082 2.0662 1.3866.363.146.7011.2892 1.0146.4298.3134.1405.5842.2865.8124.4378.2282.1514.4083.3162.5403.4946s.198.3811.198.6082c0 .1676-.0413.323-.1238.4662-.0825.1433-.2076.2676-.3753.373s-.3766.1879-.6268.2473c-.2502.0595-.5431.0892-.8785.0892-.5719 0-1.1383-.0986-1.6992-.2959-.5608-.1973-1.0805-.4933-1.5589-.8879z"
|
||||
fill="#fff" fill-rule="evenodd"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
4
src/assets/icons/svg/twitter.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="33" height="32" fill="rgb(217, 217, 217)">
|
||||
<path
|
||||
d="M32.411 6.584c-1.113.493-2.309.826-3.566.977a6.228 6.228 0 002.73-3.437 12.4 12.4 0 01-3.944 1.506 6.212 6.212 0 00-10.744 4.253c0 .486.056.958.16 1.414a17.638 17.638 0 01-12.802-6.49 6.208 6.208 0 00-.84 3.122 6.212 6.212 0 002.762 5.17 6.197 6.197 0 01-2.813-.777v.08c0 3.01 2.14 5.52 4.983 6.091a6.258 6.258 0 01-2.806.107 6.215 6.215 0 005.803 4.312 12.464 12.464 0 01-7.715 2.66c-.501 0-.996-.03-1.482-.087a17.566 17.566 0 009.52 2.79c11.426 0 17.673-9.463 17.673-17.671 0-.267-.007-.536-.019-.803a12.627 12.627 0 003.098-3.213l.002-.004z" />
|
||||
</svg>
|
After Width: | Height: | Size: 630 B |
BIN
src/assets/images/banner-02.webp
Normal file
After Width: | Height: | Size: 17 KiB |
48
src/assets/images/banner2.svg
Normal file
@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 479 285" class="design-iconfont">
|
||||
<defs>
|
||||
<path id="pv1wlsps5a" d="M30.0000012 0A30.0000012 30.0000012 0 1 0 30.0000012 60.0000024A30.0000012 30.0000012 0 1 0 30.0000012 0Z"/>
|
||||
<path id="cln61id2xc" d="M0 10L107 10 107 0 0 0z"/>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#0050F0" d="M0 -2.6454533e-17L341 0 341 244 0 244z" transform="translate(85)"/>
|
||||
<path fill="#FFF" d="M0 0H73V76H0z" transform="translate(140 39)"/>
|
||||
<path fill="#00D2FA" d="M6 52H67V70H6z" transform="translate(140 39)"/>
|
||||
<path d="M6,0.4 C9.0927946,0.4 11.6,2.9072054 11.6,6 C11.6,9.0927946 9.0927946,11.6 6,11.6 C2.9072054,11.6 0.4,9.0927946 0.4,6 C0.4,2.9072054 2.9072054,0.4 6,0.4 Z M8.54838049,3.57744612 L5.6318,6.8897 L3.7055065,4.8789932 L2.6944935,5.84742115 L5.67541296,8.95942171 L9.5991781,4.50255388 L8.54838049,3.57744612 Z" transform="translate(149 94)" fill="#FFF" fill-rule="nonzero"/>
|
||||
<text font-family="DINAlternate-Bold, DIN Alternate" font-size="10" font-weight="bold" fill="#111" transform="translate(140 39)">
|
||||
<tspan x="6.19999766" y="15.3999989">12</tspan>
|
||||
</text>
|
||||
<path fill="#FFF" d="M215 149H224V202H215z" transform="translate(85)"/>
|
||||
<path fill="#00F0DC" d="M231 130H240V202H231z" transform="translate(85)"/>
|
||||
<path fill="#FFF" d="M247 174H256V202H247z" transform="translate(85)"/>
|
||||
<path fill="#00D2FA" d="M263 161H272V202H263z" transform="translate(85)"/>
|
||||
<path fill="#FFF" d="M128 174H179V178H128z" transform="translate(85)"/>
|
||||
<path fill="#FFF" d="M128 158H179V162H128z" transform="translate(85)"/>
|
||||
<path fill="#FFF" d="M128 190H179V194H128z" transform="translate(85)"/>
|
||||
<g transform="translate(140 147)">
|
||||
<mask id="df62zkqa3b" fill="#fff">
|
||||
<use xlink:href="#pv1wlsps5a"/>
|
||||
</mask>
|
||||
<use fill="#FFF" xlink:href="#pv1wlsps5a"/>
|
||||
<path stroke="#003FFF" stroke-width="2.4000001" mask="url(#df62zkqa3b)" d="M30.0000012 30.0000012L30.0000012 -9.60000038 71.1003858 7.68531325 60.0000024 44.4000018z"/>
|
||||
</g>
|
||||
<g fill="#FFF">
|
||||
<g transform="translate(264 40)">
|
||||
<use xlink:href="#cln61id2xc"/>
|
||||
<use xlink:href="#cln61id2xc"/>
|
||||
</g>
|
||||
<path d="M0 32L107 32 107 22 0 22z" transform="translate(264 40)"/>
|
||||
<path d="M0 54L85 54 85 44 0 44z" transform="translate(264 40)"/>
|
||||
<path d="M6.72000027,0 C10.4313539,0 13.4400005,3.0086466 13.4400005,6.72000027 C13.4400005,10.4313539 10.4313539,13.4400005 6.72000027,13.4400005 C3.0086466,13.4400005 0,10.4313539 0,6.72000027 C0,3.0086466 3.0086466,0 6.72000027,0 Z M9.77805698,3.81293549 L6.27816025,7.78764031 L3.96660796,5.37479205 L2.75339231,6.53690564 L6.33049581,10.2713065 L11.0390142,4.92306486 L9.77805698,3.81293549 Z" transform="translate(264 101)" fill-rule="nonzero"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#00F0DC" d="M0 53H97V79H0z" transform="translate(341 206)"/>
|
||||
<path fill="#00D2FA" d="M20 27H117V53H20z" transform="translate(341 206)"/>
|
||||
<path fill="#0050F0" d="M41 1H138V27H41z" transform="translate(341 206)"/>
|
||||
<path stroke="#FFF" stroke-width="2" d="M85 0L40.1603053 0 40.1603053 26.3466667 19 26.3466667 19 38" transform="translate(341 206)"/>
|
||||
</g>
|
||||
<path fill="#0050F0" d="M0 177H54V231H0z"/>
|
||||
<path fill="#00D2FA" d="M54 231H108V285H54z"/>
|
||||
<path fill="#00F0DC" d="M108 231H162V285H108z"/>
|
||||
<path stroke="#FFF" stroke-width="2" d="M163 244L163 230 85 230"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/images/login-banner.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
src/assets/images/qunerweima.jpg
Normal file
After Width: | Height: | Size: 20 KiB |