Compare commits

34 Commits

Author SHA1 Message Date
刘引
70e5753dcd update: 依赖更新 2025-12-27 14:30:04 +08:00
刘引
7e6921c8df perf: 模板优化 2025-12-27 14:27:33 +08:00
刘引
99e1c9b445 新增pinia持久化插件 2025-12-26 11:11:18 +08:00
刘引
aff235bbf4 update: 将ElementUI-Plus替换为TDesignNext 2025-12-26 10:58:42 +08:00
刘引
0f59cc6c87 feat: 添加vite-plugin-pwa插件以支持PWA功能 2025-10-15 10:48:17 +08:00
刘引
eb7312fb23 update: 修复version.json文件格式并添加通知组件样式 2025-10-15 09:56:10 +08:00
刘引
20428e2ac8 feat: 添加版本检测功能,包括构建时自动更新版本信息和运行时定期检查更新 2025-10-15 09:54:16 +08:00
刘引
fae1999a93 refactor: 重构store模块化结构,新增user模块并更新相关引用 2025-10-15 09:36:47 +08:00
刘引
64e8813f35 feat: 添加prettier代码格式化工具及相关配置 2025-10-15 09:24:45 +08:00
刘引
4b99518c16 remove: 移除oxlint静态代码检查工具及相关配置 2025-10-15 09:20:00 +08:00
刘引
4fdf48893c feat: 添加oxlint静态代码检查工具及相关配置 2025-10-15 09:10:42 +08:00
刘引
55d51efd20 update: 升级依赖版本,包括axios、dayjs、vue-router等核心库 2025-10-15 09:06:57 +08:00
刘引
fc76632d4d update: 升级 tdesign-vue-next 至 1.17.1 和 vue 至 3.5.22 2025-10-13 08:41:24 +08:00
刘引
2a38aed1f7 perf: 迁移项目至Vite构建工具,更新依赖和配置 2025-07-28 09:46:28 +08:00
刘引
11e617adc9 update: 修改注释以包含github信息 2025-03-19 14:28:45 +08:00
刘引
32051eb13f update: 修改注释以包含gitee信息 2025-03-19 14:23:40 +08:00
刘引
2e61e255a2 update:注释更改 2025-03-19 14:10:25 +08:00
刘引
499796afbe feat:注释增加 2025-03-19 14:09:51 +08:00
刘引
15617f45b9 fix:去除app.vue多余注释 2025-03-19 14:07:07 +08:00
刘引
ceac848fd2 fix:去除多余注释 2025-03-19 13:50:57 +08:00
刘引
9b35ac6474 feat: 添加环境配置文件,更新API基础地址 2025-03-19 13:44:54 +08:00
刘引
4368d0a88d perf:删除多余文件 2025-01-07 15:43:14 +08:00
刘引
d5090f66e2 perf:基础架构升级 2024-12-03 13:56:17 +08:00
刘引
24a01c9bb0 fix:ts类型新增node 2024-09-03 17:23:10 +08:00
刘引
38e65e9917 fix:解决vue ts无法识别导入 2024-09-03 17:10:35 +08:00
刘引
d6186e73ad feat:依赖版本更新,解决下载启动报错 2024-09-03 17:03:17 +08:00
刘引
690331f88c feat:依赖版本升级补充 2024-05-27 16:42:14 +08:00
刘引
5e679bdde8 feat:依赖包升级补充 2024-05-27 16:37:56 +08:00
刘引
2732efbefb 依赖包升级 2024-05-27 16:37:43 +08:00
6efc8b908b 多余代码去除 2023-12-13 17:34:30 +08:00
935e7f61b3 空文件新增 2023-12-13 17:31:52 +08:00
26a6d313e3 模板请求处理 2023-12-13 17:27:24 +08:00
dfa6146840 打包命令更改 2023-12-11 11:34:11 +08:00
c76c4329f9 zhCn引入 2023-12-07 15:03:51 +08:00
42 changed files with 9671 additions and 4803 deletions

7
.env.dev Normal file
View File

@@ -0,0 +1,7 @@
# base api
VITE_BASE_API = '/api'

4
.env.prod Normal file
View File

@@ -0,0 +1,4 @@
# base api
VITE_BASE_API = '/prod'

4
.env.test Normal file
View File

@@ -0,0 +1,4 @@
# base api
VITE_BASE_API = '/test'

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

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AngularCliAddDependency" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
<inspection_tool class="AngularInaccessibleComponentMemberInAotMode" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
<inspection_tool class="AngularInsecureBindingToEvent" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
<inspection_tool class="AngularInvalidI18nAttribute" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
<inspection_tool class="AngularNgOptimizedImage" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
</profile>
</component>

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

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

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/vue-frame.iml" filepath="$PROJECT_DIR$/.idea/vue-frame.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>

9
.idea/vue-frame.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

12
.prettierrc Normal file
View File

@@ -0,0 +1,12 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"vueIndentScriptAndStyle": false,
"endOfLine": "lf"
}

View File

@@ -1 +1 @@
# Vue 3 + Typescript + Vite + ElementUI-Plus + DayJs + Pinia # Vue 3 + Typescript + Vite + TDdesign + DayJs + Pinia

3
auto-imports.d.ts vendored
View File

@@ -3,7 +3,8 @@
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
// biome-ignore lint: disable
export {} export {}
declare global { declare global {
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
} }

3
components.d.ts vendored
View File

