style: 添加大屏插件和格式化代码

This commit is contained in:
Bunny 2025-02-26 11:23:04 +08:00
parent d5acd952d6
commit 2458802210
32 changed files with 1056 additions and 989 deletions

3
.env
View File

@ -13,6 +13,9 @@ VITE_APP_URL=http://localhost:8801
# 如果端口被占用会直接退出,而不是尝试下一个端口 # 如果端口被占用会直接退出,而不是尝试下一个端口
VITE_STRICT_PORT=false VITE_STRICT_PORT=false
# 是否启用屏幕转vw适配
VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN=false
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false # 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN=false VITE_CDN=false

View File

@ -10,6 +10,9 @@ VITE_APP_URL=http://localhost:8801
# 如果端口被占用会直接退出,而不是尝试下一个端口 # 如果端口被占用会直接退出,而不是尝试下一个端口
VITE_STRICT_PORT=false VITE_STRICT_PORT=false
# 是否启用屏幕转vw适配
VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN=false
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false # 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN=false VITE_CDN=false

View File

@ -10,6 +10,9 @@ VITE_APP_URL=http://localhost:8000
# 如果端口被占用会直接退出,而不是尝试下一个端口 # 如果端口被占用会直接退出,而不是尝试下一个端口
VITE_STRICT_PORT=false VITE_STRICT_PORT=false
# 是否启用屏幕转vw适配
VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN=false
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false # 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN=false VITE_CDN=false

View File

@ -1,46 +1,46 @@
module.exports = { export default {
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always) // (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
arrowParens: "always", arrowParens: "always",
// 开始标签的右尖括号是否跟随在最后一行属性末尾默认false // 开始标签的右尖括号是否跟随在最后一行属性末尾默认false
bracketSameLine: false, bracketSameLine: false,
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar}) // 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
bracketSpacing: true, bracketSpacing: true,
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto) // 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
embeddedLanguageFormatting: "auto", embeddedLanguageFormatting: "auto",
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css) // 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
htmlWhitespaceSensitivity: "ignore", htmlWhitespaceSensitivity: "ignore",
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记默认false // 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记默认false
insertPragma: false, insertPragma: false,
// 在 JSX 中使用单引号替代双引号默认false // 在 JSX 中使用单引号替代双引号默认false
jsxSingleQuote: false, jsxSingleQuote: false,
// 每行最多字符数量,超出换行(默认100) // 每行最多字符数量,超出换行(默认100)
printWidth: 100, printWidth: 100,
// 超出打印宽度 (always | never | preserve ) // 超出打印宽度 (always | never | preserve )
proseWrap: "preserve", proseWrap: "preserve",
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;) // 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
quoteProps: "as-needed", quoteProps: "as-needed",
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件默认false // 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件默认false
requirePragma: false, requirePragma: false,
// 结尾添加分号 // 结尾添加分号
semi: true, semi: true,
// 使用单引号 (true:单引号;false:双引号) // 使用单引号 (true:单引号;false:双引号)
singleQuote: false, singleQuote: true,
// 缩进空格数默认2个空格 // 缩进空格数默认2个空格
tabWidth: 2, tabWidth: 2,
// 元素末尾是否加逗号默认es5: ES5中的 objects, arrays 等会添加逗号TypeScript 中的 type 后不加逗号 // 元素末尾是否加逗号默认es5: ES5中的 objects, arrays 等会添加逗号TypeScript 中的 type 后不加逗号
trailingComma: "es5", trailingComma: "es5",
// 指定缩进方式空格或tab默认false即使用空格 // 指定缩进方式空格或tab默认false即使用空格
useTabs: false, useTabs: false,
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false // vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
vueIndentScriptAndStyle: false, vueIndentScriptAndStyle: false,
endOfLine: "auto", endOfLine: "auto",
overrides: [ overrides: [
{ {
files: "*.html", files: "*.html",
options: { options: {
parser: "html", parser: "html",
}, },
}, },
], ],
}; };

View File

@ -1,51 +1,52 @@
import type { BuildOptions } from 'vite'; import type { BuildOptions } from 'vite';
import { pathResolve } from './utils'; import { pathResolve } from './utils';
export const buildEnv = (): BuildOptions => { export const buildEnv = (): BuildOptions => {
return { return {
target: 'es2015', target: 'es2015',
assetsInlineLimit: 20000, assetsInlineLimit: 20000,
// 构建输出的目录,默认值为"dist" // 构建输出的目录,默认值为"dist"
outDir: 'docker/dist', outDir: 'docker/dist',
// 用于指定使用的代码压缩工具。在这里minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser // 用于指定使用的代码压缩工具。在这里minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser
// esbuild 打包更快,但是不能去除 console.logterser打包慢但能去除 console.log // esbuild 打包更快,但是不能去除 console.logterser打包慢但能去除 console.log
minify: 'terser', // "esbuild" minify: 'terser', // "esbuild"
// 用于配置 Terser 的选项 // 用于配置 Terser 的选项
terserOptions: { terserOptions: {
// 用于配置压缩选项 // 用于配置压缩选项
compress: { compress: {
drop_console: true, // 是否删除代码中的 console 语句, 默认值false drop_console: true, // 是否删除代码中的 console 语句, 默认值false
drop_debugger: true, // 是否删除代码中的 debugger 语句, 默认值false drop_debugger: true, // 是否删除代码中的 debugger 语句, 默认值false
}, },
}, },
// 禁用 gzip 压缩大小报告,可略微减少打包时间 // 禁用 gzip 压缩大小报告,可略微减少打包时间
reportCompressedSize: false, reportCompressedSize: false,
// 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时构建过程不会生成源映射文件 // 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时构建过程不会生成源映射文件
sourcemap: false, sourcemap: false,
// 用于配置 CommonJS 模块的选项 // 用于配置 CommonJS 模块的选项
commonjsOptions: { commonjsOptions: {
// 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时构建过程会保留 CommonJS 模块中的 try-catch 语句 // 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时构建过程会保留 CommonJS 模块中的 try-catch 语句
ignoreTryCatch: false, ignoreTryCatch: false,
}, },
// 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时Vite 会发出警告 // 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时Vite 会发出警告
chunkSizeWarningLimit: 2000, chunkSizeWarningLimit: 2000,
rollupOptions: { rollupOptions: {
external: ['md-editor-v3', 'echarts'], external: ['md-editor-v3', 'echarts'],
input: { input: {
index: pathResolve('../index.html', import.meta.url), index: pathResolve('../index.html', import.meta.url),
}, },
// 静态资源分类打包 // 静态资源分类打包
output: { output: {
chunkFileNames: 'static/js/[name]-[hash].js', chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js', entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]', assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
manualChunks: id => { manualChunks: (id) => {
// 如果是包含在包中则打包成 vendor // 如果是包含在包中则打包成 vendor
if (id.includes('node_modules')) { if (id.includes('node_modules')) {
return `vendor`; return `vendor`;
} }
}, },
}, },
}, },
}; };
}; };

View File

@ -1,4 +1,5 @@
import { Plugin as importToCDN } from 'vite-plugin-cdn-import'; import { Plugin as importToCDN } from 'vite-plugin-cdn-import';
import { wrapperEnv } from './utils'; import { wrapperEnv } from './utils';
/** /**
@ -7,61 +8,61 @@ import { wrapperEnv } from './utils';
* 使jscss文件cdn * 使jscss文件cdn
*/ */
export const cdn = importToCDN({ export const cdn = importToCDN({
//prodUrl解释 name: 对应下面modules的nameversion: 自动读取本地package.json中dependencies依赖中对应包的版本号path: 对应下面modules的path当然也可写完整路径会替换prodUrl //prodUrl解释 name: 对应下面modules的nameversion: 自动读取本地package.json中dependencies依赖中对应包的版本号path: 对应下面modules的path当然也可写完整路径会替换prodUrl
// prodUrl: 'https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}', // prodUrl: 'https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}',
prodUrl: 'https://unpkg.com/{name}@{version}/{path}', prodUrl: 'https://unpkg.com/{name}@{version}/{path}',
modules: [ modules: [
{ {
name: 'vue', name: 'vue',
var: 'Vue', var: 'Vue',
path: 'dist/vue.global.prod.js', path: 'dist/vue.global.prod.js',
}, },
{ {
name: 'vue-router', name: 'vue-router',
var: 'VueRouter', var: 'VueRouter',
path: 'dist/vue-router.global.js', path: 'dist/vue-router.global.js',
}, },
// { // {
// name: 'vue-i18n', // name: 'vue-i18n',
// var: 'VueI18n', // var: 'VueI18n',
// path: 'dist/vue-i18n.global.prod.js', // path: 'dist/vue-i18n.global.prod.js',
// }, // },
// { // {
// name: 'vue-demi', // name: 'vue-demi',
// var: 'VueDemi', // var: 'VueDemi',
// path: 'lib/index.iife.js', // path: 'lib/index.iife.js',
// }, // },
{ {
name: 'pinia', name: 'pinia',
var: 'Pinia', var: 'Pinia',
path: 'dist/pinia.iife.js', path: 'dist/pinia.iife.js',
}, },
// { // {
// name: 'element-plus', // name: 'element-plus',
// var: 'ElementPlus', // var: 'ElementPlus',
// path: 'dist/index.full.js', // path: 'dist/index.full.js',
// css: 'dist/index.css', // css: 'dist/index.css',
// }, // },
{ {
name: 'axios', name: 'axios',
var: 'axios', var: 'axios',
path: 'dist/axios.min.js', path: 'dist/axios.min.js',
}, },
{ {
name: 'dayjs', name: 'dayjs',
var: 'dayjs', var: 'dayjs',
path: 'dayjs.min.js', path: 'dayjs.min.js',
}, },
{ {
name: 'echarts', name: 'echarts',
var: 'echarts', var: 'echarts',
path: 'dist/echarts.min.js', path: 'dist/echarts.min.js',
}, },
], ],
}); });
/* 是否使用CDN加速 */ /* 是否使用CDN加速 */
export const useCDN = mode => { export const useCDN = (mode) => {
const env = wrapperEnv(mode, 'VITE'); const env = wrapperEnv(mode, 'VITE');
return env.VITE_CDN ? cdn : null; return env.VITE_CDN ? cdn : null;
}; };

