全量上传

This commit is contained in:
闵宪瑞 2025-01-07 15:29:05 +08:00
parent 3a9d671416
commit 769eb676d2
102 changed files with 12588 additions and 13 deletions

View File

@ -0,0 +1,45 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Node.js & TypeScript",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-18-buster",
"features": {
"ghcr.io/devcontainers-contrib/features/pnpm:2": {}
},
"customizations": {
"vscode": {
"extensions": [
"antfu.goto-alias",
"mikestead.dotenv",
"redhat.vscode-yaml",
"Lokalise.i18n-ally",
"Vue.volar",
"steoates.autoimport",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"EditorConfig.EditorConfig",
"usernamehw.errorlens",
"shd101wyy.markdown-preview-enhanced",
"voorjaar.windicss-intellisense",
"yoavbls.pretty-ts-errors",
"bodil.prettier-toml",
"bungcip.better-toml"
]
}
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

12
.dockerignore Normal file
View File

@ -0,0 +1,12 @@
dist
.husky
.gitee
.github
.vscode
LICENSE
.gitignore
netlify.toml
CHANGELOG.md
node_modules
renovate.json
.devcontainer

23
.editorConfig Normal file
View File

@ -0,0 +1,23 @@
root = true
# 匹配全部文件
[*]
# 设置字符集
charset = utf-8
# 缩进风格,可选 space、tab
indent_style = tab
# 缩进的空格数,当 indent_style = tab 将使用 tab_width
# 否则使用 indent_size
indent_size = 2
tab_width = 2
# 结尾换行符,可选 lf、cr、crlf
end_of_line = crlf
# 在文件结尾插入新行
insert_final_newline = true
# 删除一行中的前后空格
trim_trailing_whitespace = true
# 匹配 md 结尾的文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

32
.env Normal file
View File

@ -0,0 +1,32 @@
# 通用环境变量
# api baseURL
VITE_API_BASE_URL = /api
# 标题
VITE_APP_TITLE = tov
# markdown 渲染支持
VITE_APP_MARKDOWN = true
# 开发时的开发面板
VITE_APP_DEV_TOOLS = true
# 生产时 mock 支持
VITE_APP_MOCK_IN_PRODUCTION = false
# 生产时压缩算法,可选 gzip, brotliCompress, deflate, deflateRaw
VITE_APP_COMPRESSINON_ALGORITHM = gzip
# api 自动按需引入
# 注意设置关闭时,其他的 api 自动按需引入也将自动关闭
VITE_APP_API_AUTO_IMPORT = true
# 项目级 api 自动按需导入
VITE_APP_DIR_API_AUTO_IMPORT = true

4
.eslintignore Normal file
View File

@ -0,0 +1,4 @@
# 忽略 eslint 检查
dist
node_modules
presets/types

35
.eslintrc.json Normal file
View File

@ -0,0 +1,35 @@
{
"root": true, //
"env": {
"node": true,
"es2021": true,
"browser": true //
},
"parser": "vue-eslint-parser",
"parserOptions": {
"ecmaVersion": "latest",
"parser": "@typescript-eslint/parser",
"ecmaFeatures": {
"jsx": true // jsx
}
},
"plugins": ["prettier"],
"extends": [
"@unocss",
"eslint:recommended", //
"plugin:vue/vue3-recommended", // vue sfc
"prettier",
"./presets/eslint/.eslintrc-auto-import.json"
],
"rules": {
//
"vue/multi-word-component-names": "off",
// prettier
"prettier/prettier": "error",
// 使 var let const
"no-var": "error"
},
"globals": {
"defineOptions": true
}
}

23
.gitignore vendored
View File

@ -1,11 +1,12 @@
# ---> Vue dist
# gitignore template for Vue.js projects .nitro
# .output
# Recommended template: Node.gitignore env.d.ts
node_modules
# TODO: where does this rule come from? .eslintcache
docs/_book components.d.ts
type-router.d.ts
# TODO: where does this rule come from? auto-imports.d.ts
test/ .eslintrc-auto-import.json
vite.config.ts.timestamp*
.idea

4
.husky/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

5
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

61
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,61 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

12
.idea/front-template.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/front-template.iml" filepath="$PROJECT_DIR$/.idea/front-template.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1
.npmrc Normal file
View File

@ -0,0 +1 @@
registry=https://registry.npmmirror.com/

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
20.12.2

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
dist
node_modules
presets/types

5
.prettierrc.json Normal file
View File

@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"endOfLine": "auto"
}

1306
README.md

File diff suppressed because it is too large Load Diff

1373
README_EN.md Normal file

File diff suppressed because it is too large Load Diff

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,10 @@
# English
home: home
about: about
echarts: echarts
edit: Edit
to test HMR: to test HMR
check out: Check out
The total number of views is: The total number of views is
the official Tov + Vue + Vite template: the official Tov + Vue + Vite template

3
locales/English/test.yml Normal file
View File

@ -0,0 +1,3 @@
# English
test.module: lanugae module test

View File

@ -0,0 +1,10 @@
# 中文
home: 主页
about: 关于
echarts: 图表
edit: 编辑
to test HMR: 测试热更新
check out: 查看
The total number of views is: 总浏览数
the official Tov + Vue + Vite template: 公共的 Tov + Vue + Vite 模板

View File

@ -0,0 +1,3 @@
# 简体中文
test.module: 多语言多模块测试

42
mock/test.ts Normal file
View File