@@ -1,10 +1,11 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */
// @ts-nocheck // @ts-nocheck
// Generated by unplugin-vue-components // Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {} export {}
/* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
Foot: typeof import('./src/components/Foot.vue')['default'] Foot: typeof import('./src/components/Foot.vue')['default']

1
dev-dist/registerSW.js Normal file
View File

@@ -0,0 +1 @@
if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })

92
dev-dist/sw.js Normal file
View File

@@ -0,0 +1,92 @@
/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// If the loader is already loaded, just stop.
if (!self.define) {
let registry = {};
// Used for `eval` and `importScripts` where we can't get script URL by other means.
// In both cases, it's safe to use a global var because those functions are synchronous.
let nextDefineUri;
const singleRequire = (uri, parentUri) => {
uri = new URL(uri + ".js", parentUri).href;
return registry[uri] || (
new Promise(resolve => {
if ("document" in self) {
const script = document.createElement("script");
script.src = uri;
script.onload = resolve;
document.head.appendChild(script);
} else {
nextDefineUri = uri;
importScripts(uri);
resolve();
}
})
.then(() => {
let promise = registry[uri];
if (!promise) {
throw new Error(`Module ${uri} didnt register its module`);
}
return promise;
})
);
};
self.define = (depsNames, factory) => {
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
if (registry[uri]) {
// Module is already loading or loaded.
return;
}
let exports = {};
const require = depUri => singleRequire(depUri, uri);
const specialDeps = {
module: { uri },
exports,
require
};
registry[uri] = Promise.all(depsNames.map(
depName => specialDeps[depName] || require(depName)
)).then(deps => {
factory(...deps);
return exports;
});
};
}
define(['./workbox-86c9b217'], (function (workbox) { 'use strict';
self.skipWaiting();
workbox.clientsClaim();
/**
* The precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
workbox.precacheAndRoute([{
"url": "registerSW.js",
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.o5uo4dculfk"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
allowlist: [/^\/$/]
}));
}));

3391
dev-dist/workbox-86c9b217.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta
<title>Vite App</title> name="viewport"
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<title>Vue模板</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

3484
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,32 @@
{ {
"name": "vue-frame", "name": "vue-frame",
"version": "0.0.0", "version": "0.0.0",
"type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "tsx src/utils/update-version.ts && vite build --mode release",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"axios": "^1.6.2", "axios": "^1.12.2",
"dayjs": "^1.11.10", "dayjs": "^1.11.18",
"element-plus": "^2.4.3",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"pinia": "^2.1.7", "pinia": "^3.0.4",
"qs": "^6.11.2", "pinia-plugin-persistedstate": "^4.7.1",
"vue": "^3.3.10", "qs": "^6.14.0",
"vue-router": "^4.2.5" "tdesign-vue-next": "^1.17.1",
"vite-plugin-pwa": "^1.1.0",
"vue": "^3.5.26",
"vue-router": "^4.6.4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.17.1", "@types/node": "^24.7.2",
"@vitejs/plugin-vue": "^4.5.1", "@vitejs/plugin-vue": "^6.0.1",
"sass": "^1.69.5", "sass": "^1.93.2",
"typescript": "^4.9.5", "typescript": "^5.1.6",
"unplugin-auto-import": "^0.17.2", "unplugin-auto-import": "^20.2.0",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^29.1.0",
"vite": "^5.0.6", "vite": "^7.3.0",
"vue-tsc": "^0.39.5" "vue-tsc": "^3.2.1"
} }
} }

6072
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

1
public/config.ts Normal file
View File

@@ -0,0 +1 @@

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

5
public/version.json Normal file
View File

@@ -0,0 +1,5 @@
{
"version": "2025-12-27 10:25:12",
"buildTime": "2025-12-27T02:25:12.719Z",
"environment": "production"
}

View File

@@ -1,18 +1,46 @@
<!-- <!-- 基于rspack打包github&gitee -->
* @Description: {{ByRuin}}
* @Version: 2.0
* @Author: Ruin 🍭
* @Date: 2022-01-25 16:22:24
* @LastEditors: 刘引 liu.yin.work@foxmail.com
* @LastEditTime: 2023-08-01 15:25:13
-->
<template> <template>
<router-view></router-view> <router-view></router-view>
<footer class="app-version">
<span>版本{{ versionText }}</span>
</footer>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
import { onMounted, ref } from 'vue'
const versionText = ref('未知')
onMounted(async () => {
try {
const res = await fetch('/version.json', { cache: 'no-store' })
if (res.ok) {
const data = await res.json()
const v = typeof data?.version === 'string' && data.version.trim() ? data.version.trim() : ''
versionText.value = v || '未知'
} else {
versionText.value = '未知'
}
} catch (e) {
versionText.value = '未知'
}
})
</script>
<style> <style>
@import './assets/scss/index.scss'; @import './assets/scss/index.scss';
.app-version {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
justify-content: center;
padding: 16px 8px;
font-size: 12px;
color: #333;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: saturate(180%) blur(6px);
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
</style> </style>

View File

@@ -1,81 +0,0 @@
/*
* @Description: {{ByRuin}}
* @Version: 2.0
* @Author: Ruin 🍭
* @Date: 2022-01-25 17:48:13
* @LastEditors: 刘引
* @LastEditTime: 2022-01-25 17:52:39
*/
/* 1.引入文件 */
import axios from 'axios' //引入 axios库
// @ts-ignore
import qs from 'qs' //引入 node中自带的qs模块数据格式转换
/* 2.全局默认配置 */
let baseURL
let process: any
// 判断开发环境(一般用于本地代理)
if (process.env.NODE_ENV === 'development') {
// 开发环境
baseURL = 'http://www.liulongbin.top:3006' // 你设置的本地代理请求(跨域代理),下文会详细介绍怎么进行跨域代理
} else {
// 编译环境
if (process.env.type === 'test') {
// 测试环境
baseURL = 'http://www.liulongbin.top:3006'
} else {
// 正式环境
baseURL = 'http://www.liulongbin.top:3006'
}
}
// 配置axios的属性
axios.defaults.timeout = 6000 // 请求超时时间1分钟
axios.defaults.baseURL = baseURL // 你的接口地址
axios.defaults.responseType = 'json'
axios.defaults.withCredentials = false //是否允许带cookie这些
/*你也可以创建一个实例,然后在实例中配置相关属性,此方法和上面的方法一样,写法不同,怎么用随个人
*喜好,我比较喜欢用这种方法,如下:
*/
const Axios = axios.create({
baseURL: baseURL, // 后台服务地址
timeout: 60000, // 请求超时时间1分钟
responseType: 'json',
withCredentials: false // 是否允许带cookie这些
})
/* 3.设置拦截器 */
/*如果不是用创建实例的方式配置那么下面的Axios都要换成axios,也就是文件开头你用import引入axios
时定义的变量*/
Axios.interceptors.request.use(
(config) => {
//发送请求前进行拦截
// 可在此处配置请求头信息
// config.headers["appkey"] = "...";
// config.headers["token"] = "...";
if (config.method == 'post') {
/*数据转换: axios post方式默认是json格式提交数据如果使用application/x-www-form-urlencoded数据格式提交要用qs.stringify()进行转换,个人建议不在拦截器中全局配置,因为不够灵活,还有一点是,如果
设置了重新请求的配置那么重新请求时请求体中的config里面的传参就会被再次进行qs.stringify()转
换,会使得参数丢失,造成请求失败。*/
config.data = qs.stringify(config.data)
}
return config
},
(error) => {
//console.log("错误的传参", 'fail');
return Promise.reject(error)
}
)
Axios.interceptors.response.use(
(res) => {
//请求响应后拦截
if (res.status == 200) {
// 对响应数据做些事
//alert("提交成功")
return Promise.resolve(res)
}
return res
},
(error) => {
//alert("网络异常!") 404等问题可以在这里处理
return Promise.reject(error)
}
)
export default Axios

