first init
This commit is contained in:
1
.env.development
Normal file
1
.env.development
Normal file
@@ -0,0 +1 @@
|
||||
VITE_SERVER=http://localhost:8080
|
||||
1
.env.production
Normal file
1
.env.production
Normal file
@@ -0,0 +1 @@
|
||||
VITE_SERVER=http://localhost:8080
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal 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?
|
||||
11
.prettierrc
Normal file
11
.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"objectWrap": "preserve",
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always"
|
||||
}
|
||||
78
auto-imports.d.ts
vendored
Normal file
78
auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const isShallow: typeof import('vue')['isShallow']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
28
eslint.config.js
Normal file
28
eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import eslint from "@eslint/js";
|
||||
import eslintPluginVue from "eslint-plugin-vue";
|
||||
import globals from "globals";
|
||||
import typescriptEslint from "typescript-eslint";
|
||||
|
||||
export default typescriptEslint.config(
|
||||
{ ignores: ["*.d.ts", "**/coverage", "**/dist"] },
|
||||
{
|
||||
extends: [
|
||||
eslint.configs.recommended,
|
||||
...typescriptEslint.configs.recommended,
|
||||
...eslintPluginVue.configs["flat/recommended"],
|
||||
],
|
||||
files: ["**/*.{ts,vue}"],
|
||||
languageOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
parser: typescriptEslint.parser,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/max-attributes-per-line": "off",
|
||||
},
|
||||
},
|
||||
);
|
||||
35
github/workflows/deploy.yml
Normal file
35
github/workflows/deploy.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
name: 构建并部署前端项目
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: 拉取代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 安装Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: 安装依赖
|
||||
run: bun install
|
||||
|
||||
- name: 构建项目
|
||||
run: bun run build
|
||||
|
||||
- name: 部署到服务器
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
port: 22
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
password: ${{ secrets.SERVER_PASSWORD }}
|
||||
source: "dist/*"
|
||||
target: ${{ secrets.SERVER_TARGET_DIR }}
|
||||
rm: true
|
||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Hucky</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
45
package.json
Normal file
45
package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "template-vue-hucky",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"generate:page": "node --no-warnings=ExperimentalWarning --experimental-strip-types scripts/generatePage.ts",
|
||||
"gp": "node --no-warnings=ExperimentalWarning --experimental-strip-types scripts/generatePage.ts",
|
||||
"generate:component": "node --no-warnings=ExperimentalWarning --experimental-strip-types scripts/generateComponent.ts",
|
||||
"gc": "node --no-warnings=ExperimentalWarning --experimental-strip-types scripts/generateComponent.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"axios": "^1.11.0",
|
||||
"daisyui": "^5.0.50",
|
||||
"motion-v": "^1.6.1",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.4.1",
|
||||
"radash": "^12.1.1",
|
||||
"vue": "^3.5.17",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/node": "^24.1.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
||||
"@vue/babel-plugin-jsx": "^1.4.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.43.0",
|
||||
"unplugin-auto-import": "^20.1.0",
|
||||
"vite": "^7.0.0",
|
||||
"vue-tsc": "^2.2.10"
|
||||
}
|
||||
}
|
||||
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 888 KiB |
132
scripts/generateComponent.ts
Normal file
132
scripts/generateComponent.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as readline from "node:readline";
|
||||
|
||||
// Constants
|
||||
const PAGES_DIR = path.resolve(process.cwd(), "src/components");
|
||||
const TEMPLATE = `<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
`;
|
||||
|
||||
/**
|
||||
* Ensures the pages directory exists
|
||||
*/
|
||||
function ensurePagesDirectory() {
|
||||
if (!fs.existsSync(PAGES_DIR)) {
|
||||
console.log(`检测到 ${PAGES_DIR} 目录不存在, 正在为您创建该目录`);
|
||||
fs.mkdirSync(PAGES_DIR, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the component name to PascalCase and ensures it ends with .vue
|
||||
* @param name The input name to format
|
||||
* @returns Properly formatted component name
|
||||
*/
|
||||
function formatComponentName(name: string): string {
|
||||
// .vue extension if present
|
||||
if (name.endsWith(".vue")) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Page suffix if present
|
||||
if (name.endsWith("Component")) {
|
||||
return `${name}.vue`;
|
||||
}
|
||||
|
||||
// Convert to PascalCase
|
||||
let baseName = name
|
||||
.split(/[-_\s.]+/)
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
||||
.join("");
|
||||
|
||||
// Ensure name is not empty
|
||||
if (!baseName) {
|
||||
throw new Error("组件名称不能为空");
|
||||
}
|
||||
|
||||
// Add Page suffix if not already present
|
||||
if (!baseName.endsWith("Component")) {
|
||||
baseName += "Component";
|
||||
}
|
||||
|
||||
// Add .vue extension
|
||||
return `${baseName}.vue`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Vue component file
|
||||
* @param componentName The name of the component to create
|
||||
*/
|
||||
async function createComponent(componentName: string) {
|
||||
const formattedName = formatComponentName(componentName);
|
||||
const filePath = path.join(PAGES_DIR, formattedName);
|
||||
|
||||
// Check if file already exists
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.error(`❌ 失败: 组件 ${formattedName} 已存在于 ${filePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Write the file
|
||||
fs.writeFileSync(filePath, TEMPLATE);
|
||||
console.log(`✅ 成功: 创建组件 ${formattedName} at ${filePath}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to run the script
|
||||
*/
|
||||
async function main() {
|
||||
ensurePagesDirectory();
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
console.log("\n🚀 组件生成器");
|
||||
console.log("==========================");
|
||||
|
||||
try {
|
||||
const componentName = await new Promise<string>((resolve) => {
|
||||
rl.question(
|
||||
"✨ 请输入组件名称, 程序将自动转换为 PascalCase: (例如: about-me => AboutMeComponent)\n",
|
||||
(answer: string) => {
|
||||
resolve(answer.trim());
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (!componentName) {
|
||||
console.error("❌ 失败: 组件名称不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
await createComponent(componentName);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
main().catch(console.error);
|
||||
132
scripts/generatePage.ts
Normal file
132
scripts/generatePage.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as readline from "node:readline";
|
||||
|
||||
// Constants
|
||||
const PAGES_DIR = path.resolve(process.cwd(), "src/pages");
|
||||
const TEMPLATE = `<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
`;
|
||||
|
||||
/**
|
||||
* Ensures the pages directory exists
|
||||
*/
|
||||
function ensurePagesDirectory() {
|
||||
if (!fs.existsSync(PAGES_DIR)) {
|
||||
console.log(`检测到 ${PAGES_DIR} 目录不存在, 正在为您创建该目录`);
|
||||
fs.mkdirSync(PAGES_DIR, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the component name to PascalCase and ensures it ends with .vue
|
||||
* @param name The input name to format
|
||||
* @returns Properly formatted component name
|
||||
*/
|
||||
function formatComponentName(name: string): string {
|
||||
// .vue extension if present
|
||||
if (name.endsWith(".vue")) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Page suffix if present
|
||||
if (name.endsWith("Page")) {
|
||||
return `${name}.vue`;
|
||||
}
|
||||
|
||||
// Convert to PascalCase
|
||||
let baseName = name
|
||||
.split(/[-_\s.]+/)
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
||||
.join("");
|
||||
|
||||
// Ensure name is not empty
|
||||
if (!baseName) {
|
||||
throw new Error("页面名称不能为空");
|
||||
}
|
||||
|
||||
// Add Page suffix if not already present
|
||||
if (!baseName.endsWith("Page")) {
|
||||
baseName += "Page";
|
||||
}
|
||||
|
||||
// Add .vue extension
|
||||
return `${baseName}.vue`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Vue component file
|
||||
* @param componentName The name of the component to create
|
||||
*/
|
||||
async function createComponent(componentName: string) {
|
||||
const formattedName = formatComponentName(componentName);
|
||||
const filePath = path.join(PAGES_DIR, formattedName);
|
||||
|
||||
// Check if file already exists
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.error(`❌ 失败: 组件 ${formattedName} 已存在于 ${filePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Write the file
|
||||
fs.writeFileSync(filePath, TEMPLATE);
|
||||
console.log(`✅ 成功: 创建组件 ${formattedName} at ${filePath}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to run the script
|
||||
*/
|
||||
async function main() {
|
||||
ensurePagesDirectory();
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
console.log("\n🚀 页面生成器");
|
||||
console.log("==========================");
|
||||
|
||||
try {
|
||||
const componentName = await new Promise<string>((resolve) => {
|
||||
rl.question(
|
||||
"✨ 请输入页面名称, 程序将自动转换为 PascalCase: (例如: about-me => AboutMePage)\n",
|
||||
(answer: string) => {
|
||||
resolve(answer.trim());
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (!componentName) {
|
||||
console.error("❌ 失败: 页面名称不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
await createComponent(componentName);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
main().catch(console.error);
|
||||
9
src/App.vue
Normal file
9
src/App.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
19
src/apis/user.ts
Normal file
19
src/apis/user.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import http from "../utils/http";
|
||||
|
||||
/**
|
||||
* This is an example, please remove if not needed
|
||||
*/
|
||||
export const getUserList = () => {
|
||||
return http({
|
||||
url: "/user/list",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
export const insertUser = (data: { account: string; passowrd: string }) => {
|
||||
return http({
|
||||
url: "/user/insert",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
BIN
src/assets/hucky.png
Normal file
BIN
src/assets/hucky.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 888 KiB |
16
src/hooks/bus/changeUserBus.ts
Normal file
16
src/hooks/bus/changeUserBus.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createEventHook } from "@vueuse/core";
|
||||
|
||||
export interface ChangeUserBus {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export const changeUserEvent = createEventHook<ChangeUserBus>();
|
||||
|
||||
/**
|
||||
* This is an example, please remove if not needed
|
||||
*/
|
||||
export const changeUserBus = {
|
||||
emit: changeUserEvent.emit,
|
||||
on: changeUserEvent.on,
|
||||
off: changeUserEvent.off,
|
||||
};
|
||||
11
src/main.ts
Normal file
11
src/main.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createApp } from "vue";
|
||||
import "./style.css";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { createPinia } from "pinia";
|
||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
|
||||
createApp(App).use(router).use(pinia).mount("#app");
|
||||
19
src/pages/AboutPage.vue
Normal file
19
src/pages/AboutPage.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
// About page component
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hero min-h-screen bg-base-200">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold">关于我们</h1>
|
||||
<p class="py-6">这是一个自动路由注册的示例页面</p>
|
||||
<p>路径将自动转换为 /about</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Component styles */
|
||||
</style>
|
||||
77
src/pages/HelpPage.vue
Normal file
77
src/pages/HelpPage.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script lang="ts" setup>
|
||||
import { range } from "radash";
|
||||
import { usePagination } from "@/utils/pagination";
|
||||
|
||||
const merchandise = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: "商品1",
|
||||
},
|
||||
]);
|
||||
|
||||
const { currentPage, changePage, pageNumbers, totalPages, getPaginatedData } =
|
||||
usePagination(() => merchandise.value);
|
||||
const handlePageChange = (page: number) => {
|
||||
changePage(page);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await new Promise(() => {
|
||||
setTimeout(() => {
|
||||
merchandise.value = Array.from(range(1, 50)).map((item) => ({
|
||||
id: item,
|
||||
name: `商品${item}`,
|
||||
}));
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hero min-h-screen bg-base-200">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold">帮助</h1>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="item in getPaginatedData" :key="item.id">
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button
|
||||
class="join-item btn btn-sm"
|
||||
:disabled="currentPage === 1"
|
||||
@click="handlePageChange(currentPage - 1)"
|
||||
>
|
||||
«
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-for="page in pageNumbers"
|
||||
:key="page"
|
||||
class="join-item btn btn-sm"
|
||||
:class="{
|
||||
'btn-active': page === currentPage,
|
||||
'btn-disabled': page === '...',
|
||||
}"
|
||||
@click="page !== '...' && handlePageChange(Number(page))"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="join-item btn btn-sm"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="handlePageChange(currentPage + 1)"
|
||||
>
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Component styles */
|
||||
</style>
|
||||
18
src/pages/HomePage.vue
Normal file
18
src/pages/HomePage.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
// Home page component
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hero min-h-screen bg-base-200">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold">首页</h1>
|
||||
<p class="py-6">欢迎使用自动路由系统</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Component styles */
|
||||
</style>
|
||||
15
src/pages/panel/MePage.vue
Normal file
15
src/pages/panel/MePage.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<template>
|
||||
<div class="hero min-h-screen bg-base-200">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold">我的</h1>
|
||||
<p class="py-6">这是一个自动路由注册的示例页面</p>
|
||||
<p>路径将自动转换为 /panel/me</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
98
src/router/index.ts
Normal file
98
src/router/index.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
createRouter,
|
||||
createWebHashHistory,
|
||||
type RouteRecordRaw,
|
||||
} from "vue-router";
|
||||
|
||||
/**
|
||||
* Convert PascalCase to kebab-case and remove 'Page' suffix
|
||||
* @param name PascalCase name (e.g., AboutMePage)
|
||||
* @returns kebab-case path (e.g., about-me)
|
||||
*/
|
||||
function formatPathFromComponentName(name: string): string {
|
||||
// Remove .vue extension if present
|
||||
const baseName = name.endsWith(".vue") ? name.slice(0, -4) : name;
|
||||
|
||||
// Remove Page suffix if present
|
||||
const withoutPageSuffix = baseName.endsWith("Page")
|
||||
? baseName.slice(0, -4)
|
||||
: baseName;
|
||||
|
||||
// Convert PascalCase to kebab-case
|
||||
return withoutPageSuffix
|
||||
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
||||
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract component name from file path
|
||||
* @param filePath Path to the component file
|
||||
* @returns Component name without extension
|
||||
*/
|
||||
function getComponentNameFromPath(filePath: string): string {
|
||||
// Get the file name from the path
|
||||
return filePath.split("/").pop() || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate route path from full file path, preserving directory structure
|
||||
* @param filePath Full path to the component file (e.g., "@/pages/test/AbcPage.vue")
|
||||
* @returns Route path (e.g., "/test/abc")
|
||||
*/
|
||||
function generateRoutePathFromFilePath(filePath: string): string {
|
||||
// Remove the "@/pages" prefix and get the relative path
|
||||
const relativePath = filePath.replace(/^@\/pages\//, "");
|
||||
|
||||
// Split into directory parts and filename
|
||||
const pathParts = relativePath.split("/");
|
||||
const fileName = pathParts.pop() || "";
|
||||
const directories = pathParts.splice(3);
|
||||
|
||||
// Transform the filename using existing logic
|
||||
const transformedFileName = formatPathFromComponentName(fileName);
|
||||
|
||||
// Combine directory path with transformed filename
|
||||
const fullPath =
|
||||
directories.length > 0
|
||||
? `/${directories.join("/")}/${transformedFileName}`
|
||||
: `/${transformedFileName}`;
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate routes from page components using Vite's import.meta.glob
|
||||
* @returns Array of route configurations
|
||||
*/
|
||||
function generateRoutesFromPages(): RouteRecordRaw[] {
|
||||
const routes: RouteRecordRaw[] = [];
|
||||
|
||||
const pages = import.meta.glob("@/pages/**/*.vue");
|
||||
|
||||
for (const path in pages) {
|
||||
const componentName = getComponentNameFromPath(path);
|
||||
const routePath = generateRoutePathFromFilePath(path);
|
||||
|
||||
// Special case for home page
|
||||
const finalPath = routePath.toLowerCase() === "/home" ? "/" : routePath;
|
||||
|
||||
routes.push({
|
||||
path: finalPath,
|
||||
name: componentName,
|
||||
component: pages[path],
|
||||
});
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
// Generate routes from pages directory
|
||||
const routes: RouteRecordRaw[] = generateRoutesFromPages();
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
||||
21
src/stores/UserStore.ts
Normal file
21
src/stores/UserStore.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useUserStore = defineStore(
|
||||
"user",
|
||||
() => {
|
||||
const token = ref("");
|
||||
|
||||
const setToken = (newToken: string) => {
|
||||
token.value = newToken;
|
||||
};
|
||||
|
||||
return {
|
||||
token,
|
||||
setToken,
|
||||
};
|
||||
},
|
||||
{
|
||||
persist: true,
|
||||
},
|
||||
);
|
||||
29
src/style.css
Normal file
29
src/style.css
Normal file
@@ -0,0 +1,29 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui";
|
||||
|
||||
/* Custom Scrollbar Styles */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 10px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Firefox scrollbar styles */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
5
src/types/http.d.ts
vendored
Normal file
5
src/types/http.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare interface Result<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
92
src/utils/confirm.tsx
Normal file
92
src/utils/confirm.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { createApp, ref } from "vue";
|
||||
import { AnimatePresence, motion } from "motion-v";
|
||||
|
||||
export interface ConfirmOptions {
|
||||
title?: string;
|
||||
message: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
onConfirm?: () => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
export function confirm(options: ConfirmOptions): void {
|
||||
const confirmHideFlag = ref(false);
|
||||
const defaultOptions = {
|
||||
title: "确认",
|
||||
confirmText: "确定",
|
||||
cancelText: "取消",
|
||||
onConfirm: () => {},
|
||||
onCancel: () => {},
|
||||
...options,
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
confirmHideFlag.value = true;
|
||||
defaultOptions.onConfirm?.();
|
||||
setTimeout(() => {
|
||||
confirmApp.unmount();
|
||||
document.body.removeChild(confirmContainer);
|
||||
}, 300); // 动画结束后移除
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
confirmHideFlag.value = true;
|
||||
defaultOptions.onCancel?.();
|
||||
setTimeout(() => {
|
||||
confirmApp.unmount();
|
||||
document.body.removeChild(confirmContainer);
|
||||
}, 300); // 动画结束后移除
|
||||
};
|
||||
|
||||
const confirmInstance = () => (
|
||||
<AnimatePresence>
|
||||
{confirmHideFlag.value ? null : (
|
||||
<div class="fixed inset-0 flex items-center justify-center z-50 bg-[#00000050]">
|
||||
<motion.div
|
||||
class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 px-2"
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
>
|
||||
{defaultOptions.title && (
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
{defaultOptions.title}
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
<div class="px-6 py-4">
|
||||
<p class="text-gray-700">{defaultOptions.message}</p>
|
||||
</div>
|
||||
<div class="px-6 py-3 flex justify-end space-x-3">
|
||||
{defaultOptions.cancelText && (
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
{defaultOptions.cancelText}
|
||||
</button>
|
||||
)}
|
||||
{defaultOptions.confirmText && (
|
||||
<button
|
||||
onClick={handleConfirm}
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
{defaultOptions.confirmText}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
||||
const confirmContainer = document.createElement("div");
|
||||
document.body.appendChild(confirmContainer);
|
||||
|
||||
const confirmApp = createApp(confirmInstance);
|
||||
confirmApp.mount(confirmContainer);
|
||||
}
|
||||
26
src/utils/http.ts
Normal file
26
src/utils/http.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import axios, { type AxiosRequestConfig } from "axios";
|
||||
import { useUserStore } from "@/stores/UserStore.ts";
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: import.meta.env.VITE_SERVER,
|
||||
});
|
||||
|
||||
instance.interceptors.request.use((config) => {
|
||||
const store = useUserStore();
|
||||
if (!store.token) {
|
||||
return config;
|
||||
}
|
||||
const token = store.token;
|
||||
if (!token) {
|
||||
return config;
|
||||
}
|
||||
config.headers["token"] = token;
|
||||
return config;
|
||||
});
|
||||
|
||||
const http = async <T>(config: AxiosRequestConfig): Promise<Result<T>> => {
|
||||
const { data } = await instance.request<Result<T>>(config);
|
||||
return data;
|
||||
};
|
||||
|
||||
export default http;
|
||||
158
src/utils/pagination.ts
Normal file
158
src/utils/pagination.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { ref, computed, ComputedRef, Ref } from "vue";
|
||||
|
||||
export interface PaginationOptions {
|
||||
pageSize?: number;
|
||||
initialPage?: number;
|
||||
maxVisiblePages?: number;
|
||||
}
|
||||
|
||||
export interface PaginationResult<T> {
|
||||
// Pagination state
|
||||
currentPage: Ref<number>;
|
||||
pageSize: Ref<number>;
|
||||
totalPages: ComputedRef<number>;
|
||||
pageNumbers: ComputedRef<(number | string)[]>;
|
||||
|
||||
// Pagination methods
|
||||
changePage: (page: number) => void;
|
||||
nextPage: () => void;
|
||||
prevPage: () => void;
|
||||
|
||||
// Data methods
|
||||
getPaginatedData: ComputedRef<T[]>;
|
||||
setTotalItems: (count: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create pagination functionality for a list of items
|
||||
*
|
||||
* @param data Ref to the full data array or a function that returns the full data
|
||||
* @param options Pagination options
|
||||
* @returns Pagination state and methods
|
||||
*/
|
||||
export function usePagination<T>(
|
||||
data: Ref<T[]> | (() => T[]),
|
||||
options: PaginationOptions = {},
|
||||
): PaginationResult<T> {
|
||||
// Initialize pagination state
|
||||
const currentPage = ref(options.initialPage || 1);
|
||||
const pageSize = ref(options.pageSize || 10);
|
||||
const maxVisiblePages = options.maxVisiblePages || 5;
|
||||
|
||||
// Calculate if data is a ref or a function
|
||||
const isDataRef = "value" in data;
|
||||
|
||||
// Calculate total items
|
||||
const totalItems = computed(() => {
|
||||
if (isDataRef) {
|
||||
return (data as Ref<T[]>).value.length;
|
||||
} else {
|
||||
return (data as () => T[])().length;
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate total pages
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalItems.value / pageSize.value);
|
||||
});
|
||||
|
||||
// Get paginated data
|
||||
const getPaginatedData = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value;
|
||||
const end = start + pageSize.value;
|
||||
|
||||
if (isDataRef) {
|
||||
return (data as Ref<T[]>).value.slice(start, end);
|
||||
} else {
|
||||
const fullData = (data as () => T[])();
|
||||
return fullData.slice(start, end);
|
||||
}
|
||||
});
|
||||
|
||||
// Generate array of page numbers for pagination
|
||||
const pageNumbers = computed(() => {
|
||||
const pages: (number | string)[] = [];
|
||||
|
||||
if (totalPages.value <= maxVisiblePages) {
|
||||
// Show all pages if total pages are less than max visible
|
||||
for (let i = 1; i <= totalPages.value; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
// Always show first page
|
||||
pages.push(1);
|
||||
|
||||
// Calculate start and end of visible page range
|
||||
let startPage = Math.max(2, currentPage.value - 1);
|
||||
let endPage = Math.min(totalPages.value - 1, currentPage.value + 1);
|
||||
|
||||
// Adjust if we're near the beginning
|
||||
if (currentPage.value <= 3) {
|
||||
endPage = Math.min(totalPages.value - 1, 4);
|
||||
}
|
||||
|
||||
// Adjust if we're near the end
|
||||
if (currentPage.value >= totalPages.value - 2) {
|
||||
startPage = Math.max(2, totalPages.value - 3);
|
||||
}
|
||||
|
||||
// Add ellipsis if needed before visible pages
|
||||
if (startPage > 2) {
|
||||
pages.push("...");
|
||||
}
|
||||
|
||||
// Add visible page numbers
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
// Add ellipsis if needed after visible pages
|
||||
if (endPage < totalPages.value - 1) {
|
||||
pages.push("...");
|
||||
}
|
||||
|
||||
// Always show last page
|
||||
pages.push(totalPages.value);
|
||||
}
|
||||
|
||||
return pages;
|
||||
});
|
||||
|
||||
// Change page
|
||||
const changePage = (page: number) => {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
currentPage.value = page;
|
||||
}
|
||||
};
|
||||
|
||||
// Next page
|
||||
const nextPage = () => {
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++;
|
||||
}
|
||||
};
|
||||
|
||||
// Previous page
|
||||
const prevPage = () => {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--;
|
||||
}
|
||||
};
|
||||
|
||||
// Set total items (useful for API pagination)
|
||||
const setTotalItems = (count: number) => {
|
||||
totalItems.value = count;
|
||||
};
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
pageSize,
|
||||
totalPages,
|
||||
pageNumbers,
|
||||
changePage,
|
||||
nextPage,
|
||||
prevPage,
|
||||
getPaginatedData,
|
||||
setTotalItems,
|
||||
};
|
||||
}
|
||||
50
src/utils/toast.tsx
Normal file
50
src/utils/toast.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { createApp, ref } from "vue";
|
||||
import { AnimatePresence, motion } from "motion-v";
|
||||
|
||||
export interface ToastOptions {
|
||||
message: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export function toast(options: ToastOptions): void {
|
||||
const toastHideFlag = ref(false);
|
||||
|
||||
const toastInstance = () => (
|
||||
<AnimatePresence>
|
||||
{toastHideFlag.value ? null : (
|
||||
<motion.div
|
||||
class="toast toast-top toast-end bg-transparent"
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
exit={{ opacity: 0 }}
|
||||
initial={{ opacity: 0 }}
|
||||
>
|
||||
<div class="alert">
|
||||
<span>{options.message}</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
||||
const toastContainer = document.createElement("div");
|
||||
document.body.appendChild(toastContainer);
|
||||
|
||||
const toastApp = createApp(toastInstance);
|
||||
toastApp.mount(toastContainer);
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
toastHideFlag.value = true;
|
||||
},
|
||||
(options.duration || 3000) + 250, // 250ms for start animation
|
||||
);
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
toastApp.unmount();
|
||||
document.body.removeChild(toastContainer);
|
||||
},
|
||||
(options.duration || 3000) + 500, // 500ms for end animation
|
||||
);
|
||||
}
|
||||
9
src/vite-env.d.ts
vendored
Normal file
9
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_SERVER: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
19
tsconfig.app.json
Normal file
19
tsconfig.app.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"verbatimModuleSyntax": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "auto-imports.d.ts"]
|
||||
}
|
||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
26
tsconfig.node.json
Normal file
26
tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"types": ["node"],
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts", "scripts/**/*.ts", "auto-imports.d.ts"]
|
||||
}
|
||||
24
vite.config.ts
Normal file
24
vite.config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
vueJsx(),
|
||||
AutoImport({
|
||||
imports: ["vue", "vue-router"],
|
||||
dts: true,
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user