@ -0,0 +1,42 @@
import { MockMethod } from 'vite-plugin-mock'
export default [
{
url: '/api/mock/get',
method: 'get',
response: () => {
return {
code: 0,
data: {
name: 'mock',
},
}
},
},
{
url: '/api/mock/post',
method: 'post',
timeout: 2000,
response: {
code: 0,
data: {
name: 'mock',
},
},
},
{
url: '/api/mock/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[]

4
netlify.toml Normal file
View File

@ -0,0 +1,4 @@
[[redirects]]
to = "/index.html"
from = "/*"
status = 200

132
package.json Normal file
View File

@ -0,0 +1,132 @@
{
"name": "tov-template",
"version": "1.19.0",
"description": "vite + vue3 + ts 开箱即用现代开发模板 | vite + vue3 + ts out-of-the-box modern development template",
"type": "module",
"scripts": {
"dev": "vite",
"test": "vitest",
"build": "vite build",
"prepare": "husky install",
"dev:host": "vite --host",
"dev:open": "vite --open",
"preview": "vite preview",
"coverage": "vitest --coverage",
"preinstall": "npx only-allow pnpm",
"typecheck": "npx vue-tsc --noEmit",
"preview:host": "vite preview --host",
"preview:open": "vite preview --open",
"lint": "eslint --ext .ts,.js,.jsx,.vue .",
"release": "plop --plopfile scripts/release.cjs",
"auto:remove": "plop --plopfile scripts/remove.cjs",
"auto:create": "plop --plopfile scripts/create.cjs",
"build:debug": "cross-env NODE_ENV=debug vite build",
"safe:init": "plop --plopfile scripts/safe-init.cjs",
"deps:fresh": "plop --plopfile scripts/deps-fresh.cjs",
"lint:fix": "eslint --fix --ext .ts,.js,.jsx,.vue,.cjs ."
},
"engines": {
"node": ">=20.12.2"
},
"packageManager": "pnpm@8.15.8",
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^3.0.1",
"@types/ityped": "^1.0.3",
"@types/node": "^20.12.7",
"@typescript-eslint/parser": "7.8.0",
"@unocss/eslint-config": "0.59.4",
"@unocss/reset": "^0.59.4",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vueuse/components": "^10.9.0",
"@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0",
"axios": "^1.6.8",
"browserslist": "^4.23.0",
"c8": "^9.1.0",
"changelogen": "^0.5.5",
"consola": "^3.2.3",
"cross-env": "^7.0.3",
"defu": "^6.1.4",
"echarts": "^5.5.0",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-vue": "^9.25.0",
"fs-extra": "^11.2.0",
"husky": "^9.0.11",
"ityped": "^1.0.3",
"kolorist": "^1.8.0",
"lightningcss": "^1.24.1",
"lint-staged": "^15.2.2",
"local-pkg": "^0.5.0",
"markdown-it-prism": "^2.3.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"perfect-debounce": "^1.0.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"plop": "^4.0.1",
"prettier": "^3.2.5",
"prism-theme-vars": "^0.2.5",
"simple-git": "^3.24.0",
"taze": "^0.13.7",
"terser": "^5.31.0",
"typescript": "^5.4.5",
"unocss": "^0.59.4",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0",
"unplugin-vue-markdown": "^0.26.2",
"unplugin-vue-router": "^0.8.6",
"vite": "^5.2.10",
"vite-auto-import-resolvers": "^3.2.1",
"vite-layers": "^0.5.2",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-env-types": "^0.1.4",
"vite-plugin-legacy-swc": "^1.1.0",
"vite-plugin-mock": "2.9.8",
"vite-plugin-removelog": "^0.2.2",
"vite-plugin-use-modules": "^1.4.8",
"vite-plugin-vue-devtools": "^7.1.3",
"vite-plugin-vue-meta-layouts": "^0.4.3",
"vitest": "^1.5.3",
"vue": "^3.4.26",
"vue-dark-switch": "^1.0.6",
"vue-echarts": "^6.7.1",
"vue-i18n": "^9.13.1",
"vue-request": "2.0.4",
"vue-router": "^4.3.2",
"vue-toastification": "2.0.0-rc.5"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,vue}": "eslint --cache --fix"
},
"overrides": {
"sourcemap-codec": "npm:@jridgewell/sourcemap-codec@latest",
"array-includes": "npm:@nolyfill/array-includes@latest",
"array.prototype.findlastindex": "npm:@nolyfill/array.prototype.findlastindex@latest",
"array.prototype.flat": "npm:@nolyfill/array.prototype.flat@latest",
"array.prototype.flatmap": "npm:@nolyfill/array.prototype.flatmap@latest",
"arraybuffer.prorotype.slice": "npm:@nolyfill/arraybuffer.prorotype.slice@latest",
"function.prototype.name": "npm:@nolyfill/function.prototype.name@latest",
"has": "npm:@nolyfill/has@latest",
"is-regex": "npm:@nolyfill/is-regex@latest",
"object-keys": "npm:@nolyfill/object-keys@latest",
"object.assign": "npm:@nolyfill/object.assign@latest",
"object.entries": "npm:@nolyfill/object.entries@latest",
"object.fromentries": "npm:@nolyfill/object.fromentries@latest",
"object.values": "npm:@nolyfill/object.values@latest",
"vue-demi": "npm:vue-demi@latest"
},
"repository": {
"url": "https://github.com/dishait/tov-template"
},
"browserslist": [
">= 0.25%",
"last 2 versions",
"not dead",
"not ie <= 11",
"Android >= 4.0",
"iOS >= 8"
]
}

7227
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

33
presets/autoprefixer.ts Normal file
View File

@ -0,0 +1,33 @@
import type { Preset } from 'unocss'
import browserslist from 'browserslist'
import { defaultBuildTargets } from './shared/detect'
import { browserslistToTargets, transformStyleAttribute } from 'lightningcss'
export default function autoprefixerPreset(
targets: string[] = defaultBuildTargets,
): Preset {
return {
name: 'unocss-preset-autoprefixer',
postprocess: (util) => {
const entries = util.entries
const { code } = transformStyleAttribute({
code: Buffer.from(
entries
.filter((item) => !item[0].startsWith('--un'))
.map((x) => x.join(':'))
.join(';'),
),
targets: browserslistToTargets(browserslist(targets)),
minify: true,
})
util.entries = [
...entries.filter((item) => item[0].startsWith('--un')),
...(code
.toString()
.split(';')
.map((i) => i.split(':')) as [string, string | number][]),
]
},
}
}

275
presets/index.ts Normal file
View File

@ -0,0 +1,275 @@
import Prism from 'markdown-it-prism'
import UnoCss from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import {
AntDesignVueResolver,
ArcoResolver,
DevUiResolver,
ElementPlusResolver,
HeadlessUiResolver,
IduxResolver,
InklineResolver,
LayuiVueResolver,
NaiveUiResolver,
PrimeVueResolver,
QuasarResolver,
TDesignResolver,
VantResolver,
VarletUIResolver,
ViewUiResolver,
VueUseComponentsResolver,
Vuetify3Resolver,
} from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
import Markdown from 'unplugin-vue-markdown/vite'
import { VueRouterAutoImports } from 'unplugin-vue-router'
import Router from 'unplugin-vue-router/vite'
import { AutoGenerateImports, vue3Presets } from 'vite-auto-import-resolvers'
import Compression from 'vite-plugin-compression'
import EnvTypes from 'vite-plugin-env-types'
import { viteMockServe as Mock } from 'vite-plugin-mock'
import Removelog from 'vite-plugin-removelog'
import Modules from 'vite-plugin-use-modules'
import VueDevTools from 'vite-plugin-vue-devtools'
import Layouts from 'vite-plugin-vue-meta-layouts'
import I18N from '@intlify/unplugin-vue-i18n/vite'
import Legacy from 'vite-plugin-legacy-swc'
import Vue from '@vitejs/plugin-vue'
import Jsx from '@vitejs/plugin-vue-jsx'
// 内置插件
import {
Alias,
Lightningcss,
Restart,
Warmup,
Layers,
Optimize,
} from './plugins'
import { defaultBuildTargets, detectResolvers, useEnv } from './shared/detect'
import { r } from './shared/path'
import type { PluginOption } from 'vite'
export default function () {
const env = useEnv()
const safelist =
'prose px-2 sm:px-0 md:prose-lg lg:prose-lg dark:prose-invert text-left w-screen prose-slate prose-img:rounded-xl prose-headings:underline prose-a:text-blue-600'
const plugins: PluginOption[] = [
/**
* vite
* mode vite (experimental)
*/
Layers(),
/**
* esmModule
* https://www.npmjs.com/package/@vitejs/plugin-legacy
*/
Legacy({
targets: defaultBuildTargets,
}),
/**
* lightningcss
*/
Lightningcss(),
/**
*
*/
Optimize(),
/**
*
* https://github.com/dishait/vite-plugin-env-types
*/
EnvTypes({
dts: r('presets/types/env.d.ts'),
}),
/**
*
*/
Warmup(),
/**
*
* https://github.com/posva/unplugin-vue-router
*/
Router({
routesFolder: r('src/pages'),
dts: r('presets/types/type-router.d.ts'),
extensions: ['.md', '.vue', '.tsx', '.jsx'],
}),
/**
* vue
* https://github.com/dishait/vite-plugin-use-modules
*/
Modules({
auto: true,
// 内部使用虚拟模块,运行在前端,所以不需要 r 重写路径
target: 'src/plugins',
}),
/**
* vue sfc
* https://www.npmjs.com/package/@vitejs/plugin-vue
*/
Vue({
include: [/\.vue$/, /\.md$/],
}),
/**
*
* https://github.com/dishait/vite-plugin-vue-meta-layouts
*/
Layouts({
skipTopLevelRouteLayout: true,
}),
/**
* mock
* https://github.com/vbenjs/vite-plugin-mock
*/
Mock({
prodEnabled: env.VITE_APP_MOCK_IN_PRODUCTION,
}),
/**
*
* https://github.com/antfu/unplugin-vue-components
*/
Components({
directoryAsNamespace: true,
include: [/\.vue$/, /\.vue\?vue/, /\.[tj]sx$/, /\.md$/],
extensions: ['md', 'vue', 'tsx', 'jsx'],
dts: r('presets/types/components.d.ts'),
types: [
{
from: 'vue-router',
names: ['RouterLink', 'RouterView'],
},
],
resolvers: detectResolvers({
onlyExist: [
[VantResolver(), 'vant'],
[QuasarResolver(), 'quasar'],
[DevUiResolver(), 'vue-devui'],
[NaiveUiResolver(), 'naive-ui'],
[Vuetify3Resolver(), 'vuetify'],
[PrimeVueResolver(), 'primevue'],
[ViewUiResolver(), 'view-design'],
[LayuiVueResolver(), 'layui-vue'],
[VarletUIResolver(), '@varlet/ui'],
[IduxResolver(), '@idux/components'],
[InklineResolver(), '@inkline/inkline'],
[ElementPlusResolver(), 'element-plus'],
[HeadlessUiResolver(), '@headlessui/vue'],
[ArcoResolver(), '@arco-design/web-vue'],
[AntDesignVueResolver({ importStyle: false }), 'ant-design-vue'],
[VueUseComponentsResolver(), '@vueuse/components'],
[TDesignResolver({ library: 'vue-next' }), 'tdesign-vue-next'],
],
}),
}),
/**
* i18n
* https://www.npmjs.com/package/@intlify/unplugin-vue-i18n
*/
I18N({
runtimeOnly: false,
compositionOnly: true,
include: ['locales/**'],
}),
/**
* jsx tsx
* https://www.npmjs.com/package/@vitejs/plugin-vue-jsx
*/
Jsx(),
/**
*
* https://github.com/vbenjs/vite-plugin-compression
*/
Compression({
// @ts-ignore
algorithm: env.VITE_APP_COMPRESSINON_ALGORITHM,
}),
/**
* ()
* `~` `@` `src`
*/
Alias(),
/**
* ()
* package.json pnpm-lock.yaml
*/
Restart(),
/**
* css
* https://github.com/unocss/unocss
*/
UnoCss({
safelist: env.VITE_APP_MARKDOWN ? safelist.split(' ') : undefined,
}),
]
/**
*
* https://github.com/webfansplz/vite-plugin-vue-devtools
*/
if (env.VITE_APP_DEV_TOOLS) {
plugins.push(VueDevTools())
}
/**
* console.log, console.warn, console.error
* https://github.com/dishait/vite-plugin-removelog
*/
if (process.env.NODE_ENV !== 'debug') {
plugins.push(Removelog())
}
/**
* markdown
* https://github.com/mdit-vue/unplugin-vue-markdown
*/
if (env.VITE_APP_MARKDOWN) {
plugins.push(
Markdown({
wrapperClasses: safelist,
markdownItSetup(md) {
md.use(Prism)
},
}),
)
}
/**
* api
* https://github.com/antfu/unplugin-auto-import
*/
if (env.VITE_APP_API_AUTO_IMPORT) {
const dirs = env.VITE_APP_DIR_API_AUTO_IMPORT
? ['src/stores/**', 'src/composables/**', 'src/api/**']
: []
plugins.push(
AutoImport({
dirs,
vueTemplate: true,
dts: r('presets/types/auto-imports.d.ts'),
imports: [
...AutoGenerateImports({
include: [...vue3Presets],
exclude: ['vue-router'],
}),
VueRouterAutoImports,
],
resolvers: detectResolvers({
onlyExist: [
[ElementPlusResolver(), 'element-plus'],
[TDesignResolver({ library: 'vue-next' }), 'tdesign-vue-next'],
],
}),
eslintrc: {
enabled: true,
globalsPropValue: true,
filepath: r('presets/eslint/.eslintrc-auto-import.json'),
},
}),
)
}
return plugins
}