View File

@@ -3,6 +3,37 @@
padding: 0; padding: 0;
box-sizing: border-box !important; box-sizing: border-box !important;
} }
/* 禁用移动端弹性滚动 */
html, body {
height: 100%;
overflow: hidden;
position: fixed;
width: 100%;
-webkit-overflow-scrolling: touch;
}
#app {
height: 100%;
overflow: auto;
-webkit-overflow-scrolling: auto;
position: relative;
}
/* 禁用iOS橡皮筋效果 */
body {
position: fixed;
width: 100%;
height: 100%;
overscroll-behavior: none;
-webkit-overflow-scrolling: touch;
}
/* 防止下拉刷新和弹性滚动 */
html {
overscroll-behavior: none;
-webkit-overflow-scrolling: touch;
}
li { li {
list-style: none; list-style: none;
} }
@@ -11,3 +42,14 @@ li {
margin: 0 auto; margin: 0 auto;
} }
$fzt: 20px; $fzt: 20px;
.t-notification__detail {
// padding: 0 20px;
.t-notification__detail-item {
padding: 6px 12px;
&:nth-child(1) {
color: #0052d9;
}
}
}

View File

@@ -10,17 +10,14 @@ import { createApp } from 'vue'
import router from './router/index' //引入vue-router import router from './router/index' //引入vue-router
import App from './App.vue' import App from './App.vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import Foot from '@/components/Foot.vue' import { startVersionCheck } from './utils/versionCheck'
import Head from '@/components/Head.vue' import TDesign from 'tdesign-vue-next'
import ElementPlus from 'element-plus' import 'tdesign-vue-next/es/style/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import 'element-plus/dist/index.css'
import 'normalize.css/normalize.css' import 'normalize.css/normalize.css'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
// 启动版本检测服务 (两小时检查一次)
startVersionCheck(1000 * 60 * 2)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) // 注入插件
// 挂载到app上 // 挂载到app上
createApp(App) createApp(App).use(router).use(pinia).use(TDesign).mount('#app')
.use(router)
.use(createPinia())
.use(ElementPlus)
.component('Foot', Foot)
.component('Head', Head)
.mount('#app')

View File

@@ -6,24 +6,17 @@
* @LastEditors: 刘引 * @LastEditors: 刘引
* @LastEditTime: 2022-03-11 14:26:24 * @LastEditTime: 2022-03-11 14:26:24
*/ */
import path from 'node:path/win32'
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [ const routes = [
{ {
path: '/', path: '/',
component: () => import('@/views/index.vue'), component: () => import('@/views/index.vue')
children: [
{
path: '/',
component: import('@/views/index.vue'),
children: [{ path: '/', component: import('@/views/index.vue') }]
}
]
// redirect: "/index",
}, },
{ {
path: '/user', path: '/excel-upload',
component: () => import('@/views/user/index.vue') name: 'ExcelUpload',
component: () => import('@/views/excel-upload/index.vue')
} }
] ]
@@ -32,4 +25,3 @@ const router = createRouter({
routes routes
}) })
export default router export default router
// 我感觉

View File

