feat:大更新

This commit is contained in:
fangyunong 2025-07-04 22:45:22 +08:00
parent b231b77043
commit a29bba3406
16 changed files with 218 additions and 147 deletions

2
.env
View File

@ -1 +1 @@
VITE_API_BASE_URL=http://your-api-url.com VITE_API_BASE_URL=http://localhost:3000

1
auto-imports.d.ts vendored
View File

@ -22,6 +22,7 @@ declare global {
const isReadonly: typeof import('vue')['isReadonly'] const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef'] const isRef: typeof import('vue')['isRef']
const login: typeof import('./src/api/loginApi')['login'] const login: typeof import('./src/api/loginApi')['login']
const loginApiTest: typeof import('./src/api/loginApi')['loginApiTest']
const markRaw: typeof import('vue')['markRaw'] const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick'] const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated'] const onActivated: typeof import('vue')['onActivated']

View File

@ -1,7 +1,10 @@
import http from "@/utils/request"; import http from "@/utils/request";
//demo //demo
export const login = () => { export const loginApiTest = () => {
return http.get('/get-user') return http({
url:'/search',
method:'get'
})
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@ -0,0 +1,12 @@
.main__container {
height: 100%;
width: 100%;
&.table {
overflow: hidden; //表格需要hidden
}
&.white-bg {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
}

View File

@ -1,9 +1,16 @@
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import NProgress from "nprogress"; // progress bar
import "nprogress/nprogress.css"; // progress bar style
import Login from "@/views/login/index.vue"; //登录组件 import Login from "@/views/login/index.vue"; //登录组件
import Layout from "@/views/layout/index.vue"; //首页布局 import Layout from "@/views/layout/index.vue"; //首页布局
import CallBack from "@/views/login/OauthCallBack.vue"; //反馈页面 import CallBack from "@/views/login/OauthCallBack.vue"; //反馈页面
import Home from '@/views/home/index.vue'; //家 import Home from "@/views/home/index.vue"; //家
import Auth from '@/views/role/pages/Auth.vue'; //权限管理 import Auth from "@/views/role/pages/Auth.vue"; //权限管理
import Menu from '@/views/system/menu/index.vue'; //动态权限菜单
import { getToken } from "@/utils/auth";
const whiteList = ["/callback"];
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
{ {
@ -20,19 +27,24 @@ const routes: Array<RouteRecordRaw> = [
path: "/layout", path: "/layout",
name: "Layout", name: "Layout",
component: Layout, component: Layout,
children:[ children: [
{ {
path:'', path: "",
name:'home', name: "home",
component:Home, component: Home,
}, },
// 后续用动态菜单 // 后续用动态菜单
{ {
path:'role', path: "role",
name:'roleAuth', name: "roleAuth",
component:Auth component: Auth,
} },
] {
path: "menu",
name: "menu",
component: Menu,
},
],
}, },
]; ];
@ -41,10 +53,20 @@ const router = createRouter({
routes, routes,
}); });
// 在这里添加路由的导航守卫
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
console.log("Navigating to:", to.path); const token = getToken();
next(); NProgress.start();
if (to.path === "/") {
token ? next("/callback") : next(); // 有 token 直接去 /layout
} else if (whiteList.includes(to.path)) {
next(); // 放行 /callback
} else {
token ? next() : next("/"); // 其他页面检查 token
}
});
router.afterEach(() => {
NProgress.done(); // finish progress bar
}); });
export default router; export default router;

View File