27
presets/plugins/alias.ts Normal file
View File

@ -0,0 +1,27 @@
import type { Plugin } from 'vite'
import { r } from '../shared/path'
/**
*
* @description `~` `@` `src`
*/
export function Alias(): Plugin {
const src = r('./src')
return {
name: 'vite-alias',
enforce: 'pre',
config(config) {
config.resolve ??= {}
config.resolve.alias = [
{
find: /^~/,
replacement: src,
},
{
find: /^@\//,
replacement: src + '/',
},
]
},
}
}

6
presets/plugins/index.ts Normal file
View File

@ -0,0 +1,6 @@
export { Alias } from './alias'
export { Layers } from './layers'
export { Warmup } from './warmup'
export { Restart } from './restart'
export { Optimize } from './optimize'
export { Lightningcss } from './lightningcss'

41
presets/plugins/layers.ts Normal file
View File

@ -0,0 +1,41 @@
import { existsSync } from 'fs'
import { gray } from 'kolorist'
import { basename } from 'path'
import { r } from '../shared/path'
import { Restart } from './restart'
import { createConsola } from 'consola'
import type { Plugin, UserConfig } from 'vite'
import { Layers as loadLayer, detectMode } from 'vite-layers'
const logger = createConsola().withTag('layers')
/**
* vite
* @description mode vite (experimental)
*/
export function Layers(): Plugin {
const mode = detectMode()
const modeFiles = [mode.slice(0, 3), mode].map((mode) =>
r(`vite.config.${mode}.ts`),
)
return {
...Restart(modeFiles.map((modeFile) => basename(modeFile))),
name: 'vite-plugin-layers',
enforce: 'post',
async config(config) {
const modeFile = modeFiles.find((modeFile) => existsSync(modeFile))
if (modeFile) {
logger
.withTag(mode)
.success(
`vite.config.ts → ${basename(modeFile)} ${gray(`(experimental)`)}`,
)
return loadLayer({
logger: false,
extends: [config, modeFile],
}) as UserConfig
}
return config
},
}
}

View File

@ -0,0 +1,62 @@
import { existsSync } from 'fs'
import { gray } from 'kolorist'
import type { Plugin } from 'vite'
import { createConsola } from 'consola'
import { isPackageExists } from 'local-pkg'
import { browserslistToTargets } from 'lightningcss'
import { defaultBuildTargets } from '../shared/detect'
const name = 'vite-plugin-fire-lightningcss'
const logger = createConsola().withTag('css')
/**
* lightningcss (使 postcss)
*/
export function Lightningcss(): Plugin {
const packages = ['less', 'sass', 'stylus']
return {
name,
config(config) {
config.css ??= {}
config.build ??= {}
const hasPreprocessor = packages.some((p) => isPackageExists(p))
const { postcss, modules, transformer } = config.css
const conflictConfiguration = [postcss, modules, transformer].some(
(c) => !isUndefined(c),
)
const hasPostcssConfigFile = [
'postcss.config.js',
'postcss.config.cts',
'postcss.config.ts',
].some((c) => existsSync(c))
// 如果有预处理器,冲突配置或者 postcss 配置文件则禁用
const disabled =
hasPreprocessor || conflictConfiguration || hasPostcssConfigFile
if (!disabled) {
const transformer = 'lightningcss'
config.css.transformer = transformer
let tip = `${transformer} ${gray(transformer)}`
if (isUndefined(config.build.cssMinify)) {
config.build.cssMinify = 'lightningcss'
tip = `${transformer} ${gray('(transformer + cssMinify)')}`
}
if (isUndefined(config.css.lightningcss?.targets)) {
config.css.lightningcss ??= {}
config.css.lightningcss.targets =
browserslistToTargets(defaultBuildTargets)
}
logger.success(tip)
}
},
}
function isUndefined(v: unknown): v is undefined {
return typeof v === 'undefined'
}
}

View File

@ -0,0 +1,19 @@
import { createConsola } from 'consola'
import { gray } from 'kolorist'
import type { Plugin } from 'vite'
const logger = createConsola().withTag('optimize')
export function Optimize(): Plugin {
return {
name: 'vite-optimize',
config(config) {
config.css ??= {}
config.optimizeDeps ??= {}
config.css.preprocessorMaxWorkers = true
config.optimizeDeps.holdUntilCrawlEnd = false
logger.success(
`optimize ${gray('(preprocessorMaxWorkers + closeHoldUntilCrawlEnd)')}`,
)
},
}
}

