框架搭建(二、改造项目结构,添加组件)

一、文件夹结构说明

  • views 页面文件
  • router 路由文件
  • api 请求
  • styles 样式
  • components 公共组件
  • store 存储
  • lauout 布局
  • utils 公共方法
  • assets 静态资源

二、添加组件

2.1 路由

pnpm install vue-router

创建vue文件

  • views/home/index.vue 首页
  • views/systom/login/index.vue 登陆
  • views/dashboard/index.vue 驾驶舱

定义路由组件 router/index.ts

TypeScript 复制代码
import { createRouter, createWebHashHistory, Router } from 'vue-router'
const routes = [
    { path: '/', component: import('~views/home/index.vue') },
    { path: "/login", component: import("~views/systom/login/index.vue") },
    { path: '/dashboard', component: import('~views/dashboard/index.vue') },
]
export const router: Router = createRouter({
    history: createWebHashHistory(),
    routes: routes
})

main.ts使用

TypeScript 复制代码
import App from "./App.vue";
import { createApp } from "vue";
/* 基础组件 */
import { router } from "~router/index";
async function bootstrap() {
  const app = createApp(App);
  app.use(router)
  app.mount("#app");
}
bootstrap();

启动项目,页面显示home.vue页面的内容,通过路由(login.vue、dashboard)可切换页面

2.2 添加ant-design-vue

安装 Ant Design的Vue实现、以及图标组件包 pnpm i --save ant-design-vue@4.x @ant-design/icons-vue

引入组件方式有两种具体参考官网

  • 全局引入
  • 全局部分引入
  • 局部注册

解放双手,按需自动导入

unplugin-vue-components 和全局引入组件的主要区别在于,使用 unplugin-vue-components 插件可以实现按需引入组件,即在代码中当你实际使用某个组件时,它会被自动引入,而不需要手动在某个文件中全局引入。这样可以减少打包体积,并可能提高构建速度。

全局引入组件通常是通过在 main.js 或类似的入口文件中使用 Vue.component 方法来注册组件的。而使用 unplugin-vue-components 插件,你只需要在你的代码中直接使用组件,无需手动注册。

pnpm install unplugin-vue-components -D

修改vite配置

TypeScript 复制代码
import components from "unplugin-vue-components/vite";
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
plugins: [
    components({
        resolvers: [AntDesignVueResolver({ importStyle: false })],
        dts: false,
      }),
      ]

配置完之后 main.ts 就不用在引入Antd了

参考:juejin.cn/post/701244...

2.3 写一个简单的登陆,正常的登陆都做了什么

现在的程序会自动跳转到home页,实际程序当中要判断当前是否有登录。

  • 已经登录过,那么跳转目标路由(home或其他)
  • 没有登录,跳转登录界面,登录成功后将信息存储(token,和患者信息)。
  • 存储信息使用pinia
  • 然后路由跳转。

2.4 引入axios

pnpm i axios

utils/http/axios/index.ts

TypeScript 复制代码
import axios from "axios";
export const MYAxios = axios.create({
  baseURL: "/api/",
  timeout: 10000,
  headers: {},
});

api/systom/user.ts

TypeScript 复制代码
import { MYAxios } from "~utils/http/axios";
enum Api {
  Login = "/login"
}
/**
 * @description: 登录
 */
export function loginApi(data: any) {
  return MYAxios.post(Api.Login, data);
}

使用: 登录页面

javascript 复制代码
import { loginApi } from "~api/systom/user";
/* 登录 */
const login = async () => {
  try {
    const data = await loginApi(formData);
    console.log(data);
  } catch (error) {
    formData;
  }
};

2.5 mock模拟后端

mock插件:pnpm install vite-plugin-mock -D

TypeScript 复制代码
   plugins: [
      viteMockServe({
        // default
        mockPath: "mock",
        enable: command === "serve",
      }),
    ],

在根目录下创建 mock文件夹,添加index.ts文件

mock:pnpm install mockjs

用于:生成随机数据,拦截 Ajax 请求

mock完数据就可以请求到数据了

TypeScript 复制代码
import { MockMethod } from "vite-plugin-mock";
import Mock from "mockjs";
export default [
  {
    url: "/api/login",
    method: "post",
    timeout: 500,
    response: (RespThisType, opt) => {
      return {
        code: 200,
        data: {
          user: { name: "", roles: [], permission: [] },
          token: Mock.mock("@guid"),
        },
        message: "成功!"
      };
    },
  },
] as MockMethod[];

