一、静态组件
//src/views/product/trademark/index.vue
<template>
<el-card class="box-card">
<!-- 卡片顶部添加品牌按钮 -->
<el-button type="primary" size="default" icon="Plus">添加品牌</el-button>
<!-- 表格组件,用于展示已有的数据 -->
<!--
table
---border:是否有纵向的边框
table-column
---lable:某一个列表
---width:设置这一列的宽度
---align:设置这一列对齐方式
-->
<el-table style="margin: 10px 0px" border>
<el-table-column
label="序号"
width="80px"
align="center"
></el-table-column>
<el-table-column label="品牌名称"></el-table-column>
<el-table-column label="品牌LOGO"></el-table-column>
<el-table-column label="品牌操作"></el-table-column>
</el-table>
<!-- 分页器组件 -->
<!--
pagination
---v-model:current-page:设置当前分页器页码
---v-model:page-size:设置每一也展示数据条数
---page-sizes:每页显示个数选择器的选项设置
---background:背景颜色
---layout:分页器6个子组件布局的调整 "->"把后面的子组件顶到右侧
-->
<el-pagination
v-model:current-page="pageNo"
v-model:page-size="limit"
:page-sizes="[3, 5, 7, 9]"
:background="true"
layout=" prev, pager, next, jumper,->,total, sizes,"
:total="400"
/>
</el-card>
</template>
<script setup lang="ts">
//引入组合式API函数
import { ref } from 'vue'
//当前页码
let pageNo = ref<number>(1)
//每一页展示的数据
let limit = ref<number>(3)
</script>
<style lang="scss" scoped></style>
二、数据模块
1、API
1)api函数
//src\api\product\trademark\index.ts
//书写品牌管理模块接口
import request from '@/utils/request'
//品牌管理模块接口地址
enum API {
//获取已有品牌接口
TRADEMARK_URL = '/admin/product/baseTrademark/',
}
//获取一样已有的品牌的接口方法
//page:获取第几页 ---默认第一页
//limit:获取几个已有品牌的数据
export const reqHasTrademark = (page: number, limit: number) =>
request.get<any, any>(API.TRADEMARK_URL + `${page}/${limit}`)
2)获取数据
我们获取数据没有放在pinia中,二是放在组件中挂载时获取数据
//src\views\product\trademark\index.vue
<script setup lang="ts">
import { reqHasTrademark } from '@/api/product/trademark'
//引入组合式API函数
import { ref, onMounted } from 'vue'
//当前页码
let pageNo = ref<number>(1)
//每一页展示的数据
let limit = ref<number>(3)
//存储已有品牌数据总数
let total = ref<number>(0)
//存储已有品牌的数据
let trademarkArr = ref<any>([])
//获取已有品牌的接口封装为一个函数:在任何情况下向获取数据,调用次函数即可
const getHasTrademark = async (pager = 1) => {
//当前页码
pageNo.value = pager
let result = await reqHasTrademark(pageNo.value, limit.value)
console.log(result)
if (result.code == 200) {
//存储已有品牌总个数
total.value = result.data.total
trademarkArr.value = result.data.records
console.log(trademarkArr)
}
}
//组件挂载完毕钩子---发一次请求,获取第一页、一页三个已有品牌数据
onMounted(() => {
getHasTrademark()
})
</script>
2、数据展示
在数据展示模块,我们使用了element-plus的el-table,下面组要讲解属性和注意点。
1)data属性:显示的数据
比如我们这里绑定的trademarkArr是个三个对象的数组,就会多出来3行。
<el-table style="margin: 10px 0px" border :data="trademarkArr">
<el-table-column label="序号" width="80px" align="center"></el-table-column>
<el-table-column label="品牌名称"></el-table-column>
<el-table-column label="品牌LOGO"></el-table-column>
<el-table-column label="品牌操作"></el-table-column>
</el-table>
2)el-table-column的type属性:对应列的类型。 如果设置了selection则显示多选框; 如果设置了 index 则显示该行的索引(从 1 开始计算); 如果设置了 expand 则显示为一个可展开的按钮
<el-table-column label="序号" width="80px" align="center" type="index"></el-table-column>
3)el-table-column的prop属性:字段名称 对应列内容的字段名, 也可以使用 property属性
注意:因为我们之前已经绑定了数据,所以在这里直接使用数据的属性tmName
<el-table-column label="品牌名称" prop="tmName"></el-table-column>
4)el-table-column的插槽
为什么要使用插槽呢?因为prop属性虽然能够展示数据,但是他默认是div,如果我们的图片使用prop展示的话,会展示图片的路径。因此如果想展示图片或者按钮,我们就要使用插槽
<el-table-column label="品牌LOGO">
<template #="{ row, $index}">
<img :src="row.logoUrl" width="100px" height="100px"/>
</template>
</el-table-column>
注意:row就是我们的trademarkArr的每一个数据(对象)
三、品牌类型定义
API中的以及组件中。
//src\api\product\trademark\type.ts
export interface ResponseData {
code: number
message: string
ok: boolean
}
//已有的品牌的ts数据类型
export interface TradeMark {
id?: number
tmName: string
logoUrl: string
}
//包含全部品牌数据的ts类型
export type Records = TradeMark[]
//获取的已有全部品牌的数据ts类型
export interface TradeMarkResponseData extends ResponseData {
data: {
records: Records
total: number
size: number
current: number
searchCount: boolean
pages: number
}
}
四、分页展示数据
此部分主要是俩个功能,第一个是当点击分页器页数时能跳转到对应的页数。第二个是每页展示的数据条数能正确显示
1、跳转页数函数
这里我们绑定的点击回调直接用的是之前写好的发送请求的回调。可以看出,发送请求的回调函数是有默认的参数:
1.注意:因为current-change方法时element-plus封装好的,它会给父组件传递并注入一个参数(点击的页码),所以相当于把这个参数传递给了getHasTrademark函数,因此能够跳转到正确的页码数
<el-pagination
v-model:current-page="pageNo"
v-model:page-size="limit"
:page-sizes="[3, 5, 7, 9]"
:background="true"
layout=" prev, pager, next, jumper,->,total, sizes,"
:total="total"
@current-change = "getHasTrademark"
/>
//src/views/product/trademark/index.vue
//获取已有品牌的接口封装为一个函数:在任何情况下向获取数据,调用次函数即可
const getHasTrademark = async (pager = 1) => {
//当前页码
pageNo.value = pager
let result: TradeMarkResponseData = await reqHasTrademark(
pageNo.value,
limit.value,
)
if (result.code == 200) {
//存储已有品牌总个数
total.value = result.data.total
trademarkArr.value = result.data.records
}
}
2、每页展示数据条数
<el-pagination
v-model:current-page="pageNo"
v-model:page-size="limit"
:page-sizes="[3, 5, 7, 9]"
:background="true"
layout=" prev, pager, next, jumper,->,total, sizes,"
:total="total"
@current-change = "getHasTrademark"
@size-change = "sizeChange"
/>
//当下拉菜单发生变化的时候触发此方法
//这个自定义事件,分页器组件会将下拉菜单选中数据返回
const sizeChange = () => {
//当前每一页的数据量发生变化的时候,当前页码归1
getHasTrademark()
console.log(123)
}
五、dialog对话框静态搭建
1、对话框的标题&&显示隐藏
v-model:属性用户控制对话框的显示与隐藏的 true显示 false隐藏
title:设置对话框左上角标题
<el-dialog v-model="dialogFormVisible" title="Shipping address" width="800">
</el-dialog>
2、表单项
<el-form style="width: 80%">
<el-form-item label="品牌名称" label-width="100px" prop="tmName">
<el-input
placeholder="请您输入品牌名称"
v-model="trademarkParams.tmName"
></el-input>
</el-form-item>
<el-form-item label="品牌LOGO" label-width="100px" prop="logoUrl">
<!-- upload组件属性:action图片上传路径书写/api,代理服务器不发送这次post请求 -->
<el-upload
class="avatar-uploader"
action="/api/admin/product/fileUpload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img
v-if="trademarkParams.logoUrl"
:src="trademarkParams.logoUrl"
class="avatar"
/>
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
</el-form>
3、确定与取消按钮
<template #footer>
<el-button type="primary" size="default" @click="cancel">取消</el-button>
<el-button type="primary" size="default" @click="confirm">确定</el-button>
</template>
六、新增品牌
1、api
//src/api/product/trademark/index.ts
//书写品牌管理模块接口
import request from '@/utils/request'
import type { TradeMarkResponseData, TradeMark } from './type'
//品牌管理模块接口地址
enum API {
。。。。。。
//添加品牌
ADDTRADEMARK_URL = '/admin/product/baseTrademark/save',
//修改已有品牌
UPDATETRADEMARK_URL = '/admin/product/baseTrademark/update',
}
。。。。。。
//添加与修改已有品牌接口方法
export const reqAddOrUpdateTrademark = (data: TradeMark) => {
//修改已有品牌的数据
if (data.id) {
return request.put<any, any>(API.UPDATETRADEMARK_URL, data)
} else {
//新增品牌
return request.post<any, any>(API.ADDTRADEMARK_URL, data)
}
}
2、收集品牌名称
1)收集数据
<el-form style="width: 80%">
<el-form-item label="品牌名称" label-width="100px" prop="tmName">
<el-input placeholder="请您输入品牌名称" v-model="trademarkParams.tmName"></el-input>
</el-form-item>
<el-form-item label="品牌LOGO" label-width="100px" prop="logoUrl">
<!-- upload组件属性:action图片上传路径书写/api,代理服务器不发送这次post请求 -->
<el-upload class="avatar-uploader" action="/api/admin/product/fileUpload" :show-file-list="false"
:on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<img v-if="trademarkParams.logoUrl" :src="trademarkParams.logoUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
</el-form>
//定义收集新增品牌数据
let trademarkParams = reactive<TradeMark>({
tmName: '',
logoUrl: '',
})
2)upload组件的属性介绍
<!-- upload组件属性:action图片上传路径书写/api,代理服务器不发送这次post请求 -->
<el-upload class="avatar-uploader" action="/api/admin/product/fileUpload" :show-file-list="false"
:on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<img v-if="trademarkParams.logoUrl" :src="trademarkParams.logoUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
class:带的一些样式,需复制到style中
action:图片上传路径需要书写/api,否则代理服务器不发送这次post请求
:show-file-list:是否展示已经上传的文件
:before-upload:上传图片之前的钩子函数
//上传图片组件->上传图片之前触发的钩子函数
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
//钩子是在图片上传成功之前触发,上传文件之前可以约束文件类型与大小
//要求:上传文件格式png|jpg|gif 4M
if (
rawFile.type == 'image/png' ||
rawFile.type == 'image/jpeg' ||
rawFile.type == 'image/gif'
) {
if (rawFile.size / 1024 / 1024 < 4) {
return true
} else {
ElMessage({
type: 'error',
message: '上传文件大小小于4M',
})
return false
}
} else {
ElMessage({
type: 'error',
message: '上传文件格式务必PNG|JPG|GIF',
})
return false
}
}
:on-success:图片上传成功钩子(收集了上传图片的地址)
在这里,你将本地的图片上传到之前el-upload组件的action="/api/admin/product/fileUpload"
这个地址上,然后on-success钩子会将上传后图片的地址返回
3)上传图片后,用图片代替加号
3、添加品牌
1)点击确定按钮回调
const confirm = async () => {
//在你发请求之前,要对于整个表单进行校验
//调用这个方法进行全部表单相校验,如果校验全部通过,在执行后面的语法
// await formRef.value.validate()
let result: any = await reqAddOrUpdateTrademark(trademarkParams)
//添加|修改已有品牌
if (result.code == 200) {
//关闭对话框
dialogFormVisible.value = false
//弹出提示信息
ElMessage({
type: 'success',
message: trademarkParams.id ? '修改品牌成功' : '添加品牌成功',
})
//再次发请求获取已有全部的品牌数据
getHasTrademark(trademarkParams.id ? pageNo.value : 1)
} else {
//添加品牌失败
ElMessage({
type: 'error',
message: trademarkParams.id ? '修改品牌失败' : '添加品牌失败',
})
//关闭对话框
dialogFormVisible.value = false
}
}
2 )每次点击添加品牌的时候先情况之前的数据
//添加品牌按钮的回调
const addTrademark = () => {
//对话框显示
dialogFormVisible.value = true
//清空收集数据
trademarkParams.tmName = ''
trademarkParams.logoUrl = ''
}
七、修改品牌数据
1、绑定点击函数
其中的row就是当前的数据
<el-button type="primary" size="small" icon="Edit" @click="updateTrademark(row)" />
2、回调函数
//修改已有品牌的按钮的回调
//row:row即为当前已有的品牌
const updateTrademark = (row: TradeMark) => {
//对话框显示
dialogFormVisible.value = true
//ES6语法合并对象
Object.assign(trademarkParams, row)
}
3、对确认按钮回调修改
const confirm = async () => {
。。。。。。。
if (result.code == 200) {
。。。
//弹出提示信息
ElMessage({
。。。。
message: trademarkParams.id ? '修改品牌成功' : '添加品牌成功',
})
//再次发请求获取已有全部的品牌数据
getHasTrademark(trademarkParams.id ? pageNo.value : 1)
} else {
//添加品牌失败
ElMessage({
。。。。
message: trademarkParams.id ? '修改品牌失败' : '添加品牌失败',
})
。。。。
}
}
4、设置对话框标题
<el-dialog v-model="dialogFormVisible" :title="trademarkParams.id?'修改品牌':'添加品牌'" width="800">
5、小问题
当我们修改操作之后再点击添加品牌,对话框的title依旧是修改品牌。怎么是因为对话框的title是根据trademarkParams.id来的,我们之前添加品牌按钮操作没有对id进行清除。修改为如下就可
//添加品牌按钮的回调
const addTrademark = () => {
//对话框显示
dialogFormVisible.value = true
//清空收集数据
trademarkParams.id = 0
trademarkParams.tmName = ''
trademarkParams.logoUrl = ''
}
八、品牌管理模块表单校验
1、表单校验(自定义规则校验,可以简略堪称三步走)
1)绑定参数
<el-form
ref="formRef"
:model="trademarkParams"
:rules="rules" style="width: 80%">
:model:校验的数据
:rules:校验规则
ref="formRef":表单实例
<el-form-item
label="品牌名称"
label-width="100px"
prop="tmName">
</el-form-item>
prop:表单元素校验的数据,可以直接使用表单绑定的数据。
2)Rules
//表单校验规则对象
const rules = {
tmName: [
//required:这个字段务必校验,表单项前面出来五角星
//trigger:代表触发校验规则时机[blur、change]
{ required: true, trigger: 'blur', validator: validatorTmName },
],
logoUrl: [{ required: true, validator: validatorLogoUrl }],
}
3)Rules中写的方法
//品牌自定义校验规则方法
const validatorTmName = (rule: any, value: any, callBack: any) => {
//是当表单元素触发blur时候,会触发此方法
//自定义校验规则
if (value.trim().length >= 2) {
callBack()
} else {
//校验未通过返回的错误的提示信息
callBack(new Error('品牌名称位数大于等于两位'))
}
}
//品牌LOGO图片的自定义校验规则方法
const validatorLogoUrl = (rule: any, value: any, callBack: any) => {
//如果图片上传
if (value) {
callBack()
} else {
callBack(new Error('LOGO图片务必上传'))
}
}
2、图片校验
1)图片校验时机
因为img是图片,不好判断。因此使用表单的validate属性,全部校验,放在确认按钮的回调函数中
const confirm = async () => {
//在你发请求之前,要对于整个表单进行校验
//调用这个方法进行全部表单相校验,如果校验全部通过,在执行后面的语法
await formRef.value.validate()//返回的是一个promise对象
。。。。。。
}
2)清除校验信息
当图片没有上传点击确认后会出来校验的提示信息,我们上传图片后校验信息应该消失。使用表单的clearValidate属性
//图片上传成功钩子
const handleAvatarSuccess: UploadProps['onSuccess'] = (
。。。。。。
) => {
。。。。。。。
//图片上传成功,清除掉对应图片校验结果
formRef.value.clearValidate('logoUrl')
}
当我们未填写信息去点击确认按钮时,会弹出2个校验信息。当我们关闭后再打开,校验信息还在。因为,我们需要在添加品牌按钮时清除校验信息。但是因为点击添加品牌,表单还没有加载,所以我们需要换个写法。
//添加品牌按钮的回调
const addTrademark = () => {
//对话框显示
dialogFormVisible.value = true
//清空收集数据
trademarkParams.id = 0
trademarkParams.tmName = ''
trademarkParams.logoUrl = ''
//第一种写法:ts的问号语法
formRef.value?.clearValidate('tmName')
formRef.value?.clearValidate('logoUrl')
/* nextTick(() => {
formRef.value.clearValidate('tmName')
formRef.value.clearValidate('logoUrl')
}) */
}
同理修改按钮
//修改已有品牌的按钮的回调
//row:row即为当前已有的品牌
const updateTrademark = (row: TradeMark) => {
//清空校验规则错误提示信息
nextTick(() => {
formRef.value.clearValidate('tmName')
formRef.value.clearValidate('logoUrl')
})
。。。。。。
}
九、删除品牌
删除业务要做的事情不多,包括API以及发请求。不过有些点要注意
1、API
//书写品牌管理模块接口
import request from '@/utils/request'
import type { TradeMarkResponseData, TradeMark } from './type'
//品牌管理模块接口地址
enum API {
。。。。。。。
//删除已有品牌
DELETE_URL = '/admin/product/baseTrademark/remove/',
}
。。。。。。
//删除某一个已有品牌的数据
export const reqDeleteTrademark = (id: number) =>
request.delete<any, any>(API.DELETE_URL + id)
2、绑定函数
这里使用了一个气泡组件,@confirm绑定的就是回调函数
<el-popconfirm :title="`您确定要删除${row.tmName}?`" width="250px" icon="Delete"
@confirm='removeTradeMark(row.id)'>
<template #reference>
<el-button type="primary" size="small" icon="Delete"></el-button>
</template>
</el-popconfirm>
3、回调函数
//气泡确认框确定按钮的回调
const removeTradeMark = async (id: number) => {
//点击确定按钮删除已有品牌请求
let result = await reqDeleteTrademark(id)
if (result.code == 200) {
//删除成功提示信息
ElMessage({
type: 'success',
message: '删除品牌成功',
})
//再次获取已有的品牌数据
getHasTrademark(
trademarkArr.value.length > 1 ? pageNo.value : pageNo.value - 1,
)
} else {
ElMessage({
type: 'error',
message: '删除品牌失败',
})
}
}