View File

@ -0,0 +1,30 @@
import type { Plugin } from 'vite'
import { utimes } from 'fs/promises'
import { r } from '../shared/path'
import { debounce } from 'perfect-debounce'
import { resolve } from 'path'
import { slash } from 'vite-layers'
const defaultPaths = ['package.json', 'pnpm-lock.yaml']
/**
*
* @description
* @param paths ['package.json', 'pnpm-lock.yaml']
*/
export function Restart(paths = defaultPaths): Plugin {
paths = paths.map((path) => slash(resolve(path)))
const restart = debounce(async function touch() {
const time = new Date()
await utimes(r('vite.config.ts'), time, time)
}, 1000)
return {
name: 'vite-plugin-force-restart',
apply: 'serve',
async watchChange(id) {
if (paths.includes(id)) {
await restart()
}
},
}
}

21
presets/plugins/warmup.ts Normal file
View File

@ -0,0 +1,21 @@
import type { Plugin } from 'vite'
/**
*
* @description
*/
export function Warmup(): Plugin {
return {
name: 'vite-plugin-warmup',
apply: 'serve',
config(config) {
const src = './src/**/*'
config.server ??= {}
config.server.warmup ??= {}
config.server.warmup.clientFiles ??= []
if (!config.server.warmup.clientFiles.includes(src)) {
config.server.warmup.clientFiles.push(src)
}
},
}
}

76
presets/shared/detect.ts Normal file
View File

@ -0,0 +1,76 @@
/**
*
* @description
*/
import { r } from './path'
import { loadEnv } from 'vite'
import browserslist from 'browserslist'
import { detectMode } from 'vite-layers'
import { isPackageExists } from 'local-pkg'
import type { ComponentResolver } from 'unplugin-vue-components'
const { loadConfig: browserslistLoadConfig } = browserslist
/**
* ()
*/
export const defaultBuildTargets = browserslistLoadConfig({
path: r('./'),
}) || ['last 2 versions and not dead, > 0.3%, Firefox ESR']
type Arrayable<T> = T | Array<T>
interface Options {
onlyExist?: [Arrayable<ComponentResolver>, string][]
include?: ComponentResolver[]
}
/**
* resolvers
*/
export function detectResolvers(options: Options = {}) {
const { onlyExist = [], include = [] } = options
const existedResolvers = []
for (let i = 0; i < onlyExist.length; i++) {
const [resolver, packageName] = onlyExist[i]
if (
isPackageExists(packageName, {
paths: [r('./')],
})
) {
existedResolvers.push(resolver)
}
}
existedResolvers.push(...include)
return existedResolvers
}
// 获取环境变量
export function useEnv() {
function stringToBoolean(v: string) {
return Boolean(v === 'true' || false)
}
const {
VITE_APP_TITLE,
VITE_APP_DEV_TOOLS,
VITE_APP_MARKDOWN,
VITE_APP_API_AUTO_IMPORT,
VITE_APP_MOCK_IN_PRODUCTION,
VITE_APP_DIR_API_AUTO_IMPORT,
VITE_APP_COMPRESSINON_ALGORITHM,
} = loadEnv(detectMode(), '.')
return {
VITE_APP_TITLE,
VITE_APP_COMPRESSINON_ALGORITHM,
VITE_APP_DEV_TOOLS: stringToBoolean(VITE_APP_DEV_TOOLS),
VITE_APP_MARKDOWN: stringToBoolean(VITE_APP_MARKDOWN),
VITE_APP_API_AUTO_IMPORT: stringToBoolean(VITE_APP_API_AUTO_IMPORT),
VITE_APP_MOCK_IN_PRODUCTION: stringToBoolean(VITE_APP_MOCK_IN_PRODUCTION),
VITE_APP_DIR_API_AUTO_IMPORT: stringToBoolean(VITE_APP_DIR_API_AUTO_IMPORT),
}
}

89
presets/shared/mock.ts Normal file
View File

@ -0,0 +1,89 @@
// @ts-nocheck
/**
* issue: https://github.com/vbenjs/vite-plugin-mock/issues/47
* fix: https://github.com/vbenjs/vite-plugin-mock/issues/47#issuecomment-982724613
*/
import Mock from 'mockjs'
export function createFetchSever(mockList: any[]) {
if (!window['originFetch']) {
window['originFetch'] = window.fetch
window.fetch = function (fetchUrl: string, init: any) {
const currentMock = mockList.find((mi) => fetchUrl.includes(mi.url))
if (currentMock) {
const result = createFetchReturn(currentMock, init)
return result
} else {
return window['originFetch'](fetchUrl, init)
}
}
}
}
function __param2Obj__(url: string) {
const search = url.split('?')[1]
if (!search) {
return {}
}
return JSON.parse(
'{"' +
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"')
.replace(/\+/g, ' ') +
'"}',
)
}
function __Fetch2ExpressReqWrapper__(handle: () => any) {
return function (options: any) {
let result = null
if (typeof handle === 'function') {
const { body, method, url, headers } = options
let b = body
b = JSON.parse(body)
result = handle({
method,
body: b,
query: __param2Obj__(url),
headers,
})
} else {
result = handle
}
return Mock.mock(result)
}
}
const sleep = (delay = 0) => {
if (delay) {
return new Promise((resolve) => {
setTimeout(resolve, delay)
})
}
return null
}
async function createFetchReturn(mock: any, init) {
const { timeout, response } = mock
const mockFn = __Fetch2ExpressReqWrapper__(response)
const data = mockFn(init)
await sleep(timeout)
const result = {
ok: true,
status: 200,
clone() {
return result
},
text() {
return Promise.resolve(data)
},
json() {
return Promise.resolve(data)
},
}
return result
}

15
presets/shared/path.ts Normal file
View File

@ -0,0 +1,15 @@
import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
const _dirname = dirname(fileURLToPath(import.meta.url))
const root = resolve(_dirname, '../../')
/**
*
* @param path
* @returns
*/
export function r(path: string) {
return resolve(root, path).replaceAll('\\', '/')
}

18
presets/types/vite.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pages/client" />
/// <reference types="unplugin-vue-router/client" />
/// <reference types="vite-plugin-use-modules/client" />
/// <reference types="vite-plugin-vue-meta-layouts/client" />
/// <reference types="@intlify/vite-plugin-vue-i18n/client" />
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module "*.md" {
import { ComponentOptions } from "vue";
const Component: ComponentOptions;
export default Component;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
public/docs/notFound.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

1
public/notFound/33.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="iconify iconify--logos" width="31.88" height="32" viewBox="0 0 256 257"><defs><linearGradient id="a" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"/><stop offset="100%" stop-color="#BD34FE"/></linearGradient><linearGradient id="b" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"/><stop offset="8.333%" stop-color="#FFDD35"/><stop offset="100%" stop-color="#FFA800"/></linearGradient></defs><path fill="url(#a)" d="M255.153 37.938 134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62z"/><path fill="url(#b)" d="M185.432.063 96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028 72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

3
renovate.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": ["github>unjs/renovate-config"]
}

76
scripts/create.cjs Normal file
View File

