从0开始学习 vben admin(二)
前言
今天是记录学习vben的第二天,我们今天主要是来讲一下如何对接后端api接口,表格的基本用法,弹窗的用法,以及把权限调整为后端权限。
正文
api接口配置
在vite.config.ts
中,有关于这个api的配置,在这里可以配置您api的地址、端口等。
import { defineApplicationConfig } from '@vben/vite-config';
export default defineApplicationConfig({
overrides: {
optimizeDeps: {
include: [
'echarts/core',
'echarts/charts',
'echarts/components',
'echarts/renderers',
'qrcode',
'@iconify/iconify',
'ant-design-vue/es/locale/zh_CN',
'ant-design-vue/es/locale/en_US',
],
},
server: {
port: 10086, // 端口号
proxy: { // api地址配置
'/api': {
target: 'http://localhost:8899/api/', // 在这里可以配置您的地址和端口
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp(`^/api/`), ''),
// only https
// secure: false
},
'/upload': { // 上传接口地址配置
target: 'http://localhost:3300/upload',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp(`^/upload/`), ''),
},
},
},
},
});
在配置好以后,可以去.env.development
文件配置开发环境的api接口,关闭mock模拟数据,然后重新启动一下,就能够访问自己的后端接口啦!
# Whether to open mock 这里是是否开启模拟数据,如果我们要使用自己的后台接口,则需要关闭他
VITE_USE_MOCK = false
# public path
VITE_PUBLIC_PATH = /
# Basic interface address SPA 这里是请求默认的api
VITE_GLOB_API_URL=/api/
# File upload address, optional
VITE_GLOB_UPLOAD_URL=/upload/
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
发起请求,看到原来的base-api
变成了api
就说明成功了,下面我们去src/api/sys/user.ts
改一下他封装的登录接口,只需要把Api
中的/
去掉就可以正常使用了,如果需要将登录的字段名称修改,则需要前往src/api/sys/model/userModel.ts
和src/views/sys/login/LoginForm.vue
进行修改,到这里,我们讲的第一部分就结束了,如果有不懂的小伙伴,可以在评论区提问。
enum Api {
Login = 'login', // 登录接口
Logout = 'logout', // 登出
GetUserInfo = 'getUserInfo', // 获取用户信息
GetPermCode = 'getPermCode', // 获取用户权限码
TestRetry = 'testRetry',
}
修改传值字段名称
// src/api/sys/model/userModel.ts 文件路径
/**
* @description: Login interface parameters
*/
export interface LoginParams {
username: string; // 可以修改为你想要的 比如 phone
password: string;
}
// src/views/sys/login/LoginForm.vue 文件路径
const formData = reactive({
username: 'vben', // 这里可以修改
password: '123456',
});
const { validForm } = useFormValid(formRef);
//onKeyStroke('Enter', handleLogin);
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN);
async function handleLogin() {
const data = await validForm();
if (!data) return;
try {
loading.value = true;
const userInfo = await userStore.login({
password: data.password,
username: data.username, // 这里可以修改
mode: 'none', //不要默认的错误提示
});
if (userInfo) {
notification.success({
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`,
duration: 3,
});
}
} catch (error) {
createErrorModal({
title: t('sys.api.errorTip'),
content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'),
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body,
});
} finally {
loading.value = false;
}
}
故意输入错误,测试是否能接通
在登录后,框架会自动调用以下几个接口,在每次刷新也会调用,以保证管理员修改权限后可以实时更新。
表格的使用方法
在官网有一个菜单是Table 表格 | Vben Admin (vvbin.cn),这里讲了表格组件的一些用法,今天就基于这个,做一个展示列表。如你所见,我并没有在官方给出的地方存放对应的api
等文件,而是在模块文件夹中存放,这样存放,有助于我个人开发毕竟我是个后端,修改起来方便才是王道
,而对我来说整个项目文件夹太多,找起来很麻烦,所以我选择了这样不太规范的开发方式,大家可以对应文件目录说明来存放你的代码。
我把他们分成了四个模块
文件夹名称 | 用途 |
---|---|
api | api接口 |
data | 数据/表头 信息 |
modal | 弹窗 |
Xxxx.Vue | 主页面 |
主页面视图
<template>
<div>
<BasicTable
@register="registerTable"
:rowSelection="{ type: 'checkbox', selectedRowKeys: checkedKeys, onChange: onSelectChange }"
>
<template #headerTop>
<a-alert type="info" show-icon>
<template #message>
<template v-if="checkedKeys.length > 0">
<span>已选中{{ checkedKeys.length }}条记录(可跨页)</span>
<a-button type="link" @click="checkedKeys = []" size="small">清空</a-button>
</template>
<template v-else>
<span>未选中任何数据</span>
</template>
</template>
</a-alert>
</template>
<template #paper="{ record }">
<TableImg :size="60" :simpleShow="true" :imgList="record.paper" />
</template>
<template #office="{ record }">
<TableImg :size="60" :simpleShow="true" :imgList="record.office" />
</template>
<template #front="{ record }">
<TableImg :size="60" :simpleShow="true" :imgList="record.front" />
</template>
<template #portrait="{ record }">
<TableImg :size="60" :simpleShow="true" :imgList="record.portrait" />
</template>
<template #national="{ record }">
<TableImg :size="60" :simpleShow="true" :imgList="record.national" />
</template>
<template #toolbar>
<a-button v-auth="'1005'" type="primary" @click="excekOut">导出excel</a-button>
<a-button v-auth="'1003'" type="primary" @click="clickAdd">新增企业管理</a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction
:actions="[
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: '1001',
},
{
label: '删除',
color: 'error',
popConfirm: {
title: '是否删除该数据',
confirm: handleDelete.bind(null, record),
},
auth: '1002',
},
{
label: '启用/禁用',
color: 'warning',
popConfirm: {
title: '是否启用/禁用该数据',
confirm: handleDelete.bind(null, record),
},
auth: '1004',
},
]"
/>
</template>
</BasicTable>
<Modal1
@register="register1"
@ok="tableReload"
@close="tableReload"
:centered="true"
:width="1200"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table'; // 引用table组件
import { getEnterpriseColumns } from './data/enterprisetable';
import { Alert } from 'ant-design-vue';
import { useModal } from '/@/components/Modal'; // 引用弹窗组件
import Modal1 from './modal/EnterpriseModal.vue'; // 引入弹窗页面
import { Tag } from 'ant-design-vue';
import { getEnterpriseList, deleteEnterprise } from './api/enterprise'; // 引入api接口
import AButton from '/@/components/Button/src/BasicButton.vue'; // 引入按钮
import { getToken } from '/@/utils/auth'; // 获取用户token,导出excel使用
export default defineComponent({
components: { TableImg, AButton, BasicTable, TableAction, AAlert: Alert, Modal1, Tag }, //声明组件
setup() {
const checkedKeys = ref<Array<string | number>>([]);
const [register1, { openModal: openModal1 }] = useModal(); // 实例化弹窗
const [registerTable, {
expandAll, collapseAll, reload // 这里是声明要使用的方法
}] = useTable({ // 配置表格
title: '企业管理列表', // 标题
api: getEnterpriseList, // 获取数据的接口
columns: getEnterpriseColumns(), // 表头数据
useSearchForm: false, // 是否开启表格搜索
// formConfig: getUserFormConfig(), // 表格搜索的字段
showTableSetting: true, // 显示表格设置工具
tableSetting: { fullScreen: true }, // 是否显示全屏按钮
showIndexColumn: false, // 是否显示序列号
rowKey: 'id', // 行的主键
clickToRowSelect: false, // 是否开启点击行选中
bordered: true, // 是否开启边框
actionColumn: { // 自定义列
width: 250,
title: '操作',
dataIndex: 'action',
slots: { customRender: 'action' }, // 插槽名称
},
});
// 新增企业管理
function clickAdd() {
openModal1(true); // 让弹窗弹出
}
function onSelectChange(selectedRowKeys: (string | number)[]) {
console.log(selectedRowKeys);
checkedKeys.value = selectedRowKeys;
}
// 编辑企业管理
function handleEdit(record: Recordable) {
openModal1(true, record); // 让弹窗弹出,为了方便我使用了同一个弹窗页面,第二个参数是传值
// console.log('点击了编辑');
}
// 删除企业管理
function handleDelete(record: Recordable) {
deleteEnterprise(record);
// console.log('点击了删除 ID: ');
tableReload();
}
function handleOpen(record: Recordable) {
console.log('点击了启用' + record);
tableReload();
}
function excekOut() {
window.open('http://127.0.0.1:8899/api/codeGenerationExcelOut?token=' + getToken());
}
function tableReload() {
reload(); // 刷新表格数据,需要在useTable中声明
console.log(reload());
}
return {
tableReload,
register1,
registerTable,
expandAll,
collapseAll,
clickAdd,
checkedKeys,
onSelectChange,
handleEdit,
handleDelete,
handleOpen,
excekOut,
};
},
});
</script>
表头配置
前面讲到表头是获取data
中的数据,以下是表头的代码,同样我会将方法的用途写在注释中,这样子表头就配置完成了,下面就是获取数据
import { BasicColumn } from '/@/components/Table/src/types/table';// 引用表头设置的方法
// 权限列表表头
export function getEnterpriseColumns(): BasicColumn[] {
return [
{
title: '企业ID',
dataIndex: 'id',
fixed: 'left',
width: 90,
},
{
title: '企业名称',
dataIndex: 'name',
},
{
title: '企业别名',
dataIndex: 'aliasname',
},
{
title: '企业地址id',
dataIndex: 'address',
defaultHidden: true,
},
{
title: '企业地址',
dataIndex: 'address_text',
},
{
dataIndex: 'status',
title: '状态',
},
];
}
api
api就正常封装调用,写在useTable中的api中,如果你没有去看我的那一篇文章,那么我还是要提醒你,返回参数需要去src\settings\componentSetting.ts
中更改,下面我就会讲。
表格配置
// Used to configure the general configuration of some components without modifying the components
import type { SorterResult } from '../components/Table';
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', // 数据返回的字段名
// 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;
},
},
vxeTable: {
table: {
border: true,
stripe: true,
columnConfig: {
resizable: true,
isCurrent: true,
isHover: true,
},
rowConfig: {
isCurrent: true,
isHover: true,
},
emptyRender: {
name: 'AEmpty',
},
printConfig: {},
exportConfig: {},
customConfig: {
storage: true,
},
},
grid: {
toolbarConfig: {
enabled: true,
export: true,
zoom: true,
print: true,
refresh: true,
custom: true,
},
pagerConfig: {
pageSizes: [20, 50, 100, 500],
pageSize: 20,
autoHidden: true,
},
proxyConfig: {
form: true,
props: {
result: 'items',
total: 'total',
},
},
zoomConfig: {},
},
},
// 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,
},
};
弹窗的使用
弹窗是一个名为Modal
的组件,文档是 Modal 弹窗 | Vben Admin (vvbin.cn)
在主页面的那个视图中,仔细看注释,会发现我已经将引用、声明、调用讲过了,所以这里我就说一下他其他的配置。
<template>
<div>
<!-- 在这里的 @ok方法来监听点击确认按钮 -->
<BasicModal
v-bind="$attrs"
@register="modalInner"
title="企业管理 窗口"
@ok="addSubmitForm"
@visible-change="handleVisibleChange"
>
<div class="pt-3px pr-3px">
<BasicForm
@register="registerForm"
:model="model"
api="127.0.0.1:8801/api/upload/uploadImage"
/>
</div>
</BasicModal>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, nextTick } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { addEnterprise, updateEnterprise } from '../api/enterprise';
import { getEnterpriseForm } from '../data/enterpriseform';
export default defineComponent({
components: { BasicModal, BasicForm },
props: {
userData: { type: Object },
},
setup(props) {
const modelRef = ref({});
const isUpdate = ref(false);
const id = ref(0);
const [modalInner, { closeModal }] = useModalInner((data) => { // 这个函数是用来接值的
if (data != null) { // 根据接值来判断是新增还是修改
isUpdate.value = true;
}
id.value = data.id;
setFieldsValue(data);
});
const [ // 声明表单
registerForm,
{ getFieldsValue, setFieldsValue, resetFields, validate, updateSchema },
] = useForm({
labelWidth: 180,
schemas: getEnterpriseForm(), // 获取你设置的表单字段
showActionButtonGroup: false,
actionColOptions: {
span: 24,
},
});
function onDataReceive(data) {
console.log('Data Received', data);
}
// 新增企业管理
function addSubmitForm() {
// 这里是验证表单必填值
validate();
// 获取表单中的值
let data = getFieldsValue();
console.log(data);
if (!isUpdate.value) {
// 执行添加接口,是在api中封装的接口
addEnterprise(data);
} else {
data.id = id.value;
// 执行修改接口,是在api中封装的接口
updateEnterprise(data);
}
// 这个方法是官网的一个重置表单的方法
resetFields();
isUpdate.value = false;
// 关闭弹窗
closeModal();
}
function handleVisibleChange(v) {
v && props.userData && nextTick(() => onDataReceive(props.userData));
}
return {
updateSchema,
modalInner,
registerForm,
model: modelRef,
handleVisibleChange,
addSubmitForm,
};
},
});
</script>
表单配置
下面就是表单的配置,Form 表单组件 | Vben Admin (vvbin.cn)
// import { getPermissionOption } from '/@/api/sys/permission';
import { FormSchema } from '/@/components/Form';
import address from '/@/../public/json/address.json';
export function addressJson() {
return address;
}
// 权限表单
export function getEnterpriseForm(): FormSchema[] {
return [
{
field: 'name', // 字段名称
component: 'Input', // 表单组件
label: '企业名称', // 字段标题
required: true, // 是否必填
colProps: {
span: 24, // 占位长度 24 为一行
},
},
{
field: 'aliasname',
component: 'Input',
label: '企业别名',
required: true,
colProps: {
span: 24,
},
},
{
field: 'address',
component: 'Cascader',
label: '企业地址',
required: true,
componentProps: {
options: address,
},
colProps: {
span: 24,
},
},
];
}
权限配置
权限,主要可以分为两种权限模式 前端权限
和后端权限
,具体可以去官网的 权限 查看,不过一般来看,我们通常会使用后端权限
来进行管理,权限是在src/settings/projectSetting.ts
,将ROUTE_MAPPING
改为BACK
即可,修改后重新启动会发现你在路由中的目录都不见了,这时候需要后端来提供该用户能看到的目录,具体也在权限中能看到,我们这里就不过分的重复说明了。
修改前
// Permission mode
permissionMode: PermissionModeEnum.ROUTE_MAPPING,
修改后
// Permission mode
permissionMode: PermissionModeEnum.BACK,
结尾
结语
总的来说,要想实现简单的增删改查是很简单的,但是要想进行一些高级的操作,就需要对vue3和# Ant Design Vue有一些经验,vben采用了对Ant的二次封装,使得对有使用经验的开发人员来说很大的提升了开发速度,但是也对一些小白来说不太友好,所以在新手开发的时候,会出现很多问题,大家在使用表格组件如果出现了自己无法解决的问题,欢迎来评论区提问、交流,毕竟我也是踩坑很久,会尽力帮助各位掘友来解决麻烦的。
下期预告
下期我会讲一些小东西
- 表单组件基础使用
- 表单组件的高级用法
转载自:https://juejin.cn/post/7239951583059951672