什么是Moc?
Mock模拟后端数据是指在后端服务尚未开发完成或不稳定的情况下,通过模拟后端接口返回的数据来支持前端开发的过程。这种方式允许前端开发者在不需要等待后端接口实际开发完成的情况下,进行页面的布局、功能实现和调试。Mock数据通常是通过工具或手动编写的代码来生成的,它可以模拟各种场景下的数据返回,包括成功、失败、超时等。
为什么使用Mock模拟后端数据
-
提高开发效率:
- 前端开发者可以独立于后端进行开发,无需等待后端接口的实现,从而大大缩短开发周期。
- 通过Mock数据,前端可以更早地开始界面的实现和调试,及时发现并修复前端代码中的问题。
-
降低开发风险:
- 在后端接口尚未稳定或存在变更风险时,使用Mock数据可以减少对后端服务的依赖,降低因后端问题导致的前端开发延误或失败的风险。
- Mock数据可以帮助前端开发者更好地理解接口需求和数据规范,减少因接口理解错误导致的返工。
-
增强测试的全面性:
- Mock模拟可以覆盖各种接口响应场景,包括正常响应、异常响应、超时等,从而增强前端代码的健壮性和容错能力。
- 通过Mock测试,可以确保前端代码在各种接口响应情况下都能正常工作,提高产品的整体质量。
-
促进前后端分离开发:
- Mock模拟是实现前后端分离开发的重要手段之一。它允许前后端团队并行工作,减少沟通成本和等待时间。
- 前后端分离开发模式下,Mock数据可以作为前端开发的临时数据源,直到后端接口开发完成并稳定后,再切换到真实的后端服务。
-
支持自动化测试:
- Mock模拟可以与自动化测试框架结合使用,实现接口测试的自动化。这有助于在代码变更时快速发现潜在问题,确保代码质量。
使用的技术
vu+vite+axios+vue-router+elementui+mock
安装依赖
npm i vite-plugin-mock
npm install axios
npm install vue-router@4
npm install element-plus --save
文件路径
|--mock
|--index.ts
|--src
|--page
|--Index.vue
|--Login.vue
|--routers
|--index.ts
|--App.vue
|--main.ts
配置文件(配置mock和路径别名@)
import { defineConfig } from "vite";
import path from "path";
import vue from "@vitejs/plugin-vue";
import { viteMockServe } from 'vite-plugin-mock'
const pathSrc = path.resolve(__dirname, "src");
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
viteMockServe({
mockPath: './mock'
}),
],
resolve: {
alias: {
"@": pathSrc,
},
},
});
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
// 别名
"baseUrl": ".",
"paths": {
"@/*":["src/*"]
},
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
mock后端模拟的数据
import { MockMethod } from 'vite-plugin-mock'
const PostUserLogin = {
"username": "a",
"password": "1",
"accessToken":'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjMzODMxODY4OTA2MzQ0NDQ4LCJpc3MiOiJ6dHkiLCJleHAiOjE3MjIwMDg2MzMsImlhdCI6MTcyMTQwMzgzM30.52S54luvyzc3SwB-M3oClgFPgjzXcJrkFjYIcZ8A7ug',
}
let userInfoList = [
{
"id":1,
"username":'atruo123',
"truename":'Amy',
"age":18,
"gender":'女',
"salary":8000
},
{
"id":2,
"username":'jdusad7788',
"truename":'Kity',
"age":20,
"gender":'男',
"salary":15000
},
{
"id":3,
"username":'das',
"truename":'LiLi',
"age":30,
"gender":'男',
"salary":6000
},
{
"id":4,
"username":'acc',
"truename":'Hee',
"age":25,
"gender":'男',
"salary":9000
},
{
"id":4,
"username":'acc',
"truename":'Hee',
"age":25,
"gender":'男',
"salary":9000
},
{
"id":4,
"username":'acc',
"truename":'Hee',
"age":25,
"gender":'男',
"salary":9000
},
{
"id": 5,
"username": "user5",
"truename": "Tom",
"age": 22,
"gender": "男",
"salary": 7500
},
{
"id": 6,
"username": "user6",
"truename": "Jerry",
"age": 28,
"gender": "男",
"salary": 12000
},
{
"id": 7,
"username": "user7",
"truename": "Linda",
"age": 24,
"gender": "女",
"salary": 8500
},
{
"id": 8,
"username": "user8",
"truename": "David",
"age": 35,
"gender": "男",
"salary": 18000
},
{
"id": 9,
"username": "user9",
"truename": "Sophia",
"age": 27,
"gender": "女",
"salary": 10000
},
{
"id": 10,
"username": "user10",
"truename": "Ethan",
"age": 32,
"gender": "男",
"salary": 14000
},
{
"id": 11,
"username": "user11",
"truename": "Eva",
"age": 29,
"gender": "女",
"salary": 9500
},
{
"id": 12,
"username": "user12",
"truename": "Adam",
"age": 37,
"gender": "男",
"salary": 22000
},
{
"id": 13,
"username": "user13",
"truename": "Alice",
"age": 21,
"gender": "女",
"salary": 7000
},
{
"id": 14,
"username": "user14",
"truename": "Ben",
"age": 26,
"gender": "男",
"salary": 11000
},
{
"id": 15,
"username": "user15",
"truename": "Catherine",
"age": 31,
"gender": "女",
"salary": 13000
},
{
"id": 16,
"username": "user16",
"truename": "Daniel",
"age": 39,
"gender": "男",
"salary": 20000
},
{
"id": 17,
"username": "user17",
"truename": "Emily",
"age": 23,
"gender": "女",
"salary": 8800
},
{
"id": 18,
"username": "user18",
"truename": "Frank",
"age": 34,
"gender": "男",
"salary": 16000
},
{
"id": 19,
"username": "user19",
"truename": "Grace",
"age": 25,
"gender": "女",
"salary": 9200
},
{
"id": 20,
"username": "user20",
"truename": "Harry",
"age": 30,
"gender": "男",
"salary": 15500
},
{
"id": 21,
"username": "user21",
"truename": "Ivy",
"age": 22,
"gender": "女",
"salary": 7700
},
{
"id": 22,
"username": "user22",
"truename": "Jack",
"age": 36,
"gender": "男",
"salary": 19000
},
{
"id": 23,
"username": "user23",
"truename": "Katy",
"age": 28,
"gender": "女",
"salary": 10500
},
{
"id": 24,
"username": "user24",
"truename": "Leo",
"age": 33,
"gender": "男",
"salary": 17000
}
]
function validateCredentials(username: string, password: string) {
return username === 'a' && password === '1';
}
const mockLogin = (options: { body: any; }) => {
const { body } = options;
if (!body || !body.username || !body.password) {
return { code: 400, msg: "缺少用户名或密码" };
}
if (validateCredentials(body.username, body.password)) {
return {
code: 200,
data: PostUserLogin,
msg: "登录成功"
};
} else {
return {
code: 401,
msg: "用户名或密码错误"
};
}
};
function deleteUserById(id: number) {
const user = userInfoList.find(user => user.id === id);
if (!user) {
return { code: 404, msg: "找不到指定ID的用户" };
}
userInfoList.splice(userInfoList.indexOf(user), 1); // 查找并删除用户
return { code: 200, msg: "用户删除成功" };
}
const mockDeleteUser = (options: { query: any; }) => {
const { query } = options; // 从 options 中解构出 query 对象
const id = parseInt(query.id, 10); // 尝试从查询参数中获取id,并转换为数字
if (isNaN(id)) {
return { code: 400, msg: "用户ID无效" };
}
return deleteUserById(id);
};
// 清理重复的用户数据(可选)
userInfoList = [...new Map(userInfoList.map(item => [item.id, item])).values()];
export default [
{
url: '/api/login',
method: 'post',
response: mockLogin
},
{
url: '/api/userList',
method: 'get',
response: () => ({
code: 200,
data: userInfoList,
msg: "成功获取员工列表"
})
},
{
url: '/api/userList/:id',
method: 'delete',
response: (options: any) => {
return mockDeleteUser(options);
}
}
] as MockMethod[];
main文件
import { createApp } from 'vue'
import App from './App.vue'
import { router } from '@/routers'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(router).use(ElementPlus).mount('#app')
页面文件
<script setup lang="ts">
</script>
<template>
<RouterView></RouterView>
</template>
<style scoped>
</style>
<template>
<h1>员工列表</h1>
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="id" label="ID" width="180" />
<el-table-column prop="username" label="昵称" width="180" />
<el-table-column prop="truename" label="真实姓名" />
<el-table-column prop="age" label="年龄" />
<el-table-column prop="gender" label="性别" />
<el-table-column prop="salary" label="工资" />
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button size="mini" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script lang="ts" setup>
import axios from "axios";
import { ElMessage } from 'element-plus';
import { onMounted, ref } from "vue";
const tableData = ref([]);
// 封装获取用户列表的函数
const fetchUsers = async () => {
try {
const res = await axios.get('/api/userList');
if (res.data.code === 200) {
tableData.value = res.data.data;
} else {
ElMessage.error('获取不到数据');
}
} catch (error) {
ElMessage.error('获取用户列表失败');
}
};
onMounted(() => {
fetchUsers(); // 组件挂载时获取用户列表
});
const handleDelete = async (id: number) => {
try {
await axios.delete(`/api/userList/${id}`);
ElMessage({
message: '删除成功',
type: 'success',
});
fetchUsers();
} catch (error) {
ElMessage.error('删除失败');
}
};
</script>
<template>
<div>
<h1>登录</h1>
<form>
<el-input v-model="username" style="width: 240px" placeholder="用户名" required />
<el-input v-model="password" style="width: 240px" type="password" placeholder="密码" required />
<el-button type="primary" @click="handleLogin()">登录</el-button>
</form>
</div>
</template>
<script setup>
import axios from "axios";
import { useRouter } from 'vue-router';
import {ref} from 'vue'
import { ElMessage } from 'element-plus'
const username = ref('');
const password = ref('');
const router = useRouter();
const handleLogin = () => {
axios({
method: 'post',
url: '/api/login',
data: {
username: username.value,
password: password.value
}
}).then((res)=>{
if(res.data.code == 200){
ElMessage({
message: '登录成功',
type: 'success',
})
router.push('/index')
}else{
ElMessage.error('账号或密码错误')
}
})
}
</script>
路由配置
import { createRouter, createWebHashHistory } from "vue-router";
import login from '@/page/Login.vue';
import index from '@/page/Index.vue'; // 假设你有一个 Dashboard.vue 组件
const routes = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'Login',
component: login,
},
{
path: '/index',
name: 'Index',
component: index,
meta: { requiresAuth: true }
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, form, next) => {
const isLogin: Boolean = Boolean(localStorage.getItem("token"))
if (to.name !== 'Login' && !isLogin) {
next({
name: "Login",
query: { redirect: to.fullPath } // 将要跳转路由的path作为参数,传递到登录页面
})
} else {
next()
}
})
export { router };