@ -0,0 +1,76 @@
const { existsSync } = require('fs')
const { showDir, showExt, moduleTypes } = require('./shared/base.cjs')
/**
* 自动创建
* @param {import('plop').NodePlopAPI} plop
*/
function create(plop) {
let exist = null
let modulePath = null
plop.setGenerator('controller', {
description: '自动创建',
prompts: [
{
name: 'type',
type: 'list',
default: 'component',
message: '您希望生成哪种类型的模块?',
choices: moduleTypes,
},
{
name: 'isMarkdown',
type: 'confirm',
message: '是否 markdown 类型?',
default: false,
// 如果是 page 类型需要询问是否为 markdown 类型
when({ type }) {
return type === 'page'
},
},
{
name: 'name',
type: 'input',
message({ type }) {
return `请输入 ${type} 的命名`
},
},
{
name: 'shouldReset',
type: 'confirm',
default: false,
message({ type }) {
return `目标 ${type} 已存在,是否重置?`
},
// 确认模块是否已存在,是则询问是否重置
when({ type, name, isMarkdown }) {
const dir = showDir(type)
const ext = showExt(type, isMarkdown)
modulePath = `src/${dir}/${name}.${ext}`
exist = existsSync(modulePath)
if (exist) {
return true
}
},
},
],
actions(answer) {
const { type, shouldReset } = answer
if (exist && !shouldReset) {
throw new Error(`${type} 创建失败`)
}
return [
{
type: 'add',
force: true,
path: `../${modulePath}`,
templateFile: `./template/${type}.hbs`,
},
]
},
})
}
module.exports = create

36
scripts/deps-fresh.cjs Normal file
View File

@ -0,0 +1,36 @@
const { execSync } = require('child_process')
/**
* 自动更新依赖
* @param {import('plop').NodePlopAPI} plop
*/
function depsFresh(plop) {
plop.setGenerator('controller', {
description: '自动更新依赖',
prompts: [
{
name: 'type',
type: 'list',
default: 'patch',
message: '你希望发布一个什么版本?',
choices: ['patch', 'minor', 'major'],
},
{
name: 'shouldWrite',
type: 'confirm',
default: false,
message: '是否直接更新?',
},
],
actions(answer) {
const { type, shouldWrite } = answer
execSync(`npx taze ${type} ${shouldWrite ? '-w' : ''}`, {
stdio: 'inherit',
})
return []
},
})
}
module.exports = depsFresh

65
scripts/release.cjs Normal file
View File

@ -0,0 +1,65 @@
const { createConsola } = require('consola')
const { execSync } = require('child_process')
const { repository } = require('../package.json')
const { gray } = require('kolorist')
const { simpleGit } = require('simple-git')
const logger = createConsola().withTag('release')
/**
* 自动发版
* @param {import('plop').NodePlopAPI} plop
*/
async function release(plop) {
const git = simpleGit()
const remotes = await git.getRemotes(true)
const urls = remotes.map((r) => {
return r.refs.push
.replace('git@github.com:', 'https://github.com/')
.replace('.git', '')
})
let allowRelease = false
if (!urls.includes(repository.url)) {
allowRelease = await logger.prompt(`是否发布到 ${gray(repository.url)}`, {
type: 'confirm',
})
} else {
allowRelease = true
}
if (allowRelease) {
plop.setGenerator('controller', {
description: '自动发版',
prompts: [
{
name: 'type',
type: 'list',
default: 'patch',
message: '你希望发布一个什么版本?',
choices: [
'patch',
'minor',
'major',
'prepatch',
'premajor',
'preminor',
'prerelease',
],
},
],
actions(answer) {
const { type } = answer
execSync(
`npx changelogen --${type} --release && git push --follow-tags`,
{
stdio: 'inherit',
},
)
return []
},
})
}
}
module.exports = release

75
scripts/remove.cjs Normal file
View File

@ -0,0 +1,75 @@
const { unlinkSync } = require('fs')
const { readdir } = require('fs/promises')
const { basename } = require('path')
const { showDir, moduleTypes } = require('./shared/base.cjs')
/**
* 自动删除
* @param {import('plop').NodePlopAPI} plop
*/
function remove(plop) {
plop.setActionType('remove', (answers) => {
const { name, type, shouldRemove } = answers
const dir = showDir(type)
const target = `./src/${dir}/${name}`
if (shouldRemove) {
return unlinkSync(target)
}
throw new Error(`删除 ${target} 失败`)
})
plop.setGenerator('controller', {
description: '自动删除',
prompts: [
{
name: 'type',
type: 'list',
message: '请选择您要删除的类型',
async choices() {
const entrys = await readdir('./src', {
recursive: false,
withFileTypes: true,
})
const dirs = entrys.filter((e) => e.isDirectory())
const types = moduleTypes.filter((type) => {
const dir = showDir(type)
return dirs.includes(`./src/${dir}`)
})
return types
},
},
{
name: 'name',
type: 'list',
message({ type }) {
return `请选择您要删除的 ${type} 模块`
},
async choices({ type }) {
const dir = showDir(type)
const entrys = await readdir(`src/${dir}`, {
recursive: false,
withFileTypes: true,
})
let modules = entrys.filter((e) => e.isFile())
modules = modules.map((module) => {
return basename(module)
})
return modules
},
},
{
name: 'shouldRemove',
type: 'confirm',
default: false,
message: '再次确认是否删除',
},
],
actions: [
{
type: 'remove',
},
],
})
}
module.exports = remove

113
scripts/safe-init.cjs Normal file
View File

@ -0,0 +1,113 @@
const { resolve } = require('path')
const { gray, green } = require('kolorist')
const { createConsola } = require('consola')
const { existsSync, lstatSync } = require('fs')
const { removeSync, emptyDirSync } = require('fs-extra')
function slash(path) {
return path.replace(/\\/g, '/')
}
function r(dir) {
return slash(resolve(__dirname, '../', dir))
}
const entrys = [
'src/components',
'src/api',
'mock',
'layouts/default.vue',
'src/pages/index.vue',
'src/pages/about.md',
'src/pages/echarts.vue',
'src/stores',
'locales/简体中文',
'locales/English',
]
const resolvedEntrys = entrys.map((entry) => r(entry))
/**
* 安全初始化
* @param {import('plop').NodePlopAPI} plop
*/
function safeInit(plop) {
const logger = createConsola().withTag('safe:init')
logger.warn('实验性功能')
plop.setGenerator('controller', {
description: '安全初始化',
prompts: [
{
name: 'yes',
type: 'confirm',
message: '是否安全的初始化?',
default: false,
},
{
name: 'cleanStyles',
type: 'confirm',
message: '是否清理 styles?',
default: false,
},
],
actions(answer) {
if (!answer.yes) {
return []
}
if (answer.cleanStyles) {
resolvedEntrys.push(r('src/styles'))
}
console.log()
// 这里不用异步是因为 plop action 只支持同步
resolvedEntrys.forEach((e) => {
if (!existsSync(e)) {
return
}
const entry = lstatSync(e)
if (entry.isFile()) {
removeSync(e)
logClean(e)
return
}
if (entry.isDirectory()) {
emptyDirSync(e)
logClean(e)
}
})
return [
{
type: 'add',
force: true,
path: '../src/pages/index.vue',
templateFile: './template/page.hbs',
data: {
name: 'index',
isMarkdown: false,
},
},
{
type: 'add',
force: true,
path: '../src/layouts/default.vue',
templateFile: './template/layout.hbs',
data: {
name: 'default',
},
},
]
},
})
}
function logClean(path) {
console.log(`${green('√ clean')} ${gray(path)}`)
}
module.exports = safeInit

41
scripts/shared/base.cjs Normal file
View File

@ -0,0 +1,41 @@
/**
* 获取扩展名
* @param {string} type 模块类型
* @param {boolean} isMarkdown 是否是 markdown默认为 false
* @returns {string} 扩展名
*/
const showExt = (type, isMarkdown = false) => {
const isTs = type === 'api' || type === 'store' || type === 'module'
const ext = isMarkdown ? 'md' : isTs ? 'ts' : 'vue'
return ext
}
/**
* 模块类型
*/
const moduleTypes = [
'api',
'page',
'store',
'layout',
'module',
'component',
'composable',
]
/**
* 获取目录
* @param {string} type 类型
*/
const showDir = (type) => {
if (type === 'api') {
return 'api'
}
return `${type}s`
}
module.exports = {
showExt,
showDir,
moduleTypes,
}

