first commit

This commit is contained in:
2025-04-24 11:10:30 +08:00
commit 8444433e5b
134 changed files with 13484 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
end_of_line = crlf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120
+5
View File
@@ -0,0 +1,5 @@
# 本地运行端口号
VITE_PORT = 8686
# API接口域名配置
API_BASE_URL = https://api.example.com/
+8
View File
@@ -0,0 +1,8 @@
# 本地环境
VITE_USER_NODE_ENV = development
# 公共基础路径
VITE_PUBLIC_PATH = /
# proxy
VITE_PROXY = [["/api","http://localhost:8080"]]
+5
View File
@@ -0,0 +1,5 @@
# 线上环境
VITE_USER_NODE_ENV = production
# 公共基础路径
VITE_PUBLIC_PATH = /
+7
View File
@@ -0,0 +1,7 @@
node_modules
dist
public
*.md
*.txt
.vscode
index.html
+37
View File
@@ -0,0 +1,37 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-essential',
'plugin:prettier/recommended' // 后续兼容prettier
],
overrides: [
{
env: {
node: true
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script'
}
}
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module'
},
plugins: ['@typescript-eslint', 'vue'],
rules: {
// Switch语句 https://zh-hans.eslint.org/docs/latest/rules/indent#switchcase
indent: ['error', 2, { SwitchCase: 1 }],
'linebreak-style': ['error', 'windows'],
quotes: ['error', 'single'],
semi: ['error', 'never']
}
}
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+7
View File
@@ -0,0 +1,7 @@
node_modules
dist
public
*.md
*.txt
.vscode
index.html
+10
View File
@@ -0,0 +1,10 @@
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"semi": false,
"endOfLine": "crlf"
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}
+1
View File
@@ -0,0 +1 @@
PATH=D:\Software\Python36\Scripts\;D:\Software\Python36;D:\Software\VMware\VMware Workstation\bin\;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps;C:\Windows;C:\Windows\System32\OpenSSH\;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\system32;D:\Cache\Android\android-sdk\platform-tools;D:\Software\Git\cmd;D:\Software\JetBrains\IntelliJ IDEA 2023.3.5\bin;D:\Software\JetBrains\PhpStorm 2023.3.2\bin;D:\Software\JetBrains\PyCharm 2023.3.4\bin;c:\Users\Administrator\AppData\Local\Programs\cursor\resources\app\bin;C:\ProgramData\ComposerSetup\bin;D:\Software\Tencent\΢ÐÅweb¿ª·¢Õß¹¤¾ß\dll;C:\Program Files\python;C:\Program Files\python\Scripts;D:\BtSoft\panel\script;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;D:\Software\TortoiseGit\bin;D:\Software\nvm;D:\Software\nvm4w\nodejs;D:\Software\phpstudy_pro\Extensions\php\php7.4.3nts;C:\Program Files\PuTTY\;d:\Software\Trae CN\bin;D:\Software\Python36\;D:\Software\Python39\Scripts\;D:\Software\Python39\;D:\Software\Python310\Scripts\;D:\Software\Python310\;C:\Users\Administrator\AppData\Local\JetBrains\Toolbox\scripts;D:\phpEnv\php\php-8.0;D:\phpEnv\server\mysql\mysql-5.7\bin;D:\phpEnv\tools\Composer;C:\Users\Administrator\AppData\Roaming\Composer\vendor\bin;D:\Software\Microsoft VS Code\bin;C:\Users\Administrator\AppData\Local\Programs\Ollama
+46
View File
@@ -0,0 +1,46 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
# 目录结构
```
| .env
| .env.development
| .env.production
| .gitignore
| index.html
| package-lock.json
| package.json
| README.md
| tree.txt
| tsconfig.json
| tsconfig.node.json
| vite.config.ts
|
+---.vscode
| extensions.json
|
+---build
| index.ts
|
+---node_modules
+---public
| vite.svg
|
+---src
| | App.vue
| | main.ts
| | style.css
| | vite-env.d.ts
| |
| +---assets
| | vue.svg
| |
| \---components
| HelloWorld.vue
|
\---types
index.d.ts
```
+17
View File
@@ -0,0 +1,17 @@
// 环境变量处理方法
export function wrapperEnv(envConf: Recordable): ViteEnv {
const ret: Record<string, string | number | boolean> = {};
for (const envName of Object.keys(envConf)) {
let realName = envConf[envName].replace(/\\n/g, "\n");
realName = realName === "true" ? true : realName === "false" ? false : realName;
if (envName === "VITE_PORT") realName = Number(realName);
ret[envName] = realName;
if (typeof realName === "string") {
process.env[envName] = realName;
} else if (typeof realName === "object") {
process.env[envName] = JSON.stringify(realName);
}
}
return ret;
}
+16
View File
@@ -0,0 +1,16 @@
import vue from '@vitejs/plugin-vue'
/**
* * 扩展setup插件,支持在script标签中使用name属性
* usage: <script setup name="MyComp"></script>
*/
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export function createVitePlugins(viteEnv: Record<string, string>, isBuild: boolean): any[] {
const plugins = [
vue(),
VueSetupExtend(),
]
return plugins
}
+14
View File
@@ -0,0 +1,14 @@
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import pluginVue from "eslint-plugin-vue";
import { defineConfig } from "eslint/config";
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs,ts,vue}"], plugins: { js }, extends: ["js/recommended"] },
{ files: ["**/*.{js,mjs,cjs,ts,vue}"], languageOptions: { globals: {...globals.browser, ...globals.node} } },
tseslint.configs.recommended,
pluginVue.configs["flat/essential"],
{ files: ["**/*.vue"], languageOptions: { parserOptions: { parser: tseslint.parser } } },
]);
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+5996
View File
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
{
"name": "vue3project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"type-check": "vue-tsc --noEmit",
"preview": "vite preview",
"lint": "eslint --fix --ext .ts,.tsx,.vue,.js,.jsx --max-warnings 0"
},
"dependencies": {
"@types/js-cookie": "^3.0.6",
"@vueuse/core": "^13.1.0",
"axios": "^1.8.4",
"echarts": "^5.6.0",
"element-plus": "^2.9.8",
"js-cookie": "^3.0.5",
"pinia": "^3.0.2",
"pinia-plugin-persistedstate": "^4.2.0",
"vue": "^3.5.13",
"vue-i18n": "^11.1.3",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@eslint/js": "^9.25.1",
"@types/node": "^22.14.1",
"@typescript-eslint/eslint-plugin": "^8.31.0",
"@typescript-eslint/parser": "^8.31.0",
"@vitejs/plugin-vue": "^5.2.2",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.25.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-vue": "^10.0.0",
"globals": "^16.0.0",
"naive-ui": "^2.41.0",
"prettier": "^3.5.3",
"sass": "^1.87.0",
"sass-embedded": "^1.87.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.31.0",
"unplugin-vue-components": "^28.5.0",
"unplugin-vue-router": "^0.12.0",
"vite": "^6.3.1",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-tsc": "^2.2.8"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+4
View File
@@ -0,0 +1,4 @@
root: pc
path: C:\Program Files\nodejs
arch: 64
proxy: none
+48
View File
@@ -0,0 +1,48 @@
<script setup lang="ts">
import { onMounted, ref ,provide} from 'vue'
import * as echarts from "echarts";
import { useI18n } from 'vue-i18n'
//通过provide提供echarts
provide("echarts", echarts);
const I18n = useI18n()
const { locale } = useI18n()
// 切换语言更改locale.value的值即可但要跟你index.js中message设置的值一致!
const translate = (lang) => {
locale.value = lang
localStorage.setItem('lang', lang)
}
const type = ref('light')
const onChange = (e) => {
document.documentElement.setAttribute('theme-mode', type.value)
}
</script>
<template>
<div>
<div>
<p>{{ $t('welcome') }}</p>
</div>
<button @click="translate('zh-cn')">切换为中文</button>
<button @click="translate('en-us')">切换为英文</button>
</div>
<div>
<el-select style="width: 80px;margin: 10px;" v-model="type" @change="onChange">
<el-option label="light" value="light" />
<el-option label="dark" value="dark" />
<el-option label="red" value="red" />
</el-select>
</div>
<router-link to="/"> 去首页 </router-link> <router-link to="/login"> 去登录 </router-link> <router-link to="/demo"> 查看demo </router-link>
<router-view />
</template>
<style lang="scss">
#app {
background-color: $primaryColor;
}
</style>
+6
View File
@@ -0,0 +1,6 @@
import { defRequest } from '../utils/request'
export const loginApi = (params: Record<string, unknown>) => {
// 设置 showLoadingtimeout 会覆盖index.ts里的默认值
return defRequest.post<Record<string, unknown>>('/login', params, { showLoading: false, timeout: 1000 })
}
+14
View File
@@ -0,0 +1,14 @@
:root[theme-mode='light'] {
--bg-color: #fff;
--text-color: #000
}
:root[theme-mode='dark'] {
--bg-color: #2c2c2c;
--text-color: #fff
}
:root[theme-mode='red'] {
--bg-color: rgb(0, 128, 255);
--text-color: red;
}
+4
View File
@@ -0,0 +1,4 @@
:root {
color: var(--text-color);
background-color: var(--bg-color);
}
+1
View File
@@ -0,0 +1 @@
$primaryColor: #316c72;
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