@@ -0,0 +1,71 @@
import Axios from '../utils/request/base-service' // 导入配置好的axios文件
import type { AxiosProgressEvent } from 'axios'
// 封装axios请求函数并用export导出
export function uploadExcel(
file: FormData,
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void
) {
return Axios({
url: '/upload',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: file,
responseType: 'blob',
onUploadProgress
})
}
export function checkHealth() {
return Axios({
url: '/health',
method: 'get',
headers: {
'Cache-Control': 'no-cache',
Pragma: 'no-cache'
},
params: {
_t: Date.now()
}
})
}
export function checkDataSourceStatus() {
return Axios({
url: '/data-source-status',
method: 'get',
headers: {
'Cache-Control': 'no-cache',
Pragma: 'no-cache'
},
params: {
_t: Date.now()
}
})
}
export function testConnection() {
return Axios({
url: '/test',
method: 'get',
params: {
_t: Date.now()
}
})
}
export function getProcessingProgress() {
return Axios({
url: '/progress',
method: 'get',
headers: {
'Cache-Control': 'no-cache',
Pragma: 'no-cache'
},
params: {
_t: Date.now()
}
})
}

View File

@@ -7,41 +7,41 @@
* @LastEditTime: 2022-01-25 18:04:29 * @LastEditTime: 2022-01-25 18:04:29
*/ */
import Axios from "../api/base-service"; // 导入配置好的axios文件 import Axios from '../utils/request/base-service' // 导入配置好的axios文件
// 封装axios请求函数并用export导出 // 封装axios请求函数并用export导出
export function getInfo(datas: unknown) { export function getInfo(datas: unknown) {
return Axios({ return Axios({
url: "/api.php?key=free&appid=0&msg=鹅鹅鹅", url: '/api.php?key=free&appid=0&msg=鹅鹅鹅',
method: "GET", method: 'GET',
headers: { headers: {
"content-type": "application/json", 'content-type': 'application/json'
}, },
data: datas, data: datas
}); })
} }
export function getInfoA(datas: unknown) { export function getInfoA(datas: unknown) {
return Axios({ return Axios({
url: "/api/getbooks", url: '/api/getbooks',
method: "get", method: 'get',
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", //设置请求头请求格式form 'Content-Type': 'application/x-www-form-urlencoded' //设置请求头请求格式form
}, },
data: datas, data: datas
}); })
} }
export function getItem(datas: unknown) { export function getItem(datas: unknown) {
return Axios({ return Axios({
url: "/api/getItem", url: '/api/getItem',
method: "post", method: 'post',
headers: { headers: {
"Content-Type": "application/json", //设置请求头请求格式为json 'Content-Type': 'application/json' //设置请求头请求格式为json
}, },
data: datas, data: datas
}); })
} }
export function getItemInfo(datas: unknown) { export function getItemInfo(datas: unknown) {
return Axios({ return Axios({
url: "/api/getItemInfo" + datas, url: '/api/getItemInfo' + datas,
method: "get", method: 'get'
}); })
} }

View File

@@ -1,35 +1,29 @@
/* import { createPinia } from 'pinia'
* @Author: 刘引 liu.yin.work@foxmail.com const store = createPinia()
* @Date: 2023-08-01 13:46:06 export { store }
* @LastEditors: 刘引 liu.yin.work@foxmail.com export * from './modules/user'
* @LastEditTime: 2023-08-01 15:56:19 // export let useStore = defineStore('useStore', {
* @FilePath: \kthec-emss-web\src\pinia\index.js // /**
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE // * 存储全局状态
*/ // * 1.必须是箭头函数: 为了在服务器端渲染的时候避免交叉请求导致数据状态污染
import { defineStore } from 'pinia' // 定义容器 // * 和 TS 类型推导
// */
export let useMain = defineStore('useStore', { // state: () => {
/** // return {
* 存储全局状态 // count: 0,
* 1.必须是箭头函数: 为了在服务器端渲染的时候避免交叉请求导致数据状态污染 // list: [1, 2, 3, 4]
* 和 TS 类型推导 // }
*/ // },
state: () => { // /**
return { // * 用来封装计算属性 有缓存功能 类似于computed
count: 0, // */
list: [1, 2, 3, 4] // getters: {},
} // /**
}, // * 编辑业务逻辑 类似于methods
/** // */
* 用来封装计算属性 有缓存功能 类似于computed // actions: {
*/ // changeData(val: number) {
getters: {}, // this.count = val + 10
/** // }
* 编辑业务逻辑 类似于methods // }
*/ // })
actions: {
changeData(val: number) {
this.count = val + 10
}
}
})

16
src/store/modules/user.ts Normal file
View File

@@ -0,0 +1,16 @@
import { defineStore } from 'pinia'
import { reactive } from 'vue'
export const useUserStore = defineStore(
'useUserStore',
() => {
const userInfo = reactive({
name: 'yin.liu',
age: 18
})
return { userInfo }
},
{
persist: true
}
)

View File

