feat: 添加路由和仓库
This commit is contained in:
parent
e3c5d7333a
commit
d5acd952d6
2
.env
2
.env
|
@ -1,5 +1,5 @@
|
|||
# 应用名称
|
||||
VITE_APP_TITLE=DashboardTemplate
|
||||
VITE_APP_TITLE="Vite 模板"
|
||||
|
||||
# 平台本地运行端口号
|
||||
VITE_PORT=7000
|
||||
|
|
|
@ -11,4 +11,7 @@ VITE_APP_URL=http://localhost:8801
|
|||
VITE_STRICT_PORT=false
|
||||
|
||||
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
||||
VITE_CDN=gzip
|
||||
VITE_CDN=false
|
||||
|
||||
# 是否启用gzip压缩
|
||||
VITE_COMPRESSION=gzip
|
|
@ -1,5 +1,5 @@
|
|||
# 平台本地运行端口号
|
||||
VITE_PORT=8888
|
||||
VITE_PORT=8800
|
||||
|
||||
# 开发环境读取配置文件路径
|
||||
VITE_PUBLIC_PATH=/
|
||||
|
@ -11,4 +11,7 @@ VITE_APP_URL=http://localhost:8000
|
|||
VITE_STRICT_PORT=false
|
||||
|
||||
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
||||
VITE_CDN=gzip
|
||||
VITE_CDN=false
|
||||
|
||||
# 是否启用gzip压缩
|
||||
VITE_COMPRESSION=gzip
|
|
@ -0,0 +1,11 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -1,38 +1,46 @@
|
|||
// @see: https://www.prettier.cn
|
||||
module.exports = {
|
||||
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
|
||||
arrowParens: "always",
|
||||
// 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
|
||||
bracketSameLine: false,
|
||||
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
|
||||
bracketSpacing: true,
|
||||
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
|
||||
embeddedLanguageFormatting: "auto",
|
||||
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
|
||||
htmlWhitespaceSensitivity: "ignore",
|
||||
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false
|
||||
insertPragma: false,
|
||||
// 在 JSX 中使用单引号替代双引号,默认false
|
||||
jsxSingleQuote: false,
|
||||
// 每行最多字符数量,超出换行(默认100)
|
||||
printWidth: 100,
|
||||
// 超出打印宽度 (always | never | preserve )
|
||||
proseWrap: "preserve",
|
||||
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
|
||||
quoteProps: "as-needed",
|
||||
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件,默认false
|
||||
requirePragma: false,
|
||||
// 结尾添加分号
|
||||
semi: true,
|
||||
// 使用单引号 (true:单引号;false:双引号)
|
||||
singleQuote: false,
|
||||
// 缩进空格数,默认2个空格
|
||||
tabWidth: 2,
|
||||
// 元素末尾是否加逗号,默认es5: ES5中的 objects, arrays 等会添加逗号,TypeScript 中的 type 后不加逗号
|
||||
trailingComma: "es5",
|
||||
// 指定缩进方式,空格或tab,默认false,即使用空格
|
||||
useTabs: false,
|
||||
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
|
||||
vueIndentScriptAndStyle: false,
|
||||
|
||||
export default {
|
||||
// 超过最大值换行
|
||||
printWidth: 140,
|
||||
// 缩进字节数
|
||||
tabWidth: 1,
|
||||
// 使用制表符而不是空格缩进行
|
||||
useTabs: true,
|
||||
// 结尾不用分号(true有,false没有)
|
||||
semi: true,
|
||||
// 使用单引号(true单引号,false双引号)
|
||||
singleQuote: true,
|
||||
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
|
||||
quoteProps: 'as-needed',
|
||||
// 在对象,数组括号与文字之间加空格 "{ foo: bar }"
|
||||
bracketSpacing: true,
|
||||
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
|
||||
trailingComma: 'all',
|
||||
// 在JSX中使用单引号而不是双引号
|
||||
jsxSingleQuote: true,
|
||||
// (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 ,always:不省略括号
|
||||
arrowParens: 'avoid',
|
||||
// 如果文件顶部已经有一个 doclock,这个选项将新建一行注释,并打上@format标记。
|
||||
insertPragma: false,
|
||||
// 指定要使用的解析器,不需要写文件开头的 @prettier
|
||||
requirePragma: false,
|
||||
// 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
|
||||
proseWrap: 'preserve',
|
||||
// 在html中空格是否是敏感的 "css" - 遵守CSS显示属性的默认值, "strict" - 空格被认为是敏感的 ,"ignore" - 空格被认为是不敏感的
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
|
||||
endOfLine: 'auto',
|
||||
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
vueIndentScriptAndStyle: false, // Vue文件脚本和样式标签缩进
|
||||
endOfLine: "auto",
|
||||
overrides: [
|
||||
{
|
||||
files: "*.html",
|
||||
options: {
|
||||
parser: "html",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -62,7 +62,6 @@ export const cdn = importToCDN({
|
|||
|
||||
/* 是否使用CDN加速 */
|
||||
export const useCDN = mode => {
|
||||
const { VITE_CDN } = wrapperEnv(mode);
|
||||
|
||||
return VITE_CDN ? cdn : null;
|
||||
const env = wrapperEnv(mode, 'VITE');
|
||||
return env.VITE_CDN ? cdn : null;
|
||||
};
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
export const define = () => {
|
||||
return {};
|
||||
import dayjs from 'dayjs';
|
||||
import { dependencies, devDependencies, engines, name, version } from '../package.json';
|
||||
|
||||
const __APP_INFO__ = {
|
||||
pkg: { name, version, engines, dependencies, devDependencies },
|
||||
lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
|
||||
};
|
||||
|
||||
export const define = () => {
|
||||
return {
|
||||
__APP_INFO__: JSON.stringify(__APP_INFO__),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include里,否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存
|
||||
* 温馨提示:如果您使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite
|
||||
*/
|
||||
const include = ['dayjs', 'axios', 'pinia', 'vue-types', 'js-cookie'];
|
||||
const include = ['vue', 'vue-router', 'dayjs', 'axios', 'pinia', 'vue-types', 'js-cookie'];
|
||||
|
||||
/**
|
||||
* 在预构建中强制排除的依赖项
|
||||
|
|
|
@ -6,7 +6,39 @@ import { compressPack, report } from './utils';
|
|||
import removeConsole from 'vite-plugin-remove-console';
|
||||
import { useCDN } from './cdn';
|
||||
import { viteConsoleLog } from './info';
|
||||
import UnoCSS from 'unocss/vite';
|
||||
import { presetIcons, presetUno } from 'unocss';
|
||||
import UnoCssIcons from '@unocss/preset-icons';
|
||||
|
||||
export const plugins = (mode): PluginOption[] => {
|
||||
return [vue(), vueJsx(), Inspector(), report(), removeConsole(), useCDN(mode), viteConsoleLog(mode), compressPack(mode)];
|
||||
return [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
Inspector(),
|
||||
report(),
|
||||
removeConsole(),
|
||||
useCDN(mode),
|
||||
viteConsoleLog(mode),
|
||||
UnoCSS({
|
||||
hmrTopLevelAwait: false,
|
||||
|
||||
presets: [
|
||||
presetIcons({
|
||||
extraProperties: {
|
||||
display: 'inline-block',
|
||||
'vertical-align': 'middle',
|
||||
},
|
||||
}),
|
||||
UnoCssIcons({
|
||||
// 其他选项
|
||||
prefix: 'i-',
|
||||
extraProperties: {
|
||||
display: 'inline-block',
|
||||
},
|
||||
}),
|
||||
presetUno(),
|
||||
],
|
||||
}),
|
||||
compressPack(mode),
|
||||
];
|
||||
};
|
||||
|
|
|
@ -35,6 +35,18 @@ export const pathResolve = (dir = '.', metaUrl = import.meta.url) => {
|
|||
*/
|
||||
export const wrapperEnv = (mode, prefix = ''): ViteEnv => {
|
||||
const env = loadEnv(mode, root, prefix);
|
||||
|
||||
// 将变量转换指定类型
|
||||
for (const envName of Object.keys(env)) {
|
||||
let realName = env[envName].replace(/\\n/g, '\n');
|
||||
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
|
||||
|
||||
if (envName === 'VITE_PORT') {
|
||||
realName = Number(realName);
|
||||
}
|
||||
env[envName] = realName;
|
||||
process.env[envName] = realName;
|
||||
}
|
||||
return env;
|
||||
};
|
||||
|
||||
|
|
20
index.html
20
index.html
|
@ -1,13 +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>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link href="/vite.svg" rel="icon" type="image/svg+xml"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/src/main.ts" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
18
package.json
18
package.json
|
@ -1,17 +1,21 @@
|
|||
{
|
||||
"name": "dashboard-template",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"report": "rimraf dist && vite build"
|
||||
"report": "rimraf dist && vite build",
|
||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
|
||||
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
||||
"@typescript-eslint/parser": "^8.24.1",
|
||||
"@unocss/preset-icons": "^66.0.0",
|
||||
"@unocss/reset": "^66.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.7.9",
|
||||
|
@ -26,13 +30,20 @@
|
|||
"gradient-string": "^3.0.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"list": "^2.0.19",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.3.1",
|
||||
"pinia-plugin-persistedstate": "^3.2.3",
|
||||
"prettier": "^3.3.3",
|
||||
"rimraf": "^5.0.10",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"sass": "^1.77.8",
|
||||
"stylelint": "^16.14.1",
|
||||
"stylelint-config-recess-order": "^6.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.6.0",
|
||||
"stylelint-config-standard-scss": "^14.0.0",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"terser": "^5.39.0",
|
||||
"unocss": "^66.0.0",
|
||||
"vite-plugin-cdn-import": "^1.0.1",
|
||||
"vite-plugin-remove-console": "^2.2.0",
|
||||
"vite-plugin-vue-inspector": "^5.3.1",
|
||||
|
@ -41,6 +52,7 @@
|
|||
"vue-types": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.310",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"typescript": "~5.7.2",
|
||||
|
|
1430
pnpm-lock.yaml
1430
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
14
src/App.vue
14
src/App.vue
|
@ -1,17 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import HelloWorld from './components/HelloWorld.vue';
|
||||
</script>
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img alt="Vite logo" class="logo" src="/vite.svg" />
|
||||
</a>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img alt="Vue logo" class="logo vue" src="./assets/vue.svg" />
|
||||
</a>
|
||||
</div>
|
||||
<HelloWorld msg="Vite + Vue" />
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,6 @@
|
|||
import type { App } from 'vue';
|
||||
|
||||
// 全局注册 directive
|
||||
export function setupDirective(app: App<Element>) {
|
||||
// 使 v-hasPerm 在所有组件中都可用
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts" setup></script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex items-center justify-center flex-wrap gap-x-4 text-4xl p-2 mt-4">
|
||||
<div class="w-full flex items-center justify-center mb-4">
|
||||
<button>清除默认样式的按钮</button>
|
||||
</div>
|
||||
<div class="i-ph-anchor-simple-thin" />
|
||||
<!-- 来自 Phosphor 图标的基本锚点图标 -->
|
||||
<div class="i-ph-anchor-simple-thin" />
|
||||
<!-- 来自 Material Design Icons 的一个橙色闹钟 -->
|
||||
<div class="i-mdi-alarm text-orange-400" />
|
||||
<!-- 一个大尺寸的 Vue 标志 -->
|
||||
<div class="i-logos-vue text-3xl" />
|
||||
<!-- 太阳在亮模式,月亮在暗模式,来自 Carbon -->
|
||||
<button class="i-carbon-sun dark:i-carbon-moon" />
|
||||
<!-- Twemoji 笑脸,悬停时变成流泪表情 -->
|
||||
<div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" />
|
||||
<div class="i-vscode-icons:file-type-light-pnpm" />
|
||||
<div class="i-vscode-icons:file-type-light-pnpm?mask text-red-300" />
|
||||
<span class="i-ic:baseline-16mp" />
|
||||
<span class="i-vscode-icons:file-type-java" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,5 +1,9 @@
|
|||
import { createApp } from 'vue';
|
||||
import './style.css';
|
||||
import 'animate.css';
|
||||
import App from './App.vue';
|
||||
import plugins from '@/plugins';
|
||||
import '@unocss/reset/tailwind-compat.css';
|
||||
import 'uno.css';
|
||||
import 'virtual:unocss-devtools';
|
||||
|
||||
createApp(App).mount('#app');
|
||||
createApp(App).use(plugins).mount('#app');
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import type { App } from 'vue';
|
||||
import { setupDirective } from '@/directive';
|
||||
import { setUpRouter } from '@/router';
|
||||
import { setupStore } from '@/store';
|
||||
|
||||
export default {
|
||||
install(app: App<Element>) {
|
||||
// 设置路由
|
||||
setUpRouter(app);
|
||||
// 设置状态管理
|
||||
setupStore(app);
|
||||
// 设置指令
|
||||
setupDirective(app);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
import type { App } from 'vue';
|
||||
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router';
|
||||
|
||||
export const Layout = () => import('@/layout/index.vue');
|
||||
|
||||
// 静态路由
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/redirect',
|
||||
component: Layout,
|
||||
meta: { hidden: true },
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path(.*)',
|
||||
component: () => import('@/views/redirect/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: '/',
|
||||
component: Layout,
|
||||
// redirect: '/dashboard',
|
||||
children: [
|
||||
// {
|
||||
// path: 'dashboard',
|
||||
// component: () => import('@/views/index.vue'),
|
||||
// // 用于 keep-alive 功能,需要与 SFC 中自动推导或显式声明的组件名称一致
|
||||
// // 参考文档: https://cn.vuejs.org/guide/built-ins/keep-alive.html#include-exclude
|
||||
// name: 'Dashboard',
|
||||
// meta: {
|
||||
// title: 'dashboard',
|
||||
// icon: 'homepage',
|
||||
// affix: true,
|
||||
// keepAlive: true,
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('@/views/error-page/404.vue'),
|
||||
meta: { hidden: true },
|
||||
},
|
||||
];
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
scrollBehavior: () => ({ x: 0, y: 0 }),
|
||||
});
|
||||
|
||||
/** 全局注册 router */
|
||||
export const setUpRouter = (app: App<Element>) => {
|
||||
app.use(router);
|
||||
};
|
||||
|
||||
/** 重置路由 */
|
||||
export const resetRouter = () => {
|
||||
router.replace({ path: '/' }).then();
|
||||
};
|
||||
export default router;
|
|
@ -0,0 +1,9 @@
|
|||
import type { App } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
const store = createPinia();
|
||||
|
||||
// 全局注册 store
|
||||
export function setupStore(app: App<Element>) {
|
||||
app.use(store);
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
:root {
|
||||
font-family: Inter, 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;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
declare global {
|
||||
/* 环境便配置 */
|
||||
interface ViteEnv {
|
||||
declare interface ViteEnv {
|
||||
VITE_APP_TITLE: string;
|
||||
VITE_PORT: number;
|
||||
VITE_PUBLIC_PATH: string;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
type Recordable<T = any> = Record<string, T>;
|
|
@ -0,0 +1,18 @@
|
|||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
// 进度条
|
||||
NProgress.configure({
|
||||
// 动画方式
|
||||
easing: 'ease',
|
||||
// 递增进度条的速度
|
||||
speed: 500,
|
||||
// 是否显示加载ico
|
||||
showSpinner: false,
|
||||
// 自动递增间隔
|
||||
trickleSpeed: 200,
|
||||
// 初始化时的最小百分比
|
||||
minimum: 0.3,
|
||||
});
|
||||
|
||||
export default NProgress;
|
|
@ -0,0 +1,71 @@
|
|||
import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
|
||||
import { useUserStoreHook } from '@/store/modules/user';
|
||||
import { ResultEnum } from '@/enums/ResultEnum';
|
||||
import { TOKEN_KEY } from '@/enums/CacheEnum';
|
||||
import qs from 'qs';
|
||||
|
||||
// 创建 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: 50000,
|
||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||
paramsSerializer: params => {
|
||||
return qs.stringify(params);
|
||||
},
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
const accessToken = localStorage.getItem(TOKEN_KEY);
|
||||
if (accessToken) {
|
||||
config.headers.Authorization = accessToken;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error: any) => {
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer'), 如果是,直接返回响应对象
|
||||
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
|
||||
return response;
|
||||
}
|
||||
|
||||
const { code, data, msg } = response.data;
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
return data;
|
||||
}
|
||||
|
||||
ElMessage.error(msg || '系统出错');
|
||||
return Promise.reject(new Error(msg || 'Error'));
|
||||
},
|
||||
(error: any) => {
|
||||
// 异常处理
|
||||
if (error.response.data) {
|
||||
const { code, msg } = error.response.data;
|
||||
if (code === ResultEnum.TOKEN_INVALID) {
|
||||
ElNotification({
|
||||
title: '提示',
|
||||
message: '您的会话已过期,请重新登录',
|
||||
type: 'info',
|
||||
});
|
||||
useUserStoreHook()
|
||||
.resetToken()
|
||||
.then(() => {
|
||||
location.reload();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error(msg || '系统出错');
|
||||
}
|
||||
}
|
||||
return Promise.reject(error.message);
|
||||
},
|
||||
);
|
||||
|
||||
// 导出 axios 实例
|
||||
export default service;
|
|
@ -0,0 +1,232 @@
|
|||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="pic-404">
|
||||
<img alt="404" class="pic-404__parent" src="@/assets/images/404.png" />
|
||||
<img alt="404" class="pic-404__child left" src="@/assets/images/404_cloud.png" />
|
||||
<img alt="404" class="pic-404__child mid" src="@/assets/images/404_cloud.png" />
|
||||
<img alt="404" class="pic-404__child right" src="@/assets/images/404_cloud.png" />
|
||||
</div>
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__oops">OOPS!</div>
|
||||
<div class="bullshit__info">
|
||||
All rights reserved
|
||||
<a href="https://wallstreetcn.com" style="color: #20a0ff" target="_blank">wallstreetcn</a>
|
||||
</div>
|
||||
<div class="bullshit__headline">The webmaster said that you can not enter this page...</div>
|
||||
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
|
||||
<a class="bullshit__return-home" href="/" @click.prevent="router.replace('/')"> Back to home </a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
display: flex;
|
||||
padding: 100px;
|
||||
|
||||
.pic-404 {
|
||||
width: 600px;
|
||||
overflow: hidden;
|
||||
|
||||
&__parent {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__child {
|
||||
&.left {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
width: 80px;
|
||||
opacity: 0;
|
||||
animation-name: cloudLeft;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&.mid {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
width: 46px;
|
||||
opacity: 0;
|
||||
animation-name: cloudMid;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&.right {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
width: 62px;
|
||||
opacity: 0;
|
||||
animation-name: cloudRight;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bullshit {
|
||||
width: 300px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
|
||||
&__oops {
|
||||
margin-bottom: 20px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: #1482f0;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&__headline {
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&__info {
|
||||
margin-bottom: 30px;
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: grey;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&__return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: #1482f0;
|
||||
border-radius: 100px;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(60px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts" setup></script>
|
||||
|
||||
<template>初始化页面</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const { params, query } = route;
|
||||
const { path } = params;
|
||||
|
||||
router.replace({ path: '/' + path, query });
|
||||
</script>
|
|
@ -0,0 +1,51 @@
|
|||
module.exports = {
|
||||
// 继承推荐规范配置
|
||||
extends: [
|
||||
'stylelint-config-standard',
|
||||
'stylelint-config-recommended-scss',
|
||||
'stylelint-config-recommended-vue/scss',
|
||||
'stylelint-config-html/vue',
|
||||
'stylelint-config-recess-order',
|
||||
],
|
||||
// 指定不同文件对应的解析器
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.{vue,html}'],
|
||||
customSyntax: 'postcss-html',
|
||||
},
|
||||
{
|
||||
files: ['**/*.{css,scss}'],
|
||||
customSyntax: 'postcss-scss',
|
||||
},
|
||||
],
|
||||
// 自定义规则
|
||||
rules: {
|
||||
'import-notation': 'string', // 指定导入CSS文件的方式("string"|"url")
|
||||
'selector-class-pattern': null, // 选择器类名命名规则
|
||||
'custom-property-pattern': null, // 自定义属性命名规则
|
||||
'keyframes-name-pattern': null, // 动画帧节点样式命名规则
|
||||
'no-descending-specificity': null, // 允许无降序特异性
|
||||
'no-empty-source': null, // 允许空样式
|
||||
// 允许 global 、export 、deep伪类
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ['global', 'export', 'deep'],
|
||||
},
|
||||
],
|
||||
// 允许未知属性
|
||||
'property-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreProperties: [],
|
||||
},
|
||||
],
|
||||
// 允许未知规则
|
||||
'at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: ['apply', 'use', 'forward'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"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"]
|
||||
}
|
|
@ -1,13 +1,38 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"noLib": false,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "preserve",
|
||||
"jsxFactory": "h",
|
||||
"jsxFragmentFactory": "Fragment",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
"vite/client",
|
||||
"unplugin-icons/types/vue",
|
||||
"element-plus/global"
|
||||
]
|
||||
},
|
||||
"files": [],
|
||||
"include": [
|
||||
"mock/*.ts",
|
||||
"src/**/*.ts",
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import {
|
||||
defineConfig,
|
||||
presetAttributify,
|
||||
presetIcons,
|
||||
presetTypography,
|
||||
presetUno,
|
||||
presetWebFonts,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from 'unocss';
|
||||
|
||||
export default defineConfig({
|
||||
shortcuts: [
|
||||
// ...
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
// ...
|
||||
},
|
||||
},
|
||||
presets: [
|
||||
presetUno(),
|
||||
presetAttributify(),
|
||||
presetIcons(),
|
||||
presetTypography(),
|
||||
presetWebFonts({
|
||||
fonts: {
|
||||
// ...
|
||||
},
|
||||
}),
|
||||
],
|
||||
transformers: [transformerDirectives(), transformerVariantGroup()],
|
||||
});
|
Loading…
Reference in New Issue