39
build/css.ts Normal file
View File

@ -0,0 +1,39 @@
import postCssPxToViewport8plugin from 'postcss-px-to-viewport-8-plugin';
import type { CSSOptions, Plugin } from 'vite';
import { wrapperEnv } from './utils';
export const css = (mode): CSSOptions => {
const plugins: Plugin[] = [usePostCssPxToViewport8plugin(mode)];
return {
postcss: {
plugins: plugins.filter(Boolean),
},
};
};
/** 是否启用px转换vw插件 */
const usePostCssPxToViewport8plugin = (mode): Plugin => {
const { VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN } = wrapperEnv(mode, 'VITE');
return VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN
? postCssPxToViewport8plugin({
unitToConvert: 'px',
viewportWidth: 1920, // 设计稿的宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ['*'], // 能转化为vw的属性列表
viewportUnit: 'vw', // 希望使用的视口单位
fontViewportUnit: 'vw', // 字体使用的视口单位
selectorBlackList: [], // 需要忽略的CSS选择器不会转为视口单位使用原有的px等单位。
minPixelValue: 1, // 设置最小的转换数值如果为1的话只有大于1的值会被转换
mediaQuery: true, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: [/node_modules/], // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
include: [], // 如果设置了include那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1024, // 横屏时使用的视口宽度
})
: null;
};

View File

@ -1,13 +1,14 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { dependencies, devDependencies, engines, name, version } from '../package.json'; import { dependencies, devDependencies, engines, name, version } from '../package.json';
const __APP_INFO__ = { const __APP_INFO__ = {
pkg: { name, version, engines, dependencies, devDependencies }, pkg: { name, version, engines, dependencies, devDependencies },
lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'), lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
}; };
export const define = () => { export const define = () => {
return { return {
__APP_INFO__: JSON.stringify(__APP_INFO__), __APP_INFO__: JSON.stringify(__APP_INFO__),
}; };
}; };

View File

@ -1,50 +1,56 @@
import { wrapperEnv } from './utils';
import dayjs, { type Dayjs } from 'dayjs';
import gradientString from 'gradient-string';
import duration from 'dayjs/plugin/duration';
import boxen, { type Options as BoxenOptions } from 'boxen'; import boxen, { type Options as BoxenOptions } from 'boxen';
import dayjs, { type Dayjs } from 'dayjs';
import duration from 'dayjs/plugin/duration';
import gradientString from 'gradient-string';
import { wrapperEnv } from './utils';
dayjs.extend(duration); dayjs.extend(duration);
const boxenOptions: BoxenOptions = { const boxenOptions: BoxenOptions = {
padding: 0.94, padding: 0.94,
borderColor: 'cyan', borderColor: 'cyan',
borderStyle: 'round', borderStyle: 'round',
textAlignment: 'left', textAlignment: 'left',
}; };
/* 输出日志信息 */ /* 输出日志信息 */
const printLogMessage = (VITE_PORT: number) => { const printLogMessage = (VITE_PORT: number) => {
return gradientString('cyan', 'magenta').multiline( return gradientString('cyan', 'magenta').multiline(
`保存成功!服务器重新启动 `保存成功!服务器重新启动...
访 访
http://localhost:${VITE_PORT}`, http://localhost:${VITE_PORT}`
); );
}; };
export const viteConsoleLog = mode => { export const viteConsoleLog = (mode) => {
const { VITE_PORT } = wrapperEnv(mode); const { VITE_PORT } = wrapperEnv(mode);
let config: { command: string }; let config: { command: string };
let startTime: Dayjs; let startTime: Dayjs;
let endTime: Dayjs; let endTime: Dayjs;
return { return {
name: 'vite:buildInfo', name: 'vite:buildInfo',
configResolved(resolvedConfig) { configResolved(resolvedConfig) {
config = resolvedConfig; config = resolvedConfig;
}, },
buildStart() { buildStart() {
console.log(boxen(printLogMessage(VITE_PORT), boxenOptions)); console.log(boxen(printLogMessage(VITE_PORT), boxenOptions));
if (config.command === 'build') { if (config.command === 'build') {
startTime = dayjs(new Date()); startTime = dayjs(new Date());
} }
}, },
closeBundle() { closeBundle() {
if (config.command === 'build') { if (config.command === 'build') {
endTime = dayjs(new Date()); endTime = dayjs(new Date());
const format = dayjs.duration(endTime.diff(startTime)).format('mm分ss秒'); const format = dayjs.duration(endTime.diff(startTime)).format('mm分ss秒');
console.log(boxen(gradientString('cyan', 'magenta').multiline(`🎉 恭喜打包完成(总用时${format}`), boxenOptions)); console.log(
} boxen(
}, gradientString('cyan', 'magenta').multiline(`🎉 恭喜打包完成(总用时${format}`),
}; boxenOptions
)
);
}
},
};
}; };

View File

@ -1,44 +1,44 @@
import UnoCssIcons from '@unocss/preset-icons';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import type { PluginOption } from 'vite';
import vueJsx from '@vitejs/plugin-vue-jsx'; import vueJsx from '@vitejs/plugin-vue-jsx';
import Inspector from 'vite-plugin-vue-inspector'; import { presetIcons, presetUno } from 'unocss';
import { compressPack, report } from './utils'; import UnoCSS from 'unocss/vite';
import type { PluginOption } from 'vite';
import removeConsole from 'vite-plugin-remove-console'; import removeConsole from 'vite-plugin-remove-console';
import Inspector from 'vite-plugin-vue-inspector';
import { useCDN } from './cdn'; import { useCDN } from './cdn';
import { viteConsoleLog } from './info'; import { viteConsoleLog } from './info';
import UnoCSS from 'unocss/vite'; import { compressPack, report } from './utils';
import { presetIcons, presetUno } from 'unocss';
import UnoCssIcons from '@unocss/preset-icons';
export const plugins = (mode): PluginOption[] => { export const plugins = (mode): PluginOption[] => {
return [ return [
vue(), vue(),
vueJsx(), vueJsx(),
Inspector(), Inspector(),
report(), report(),
removeConsole(), removeConsole(),
useCDN(mode), useCDN(mode),
viteConsoleLog(mode), viteConsoleLog(mode),
UnoCSS({ UnoCSS({
hmrTopLevelAwait: false, hmrTopLevelAwait: false,
inspector: true, // 控制台是否打印 UnoCSS inspector
presets: [ presets: [
presetIcons({ presetIcons({
extraProperties: { extraProperties: {
display: 'inline-block', display: 'inline-block',
'vertical-align': 'middle', 'vertical-align': 'middle',
}, },
}), }),
UnoCssIcons({ UnoCssIcons({
// 其他选项 prefix: 'i-',
prefix: 'i-', extraProperties: {
extraProperties: { display: 'inline-block',
display: 'inline-block', },
}, }),
}), presetUno(),
presetUno(), ],
], }),
}), compressPack(mode),
compressPack(mode), ];
];
}; };

View File

