Vben Admin —— 使用 mock 模拟数据以及模拟api联调接口
数据 mock&联调相关概念及使用
练习 —— 在之前table基础上,使用mock模拟数据,替换原来的死数据,添加新增、查看、修改和删除api并添加逻辑,实现一个简单的、完整的增删改查页面
/mock文件夹下添加table文件夹,其中添加tableMock.ts
/mock/table/tableMock.ts
import { time } from 'console';
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess, resultError, requestParams, getRequestToken } from '../_util';
const tableListData = {
pager: {
list: [
{
contno: '610964224475996443',
name: '张三',
sex: '0',
dt: '20200701',
age: '22',
tel: '13789890909',
address: '北京市北京小区',
},
{
contno: '610964224475996442',
name: '李四',
sex: '1',
dt: '20230507',
age: '27',
tel: '15477778888',
address: '大连市大连小区',
},
{
contno: '610964224475996445',
name: '王五',
sex: '0',
dt: '20221001',
age: '26',
tel: '15477778888',
address: '大连市大连小区',
},
{
contno: '610964224475996446',
name: '小明',
sex: '0',
dt: '20220701',
age: '25',
tel: '15477778888',
address: '大连市大连小区',
},
{
contno: '610964224475996447',
name: '小红',
sex: '1',
dt: '20180808',
age: '28',
tel: '15477778888',
address: '大连市大连小区',
},
],
},
};
export default [
{
url: '/basic-api/table/getListData',
timeout: 200,
method: 'get',
response: () => {
return resultSuccess(tableListData);
},
},
{
url: '/basic-api/table/addListData',
timeout: 1000,
method: 'post',
response: (request: requestParams) => {
tableListData.pager.list.push(request.body);
return resultSuccess({});
},
},
{
url: '/basic-api/table/editListData',
timeout: 1000,
method: 'post',
response: (request: requestParams) => {
let list: any[] = [];
for (let item of tableListData.pager.list) {
console.log('item', item);
if (request.body.contno === item.contno) {
list.push(request.body);
} else {
list.push(item);
}
}
tableListData.pager.list = list;
return resultSuccess({});
},
},
{
url: '/basic-api/table/delListData',
timeout: 1000,
method: 'post',
response: (request: requestParams) => {
let list: any[] = [];
for (let item of tableListData.pager.list) {
console.log('item', item);
if (request.body.contno === item.contno) {
} else {
list.push(item);
}
}
tableListData.pager.list = list;
return resultSuccess({});
},
},
] as MockMethod[];
/src/api/table下添加相关api
/src/api/table/tableApi.ts
import { defHttp } from '/@/utils/http/axios';
enum Api {
LIST_DATA = '/table/getListData',
ADD_DATA = '/table/addListData',
EDIT_DATA = '/table/editListData',
DEL_DATA = '/table/delListData',
}
/**
* @description: Get sample list value
*/
// export const printSignIssue = (params) =>
// defHttp.post({
// url: Api.PRINT_AMT,
// params,
// });
// table列表
export const getListDataApi = (params) =>
defHttp.get({
url: Api.LIST_DATA,
params,
});
// 新增
export const addListDataApi = (params) =>
defHttp.post({
url: Api.ADD_DATA,
params,
});
// 编辑
export const editListDataApi = (params) =>
defHttp.post({
url: Api.EDIT_DATA,
params,
});
// 删除
export const delListDataApi = (params) =>
defHttp.post({
url: Api.DEL_DATA,
params,
});
!注意:
在使用页面使用mock和api前,要先在 /src/settings/componentSetting.ts中修改响应后处理的配置,我自己之前没有设置这里,所以table组件useTable调接口一直不通
componentSetting.ts
export default {
// basic-table setting
table: {
// Form interface request general configuration
// support xxx.xxx.xxx
fetchSetting: {
// The field name of the current page passed to the background
pageField: 'page',
// The number field name of each page displayed in the background
sizeField: 'pageSize',
// Field name of the form data returned by the interface
// listField: 'items',
// 这里要和 mock 和 api 返回的数据字段匹配
listField: 'pager.list',
// Total number of tables returned by the interface field name
totalField: 'total',
},
// Number of pages that can be selected
pageSizeOptions: ['10', '50', '80', '100'],
// Default display quantity on one page
defaultPageSize: 10,
// Default Size
defaultSize: 'middle',
// Custom general sort function
defaultSortFn: (sortInfo: SorterResult) => {
const { field, order } = sortInfo;
if (field && order) {
return {
// The sort field passed to the backend you
field,
// Sorting method passed to the background asc/desc
order,
};
} else {
return {};
}
},
// Custom general filter function
defaultFilterFn: (data: Partial<Recordable<string[]>>) => {
return data;
},
},
// scrollbar setting
scrollbar: {
// Whether to use native scroll bar
// After opening, the menu, modal, drawer will change the pop-up scroll bar to native
native: false,
},
};
在之前的table练习中添加并使用api,注:我的新增、编辑和查看都改成了使用Drawer组件弹出,添加了AEVDrawerPage.vue,和之前Drawer练习的页面代码差不太多
之前相关记录:
- Table组件的基本使用及练习
- Drawer组件的基本使用及练习
data.ts
import { FormProps, FormSchema } from '/@/components/Form';
import { BasicColumn } from '/@/components/Table';
import { formatToDate } from '/@/utils/dateUtil';
import { Switch } from 'ant-design-vue';
import { ref, h, computed } from 'vue';
type sexOptionType = [{ label: string; value: string }, { label: string; value: string }];
const sexOption: sexOptionType = [
{ value: '0', label: '男' },
{ value: '1', label: '女' },
];
// 配置表格SearchForm字段
export const formConfig: Partial<FormProps> = {
labelWidth: 120,
actionColOptions: {
span: 25,
},
//自动展开行
autoAdvancedLine: 1,
showAdvancedButton: true,
baseColProps: {
span: 8,
},
schemas: [
{
field: 'contno',
label: '承兑协议号',
component: 'Input',
defaultValue: '',
componentProps: ({ formModel }) => {
return {
style: { width: '100%', textAlign: 'left' },
onInput: (e) => {
formModel.contno = formModel.contno.replace(/[^\w-\/]/gi, '');
},
};
},
},
{
field: 'name',
label: '姓名',
component: 'Input',
},
{
field: 'sex',
label: '性别',
component: 'Select',
componentProps: {
options: sexOption,
},
},
{
field: 'dt',
label: '出生日期',
component: 'DatePicker',
componentProps: {
style: {
width: '100%',
},
valueFormat: 'YYYYMMDD',
},
},
{
field: 'age',
label: '年龄',
component: 'InputNumber',
componentProps: {
style: {
width: '100%',
},
},
},
],
};
// 配置表格表头字段
export const columns: BasicColumn[] = [
{
title: '承兑协议号',
dataIndex: 'contno',
// ifShow:false
width: 200,
},
{
title: '姓名',
width: 200,
dataIndex: 'name',
// helpMessage:'aaa',
// edit:true
},
{
title: '性别',
width: 200,
dataIndex: 'sex',
customRender: function (text) {
console.log('text', text);
return text.text === '0' ? '男' : '女';
},
// customRender:({record}) => {
// console.log(record)
// return h(Switch,{checked:record.sex == '0'},)
// }
},
{
title: '出生日期',
width: 200,
dataIndex: 'dt',
customRender: function (text) {
return formatToDate(text.text);
},
},
{
title: '年龄',
width: 200,
dataIndex: 'age',
},
{
title: '电话',
width: 200,
dataIndex: 'tel',
},
{
title: '住址',
width: 200,
dataIndex: 'address',
},
];
export const schemasView: FormSchema[] = [
{
field: 'divider-bill',
component: 'Divider',
label: '信息',
colProps: {
span: 24,
},
},
{
field: 'contno',
component: 'Input',
label: '承兑协议号',
required: true,
defaultValue: '',
componentProps: ({ formModel }) => {
return {
style: { width: '100%', textAlign: 'left' },
oninput: (e) => {
// formModel.contno = formModel.contno.replace(/[^\w\/]/ig, '');
},
};
},
dynamicRules: () => {
return [
{
required: true,
validator: (_, value) => {
if (!value) {
return Promise.reject('请输入承兑协议号');
}
},
},
];
},
},
{
field: 'name',
component: 'Input',
label: '姓名',
required: true,
},
{
field: 'sex',
component: 'Select',
label: '性别',
required: true,
componentProps: {
options: sexOption,
},
},
{
field: 'dt',
component: 'DatePicker',
label: '出生日期',
required: true,
componentProps: {
style: { width: '100%' },
valueFormat: 'YYYYMMDD',
},
},
{
field: 'age',
component: 'InputNumber',
label: '年龄',
required: true,
componentProps: {
style: { width: '100%' },
step: 1,
min: 0,
max: 150,
},
},
{
label: '电话',
field: 'tel',
component: 'Input',
required: true,
dynamicRules: ({ values }) => {
if (values.tel !== undefined) {
if (values.tel.indexOf('-') > 0) {
return [
{
required: true,
trigger: 'change',
message: '请输入正确的号码',
pattern: /(^\d{4}-\d{7}$)|(^\d{3}-\d{8}$)/,
},
];
} else {
return [
{
required: true,
trigger: 'change',
message: '请输入正确的号码',
pattern: /^1[3|4|5|7|8][0-9]{9}$/,
},
];
}
} else {
return [{ required: true, message: '请输入电话' }];
}
},
},
{
field: 'address',
component: 'InputTextArea',
label: '住址',
required: true,
},
];
// 姓名
// 性别
// 出生日期
// 年龄
// 电话 tel
// 住址 address
// 表单数据
export function initData() {
return [
{
name: '张三',
sex: '男',
dt: '20200701',
age: '22',
tel: '13789890909',
address: '北京市北京小区',
},
{
name: '李四',
sex: '女',
dt: '20230507',
age: '27',
tel: '15477778888',
address: '大连市大连小区',
},
{
name: '王五',
sex: '男',
dt: '20221001',
age: '26',
tel: '15477778888',
address: '大连市大连小区',
},
{
name: '小明',
sex: '男',
dt: '20220701',
age: '25',
tel: '15477778888',
address: '大连市大连小区',
},
{
name: '小红',
sex: '女',
dt: '20180808',
age: '28',
tel: '15477778888',
address: '大连市大连小区',
},
];
}
basicTable.vue
<template>
<div
:style="{
overflow: 'hidden',
position: 'relative',
height: '100%',
}"
>
<!-- 注册table -->
<BasicTable @register="registerTable">
<template #action="{ record }">
<TableAction
:actions="[
{
tooltip: '查看',
icon: 'clarity:info-standard-line',
onClick: handleOpen.bind(null, { type: 'view', data: record }),
},
{
tooltip: '编辑',
icon: 'clarity:note-edit-line',
onClick: handleOpen.bind(null, { type: 'edit', data: record }),
},
{
tooltip: '删除',
color: 'error',
icon: 'ant-design:delete-outlined',
popConfirm: {
title: '是否确定删除?',
confirm: handleDel.bind(null, record),
},
},
]"
/>
</template>
<template #toolbar>
<a-button type="primary" @click="handleOpen({ type: 'add', data: {} })">{{
'新增'
}}</a-button>
<a-button color="warning" @click="exitExcel()">{{ '导出 Excel' }}</a-button>
</template>
</BasicTable>
<AEVDrawerPage @reload="handleReload" @register="registerDrawer" />
<!-- <ViewDrawer @reload="handleReload" @register="registerDrawer" /> -->
<!-- <EditModal @reload="handleReload" @register="registerModal" /> -->
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
// import { router } from '/@/router';
import { formConfig, columns, initData } from './data';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useDrawer } from '/@/components/Drawer';
// import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
// import { aoaToSheetXlsx } from '/@/components/Excel';
import { getListDataApi, delListDataApi } from '../../../api/table/tableApi';
// import ViewDrawer from './ViewDrawer.vue';
// import EditModal from './EditModal.vue';
import AEVDrawerPage from './AEVDrawerPage.vue';
export default defineComponent({
name: 'tableTest',
components: {
BasicTable,
TableAction,
AEVDrawerPage,
// ViewDrawer,
// EditModal
},
setup() {
// const data = initData();
const { createMessage } = useMessage();
// 设置table
const [registerTable, { reload, clearSelectedRowKeys }] = useTable(
{
title: '查询结果',
// 调用接口,之前的写死数据就不用了
api: getListDataApi,
// dataSource: data,
columns: columns,
bordered: true,
useSearchForm: true, //开启搜索区域
formConfig: formConfig,
// striped:false,
// showSummary:true,
// loading:true,
afterFetch: (data) => {
console.log('data', data);
clearSelectedRowKeys();
},
actionColumn: {
width: 120,
title: '操作',
dataIndex: 'action',
slots: { customRender: 'action' },
},
rowSelection: { type: 'radio' },
pagination: { pageSize: 10 },
showTableSetting: true,
tableSetting: { fullScreen: true },
showIndexColumn: true,
indexColumnProps: { fixed: 'left' },
},
);
// 注册Drawer
const [registerDrawer, { openDrawer: openAEVDrawerPage, setDrawerProps }] = useDrawer();
// 配置Modal
// const [registerModal, { openModal }] = useModal();
async function handleOpen({ type, data }) {
await openAEVDrawerPage(true, { type, data });
if (type === 'add') {
setDrawerProps({ title: '新增' });
} else if (type === 'edit') {
setDrawerProps({ title: '修改' });
} else {
setDrawerProps({ title: '查看' });
}
}
// async function exitExcel() {
// let tableData:[][] = getDataSource();
// let columns = getColumns()
// let titleArr = columns.map(item => {
// return item.title
// })
// console.log('tableData', tableData,columns,titleArr);
// // debugger;
// aoaToSheetXlsx({
// data: tableData,
// header: titleArr,
// filename: '导出excel.xlsx',
// });
// }
// 新增按钮
// function handleAdd() {
// router.push({
// path: '/testRoute/addPage',
// });
// }
// 查看按钮
// function handleView({ data }) {
// openDrawer(true, data);
// }
// 编辑按钮
// function handleEdit({ data }) {
// openModal(true, data);
// }
// 删除按钮
async function handleDel(record: Recordable) {
console.log('删除数据', record);
await delListDataApi(record).then(() => {
createMessage.success('删除成功');
reload();
});
}
function handleReload() {
reload();
}
return {
registerTable,
reload,
handleOpen,
// 新增路由跳转
// handleAdd,
// 查看抽屉
registerDrawer,
// handleView,
handleReload,
// 编辑弹窗
// registerModal,
// handleEdit,
// 删除
handleDel,
// exitExcel,
};
},
});
</script>
<style scoped></style>
AEVDrawerPage.vue(除了主要的调用接口,还有点我自己练习的tabs等相关代码,可以忽略不看…)
<template>
<div
:style="{
overflow: 'hidden',
}"
>
<BasicDrawer
v-bind="$attrs"
@register="registerDrawer"
@visibleChange="handleVisibleChange"
:isDetail="true"
title="查看"
placement="bottom"
height="100%"
:destroyOnClose="true"
>
<a-tabs>
<a-tab-pane key="1" tab="表单页面">
<div>
<BasicForm @register="registerForm"></BasicForm>
</div>
<Divider />
<PageFooter v-if="showPageFooter">
<template #right>
<a-button @click="handleCancel"> 取消</a-button>
<a-button class="!ml-4" type="primary" @click="handleSubmit" :loading="isLoading">
保存</a-button
>
</template>
</PageFooter>
</a-tab-pane>
<a-tab-pane key="2" tab="明细页面">
<!-- <BasicTable @register="registerTable">
<template #action="{}">
<TableAction
:actions="[
{
tooltip: '查看',
icon: 'clarity:info-standard-line',
},
{
tooltip: '编辑',
icon: 'clarity:note-edit-line',
},
{
tooltip: '删除',
color: 'error',
icon: 'ant-design:delete-outlined',
},
]"
/>
</template>
<template #toolbar>
<a-button type="primary">{{
'新增'
}}</a-button>
<a-button color="warning" >{{ '导出 Excel' }}</a-button>
</template>
</BasicTable> -->
</a-tab-pane>
<a-tab-pane key="3" tab="其他页面"></a-tab-pane>
</a-tabs>
</BasicDrawer>
</div>
</template>
<script lang="ts">
import { defineComponent, Ref, ref } from 'vue';
import { Button, Divider, Tabs } from 'ant-design-vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { BasicForm, useForm } from '/@/components/Form';
import { PageFooter } from '/@/components/Page';
import { useMessage } from '/@/hooks/web/useMessage';
import { schemasView } from './data';
import {
// getListDataApi,
addListDataApi,
editListDataApi,
} from '/@/api/table/tableApi';
// import { formConfig, columns, initData } from './data';
export default defineComponent({
name: 'AEVDrawerPage',
components: {
BasicDrawer,
BasicForm,
Divider,
PageFooter,
[Tabs.name]: Tabs,
[Tabs.TabPane.name]: Tabs.TabPane,
[Button.name]: Button,
BasicTable,
TableAction,
},
setup(_, { emit }) {
const { createMessage } = useMessage();
const currentType: Ref = ref('add');
const showPageFooter: Ref = ref(true);
const isLoading = ref(false);
// const [registerTable, { }] = useTable(
// {
// title: '查询结果',
// api: getListDataApi,
// // dataSource: data,
// columns: columns,
// bordered: true,
// // useSearchForm: true, //开启搜索区域
// // formConfig: formConfig,
// // striped:false,
// // showSummary:true,
// // loading:true,
// actionColumn: {
// width: 120,
// title: '操作',
// dataIndex: 'action',
// slots: { customRender: 'action' },
// },
// rowSelection: { type: 'radio' },
// pagination: { pageSize: 10 },
// showTableSetting: true,
// tableSetting: { fullScreen: true },
// showIndexColumn: true,
// indexColumnProps: { fixed: 'left' },
// },
// );
// 配置Drawer
const [registerDrawer, { closeDrawer }] = useDrawerInner(async ({ type, data }) => {
console.log('打印从table传递的数据:', type, data);
currentType.value = type;
console.log('currentType', currentType);
setFieldsValue(data);
if (type == 'view') {
showPageFooter.value = false;
setProps({ disabled: true });
} else {
showPageFooter.value = true;
}
});
// 配置Form
const [registerForm, { validate, getFieldsValue, setFieldsValue, setProps }] = useForm({
labelWidth: 150,
baseColProps: {
offset: 1,
span: 10,
},
schemas: schemasView,
showActionButtonGroup: false,
});
function handleVisibleChange(visible: boolean) {
if (!visible) {
emit('reload');
}
}
async function handleSubmit() {
console.log('handleSubmit', currentType);
// await validate();
const paramsData = await getFieldsValue();
console.log('paramsData', paramsData);
isLoading.value = true;
if (currentType.value === 'add') {
await addListDataApi({ ...paramsData }).then((res) => {
isLoading.value = false;
createMessage.success('保存成功');
closeDrawer();
});
} else {
await editListDataApi({ ...paramsData }).then((res) => {
isLoading.value = false;
createMessage.success('保存成功');
closeDrawer();
});
}
}
// 取消按钮
function handleCancel() {
console.log('handleCancel', currentType);
emit('back');
closeDrawer();
}
return {
currentType,
showPageFooter,
isLoading,
registerDrawer,
closeDrawer,
registerForm,
validate,
getFieldsValue,
setFieldsValue,
setProps,
handleVisibleChange,
handleSubmit,
handleCancel,
// registerTable,
};
},
});
</script>
<style scoped></style>
页面效果
vben-admin 使用mock添加增删改接口逻辑及联调