@ -1,5 +1,5 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { getToken, removeToken } from './auth'; import { getToken, removeToken } from "./auth";
// 定义后端返回的统一数据结构 // 定义后端返回的统一数据结构
interface ApiResponse<T = any> { interface ApiResponse<T = any> {
@ -8,106 +8,65 @@ interface ApiResponse<T = any> {
msg: string; msg: string;
} }
// 创建 Axios 实例 const instance = axios.create({
const createAxiosInstance = (): AxiosInstance => { baseURL: `${import.meta.env.VITE_API_BASE_URL as string}/api`, // 从 Vite 环境变量获取 baseURL
const instance = axios.create({ timeout: 10000, // 请求超时时间
baseURL: import.meta.env.VITE_API_BASE_URL as string, // 从 Vite 环境变量获取 baseURL headers: {
timeout: 10000, // 请求超时时间 "Content-Type": "application/json",
headers: { },
'Content-Type': 'application/json', });
},
});
// 请求拦截器 // 请求拦截器
instance.interceptors.request.use( instance.interceptors.request.use(
(config) => { (config) => {
// 添加 token 等请求头 // 添加 token 等请求头
const token = getToken() const token = getToken();
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
} }
); return config;
},
// 响应拦截器 (error) => {
instance.interceptors.response.use( return Promise.reject(error);
(response: AxiosResponse<ApiResponse>) => {
const { code, msg } = response.data;
// 401 未授权,跳转首页
if (code === 401) {
// 这里调用退出登录的逻辑
console.log('未授权,跳转首页');
// 清除用户信息
removeToken();
// 跳转首页
window.location.href = '/'; //后续用发布订阅模式修改
return Promise.reject(new Error(msg || '未授权'));
}
// 其他非 200 状态码
if (code !== 200) {
console.error(`请求错误 ${code}: ${msg}`);
// 这里可以根据需要弹出错误提示
return Promise.reject(new Error(msg || '请求错误'));
}
// 返回数据部分
return response.data.data;
},
(error) => {
// 处理 HTTP 状态码不是 200 的情况
if (error.response) {
const { status, data } = error.response;
console.error(`HTTP 错误 ${status}:`, data);
} else {
console.error('请求错误:', error.message);
}
return Promise.reject(error);
}
);
return instance;
};
const http = createAxiosInstance();
// 封装通用的 request 函数
async function request<T = any>(config: AxiosRequestConfig): Promise<T> {
try {
const { data } = await http.request<ApiResponse<T>>(config);
return data.data;
} catch (error) {
// 这里可以统一处理错误,或者让调用者自己处理
throw error;
} }
} );
// 封装常用的 HTTP 方法 // 响应拦截器
const httpClient = { instance.interceptors.response.use(
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> { (response: AxiosResponse<ApiResponse>) => {
return request<T>({ ...config, method: 'GET', url }); const { code, msg } = response.data;
// 401 未授权,跳转首页
if (code === 401) {
// 这里调用退出登录的逻辑
console.log("未授权,跳转首页");
// 清除用户信息
removeToken();
// 跳转首页
window.location.href = "/"; //后续用发布订阅模式修改
return Promise.reject(new Error(msg || "未授权"));
}
// 其他非 200 状态码
if (code !== 200) {
console.error(`请求错误 ${code}: ${msg}`);
// 这里可以根据需要弹出错误提示
return Promise.reject(new Error(msg || "请求错误"));
}
// 返回数据部分
return response.data.data;
}, },
(error) => {
// 处理 HTTP 状态码不是 200 的情况
if (error.response) {
const { status, data } = error.response;
console.error(`HTTP 错误 ${status}:`, data);
} else {
console.error("请求错误:", error.message);
}
return Promise.reject(error);
}
);
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> { export default instance;
return request<T>({ ...config, method: 'POST', url, data });
},
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return request<T>({ ...config, method: 'PUT', url, data });
},
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return request<T>({ ...config, method: 'DELETE', url });
},
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return request<T>({ ...config, method: 'PATCH', url, data });
},
};
export default httpClient;

View File

@ -1,8 +1,12 @@
<template> <template>
<div>我老家</div> <div class="main__container">我老家</div>
</template> </template>
<script setup lang='ts'> <script setup lang='ts'>
</script> </script>
<style scoped lang='scss'> <style scoped lang='scss'>
.main__container{
background:url('../../assets/images/home_bg.webp');
background-size: cover;
}
</style> </style>

View File