@ -1,10 +1,10 @@
import { pathResolve } from './utils'; import { pathResolve } from './utils';
export const resolve = () => { export const resolve = () => {
return { return {
alias: { alias: {
'@': pathResolve('../src'), '@': pathResolve('../src'),
'@build': pathResolve(), '@build': pathResolve(),
}, },
}; };
}; };

View File

@ -1,33 +1,34 @@
import type { ServerOptions } from 'vite'; import type { ServerOptions } from 'vite';
import { wrapperEnv } from './utils'; import { wrapperEnv } from './utils';
/* 开发服务配置 */ /* 开发服务配置 */
export const server = mode => { export const server = (mode) => {
const { VITE_PORT, VITE_APP_URL, VITE_STRICT_PORT } = wrapperEnv(mode); const { VITE_PORT, VITE_APP_URL, VITE_STRICT_PORT } = wrapperEnv(mode);
const options: ServerOptions = { const options: ServerOptions = {
strictPort: VITE_STRICT_PORT, strictPort: VITE_STRICT_PORT,
port: VITE_PORT, port: VITE_PORT,
host: '0.0.0.0', host: '0.0.0.0',
open: true, open: true,
cors: true, cors: true,
proxy: { proxy: {
'/api': { '/api': {
target: VITE_APP_URL, target: VITE_APP_URL,
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/admin/, '/api'), rewrite: (path: string) => path.replace(/^\/admin/, '/api'),
}, },
'/mock': { '/mock': {
target: VITE_APP_URL, target: VITE_APP_URL,
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/mock/, '/mock'), rewrite: (path: string) => path.replace(/^\/mock/, '/mock'),
}, },
}, },
// 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布 // 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布
warmup: { warmup: {
clientFiles: ['./index.html', './src/{views,components}/*'], clientFiles: ['./index.html', './src/{views,components}/*'],
}, },
}; };
return options; return options;
}; };

View File

@ -1,7 +1,8 @@
import { dirname, resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { loadEnv } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from 'rollup-plugin-visualizer';
import { loadEnv } from 'vite';
import viteCompression from 'vite-plugin-compression'; import viteCompression from 'vite-plugin-compression';
export const root: string = process.cwd(); export const root: string = process.cwd();
@ -12,19 +13,19 @@ export const root: string = process.cwd();
* @param metaUrl `url``build``import.meta.url` * @param metaUrl `url``build``import.meta.url`
*/ */
export const pathResolve = (dir = '.', metaUrl = import.meta.url) => { export const pathResolve = (dir = '.', metaUrl = import.meta.url) => {
// 当前文件目录的绝对路径 // 当前文件目录的绝对路径
const currentFileDir = dirname(fileURLToPath(metaUrl)); const currentFileDir = dirname(fileURLToPath(metaUrl));
// build 目录的绝对路径 // build 目录的绝对路径
const buildDir = resolve(currentFileDir, 'build'); const buildDir = resolve(currentFileDir, 'build');
// 解析的绝对路径 // 解析的绝对路径
const resolvedPath = resolve(currentFileDir, dir); const resolvedPath = resolve(currentFileDir, dir);
// 检查解析的绝对路径是否在 build 目录内 // 检查解析的绝对路径是否在 build 目录内
if (resolvedPath.startsWith(buildDir)) { if (resolvedPath.startsWith(buildDir)) {
// 在 build 目录内,返回当前文件路径 // 在 build 目录内,返回当前文件路径
return fileURLToPath(metaUrl); return fileURLToPath(metaUrl);
} }
// 不在 build 目录内,返回解析后的绝对路径 // 不在 build 目录内,返回解析后的绝对路径
return resolvedPath; return resolvedPath;
}; };
/** /**
@ -33,32 +34,34 @@ export const pathResolve = (dir = '.', metaUrl = import.meta.url) => {
* @param prefix * @param prefix
* @link https://cn.vite.dev/config/#using-environment-variables-in-config * @link https://cn.vite.dev/config/#using-environment-variables-in-config
*/ */
export const wrapperEnv = (mode, prefix = ''): ViteEnv => { export const wrapperEnv = (mode, prefix: string = ''): ViteEnv => {
const env = loadEnv(mode, root, prefix); const env = loadEnv(mode, root, prefix);
// 将变量转换指定类型 // 将变量转换指定类型
for (const envName of Object.keys(env)) { for (const envName of Object.keys(env)) {
let realName = env[envName].replace(/\\n/g, '\n'); let realName = env[envName].replace(/\\n/g, '\n');
realName = realName === 'true' ? true : realName === 'false' ? false : realName; realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') { if (envName === 'VITE_PORT') {
realName = Number(realName); realName = Number(realName);
} }
env[envName] = realName; env[envName] = realName;
process.env[envName] = realName; process.env[envName] = realName;
} }
return env; return env;
}; };
/* 打包分析 */ /* 打包分析 */
export const report = () => { export const report = () => {
const lifecycle = process.env.npm_lifecycle_event; const lifecycle = process.env.npm_lifecycle_event;
return lifecycle === 'report' ? visualizer({ open: true, brotliSize: true, filename: 'report.html' }) : (null as any); return lifecycle === 'report'
? visualizer({ open: true, brotliSize: true, filename: 'report.html' })
: (null as any);
}; };
/* 启用gzip压缩 */ /* 启用gzip压缩 */
export const compressPack = mode => { export const compressPack = (mode) => {
const { VITE_COMPRESSION } = wrapperEnv(mode); const { VITE_COMPRESSION } = wrapperEnv(mode);
return VITE_COMPRESSION == 'gzip' ? viteCompression({ threshold: 1024000 }) : null; return VITE_COMPRESSION == 'gzip' ? viteCompression({ threshold: 1024000 }) : null;
}; };

View File