2
scripts/template/api.hbs Normal file
View File

@ -0,0 +1,2 @@
import axios from "axios"

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
{{name}}
</template>
<style scoped>
</style>

View File

@ -0,0 +1,5 @@
import { ref } from "vue"
export default {{name}} = () => {
}

View File

@ -0,0 +1,4 @@
<template>
{{pascalCase name}} layout
<router-view />
</template>

View File

@ -0,0 +1,6 @@
import type { App } from "vue"
export default (app: App) => {
}

17
scripts/template/page.hbs Normal file
View File

@ -0,0 +1,17 @@
{{#if isMarkdown}}
## {{pascalCase name}} Page
> The page is markdown file
{{else}}
<script setup lang="ts">
</script>
<template>
{{pascalCase name}} page
</template>
<style>
</style>
{{/if}}

View File

@ -0,0 +1,9 @@
import { defineStore } from 'pinia'
export default defineStore('{{name}}', {
state() {
return {}
},
getters: {},
actions: {}
})

13
src/App.vue Normal file
View File

@ -0,0 +1,13 @@
<template>
<router-view />
</template>
<style>
/* a {
color: rgba(37, 99, 235);
} */
p {
padding: 0 10px;
}
</style>

6
src/api/mock.ts Normal file
View File

@ -0,0 +1,6 @@
import { useRequest } from 'vue-request'
export const testRequest = () => {
const { data, loading, error } = useRequest(() => http.post('/mock/post'))
return { data, loading, error }
}

View File

@ -0,0 +1,62 @@
<script setup lang="ts">
import { vOnClickOutside } from '@vueuse/components'
const visiable = ref(false)
function dropdownHandler() {
visiable.value = false
}
const { availableLocales, locale } = useI18n()
</script>
<template>
<div class="relative">
<div class="inline-flex items-center overflow-hidden rounded-md">
<button
class="h-full cursor-pointer border-0 bg-white p-2 text-gray-600 hover:bg-gray-200 hover:text-gray-700"
dark="bg-transparent hover:bg-gray-500"
:class="visiable ? 'bg-gray-200 bg-gray-500 dark:bg-gray-500' : ''"
@click.stop="visiable = !visiable"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
<Transition name="fade" mode="out-in">
<div
v-if="visiable"
v-on-click-outside.bubble="dropdownHandler"
dark="bg-gray-500"
class="absolute end-0 z-10 mt-2 w-56 rounded-md bg-white shadow-lg divide-y divide-gray-100"
>
<div class="p-2">
<span
v-for="availableLocale of availableLocales"
:key="availableLocale"
:class="
locale === availableLocale
? 'bg-gray-100 text-gray-800 dark:bg-gray-400'
: ''
"
class="block cursor-pointer rounded-lg px-4 py-2 text-sm text-gray-500 hover:text-gray-900"
dark="text-light-500 hover:text-light-900"
@click="locale = availableLocale"
>
{{ availableLocale }}
</span>
</div>
</div>
</Transition>
</div>
</template>

View File

@ -0,0 +1,52 @@
<script setup lang="ts">
defineProps<{ msg: string }>()
const visits = useVisits()
const { t } = useI18n()
</script>
<template>
<div class="mt-10 flex flex-col items-center space-y-7">
<h1 class="text-4xl">{{ msg }}</h1>
<Counter />
<p>
{{ t('edit') }}
<code>components/HelloWorld.vue</code> {{ t('to test HMR') }}
</p>
<p>
{{ t('test.module') }}
</p>
<p>
{{ t('check out') }}
<a href="https://github.com/dishait/tov-template" target="_blank">
tov-template </a
>, {{ t('the official Tov + Vue + Vite template') }}
</p>
<p class="read-the-docs">
{{ t('The total number of views is') }}
<span class="text-gray-800" dark="text-gray-300">{{ visits ?? 0 }}</span>
</p>
</div>
</template>
<style scoped>
.read-the-docs {
color: #888;
font-size: 1.2rem;
animation: slide-up 0.5s ease-out;
}
@keyframes slide-up {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
</style>

View File

@ -0,0 +1,146 @@
<script setup lang="ts">
import { getRoutes } from '@/plugins/router'
import { SwitchIcon } from 'vue-dark-switch'
const { te, t } = useI18n()
const routes = getRoutes()
.filter((r) => !r.path.includes('notFound'))
.map((r) => {
let { path, name } = r
if (path === safeResolve('/')) {
return { path, name: 'home' }
}
if (!name) {
name = path
}
return { path, name: name.toString().slice(1).replaceAll('/', ' · ') }
})
const $route = useRoute()
</script>
<template>
<nav
aria-label="Site Nav"
class="mx-auto h-80px max-w-3xl flex items-center justify-between p-4"
>
<div class="flex items-center justify-center space-x-5">
<SwitchIcon unmount-persets />
<a href="https://pc.dishait.cn/" target="_blank">
<span
style="
color: white;
font-size: 14px;
border-radius: 3px 0 0 3px;
padding: 4px 4px 4px 4px;
background: #00b894;
"
>帝莎编程</span
>
<span
style="
border-radius: 0 3px 3px 0;
padding: 5px 10px 5px 2px;
background: #00dc8220;
font-size: 13px;
color: rgba(37, 99, 235);
"
>
https://pc.dishait.cn/
</span>
</a>
</div>
<ul class="flex items-center gap-2 text-sm font-medium">
<li v-for="r of routes" :key="r.path" class="hidden !block">
<RouterLink
class="rounded-lg px-3 py-2 hover:text-blue-700"
:class="$route.path === r.path ? 'text-blue-700' : ''"
:to="r.path"
>
{{ te(r.name) ? t(r.name) : r.name }}
</RouterLink>
</li>
<li>
<a
class="inline-flex items-center gap-2 rounded-lg px-3 py-2"
href="https://github.com/dishait/tov-template"
target="_blank"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<mask id="lineMdGithubLoop0" width="24" height="24" x="0" y="0">
<g fill="#fff">
<ellipse cx="9.5" cy="9" rx="1.5" ry="1" />
<ellipse cx="14.5" cy="9" rx="1.5" ry="1" />
</g>
</mask>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="30"
stroke-dashoffset="30"
d="M12 4C13.6683 4 14.6122 4.39991 15 4.5C15.5255 4.07463 16.9375 3 18.5 3C18.8438 4 18.7863 5.21921 18.5 6C19.25 7 19.5 8 19.5 9.5C19.5 11.6875 19.017 13.0822 18 14C16.983 14.9178 15.8887 15.3749 14.5 15.5C15.1506 16.038 15 17.3743 15 18C15 18.7256 15 21 15 21M12 4C10.3317 4 9.38784 4.39991 9 4.5C8.47455 4.07463 7.0625 3 5.5 3C5.15625 4 5.21371 5.21921 5.5 6C4.75 7 4.5 8 4.5 9.5C4.5 11.6875 4.98301 13.0822 6 14C7.01699 14.9178 8.1113 15.3749 9.5 15.5C8.84944 16.038 9 17.3743 9 18C9 18.7256 9 21 9 21"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.6s"
values="30;0"
/>
</path>
<path stroke-dasharray="10" stroke-dashoffset="10" d="M9 19">
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.7s"
dur="0.2s"
values="10;0"
/>
<animate
attributeName="d"
dur="3s"
repeatCount="indefinite"
values="M9 19c-1.406 0-2.844-.563-3.688-1.188C4.47 17.188 4.22 16.157 3 15.5;M9 19c-1.406 0-3-.5-4-.5-.532 0-1 0-2-.5;M9 19c-1.406 0-2.844-.563-3.688-1.188C4.47 17.188 4.22 16.157 3 15.5"
/>
</path>
</g>
<rect
width="8"
height="4"
x="8"
y="11"
fill="currentColor"
mask="url(#lineMdGithubLoop0)"
>
<animate
attributeName="y"
dur="10s"
keyTimes="0;0.45;0.46;0.54;0.55;1"
repeatCount="indefinite"
values="11;11;7;7;11;11"
/>
</rect>
</svg>
</a>
</li>
<li class="hidden !block">
<Dropdown />
</li>
</ul>
</nav>
</template>

View File

@ -0,0 +1,16 @@
<script lang="ts" setup>
defineOptions({
name: 'Counter',
})
const counter = createCounter()
</script>
<template>
<button
class="inline-block cursor-pointer rounded from-pink-500 via-red-500 to-yellow-500 bg-gradient-to-r px-8 py-2 text-sm text-white font-medium transition !border-0 !outline-none"
hover="scale-110 shadow-xl"
@click="counter.inc()"
>
{{ counter.count }}
</button>
</template>

27
src/composables/env.ts Normal file
View File

@ -0,0 +1,27 @@
/**
*
* @description `if (IN_DEV)` `vite build`
* @example
* ```ts
* if (IN_DEV) {
* console.log("开发环境")
* }
* ```
*/
export const IN_DEV = import.meta.env.DEV
/**
*
* @example
* if (IN_PROD) {
* console.log("生产环境")
* }
*/
export const IN_PROD = import.meta.env.PROD
/**
* / BASE_URL
*/
export const BASE_URL_WITHOUT_TAIL = import.meta.env.BASE_URL.endsWith('/')
? import.meta.env.BASE_URL.slice(0, -1)
: import.meta.env.BASE_URL

60
src/composables/http.ts Normal file
View File

@ -0,0 +1,60 @@
import axios from 'axios'
export const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
})
// 添加请求拦截器
http.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config
},
function (error) {
toast.warning(error.message ?? '未知请求错误')
// 对请求错误做些什么
return Promise.reject(error)
},
)
// 添加响应拦截器
http.interceptors.response.use(
function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据进行格式化
if (response.data) {
return response.data
}
return response
},
function (error) {
const status = error.response?.status
let { msg, message } = error.response?.data ?? {}
if (!msg && message) {
msg = message
}
if (!msg) {
switch (status) {
case 400:
msg = '参数错误'
break
case 500:
msg = '服务端错误'
break
case 404:
msg = '路由未找到'
break
default:
msg = error.message ?? '未知响应错误'
break
}
}
toast.warning(msg)
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
},
)