+41
View File
@@ -0,0 +1,41 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
+6
View File
@@ -0,0 +1,6 @@
export default {
welcome: 'Welcome',
login: 'Login',
register: 'Register',
// 可以根据实际需求添加更多翻译项
};
+20
View File
@@ -0,0 +1,20 @@
import {createI18n} from 'vue-i18n'
// 从语言包文件中导入语言包对象
import zh from '@language/zh-CN'
import en from '@language/en-US'
const messages = {
'zh-cn': zh,
'en-us': en
}
const language = (navigator.language || 'en').toLocaleLowerCase() // 这是获取浏览器的语言
// 获取浏览器当前使用的语言,并进行处理
const i18n = createI18n({
legacy: false,
locale: localStorage.getItem('lang') || language.split('-')[0] || 'en', // 首先从缓存里拿,没有的话就用浏览器语言,
fallbackLocale: 'zh-cn', // 设置备用语言
messages,
})
export default i18n
+6
View File
@@ -0,0 +1,6 @@
export default {
welcome: '欢迎',
login: '登录',
register: '注册',
// 可以根据实际需求添加更多翻译项
};
+27
View File
@@ -0,0 +1,27 @@
import { createApp } from 'vue'
import pinia from '@/store'
import './style.css'
import '@css/index.scss'
import App from './App.vue'
import * as echarts from 'echarts'
import router from '@/router/index'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import i18n from './language'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 初始化pinia插件
pinia.use(piniaPluginPersistedstate)
// 创建并配置Vue应用
const app = createApp(App)
.use(router)
.use(pinia)
.use(i18n)
.use(ElementPlus)
// 全局挂载echarts
app.config.globalProperties.$echarts = echarts
// 挂载应用
app.mount('#app')
+58
View File
@@ -0,0 +1,58 @@
import { Router, createRouter, createWebHistory } from 'vue-router'
/** 自动导入 src/router/modules 下的静态路由
* import.meta.glob使用说明:https://cn.vitejs.dev/guide/features#glob-import
*/
const modules: Record<string, unknown> = import.meta.glob(['./modules/**/*.ts'], {
eager: true
})
/** 初始路由 **/
const routes: unknown[] = []
Object.keys(modules).forEach((key) => {
const module = (modules[key] as { default: unknown }).default;
if (Array.isArray(module)) {
for (const item of module) {
routes.push(item)
}
} else {
routes.push(module)
}
})
/**
* 创建路由实例
* createRouter选项有:https://router.vuejs.org/zh/api/interfaces/RouterOptions.html
* hash模式使用createWebHashHistory(): https://router.vuejs.org/zh/api/#Functions-createWebHashHistory
*/
export const router: Router = createRouter({
history: createWebHistory(),
routes,
strict: true,
scrollBehavior(_to, from, savedPosition) {
return new Promise((resolve) => {
if (savedPosition) {
return savedPosition
} else {
if (from.meta.saveSrollTop) {
const top: number = document.documentElement.scrollTop || document.body.scrollTop
resolve({ left: 0, top })
}
}
})
}
})
/**
* 路由守卫
* https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
*/
router.beforeEach((to, _from, next) => {
// isAuthenticated 代表你的鉴权
const isAuthenticated = true
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
export default router
+12
View File
@@ -0,0 +1,12 @@
const routes = [
{
path: '/',
component: () => import('@/views/default/home.vue')
},
{
path: '/login',
component: () => import('@/views/default/login.vue') //路由懒加载
}
]
export default routes
+7
View File
@@ -0,0 +1,7 @@
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(persist);
export default pinia
+31
View File
@@ -0,0 +1,31 @@
import { defineStore } from 'pinia'
import { UserState } from 'types/store'
import { getToken, setToken } from '@/utils/auth'
// 第一个参数是id,唯一
export const useUserStore = defineStore('user', {
state: () => {
return {
token: getToken() || 'YUNZER68205747',
userInfo: { name: 'yunzer', phone: '19895983967' }
}
},
getters: {
namePic: (state) => state.userInfo.name.substring(0, 1)
},
actions: {
setToken(token: string) {
this.token = token
},
setUserInfo(userInfo: UserState['userInfo']) {
this.userInfo = { ...this.userInfo, ...userInfo }
}
},
setToken(token: string) {
this.token = token
setToken({
token,
expires: 30
})
}
})
+19
View File
@@ -0,0 +1,19 @@
import {defineStore} from 'pinia'
import {getToken,setToken} from "@/utils/storage.ts";
export const useSettingsStore = defineStore('settings', {
id: 'settings', // id必填,且需要唯一
state: () => {
return {
menuCollapse: false,//// 是否水平折叠收起菜单
// 布局方式 Classic 经典布局 Streamline 单行布局
layoutMode: getToken('layoutMode')?getToken('layoutMode'):'Classic'
}
},
actions: {
changeSetting({ key, value }) {
//改变全局变量的方法
this[key] = value
setToken(key, value)
},
}
})
+25
View File
@@ -0,0 +1,25 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import type { User } from '@/types/user';
export const usersStore = defineStore('users', () => {
const userInfo = ref<User>({
name:'abc',
avatar: '123', // 头像
mobile: '13221091091', // 手机号
account: 'lita', // 用户名
id: 1
});
const setUserInfo = (u:User) =>{
userInfo.value = u;
}
const clearUserInfo = () =>{
// void 是用来创建 undefined,不管它后面跟个啥,得到的都是 undefined;
userInfo.value = void 0;
// 上面的代码代表 userinfo.value = undefined;
}
return { userInfo ,setUserInfo, clearUserInfo }
},{persist: true})
+79
View File
@@ -0,0 +1,79 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
+22
View File
@@ -0,0 +1,22 @@
import Cookies from 'js-cookie'
export const TokenKey = 'yunzer-token'
type ExpiresData = Date | number
export interface TokenInfo {
token: string
expires: ExpiresData
}
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(data: TokenInfo) {
const { token, expires } = data
return expires ? Cookies.set(TokenKey, token, { expires: expires }) : Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
+35
View File
@@ -0,0 +1,35 @@
/**
* 创建实例,可以多个,当你需要请求多个不同域名的接口时
*/
import Request from './request'
import { getToken } from '@/utils/auth'
const defRequest = new Request({
// 这里用 Easy Mock 模拟了真实接口
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 5000,
showLoading: true,
interceptorHooks: {
requestInterceptor: (config) => {
const token = getToken()
if (token) {
config.headers.Authorization = token
}
return config
},
requestInterceptorCatch: (err) => {
return err
},
responseInterceptor: (res) => {
return res.data
},
responseInterceptorCatch: (err) => {
return Promise.reject(err)
}
}
})
// 创建其他示例,然后导出
// const otherRequest = new Request({...})
export { defRequest }
+123
View File
@@ -0,0 +1,123 @@
/**
* 封装axios
* axios 实例的类型为 AxiosInstance,请求需要传入的参数类型为 AxiosRequestConfig,响应的数据类型为 AxiosResponseInternalAxiosRequestConfig 继承于 AxiosRequestConfig
*/
import axios, { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
import { ErrMessage } from './status'
// 自定义请求返回数据的类型
interface Data<T> {
data: T
code: string
success: boolean
}
// 扩展 InternalAxiosRequestConfig,让每个请求都可以控制是否要loading
interface RequestInternalAxiosRequestConfig extends InternalAxiosRequestConfig {
showLoading?: boolean
}
// 拦截器
interface InterceptorHooks {
requestInterceptor?: (config: RequestInternalAxiosRequestConfig) => RequestInternalAxiosRequestConfig
requestInterceptorCatch?: (error: unknown) => unknown
responseInterceptor?: (response: AxiosResponse) => AxiosResponse
responseInterceptorCatch?: (error: unknown) => unknown
}
// 扩展 AxiosRequestConfigshowLoading 给实例默认增加loadinginterceptorHooks 拦截
interface RequestConfig extends AxiosRequestConfig {
showLoading?: boolean
interceptorHooks?: InterceptorHooks
}
class Request {
config: RequestConfig
instance: AxiosInstance
loading?: boolean // 用loading指代加载动画状态
constructor(options: RequestConfig) {
this.config = options
this.instance = axios.create(options)
this.setupInterceptor()
}
// 类型参数的作用,T决定AxiosResponse实例中data的类型
request<T>(config: RequestConfig): Promise<T> {
return new Promise((resolve, reject) => {
this.instance
.request<Data<T>, Data<T>>(config)
.then((res) => {
resolve(res.data)
})
.catch((err) => {
reject(err)
})
})
}
// 封装常用方法
// 移除了默认的 any 类型,要求调用者显式指定类型
get<T>(url: string, params?: object, _object = {}): Promise<T> {
return this.request({ url, params, ..._object, method: 'GET' })
}
post<T>(url: string, params?: object, _object = {}): Promise<T> {
return this.request({ url, params, ..._object, method: 'POST' })
}
delete<T>(url: string, params?: object, _object = {}): Promise<T> {
return this.request({ url, params, ..._object, method: 'DELETE' })
}
patch<T>(url: string, params?: object, _object = {}): Promise<T> {
return this.request({ url, params, ..._object, method: 'PATCH' })
}
put<T>(url: string, params?: object, _object = {}): Promise<T> {
return this.request({ url, params, ..._object, method: 'PUT' })
}
// 自定义拦截器 https://axios-http.com/zh/docs/interceptors
setupInterceptor(): void {
/**
* 通用拦截
*/
this.instance.interceptors.request.use((config: RequestInternalAxiosRequestConfig) => {
if (config.showLoading) {
// 加载loading动画
this.loading = true
}
return config
})
// 响应后关闭loading
this.instance.interceptors.response.use(
(res) => {
if (this.loading) this.loading = false
return res
},
(err) => {
const { response, message } = err
if (this.loading) this.loading = false
// 根据不同状态码,返回不同信息
const messageStr = response ? ErrMessage(response.status) : message || '请求失败,请重试'
window.alert(messageStr)
return Promise.reject(err)
}
)
/**
* 使用通用实例里的拦截,两个拦截都会生效,返回值以后一个执行的为准
*/
// 请求拦截
this.instance.interceptors.request.use(
this.config?.interceptorHooks?.requestInterceptor,
this.config?.interceptorHooks?.requestInterceptorCatch
)
// 响应拦截
this.instance.interceptors.response.use(
this.config?.interceptorHooks?.responseInterceptor,
this.config?.interceptorHooks?.responseInterceptorCatch
)
}
}
export default Request
+41
View File
@@ -0,0 +1,41 @@
export const ErrMessage = (status: number | string): string => {
let message: string = ''
switch (status) {
case 400:
message = '请求错误!请您稍后重试'
break
case 401:
message = '未授权!请您重新登录'
break
case 403:
message = '当前账号无访问权限!'
break
case 404:
message = '访问的资源不存在!请您稍后重试'
break
case 405:
message = '请求方式错误!请您稍后重试'
break
case 408:
message = '请求超时!请您稍后重试'
break
case 500:
message = '服务异常!请您稍后重试'
break
case 501:
message = '不支持此请求!请您稍后重试'
break
case 502:
message = '网关错误!请您稍后重试'
break
case 503:
message = '服务不可用!请您稍后重试'
break
case 504:
message = '网关超时!请您稍后重试'
break
default:
message = '请求失败!请您稍后重试'
}
return message
}
+32
View File
@@ -0,0 +1,32 @@
<script setup lang="ts">
import { onMounted, ref ,provide} from 'vue'
import * as echarts from "echarts";
import { useI18n } from 'vue-i18n'
//通过provide提供echarts
provide("echarts", echarts);
const I18n = useI18n()
const { locale } = useI18n()
// 切换语言更改locale.value的值即可但要跟你index.js中message设置的值一致!
const translate = (lang) => {
locale.value = lang
localStorage.setItem('lang', lang)
}
</script>
<template>
<div>
<div>
<p>{{ $t('welcome') }}</p>
</div>
<button @click="translate('zh-cn')">切换为中文</button>
<button @click="translate('en-us')">切换为英文</button>
</div>
<router-link to="/"> 去首页 </router-link> <router-link to="/login"> 去登录 </router-link> <router-link to="/demo"> 查看demo </router-link>
<router-view />
</template>
<style scoped></style>
+55
View File
@@ -0,0 +1,55 @@
<template>
<div class="right-content">
<div ref="Chart" style="width: 800px; height: 500px"></div>
</div>
</template>
<script setup lang="ts">
import { onMounted, getCurrentInstance, ref } from 'vue'
let internalInstance = getCurrentInstance(); //获取当前实例
let echarts = internalInstance.appContext.config.globalProperties.$echarts; //获取echarts实例
//通过ref获取html元素
const Chart = ref();
const init = () => {
// 渲染echarts的父元素
var infoEl = Chart.value;
// light dark
var myChart = echarts.init(infoEl, "light"); //初始化echarts实例
// 指定图表的配置项和数据 树图
var option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
window.onresize = function () {
myChart.resize()
}
}
onMounted(() => {
init()
});
</script>
<style scope lang="scss"></style>
+22
View File
@@ -0,0 +1,22 @@
<script setup lang="ts">
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store/modules/user'
defineOptions({
name: 'V-home'
})
const userStore = useUserStore()
// 获取state使用computed或者使用storeToRefs,直接使用不具备响应式(拿到的永远是初次的值)
const username = computed(() => userStore.userInfo.name)
// 获取getter使用storeToRefs,或者直接使用,在模板里 userStore.namePic
const { token } = storeToRefs(userStore)
const namePic = computed(() => userStore.userInfo.name + '的头像') // 根据store结构,使用userInfo.name生成头像信息
</script>
<template>
<div>Hello: {{ namePic }}, your name is {{ username }}, your token is {{ token }}</div>
</template>
<style scoped></style>
+51
View File
@@ -0,0 +1,51 @@
<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@store/modules/user'
import { loginApi } from '@/api/login'
defineOptions({
name: 'V-login'
})
const userStore = useUserStore()
const { userInfo, token } = storeToRefs(userStore)
let userName = ref(userInfo.value.name)
let userToken = ref(token)
const updateUserName = () => {
userStore.setUserInfo({
name: userName.value
})
}
const updateUserToken = () => {
userStore.setToken(userToken.value)
}
const login = () => {
loginApi({
name: userName.value
})
.then((res) => {
userName.value = res.name
userToken.value = res.token
updateUserToken()
})
.catch((err) => {
console.log(err)
})
}
</script>
<template>
<div>login page</div>
name:
<input type="text" v-model="userName" @input="updateUserName" />
<br />
token:
<input type="text" v-model="userToken" />
<hr />
<button @click="login">login</button>
</template>
<style scoped></style>
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
+54
View File
@@ -0,0 +1,54 @@
{
"compilerOptions": {
"target": "ESNext", // 将代码编译为最新版本的 JS
"useDefineForClassFields": true,
"module": "ESNext", // 使用 ES Module 格式打包编译后的文件
"lib": ["ESNext", "DOM", "DOM.Iterable"], // 引入 ES 最新特性和 DOM 接口的类型定义
"skipLibCheck": true, // 跳过对 .d.ts 文件的类型检查
"esModuleInterop": true, // 允许使用 import 引入使用 export = 导出的内容
"sourceMap": true, // 用来指定编译时是否生成.map文件
"allowJs": false, // 是否允许使用js
"baseUrl": ".", // 查询的基础路径
"paths": {
// 路径映射,配合别名使用
"@": ["src"],
"@/*": ["src/*"],
"@build/*": ["build/*"],
"@language/*": ["src/language/*"],
"@store/*": ["src/store/modules/*"],
"#/*": ["types/*"]
},
/* Bundler mode */
"moduleResolution": "node", // 使用 Node/bundler 的模块解析策略
"allowImportingTsExtensions": true,
"resolveJsonModule": true, // 允许引入 JSON 文件
"isolatedModules": true, // 要求所有文件都是 ES Module 模块。
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"jsx": "preserve", // 保留原始的 JSX 代码,不进行编译
/* Linting */
"strict": true, // 开启所有严格的类型检查
"noUnusedLocals": true, // 报告未使用的局部变量的错误
"noUnusedParameters": true, // 报告函数中未使用参数的错误
"noFallthroughCasesInSwitch": true // 确保switch语句中的任何非空情况都包含
},
"include": [
// 需要检测的文件
"src/**/*.ts",
"build/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"mock/*.ts",
"types/*.d.ts",
"vite.config.ts"
],
"exclude": [
// 不需要检测的文件
"dist",
"**/*.js",
"node_modules"
],
"references": [{ "path": "./tsconfig.node.json" }] // 为文件进行不同配置
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true, // 对于引用项目必须设置该属性
"skipLibCheck": true, // 跳过对 .d.ts 文件的类型检查
"module": "ESNext", // 使用 ES Module 格式打包编译后的文件
"moduleResolution": "Node", // 使用 Node/bundler 的模块解析策略
"allowSyntheticDefaultImports": true // 允许使用 import 导入使用 export = 导出的默认内容
},
"include": ["build/*.ts", "types/*.d.ts", "vite.config.ts"]
}
+26
View File
@@ -0,0 +1,26 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
declare module 'vue-router/auto-routes' {
import type {
RouteRecordInfo,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from 'vue-router'
/**
* Route name map generated by unplugin-vue-router
*/
export interface RouteNamedMap {
'/default/demo': RouteRecordInfo<'/default/demo', '/default/demo', Record<never, never>, Record<never, never>>,
'/default/demo-i18n': RouteRecordInfo<'/default/demo-i18n', '/default/demo-i18n', Record<never, never>, Record<never, never>>,
'/default/home': RouteRecordInfo<'/default/home', '/default/home', Record<never, never>, Record<never, never>>,
'/default/login': RouteRecordInfo<'/default/login', '/default/login', Record<never, never>, Record<never, never>>,
}
}
+22
View File
@@ -0,0 +1,22 @@
type TargetContext = "_self" | "_blank";
type EmitType = (event: string, ...args: any[]) => void;
type AnyFunction<T> = (...args: any[]) => T;
type PropType<T> = VuePropType<T>;
type Writable<T> = {
-readonly [P in keyof T]: T[P];
};
type Nullable<T> = T | null;
type NonNullable<T> = T extends null | undefined ? never : T;
type Recordable<T = any> = Record<string, T>;
interface Fn<T = any, R = T> {
(...arg: T[]): R;
}
interface PromiseFn<T = any, R = T> {
(...arg: T[]): Promise<R>;
}
interface ViteEnv {
VITE_USER_NODE_ENV: "development" | "production";
VITE_PUBLIC_PATH: string;
VITE_PORT: number;
}
+4
View File
@@ -0,0 +1,4 @@
export interface UserState {
token: string
userInfo: { name?: string; phone?: string }
}
+7
View File
@@ -0,0 +1,7 @@
interface User {
token: string;
avatar: string; // 头像
mobile:string; // 手机号
account:string; // 用户名
id:number; // 用户id
}
+63
View File
@@ -0,0 +1,63 @@
import { defineConfig, loadEnv, ConfigEnv, UserConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import { wrapperEnv } from "./build";
import VueRouter from 'unplugin-vue-router/vite';
// 路径查找
const pathResolve = (dir: string): string => {
return resolve(__dirname, ".", dir);
};
// 设置别名,还可以添加其他路径
const alias: Record<string, string> = {
"@": pathResolve("src"),
"@views": pathResolve("src/views"),
"@store": pathResolve("src/store"),
"@language": pathResolve("src/language"),
"@css": pathResolve("src/assets/css"),
};
// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
const root = process.cwd();
const env = loadEnv(mode, root);
const viteEnv = wrapperEnv(env);
return {
base: viteEnv.VITE_PUBLIC_PATH,
plugins: [
VueRouter({
routesFolder: 'src/views', // 指定路由文件所在的目录
exclude: ['**/components/*.vue'],
extensions: ['.vue'], // 指定路由文件的后缀名
}),
vue(),
],
resolve: {
alias, // 设置别名
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@css/variables.scss";`, // 引入全局变量
},
},
},
server: {
host: "0.0.0.0", // 设置服务器主机名
port: viteEnv.VITE_PORT, // 设置服务启动端口号
https: undefined, // 是否开启 https
open: true, // 是否自动打开浏览器
cors: true, // 允许跨域
// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: {
"^/api": {
target: "http://127.0.0.1:8686", // 代理的目标地址
changeOrigin: true, // 开发模式,默认的 origin 是真实的 origin:localhost:3000
rewrite: (path) => path.replace(/^\/api/, ""), // 把 /api 替换成 target 中的地址
},
},
},
};
});