效果图:
注意:
同时封装了el-table列表和表头
- 使用了jsx jsx的引入使用
- 使用了lodash
- 使用了moment
文章目录
一、封装内容
1.封装el-table组件
1.通过:formatter="item.formatter"渲染列表单元格,使用jsx写dom
2.表格结构在tableData.value.columnsData下,表格数据在tableData.value.data
3.表格封装了多选、单选、点击表头、点击排序(多列)、点击某列、点击操作类事件、插槽等
4.title:名称 field:key type:单选复选框 formType:筛选方式 sortable:排序 needGetList:需要远程搜索 needJavaList:需要获取后端下拉数据
width minWidth maxWidth showOverflowTooltip formatter:格式化内容 fixed: 'right' 固定列
2.封装表头筛选、排序和下拉数据接口获取
1.通过遍历表格结构,拿到每列类型formType,渲染不同筛选
2.目前支持输入框、单选框、下拉多选框、远程下拉多选框、时间范围筛选和多列的排序
3.筛选类型formType:input 输入框 select 单选 selectMultiple 多选 daterange 年月日范围 formType 年月日时分秒范围
4.sortable: 'custom' 排序 needGetList: true 需要远程获取下拉筛选数据
3.某列举例
{
title: '姓名', field: 'name', formType: 'selectMultiple', selectList: nameList, needGetList: true, sortable: 'custom', showOverflowTooltip: true, minWidth: 100,
formatter: (row) => {
const val = filters(row.name, nameList.value)
return [<span>{val}</span>]
}
},
姓名列,key是name,该列多选筛选,下拉数据取nameList数组,下拉数据需要通过接口获取,可排序,当内容过长被隐藏时显示 tooltip,最小宽度100px,通过value回显渲染出对用name
4.needGetListt: true需要远程搜索
1.注意:获取下拉数据的逻辑,去table.vue组件内自己写,在 remoteMethod 里写逻辑
5.needJavaList: true 需要后端返回下拉数据
1.搜索needJavaList即可
6.单列排序和多列排序
查看sortChange部分
二、代码
1.封装整个表格和表头组件table.vue
table.vue
<template>
<div class="page-view flex-column" @click="handleClickOutside">
<div class="flex-between mb">
<el-space size="large">
<el-button type="primary" @click="resetFilters">重置</el-button>
</el-space>
<slot name="btnContent"></slot>
</div>
<div style="flex: 1;overflow-y: auto;">
<el-table v-loading="tableData.tableLoading" ref="myTableRef" class="jg-table" :data="tableData.data"
style="width: 100%;height: 100%;" :border="Boolean(tableData.mergeKey)" :span-method="objectSpanMethod"
@selection-change="handleSelectionChange" @row-click="rowClick" @sort-change="sortChange"
@header-click="handleHeaderClick" :header-cell-class-name="handleHeadAddClass">
<template v-for="(item, index) in tableData.columnsData " :key="index">
<!-- 多选 -->
<el-table-column v-if="item.type === 'selection'" :type="item.type" :width="item.width" fixed />
<!-- 单选 -->
<el-table-column v-else-if="item.type === 'radio'" :width="item.width" fixed class-name="no-radio-label">
<template #default="{ row, $index }">
<el-radio @change="changeRadio" v-model="radioIndex" :label="$index"></el-radio>
</template>
</el-table-column>
<!-- 渲染formatter -->
<!-- <el-table-column v-else :property="item.field" :label="item.title" :width="item.width"
:min-width="item.minWidth" :max-idth="item.maxWidth" :showOverflowTooltip="item.showOverflowTooltip"
:sortable="item.sortable" :formatter="item.formatter" /> -->
<el-table-column v-else :property="item.field" :label="item.title" :width="item.width"
:min-width="item.minWidth" :max-idth="item.maxWidth" :showOverflowTooltip="item.showOverflowTooltip"
:sortable="item.sortable" :formatter="item.formatter" :fixed="item.fixed">
<template #header>
<div class="table-header-div">
<span class="title-span">{{ item.title }}</span>
<!-- 控制筛选按钮显隐 -->
<template v-if="item.field !== 'actionBtn' && item.formType">
<el-popover :visible="visible && item.field === showKey" :virtual-ref="refName" placement="bottom"
:width="'fit-content'">
<template #reference>
<!-- 两个筛选icon -->
<svg v-if="searchData[item.field] || searchData[item.field] === 0" :id="item.field"
@click.stop="toggleNameFilter(item.field, item)" t="1721807387166" class="icon icon-span"
viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5539" width="20"
height="20">
<path
d="M609.507556 463.246222H414.492444L170.666667 170.666667h682.666666l-243.825777 292.579555z m0 48.753778v212.878222L414.492444 853.333333V512h195.015112z"
fill="#11716f" p-id="5540"></path>
</svg>
<svg v-else :id="item.field" @click.stop="toggleNameFilter(item.field, item)" t="1721807387166"
class="icon icon-span" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="5539" width="20" height="20">
<path
d="M609.507556 463.246222H414.492444L170.666667 170.666667h682.666666l-243.825777 292.579555z m0 48.753778v212.878222L414.492444 853.333333V512h195.015112z"
fill="#c1c2c6" p-id="5540"></path>
</svg>
<!-- <el-icon class="icon-span" :id="item.field" @click.stop="toggleNameFilter(item.field, item)"
style="cursor: pointer; margin-left: 10px;"
:style="{ color: searchData[item.field] ? '#11716f' : '#85888e' }">
<Filter />
</el-icon> -->
</template>
<div>
<!-- 输入 -->
<el-input v-if="item.formType === 'input'" v-model="searchData[item.field]" :autofocus="true"
placeholder="请输入" @input="applyNameFilter" clearable style="margin-top: 10px; width: 150px;" />
<template v-if="!item.needGetList">
<!-- 下拉 -->
<el-select v-if="item.formType === 'select' || item.formType === 'radio'" filterable
:reserve-keyword="false" v-model="searchData[item.field]" placeholder="请选择"
@change="applyTagFilter" clearable style="margin-top: 10px; width: 150px;">
<el-option v-for="item in item.selectList" :key="item" :value="item.value"
:label="item.name" />
</el-select>
<!-- 多选下拉 -->
<el-select v-if="item.formType === 'selectMultiple'" multiple filterable
:reserve-keyword="false" v-model="searchData[item.field]" placeholder="请选择" clearable
style="margin-top: 10px; width: 150px;">
<el-option v-for="item in item.selectList" :key="item" :value="item.value"
:label="item.name" />
</el-select>
</template>
<!-- 多选下拉 需要搜索获取后端接口数据 -->
<el-select
v-if="(item.formType === 'select' || item.formType === 'selectMultiple') && item.needGetList"
multiple filterable remote :reserve-keyword="false" :loading="listLoading" remote-show-suffix
:remote-method="remoteMethod" v-model="searchData[item.field]" placeholder="请搜索后选择" clearable
style="margin-top: 10px; width: 150px;">
<el-option v-for="item in javaList" :key="item.value" :value="item.value" :label="item.name" />
</el-select>
<!-- 日期 && 日期时间 -->
<el-date-picker v-if="item.formType === 'daterange' || item.formType === 'datetimerange'"
style="width: 360px;" v-model="searchData[item.field]" :type="item.formType" range-separator="~"
start-placeholder="开始时间" end-placeholder="结束时间" :format="item.dateType"
@change="changeData($event, item.field, item.dateType)" />
<div class="mt" style="text-align: right;">
<el-button type="info" link @click="cancelFilter">重置</el-button>
<el-button type="primary" link @click="searchFilter">筛选</el-button>
</div>
</div>
</el-popover>
</template>
</div>
</template>
</el-table-column>
</template>
</el-table>
</div>
<pre>筛选和排序参数:{{ { ...searchData, ...sortJavaField } }}</pre>
<!-- <pre>表格默认多列排序集合:{{ sortField }}</pre> -->
<!-- <pre>表格参数:{{ tableData }}</pre> -->
</div>
</template>
<script setup lang="jsx">
import { ref, defineOptions, defineEmits, defineProps, watch, } from 'vue' // 按需引入ref函数
import _ from 'lodash'
import moment from 'moment'
// import { getUserList } from '@/service/index.js'
defineOptions({
name: "JgTable"
})
const emit = defineEmits(['setSearchData', 'updateHandleSelectionChange', 'updateChangeRadio', 'updateSortChange'])
const props = defineProps({
description: {
type: String,
default: () => {
return ' '
}
},
tableDataObj: {
type: Object,
default: () => {
return {}
}
},
})
let tableFlag = ref(false)//确保拿到父传子的表格数据再渲染
let tableData = ref({})
let myTableRef = ref(null) // 表格实例
let radioIndex = ref(null)
let searchData = ref({}) // 搜索参数 每个表头字段
// let sortObj = ref({}) // 单列排序
const sortField = ref({}) // 表格默认多列排序集合
const sortJavaField = ref({
sortVOList: []
}) // 后端要的排序集合
// 时间格式化
const changeData = (value, key, dateType) => {
// console.log(value, key, dateType);
if (!_.isEmpty(value)) {
searchData.value[key][0] = value[0] ? moment(value[0]).format(dateType) : ''
searchData.value[key][1] = value[1] ? moment(value[1]).format(dateType) : ''
} else {
searchData.value[key] = []
}
}
const showKey = ref(undefined) // 当前展示哪个筛选窗
const visible = ref(false) // 手动控制筛选窗显隐
const refName = ref(null) // 动态绑定在哪个表头图标下
// 全局重置
const resetFilters = () => {
// searchData.value.nameFilter = {}
for (const key in searchData.value) {
if (Object.hasOwnProperty.call(searchData.value, key)) {
searchData.value[key] = undefined
}
}
visible.value = false
sortField.value = {}
sortJavaField.value.sortVOList = []
// 因为无法通过element ui的api来清除排序样式,所以只能通过原生js来清除
myTableRef.value.$el.querySelectorAll(".is-sortable").forEach((item) => {
// 移除table表头中的排序样式descending和ascending
item.classList.remove("descending");
item.classList.remove("ascending");
}) // 清除多个排序
// myTableRef.value.clearSort() // 清除单个排序
applyNameFilter()
applyTagFilter()
getData()
}
// 某些下拉数据 例如下拉大数据有500条 直接赋值会导致表格卡顿 通过点击表头再去接口获取数据赋值就不会影响表格的渲染
// (列里面数据回显使用后端给的中文,或者将javaList赋值给item的selectList)
let javaList = ref([])
const getJavaList = async (item) => {
// 模拟几口
await setTimeout(() => {
item.selectList = Array.from({ length: 500 }).map((ele, index) => {
return {
name: index,
value: index,
}
})
}, 500);
}
const listLoading = ref(false)
// 远程搜索接口获取下拉数据
const remoteMethod = (query) => {
if (query) {
listLoading.value = true
// getUserList({
// pageNum: 1,
// pageSize: 1000,
// chineseName: query
// }).then((res = []) => {
// javaList.value = (res || []).map(ele => {
// return {
// name: ele.chineseName,
// value: ele.id,
// }
// })
// }).finally(() => {
// listLoading.value = false
// })
// 模拟接口
setTimeout(() => {
javaList.value = Array.from({ length: 3 }).map((ele, index) => {
return {
name: '模拟' + index,
value: index,
}
})
listLoading.value = false
}, 500);
} else {
// javaList.value = []
}
}
// 触发筛选
const toggleNameFilter = async (key, item) => {
// console.log(key, item);
if (item.needJavaList) {
await getJavaList(item) // 调用后端接口 获取下拉数据
}
if (visible.value && showKey.value && showKey.value !== key) {
visible.value = false
getData()
}
refName.value = document.getElementById(key)
showKey.value = key
visible.value = !visible.value
}
// 点击其他元素
const handleClickOutside = () => {
// 且有筛选打开即去查询
if (visible.value) {
visible.value = false
getData()
}
};
// 重置
const cancelFilter = () => {
searchData.value[showKey.value] = undefined
visible.value = false;
getData()
}
// 筛选
const searchFilter = () => {
visible.value = false;
getData()
}
// 单独过滤
const applyNameFilter = () => {
// Filtering logic can be customized if needed
}
const applyTagFilter = () => {
// Filtering logic can be customized if needed
}
const getData = () => {
console.log('筛选参数', { ...searchData.value, ...sortJavaField.value });
emit('setSearchData', { ...searchData.value, ...sortJavaField.value })
}
watch([() => props.tableDataObj, () => props.rulesData], ([tableDataObj, rulesData]) => {
// console.log('监听');
tableFlag.value = false
tableData.value = _.cloneDeep(tableDataObj) // 使用lodash的深拷贝方法
tableFlag.value = true
// 判断空 也就是初次渲染时候需要排列出查询参数 后续数据变动不在初始化不在设置为undefined 否则会导致每次查询清空掉上次筛选条件
if (_.isEmpty(searchData.value)) {
tableDataObj?.columnsData.forEach(ele => {
if (ele.field) {
searchData.value[ele.field] = undefined
}
})
}
}, { deep: true, immediate: true })
// 合并单元格
const objectSpanMethod = ({
row,
column,
rowIndex,
}) => {
// console.log(row, column, rowIndex, columnIndex,);
const fields = tableData.value.mergeKey || []
const cellValue = row[column.property]
if (cellValue && fields.includes(column.property)) {
const prevRow = tableData.value.data[rowIndex - 1]
let nextRow = tableData.value.data[rowIndex + 1]
if (prevRow && prevRow[column.property] === cellValue) {
return { rowspan: 0, colspan: 0 }
} else {
let countRowspan = 1
while (nextRow && nextRow[column.property] === cellValue) {
nextRow = tableData.value.data[++countRowspan + rowIndex]
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 }
}
}
}
}
// 多选
const handleSelectionChange = (val) => {
// console.log(val);
// 子传父
emit('updateHandleSelectionChange', val);
}
// 单选
const changeRadio = (val) => {
// console.log('单选', val, radioIndex.value.aa);
let item = tableData.value.data?.[val] || {}
emit('updateChangeRadio', item);
}
// 表格点击事件
const rowClick = (val) => {
// console.log(val);
}
// 单列排序
// const sortChange = (val) => {
// console.log(val, val.prop, val.order);
// sortObj.value = {}
// if (!_.isEmpty(val)) {
// // sortObj.value[val.prop + 'Sort'] = val.order
// sortObj.value['sortName'] = val.order
// sortObj.value['sortType'] = val.prop
// }
// getData() // 直接统一在筛选里加入排序字段
// // emit('updateSortChange', sortObj.value) // 不再单独传递排序字段
// }
// 多列排序
const sortChange = ({ order, prop }) => {
visible.value = false
//触发的排序和缓存的排序相同时,取消该字段的排序
if (!order || sortField.value[prop] === order) {
sortField.value[prop] = null;
} else {
sortField.value[prop] = order;
}
sortJavaField.value.sortVOList = []
for (const key in sortField.value) {
if (Object.hasOwnProperty.call(sortField.value, key)) {
if (sortField.value[key]) {
sortJavaField.value.sortVOList.push({
sortName: key,
sortType: sortField.value[key] === 'ascending' ? 'ASC' : sortField.value[key] === 'descending' ? 'DESC' : undefined,
})
}
}
}
// console.log(sortField.value)
getData() // 直接统一在筛选里加入排序字段
// emit('updateSortChange', sortObj.value) // 不再单独传递排序字段
}
// 多列排序-类名样式保存
const handleHeadAddClass = ({ column }) => {
if (sortField.value[column.property]) {
column.order = sortField.value[column.property];
}
}
// 表头点击事件
const handleHeaderClick = (column, event) => {
// visible.value = false
// console.log(column, event);
// // 如果点击的是排序图标,不阻止事件,否则阻止默认的排序行为
// const target = event.target
// event.preventDefault();
// event.stopPropagation();
// event.cancelBubble = true
// if (!target.classList.contains('caret-wrapper') && !target.classList.contains('ascending') && !target.classList.contains('descending')) {
// }
}
</script>
<style lang="scss">
@import "./index.scss";
</style>
<style lang="scss" scoped>
:deep(.el-table) {
thead {
.cell {
// background-color: #1fff;
display: flex;
min-width: 100px;
position: relative;
.table-header-div {
// flex: 1;
display: flex;
.title-span {
flex-grow: 1;
word-break: break-word;
/* 允许单词在任何地方断行 */
}
.el-only-child__content {
height: 23px;
}
.icon-span {
flex-shrink: 0;
margin-top: 1px;
margin-left: 5px;
cursor: pointer;
/* 防止缩小 */
/* 给图标和标题之间添加一些间距 */
}
}
.caret-wrapper {
margin-top: 5px;
}
}
}
}
// :deep(.el-table) {
// thead {
// .cell {
// .table-header-div {
// display: inline-block;
// .title-span {
// }
// }
// .caret-wrapper {
// margin-top: -2px;
// }
// }
// }
// }
// :deep(.el-table) {
// thead {
// .cell {
// // display: flex;
// .table-header-div {
// // flex: 1;
// display: flex;
// .title-span {
// // flex: 1;
// white-space: nowrap!important;
// text-overflow: ellipsis!important;
// overflow: hidden!important;
// word-break: break-all!important;
// }
// .icon-span {}
// }
// .caret-wrapper {
// margin-top: 5px;
// }
// }
// }
// }
// 去除单选框的内容
:deep(.no-radio-label) {
.el-radio__label {
display: none;
}
}
</style>
2.index.vue 使用
<template>
<div>
<!-- JgTable就是封装的公共组件 注意自己需要引入使用 -->
<JgTable :tableDataObj="tableData" @setSearchData="setSearchData" @updateEditItem="updateEditItem"
@updateHandleSelectionChange="updateHandleSelectionChange" @updateChangeRadio="updateChangeRadio"
@updateSortChange="updateSortChange">
<!-- 右侧插槽内容 可不写 -->
<template v-slot:btnContent>
<div>
<el-button :icon="Plus" type="primary">右侧插槽内容</el-button>
</div>
</template>
</JgTable>
</div>
</template>
<script setup lang="jsx">
import { ref, onMounted, } from 'vue'
import { Delete, Plus, EditPen, Tickets } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
defineOptions({
name: 'EquipmentDefect'
})
const nameList = ref([])
let tableData = ref({
tableLoading: false,
// title:名称 field:key type:单选复选框 formType:筛选方式 sortable:排序 needGetList:需要远程搜索 width minWidth maxWidth showOverflowTooltip formatter:格式化内容
columnsData: [
{ title: '多选', type: 'selection', width: 40 },
{ title: '单选', type: 'radio', width: 40 },
{ title: '长文本不换行', field: 'aa', formType: 'input', sortable: 'custom', width: 120 },
{ title: '超出隐藏', field: 'bb', formType: 'input', sortable: 'custom', showOverflowTooltip: true },
{ title: '年月日', field: 'cc1', formType: 'daterange', sortable: 'custom', dateType: 'YYYY-MM-DD', width: 120 },
{ title: '年月日时分秒', field: 'cc2', formType: 'datetimerange', sortable: 'custom', dateType: 'YYYY-MM-DD HH:mm:ss', width: 150 },
{
title: '单选', field: 'dd1', formType: 'select', sortable: 'custom', selectList: nameList,
formatter: (row) => {
const val = nameList.value.find(item => item.value === row.dd1)?.name || ''
return [<span>{val}</span>]
}
},
{
title: '多选', field: 'dd2', formType: 'selectMultiple', sortable: 'custom', width: 120, selectList: nameList,
formatter: (row) => {
const val = filters(row.dd2, nameList.value)
return [<span>{val}</span>]
}
},
{
title: '远程搜索获取下拉数据', field: 'dd3', formType: 'selectMultiple', selectList: [], needGetList: true, sortable: 'custom', showOverflowTooltip: true, minWidth: 100,
formatter: (row) => {
// 如果这个下拉的数据几百条,使用过滤回显,会导致列表渲染卡住,所以这里建议直接使用name中文回显
// 注意:获取下拉数据的逻辑,去table.vue组件内自己写,在 remoteMethod 里写逻辑
// const val = filters(row.dd3, nameList.value)
// return [<span>{val}</span>]
return [<span>{row.dd3}</span>]
}
},
{
title: '接口直接获取下拉数据', field: 'dd4', formType: 'selectMultiple', selectList: [], needJavaList: true, sortable: 'custom', showOverflowTooltip: true, minWidth: 100,
formatter: (row) => {
return [<span>{row.dd4}</span>]
}
},
{ title: '无排序', field: 'ee', formType: 'input', },
{ title: '无筛选', field: 'ff', sortable: 'custom', width: 160, showOverflowTooltip: true },
{
title: '点击事件', field: 'gg', formType: 'input', sortable: 'custom',
formatter: (row, column) => {
// console.log(112,row, column); // 直接{row}或{column} 不会显示内容
return [<span style="cursor: pointer;color:cyan;" onClick={() => clickItem(row, 'view')}> {row.gg} </span>]
}
},
{
title: '缺陷反馈状态', field: 'hh', formType: 'radio', sortable: 'custom', selectList: [
{ name: '已反馈', value: 1 },
{ name: '未反馈', value: 0 },
],
formatter: (row) => (
row ? <el-tag style="border: none;" class="cursor" type={row.hh === 1 ? 'primary' : 'warning'}>{row.hh === 1 ? '已反馈' : row.hh === 0 ? '未反馈' : ''}</el-tag> : ''
)
},
{
title: 'jsx三元或遍历元素', field: 'ii', formType: 'input', sortable: 'custom',
formatter: (row) => {
let dom = []
let arr = [1, 2, 3]
arr.forEach(ele => {
// jsx里使用v-show替换v-if
dom.push(<el-tag v-show={Number(row.ii) !== Number(ele)}>{ele}</el-tag>)
})
return dom
}
},
{
title: '操作', field: 'actionBtn', width: 220, fixed: 'right',
formatter: (row) => (
[
<el-button link type="primary" size="small" icon={Tickets} onClick={() => editItem(row, 'view')} >查看</el-button >,
<el-button link type="primary" size="small" icon={EditPen} onClick={() => editItem(row, 'edit')} >编辑</el-button >,
<el-button link type="danger" size="small" icon={Delete} onClick={() => editItem(row, 'delete')} >删除</el-button >,
]
)
},
],
data: Array.from({ length: 7 }).map((ele, index) => {
return {
aa: '长文本不换行长文本不换行',
bb: '长文本隐藏隐藏隐藏隐藏隐藏',
cc1: '2023-7-20',
cc2: '2023-7-20 10:10:10',
dd1: Math.floor(Math.random() * 5),
dd2: [1, 2],
dd3: '使用下拉name中文回显',
dd4: '使用下拉name中文回显',
ee: '帕拉迪',
ff: 'PLD Sys 61012100',
gg: Math.floor(Math.random() * 20),
hh: Math.floor(Math.random() * 2),
ii: Math.floor(Math.random() * 3),
}
})
})
const clickItem = (row, type) => {
// console.log(row, type);
ElMessage({
type: 'success',
message: '点击',
})
}
// 编辑或查看
const editItem = (row, type) => {
ElMessage({
type: 'success',
message: '点击',
})
switch (type) {
case 'view':
case 'edit':
console.log(row, type);
break;
case 'delete':
deleteItem(row, type)
break;
default:
break;
}
}
// 删除
const deleteItem = (row, type) => {
ElMessageBox.confirm(
'确定删除该列?',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
ElMessage({
type: 'success',
message: 'Delete completed',
})
})
.catch(() => {
})
}
// 拿到搜索条件
const setSearchData = (data) => {
// console.log(data);
tableData.value.tableLoading = true
setTimeout(() => {
ElMessage({
type: 'success',
message: '调用后端查询接口逻辑',
})
tableData.value.tableLoading = false
}, 300);
}
// 子组件调用父组件的测试事件
const updateEditItem = (data) => {
console.log('子组件调用父组件的测试事件', data);
editItem(data.row, data.type)
}
const updateHandleSelectionChange = (data) => {
console.log('updateHandleSelectionChange', data);
}
const updateChangeRadio = (data) => {
console.log('updateChangeRadio', data);
}
const updateSortChange = (data) => {
// console.log(data);
}
// 下拉过滤回显
const filters = (key, arr = []) => {
const names = []
const value = Array.isArray(key) ? key : [key]
for (let i = 0; i < arr.length; i++) {
if (value.includes(arr[i].value)) {
names.push(arr[i].name)
}
}
return names.join(';')
}
onMounted(() => {
setTimeout(() => {
nameList.value = [
{ name: '横向隔离', value: 0 },
{ name: '纵向加密', value: 1 },
{ name: '防火墙', value: 2 },
{ name: '监测装置', value: 3 },
{ name: '运维网关', value: 4 },
{ name: '其他', value: 5 },
]
}, 1000);
})
// 将变量和函数返回,以便在模版中使用
</script>
<style lang="scss" scoped></style>
3.index.scss
.page-view {
height: 100%;
width: 100%;
}
.flex-start {
display: flex;
justify-content: flex-start;
align-items: center;
}
.flex-end {
display: flex;
justify-content: flex-end;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-row {
display: flex;
flex-direction: row;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.mr {
margin-right: 16px;
}
.ml {
margin-left: 16px;
}
.mt {
margin-top: 12px;
}
.mb {
margin-bottom: 12px;
}
.m0 {
margin: 0;
}