2.6 引入状态管理库 pinia

pnpm install pinia pinia-plugin-persistedstate

pinia.vuejs.org/zh/introduc...

prazdevs.github.io/pinia-plugi...

src/store/index.ts

TypeScript 复制代码
import type { App } from 'vue';
import { createPinia } from 'pinia';
import { createPersistedState } from "pinia-plugin-persistedstate";
const store = createPinia();
store.use(createPersistedState())

export function setupStore(app: App<Element>) {
    app.use(store);
}
export { store };

main.ts中使用这个setupStore方法

2.7 实现检测token成功放行,失败调登陆页逻辑

添加路由守卫,检测到没有token就跳转到login页面

TypeScript 复制代码
import { PageEnum } from "~/enums/pageEnum";
import { useUserStoreWithOut } from "~store/modules/user";
router.beforeEach((to, from) => {
  const userStore = useUserStoreWithOut();
  // ...
  // 返回 false 以取消导航
  // console.log("beforeEach", to.path, from.path);
  if (whiteList.findIndex((i) => i === to.path) == -1) {
    if (
      // 检查用户是否已登录
      !userStore.getToken
    ) {
      console.log("beforeEach", to.path, from.path);
      //     // 将用户重定向到登录页面
      return { path: PageEnum.BASE_LOGIN };
    }
  }
});

登陆页面

TypeScript 复制代码
<template>
  <div>
    <a-form
      ref="formLoginRef"
      :model="formData"
      :label-col="{ span: 8 }"
      :wrapper-col="{ span: 16 }"
      autocomplete="off"
      :rules="getFormRules"
      @finishFailed="onFinishFailed"
    >
      <a-form-item label="账号" name="username">
        <a-input v-model:value="formData.username" />
      </a-form-item>

      <a-form-item label="密码" name="password">
        <a-input-password
          autocomplete="off"
          v-model:value="formData.password"
        />
      </a-form-item>
      <a-form-item>
        <a-form-item name="remember" no-style>
          <a-checkbox v-model:checked="formData.remember"
            >Remember me</a-checkbox
          >
        </a-form-item>
        <a class="login-form-forgot" href="">Forgot password</a>
      </a-form-item>

      <a-form-item :wrapper-col="{ offset: 8, span: 16 }">
        <a-button type="primary" @click="onSubmit">登录</a-button>
        Or
        <a href="">register now!</a>
      </a-form-item>
    </a-form>
  </div>
</template>

<script setup lang="ts">
//api

import { loginApi } from "~api/systom/user";

/* UI comp */
import { reactive, ref } from "vue";
import { useFormRules } from "./useLogin";
interface FormState {
  username: string;
  password: string;
  remember: boolean;
}
const { getFormRules } = useFormRules();
import { useUserStore } from "~store/modules/user";
const userStore = useUserStore();
const formData = reactive<FormState>({
  username: "",
  password: "",
  remember: true,
});
const formLoginRef = ref();
const onSubmit = () => {
  formLoginRef.value
    .validate()
    .then(() => {
      login();
    })
    .catch((error: any) => {
      console.log(error);
    });
};
/* 登录 */
const login = async () => {
  try {
    const data = await userStore.login(formData);
    console.log("userStore.login", data);
  } catch (error) {
    formData;
  }
};

// 提交表单且数据验证失败后回调事件;
const onFinishFailed = (errorInfo: any) => {
  console.log("Failed:", errorInfo);
};
</script>
<style scoped lang="scss"></style>

封装用户相关状态管理: store/modules/user.ts

TypeScript 复制代码
import { defineStore } from "pinia";
import { PageEnum } from "~/enums/pageEnum";
import { UserInfo } from "~api/systom/model/user";
import { loginApi } from "~api/systom/user";
import { router } from "~router/index";
import { store } from "~store/index";
/* 将来移动到types */
// TODO