@ -1,174 +1,177 @@
import js from '@eslint/js'; import js from '@eslint/js';
import pluginTypeScript from '@typescript-eslint/eslint-plugin';
import * as parserTypeScript from '@typescript-eslint/parser';
import configPrettier from 'eslint-config-prettier';
import { defineFlatConfig } from 'eslint-define-config';
import pluginPrettier from 'eslint-plugin-prettier';
import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginVue from 'eslint-plugin-vue'; import pluginVue from 'eslint-plugin-vue';
import * as parserVue from 'vue-eslint-parser'; import * as parserVue from 'vue-eslint-parser';
import configPrettier from 'eslint-config-prettier';
import pluginPrettier from 'eslint-plugin-prettier';
import { defineFlatConfig } from 'eslint-define-config';
import * as parserTypeScript from '@typescript-eslint/parser';
import pluginTypeScript from '@typescript-eslint/eslint-plugin';
export default defineFlatConfig([ export default defineFlatConfig([
{ {
...js.configs.recommended, ...js.configs.recommended,
ignores: ['**/.*', 'dist/*', '*.d.ts', 'public/*', 'src/assets/**', 'src/**/iconfont/**'], ignores: ['**/.*', 'dist/*', '*.d.ts', 'public/*', 'src/assets/**', 'src/**/iconfont/**'],
languageOptions: { languageOptions: {
globals: { globals: {
// index.d.ts // index.d.ts
RefType: 'readonly', RefType: 'readonly',
EmitType: 'readonly', EmitType: 'readonly',
TargetContext: 'readonly', TargetContext: 'readonly',
ComponentRef: 'readonly', ComponentRef: 'readonly',
ElRef: 'readonly', ElRef: 'readonly',
ForDataType: 'readonly', ForDataType: 'readonly',
AnyFunction: 'readonly', AnyFunction: 'readonly',
PropType: 'readonly', PropType: 'readonly',
Writable: 'readonly', Writable: 'readonly',
Nullable: 'readonly', Nullable: 'readonly',
NonNullable: 'readonly', NonNullable: 'readonly',
Recordable: 'readonly', Recordable: 'readonly',
ReadonlyRecordable: 'readonly', ReadonlyRecordable: 'readonly',
Indexable: 'readonly', Indexable: 'readonly',
DeepPartial: 'readonly', DeepPartial: 'readonly',
Without: 'readonly', Without: 'readonly',
Exclusive: 'readonly', Exclusive: 'readonly',
TimeoutHandle: 'readonly', TimeoutHandle: 'readonly',
IntervalHandle: 'readonly', IntervalHandle: 'readonly',
Effect: 'readonly', Effect: 'readonly',
ChangeEvent: 'readonly', ChangeEvent: 'readonly',
WheelEvent: 'readonly', WheelEvent: 'readonly',
ImportMetaEnv: 'readonly', ImportMetaEnv: 'readonly',
Fn: 'readonly', Fn: 'readonly',
PromiseFn: 'readonly', PromiseFn: 'readonly',
ComponentElRef: 'readonly', ComponentElRef: 'readonly',
parseInt: 'readonly', parseInt: 'readonly',
parseFloat: 'readonly', parseFloat: 'readonly',
}, },
}, },
plugins: { plugins: {
prettier: pluginPrettier, prettier: pluginPrettier,
}, 'simple-import-sort': eslintPluginSimpleImportSort,
rules: { },
...configPrettier.rules, rules: {
...pluginPrettier.configs.recommended.rules, ...configPrettier.rules,
'no-debugger': 'off', ...pluginPrettier.configs.recommended.rules,
'no-unused-vars': [ 'simple-import-sort/imports': 'error',
'error', 'no-debugger': 'off',
{ 'no-unused-vars': [
argsIgnorePattern: '^_', 'error',
varsIgnorePattern: '^_', {
}, argsIgnorePattern: '^_',
], varsIgnorePattern: '^_',
'prettier/prettier': [ },
'error', ],
{ 'prettier/prettier': [
endOfLine: 'auto', 'error',
}, {
], endOfLine: 'auto',
}, },
}, ],
{ },
files: ['**/*.?([cm])ts', '**/*.?([cm])tsx'], },
languageOptions: { {
parser: parserTypeScript, files: ['**/*.?([cm])ts', '**/*.?([cm])tsx'],
parserOptions: { languageOptions: {
sourceType: 'module', parser: parserTypeScript,
}, parserOptions: {
}, sourceType: 'module',
plugins: { },
'@typescript-eslint': pluginTypeScript, },
}, plugins: {
rules: { '@typescript-eslint': pluginTypeScript,
...pluginTypeScript.configs.strict.rules, },
'@typescript-eslint/ban-types': 'off', rules: {
'@typescript-eslint/no-redeclare': 'error', ...pluginTypeScript.configs.strict.rules,
'@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/prefer-as-const': 'warn', '@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/prefer-as-const': 'warn',
'@typescript-eslint/no-import-type-side-effects': 'error', '@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/prefer-literal-enum-member': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/consistent-type-imports': [ '@typescript-eslint/prefer-literal-enum-member': 'off',
'error', '@typescript-eslint/explicit-module-boundary-types': 'off',
{ '@typescript-eslint/consistent-type-imports': [
disallowTypeAnnotations: false, 'error',
fixStyle: 'inline-type-imports', {
}, disallowTypeAnnotations: false,
], fixStyle: 'inline-type-imports',
'@typescript-eslint/no-unused-vars': [ },
'error', ],
{ '@typescript-eslint/no-unused-vars': [
argsIgnorePattern: '^_', 'error',
varsIgnorePattern: '^_', {
}, argsIgnorePattern: '^_',
], varsIgnorePattern: '^_',
}, },
}, ],
{ },
files: ['**/*.d.ts'], },
rules: { {
'eslint-comments/no-unlimited-disable': 'off', files: ['**/*.d.ts'],
'import/no-duplicates': 'off', rules: {
'unused-imports/no-unused-vars': 'off', 'eslint-comments/no-unlimited-disable': 'off',
}, 'import/no-duplicates': 'off',
}, 'unused-imports/no-unused-vars': 'off',
{ },
files: ['**/*.?([cm])js'], },
rules: { {
'@typescript-eslint/no-require-imports': 'off', files: ['**/*.?([cm])js'],
'@typescript-eslint/no-var-requires': 'off', rules: {
}, '@typescript-eslint/no-require-imports': 'off',
}, '@typescript-eslint/no-var-requires': 'off',
{ },
files: ['**/*.vue'], },
languageOptions: { {
globals: { files: ['**/*.vue'],
$: 'readonly', languageOptions: {
$$: 'readonly', globals: {
$computed: 'readonly', $: 'readonly',
$customRef: 'readonly', $$: 'readonly',
$ref: 'readonly', $computed: 'readonly',
$shallowRef: 'readonly', $customRef: 'readonly',
$toRef: 'readonly', $ref: 'readonly',
}, $shallowRef: 'readonly',
parser: parserVue, $toRef: 'readonly',
parserOptions: { },
ecmaFeatures: { parser: parserVue,
jsx: true, parserOptions: {
}, ecmaFeatures: {
extraFileExtensions: ['.vue'], jsx: true,
parser: '@typescript-eslint/parser', },
sourceType: 'module', extraFileExtensions: ['.vue'],
}, parser: '@typescript-eslint/parser',
}, sourceType: 'module',
plugins: { },
vue: pluginVue, },
}, plugins: {
processor: pluginVue.processors['.vue'], vue: pluginVue,
rules: { },
...pluginVue.configs.base.rules, processor: pluginVue.processors['.vue'],
...pluginVue.configs['vue3-essential'].rules, rules: {
...pluginVue.configs['vue3-recommended'].rules, ...pluginVue.configs.base.rules,
'no-undef': 'off', ...pluginVue.configs['vue3-essential'].rules,
'no-unused-vars': 'off', ...pluginVue.configs['vue3-recommended'].rules,
'vue/no-v-html': 'off', 'no-undef': 'off',
'vue/require-default-prop': 'off', 'no-unused-vars': 'off',
'vue/require-explicit-emits': 'off', 'vue/no-v-html': 'off',
'vue/multi-word-component-names': 'off', 'vue/require-default-prop': 'off',
'vue/no-setup-props-reactivity-loss': 'off', 'vue/require-explicit-emits': 'off',
'vue/html-self-closing': [ 'vue/multi-word-component-names': 'off',
'error', 'vue/no-setup-props-reactivity-loss': 'off',
{ 'vue/html-self-closing': [
html: { 'error',
void: 'always', {
normal: 'always', html: {
component: 'always', void: 'always',
}, normal: 'always',
svg: 'always', component: 'always',
math: 'always', },
}, svg: 'always',
], math: 'always',
}, },
}, ],
},
},
]); ]);

View File

@ -12,6 +12,7 @@
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint" "lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint"
}, },
"dependencies": { "dependencies": {
"@eslint/js": "^9.21.0",
"@typescript-eslint/eslint-plugin": "^8.24.1", "@typescript-eslint/eslint-plugin": "^8.24.1",
"@typescript-eslint/parser": "^8.24.1", "@typescript-eslint/parser": "^8.24.1",
"@unocss/preset-icons": "^66.0.0", "@unocss/preset-icons": "^66.0.0",
@ -33,6 +34,7 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^3.2.3", "pinia-plugin-persistedstate": "^3.2.3",
"postcss-px-to-viewport-8-plugin": "^1.2.5",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"rimraf": "^5.0.10", "rimraf": "^5.0.10",
"rollup-plugin-visualizer": "^5.14.0", "rollup-plugin-visualizer": "^5.14.0",
@ -48,6 +50,7 @@
"vite-plugin-remove-console": "^2.2.0", "vite-plugin-remove-console": "^2.2.0",
"vite-plugin-vue-inspector": "^5.3.1", "vite-plugin-vue-inspector": "^5.3.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-eslint-parser": "^9.4.3",
"vue-router": "^4.4.3", "vue-router": "^4.4.3",
"vue-types": "^6.0.0" "vue-types": "^6.0.0"
}, },
@ -55,6 +58,7 @@
"@iconify/json": "^2.2.310", "@iconify/json": "^2.2.310",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.7.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"typescript": "~5.7.2", "typescript": "~5.7.2",
"vite": "^6.1.0", "vite": "^6.1.0",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",