@ -24,6 +24,7 @@
</template> </template>
<script setup lang='ts'> <script setup lang='ts'>
import { removeToken } from '@/utils/auth';
import { useAuth0 } from '@auth0/auth0-vue'; import { useAuth0 } from '@auth0/auth0-vue';
import { import {
Pencil as EditIcon, Pencil as EditIcon,
@ -60,10 +61,11 @@ const options = [
]; ];
const handleSelect = (key: string | number) => { const handleSelect = (key: string | number) => {
if(key === 'logout'){ if (key === 'logout') {
logout({ logoutParams: { returnTo: window.location.origin } }); removeToken()
router.push('/'); logout({ logoutParams: { returnTo: window.location.origin } });
} router.push('/');
}
} }
</script> </script>
<style scoped lang='scss'> <style scoped lang='scss'>

View File

@ -19,13 +19,14 @@ import {
CaretDownOutline, CaretDownOutline,
HomeSharp, HomeSharp,
PersonCircleSharp, PersonCircleSharp,
Layers Layers,
Settings,
Menu
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { MenuOption, NIcon } from 'naive-ui'; import { MenuOption, NIcon } from 'naive-ui';
import { useAppStore } from '@/store/app'; import { useAppStore } from '@/store/app';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { RouterLink } from 'vue-router'; import { RouterLink } from 'vue-router';
import { h } from 'vue'
const appStore = useAppStore(); const appStore = useAppStore();
const activeKey = ref('default'); const activeKey = ref('default');
const { menuApp } = storeToRefs(appStore) const { menuApp } = storeToRefs(appStore)
@ -77,6 +78,29 @@ const menuOptions: MenuOption[] = [
} }
] ]
}, },
{
label: '系统配置',
key: 'system',
icon: renderIcon(Settings),
children: [
{
label: () => h(
RouterLink,
{
to: {
name: 'menu',
params: {
lang: 'zh-CN'
}
}
},
{ default: () => '菜单管理' }
),
key: 'system',
icon: renderIcon(Menu)
}
]
},
]; ];
</script> </script>
<style scoped lang='scss'> <style scoped lang='scss'>

View File

@ -3,11 +3,12 @@
<LayoutHeader /> <LayoutHeader />
<n-layout has-sider style="height: calc(100vh - 60px);"> <n-layout has-sider style="height: calc(100vh - 60px);">
<LayoutNav /> <LayoutNav />
<n-layout-content <n-layout-content content-style="padding: 16px; height: calc(100vh - 60px);" :native-scrollbar="false">
content-style="padding: 24px; min-height: calc(100vh - 60px);" <router-view v-slot="{ Component }">
:native-scrollbar="false" <transition name="slide-fade" mode="out-in">
> <component :is="Component" />
<RouterView></RouterView> </transition>
</router-view>
</n-layout-content> </n-layout-content>
</n-layout> </n-layout>
</n-layout> </n-layout>
@ -24,6 +25,24 @@ import LayoutNav from './components/LayoutNav.vue';
.n-layout-content { .n-layout-content {
overflow: auto; overflow: auto;
flex: 1; flex: 1;
background:#f5f7f9 background: #f5f7f9
}
.slide-fade-enter-active {
transition: all 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
.slide-fade-leave-active {
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
.slide-fade-enter-from {
transform: translate(30px, 30px) scale(0.9);
opacity: 0;
}
.slide-fade-leave-to {
transform: translate(30px, 30px) scale(0.9);
opacity: 0;
} }
</style> </style>

View File

@ -20,7 +20,7 @@ const oSomethingWithToken = async () => {
router.push('/layout'); router.push('/layout');
} catch (error) { } catch (error) {
router.push('/'); router.push('/');
message.error('错误!'); message.error(error.message);
} }
} }

View File

