🔧工具(deps): build framework

This commit is contained in:
2024-03-21 00:28:41 +08:00
parent dda579424c
commit 1594b21688
17 changed files with 2273 additions and 315 deletions

10
.env
View File

@@ -1,4 +1,10 @@
# 生产环境配置 # 生产环境配置
NODE_ENV= 'production' VITE_NODE_ENV= 'production'
# 生产环境 # 生产环境
VUE_APP_BASE_API = '/api' VITE_APP_BASE_API = '/api'
# 页面 title 前缀
VITE_APP_TITLE=生产环境
# 网络请求公用地址
VITE_API_BASE_URL=''

View File

@@ -1,8 +1,11 @@
# 开发环境配置 # 开发环境配置
NODE_ENV= 'development' VITE_NODE_ENV='development'
# 开发环境 # 开发环境
VUE_APP_BASE_API = '/dev-api' VITE_APP_BASE_API='/dev-api'
# 路由懒加载 # 页面 title 前缀
VUE_CLI_BABEL_TRANSPILE_MODULES = true VITE_APP_TITLE=开发环境
# 网络请求公用地址
VITE_API_BASE_URL=''

View File

@@ -1,4 +1,10 @@
# 生产环境配置 # 生产环境配置
NODE_ENV= 'production' VITE_NODE_ENV= 'production'
# 生产环境 # 生产环境
VUE_APP_BASE_API = '/api' VITE_APP_BASE_API = '/api'
# 页面 title 前缀
VITE_APP_TITLE=生产环境
# 网络请求公用地址
VITE_API_BASE_URL=''

2
.npmrc
View File

@@ -1,3 +1,3 @@
registry=https://registry.npmmirror.com registry=https://registry.npmmirror.com
npm config set registry https://registry.npmmirror.com -g npm config set registry https://registry.npmmirror.com -g
shamefully-hoist = true

3
components.d.ts vendored
View File

@@ -7,8 +7,7 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
Echarts: typeof import('./src/components/echarts/ECharts.vue')['default'] ECharts: typeof import('./src/components/echarts/ECharts.vue')['default']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
SvgIcon: typeof import('./src/components/svgIcon/SvgIcon.vue')['default'] SvgIcon: typeof import('./src/components/svgIcon/SvgIcon.vue')['default']

View File

@@ -13,6 +13,7 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1", "@ant-design/icons-vue": "^7.0.1",
"@vueuse/core": "^10.9.0",
"ant-design-vue": "4.1.2", "ant-design-vue": "4.1.2",
"axios": "^1.6.8", "axios": "^1.6.8",
"core-js": "^3.36.1", "core-js": "^3.36.1",
@@ -22,15 +23,17 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-global-api": "^0.4.1",
"vue-i18n": "^9.10.2", "vue-i18n": "^9.10.2",
"vue-router": "4" "vue-router": "4"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.2.1", "@commitlint/cli": "^19.2.1",
"@commitlint/config-conventional": "^19.1.0", "@commitlint/config-conventional": "^19.1.0",
"@types/node": "^20.11.29", "@types/node": "^20.11.30",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1", "@typescript-eslint/parser": "^7.3.1",
@@ -48,15 +51,21 @@
"lint-staged": "^15.2.2", "lint-staged": "^15.2.2",
"postcss-pxtorem": "^6.1.0", "postcss-pxtorem": "^6.1.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"terser": "^5.29.2",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"unplugin-auto-import": "^0.17.5", "unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",
"vite": "^5.1.6", "vite": "^5.1.6",
"vite-plugin-imagemin": "^0.6.1",
"vue-tsc": "^1.8.27" "vue-tsc": "^1.8.27"
}, },
"config": { "config": {
"commitizen": { "commitizen": {
"path": "./node_modules/cz-customizable" "path": "./node_modules/cz-customizable"
} }
} },
"browserslist": [
"last 2 Chrome versions",
"last 2 Firefox versions"
]
} }