View File

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@eslint/js':
specifier: ^9.21.0
version: 9.21.0
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^8.24.1 specifier: ^8.24.1
version: 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.21.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.21.0(jiti@2.4.2))(typescript@5.7.3) version: 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.21.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.21.0(jiti@2.4.2))(typescript@5.7.3)
@ -43,16 +46,16 @@ importers:
version: 9.21.0(jiti@2.4.2) version: 9.21.0(jiti@2.4.2)
eslint-config-prettier: eslint-config-prettier:
specifier: ^9.1.0 specifier: ^9.1.0
version: 9.1.0(eslint@9.21.0) version: 9.1.0(eslint@9.21.0(jiti@2.4.2))
eslint-define-config: eslint-define-config:
specifier: ^2.1.0 specifier: ^2.1.0
version: 2.1.0 version: 2.1.0
eslint-plugin-prettier: eslint-plugin-prettier:
specifier: ^5.2.1 specifier: ^5.2.1
version: 5.2.3(eslint-config-prettier@9.1.0(eslint@9.21.0))(eslint@9.21.0)(prettier@3.5.2) version: 5.2.3(eslint-config-prettier@9.1.0(eslint@9.21.0(jiti@2.4.2)))(eslint@9.21.0(jiti@2.4.2))(prettier@3.5.2)
eslint-plugin-vue: eslint-plugin-vue:
specifier: ^9.27.0 specifier: ^9.27.0
version: 9.32.0(eslint@9.21.0) version: 9.32.0(eslint@9.21.0(jiti@2.4.2))
gradient-string: gradient-string:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
@ -71,6 +74,9 @@ importers:
pinia-plugin-persistedstate: pinia-plugin-persistedstate:
specifier: ^3.2.3 specifier: ^3.2.3
version: 3.2.3(pinia@2.3.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))) version: 3.2.3(pinia@2.3.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)))
postcss-px-to-viewport-8-plugin:
specifier: ^1.2.5
version: 1.2.5
prettier: prettier:
specifier: ^3.3.3 specifier: ^3.3.3
version: 3.5.2 version: 3.5.2
@ -116,6 +122,9 @@ importers:
vue: vue:
specifier: ^3.5.13 specifier: ^3.5.13
version: 3.5.13(typescript@5.7.3) version: 3.5.13(typescript@5.7.3)
vue-eslint-parser:
specifier: ^9.4.3
version: 9.4.3(eslint@9.21.0(jiti@2.4.2))
vue-router: vue-router:
specifier: ^4.4.3 specifier: ^4.4.3
version: 4.5.0(vue@3.5.13(typescript@5.7.3)) version: 4.5.0(vue@3.5.13(typescript@5.7.3))
@ -128,10 +137,13 @@ importers:
version: 2.2.310 version: 2.2.310
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^5.2.1 specifier: ^5.2.1
version: 5.2.1(vite@6.1.1(sass@1.85.0))(vue@3.5.13(typescript@5.7.3)) version: 5.2.1(vite@6.1.1(jiti@2.4.2)(sass@1.85.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))
'@vue/tsconfig': '@vue/tsconfig':
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)) version: 0.7.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
eslint-plugin-simple-import-sort:
specifier: ^12.1.1
version: 12.1.1(eslint@9.21.0(jiti@2.4.2))
typescript: typescript:
specifier: ~5.7.2 specifier: ~5.7.2
version: 5.7.3 version: 5.7.3
@ -1374,6 +1386,11 @@ packages:
eslint-config-prettier: eslint-config-prettier:
optional: true optional: true
eslint-plugin-simple-import-sort@12.1.1:
resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==}
peerDependencies:
eslint: '>=5.0.0'
eslint-plugin-vue@9.32.0: eslint-plugin-vue@9.32.0:
resolution: {integrity: sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==} resolution: {integrity: sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==}
engines: {node: ^14.17.0 || >=16.0.0} engines: {node: ^14.17.0 || >=16.0.0}
@ -1890,6 +1907,10 @@ packages:
nth-check@2.1.1: nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
ofetch@1.4.1: ofetch@1.4.1:
resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
@ -1986,6 +2007,9 @@ packages:
postcss-media-query-parser@0.2.3: postcss-media-query-parser@0.2.3:
resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==}
postcss-px-to-viewport-8-plugin@1.2.5:
resolution: {integrity: sha512-+yc69+q/euV7iKh5fGXY6C/lpepmVx2DGFHeYj5BpzIFyBBpdACDjZyrZ8AV0kCg+J0bplBv4ZA1QTzgaK0rGg==}
postcss-resolve-nested-selector@0.1.6: postcss-resolve-nested-selector@0.1.6:
resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==}
@ -2849,11 +2873,6 @@ snapshots:
eslint: 9.21.0(jiti@2.4.2) eslint: 9.21.0(jiti@2.4.2)
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.4.1(eslint@9.21.0)':
dependencies:
eslint: 9.21.0
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {} '@eslint-community/regexpp@4.12.1': {}
'@eslint/config-array@0.19.2': '@eslint/config-array@0.19.2':
@ -3342,9 +3361,9 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@vitejs/plugin-vue@5.2.1(vite@6.1.1(sass@1.85.0))(vue@3.5.13(typescript@5.7.3))': '@vitejs/plugin-vue@5.2.1(vite@6.1.1(jiti@2.4.2)(sass@1.85.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))':
dependencies: dependencies:
vite: 6.1.1(sass@1.85.0) vite: 6.1.1(jiti@2.4.2)(sass@1.85.0)(terser@5.39.0)
vue: 3.5.13(typescript@5.7.3) vue: 3.5.13(typescript@5.7.3)
'@volar/language-core@2.4.11': '@volar/language-core@2.4.11':
@ -3797,31 +3816,35 @@ snapshots:
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}
eslint-config-prettier@9.1.0(eslint@9.21.0): eslint-config-prettier@9.1.0(eslint@9.21.0(jiti@2.4.2)):
dependencies: dependencies:
eslint: 9.21.0 eslint: 9.21.0(jiti@2.4.2)
eslint-define-config@2.1.0: {} eslint-define-config@2.1.0: {}
eslint-plugin-prettier@5.2.3(eslint-config-prettier@9.1.0(eslint@9.21.0))(eslint@9.21.0)(prettier@3.5.2): eslint-plugin-prettier@5.2.3(eslint-config-prettier@9.1.0(eslint@9.21.0(jiti@2.4.2)))(eslint@9.21.0(jiti@2.4.2))(prettier@3.5.2):
dependencies: dependencies:
eslint: 9.21.0 eslint: 9.21.0(jiti@2.4.2)
prettier: 3.5.2 prettier: 3.5.2
prettier-linter-helpers: 1.0.0 prettier-linter-helpers: 1.0.0
synckit: 0.9.2 synckit: 0.9.2
optionalDependencies: optionalDependencies:
eslint-config-prettier: 9.1.0(eslint@9.21.0) eslint-config-prettier: 9.1.0(eslint@9.21.0(jiti@2.4.2))
eslint-plugin-vue@9.32.0(eslint@9.21.0): eslint-plugin-simple-import-sort@12.1.1(eslint@9.21.0(jiti@2.4.2)):
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0) eslint: 9.21.0(jiti@2.4.2)
eslint: 9.21.0
eslint-plugin-vue@9.32.0(eslint@9.21.0(jiti@2.4.2)):
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2))
eslint: 9.21.0(jiti@2.4.2)
globals: 13.24.0 globals: 13.24.0
natural-compare: 1.4.0 natural-compare: 1.4.0
nth-check: 2.1.1 nth-check: 2.1.1
postcss-selector-parser: 6.1.2 postcss-selector-parser: 6.1.2
semver: 7.7.1 semver: 7.7.1
vue-eslint-parser: 9.4.3(eslint@9.21.0) vue-eslint-parser: 9.4.3(eslint@9.21.0(jiti@2.4.2))
xml-name-validator: 4.0.0 xml-name-validator: 4.0.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -3840,45 +3863,6 @@ snapshots:
eslint-visitor-keys@4.2.0: {} eslint-visitor-keys@4.2.0: {}
eslint@9.21.0:
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0)
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.19.2
'@eslint/core': 0.12.0
'@eslint/eslintrc': 3.3.0
'@eslint/js': 9.21.0
'@eslint/plugin-kit': 0.2.7
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.2
'@types/estree': 1.0.6
'@types/json-schema': 7.0.15
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.0
escape-string-regexp: 4.0.0
eslint-scope: 8.2.0
eslint-visitor-keys: 4.2.0
espree: 10.3.0
esquery: 1.6.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
ignore: 5.3.2
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.4
transitivePeerDependencies:
- supports-color
eslint@9.21.0(jiti@2.4.2): eslint@9.21.0(jiti@2.4.2):
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2)) '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2))
@ -4347,6 +4331,8 @@ snapshots:
dependencies: dependencies:
boolbase: 1.0.0 boolbase: 1.0.0
object-assign@4.1.1: {}
ofetch@1.4.1: ofetch@1.4.1:
dependencies: dependencies:
destr: 2.0.3 destr: 2.0.3
@ -4445,6 +4431,10 @@ snapshots:
postcss-media-query-parser@0.2.3: {} postcss-media-query-parser@0.2.3: {}
postcss-px-to-viewport-8-plugin@1.2.5:
dependencies:
object-assign: 4.1.1
postcss-resolve-nested-selector@0.1.6: {} postcss-resolve-nested-selector@0.1.6: {}
postcss-safe-parser@6.0.0(postcss@8.5.3): postcss-safe-parser@6.0.0(postcss@8.5.3):
@ -4924,25 +4914,16 @@ snapshots:
sass: 1.85.0 sass: 1.85.0
terser: 5.39.0 terser: 5.39.0
vite@6.1.1(sass@1.85.0):
dependencies:
esbuild: 0.24.2
postcss: 8.5.3
rollup: 4.34.8
optionalDependencies:
fsevents: 2.3.3
sass: 1.85.0
vscode-uri@3.1.0: {} vscode-uri@3.1.0: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.7.3)): vue-demi@0.14.10(vue@3.5.13(typescript@5.7.3)):
dependencies: dependencies:
vue: 3.5.13(typescript@5.7.3) vue: 3.5.13(typescript@5.7.3)
vue-eslint-parser@9.4.3(eslint@9.21.0): vue-eslint-parser@9.4.3(eslint@9.21.0(jiti@2.4.2)):
dependencies: dependencies:
debug: 4.4.0 debug: 4.4.0
eslint: 9.21.0 eslint: 9.21.0(jiti@2.4.2)
eslint-scope: 7.2.2 eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
espree: 9.6.1 espree: 9.6.1

