目录
一、网页实战
1.网页布局开发
上中下最基础最简单网页布局
<a-layout>
<a-layout-header>Header</a-layout-header>
<a-layout-content>Content</a-layout-content>
<a-layout-footer>Footer</a-layout-footer>
</a-layout>
footer底部栏最好开发
中间content的内容是需要我们动态替换的,需要引入vue-router库,我们的项目中已经有了router目录,打开index.ts文件,修改我们需要配置的路由
<template>
<div id="basicLayout">
<a-layout>
<a-layout-header class="header">
<GlobalHeader />
</a-layout-header>
<a-layout-content class="content">
<router-view />
</a-layout-content>
<GlobalFooter />
</a-layout>
</div>
</template>
2.顶部导航条的开发
把全局顶部导航栏GlobalHeader单独定义到组件component目录中
这是antdesignvue中的一个组件示例
<template>
<a-menu v-model:selectedKeys="current" mode="horizontal">
<a-menu-item key="mail">
<template #icon>
<mail-outlined />
</template>
Navigation One
</a-menu-item>
<a-menu-item key="app" disabled>
<template #icon>
<appstore-outlined />
</template>
Navigation Two
</a-menu-item>
<a-sub-menu>
<template #icon>
<setting-outlined />
</template>
<template #title>Navigation Three - Submenu</template>
<a-menu-item-group title="Item 1">
<a-menu-item key="setting:1">Option 1</a-menu-item>
<a-menu-item key="setting:2">Option 2</a-menu-item>
</a-menu-item-group>
<a-menu-item-group title="Item 2">
<a-menu-item key="setting:3">Option 3</a-menu-item>
<a-menu-item key="setting:4">Option 4</a-menu-item>
</a-menu-item-group>
</a-sub-menu>
<a-menu-item key="alipay">
<a href="https://antdv.com" target="_blank" rel="noopener noreferrer">
Navigation Four - Link
</a>
</a-menu-item>
</a-menu>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { MailOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons-vue';
export default defineComponent({
setup() {
const current = ref<string[]>(['mail']);
return {
current,
};
},
components: {
MailOutlined,
AppstoreOutlined,
SettingOutlined,
},
});
</script>
按需修改后,再全局布局basicLayout中引入globalheader组件直接看效果即可
{
key: "/",
icon: () => h(HomeOutlined),
label: "系统主页",
},
{
key: "/user/login",
icon: () => h(LoginOutlined),
label: "用户登录",
},
{
key: "/user/register",
icon: () => h(UserAddOutlined),
label: "用户注册",
},
{
key: "/admin/manage",
icon: () => h(TeamOutlined),
label: "用户管理",
},
{
key: "about",
icon: () => h(InfoCircleOutlined),
label: "其他信息",
children: [
{
key: "help-center",
label: "帮助中心",
},
{
key: "contact-us",
label: "联系我们",
},
{
key: "privacy-policy",
label: "隐私政策",
},
],
},
实际效果图如下所示
系统主页的左侧还需要插入一个网站logo和网站名
左边中间右边逐步完善导航条
Q:div区域内使用这样的布局可以实现什么样的效果?
<a-row>
<a-col flex="300px">
<!--网站图标and网站名-->
</a-col>
<a-col flex="auto">
<!--网站菜单-->
</a-col>
<a-row flex="100px">
<!--登录注册按钮-->
</a-row>
</a-row>
导航条的功能实现:路由跳转
点击不同的菜单项跳转到不同的页面,切换路由
如何配置路由?
项目中所有可以跳转的页面都是在index.js中定义的
path代表用户访问的路径,component代表进入页面所对应加载的组件,name命名相关即可
{
path: "/",
name: "home",
component: HomePage,
},
页面最基础的就包括:用户登录页面(权限等级:普通用户)、用户注册页面(权限等级:普通用户)、用户管理页面(权限等级:管理员)
@click和函数双向绑定,点击key可以实现路由跳转
定义变量来获取路由对象(按alt+回车引入),使用useRouter
,引入钩子函数获取一个路由跳转器
const router = useRouter();
使用路由跳转器的push方法确定要跳转的页面
刷新页面时,需要监听路由变化,更新当前菜单选中状态
const router = useRouter(); // 菜单栏路由跳转
const current = ref<string[]>(["home"]);
router.afterEach((to) => {
//current是vue的响应式变量,更新.value值
current.value = [to.path];//要去的页面
});
简单来说就是从 url——>导航条的选项——>页面显示的内容 要一一对应
点击导航栏的用户登录,URL显示xxx/user/login,实际页面也为登录页,这些都是基于vueRouter实现的
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
component: HomePage,
},
{
path: "/user/login",
name: "userLogin",
component: UserLoginPage,
},
{
path: "/user/register",
name: "userRegister",
component: UserRegisterPage,
},
{
path: "/admin/manage",
name: "adminManage",
component: UserManagePage,
},
];
思考:如何实现统一配置路由和菜单项?重复的path代码是否可以合并?
3.前端请求request
动态获取用户的登录信息,向后端发送请求,后端执行操作并响应给前端
前端负责界面展示和动态交互,避免写很复杂的逻辑
如何发送请求:传统我们用ajax,当下主流请求工具库axios(封装库、简化)
在src目录下新建文件request.ts
以下是axios的使用案例
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做些什么(状态码在2xx范围内会触发)
return response;
}, function (error) {
// 对响应错误做些什么(状态码超出2xx范围会触发)
return Promise.reject(error);
});
因为是发送请求,所以肯定需要有一个接收方,这里需要一个后端项目配合。
注意如果前后端都在同一台电脑上运行注意端口port冲突,可以在package.json文件中加上--port=3000
强制修改前端运行端口
"scripts": {
"serve": "vue-cli-service serve --port=3000",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
编写请求示例
前端要发很多的请求,对应调用后端多个接口:用户注册、用户登录、用户管理等等
在src下新建api包用来存放接口文件,用户中心主要运用到的是用户类接口,不妨新建一个user.ts文件,模块化更利于维护
注意request.ts文件最后要export default myAxios;
然后我们在user.ts中引入myAxios实例import myAxios from "@/request";
编写示例接口的注意点:
注意函数需要导出在其他项目文件中才能使用
请求包括URL地址、请求方法类型大多一致
post请求使用data传递参数
get请求可以直接传参,或者使用params传参
export const userLogin = async (params: any) => {
return myAxios.request({
url: "/myapp/user/login",
method: "POST",
data: params,
});
};
最后一共需要用户注册、用户登录、用户注销、获取当前用户、获取用户列表、删除用户这些接口,其中用户删除接口需要注意:后端把long id作为一个完整的json对象。
还有需要注意的是前端端口3000和后端端口不一致会导致跨域报错,有几种解决跨域的方法
最简单的就是在后端UserController中加入注解
@CrossOrigin(origins={"http://loaclhost:3000"},allowCredential="true")
4.全局用户管理
核心:每一个页面都可以获取当前用户的登录信息
全局变量:适合所有页面全局共享的变量不局限于一个页面中
选择pinia这个主流的全局状态管理库,在功能上类似vuex
在项目入口main.ts文件中引入pinia
什么是状态?store是保存状态和业务逻辑的实体,如loginUserStore,可以选择组合式api的写法
在src下新建store目录存储所有需要使用的状态,我们先把登录用户的用户名设置为“未登录”,然后根据需要获取或更新当前用户的值,创建函数fetchLoginUser,调用后端getCurrentUser方法获取到返回值(使用await才是获取到值不然就是一个promise变量)
Promise 是 JavaScript 中用于处理异步操作的特殊对象,它代表一个尚未完成但预期将来会完成的操作的最终结果(可能是成功值或失败原因)。
Promise 有三种状态:
pending(进行中):初始状态
fulfilled(已成功):操作成功完成
rejected(已失败):操作失败
根据返回值且是否获取到了用户的详细信息进行判断,再给store里的loginUser赋值,即 loginUser.value = res.data.data;
获取到了用户信息还需要一个对用户信息进行设置的函数setLoginUser,用于接收新的用户信息loginUser.value = newLoginUser;
总结:如何定义变量、获取变量、改变变量?就像就是一个get和set方法
import { defineStore } from "pinia";
import { ref } from "vue";
import { getCurrentUser } from "@/api/user";
export const useLoginUserStore = defineStore("loginUser", () => {
const loginUser = ref<any>({
userAccount: "未登录",
// userPassword: "null",
});
// const loading = ref(false);
// const error = ref<string | null>(null);
// 远程获取用户信息
async function fetchLoginUser() {
try {
const res = await getCurrentUser();
console.groupCollapsed("[fetchLoginUser] 完整响应详情");
console.log("响应状态:", res.status);
console.log("响应数据:", res.data);
console.log("数据结构:", res.data.data ? "嵌套格式" : "扁平格式");
console.groupEnd();
if (res.data.code === 0 && res.data.data) {
loginUser.value = res.data.data;
console.log("[fetchLoginUser] 更新后的loginUser:", loginUser.value);
} else {
console.warn("[fetchLoginUser] 无用户数据返回");
}
} catch (error) {
console.error("[fetchLoginUser] 获取用户信息失败:", error);
throw error;
}
}
// 设置用户信息(接收一个新的用户信息)
function setLoginUser(newLoginUser: any) {
loginUser.value = newLoginUser;
}
return {
loginUser,
fetchLoginUser,
setLoginUser,
};
});
使用状态
在app.vue文件中引入loginUserStore,进入主页时会全局获取用户信息,在任意页面都可以使用这个信息。
可以在globalheader中先引入store后再在登录按钮旁使用
{{JSON.stringify(loginUserStore.loginUser)}}
或者先在div中进行v-if的判断如果获取到了用户id就展示store中的loginUser的username,如果没有获取到用户名就设置为“未知用户”
<div class="user-login-status">
<div v-if="loginUserStore.loginUser.id">
{{
loginUserStore.loginUser.userAccount || "登录用户-请设置用户昵称"
}}
</div>
<div v-else>
<a-button type="primary" href="/user/login">登录</a-button>
<a-button href="/user/register">注册</a-button>
</div>
</div>
二、页面开发
功能上:用户登录、用户注册、用户管理
新建pages目录,存放页面,比views命名上更像
新建homePage.vue文件
按照url的层级对pages目录进行进一步的分层,homepage为第一层,新建user目录,第二层里存放登录注册页面,新建admin目录,存放管理页面,记得为每个页面分配不同的div-id
![]() |
![]() |
记得修改index.ts中对应的组件部分
(一)用户登录界面
1.表单
表单样式:https://2x.antdv.com/components/form-cn/#Form-
<template>
<div id="userLoginPage">
<div id="loginSection">
<h2>用户登录</h2>
<a-form
:model="formState"
name="basic"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
autocomplete="off"
@finish="handleSubmit"
>
<a-form-item
label="账户"
name="userAccount"
:rules="[{ required: true, message: '请输入账户名!' }]"
>
<a-input
v-model:value="formState.userAccount"
placeholder="请输入用户名"
/>
</a-form-item>
<a-form-item
label="密码"
name="userPassword"
:rules="[{ required: true, message: '请输入密码!' }]"
>
<a-input-password
v-model:value="formState.userPassword"
placeholder="请输入密码"
/>
</a-form-item>
<!-- <a-form-item name="remember" :wrapper-col="{ offset: 8, span: 16 }">
<a-checkbox v-model:checked="formState.remember">记住我</a-checkbox>
</a-form-item> -->
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit">登录</a-button>
</a-form-item>
</a-form>
</div>
</div>
</template>
定义接口
reactive
函数:Vue 3 的响应式 API,会将普通对象转换为响应式对象泛型
<FormState>
:指定该响应式对象必须符合FormState
接口类型初始值:
字符串字段初始化为空字符串(
""
)布尔值字段被注释,若启用默认值为
true
interface FormState {
// 接收用户输入的变量
userAccount: string;
userPassword: string;
// remember: boolean;
}
const formState = reactive<FormState>({
// 接收用户输入的变量
userAccount: "",
userPassword: "",
// remember: true,
});
对登录表单进行美化:(要点)居中布局,合适的行距,提高用户的体验(用户思维)
2.用户点击登录后的操作
通过handlesubmit函数:需要引入store和router
根据表单提交的用户名和密码
// 提交表单后的逻辑处理
const handleSubmit = async (values: any) => {
try {
const res = await userLogin(values);
if (res.data.code === 0 && res.data.data !== null) {
await loginUserStore.fetchLoginUser();
message.success("登录成功");
router.push({ name: "home", replace: true });
} else {
message.error("登录失败: 账号或密码错误"); // 更明确的错误提示
}
} catch (error) {
console.error("请求异常:", error); // 捕获网络或服务器错误
message.error("请求失败,请检查网络");
}
user.ts文件里的userLogin异步函数,根据返回码和是否获取到了用户数据
/**
* 用户登录
* @param params
*/
export const userLogin = async (params: any) => {
return myAxios.request({
url: "/myapp/user/login",
method: "POST",
data: params,
});
};

(二)用户注册界面
用户登录页面的基础上再加一个确认密码的表单,总而言之就是改造
注册成功后跳转到登录页面,对前端注册页面进行一些初步的校验,虽然主要还是依靠后端
一些报错可以结合上报错信息和报错描述
注册界面成品图如下
(三)用户管理界面
注意这个仅管理员可见
这个页面像是对数据库表格选择性的美化
对于antdesignvue我们可以选择必要的
<template>
<a-table :columns="columns" :data-source="data">
<template #name="{ text }">
<a>{{ text }}</a>
</template>
<template #customTitle>
<span>
<smile-outlined />
姓名
</span>
</template>
<template #tags="{ text: tags }">
<span>
<a-tag
v-for="tag in tags"
:key="tag"
:color="tag === 'loser' ? 'volcano' : tag.length > 5 ? 'geekblue' : 'green'"
>
{{ tag.toUpperCase() }}
</a-tag>
</span>
</template>
<template #action="{ record }">
<span>
<a>邀请 一 {{ record.name }}</a>
<a-divider type="vertical" />
<a>删除</a>
<a-divider type="vertical" />
<a class="ant-dropdown-link">
更多操作
<down-outlined />
</a>
</span>
</template>
</a-table>
</template>
<script lang="ts">
import { SmileOutlined, DownOutlined } from '@ant-design/icons-vue';
import { defineComponent } from 'vue';
const columns = [
{
dataIndex: 'name',
key: 'name',
slots: { title: 'customTitle', customRender: 'name' },
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '地址',
dataIndex: 'address',
key: 'address',
},
{
title: '标签',
key: 'tags',
dataIndex: 'tags',
slots: { customRender: 'tags' },
},
{
title: '操作',
key: 'action',
slots: { customRender: 'action' },
},
];
const data = [
{
key: '1',
name: '张三',
age: 32,
address: '北京市朝阳区一号湖公园',
tags: ['优秀', '开发'],
},
{
key: '2',
name: '李四',
age: 42,
address: '上海市浦东新区一号湖公园',
tags: ['待提升'],
},
{
key: '3',
name: '王五',
age: 32,
address: '广州市天河区一号湖公园',
tags: ['资深', '教师'],
},
];
export default defineComponent({
setup() {
return {
data,
columns,
};
},
components: {
SmileOutlined,
DownOutlined,
},
});
</script>
总之写前端页面缺啥就去组件库里翻一翻可以大幅提高开发效率。