7
src/composables/path.ts Normal file
View File

@ -0,0 +1,7 @@
/**
* base
* @param path
*/
export function safeResolve(path: string) {
return BASE_URL_WITHOUT_TAIL + path
}

4
src/composables/toast.ts Normal file
View File

@ -0,0 +1,4 @@
import 'vue-toastification/dist/index.css'
import { createToastInterface } from 'vue-toastification'
export default createToastInterface()

View File

@ -0,0 +1,12 @@
export default () => {
const { t, locale } = useI18n()
const toggleLocale = () => {
locale.value = locale.value === 'zh-CN' ? 'en' : 'zh-CN'
}
const language = computed(() =>
locale.value === 'zh-CN' ? '中文' : 'English',
)
return { t, language, toggleLocale }
}

View File

@ -0,0 +1,14 @@
import { init } from 'ityped'
export default (strings: string[]) => {
const typedRef = ref<Element>()
onMounted(() => {
init(typedRef.value!, {
strings,
showCursor: false,
disableBackTyping: true,
})
})
return typedRef
}

View File

@ -0,0 +1,25 @@
import { useRequest } from 'vue-request'
export function useVisits() {
// 开发环境下
if (import.meta.env.DEV) {
const visits = useStorage('visits-kv', 0)
if (typeof visits.value === 'number') {
visits.value++
}
return visits
}
const { data: visits } = useRequest(async function () {
try {
const n = await http.get('https://visits-kv.deno.dev/tov-template', {
baseURL: '',
})
return Number(n) ?? 0
} catch (error) {
console.error(error)
return 0
}
})
return visits ?? 0
}

14
src/layouts/default.vue Normal file
View File

@ -0,0 +1,14 @@
<template>
<Navigation />
<div class="w-screen flex flex-col items-center justify-center">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</div>
</template>
<style>
</style>

3
src/layouts/notFound.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
<router-view />
</template>

13
src/main.ts Normal file
View File

@ -0,0 +1,13 @@
// https://unocss.dev/ 原子 css 库
import '@unocss/reset/tailwind-compat.css' // unocss reset
import 'virtual:uno.css'
import 'virtual:unocss-devtools'
// 你自定义的 css
import './styles/main.css'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')

View File

@ -0,0 +1,58 @@
<script setup lang="ts">
const typedRef = useTyped([' is not found!'])
</script>
<template>
<div class="h-screen flex flex-wrap items-center justify-around text-center">
<div class="desc font-blod">
<div class="code text-7xl">404</div>
<div ref="typedRef" class="content mb-5 text-3xl">The Page</div>
<RouterLink :to="safeResolve('/')">
<button
class="rounded bg-light-800 px-5 py-2 text-lg transition"
hover="shadow-md"
dark="text-black"
>
Go Home
</button>
</RouterLink>
</div>
<img
:src="safeResolve('/notFound/32.svg')"
class="cover"
alt="page not found"
/>
</div>
</template>
<style>
.code {
margin-bottom: 20px;
}
.content {
height: 40px;
}
.cover {
height: auto;
width: 700px;
margin: 0 5px;
max-width: 100%;
max-height: 100%;
}
.desc {
flex: 1;
width: 300px;
}
</style>
<route lang="json">
{
"meta": {
"title": "404",
"layout": "notFound"
}
}
</route>

12
src/pages/about.md Normal file
View File

@ -0,0 +1,12 @@
# About
> The page is markdown file
```js
console.log("hello");
```
<br />
<!-- 支持嵌入组件 -->
<Counter />

78
src/pages/echarts.vue Normal file
View File