View File

@ -1,22 +1,22 @@
<script lang="ts" setup></script> <script lang="ts" setup></script>
<template> <template>
<router-view /> <router-view />
</template> </template>
<style scoped> <style scoped>
.logo { .logo {
height: 6em; height: 6em;
padding: 1.5em; padding: 1.5em;
will-change: filter; will-change: filter;
transition: filter 300ms; transition: filter 300ms;
} }
.logo:hover { .logo:hover {
filter: drop-shadow(0 0 2em #646cffaa); filter: drop-shadow(0 0 2em #646cffaa);
} }
.logo.vue:hover { .logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa); filter: drop-shadow(0 0 2em #42b883aa);
} }
</style> </style>

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue';
defineProps<{ msg: string }>() defineProps<{ msg: string }>();
const count = ref(0) const count = ref(0);
</script> </script>
<template> <template>
@ -13,23 +13,22 @@ const count = ref(0)
<button type="button" @click="count++">count is {{ count }}</button> <button type="button" @click="count++">count is {{ count }}</button>
<p> <p>
Edit Edit
<code>components/HelloWorld.vue</code> to test HMR <code>components/HelloWorld.vue</code>
to test HMR
</p> </p>
</div> </div>
<p> <p>
Check out Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank" <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>
>create-vue</a , the official Vue + Vite starter
>, the official Vue + Vite starter
</p> </p>
<p> <p>
Learn more about IDE Support for Vue in the Learn more about IDE Support for Vue in the
<a <a href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support" target="_blank">
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support" Vue Docs Scaling up Guide
target="_blank" </a>
>Vue Docs Scaling up Guide</a .
>.
</p> </p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p> <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template> </template>

View File

@ -2,5 +2,5 @@ import type { App } from 'vue';
// 全局注册 directive // 全局注册 directive
export function setupDirective(app: App<Element>) { export function setupDirective(app: App<Element>) {
// 使 v-hasPerm 在所有组件中都可用 // 使 v-hasPerm 在所有组件中都可用
} }

View File

@ -1,26 +1,27 @@
<script lang="ts" setup></script> <script lang="ts" setup></script>
<template> <template>
<div class="w-full flex items-center justify-center flex-wrap gap-x-4 text-4xl p-2 mt-4"> <div class="w-full flex items-center justify-center flex-wrap gap-x-4 text-4xl p-2 mt-4">
<div class="w-full flex items-center justify-center mb-4"> <div class="w-full flex items-center justify-center mb-4">
<button>清除默认样式的按钮</button> <button>清除默认样式的按钮</button>
</div> <div class="w-[100px] h-[100px] bg-green" />
<div class="i-ph-anchor-simple-thin" /> </div>
<!-- 来自 Phosphor 图标的基本锚点图标 --> <div class="i-ph-anchor-simple-thin" />
<div class="i-ph-anchor-simple-thin" /> <!-- 来自 Phosphor 图标的基本锚点图标 -->
<!-- 来自 Material Design Icons 的一个橙色闹钟 --> <div class="i-ph-anchor-simple-thin" />
<div class="i-mdi-alarm text-orange-400" /> <!-- 来自 Material Design Icons 的一个橙色闹钟 -->
<!-- 一个大尺寸的 Vue 标志 --> <div class="i-mdi-alarm text-orange-400" />
<div class="i-logos-vue text-3xl" /> <!-- 一个大尺寸的 Vue 标志 -->
<!-- 太阳在亮模式月亮在暗模式来自 Carbon --> <div class="i-logos-vue text-3xl" />
<button class="i-carbon-sun dark:i-carbon-moon" /> <!-- 太阳在亮模式月亮在暗模式来自 Carbon -->
<!-- Twemoji 笑脸悬停时变成流泪表情 --> <button class="i-carbon-sun dark:i-carbon-moon" />
<div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" /> <!-- Twemoji 笑脸悬停时变成流泪表情 -->
<div class="i-vscode-icons:file-type-light-pnpm" /> <div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" />
<div class="i-vscode-icons:file-type-light-pnpm?mask text-red-300" /> <div class="i-vscode-icons:file-type-light-pnpm" />
<span class="i-ic:baseline-16mp" /> <div class="i-vscode-icons:file-type-light-pnpm?mask text-red-300" />
<span class="i-vscode-icons:file-type-java" /> <span class="i-ic:baseline-16mp" />
</div> <span class="i-vscode-icons:file-type-java" />
</div>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -1,9 +1,12 @@
import { createApp } from 'vue';
import 'animate.css'; import 'animate.css';
import App from './App.vue';
import plugins from '@/plugins';
import '@unocss/reset/tailwind-compat.css'; import '@unocss/reset/tailwind-compat.css';
import 'uno.css'; import 'uno.css';
import 'virtual:unocss-devtools'; import 'virtual:unocss-devtools';
import { createApp } from 'vue';
import plugins from '@/plugins';
import App from './App.vue';
createApp(App).use(plugins).mount('#app'); createApp(App).use(plugins).mount('#app');

View File

@ -1,15 +1,16 @@
import type { App } from 'vue'; import type { App } from 'vue';
import { setupDirective } from '@/directive'; import { setupDirective } from '@/directive';
import { setUpRouter } from '@/router'; import { setUpRouter } from '@/router';
import { setupStore } from '@/store'; import { setupStore } from '@/store';
export default { export default {
install(app: App<Element>) { install(app: App<Element>) {
// 设置路由 // 设置路由
setUpRouter(app); setUpRouter(app);
// 设置状态管理 // 设置状态管理
setupStore(app); setupStore(app);
// 设置指令 // 设置指令
setupDirective(app); setupDirective(app);
}, },
}; };

View File

@ -5,57 +5,57 @@ export const Layout = () => import('@/layout/index.vue');
// 静态路由 // 静态路由
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
path: '/redirect', path: '/redirect',
component: Layout, component: Layout,
meta: { hidden: true }, meta: { hidden: true },
children: [ children: [
{ {
path: '/redirect/:path(.*)', path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index.vue'), component: () => import('@/views/redirect/index.vue'),
}, },
], ],
}, },
{ {
path: '/', path: '/',
name: '/', name: '/',
component: Layout, component: Layout,
// redirect: '/dashboard', // redirect: '/dashboard',
children: [ children: [
// { // {
// path: 'dashboard', // path: 'dashboard',
// component: () => import('@/views/index.vue'), // component: () => import('@/views/index.vue'),
// // 用于 keep-alive 功能,需要与 SFC 中自动推导或显式声明的组件名称一致 // // 用于 keep-alive 功能,需要与 SFC 中自动推导或显式声明的组件名称一致
// // 参考文档: https://cn.vuejs.org/guide/built-ins/keep-alive.html#include-exclude // // 参考文档: https://cn.vuejs.org/guide/built-ins/keep-alive.html#include-exclude
// name: 'Dashboard', // name: 'Dashboard',
// meta: { // meta: {
// title: 'dashboard', // title: 'dashboard',
// icon: 'homepage', // icon: 'homepage',
// affix: true, // affix: true,
// keepAlive: true, // keepAlive: true,
// }, // },
// }, // },
], ],
}, },
{ {
path: '/404', path: '/404',
component: () => import('@/views/error-page/404.vue'), component: () => import('@/views/error-page/404.vue'),
meta: { hidden: true }, meta: { hidden: true },
}, },
]; ];
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes, routes,
scrollBehavior: () => ({ x: 0, y: 0 }), scrollBehavior: () => ({ x: 0, y: 0 }),
}); });
/** 全局注册 router */ /** 全局注册 router */
export const setUpRouter = (app: App<Element>) => { export const setUpRouter = (app: App<Element>) => {
app.use(router); app.use(router);
}; };
/** 重置路由 */ /** 重置路由 */
export const resetRouter = () => { export const resetRouter = () => {
router.replace({ path: '/' }).then(); router.replace({ path: '/' }).then();
}; };
export default router; export default router;

View File

@ -1,9 +1,9 @@
import type { App } from 'vue';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
import type { App } from 'vue';
const store = createPinia(); const store = createPinia();
// 全局注册 store // 全局注册 store
export function setupStore(app: App<Element>) { export function setupStore(app: App<Element>) {
app.use(store); app.use(store);
} }

