This commit is contained in:
wmn 2022-05-02 18:38:10 +08:00
commit b52f2bfb4e
149 changed files with 56069 additions and 0 deletions

0
.d.ts vendored Normal file
View File

10
.env.development Normal file
View 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
View 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
View 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
View 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
View 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
View 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
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

1
.pnpm-debug.log Normal file
View File

@ -0,0 +1 @@
{}

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
dist

22
.prettierrc.js Normal file
View 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
View File

@ -0,0 +1,3 @@
{
"recommendations": ["johnsoncodehk.volar"]
}

15
.vscode/launch.json vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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.formatOnSavetrue
- Gitフックと組み合わせるコミット前に実行pre-commit => npm run lintlint-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>

1
README.md Normal file
View File

@ -0,0 +1 @@
# GrasscutterTools

37
config/constant.ts Normal file
View 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
View 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'
}
}

View 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'],
})
}

View 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(),
],
})
}

View 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 [];
}

View 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
}

View 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();
`,
})
}

View 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,
})
}

View 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'
]
})
}

View 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,
})
}

View 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
View 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
View File

@ -0,0 +1,15 @@
# Fast-Vue3版本更新
## V0.1.1-2022/01/28
- 🚃 咱的mock模拟的是真实登录流程请访问`login`路由
- 🥵 修复好几卡车的bug
- 🎸 搞了一个好看的logosvg的~
- 😈 重写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
View 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>

View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
node_modules
.husky
pnpm-lock.yaml
yarn-error.log

View File

@ -0,0 +1 @@
pnpm-lock.yaml

View File

@ -0,0 +1,7 @@
{
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

View 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
```

View 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)
})

File diff suppressed because it is too large Load Diff

View 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"
]
}
}

View 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)
}
}

View 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
View File

@ -0,0 +1,6 @@
.DS_Store
*.log
node_modules/
.idea
yarn.lock
.env

View 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>

View 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"
}
}

View 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 || ''
}

View 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)

View 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}&timestamp=${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

View 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

View 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

View 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)
}

View 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 }
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 KiB

View 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).

View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

27342
packages/juejin-maths-vue/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View 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"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View 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>

View 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>

View 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,
});
}

View 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>

View 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;
}
}

View 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);
}
}

View 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')

View 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;

View 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'
};

View 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,
},
},
},
};

View 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>

View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,2 @@
packages:
- 'packages/*'

28
postcss.config.js Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

19
src/.pnpm-debug.log Normal file
View 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
View 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
View 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
View 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
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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

View 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

View 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

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show More