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 isRef: typeof import('vue')['isRef']
const login: typeof import('./src/api/loginApi')['login']
const loginApiTest: typeof import('./src/api/loginApi')['loginApiTest']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']

View File

@ -1,7 +1,10 @@
import http from "@/utils/request";
//demo
export const login = () => {
return http.get('/get-user')
export const loginApiTest = () => {
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 NProgress from "nprogress"; // progress bar
import "nprogress/nprogress.css"; // progress bar style
import Login from "@/views/login/index.vue"; //登录组件
import Layout from "@/views/layout/index.vue"; //首页布局
import CallBack from "@/views/login/OauthCallBack.vue"; //反馈页面
import Home from '@/views/home/index.vue'; //家
import Auth from '@/views/role/pages/Auth.vue'; //权限管理
import Home from "@/views/home/index.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> = [
{
@ -22,17 +29,22 @@ const routes: Array<RouteRecordRaw> = [
component: Layout,
children: [
{
path:'',
name:'home',
path: "",
name: "home",
component: Home,
},
// 后续用动态菜单
{
path:'role',
name:'roleAuth',
component:Auth
}
]
path: "role",
name: "roleAuth",
component: Auth,
},
{
path: "menu",
name: "menu",
component: Menu,
},
],
},
];
@ -41,10 +53,20 @@ const router = createRouter({
routes,
});
// 在这里添加路由的导航守卫
router.beforeEach((to, from, next) => {
console.log("Navigating to:", to.path);
next();
const token = getToken();
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;

View File

@ -1,5 +1,5 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { getToken, removeToken } from './auth';
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { getToken, removeToken } from "./auth";
// 定义后端返回的统一数据结构
interface ApiResponse<T = any> {
@ -8,13 +8,11 @@ interface ApiResponse<T = any> {
msg: string;
}
// 创建 Axios 实例
const createAxiosInstance = (): AxiosInstance => {
const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL as string, // 从 Vite 环境变量获取 baseURL
baseURL: `${import.meta.env.VITE_API_BASE_URL as string}/api`, // 从 Vite 环境变量获取 baseURL
timeout: 10000, // 请求超时时间
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
});
@ -22,7 +20,7 @@ const createAxiosInstance = (): AxiosInstance => {
instance.interceptors.request.use(
(config) => {
// 添加 token 等请求头
const token = getToken()
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
@ -41,19 +39,19 @@ const createAxiosInstance = (): AxiosInstance => {
// 401 未授权,跳转首页
if (code === 401) {
// 这里调用退出登录的逻辑
console.log('未授权,跳转首页');
console.log("未授权,跳转首页");
// 清除用户信息
removeToken();
// 跳转首页
window.location.href = '/'; //后续用发布订阅模式修改
return Promise.reject(new Error(msg || '未授权'));
window.location.href = "/"; //后续用发布订阅模式修改
return Promise.reject(new Error(msg || "未授权"));
}
// 其他非 200 状态码
if (code !== 200) {
console.error(`请求错误 ${code}: ${msg}`);
// 这里可以根据需要弹出错误提示
return Promise.reject(new Error(msg || '请求错误'));
return Promise.reject(new Error(msg || "请求错误"));
}
// 返回数据部分
@ -65,49 +63,10 @@ const createAxiosInstance = (): AxiosInstance => {
const { status, data } = error.response;
console.error(`HTTP 错误 ${status}:`, data);
} else {
console.error('请求错误:', error.message);
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 = {
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return request<T>({ ...config, method: 'GET', url });
},
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
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;
export default instance;

View File

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

View File

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

View File

@ -19,13 +19,14 @@ import {
CaretDownOutline,
HomeSharp,
PersonCircleSharp,
Layers
Layers,
Settings,
Menu
} from '@vicons/ionicons5'
import { MenuOption, NIcon } from 'naive-ui';
import { useAppStore } from '@/store/app';
import { storeToRefs } from 'pinia';
import { RouterLink } from 'vue-router';
import { h } from 'vue'
const appStore = useAppStore();
const activeKey = ref('default');
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>
<style scoped lang='scss'>

View File

@ -3,11 +3,12 @@
<LayoutHeader />
<n-layout has-sider style="height: calc(100vh - 60px);">
<LayoutNav />
<n-layout-content
content-style="padding: 24px; min-height: calc(100vh - 60px);"
:native-scrollbar="false"
>
<RouterView></RouterView>
<n-layout-content content-style="padding: 16px; height: calc(100vh - 60px);" :native-scrollbar="false">
<router-view v-slot="{ Component }">
<transition name="slide-fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</n-layout-content>
</n-layout>
</n-layout>
@ -26,4 +27,22 @@ import LayoutNav from './components/LayoutNav.vue';
flex: 1;
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>

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<template>
<div>权限管理菜单内容</div>
<div class="main__container white-bg table">权限管理菜单内容</div>
</template>
<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"],
}),
],
server:{
proxy:{
'/api':{
target:'http://localhost:3000/api',
changeOrigin:true,
rewrite:(path) => path.replace(/^\api/,'')
}
},
},
css: {
preprocessorOptions: {
scss: {