21
src/types/global.d.ts vendored
View File

@ -1,12 +1,13 @@
declare global { declare global {
/* 环境便配置 */ /* 环境便配置 */
declare interface ViteEnv { declare interface ViteEnv {
VITE_APP_TITLE: string; VITE_APP_TITLE: string;
VITE_PORT: number; VITE_PORT: number;
VITE_PUBLIC_PATH: string; VITE_PUBLIC_PATH: string;
VITE_APP_URL: string; VITE_APP_URL: string;
VITE_STRICT_PORT: boolean; VITE_STRICT_PORT: boolean;
VITE_CDN: boolean; VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN: boolean;
VITE_COMPRESSION: string; VITE_CDN: boolean;
} VITE_COMPRESSION: string;
}
} }

View File

@ -1,18 +1,19 @@
import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
import NProgress from 'nprogress';
// 进度条 // 进度条
NProgress.configure({ NProgress.configure({
// 动画方式 // 动画方式
easing: 'ease', easing: 'ease',
// 递增进度条的速度 // 递增进度条的速度
speed: 500, speed: 500,
// 是否显示加载ico // 是否显示加载ico
showSpinner: false, showSpinner: false,
// 自动递增间隔 // 自动递增间隔
trickleSpeed: 200, trickleSpeed: 200,
// 初始化时的最小百分比 // 初始化时的最小百分比
minimum: 0.3, minimum: 0.3,
}); });
export default NProgress; export default NProgress;

View File

@ -1,70 +1,71 @@
import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios'; import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
import { useUserStoreHook } from '@/store/modules/user';
import { ResultEnum } from '@/enums/ResultEnum';
import { TOKEN_KEY } from '@/enums/CacheEnum';
import qs from 'qs'; import qs from 'qs';
import { TOKEN_KEY } from '@/enums/CacheEnum';
import { ResultEnum } from '@/enums/ResultEnum';
import { useUserStoreHook } from '@/store/modules/user';
// 创建 axios 实例 // 创建 axios 实例
const service = axios.create({ const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000, timeout: 50000,
headers: { 'Content-Type': 'application/json;charset=utf-8' }, headers: { 'Content-Type': 'application/json;charset=utf-8' },
paramsSerializer: params => { paramsSerializer: (params) => {
return qs.stringify(params); return qs.stringify(params);
}, },
}); });
// 请求拦截器 // 请求拦截器
service.interceptors.request.use( service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
const accessToken = localStorage.getItem(TOKEN_KEY); const accessToken = localStorage.getItem(TOKEN_KEY);
if (accessToken) { if (accessToken) {
config.headers.Authorization = accessToken; config.headers.Authorization = accessToken;
} }
return config; return config;
}, },
(error: any) => { (error: any) => {
return Promise.reject(error); return Promise.reject(error);
}, }
); );
// 响应拦截器 // 响应拦截器
service.interceptors.response.use( service.interceptors.response.use(
(response: AxiosResponse) => { (response: AxiosResponse) => {
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer', 如果是,直接返回响应对象 // 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer', 如果是,直接返回响应对象
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') { if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
return response; return response;
} }
const { code, data, msg } = response.data; const { code, data, msg } = response.data;
if (code === ResultEnum.SUCCESS) { if (code === ResultEnum.SUCCESS) {
return data; return data;
} }
ElMessage.error(msg || '系统出错'); ElMessage.error(msg || '系统出错');
return Promise.reject(new Error(msg || 'Error')); return Promise.reject(new Error(msg || 'Error'));
}, },
(error: any) => { (error: any) => {
// 异常处理 // 异常处理
if (error.response.data) { if (error.response.data) {
const { code, msg } = error.response.data; const { code, msg } = error.response.data;
if (code === ResultEnum.TOKEN_INVALID) { if (code === ResultEnum.TOKEN_INVALID) {
ElNotification({ ElNotification({
title: '提示', title: '提示',
message: '您的会话已过期,请重新登录', message: '您的会话已过期,请重新登录',
type: 'info', type: 'info',
}); });
useUserStoreHook() useUserStoreHook()
.resetToken() .resetToken()
.then(() => { .then(() => {
location.reload(); location.reload();
}); });
} else { } else {
ElMessage.error(msg || '系统出错'); ElMessage.error(msg || '系统出错');
} }
} }
return Promise.reject(error.message); return Promise.reject(error.message);
}, }
); );
// 导出 axios 实例 // 导出 axios 实例

View File

