用户中心Vue3项目开发2.0

发布于:2025-07-07 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

 一、网页实战

1.网页布局开发

2.顶部导航条的开发

Q:div区域内使用这样的布局可以实现什么样的效果?

导航条的功能实现:路由跳转

3.前端请求request

编写请求示例

4.全局用户管理

使用状态

二、页面开发

(一)用户登录界面

1.表单

2.用户点击登录后的操作

​编辑

(二)用户注册界面

(三)用户管理界面


 一、网页实战

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

定义 Store | Pinia

在项目入口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>

总之写前端页面缺啥就去组件库里翻一翻可以大幅提高开发效率。


网站公告

今日签到

点亮在社区的每一天
去签到