后台管理项目
平台属性页面
分类全局组件
创建分类组件:src/components/Category/index.vue
<template>
<el-card>
<!-- 设置行内 -->
<el-form :inline="true">
<el-form-item label="一级分类">
<el-select >
<!-- option:label 决定用户展示的选项 -->
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="二级分类">
<el-select >
<!-- option:label 决定用户展示的选项 -->
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="三级分类">
<el-select >
<!-- option:label 决定用户展示的选项 -->
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup lang='ts'>
</script>
<style scoped lang="less">
</style>
在入口文件引入,挂载为全局组件:src/main.ts
import Category from 'src/components/Category/index.vue'
// 挂载为全局组件
app.component('Category',Category)
在平台属性组件中使用分类组件:src/views/product/attr/index.vue
<template>
<Category></Category>
<el-card style="margin: 10px 0px">xxx</el-card>
</template>
<script setup lang='ts'>
</script>
<style scoped lang="less">
</style>
获取分类信息
封装分类数据api: src/api/product/category.ts
import request from '@/utils/request';
import { CategoryResponseData } from './type/category';
//枚举地址
enum API {
//获取一级分类的GET请求:不需要携带任何参数
GETC1_URL="/admin/product/getCategory1",
//获取二级分类的数据GET请求:需要携带一级分类的ID
GETC2_URL="/admin/product/getCategory2/",
//获取三级分类的数据GET请求:需要携带二级分类的ID
GETC3_URL="/admin/product/getCategory3/"
}
// 获取一级分类
export const getC1 = () => {
return request.get<any,CategoryResponseData>(API.GETC1_URL)
}
//获取二级分类
export const getC2 = (c1Id:number)=>request.get<any,CategoryResponseData>(API.GETC2_URL+c1Id);
//获取三级分类
export const getC3 = (c2Id:number)=>request.get<any,CategoryResponseData>(API.GETC3_URL+c2Id);
定义api类型 :src/api/product/type/category.ts
//分类的数据的ts类型
export interface Category {
id: number,
name: string,
category1Id?: number,
category2Id?: number
}
//分类接口返回数据数据ts类型
export type CategoryResponseData = Category[];
封装属性数据api:src/api/product/attr.ts
import request from '@/utils/request';
import type { AttrResponseData, Attr } from './type/attr';
//枚举地址
enum API {
//获取平台属性与属性值GET:需要携带三个参数 1|2|3分类的ID
GETATTR_URL="/admin/product/attrInfoList/",
//添加新的属性|更新已有属性
ADDORUPDATEATTR_URL="/admin/product/saveAttrInfo",
//删除已有的属性接口
DELETEATTR_URL="/admin/product/deleteAttr/"
}
//获取平台属性与属性值
export const reqAttrList = (c1Id:number|string,c2Id:number|string,c3Id:number|string)=>{
return request.get<any,AttrResponseData>(API.GETATTR_URL+`${c1Id}/${c2Id}/${c3Id}`)
}
//添加属性与更新属性接口
export const reqAddOrUpdateAttr = (data:Attr)=>request.post<any,any>(API.ADDORUPDATEATTR_URL,data);
//删除已有的属性
export const reqDeleteAttr = (attrId:number)=>request.delete<any,any>(API.DELETEATTR_URL+attrId);
定义api类型:src/api/product/type/attr.ts
//属性值的ts类型
export interface AttrValue {
id?: number,
valueName: string,
attrId?: number,
showInput?:boolean
}
// 属性值列表类型
export type AttrValueList = AttrValue[];
// 属性的类型
export interface Attr {
id?: number,
attrName: string,
categoryId: number|string,
categoryLevel: number|string,
attrValueList: AttrValueList
}
// 属性列表类型
export type AttrResponseData = Attr[];
获取一级分类数据
category仓库 存储分类组件中的数据状态:src/store/category.ts
//分类全局组件小仓库
import { defineStore } from "pinia";
//引入vue3组合式API函数
import { ref } from 'vue';
// 导入数据类型
import { CategoryResponseData } from "@/api/product/type/category";
//引入请求分类的API
import { getC1, getC2, getC3 } from '@/api/product/category';
// 创建切片 分类仓库
const useCategoryStore = defineStore('category',()=>{
// 创建数据状态
//一级分类的数据
const c1Arr = ref<CategoryResponseData>([]);
//二级分类的数据
const c2Arr = ref<CategoryResponseData>([]);
//三级分类的数据
const c3Arr = ref<CategoryResponseData>([]);
// 一级分类ID的字段
const c1Id = ref<string | number>('');
//二级分类的ID字段
const c2Id = ref<string | number>('');
//三级分类的ID字段
const c3Id = ref<string | number>('');
// 方法
const getC1Data = async () => {
const res:CategoryResponseData = await getC1()
// 存储数据
c1Arr.value = res
}
const getC2Data = async () => {
const res:CategoryResponseData = await getC2(c1Id.value as number)
// 存储数据
c2Arr.value = res
}
const getC3Data = async () => {
const res:CategoryResponseData = await getC3(c2Id.value as number)
// 存储数据
c3Arr.value = res
}
// 返回
return {
c1Arr,
c2Arr,
c3Arr,
c1Id,
c2Id,
c3Id,
getC1Data,
getC2Data,
getC3Data
}
})
//对外暴露方法[默认暴露:引入的时候不需要花括号]
export default useCategoryStore;
分类组件中,挂载完成后,发送获取一级分类请求:src/components/Category/index.vue
<template>
<el-card>
<!-- 设置行内 -->
<el-form :inline="true">
<el-form-item label="一级分类">
<el-select >
<!-- option:label 决定用户展示的选项 -->
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="二级分类">
<el-select >
<!-- option:label 决定用户展示的选项 -->
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="三级分类">
<el-select >
<!-- option:label 决定用户展示的选项 -->
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup lang='ts'>
//为什么这里需要使用pinia仓库?(不用也可以)
//因为当前一级、二级、三级分类id,父组件获取属性需要,涉及到组件通信[自定义事件:子->父]、pinia
import useCategoryStore from "@/stores/category";
import { onMounted, onUnmounted } from "vue";
//获取分类的小仓库
const categoryStore = useCategoryStore()
// 生命周期
onMounted(()=>{
//通知pinia调用getC1,获取一级分类的数据
categoryStore.getC1Data()
})
</script>
<style scoped lang="less">
</style>
平台属性页面 :src/views/product/attr/index.vue
<template>
<div>
<Category></Category>
<el-card style="margin: 10px 0px">xxx</el-card>
</div>
</template>
<script setup lang='ts'>
</script>
<style scoped lang="less">
</style>
获取二三级分类数据
src/components/Category/index.vue
// 可以在 pinia 小仓库中使用watch监听 一级分类id 时发送请求(获取二级分类数据)
// 或者在el-select 组件中发生 change 事件时 发送请求(获取二级分类数据)
// 可以在 pinia 小仓库中使用watch监听 二级分类id 时发送请求(获取三级分类数据)
// 或者在el-select 组件中发生 change 事件时 发送请求(获取三级分类数据)
<template>
<el-card>
<!-- 设置行内 -->
<el-form :inline="true">
<el-form-item label="一级分类">
<el-select v-model="categoryStore.c1Id" @change="handleChangeC1" >
<!-- option:label 决定用户展示的选项 -->
<el-option :key="c1.id" v-for="(c1,index) in categoryStore.c1Arr" :label="c1.name" :value="c1.id"/>
</el-select>
</el-form-item>
<el-form-item label="二级分类">
<el-select v-model="categoryStore.c2Id" @change="handleChangeC2">
<!-- option:label 决定用户展示的选项 -->
<el-option :key="c2.id" v-for="(c2,index) in categoryStore.c2Arr" :label="c2.name" :value="c2.id"/>
</el-select>
</el-form-item>
<el-form-item label="三级分类">
<el-select v-model="categoryStore.c3Id">
<!-- option:label 决定用户展示的选项 -->
<el-option :key="c3.id" v-for="(c3,index) in categoryStore.c3Arr" :label="c3.name" :value="c3.id"/>
</el-select>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup lang='ts'>
//为什么这里需要使用pinia仓库?(不用也可以)
//因为当前一级、二级、三级分类id,父组件获取属性需要,涉及到组件通信[自定义事件:子->父]、pinia
import useCategoryStore from "@/stores/category";
import { onMounted, onUnmounted } from "vue";
//获取分类的小仓库
const categoryStore = useCategoryStore()
// 生命周期
onMounted(()=>{
//通知pinia调用getC1,获取一级分类的数据
categoryStore.getC1Data()
})
// handle change事件回调
const handleChangeC1 = () => {
//获取二级分类的数据
categoryStore.getC2Data();
}
const handleChangeC2 = () => {
//获取二级分类的数据
categoryStore.getC3Data();
}
</script>
<style scoped lang="less">
</style>
清除上次数据
// 一级分类在el-select 组件中发生 change 事件时 清除二级分类上次的c1Id
// 一级分类在el-select 组件中发生 change 事件时 清除三级分类上次的c2Id和数组
// 二级分类在el-select 组件中发生 change 事件时 清除三级分类上次的c3Id
// 销毁组件时(src/component/Category/index.vue),清除分类数组和id
// 生命周期
onMounted(()=>{
//通知pinia调用getC1,获取一级分类的数据
categoryStore.getC1Data()
})
//组件销毁钩子
onUnmounted(() => {
//清空仓库全部的数据---只能是选择是API清空仓库数组,组合式API写法不行
// categoryStore.$reset();
categoryStore.c1Id = "";
categoryStore.c2Id = "";
categoryStore.c3Id = "";
categoryStore.c1Arr = [];
categoryStore.c2Arr = [];
categoryStore.c3Arr = [];
});
// handle change事件回调
const handleChangeC1 = () => {
//清除二级分类上一次收集的ID
categoryStore.c2Id = "";
//清除三级分类的ID与数组数据
categoryStore.c3Id = "";
categoryStore.c3Arr = [];
//获取二级分类的数据
categoryStore.getC2Data();
}
const handleChangeC2 = () => {
//清除三级分类的ID
categoryStore.c3Id = "";
//获取二级分类的数据
categoryStore.getC3Data();
}
平台属性表格数据渲染
静态页面
平台属性静态页面:src/views/product/attr/index.vue
<template>
<div>
<Category></Category>
<el-card style="margin: 10px 0px">
<el-button type="primary" icon="Plus" >添加属性</el-button>
<el-table border style="margin: 10px 0px" :data="attrList">
<el-table-column type="index" align="center" label="序号" width="80px"></el-table-column>
<el-table-column label="属性名称" width="120px" prop="attrName"></el-table-column>
<el-table-column label="属性值">
<template #="{ row, $index }">
<el-tag
style="margin: 5px"
:type="item.id % 2 == 0 ? 'primary' : 'warning'"
v-for="(item, index) in row.attrValueList"
:key="item.id">
{{ item.valueName }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120px">
<template #="{ row, $index }">
<el-button
type="warning"
size="small"
:icon="Edit"
></el-button>
<el-popconfirm
:title="`你确定要删除${row.attrName}`"
width="250px"
>
<template #reference>
<el-button type="danger" size="small" :icon="Delete"></el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup lang='ts'>
import type { AttrResponseData } from "@/api/product/type/attr";
import useCategoryStore from "@/stores/category";
import { Plus, Edit, Delete } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { watch, ref, reactive, nextTick } from "vue";
// 定义数据状态
const attrList = ref<AttrResponseData>([])
</script>
<style scoped lang="less">
</style>
发送请求
发送请求(获取平台属性):src/views/product/attr/index.vue
// 使用watch监听 仓库中三级分类id变化 且 仓库中三级分类id不为空 时发送请求
// 定义获取平台属性函数
<script setup lang='ts'>
import { reqAttrList } from "@/api/product/attr";
import type { AttrResponseData } from "@/api/product/type/attr";
import useCategoryStore from "@/stores/category";
import { Plus, Edit, Delete } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { watch, ref, reactive, nextTick } from "vue";
// 属性列表
const attrList = ref<AttrResponseData>([])
// 定义分类小仓库(分类组件的)
const categoryStore = useCategoryStore();
// 定义input组件实例
const inputRef = ref<any>();
// 方法
//获取全部已有的属性与属性值方法
const getAttrList = async () => {
//一级、二级、三级分类的ID
const { c1Id, c2Id, c3Id } = categoryStore;
const result: AttrResponseData = await reqAttrList(c1Id, c2Id, c3Id)
attrList.value = result;
}
// 监听
watch(()=>categoryStore.c3Id,()=>{
//每一次监听到三级分类变化:把已有属性与属性值清空
attrList.value = [];
// 使用watch监听 仓库中三级分类id变化 且 仓库中三级分类id不为空 时发送请求
categoryStore.c3Id && getAttrList();
})
</script>
渲染数据
渲染数据:src/views/product/attr/index.vue
<template>
<div>
<Category></Category>
<el-card style="margin: 10px 0px">
<el-button type="primary" icon="Plus" >添加属性</el-button>
<el-table border style="margin: 10px 0px" :data="attrList">
<el-table-column type="index" align="center" label="序号" width="80px"></el-table-column>
<el-table-column label="属性名称" width="120px" prop="attrName"></el-table-column>
<el-table-column label="属性值">
<template #="{ row, $index }">
<el-tag
style="margin: 5px"
v-for="(item, index) in row.attrValueList"
:key="item.id"
type="warning"
>{{ item.valueName }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120px">
<template #="{ row, $index }">
<el-button
type="warning"
size="small"
:icon="Edit"
></el-button>
<el-popconfirm
:title="`你确定要删除${row.attrName}`"
width="250px"
>
<template #reference>
<el-button type="danger" size="small" :icon="Delete"></el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
// 根据ID %2 动态的给 tag标签 设置type
< :type="item.id%2? 'primary':'danger'">
细节优化
添加属性按钮禁用:src/views/product/attr/index.vue
<el-button type="primary" icon="Plus" :disabled="categoryStore.c3Id ? false : true">添加属性</el-button>
表格无数据时:src/views/product/attr/index.vue
// 使用empty组件
<el-table border style="margin: 10px 0px" :data="attrList" v-show="attrList.length"></el-table>
<!-- 空数据时的组件 -->
<el-empty
description="暂无数据"
v-show="!attrList.length"
:image="src"
:image-size="400"
/>
过渡动画
过渡动画:src/views/product/attr/index.vue
<transition name="attr"> <el-table></el-table> </transition>
<style scope>
.attr-enter-active,.attr-leave-active{
transition: all 1.5s;
}
.attr-leave-to,.attr-enter{
opacity: 0;
}
</style>
添加属性(场景切换)
静态页面
添加属性:src/views/product/attr/index.vue
<template>
<div>
<Category></Category>
<el-card style="margin: 10px 0px">
<div v-show="scene === 0">
<!-- 按钮 -->
<el-button type="primary" icon="Plus" :disabled="categoryStore.c3Id ? false : true" @click="addAttr" >添加属性</el-button>
<!-- 属性表格 -->
<transition name="attr">
<el-table border style="margin: 10px 0px" :data="attrList" v-show="attrList.length">
<el-table-column type="index" align="center" label="序号" width="80px"></el-table-column>
<el-table-column label="属性名称" width="120px" prop="attrName"></el-table-column>
<el-table-column label="属性值">
<template #="{ row, $index }">
<el-tag
style="margin: 5px"
v-for="(item, index) in row.attrValueList"
:key="item.id"
type="warning"
>{{ item.valueName }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120px">
<template #="{ row, $index }">
<el-button
type="warning"
size="small"
:icon="Edit"
></el-button>
<el-popconfirm
:title="`你确定要删除${row.attrName}`"
width="250px"
>
<template #reference>
<el-button type="danger" size="small" :icon="Delete"></el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</transition>
<!-- 空数据时的组件 -->
<el-empty description="暂无数据"
v-show="!attrList.length"
:image="src"
:image-size="400"
/>
</div>
<!-- 添加属性 -->
<div v-show="scene === 1">
<el-form :inline="true">
<el-form-item label="属性名称">
<el-input
type="text"
placeholder="请你输入属性名称"
></el-input>
</el-form-item>
</el-form>
<el-button type="primary" :icon="Plus">添加属性值</el-button>
<el-button @click="scene = 0">取消</el-button>
<el-table border style="margin: 10px 0px" >
<el-table-column
type="index"
label="序号"
align="center"
width="80px"
></el-table-column>
<el-table-column label="属性值">
<!-- row:即为每一个属性值对象 -->
<template #="{ row, $index }">
<el-input
ref="inputRef"
size="small"
></el-input>
</template>
</el-table-column>
<el-table-column label="操作">
<template #="{ row, $index }">
<el-button
type="danger"
:icon="Delete"
size="small"
></el-button>
</template>
</el-table-column>
</el-table>
<el-button
type="primary"
>保存</el-button>
<el-button @click="scene = 0">取消</el-button>
</div>
</el-card>
</div>
</template>
<script>
const addAttr = () =>{
// 设置场景为添加属性
scene.value = 1;
}
</script>
细节优化
添加属性时 禁用分类切换:src/views/product/attr/index.vue父亲
若禁用状态仍然可以选择下拉,需要升级element-plus版本
<Category :scene="scene"></Category>
添加属性时 禁用分类切换:src/components/Category/index.vue儿子
<template>
<el-card>
<!-- 设置行内 -->
<el-form :inline="true">
<el-form-item label="一级分类">
<el-select v-model="categoryStore.c1Id" @change="handleChangeC1" :disabled="scene==0?false:true" style="width: 180px">
<!-- option:label 决定用户展示的选项 -->
<el-option :key="c1.id" v-for="(c1,index) in categoryStore.c1Arr" :label="c1.name" :value="c1.id"/>
</el-select>
</el-form-item>
<el-form-item label="二级分类">
<el-select v-model="categoryStore.c2Id" @change="handleChangeC2" :disabled="scene==0?false:true" style="width: 180px">
<!-- option:label 决定用户展示的选项 -->
<el-option :key="c2.id" v-for="(c2,index) in categoryStore.c2Arr" :label="c2.name" :value="c2.id"/>
</el-select>
</el-form-item>
<el-form-item label="三级分类">
<el-select v-model="categoryStore.c3Id" :disabled="scene==0?false:true" style="width: 180px">
<!-- option:label 决定用户展示的选项 -->
<el-option :key="c3.id" v-for="(c3,index) in categoryStore.c3Arr" :label="c3.name" :value="c3.id"/>
</el-select>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup lang='ts'>
//为什么这里需要使用pinia仓库?(不用也可以)
//因为当前一级、二级、三级分类id,父组件获取属性需要,涉及到组件通信[自定义事件:子->父]、pinia
import useCategoryStore from "@/stores/category";
import { onMounted, onUnmounted } from "vue";
// 接收props
defineProps(['scene'])
</<script>
封装接口
定义添加属性和更新属性接口
src/api/product/attr.ts
//添加属性与更新属性接口
export const reqAddOrUpdateAttr = (data:Attr)=>request.post<any,any>(API.ADDORUPDATEATTR_URL,data);
//删除已有的属性
export const reqDeleteAttr = (attrId:number)=>request.delete<any,any>(API.DELETEATTR_URL+attrId);
src/api/product/type/attr.ts
//属性值的ts类型
export interface AttrValue {
id?: number,
valueName: string,
attrId?: number,
showInput?:boolean
}
// 属性值列表类型
export type AttrValueList = AttrValue[];
// 属性的类型
export interface Attr {
id?: number,
attrName: string,
categoryId: number|string,
categoryLevel: number|string,
attrValueList: AttrValueList
}
// 属性列表类型
export type AttrResponseData = Attr[];
收集表单数据
收集表单数据:src/views/product/attr/index.vue
# 添加属性按钮的禁用时机
# 收集属性名称
定义数据状态 attrParams 收集添加属性的属性和属性值
双向绑定
# 收集input输入框的属性值
定义数据状态 attrParams 收集添加属性的属性和属性值
双向绑定
# 收集三级分类的id
在添加属性按钮处触发
attrParams.categoryId = categoryStore.c3Id;
# 添加属性值绑定事件@click="addAttrValue":添加属性值 addAttrValue 往数组中push对象
# 添加属性值按钮设置禁用时机
# table渲染双向绑定数据 :date=attrParams.attrValueList
<!-- 添加属性 -->
<div v-show="scene === 1">
<el-form :inline="true">
<el-form-item label="属性名称">
<el-input
type="text"
placeholder="请你输入属性名称"
v-model="attrParams.attrName"
></el-input>
</el-form-item>
</el-form>
<el-button type="primary" :icon="Plus" @click="addAttrValue" :disabled="attrParams.attrName ? false : true">添加属性值</el-button>
<el-button @click="scene = 0">取消</el-button>
<el-table border style="margin: 10px 0px" :data="attrParams.attrValueList">
<el-table-column
type="index"
label="序号"
align="center"
width="80px"
></el-table-column>
<el-table-column label="属性值">
<!-- row:即为每一个属性值对象 -->
<template #="{ row, $index }">
<el-input
ref="inputRef"
size="small"
v-model="row.valueName"
></el-input>
</template>
</el-table-column>
<el-table-column label="操作">
<template #="{ row, $index }">
<el-button
type="danger"
:icon="Delete"
size="small"
></el-button>
</template>
</el-table-column>
</el-table>
<el-button
type="primary"
>保存</el-button>
<el-button @click="scene = 0">取消</el-button>
</div>
<script>
// 添加属性和属性值,请求体
const attrParams = reactive<Attr>({
categoryId: "", //新增的属性归属于哪一个三级分类
categoryLevel: "3", //代表几级分类,默认3级
attrName: "", //属性的名字
//属性值数组
attrValueList: [],
})
// 添加属性
const addAttr = () =>{
//收集三级分类的ID[也可以点击保存的时候收集]
attrParams.categoryId = categoryStore.c3Id;
// 设置场景为添加属性
scene.value = 1;
}
// 添加属性值
const addAttrValue =()=>{
//向属性值数组添加属性对象
//在每一个属性值对象身上放置编辑与查看模式标记
attrParams.attrValueList.push({
valueName: "",
// showInput: true,
})
//当响应式数据发生变化后,立即调用nextTick方法,获取更新后的DOM|组件实例
// nextTick(() => {
// inputRef.value.focus();
// });
}
</script>
发送请求
src/views/product/attr/index.vue
# 点击提交按钮发送请求
# 消息提示
# 回到场景0
# 重新获取属性数据
# 设置保存按钮的禁用时机
<template>
<el-button
type="primary"
@click="save"
:disabled="attrParams.attrName && attrParams.attrValueList.length ? false : true"
>保存</el-button>
<el-button @click="scene = 0">取消</el-button>
</template>
<script>
// 提交
const save = async() =>{
try {
await reqAddOrUpdateAttr(attrParams)
//消息提示
ElMessage({
type: "success",
message: attrParams.id ? "更新成功" : "添加成功",
});
//切换场景为0
scene.value = 0;
//添加或者更新成功以后再次获取全部已有的属性
getAttrList();
} catch (error) {
//消息提示
ElMessage({
type: "error",
message: attrParams.id ? "更新失败" : "添加失败",
});
}
}
</script>
<style>
</style>
清空数据
src/views/product/attr/index.vue
# 在添加属性按钮处,调用清空数据方法
<script>
// 清空数据方法
const reset =()=>{
// 使用Object.assign()方法进行合并,相同的字段会合并,不同的字段会合并
}
</script>
<template>
</template>
<script>
// 添加属性
const addAttr = () =>{
//清空数据
reset();
//收集三级分类的ID[也可以点击保存的时候收集]
attrParams.categoryId = categoryStore.c3Id;
// 设置场景为添加属性
scene.value = 1;
}
// 用户取消、保存成功以后要清空数据的方法
const reset = () => {
//ES6的语法:Object.assign
Object.assign(attrParams, {
categoryId: "", //新增的属性归属于哪一个三级分类
categoryLevel: "3", //代表几级分类
attrName: "", //属性的名字
//属性值数组
attrValueList: [],
// 更新时
id: "",
});
};
</script>
编辑与查看切换
src/views/product/attr/index.vue
谁显示谁隐藏实现方式:第一种方式使用v-if
或者v-show
,第二种使用行内样式display:none,block
# 增加div静态页面
# 定义数据状态:控制input和div标签的显示和隐藏
定义在attrValueList 数组中,在添加属性值的回调函数中,往数组中添加元素对象时,对象中添加 showInput属性。
# 给input绑定blur事件
# 给div绑定点击事件
# 解决全部同时显示与隐藏
在 attrValueList 数组的 元素对象中 添加 input标记
# 解决div空白的时候,无法切换
属性值不能为空,为空时 在数组中删除,并停止
# 属性值不能重复
在blur时,触发
因为是双向绑定的,在添加时,自己已经在数组中,把自己去除之后(使用 trim()方法去除空格),用剩余的数组再去使用find()方法,判断find()的返回值是否存在,存在即有重复,把重复的数据从数组中删除。
<template>
<el-table-column label="属性值">
<!-- row:即为每一个属性值对象 -->
<template #="{ row, $index }">
<!-- input标签 -->
<el-input
ref="inputRef"
size="small"
v-if="row.showInput"
v-model="row.valueName"
@blur="toLook(row, $index)"
></el-input>
<!-- div标签 -->
<div v-else @click="toEdit(row)">{{ row.valueName }}</div>
</template>
</el-table-column>
</template>
<script>
// input失去焦点
const toLook = (row:AttrValue,index:number) => {
//属性值不能为空
if (row.valueName.trim() == "") {
ElMessage({
type: "error",
message: "属性值不能为空",
});
//从数组中删除当前元素
attrParams.attrValueList.splice(index, 1);
//属性值为空,后面的语句不在执行,div不出现
return;
}
//判断属性值不能重复
const repeat = attrParams.attrValueList.find((item) => {
//把除自己以外的元素都返回
if (row != item) {
return row.valueName.trim() === item.valueName.trim();
}
})
if (repeat) {
ElMessage({
type: "error",
message: "属性值不能重复",
});
//从数组中删除当前元素
attrParams.attrValueList.splice(index, 1);
return;
}
//显示div
row.showInput = false
}
//div点击事件
const toEdit = (row: AttrValue) => {
//点击div 变为 input
row.showInput = true;
// input输入框获取焦点
nextTick(() => {
inputRef.value.focus();
});
};
</script>
编辑与查看切换时聚焦
查看element-plus文件中的input组件的聚焦方法
# 获取input 对象
# 在添加属性按钮时,添加input focus方法
在响应式数据发生变化后,立即调用nextTick方法,获取dom元素
# 在点击div事件时,添加input focus方法
在响应式数据发生变化后,立即调用nextTick方法,获取dom元素
<template>
</template>
<script>
// 添加属性值
const addAttrValue =()=>{
//向属性值数组添加属性对象
//在每一个属性值对象身上放置编辑与查看模式标记
attrParams.attrValueList.push({
valueName: "",
showInput: true,
})
//当响应式数据发生变化后,立即调用nextTick方法,获取更新后的DOM|组件实例
nextTick(() => {
inputRef.value.focus();
});
}
</script>
保存按钮禁用
# 在 attrName存在 且 attrValueList数组的 长度>0 时 不禁用
<template>
<el-button
type="primary"
@click="save"
:disabled="attrParams.attrName && attrParams.attrValueList.length ? false : true"
>保存</el-button>
<el-button @click="scene = 0">取消</el-button>
</template>
<script>
</script>
删除按钮
# 删除按钮
在删除按钮设置点击事件,把当前属性值从 数组中 删除(不用发送请求)
<template>
<el-table-column label="操作">
<template #="{ row, $index }">
<el-button
type="danger"
:icon="Delete"
size="small"
@click="attrParams.attrValueList.splice($index, 1)"
></el-button>
</template>
</el-table-column>
</template>
<script>
</script>
更新属性值
收集数据
# 把当前行对象中的数据 赋值给 attrparams
Object.assign(attrParams,row)(存在浅拷贝的问题)
# 在 reset清空数据 时,把ID也清空
# 取消按钮时,attrParams的数据会同步到attrValueList数组中
原因:引用数据类型浅拷贝的问题
解决:使用json深拷贝
Object.assign(attrParams,JSON.parse(JSON.stringify(row)))
<template>
<el-table-column label="操作" width="120px">
<template #="{ row, $index }">
<el-button
type="warning"
size="small"
:icon="Edit"
@click="updateAttr(row)"
>
</el-button>
<el-popconfirm
:title="`你确定要删除${row.attrName}`"
width="250px"
>
<template #reference>
<el-button type="danger" size="small" :icon="Delete"></el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</template>
<script>
// 更新已有属性
const updateAttr = (row:Attr) =>{
// 获取当前点击行对象,使用lodash插件中的函数,深拷贝
Object.assign(attrParams, cloneDeep(row));
// 切换场景为1
scene.value = 1
}
</script>
使用lodash解决浅拷贝问题
# 使用lodash(项目中已包含)防抖节流中的递归函数,解决对象的浅拷贝问题,把浅拷贝转为深拷贝
# 在入口文件中引入:main.ts
import cloneDeep from 'lodash/cloneDeep'
# 取消按钮时,attrParams的数据会同步到attrValueList数组中
解决:使用递归函数
Object.assign(attrParams,cloneDeep(row))
删除属性
封装接口
项目代码
src/api/product/attr.ts
//删除已有的属性
export const reqDeleteAttr = (attrId:number)=>request.delete<any,any>(API.DELETEATTR_URL+attrId);
绑定事件发送请求
在组件中绑定单机事件发送删除属性的请求:src/views/product/attr/index.vue
# 删除成功后,重新获取属性列表
<template>
<el-popconfirm
:title="`你确定要删除${row.attrName}`"
width="250px"
@confirm="deleteAttr(row.id)"
>
<template #reference>
<el-button type="danger" size="small" :icon="Delete"></el-button>
</template>
</el-popconfirm>
</template>
<script>
//删除已有的属性
const deleteAttr = async (id: number) => {
try {
//删除已有属性成功
await reqDeleteAttr(id);
//消息提示
ElMessage({
type:'success',
message:'删除成功'
});
//再次获取最新剩下全部属性
getAttrList();
} catch (error) {
console.log(error)
}
}
</script>