@ -5,228 +5,233 @@ const router = useRouter();
</script> </script>
<template> <template>
<div class="page-container"> <div class="page-container">
<div class="pic-404"> <div class="pic-404">
<img alt="404" class="pic-404__parent" src="@/assets/images/404.png" /> <img alt="404" class="pic-404__parent" src="@/assets/images/404.png" />
<img alt="404" class="pic-404__child left" src="@/assets/images/404_cloud.png" /> <img alt="404" class="pic-404__child left" src="@/assets/images/404_cloud.png" />
<img alt="404" class="pic-404__child mid" src="@/assets/images/404_cloud.png" /> <img alt="404" class="pic-404__child mid" src="@/assets/images/404_cloud.png" />
<img alt="404" class="pic-404__child right" src="@/assets/images/404_cloud.png" /> <img alt="404" class="pic-404__child right" src="@/assets/images/404_cloud.png" />
</div> </div>
<div class="bullshit"> <div class="bullshit">
<div class="bullshit__oops">OOPS!</div> <div class="bullshit__oops">OOPS!</div>
<div class="bullshit__info"> <div class="bullshit__info">
All rights reserved All rights reserved
<a href="https://wallstreetcn.com" style="color: #20a0ff" target="_blank">wallstreetcn</a> <a href="https://wallstreetcn.com" style="color: #20a0ff" target="_blank">wallstreetcn</a>
</div> </div>
<div class="bullshit__headline">The webmaster said that you can not enter this page...</div> <div class="bullshit__headline">The webmaster said that you can not enter this page...</div>
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div> <div class="bullshit__info">
<a class="bullshit__return-home" href="/" @click.prevent="router.replace('/')"> Back to home </a> Please check that the URL you entered is correct, or click the button below to return to the
</div> homepage.
</div> </div>
<a class="bullshit__return-home" href="/" @click.prevent="router.replace('/')">
Back to home
</a>
</div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.page-container { .page-container {
display: flex; display: flex;
padding: 100px; padding: 100px;
.pic-404 { .pic-404 {
width: 600px; width: 600px;
overflow: hidden; overflow: hidden;
&__parent { &__parent {
width: 100%; width: 100%;
} }
&__child { &__child {
&.left { &.left {
top: 17px; top: 17px;
left: 220px; left: 220px;
width: 80px; width: 80px;
opacity: 0; opacity: 0;
animation-name: cloudLeft; animation-name: cloudLeft;
animation-duration: 2s; animation-duration: 2s;
animation-timing-function: linear; animation-timing-function: linear;
animation-delay: 1s; animation-delay: 1s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
&.mid { &.mid {
top: 10px; top: 10px;
left: 420px; left: 420px;
width: 46px; width: 46px;
opacity: 0; opacity: 0;
animation-name: cloudMid; animation-name: cloudMid;
animation-duration: 2s; animation-duration: 2s;
animation-timing-function: linear; animation-timing-function: linear;
animation-delay: 1.2s; animation-delay: 1.2s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
&.right { &.right {
top: 100px; top: 100px;
left: 500px; left: 500px;
width: 62px; width: 62px;
opacity: 0; opacity: 0;
animation-name: cloudRight; animation-name: cloudRight;
animation-duration: 2s; animation-duration: 2s;
animation-timing-function: linear; animation-timing-function: linear;
animation-delay: 1s; animation-delay: 1s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
@keyframes cloudLeft { @keyframes cloudLeft {
0% { 0% {
top: 17px; top: 17px;
left: 220px; left: 220px;
opacity: 0; opacity: 0;
} }
20% { 20% {
top: 33px; top: 33px;
left: 188px; left: 188px;
opacity: 1; opacity: 1;
} }
80% { 80% {
top: 81px; top: 81px;
left: 92px; left: 92px;
opacity: 1; opacity: 1;
} }
100% { 100% {
top: 97px; top: 97px;
left: 60px; left: 60px;
opacity: 0; opacity: 0;
} }
} }
@keyframes cloudMid { @keyframes cloudMid {
0% { 0% {
top: 10px; top: 10px;
left: 420px; left: 420px;
opacity: 0; opacity: 0;
} }
20% { 20% {
top: 40px; top: 40px;
left: 360px; left: 360px;
opacity: 1; opacity: 1;
} }
70% { 70% {
top: 130px; top: 130px;
left: 180px; left: 180px;
opacity: 1; opacity: 1;
} }
100% { 100% {
top: 160px; top: 160px;
left: 120px; left: 120px;
opacity: 0; opacity: 0;
} }
} }
@keyframes cloudRight { @keyframes cloudRight {
0% { 0% {
top: 100px; top: 100px;
left: 500px; left: 500px;
opacity: 0; opacity: 0;
} }
20% { 20% {
top: 120px; top: 120px;
left: 460px; left: 460px;
opacity: 1; opacity: 1;
} }
80% { 80% {
top: 180px; top: 180px;
left: 340px; left: 340px;
opacity: 1; opacity: 1;
} }
100% { 100% {
top: 200px; top: 200px;
left: 300px; left: 300px;
opacity: 0; opacity: 0;
} }
} }
} }
} }
.bullshit { .bullshit {
width: 300px; width: 300px;
padding: 30px 0; padding: 30px 0;
overflow: hidden; overflow: hidden;
&__oops { &__oops {
margin-bottom: 20px; margin-bottom: 20px;
font-size: 32px; font-size: 32px;
font-weight: bold; font-weight: bold;
line-height: 40px; line-height: 40px;
color: #1482f0; color: #1482f0;
opacity: 0; opacity: 0;
animation-name: slideUp; animation-name: slideUp;
animation-duration: 0.5s; animation-duration: 0.5s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
&__headline { &__headline {
margin-bottom: 10px; margin-bottom: 10px;
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
line-height: 24px; line-height: 24px;
color: #222; color: #222;
opacity: 0; opacity: 0;
animation-name: slideUp; animation-name: slideUp;
animation-duration: 0.5s; animation-duration: 0.5s;
animation-delay: 0.1s; animation-delay: 0.1s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
&__info { &__info {
margin-bottom: 30px; margin-bottom: 30px;
font-size: 13px; font-size: 13px;
line-height: 21px; line-height: 21px;
color: grey; color: grey;
opacity: 0; opacity: 0;
animation-name: slideUp; animation-name: slideUp;
animation-duration: 0.5s; animation-duration: 0.5s;
animation-delay: 0.2s; animation-delay: 0.2s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
&__return-home { &__return-home {
display: block; display: block;
float: left; float: left;
width: 110px; width: 110px;
height: 36px; height: 36px;
font-size: 14px; font-size: 14px;
line-height: 36px; line-height: 36px;
color: #fff; color: #fff;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
background: #1482f0; background: #1482f0;
border-radius: 100px; border-radius: 100px;
opacity: 0; opacity: 0;
animation-name: slideUp; animation-name: slideUp;
animation-duration: 0.5s; animation-duration: 0.5s;
animation-delay: 0.3s; animation-delay: 0.3s;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
@keyframes slideUp { @keyframes slideUp {
0% { 0% {
opacity: 0; opacity: 0;
transform: translateY(60px); transform: translateY(60px);
} }
100% { 100% {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
} }
} }
} }
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div /> <div />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -1,51 +1,51 @@
module.exports = { module.exports = {
// 继承推荐规范配置 // 继承推荐规范配置
extends: [ extends: [
'stylelint-config-standard', 'stylelint-config-standard',
'stylelint-config-recommended-scss', 'stylelint-config-recommended-scss',
'stylelint-config-recommended-vue/scss', 'stylelint-config-recommended-vue/scss',
'stylelint-config-html/vue', 'stylelint-config-html/vue',
'stylelint-config-recess-order', 'stylelint-config-recess-order',
], ],
// 指定不同文件对应的解析器 // 指定不同文件对应的解析器
overrides: [ overrides: [
{ {
files: ['**/*.{vue,html}'], files: ['**/*.{vue,html}'],
customSyntax: 'postcss-html', customSyntax: 'postcss-html',
}, },
{ {
files: ['**/*.{css,scss}'], files: ['**/*.{css,scss}'],
customSyntax: 'postcss-scss', customSyntax: 'postcss-scss',
}, },
], ],
// 自定义规则 // 自定义规则
rules: { rules: {
'import-notation': 'string', // 指定导入CSS文件的方式("string"|"url") 'import-notation': 'string', // 指定导入CSS文件的方式("string"|"url")
'selector-class-pattern': null, // 选择器类名命名规则 'selector-class-pattern': null, // 选择器类名命名规则
'custom-property-pattern': null, // 自定义属性命名规则 'custom-property-pattern': null, // 自定义属性命名规则
'keyframes-name-pattern': null, // 动画帧节点样式命名规则 'keyframes-name-pattern': null, // 动画帧节点样式命名规则
'no-descending-specificity': null, // 允许无降序特异性 'no-descending-specificity': null, // 允许无降序特异性
'no-empty-source': null, // 允许空样式 'no-empty-source': null, // 允许空样式
// 允许 global 、export 、deep伪类 // 允许 global 、export 、deep伪类
'selector-pseudo-class-no-unknown': [ 'selector-pseudo-class-no-unknown': [
true, true,
{ {
ignorePseudoClasses: ['global', 'export', 'deep'], ignorePseudoClasses: ['global', 'export', 'deep'],
}, },
], ],
// 允许未知属性 // 允许未知属性
'property-no-unknown': [ 'property-no-unknown': [
true, true,
{ {
ignoreProperties: [], ignoreProperties: [],
}, },
], ],
// 允许未知规则 // 允许未知规则
'at-rule-no-unknown': [ 'at-rule-no-unknown': [
true, true,
{ {
ignoreAtRules: ['apply', 'use', 'forward'], ignoreAtRules: ['apply', 'use', 'forward'],
}, },
], ],
}, },
}; };

View File

@ -1,33 +1,36 @@
import { import {
defineConfig, defineConfig,
presetAttributify, presetAttributify,
presetIcons, presetIcons,
presetTypography, presetTypography,
presetUno, presetUno,
presetWebFonts, presetWebFonts,
transformerDirectives, transformerDirectives,
transformerVariantGroup, transformerVariantGroup,
} from 'unocss'; } from 'unocss';
export default defineConfig({ export default defineConfig({
shortcuts: [ shortcuts: {
// ... 'flex-center': 'flex justify-center items-center',
], 'flex-x-between': 'flex items-center justify-between',
theme: { 'flex-x-around': 'flex items-center justify-around',
colors: { 'flex-y-center': 'flex flex-col flex-wrap justify-center items-center',
// ... },
}, theme: {
}, colors: {
presets: [ // ...
presetUno(), },
presetAttributify(), },
presetIcons(), presets: [
presetTypography(), presetUno(),
presetWebFonts({ presetAttributify(),
fonts: { presetIcons(),
// ... presetTypography(),
}, presetWebFonts({
}), fonts: {
], // ...
transformers: [transformerDirectives(), transformerVariantGroup()], },
}),
],
transformers: [transformerDirectives(), transformerVariantGroup()],
}); });

View File

@ -1,33 +1,36 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import { buildEnv } from './build/buildEnv';
import { css } from './build/css';
import { define } from './build/define';
import { exclude, include } from './build/optimize';
import { plugins } from './build/plugins'; import { plugins } from './build/plugins';
import { resolve } from './build/resolve'; import { resolve } from './build/resolve';
import { buildEnv } from './build/buildEnv';
import { define } from './build/define';
import { root, wrapperEnv } from './build/utils';
import { server } from './build/server'; import { server } from './build/server';
import { exclude, include } from './build/optimize'; import { root, wrapperEnv } from './build/utils';
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => { export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => {
const env = wrapperEnv(mode, 'VITE'); const env = wrapperEnv(mode, 'VITE');
return { return {
root, root,
base: env.VITE_PUBLIC_PATH, base: env.VITE_PUBLIC_PATH,
define: define(), define: define(),
plugins: plugins(mode), plugins: plugins(mode),
resolve: resolve(), resolve: resolve(),
esbuild: { esbuild: {
jsxFactory: 'h', jsxFactory: 'h',
jsxFragment: 'Fragment', jsxFragment: 'Fragment',
jsxInject: "import { h } from 'vue';", jsxInject: "import { h } from 'vue';",
}, },
logLevel: 'info', logLevel: 'info',
// 设为 false 可以避免 Vite 清屏而错过在终端中打印某些关键信息 css: css(mode),
clearScreen: false, // 设为 false 可以避免 Vite 清屏而错过在终端中打印某些关键信息
build: buildEnv(), clearScreen: false,
server: server(mode), build: buildEnv(),
preview: server(mode), server: server(mode),
optimizeDeps: { include, exclude }, preview: server(mode),
}; optimizeDeps: { include, exclude },
};
}); });