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

一、文件夹结构说明

  • 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.

相关推荐
代码搬运媛4 小时前
Jest 测试框架详解与实现指南
前端
counterxing5 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
wangqiaowq5 小时前
windows下nginx的安装
linux·服务器·前端
之歆6 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
发现一只大呆瓜6 小时前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite
Maimai108086 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
kyriewen8 小时前
产品经理把PRD写成“天书”,我用AI半小时重写了一遍,他当场愣住
前端·ai编程·cursor
humcomm8 小时前
元框架的工作原理详解
前端·前端框架
canonical_entropy8 小时前
Attractor Before Harness: AI 大规模开发的方法论
前端·aigc·ai编程
zhangxingchao9 小时前
多 Agent 架构到底怎么选?从 Claude Agent Teams、Cognition/Devin 到工程落地原则
前端·人工智能·后端