@@ -0,0 +1,118 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { DialogPlugin } from 'tdesign-vue-next'
// 获取浏览器中的url地址和端口号并拼接
/* import { config } from 'public/config.js'
const getBaseUrl = () => {
let baseUrlData = ''
if (process.env.NODE_ENV === 'development') {
baseUrlData = config.baseUrl
} else if (process.env.NODE_ENV === 'production') {
baseUrlData = 'http://10.0.0.88:3000/api'
}
return baseUrlData
} */
// 创建一个新的axios实例
const instance: AxiosInstance = axios.create({
baseURL: '/api',
timeout: 3000000,
withCredentials: false,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
instance.interceptors.request.use(
(config: any) => {
// 在发送请求之前做些什么
// 例如添加token到请求头
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
return config
},
(error: any) => {
// 对请求错误做些什么
return Promise.reject(error)
}
)
// 响应拦截器
instance.interceptors.response.use(
(response: AxiosResponse) => {
// 对响应数据做点什么
console.log(process.env.NODE_ENV, '变量')
const data = response.data
console.log('接口返回', data)
// 对于健康检查和数据源状态检查接口,直接返回数据
if (
response.config.url?.includes('/health') ||
response.config.url?.includes('/data-source-status')
) {
return response
}
// 判断接口返回的 Message 字段是否为 Success对于其他接口
if (data && data.Message && !data.Message.includes('Success')) {
// 如果不是 Success则将请求视为失败
const errorDia = DialogPlugin({
header: '错误',
body: `接口${data.config.url}报错!【${JSON.stringify(data) || '未知错误'}`,
confirmBtn: null,
cancelBtn: null,
destroyOnClose: true,
theme: 'danger',
zIndex: 10000,
onClose: () => {
errorDia.destroy()
}
})
return Promise.reject({
response: {
status: 200,
data: data,
message: JSON.stringify(data) || '请求失败'
}
})
}
// 如果是 Success或者没有Message字段则返回数据
return response
},
(error: any) => {
// 对响应错误做点什么
if (error.response) {
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
console.error('请求错误:', error.response.status, error.response.data)
} else if (error.request) {
// 请求已经发出,但没有收到响应
console.error('网络错误:', error.request)
} else {
// 在设置请求时发生了一些事情,触发了一个错误
console.error('错误:', error.message)
}
console.error('报错', error)
const errorDia = DialogPlugin({
header: '错误',
body: `接口${error.config.url}报错!【${JSON.stringify(error.message) || '未知错误'}`,
confirmBtn: null,
cancelBtn: null,
destroyOnClose: true,
theme: 'danger',
zIndex: 10000,
onClose: () => {
errorDia.destroy()
}
})
return Promise.reject(error)
}
)
export default instance

View File

@@ -0,0 +1,57 @@
/**
* 更新版本信息脚本
* 在构建过程中自动更新 version.json 文件
*/
import fs from 'node:fs'
import path, { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
// 获取当前文件的目录路径
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
// 版本信息
const now = new Date()
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(
2,
'0'
)}-${String(now.getDate()).padStart(2, '0')}`
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(
2,
'0'
)}:${String(now.getSeconds()).padStart(2, '0')}`
interface VersionInfo {
version: string
buildTime: string
environment: string
}
// 不再需要检查构建序号,直接使用时间戳作为版本号
try {
if (fs.existsSync(path.resolve(__dirname, '../../public/version.json'))) {
const currentVersionInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../public/version.json'), 'utf8'))
console.log(`当前版本: ${currentVersionInfo.version}, 更新为时间格式版本`)
}
} catch (error) {
console.error('读取当前版本信息失败', error)
}
// 设置版本格式为 YYYY-MM-DD HH:MM:SS
const versionFormat = `${dateStr} ${timeStr}`
const versionInfo: VersionInfo = {
version: versionFormat,
buildTime: now.toISOString(),
environment: process.env.NODE_ENV || 'production'
}
// 版本文件路径
// 确保路径正确解析到 pc-front/public/version.json
const versionFilePath = path.resolve(__dirname, '../../public/version.json')
// 写入版本信息
fs.writeFileSync(versionFilePath, JSON.stringify(versionInfo, null, 2), 'utf8')
console.log(`版本信息已更新: ${JSON.stringify(versionInfo)}`)
console.log(`当前版本时间戳: ${versionFormat}`)

154
src/utils/versionCheck.ts Normal file
View File

@@ -0,0 +1,154 @@
/**
* 版本检测服务
* 定期检查应用版本并提示用户更新
*/
import { NotifyPlugin } from 'tdesign-vue-next'
import { h } from 'vue'
interface VersionInfo {
version: string
buildTime: string
environment: string
}
let currentVersion: string | null = null
let checkInterval: number | null = null
let isUpdateNotificationShown = false
/**
* 获取当前版本信息
*/
export const fetchVersionInfo = async (): Promise<VersionInfo> => {
try {
// 确保使用正确的路径访问version.json
const response = await fetch(`/version.json?t=${new Date().getTime()}`)
if (!response.ok) {
throw new Error('无法获取版本信息')
}
return await response.json()
} catch (error) {
console.error('获取版本信息失败:', error)
return {
version: '0.0.0',
buildTime: new Date().toISOString(),
environment: 'unknown'
}
}
}
/**
* 检查版本更新
*/
export const checkVersion = async (): Promise<boolean> => {
try {
const versionInfo = await fetchVersionInfo()
// 首次加载,保存当前版本
if (!currentVersion) {
currentVersion = versionInfo.version
return false
}
// 版本不一致,需要更新
if (currentVersion !== versionInfo.version) {
return true
}
return false
} catch (error) {
console.error('检查版本更新失败:', error)
return false
}
}
/**
* 显示更新提示
*/
export const showUpdateNotification = () => {
// 如果已经显示了更新提示,则不再重复显示
if (isUpdateNotificationShown) {
return
}
// 标记已显示更新提示
isUpdateNotificationShown = true
const notify = NotifyPlugin.warning({
title: '有新的版本需要更新',
content: '点击后将刷新页面获取最新版本!',
footer: h('div', { class: 't-notification__detail' }, [
h(
't-button',
{
class: 't-notification__detail-item',
theme: 'default',
variant: 'text',
onClick: () => window.location.reload()
},
'立即刷新'
),
h(
't-button',
{
class: 't-notification__detail-item',
theme: 'primary',
variant: 'text',
onClick: () => {
isUpdateNotificationShown = false
NotifyPlugin.close(notify)
}
},
'稍后提醒我'
)
]),
duration: 0
})
}
/**
* 启动版本检测服务
* @param intervalTime 检测间隔时间(毫秒)默认5分钟
*/
export const startVersionCheck = (intervalTime = 1000) => {
// 重置通知标志
isUpdateNotificationShown = false
// 初始化获取当前版本
fetchVersionInfo().then(info => {
currentVersion = info.version
console.log('当前应用版本:', currentVersion)
})
// 清除可能存在的旧定时器
if (checkInterval) {
window.clearInterval(checkInterval)
}
// 设置定期检查
checkInterval = window.setInterval(async () => {
const needUpdate = await checkVersion()
if (needUpdate) {
showUpdateNotification()
}
}, intervalTime)
return () => {
if (checkInterval) {
window.clearInterval(checkInterval)
checkInterval = null
}
}
}
/**
* 停止版本检测服务
*/
export const stopVersionCheck = () => {
if (checkInterval) {
window.clearInterval(checkInterval)
checkInterval = null
}
// 重置通知标志
isUpdateNotificationShown = false
}

View File

@@ -0,0 +1,437 @@
<template>
<div class="excel-upload-container">
<t-card title="Excel 地址信息补充" class="upload-card">
<!-- 服务状态检查 -->
<div class="status-section">
<t-space>
<t-tag variant="outline" :theme="serviceStatus === 'ok' ? 'success' : 'danger'">
服务器状态:
{{ serviceStatus === 'ok' ? '正常' : serviceStatus === 'error' ? '异常' : '检查中' }}
</t-tag>
<t-tag :theme="dataSourceStatus.both_exist ? 'success' : 'danger'" variant="outline">
数据源: {{ dataSourceStatus.both_exist ? '完整' : '缺失' }}
</t-tag>
</t-space>
</div>
<t-divider />
<!-- 文件上传区域 -->
<div class="upload-section" @click="openFileSelect($event)">
<t-upload
v-model="fileList"
:before-upload="beforeUpload"
accept=".xlsx,.xls"
ref="uploadRef"
:cancelUploadButton="null"
:max="2"
upload-all-files-in-one-request
:disabled="uploading"
:request-method="customUpload"
:showUploadProgress="false"
:auto-upload="false"
theme="file-flow"
multiple
placeholder="最多支持2个Excel文件"
>
</t-upload>
<!-- 上传进度 -->
<div v-if="uploading" class="upload-progress">
<p class="progress-text">{{ uploadStatus }}</p>
<t-progress :percentage="uploadProgress" />
</div>
</div>
<t-divider />
<!-- 说明信息 -->
<div class="info-section">
<t-alert theme="info" message="使用说明" :close="false">
<template #default>
<li>1上传需要处理的Excel文件包含身份证号列</li>
<li>2系统将自动根据数据源补充乡镇街道和村社区信息</li>
<li>3处理完成后会自动下载结果文件</li>
</template>
</t-alert>
</div>
</t-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { MessagePlugin, DialogPlugin } from 'tdesign-vue-next'
import {
uploadExcel,
checkHealth,
checkDataSourceStatus,
getProcessingProgress
} from '@/services/excel-upload'
// 响应式数据
const fileList = ref([])
const uploadRef = ref(null)
const uploading = ref(false)
const uploadProgress = ref(0)
const uploadStatus = ref('')
const serviceStatus = ref('checking')
const dataSourceStatus = reactive({
urban_rural_exists: false,
worker_exists: false,
both_exist: false
})
const processingProgress = reactive({
status: 'idle',
current_step: '',
progress: 0,
total_files: 0,
processed_files: 0,
current_file: '',
error_message: ''
})
const progressTimer = ref<NodeJS.Timeout | null>(null)
// 文件上传前检查
const beforeUpload = (file: File) => {
// 检查文件类型
const allowedTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
'application/vnd.ms-excel' // .xls
]
if (!allowedTypes.includes(file.type)) {
MessagePlugin.error('只支持Excel文件格式.xlsx, .xls')
return false
}
// 检查文件大小50MB
/* const maxSize = 50 * 1024 * 1024
if (file.size > maxSize) {
MessagePlugin.error('文件大小不能超过50MB')
return false
}
*/
return true
}
// 获取处理进度
const fetchProcessingProgress = async () => {
try {
const response = await getProcessingProgress()
if (response.data.success && response.data.data) {
Object.assign(processingProgress, response.data.data)
// 更新进度显示
if (processingProgress.status !== 'idle') {
uploadProgress.value = processingProgress.progress
// 直接从接口获取状态文本,增加更友好的状态显示
const step = processingProgress.current_step || '处理中...'
// 根据不同的处理状态提供更详细的提示
uploadStatus.value = `🚀 ${step}`
}
// 如果处理完成或出错,停止轮询
if (processingProgress.status === 'completed' || processingProgress.status === 'error') {
if (progressTimer.value) {
clearInterval(progressTimer.value)
progressTimer.value = null
}
if (processingProgress.status === 'error') {
throw new Error(processingProgress.error_message || '处理失败')
}
}
}
console.log('进度显示', uploadProgress.value)
} catch (error) {
console.error('获取进度失败:', error)
if (progressTimer.value) {
clearInterval(progressTimer.value)
progressTimer.value = null
}
}
}
// 开始进度轮询
const startProgressPolling = () => {
// 重置进度状态
Object.assign(processingProgress, {
status: 'idle',
current_step: '',
progress: 0,
total_files: 0,
processed_files: 0,
current_file: '',
error_message: ''
})
// 立即获取一次进度
fetchProcessingProgress()
// 开始轮询
progressTimer.value = setInterval(() => {
fetchProcessingProgress()
}, 2000) // 每1秒查询一次
}
// 停止进度轮询
const stopProgressPolling = () => {
if (progressTimer.value) {
clearInterval(progressTimer.value)
progressTimer.value = null
}
}
// 自定义上传方法
const customUpload = async (options: any) => {
const files = options.map((item: any) => item.raw)
console.log('文件', files)
// 开始进度轮询
startProgressPolling()
try {
uploading.value = true
// 创建FormData并添加文件
const formData = new FormData()
// 逐个添加文件到 FormData添加索引避免覆盖
files.forEach((file: File, index: number) => {
formData.append(`file_${index}`, file)
})
// 调用上传API传递formData
const response = await uploadExcel(formData)
// 停止轮询,因为后端处理已完成
stopProgressPolling()
uploadStatus.value = '处理完成!'
uploadProgress.value = 100
// 处理响应,下载文件
if (response.data instanceof Blob) {
// 生成带时间戳的文件名
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
const seconds = String(now.getSeconds()).padStart(2, '0')
const fileName = `已添加地址信息_${year}-${month}-${day} ${hours}:${minutes}:${seconds}.zip`
// 创建下载链接
const url = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement('a')
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
// 清理
link.remove()
window.URL.revokeObjectURL(url)
MessagePlugin.success('文件处理完成,已自动下载结果')
// 显示成功对话框
const tipDialog = DialogPlugin({
header: '处理完成',
theme: 'success',
body: 'Excel文件处理完成已自动下载处理结果文件。',
confirmBtn: '确定',
cancelBtn: null,
onConfirm: () => {
fileList.value = []
uploadProgress.value = 0
uploading.value = false
tipDialog.destroy()
},
onClose: () => {
fileList.value = []
uploadProgress.value = 0
uploading.value = false
tipDialog.destroy()
}
})
}
} catch (error: any) {
console.error('处理失败:', error)
// 如果响应是blob但包含错误信息尝试读取
if (error.response && error.response.data instanceof Blob) {
try {
const errorText = await error.response.data.text()
const errorData = JSON.parse(errorText)
console.error('服务器错误详情:', errorData)
MessagePlugin.error(errorData.error || '处理失败,请重试')
} catch (parseError) {
console.error('解析错误响应失败:', parseError)
MessagePlugin.error('处理失败,请重试')
}
} else {
MessagePlugin.error(error.message || '处理失败,请重试')
}
stopProgressPolling()
uploading.value = false
uploadProgress.value = 0
uploadStatus.value = '处理失败'
fileList.value = []
}
}
// 检查服务状态
const checkServiceStatus = async () => {
try {
const response = await checkHealth()
console.log('健康检查响应:', response.data)
if (response.data.success && response.data.data.status === 'ok') {
serviceStatus.value = 'ok'
}
} catch (error) {
serviceStatus.value = 'error'
console.error('服务状态检查失败:', error)
}
}
// 检查数据源状态
const checkDataSourceStatusLocal = async () => {
try {
const response = await checkDataSourceStatus()
console.log('数据源状态响应:', response.data)
if (response.data.success && response.data.data) {
Object.assign(dataSourceStatus, response.data.data)
}
} catch (error) {
console.error('数据源状态检查失败:', error)
}
}
const openFileSelect = (event: Event) => {
const target = event.target as HTMLElement
if (
target &&
uploadRef.value &&
['t-upload__flow-empty', 't-upload__flow-card-area'].includes(target.className)
) {
;(uploadRef.value as any).triggerUpload()
}
}
// 组件挂载时检查状态
onMounted(() => {
checkServiceStatus()
checkDataSourceStatusLocal()
})
// 组件卸载时清理定时器
onUnmounted(() => {
stopProgressPolling()
})
</script>
<style lang="scss" scoped>
.excel-upload-container {
padding: 24px;
max-width: 800px;
margin: 0 auto;
:deep(.t-upload__flow-table) {
th {
&:nth-child(2) {
display: none;
}
&:nth-child(3) {
display: none;
}
}
td {
&:nth-child(3) {
display: none;
}
&:nth-child(2) {
display: none;
}
}
}
}
:deep(.t-card__title) {
font-size: 20px;
}
:deep(.t-alert__content) {
// margin-left: 0 !important;
}
.upload-card {
margin-bottom: 50px;
.status-section {
display: flex;
align-items: center;
justify-content: space-between;
}
.upload-section {
margin: 24px 0;
cursor: pointer;
:deep(.t-upload__flow-empty) {
color: var(--td-brand-color);
}
:deep(.t-upload__flow) {
width: 100% !important;
min-width: 100px !important;
max-width: none !important;
}
:deep(.t-upload__flow-card-area) {
border: 1px dashed var(--td-brand-color) !important;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background-color: var(--td-bg-color-container-hover);
border-color: var(--td-brand-color-hover) !important;
}
}
.drag-content {
text-align: center;
// padding: 40px 20px;
.t-icon {
color: var(--td-brand-color);
margin-bottom: 16px;
}
.upload-tips {
color: var(--td-text-color-secondary);
font-size: 12px;
margin-top: 8px;
}
}
.upload-progress {
.progress-text {
text-align: center;
color: var(--td-text-color-secondary);
}
}
}
.info-section {
margin-top: 24px;
ol {
margin: 8px 0 0 0;
padding-left: 20px;
li {
margin-bottom: 4px;
line-height: 1.5;
}
}
}
}
@media (max-width: 768px) {
.excel-upload-container {
padding: 16px;
}
}
</style>