interface UserState {
  userInfo: Nullable<UserInfo>;
  token?: string;
  roleList: any[];
  sessionTimeout?: boolean;
  lastUpdateTime: number;
}
export const useUserStore = defineStore("app-user", {
  state: (): UserState => ({
    userInfo: null,
    token: undefined,
    roleList: [],
    // Whether the login expired
    sessionTimeout: false,
    lastUpdateTime: 0,
  }),
  getters: {
    getUserInfo(state) {
      return state.userInfo;
    },
    // getToken(state): string { // TODO
    getToken(state) {
      return state.token;
    },
    getRoleList(state) {
      return state.roleList.length > 0 ? state.roleList : [];
    },
    getSessionTimeout(state): boolean {
      return !!state.sessionTimeout;
    },
    getLastUpdateTime(state): number {
      return state.lastUpdateTime;
    },
  },
  actions: {
    setToken(info: string | undefined) {
      this.token = info ? info : ""; // for null or undefined value
    },
    setRoleList(roleList: []) {
      this.roleList = roleList;
    },
    setUserInfo(info: UserInfo | null) {
      this.userInfo = info;
      this.lastUpdateTime = new Date().getTime();
    },
    setSessionTimeout(flag: boolean) {
      this.sessionTimeout = flag;
    },
    resetState() {
      this.userInfo = null;
      this.token = "";
      this.roleList = [];
      this.sessionTimeout = false;
    },

    /**
     * @description: login
     */
    async login(params: any) {
      try {
        const response = await loginApi(params);
        const { token, user } = response.data.data;
        // save token
        this.setToken(token);
        this.setUserInfo({
          userId: user.name,
          username: user.name,
        });
        return this.afterLoginAction();
      } catch (error) {
        return Promise.reject(error);
      }
    },

    async afterLoginAction() {
      router.push(PageEnum.BASE_HOME);
    },
  },
  persist: true, //现在,你的整个 Store 将使用默认持久化配置保存。
});
// Need to be used outside the setup
export function useUserStoreWithOut() {
  return useUserStore(store);
}

2.8 引入css处理器sass

pnpm i sass -D

2.9 异常处理

浏览器警告问题

浏览器警告:Input elements should have autocomplete attributes (suggested: "current-password"):

在 imput-passward 添加属性autocomplete="off", autocomplete规定输入字段是否启用自动完成功能。

html 复制代码
  <a-input-password
          autocomplete="off"
          v-model:value="formData.password"
        />

Pinia使用问题

"getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?

原因:在main.ts中,是先引用了router,再调用的setupStore方法将pinia挂载在app上,此时不能使用pinia。

TypeScript 复制代码
import App from "./App.vue";
import { createApp } from "vue";
/* 基础组件 */
import { router } from "~router/index";
import { setupStore } from "~store/index";
import "ant-design-vue/dist/reset.css";
async function bootstrap() {
  const app = createApp(App);
  setupStore(app);
  app.use(router);
  app.mount("#app");
}
bootstrap();

解决:取消全局调用,在用的地方调用。

TypeScript 复制代码
export function useUserStoreWithOut() {
  return useUserStore(store);
}

碎碎

插件

css 复制代码
pnpm i vite-aliases -D

启动项目,ts配置文件中自动添加如下代码,但这些配置需要 "baseUrl": "./",(指定用于解析非相对模块名称的基本目录),配置完就不报警告了。

javascript 复制代码
"paths": {
      "~api/*": [
        "src/api/*"
      ],
      "~assets/*": [
        "src/assets/*"
      ],
      "~components/*": [
        "src/components/*"
      ],
      "~router/*": [
        "src/router/*"
      ],
      "~store/*": [
        "src/store/*"
      ],
      "~styles/*": [
        "src/styles/*"
      ],
      "~utils/*": [
        "src/utils/*"
      ],
      "~views/*": [
        "src/views/*"
      ],
      "~/*": [
        "src/*"
      ]
    }

终断提示changed tsconfig file detected: G:\study\muyang-admin\tsconfig.json - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.

相关推荐
长天一色10 分钟前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_23428 分钟前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河30 分钟前
CSS总结
前端·css
BigYe程普1 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H1 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
花花鱼1 小时前
@antv/x6 导出图片下载,或者导出图片为base64由后端去处理。
vue.js
程序员-珍1 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai1 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端
流烟默1 小时前
Vue中watch监听属性的一些应用总结
前端·javascript·vue.js·watch
2401_857297912 小时前
招联金融2025校招内推
java·前端·算法·金融·求职招聘