2025-12-21 18:41:10 +08:00
|
|
|
|
<!-- 库区选择 -->
|
2025-11-02 22:39:59 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="selectedValue"
|
|
|
|
|
|
showSearch
|
|
|
|
|
|
:placeholder="placeholder"
|
|
|
|
|
|
:loading="loading"
|
|
|
|
|
|
:allowClear="true"
|
|
|
|
|
|
:filterOption="filterOption"
|
|
|
|
|
|
:notFoundContent="notFoundContent"
|
|
|
|
|
|
:mode="multiple ? 'multiple' : 'default'"
|
|
|
|
|
|
@change="handleChange"
|
|
|
|
|
|
@search="handleSearch"
|
|
|
|
|
|
@focus="handleFocus"
|
2025-12-21 18:41:10 +08:00
|
|
|
|
@popup-scroll="handlePopupScroll"
|
2025-11-02 22:39:59 +08:00
|
|
|
|
:getPopupContainer="getParentContainer"
|
|
|
|
|
|
v-bind="attrs"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #notFoundContent>
|
2025-12-21 18:41:10 +08:00
|
|
|
|
|
2025-11-02 22:39:59 +08:00
|
|
|
|
</template>
|
2025-12-21 18:41:10 +08:00
|
|
|
|
<a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)">
|
2025-11-02 22:39:59 +08:00
|
|
|
|
{{ getOptionLabel(option) }}
|
|
|
|
|
|
</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts">
|
2025-12-21 18:41:10 +08:00
|
|
|
|
import { defineComponent, ref, watch, computed, onMounted } from 'vue';
|
|
|
|
|
|
import { useAttrs } from '/@/hooks/core/useAttrs';
|
|
|
|
|
|
import { propTypes } from '/@/utils/propTypes';
|
|
|
|
|
|
import { defHttp } from '/@/utils/http/axios';
|
|
|
|
|
|
import { queryById } from '@/views/base/area/Area.api';
|
|
|
|
|
|
import { setPopContainer } from '/@/utils';
|
|
|
|
|
|
import { debounce } from 'lodash-es';
|
|
|
|
|
|
|
|
|
|
|
|
// 库区数据接口
|
|
|
|
|
|
interface Area {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
areaCode: string;
|
|
|
|
|
|
areaName: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//响应数据接口
|
|
|
|
|
|
interface ResponseData {
|
|
|
|
|
|
records: Area[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
size: number;
|
|
|
|
|
|
current: number;
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
|
|
name: 'AreaSelect',
|
|
|
|
|
|
inheritAttrs: false,
|
|
|
|
|
|
props: {
|
|
|
|
|
|
// 选中值,支持v-model
|
|
|
|
|
|
value: propTypes.oneOfType([propTypes.string, propTypes.array, propTypes.object]),
|
|
|
|
|
|
// 占位符
|
|
|
|
|
|
placeholder: propTypes.string.def('请选择库区'),
|
|
|
|
|
|
// 是否多选
|
|
|
|
|
|
multiple: propTypes.bool.def(false),
|
|
|
|
|
|
// 是否异步加载数据
|
|
|
|
|
|
async: propTypes.bool.def(true),
|
|
|
|
|
|
// 分页大小
|
|
|
|
|
|
pageSize: propTypes.number.def(10),
|
|
|
|
|
|
// 弹出层容器
|
|
|
|
|
|
popContainer: propTypes.string,
|
|
|
|
|
|
// 自定义弹出层容器函数
|
|
|
|
|
|
getPopupContainer: {
|
|
|
|
|
|
type: Function,
|
|
|
|
|
|
default: (node: HTMLElement) => node?.parentNode,
|
2025-11-02 22:39:59 +08:00
|
|
|
|
},
|
2025-12-21 18:41:10 +08:00
|
|
|
|
// 是否立即触发change事件
|
|
|
|
|
|
immediateChange: propTypes.bool.def(false),
|
|
|
|
|
|
// 返回值类型: 'id'(默认) | 'object' | 其他字段名
|
|
|
|
|
|
returnValue: propTypes.string.def('id'),
|
|
|
|
|
|
//默认启用
|
|
|
|
|
|
izActive: propTypes.number.def(1),
|
|
|
|
|
|
//默认未删除
|
2025-12-26 18:14:08 +08:00
|
|
|
|
delFlag:propTypes.number.def(0),
|
|
|
|
|
|
//库区
|
|
|
|
|
|
areaCode: propTypes.array
|
2025-12-21 18:41:10 +08:00
|
|
|
|
},
|
|
|
|
|
|
emits: ['change', 'update:value', 'optionsLoaded'],
|
|
|
|
|
|
setup(props, { emit }) {
|
|
|
|
|
|
const Options = ref<Area[]>([]);
|
|
|
|
|
|
const loading = ref<boolean>(false);
|
|
|
|
|
|
const allAreas = ref<Area[]>([]);
|
|
|
|
|
|
const attrs = useAttrs({ excludeDefaultKeys: false });
|
|
|
|
|
|
|
|
|
|
|
|
// 分页相关
|
|
|
|
|
|
const pageNo = ref(1);
|
|
|
|
|
|
const isHasData = ref(true);
|
|
|
|
|
|
const scrollLoading = ref(false);
|
|
|
|
|
|
const searchKeyword = ref('');
|
|
|
|
|
|
|
|
|
|
|
|
// 选中值
|
|
|
|
|
|
const selectedValue = ref<string | string[] | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
|
|
// 未找到内容
|
|
|
|
|
|
const notFoundContent = computed(() => {
|
|
|
|
|
|
return loading.value ? undefined : '暂无数据';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取选项显示文本 - 始终显示完整格式
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getOptionLabel(option: Area) {
|
|
|
|
|
|
return `${option.areaCode} - ${option.areaName}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取选项值 - 根据returnValue确定实际存储的值
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getOptionValue(option: Area) {
|
|
|
|
|
|
if (props.returnValue === 'object') {
|
|
|
|
|
|
return option.id; // 对于object类型,仍然使用id作为选项值,但在change事件中返回完整对象
|
|
|
|
|
|
} else if (props.returnValue === 'id') {
|
|
|
|
|
|
return option.id;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return option[props.returnValue as keyof Area] as string;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取弹出层容器
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getParentContainer(node: HTMLElement) {
|
|
|
|
|
|
if (props.popContainer) {
|
|
|
|
|
|
return setPopContainer(node, props.popContainer);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (typeof props.getPopupContainer === 'function') {
|
|
|
|
|
|
return props.getPopupContainer(node);
|
2025-11-02 22:39:59 +08:00
|
|
|
|
} else {
|
2025-12-21 18:41:10 +08:00
|
|
|
|
return node?.parentNode;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 过滤选项 - 禁用前端过滤,使用后端搜索
|
|
|
|
|
|
*/
|
|
|
|
|
|
function filterOption(_input: string, _option: any) {
|
|
|
|
|
|
return true; // 禁用前端过滤,完全依赖后端搜索
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 确保库区唯一性的辅助函数
|
|
|
|
|
|
*/
|
|
|
|
|
|
function ensureUnique(Areas: Area[], newArea: Area): Area[] {
|
|
|
|
|
|
// 如果已经存在相同id的库区,先移除旧的再添加新的
|
|
|
|
|
|
const filtered = Areas.filter(Area => Area.id !== newArea.id);
|
|
|
|
|
|
return [newArea, ...filtered];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取库区数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
const queryData = async (page = 1, keyword = '', isSearch = false) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
|
|
|
|
|
|
const res = await defHttp.get<ResponseData>({
|
2025-12-26 18:14:08 +08:00
|
|
|
|
url: '/base/area/list',
|
2025-12-21 18:41:10 +08:00
|
|
|
|
params: {
|
|
|
|
|
|
pageSize: props.pageSize,
|
|
|
|
|
|
pageNo: page,
|
|
|
|
|
|
keyword: keyword,
|
|
|
|
|
|
izActive: props.izActive,
|
2025-12-26 18:14:08 +08:00
|
|
|
|
delFlag: props.delFlag,
|
|
|
|
|
|
areaCode: props.areaCode,
|
2025-12-21 18:41:10 +08:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const records = res.records || [];
|
|
|
|
|
|
|
|
|
|
|
|
if (page === 1 || isSearch) {
|
|
|
|
|
|
// 第一页或搜索时,重置数据
|
|
|
|
|
|
allAreas.value = records;
|
|
|
|
|
|
Options.value = records;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
} else {
|
2025-12-21 18:41:10 +08:00
|
|
|
|
// 滚动加载时,追加数据(确保不重复)
|
|
|
|
|
|
const newRecords = records.filter(record =>
|
|
|
|
|
|
!allAreas.value.some(Area => Area.id === record.id)
|
|
|
|
|
|
);
|
|
|
|
|
|
allAreas.value = [...allAreas.value, ...newRecords];
|
|
|
|
|
|
Options.value = [...Options.value, ...newRecords];
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-21 18:41:10 +08:00
|
|
|
|
// 修正分页判断逻辑
|
|
|
|
|
|
isHasData.value = records.length >= props.pageSize;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
|
2025-12-21 18:41:10 +08:00
|
|
|
|
emit('optionsLoaded', allAreas.value);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (page === 1) {
|
|
|
|
|
|
allAreas.value = [];
|
|
|
|
|
|
Options.value = [];
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
scrollLoading.value = false;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
async function queryDataById(value: string) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await queryById({ id: value });
|
|
|
|
|
|
if (res) {
|
|
|
|
|
|
// 将查询到的单个库区信息添加到列表中,确保唯一性
|
|
|
|
|
|
const Area = Array.isArray(res) ? res[0] : res;
|
|
|
|
|
|
if (Area) {
|
|
|
|
|
|
// 使用ensureUnique确保库区不重复
|
|
|
|
|
|
allAreas.value = ensureUnique(allAreas.value, Area);
|
|
|
|
|
|
Options.value = ensureUnique(Options.value, Area);
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
return Area;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('查询库区失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据选项值找到对应的选项对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
function findOptionByValue(value: string): Area | undefined {
|
|
|
|
|
|
if (props.returnValue === 'object' || props.returnValue === 'id') {
|
|
|
|
|
|
return allAreas.value.find((Area) => Area.id === value);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return allAreas.value.find((Area) => Area[props.returnValue as keyof Area] === value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取需要返回的值
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getReturnValue(value: string | string[]) {
|
|
|
|
|
|
if (!value) {
|
|
|
|
|
|
return props.multiple ? [] : undefined;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果返回整个对象
|
|
|
|
|
|
if (props.returnValue === 'object') {
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
|
return value.map((v) => findOptionByValue(v)).filter(Boolean);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return findOptionByValue(value);
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 如果返回ID(默认情况)
|
|
|
|
|
|
else if (props.returnValue === 'id') {
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果返回对象中的某个字段
|
|
|
|
|
|
else {
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
|
return value.map((v) => {
|
|
|
|
|
|
const option = findOptionByValue(v);
|
|
|
|
|
|
return option ? option[props.returnValue as keyof Area] : v;
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const option = findOptionByValue(value);
|
|
|
|
|
|
return option ? option[props.returnValue as keyof Area] : value;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 搜索处理(防抖)
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleSearch = debounce(function (value: string) {
|
|
|
|
|
|
searchKeyword.value = value;
|
|
|
|
|
|
pageNo.value = 1;
|
|
|
|
|
|
isHasData.value = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 直接调用API进行搜索
|
|
|
|
|
|
queryData(1, value, true);
|
|
|
|
|
|
}, 300);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理焦点事件
|
|
|
|
|
|
*/
|
|
|
|
|
|
function handleFocus() {
|
|
|
|
|
|
// 如果还没有数据,加载数据
|
|
|
|
|
|
if (allAreas.value.length === 0 && props.async) {
|
2025-11-02 22:39:59 +08:00
|
|
|
|
pageNo.value = 1;
|
|
|
|
|
|
isHasData.value = true;
|
2025-12-21 18:41:10 +08:00
|
|
|
|
queryData(1, '');
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
attrs.onFocus?.();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理值变化
|
|
|
|
|
|
*/
|
|
|
|
|
|
function handleChange(value: string | string[]) {
|
|
|
|
|
|
selectedValue.value = value;
|
|
|
|
|
|
|
|
|
|
|
|
// 根据配置返回相应的值
|
|
|
|
|
|
const returnValue = getReturnValue(value);
|
|
|
|
|
|
emit('update:value', returnValue);
|
|
|
|
|
|
emit('change', returnValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 滚动加载处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
function handlePopupScroll(e: Event) {
|
|
|
|
|
|
const target = e.target as HTMLElement;
|
|
|
|
|
|
const { scrollTop, scrollHeight, clientHeight } = target;
|
|
|
|
|
|
|
|
|
|
|
|
if (!scrollLoading.value && isHasData.value && scrollTop + clientHeight >= scrollHeight - 10) {
|
|
|
|
|
|
scrollLoading.value = true;
|
|
|
|
|
|
pageNo.value++;
|
|
|
|
|
|
|
|
|
|
|
|
queryData(pageNo.value, searchKeyword.value)
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
scrollLoading.value = false;
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
pageNo.value--;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据选中值初始化显示文本
|
|
|
|
|
|
*/
|
|
|
|
|
|
const initSelectValue = async () => {
|
|
|
|
|
|
if (!props.value) {
|
|
|
|
|
|
selectedValue.value = props.multiple ? [] : undefined;
|
|
|
|
|
|
return;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-21 18:41:10 +08:00
|
|
|
|
// 如果是异步模式且还没有加载数据,则先加载数据
|
|
|
|
|
|
if (props.async && allAreas.value.length === 0) {
|
|
|
|
|
|
await queryData();
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-21 18:41:10 +08:00
|
|
|
|
// 根据不同的returnValue设置选中的值
|
|
|
|
|
|
let valueIds: string[] = [];
|
2025-11-02 22:39:59 +08:00
|
|
|
|
|
2025-12-21 18:41:10 +08:00
|
|
|
|
if (props.returnValue === 'object') {
|
|
|
|
|
|
// 如果返回的是对象,value可能是对象或对象数组
|
|
|
|
|
|
if (Array.isArray(props.value)) {
|
|
|
|
|
|
valueIds = props.value.map((Area: any) => Area.id);
|
|
|
|
|
|
selectedValue.value = valueIds;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
valueIds = [(props.value as any).id];
|
|
|
|
|
|
selectedValue.value = valueIds[0];
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
} else if (props.returnValue === 'id') {
|
|
|
|
|
|
if (Array.isArray(props.value)) {
|
|
|
|
|
|
valueIds = props.value as string[];
|
|
|
|
|
|
selectedValue.value = valueIds;
|
2025-11-02 22:39:59 +08:00
|
|
|
|
} else {
|
2025-12-21 18:41:10 +08:00
|
|
|
|
valueIds = [props.value as string];
|
|
|
|
|
|
selectedValue.value = valueIds[0];
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 对于其他字段类型,直接使用传入的值
|
|
|
|
|
|
selectedValue.value = props.value as string | string[];
|
|
|
|
|
|
// 这里需要根据字段值查找对应的ID
|
|
|
|
|
|
if (Array.isArray(props.value)) {
|
|
|
|
|
|
valueIds = props.value.map((v) => {
|
|
|
|
|
|
const option = allAreas.value.find((Area) => Area[props.returnValue as keyof Area] === v);
|
|
|
|
|
|
return option ? option.id : v;
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const option = allAreas.value.find((Area) => Area[props.returnValue as keyof Area] === props.value);
|
|
|
|
|
|
valueIds = option ? [option.id] : [props.value as string];
|
2025-11-02 22:39:59 +08:00
|
|
|
|
}
|
2025-12-21 18:41:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有值不在当前列表中,如果有则查询
|
|
|
|
|
|
const missingIds = valueIds.filter((id) => !allAreas.value.some((Area) => Area.id === id));
|
|
|
|
|
|
if (missingIds.length > 0) {
|
|
|
|
|
|
// 对每个不在列表中的ID进行查询
|
|
|
|
|
|
const queryPromises = missingIds.map((id) => queryDataById(id));
|
|
|
|
|
|
await Promise.all(queryPromises);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 监听value变化
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.value,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
initSelectValue();
|
|
|
|
|
|
},
|
|
|
|
|
|
{ immediate: true }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 组件挂载时初始化
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
if (!props.async) {
|
|
|
|
|
|
queryData();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
attrs,
|
|
|
|
|
|
Options,
|
|
|
|
|
|
loading,
|
|
|
|
|
|
selectedValue,
|
|
|
|
|
|
notFoundContent,
|
|
|
|
|
|
getParentContainer,
|
|
|
|
|
|
filterOption,
|
|
|
|
|
|
handleChange,
|
|
|
|
|
|
handleSearch,
|
|
|
|
|
|
handleFocus,
|
|
|
|
|
|
getOptionLabel,
|
|
|
|
|
|
getOptionValue,
|
|
|
|
|
|
handlePopupScroll,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
2025-11-02 22:39:59 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped></style>
|