View File

@@ -1,21 +1,11 @@
<!--
* @Description: {{ByRuin}}
* @Version: 2.0
* @Author: Ruin 🍭
* @Date: 2022-03-10 10:11:16
* @LastEditors: 刘引
* @LastEditTime: 2022-07-26 14:42:11
-->
<template> <template>
<div class="root">我是home组件的子组件news</div> <div class="root">我是home组件的子组件news</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
.content { .content {
color: red; color: red;
} }
</style> </style>

View File

@@ -1,22 +1,13 @@
<!--
* @Description: {{ByRuin}}
* @Version: 2.0
* @Author: Ruin 🍭
* @Date: 2022-03-10 10:11:06
* @LastEditors: 刘引
* @LastEditTime: 2022-07-26 14:42:06
-->
<template> <template>
<div class="root-home"> <div class="root-home">
<p>我是home组件</p> <p>我是home组件</p>
<el-button>按钮</el-button>
<news></news> <news></news>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, watch, onUpdated, ref, reactive } from 'vue'; import { computed, watch, onUpdated, ref, reactive } from 'vue'
import news from './components/news.vue' import news from './components/news.vue'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@@ -1,13 +1,14 @@
<template> <template>
<Head></Head> <Head></Head>
<div>根组件{{ store.count }}</div> <p>{{ userStore.userInfo.age }}</p>
<t-button @click="userStore.userInfo.age++">测试</t-button>
<Foot></Foot> <Foot></Foot>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, watch } from 'vue' import { reactive, onMounted, watch } from 'vue'
import { useMain } from '@/store' import { useUserStore } from '@/store'
const store = useMain() const userStore = useUserStore()
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