@ -28,7 +28,7 @@
科技赋能 <span class="highlight">第五人格</span> 陪玩产业链 科技赋能 <span class="highlight">第五人格</span> 陪玩产业链
</h1> </h1>
<p class="zn-hero__subtitle"> <p class="zn-hero__subtitle">
基于.NET9 + Vue3的智能管理系统为您提供全方位的数字化解决方案 基于.NET9 + Vue3技术栈研发的智能管理系统为您提供全方位的数字化解决方案
</p> </p>
<div class="zn-hero__cta"> <div class="zn-hero__cta">
<n-button type="primary" size="large" @click="handleLogin"> <n-button type="primary" size="large" @click="handleLogin">
@ -67,7 +67,7 @@
<div class="zn-features__grid"> <div class="zn-features__grid">
<div class="zn-feature-card"> <div class="zn-feature-card">
<div class="feature-icon"> <div class="feature-icon">
<n-icon color="#3d8eff"> <n-icon color="$primaryColor">
<GitCompare /> <GitCompare />
</n-icon> </n-icon>
</div> </div>
@ -87,7 +87,7 @@
</div> </div>
<div class="zn-feature-card"> <div class="zn-feature-card">
<div class="feature-icon"> <div class="feature-icon">
<n-icon color="#3d8eff"> <n-icon color="$primaryColor">
<Server /> <Server />
</n-icon> </n-icon>
</div> </div>
@ -136,7 +136,7 @@
<div class="testimonial-author"> <div class="testimonial-author">
<img src="@/assets/images/luolan_avatar.jpg" alt="杨某人" /> <img src="@/assets/images/luolan_avatar.jpg" alt="杨某人" />
<div> <div>
<div class="author-name">嚣张</div> <div class="author-name">校长</div>
<div class="author-title">X电竞 校长</div> <div class="author-title">X电竞 校长</div>
</div> </div>
</div> </div>
@ -205,16 +205,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { ChevronForward, GitCompare, BarChartSharp, Server } from '@vicons/ionicons5'; import { ChevronForward, GitCompare, BarChartSharp, Server } from '@vicons/ionicons5';
import { useAuth0 } from '@auth0/auth0-vue'; import { useAuth0 } from '@auth0/auth0-vue';
import { useDialog } from 'naive-ui'; import { useDialog, useMessage } from 'naive-ui';
// import { loginApiTest } from '@/api/loginApi'
const dialog = useDialog(); const dialog = useDialog();
const handleDemo = () => { const message = useMessage();
console.log('观看演示'); const handleDemo = async () => {
// try {
// const data = await loginApiTest(); //data : { data:data,code:200,msg:'ok' } data
// } catch (error) {
// message.error(error.message); //error.message : { data:null,code:400,msg:'' }msg
// }
console.log('代码演示一下!!!');
} }
const { loginWithRedirect } = useAuth0(); const { loginWithRedirect } = useAuth0();
const handleLogin = () => { const handleLogin = () => {
loginWithRedirect(); loginWithRedirect();
} }
// //
const handleContact = () => { const handleContact = () => {
dialog.warning({ dialog.warning({
@ -260,7 +268,7 @@ const handleContact = () => {
&__text { &__text {
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 700;
color: #3d8eff; color: $primaryColor;
margin-right: 8px; margin-right: 8px;
} }
@ -282,7 +290,7 @@ const handleContact = () => {
transition: color 0.3s; transition: color 0.3s;
&:hover { &:hover {
color: #3d8eff; color: $primaryColor;
} }
} }
} }
@ -322,7 +330,7 @@ const handleContact = () => {
margin-bottom: 20px; margin-bottom: 20px;
.highlight { .highlight {
color: #3d8eff; color: $primaryColor;
} }
} }
@ -347,7 +355,7 @@ const handleContact = () => {
.stat-number { .stat-number {
font-size: 28px; font-size: 28px;
font-weight: 700; font-weight: 700;
color: #3d8eff; color: $primaryColor;
margin-bottom: 4px; margin-bottom: 4px;
} }
@ -401,7 +409,7 @@ const handleContact = () => {
transition: all 0.3s; transition: all 0.3s;
&.highlight { &.highlight {
background: linear-gradient(135deg, #3d8eff, #6a5acd); background: linear-gradient(135deg, $primaryColor, #6a5acd);
color: #fff; color: #fff;
.feature-link { .feature-link {
@ -437,12 +445,12 @@ const handleContact = () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: #3d8eff; color: $primaryColor;
font-size: 24px; font-size: 24px;
} }
.feature-link { .feature-link {
color: #3d8eff; color: $primaryColor;
text-decoration: none; text-decoration: none;
font-weight: 500; font-weight: 500;
display: inline-flex; display: inline-flex;
@ -551,7 +559,7 @@ const handleContact = () => {
transition: color 0.3s; transition: color 0.3s;
&:hover { &:hover {
color: #3d8eff; color: $primaryColor;
} }
} }
@ -585,7 +593,7 @@ const handleContact = () => {
transition: color 0.3s; transition: color 0.3s;
&:hover { &:hover {
color: #3d8eff; color: $primaryColor;
} }
} }
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div>权限管理菜单内容</div> <div class="main__container white-bg table">权限管理菜单内容</div>
</template> </template>
<script setup lang='ts'> <script setup lang='ts'>

View File

@ -0,0 +1,8 @@
<template>
<div class="main__container white-bg table">动态权限菜单</div>
</template>
<script setup lang='ts'>
</script>
<style scoped lang='scss'>
</style>

View File

@ -17,6 +17,15 @@ export default defineConfig({
dirs: ["./src/api"], dirs: ["./src/api"],
}), }),
], ],
server:{
proxy:{
'/api':{
target:'http://localhost:3000/api',
changeOrigin:true,
rewrite:(path) => path.replace(/^\api/,'')
}
},
},
css: { css: {
preprocessorOptions: { preprocessorOptions: {
scss: { scss: {