1910
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -30,13 +30,3 @@ const svgClass = computed(() => {
<use :xlink:href="iconName" /> <use :xlink:href="iconName" />
</svg> </svg>
</template> </template>
<style lang="less" scoped>
.svg-icon {
// svg 图标默认宽高,根据个人使用情况自行调整
width: 20px;
height: 20px;
fill: currentColor;
overflow: hidden;
}
</style>

7
src/interface/user/user.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
// 测试
export interface IUser {
token: string
userId: number
LOADING: false
userInfo: unknown
}

View File

@@ -0,0 +1 @@
# 布局

View File

@@ -10,10 +10,10 @@ import '@/polyfill/polyfill'
import '@/style/scroll-bar.less' import '@/style/scroll-bar.less'
// 国际化 // 国际化
import i18n from '../locales' import i18n from '../locales'
// svg 相关 // svg 相关
import 'virtual:svg-icons-register' import 'virtual:svg-icons-register'
import SvgIcon from './components/svgIcon/SvgIcon.vue' import SvgIcon from './components/svgIcon/SvgIcon.vue'
import 'vue-global-api'
const app = createApp(App) const app = createApp(App)
const store = createPinia() const store = createPinia()

View File

@@ -1,53 +1,33 @@
import {defineStore} from 'pinia' import { defineStore } from 'pinia'
// 加载storage模块获取token存储token import { ref } from 'vue'
import {getItem, setItem} from "@/utils/storage/storage"; import { IUser } from '@/interface/user/user'
const TOKEN_KEY: String = "X-Token"; export const useAuthStore = defineStore(
'user',
export const useAuthStore = defineStore('user', () => { () => {
const user = ref<any>({ const user = ref<IUser>()
token: getItem(TOKEN_KEY) ? getItem(TOKEN_KEY) : null,
userId: Number, function setUser(data: IUser) {
LOADING: false, user.value = data
userInfo: Object }
})
function getUser() {
function setUser(data: any) { return user.value
user.value = data }
setItem(TOKEN_KEY, user.value.token);
} function clearUser() {
user.value = void 0
function getUser() { }
return user.value
} return {
user,
function clearUser() { setUser,
user.value = { getUser,
token: null, clearUser
userId: null, }
isLogin: false, },
userInfo: {} {
} // 开启数据持久化
} persist: true
}
function showLoading() { )
user.value.LOADING = true;
}
function hideLoading() {
user.value.LOADING = false;
}
return {
user,
setUser,
getUser,
clearUser,
showLoading,
hideLoading
}
}, {
// 开启数据持久化
persist: true
}
)

View File

@@ -1,134 +1,132 @@
import axios from 'axios' import axios from 'axios'
import useStore from '@/store' import useStore from '@/store'
import * as process from 'process' import * as process from 'process'
// import { BASE_URL } from "@/config"; // console.log(BASE_URL)
const BaseURL: any = process.env['VITE_APP_BASE_API ']
// console.log(BASE_URL) // 数据返回的接口
const BaseURL: any = process.env.VUE_APP_BASE_API // 定义请求响应参数不含data
// 数据返回的接口 interface Result {
// 定义请求响应参数不含data code: number
interface Result { msg: string
code: number total?: number
msg: string }
total?: number
} // 请求响应参数包含data
interface ResultData<T = any> extends Result {
// 请求响应参数包含data data?: T
interface ResultData<T = any> extends Result { }
data?: T
} const URL: string = BaseURL
const URL: string = BaseURL enum RequestEnums {
TIMEOUT = 20000,
enum RequestEnums { OVERDUE = 600, // 登录失效
TIMEOUT = 20000, FAIL = 999, // 请求失败
OVERDUE = 600, // 登录失效 SUCCESS = 200 // 请求成功
FAIL = 999, // 请求失败 }
SUCCESS = 200 // 请求成功
} const config = {
// 默认地址
const config = { baseURL: URL as string,
// 默认地址 // 设置超时时间
baseURL: URL as string, timeout: RequestEnums.TIMEOUT as number,
// 设置超时时间 // 跨域时候允许携带凭证
timeout: RequestEnums.TIMEOUT as number, withCredentials: true
// 跨域时候允许携带凭证 }
withCredentials: true
} class RequestHttp {
// 定义成员变量并指定类型
class RequestHttp { service: any
// 定义成员变量并指定类型
service: any public constructor(config: any) {
// 实例化axios
public constructor(config: any) { this.service = axios.create(config)
// 实例化axios
this.service = axios.create(config) /**
* 请求拦截器
/** * 客户端发送请求 -> [请求拦截器] -> 服务器
* 请求拦截器 * token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中
* 客户端发送请求 -> [请求拦截器] -> 服务器 */
* token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中 this.service.interceptors.request.use(
*/ (config: any) => {
this.service.interceptors.request.use( const token = useStore().user.getUser()?.token
(config: any) => { if (token) {
const token = useStore().user.getUser().token return {
if (token) { ...config,
return { headers: {
...config, Authorization: token // 请求头中携带token信息
headers: { }
Authorization: token // 请求头中携带token信息 }
} }
} },
} (error: any) => {
}, // 请求报错
(error: any) => { Promise.reject(error)
// 请求报错 }
Promise.reject(error) )
}
) /**
* 响应拦截器
/** * 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
* 响应拦截器 */
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息 this.service.interceptors.response.use(
*/ (response: any) => {
this.service.interceptors.response.use( const { data } = response // 解构
(response: any) => { if (data.code === RequestEnums.OVERDUE) {
const { data } = response // 解构 // 登录信息失效应跳转到登录页面并清空本地的token
if (data.code === RequestEnums.OVERDUE) { // router.replace({
// 登录信息失效应跳转到登录页面并清空本地的token // path: '/login'
// router.replace({ // })
// path: '/login' return Promise.reject(data)
// }) }
return Promise.reject(data) // 全局错误信息拦截防止下载文件得时候返回数据流没有code直接报错
} if (data.code && data.code !== RequestEnums.SUCCESS) {
// 全局错误信息拦截防止下载文件得时候返回数据流没有code直接报错 return Promise.reject(data)
if (data.code && data.code !== RequestEnums.SUCCESS) { }
return Promise.reject(data) return data
} },
return data (error: any) => {
}, const { response } = error
(error: any) => { if (response) {
const { response } = error this.handleCode(response.status)
if (response) { }
this.handleCode(response.status) if (!window.navigator.onLine) {
} // 可以跳转到错误页面,也可以不做操作
if (!window.navigator.onLine) { // return router.replace({
// 可以跳转到错误页面,也可以不做操作 // path: '/404'
// return router.replace({ // });
// path: '/404' }
// }); }
} )
} }
)
} handleCode(code: number): void {
switch (code) {
handleCode(code: number): void { case 401:
switch (code) { break
case 401: default:
break break
default: }
break }
}
} // 常用方法封装
get<T>(url: string, params?: object): Promise<ResultData<T>> {
// 常用方法封装 return this.service.get(url, { params })
get<T>(url: string, params?: object): Promise<ResultData<T>> { }
return this.service.get(url, { params })
} post<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.post(url, params)
post<T>(url: string, params?: object): Promise<ResultData<T>> { }
return this.service.post(url, params)
} put<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.put(url, params)
put<T>(url: string, params?: object): Promise<ResultData<T>> { }
return this.service.put(url, params)
} delete<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.delete(url, { params })
delete<T>(url: string, params?: object): Promise<ResultData<T>> { }
return this.service.delete(url, { params }) }
}
} // 导出一个实例对象
export default new RequestHttp(config)
// 导出一个实例对象
export default new RequestHttp(config)

View File

@@ -0,0 +1,32 @@
// cookie.ts
/**
* cookie 浏览器缓存
*/
const domain = ''
// 时间单位为秒
export function setCookie(c_name: string, value: any, expire?: any) {
const date: any = new Date()
date.setSeconds(date.getSeconds() + expire)
document.cookie =
c_name + '=' + escape(value) + '; expires=' + date.toGMTString() + '; domain=' + domain + ';path=/'
}
export function getCookie(c_name: string) {
let c_start: number
let c_end: number
if (document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + '=')
if (c_start != -1) {
c_start = c_start + c_name.length + 1
c_end = document.cookie.indexOf(';', c_start)
if (c_end == -1) c_end = document.cookie.length
return unescape(document.cookie.substring(c_start, c_end))
}
}
return ''
}
export function delCookie(c_name: string) {
setCookie(c_name, '', -1)
}

21
src/vite-env.d.ts vendored
View File

@@ -1,7 +1,14 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '*.vue' { interface ImportMetaEnv {
import type { DefineComponent } from 'vue' readonly VITE_NODE_ENV: string //定义提示信息 数据是只读的无法被修改
const component: DefineComponent<{}, {}, any> readonly VITE_APP_BASE_API: string
export default component readonly VITE_APP_TITLE: string
} readonly VITE_API_BASE_URL: string
}
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"typeRoots": [ "typeRoots": [
"node_modules/@types", "node_modules/@types",
"src/types" "src/types",
], ],
"target": "ES2020", "target": "ES2020",
"useDefineForClassFields": true, "useDefineForClassFields": true,

View File

@@ -1,7 +1,7 @@
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import * as path from 'path' import * as path from 'path'
import legacyPlugin from '@vitejs/plugin-legacy' import legacy from '@vitejs/plugin-legacy'
// 自动导入vue中hook reactive ref等 // 自动导入vue中hook reactive ref等
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
@@ -15,61 +15,153 @@ import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import autoprefixer from 'autoprefixer' import autoprefixer from 'autoprefixer'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({ import viteCompression from 'vite-plugin-compression'
resolve: { import viteImagemin from 'vite-plugin-imagemin'
//设置别名
alias: {
'@': path.resolve(__dirname, 'src') export default defineConfig(({ mode, command }) => {
} const env = loadEnv(mode, process.cwd())
}, return {
css: { resolve: {
postcss: { //设置别名
plugins: [ alias: {
autoprefixer({ '@': path.resolve(__dirname, 'src')
overrideBrowserslist: ['Chrome > 40', 'ff > 31', 'ie 11'] }
}) },
] css: {
} postcss: {
}, plugins: [
plugins: [ autoprefixer({
vue(), overrideBrowserslist: ['Chrome > 40', 'ff > 31', 'ie 11']
// 修改 svg 相关配置 })
createSvgIconsPlugin({ ]
// 指定需要缓存的图标文件夹 }
iconDirs: [path.resolve(__dirname, './src/assets/svg')] },
}), esbuild: {
legacy({ // configure this value when the browser version of the development environment is lower
targets: ['cover 99.5%'] // minimum support es2015
}), // https://esbuild.github.io/api/#target
legacyPlugin({ target: 'es2015',
targets: ['chrome 52'], // 需要兼容的目标列表 include: /\.(ts|jsx|tsx)$/
additionalLegacyPolyfills: ['regenerator-runtime/runtime'] // 面向IE11时需要此插件 },
}), plugins: [
AutoImport({ vue(),
//安装两行后你会发现在组件中不用再导入refreactive等 viteImagemin({
imports: ['vue', 'vue-router'], gifsicle: { // gif图片压缩
dts: 'auto-import.d.ts', optimizationLevel: 3, // 选择1到3之间的优化级别
//ant-design-vue interlaced: false // 隔行扫描gif进行渐进式渲染
resolvers: [AntDesignVueResolver()] // colors: 2 // 将每个输出GIF中不同颜色的数量减少到num或更少。数字必须介于2和256之间。
}), },
Components({ optipng: { // png
//ant-design-vue optimizationLevel: 7 // 选择0到7之间的优化级别
resolvers: [AntDesignVueResolver({ importStyle: true, resolveIcons: true })] },
}) mozjpeg: {// jpeg
], quality: 20 // 压缩质量范围从0(最差)到100(最佳)。
optimizeDeps: { },
include: ['core-js'] pngquant: {// png
}, quality: [0.8, 0.9], // Min和max是介于0(最差)到1(最佳)之间的数字类似于JPEG。达到或超过最高质量所需的最少量的颜色。如果转换导致质量低于最低质量图像将不会被保存。
server: { speed: 4 // 压缩速度1(强力)到11(最快)
proxy: { },
'/dev-api': { svgo: { // svg压缩
//target是代理的目标路径 plugins: [
target: '', {
changeOrigin: true, //必须要开启跨域 name: 'removeViewBox'
rewrite: (path) => path.replace(/\/dev-api/, '') // 路径重写 },
{
name: 'removeEmptyAttrs',
active: false
}
]
}
}),
viteCompression({
verbose: true, // 是否在控制台中输出压缩结果
disable: false,
threshold: 10240, // 如果体积大于阈值将被压缩单位为b体积过小时请不要压缩以免适得其反
algorithm: 'gzip', // 压缩算法,可选['gzip'' brotliccompress ''deflate ''deflateRaw']
ext: '.gz',
deleteOriginFile: true // 源文件压缩后是否删除
}),
// 修改 svg 相关配置
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(__dirname, './src/assets/svg')]
}),
legacy({
renderLegacyChunks: true,
modernPolyfills: true,
targets: ['chrome 52'], // 需要兼容的目标列表
additionalLegacyPolyfills: ['regenerator-runtime/runtime'] // 面向IE11时需要此插件
}),
AutoImport({
//安装两行后你会发现在组件中不用再导入refreactive等
imports: ['vue', 'vue-router'],
dts: 'auto-import.d.ts',
//ant-design-vue
resolvers: [AntDesignVueResolver()]
}),
Components({
//ant-design-vue
resolvers: [AntDesignVueResolver({ importStyle: true, resolveIcons: true })]
})
],
optimizeDeps: {
include: ['core-js']
},
server: {
proxy: {
'/api': {
//target是代理的目标路径
target: env.VITE_API_BASE_URL,
changeOrigin: true, //必须要开启跨域
rewrite: (path) => path.replace(/\/api/, '') // 路径重写
}
}
},
build: {
// target: ['es2015'], // 设置最终构建的浏览器兼容目标
moduleResolution: 'node', // 决定如何处理模块。
outDir: 'dist', // 指定输出路径
assetsDir: 'assets', // 指定生成静态文件目录
assetsInlineLimit: '4096', // 小于此阈值的导入或引用资源将内联为 base64 编码
cssCodeSplit: true, // 启用 CSS 代码拆分
// cssTarget: '', // 允许用户为 CSS 的压缩设置一个不同的浏览器 target 与 build.target 一致
sourcemap: false, // 构建后是否生成 source map 文件
// lib: {}, // 构建为库
manifest: false, // 当设置为 true构建后将会生成 manifest.json 文件
ssrManifest: false, // 构建不生成 SSR 的 manifest 文件
ssr: undefined, // 生成面向 SSR 的构建
minify: 'terser', // 指定使用哪种混淆器
// 传递给 Terser 的更多 minify 选项
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
write: true, // 启用将构建后的文件写入磁盘
emptyOutDir: true, // 构建时清空该目录
brotliSize: true, // 启用 brotli 压缩大小报告
chunkSizeWarningLimit: 2000, // chunk 大小警告的限制
watch: null, // 设置为 {} 则会启用 rollup 的监听器
rollupOptions: { // 自定义底层的 Rollup 打包配置
output: {
chunkFileNames: 'js/[name]-[hash].js', // 引入文件名的名称
entryFileNames: 'js/[name]-[hash].js', // 包的入口文件名称
assetFileNames: '[ext]/[name]-[hash].[ext]' // 资源文件像 字体,图片等
}
}
},
output: {
// 最小化拆分包
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString()
}
} }
} }
} }
}) })