View File

@@ -9,7 +9,6 @@
<template> <template>
<div class="root-home"> <div class="root-home">
<news></news> <news></news>
<el-button @click="changeData()">更改值了</el-button>
<h1>写一点demo玩一玩</h1> <h1>写一点demo玩一玩</h1>
</div> </div>
</template> </template>

View File

@@ -15,7 +15,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext", "dom"], "lib": ["esnext", "dom"],
"types": ["element-plus/global"] "types": ["vue", "element-plus/global", "node"]
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]

View File

@@ -1,39 +1,81 @@
/* import { defineConfig } from 'vite'
* @Description: {{ByRuin}} import vue from '@vitejs/plugin-vue'
* @Version: 2.0 import { fileURLToPath } from 'url'
* @Author: Ruin 🍭 import { dirname, resolve } from 'path'
* @Date: 2022-01-25 16:22:24 import AutoImport from 'unplugin-auto-import/vite'
* @LastEditors: 刘引 import Components from 'unplugin-vue-components/vite'
* @LastEditTime: 2022-03-10 16:34:39 import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
*/ import { VitePWA } from 'vite-plugin-pwa'
import { defineConfig } from "vite"; // ESM 中获取 __dirname 的替代方案
import vue from "@vitejs/plugin-vue"; const __filename = fileURLToPath(import.meta.url)
// ui组件自动导入 const __dirname = dirname(__filename)
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import { resolve } from "path";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
VitePWA({
injectRegister: 'auto',
registerType: 'autoUpdate',
devOptions: {
enabled: true // 是否本地localhost调试pwa
},
workbox: {
// 设置需要缓存的文件类型
globPatterns: ['**/*.{js,css,html,svg,jpg,ico}']
},
manifest: {
name: 'Excel处理工具',
short_name: '处理工具',
theme_color: '#fff', // 浏览器状态栏主题
start_url: './',
display: 'standalone',
background_color: '#fff',
icons: [
{
src: 'logo.png',
sizes: '144x144',
type: 'image/png',
purpose: 'any'
},
{
src: 'logo.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any'
}
]
}
}),
AutoImport({ AutoImport({
resolvers: [ElementPlusResolver()], resolvers: [ElementPlusResolver()]
}), }),
Components({ Components({
resolvers: [ElementPlusResolver()], resolvers: [ElementPlusResolver()]
}), })
], ],
resolve: { resolve: {
alias: { alias: {
"@": resolve(__dirname, "src"), // 路径别名 '@': resolve(__dirname, 'src')
}, }
extensions: [".js", ".json", ".ts"], // 使用路径别名时想要省略的后缀名,可以自己 增减
}, },
server: { server: {
host: "0.0.0.0", host: '0.0.0.0',
port: 8888, port: 6677,
// 是否开启 https open: true,
https: false, proxy: {
}, '/api': {
}); target: 'http://10.2.0.32:5000',
changeOrigin: true,
rewrite: p => p.replace(/^\/api/, ''),
bypass: (req, res, options) => {
const proxyURL = options.target + options.rewrite(req.url)
// console.log('proxyURL', proxyURL)
req.headers['x-req-proxyURL'] = proxyURL // 设置未生效
res.setHeader('x-req-proxyURL', proxyURL) // 设置响应头可以看到
}
}
}
}
})