@ -0,0 +1,78 @@
<script setup lang="ts">
import { use } from 'echarts/core'
import { isDark } from 'vue-dark-switch'
import { PieChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers'
import {
TitleComponent,
TooltipComponent,
LegendComponent,
} from 'echarts/components'
import VChart, { THEME_KEY } from 'vue-echarts'
import type { EChartsOption } from 'echarts'
use([
CanvasRenderer,
PieChart,
TitleComponent,
TooltipComponent,
LegendComponent,
])
provide(
THEME_KEY,
computed(() => (isDark.value ? 'dark' : '')),
)
const option = ref<EChartsOption>({
title: {
text: 'Traffic Sources',
left: 'center',
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
legend: {
orient: 'vertical',
left: 'left',
data: ['Direct', 'Email', 'Ad Networks', 'Video Ads', 'Search Engines'],
},
series: [
{
name: 'Traffic Sources',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{ value: 335, name: 'Direct' },
{ value: 310, name: 'Email' },
{ value: 234, name: 'Ad Networks' },
{ value: 135, name: 'Video Ads' },
{ value: 1548, name: 'Search Engines' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
})
</script>
<template>
<div>
<!-- @vue-ignore -->
<v-chart class="chart" :option="option" autoresize />
</div>
</template>
<style>
.chart {
width: 100vw;
height: calc(100vh - 82px);
}
</style>

32
src/pages/index.vue Normal file
View File

@ -0,0 +1,32 @@
<template>
<div>
<div class="grid grid-cols-3">
<a href="https://github.com/dishait/tov-template" target="_blank">
<img :src="safeResolve('/logo.png')" class="logo" alt="Vite logo" />
</a>
<a href="https://vitejs.dev" target="_blank">
<img :src="safeResolve('/vite.svg')" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img :src="safeResolve('/vue.svg')" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Tov + Vite + Vue" />
</div>
</template>
<style>
.logo {
width: 10em;
height: 10em;
padding: 1.5rem;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

36
src/plugins/i18n.ts Normal file
View File

@ -0,0 +1,36 @@
import { defu } from 'defu'
import { createI18n } from 'vue-i18n'
const yamls = import.meta.glob('../../locales/*/**.y(a)?ml', {
eager: true,
})
const languages = Object.entries(yamls).map(([key, value]) => {
const yaml = key.endsWith('.yaml')
key = key.slice(14, yaml ? -5 : -4)
// 如果有子模块,则分割
if (key.includes('/')) {
key = key.split('/')[0]
}
// @ts-ignore
return { [key]: value.default }
})
const messages = defu({}, ...languages)
// localStorage 中的 locale第二个参数为默认值
// https://vueuse.org/core/useStorage/#usestorage
const storageLocale = useStorage('locale', '简体中文')
export const i18n = createI18n({
messages,
legacy: false,
globalInjection: true,
allowComposition: true,
})
// 同步本地 localStorage 和 i18n
// https://vueuse.org/shared/syncRef/#syncref
syncRef(storageLocale, i18n.global.locale)
export default i18n

23
src/plugins/mock.ts Normal file
View File

@ -0,0 +1,23 @@
/**
* mock
*/
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
import { createFetchSever } from '../../presets/shared/mock'
const shouldCreateServer =
!import.meta.env.DEV && import.meta.env.VITE_APP_MOCK_IN_PRODUCTION === 'true'
// 生产环境时才创建服务
if (shouldCreateServer) {
const mockModules: any[] = []
const modules = import.meta.glob('../../mock/*.ts', {
eager: true,
})
Object.values(modules).forEach((v: any) => {
if (Array.isArray(v.default)) {
mockModules.push(...v.default)
}
})
createProdMockServer(mockModules)
createFetchSever(mockModules)
}

9
src/plugins/nprogress.ts Normal file
View File

@ -0,0 +1,9 @@
import { router } from './router'
import { useNProgress } from '@vueuse/integrations/useNProgress'
// https://vueuse.org/integrations/useNProgress/
const { start, done } = useNProgress()
router.beforeEach(() => start())
router.afterEach(() => done(true))

7
src/plugins/pinia.ts Normal file
View File

@ -0,0 +1,7 @@
import persistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persistedstate)
export default pinia

26
src/plugins/router.ts Normal file
View File

@ -0,0 +1,26 @@
import { createGetRoutes, setupLayouts } from 'virtual:meta-layouts'
import { createRouter, createWebHistory } from 'vue-router'
import { routes as fileRoutes } from 'vue-router/auto-routes'
declare module 'vue-router' {
// 在这里定义你的 meta 类型
// eslint-disable-next-line no-unused-vars
interface RouteMeta {
title?: string
layout?: string
}
}
// 重定向 BASE_URL
fileRoutes.flat(Infinity).forEach((route) => {
route.path = safeResolve(route.path)
})
export const router = createRouter({
history: createWebHistory(),
routes: setupLayouts(fileRoutes),
})
export const getRoutes = createGetRoutes(router)
export default router

17
src/plugins/title.ts Normal file
View File

@ -0,0 +1,17 @@
import { router } from './router'
useTitle(
() => {
const { path, meta } = router.currentRoute.value
if (meta.title) {
return `· ${meta.title}`
}
if (path === '/') {
return '· home'
}
return path.replaceAll('/', ' · ')
},
{
titleTemplate: `${import.meta.env.VITE_APP_TITLE} %s`,
},
)

View File

@ -0,0 +1,15 @@
import { defineStore } from 'pinia'
export default defineStore('counter', {
state() {
return {
count: 0,
}
},
actions: {
inc() {
this.count++
},
},
persist: true,
})

55
src/styles/main.css Normal file
View File

@ -0,0 +1,55 @@
@import './md.css';
html.dark {
background: #100c2a !important;
}
.prose {
scroll-behavior: smooth;
}
#nprogress {
pointer-events: none;
}
#nprogress .bar {
@apply bg-blue-700 bg-opacity-75;
background: repeating-linear-gradient(90deg, #00dc82 0, #34cdfe 50%, #0047e1);
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
/*
* 全局滚动条
*/
::-webkit-scrollbar {
width: 8px;
height: 11px;
background-color: rgb(246, 247, 248);
}
::-webkit-scrollbar-thumb {
background-color: rgb(233, 236, 239);
border-radius: 10px;
}
::-webkit-scrollbar-track {
background-color: rgb(246, 247, 248);
border-radius: 10px;
}
html.dark ::-webkit-scrollbar,
html.dark ::-webkit-scrollbar-track {
background-color: #212529;
}
html.dark ::-webkit-scrollbar-thumb {
background-color: #343a40;
}

83
src/styles/md.css Normal file
View File

@ -0,0 +1,83 @@
@import 'prism-theme-vars/base.css';
.prose {
--prism-font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica,
Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
}
.prose pre {
font-weight: 500;
font-size: 1rem;
}
html:not(.dark) {
--prism-foreground: #393a34;
--prism-background: #f8f8f8;
--prism-comment: #868e96;
--prism-namespace: #444444;
--prism-string: #bc8671;
--prism-punctuation: #80817d;
--prism-literal: #36acaa;
--prism-keyword: #d73a49;
--prism-function: #0c4c7d;
--prism-deleted: #9a050f;
--prism-class: #2b91af;
--prism-builtin: #800000;
--prism-property: #ce9178;
--prism-regex: #ad502b;
}
html.dark {
--prism-foreground: #d4d4d4;
--prism-background: #1e1e1e;
--prism-namespace: #aaaaaa;
--prism-comment: #868e96;
--prism-namespace: #444444;
--prism-string: #ce9178;
--prism-punctuation: #d4d4d4;
--prism-literal: #36acaa;
--prism-keyword: #0ca678;
--prism-function: #dcdcaa;
--prism-deleted: #9a050f;
--prism-class: #4ec9b0;
--prism-builtin: #d16969;
--prism-property: #ce9178;
--prism-regex: #ad502b;
}
ol {
padding-left: 15px;
}
.prose blockquote p:first-of-type::before {
content: none;
}
.prose pre {
color: #495057;
background: #f8f9fa;
}
.prose-sm p {
font-weight: 500;
}
.prose blockquote {
margin: 0;
font-style: normal;
}
html.dark .prose blockquote {
color: white;
}
html.dark .prose pre {
color: #f8f9fa;
background: #2a2f33;
}
.token.comment {
font-style: normal;
font-size: 0.5rem;
}

View File

@ -0,0 +1,7 @@
// Vitest Snapshot v1
exports[`suite name > snapshot 1`] = `
{
"foo": "bar",
}
`;

16
src/test/index.test.ts Normal file
View File

@ -0,0 +1,16 @@
import { assert, describe, expect, it } from 'vitest'
describe('suite name', () => {
it('foo', () => {
expect(1 + 1).toEqual(2)
expect(true).to.be.true
})
it('bar', () => {
assert.equal(Math.sqrt(4), 2)
})
it('snapshot', () => {
expect({ foo: 'bar' }).toMatchSnapshot()
})
})

32
tsconfig.json Normal file
View File

@ -0,0 +1,32 @@
{
"compilerOptions": {
"strict": true,
"jsx": "preserve",
"target": "esnext",
"module": "esnext",
"sourceMap": true,
"skipLibCheck": true,
"isolatedModules": true,
"jsxImportSource": "vue",
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"resolveJsonModule": true,
"moduleResolution": "node",
"useDefineForClassFields": true,
"baseUrl": ".",
"paths": {
"~/*": ["src/*"],
"@/*": ["src/*"]
}
},
"include": [
"srcipts",
"presets",
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"./vite.config.ts"
],
"exclude": ["node_modules", "dist"]
}

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