feat: 🎸 显示更多组建开发中

This commit is contained in:
chenkl 2020-12-25 17:15:40 +08:00
parent c395e27f67
commit fa9f24d5da
21 changed files with 723 additions and 85 deletions

View File

@ -19,15 +19,15 @@
"clipboard": "^2.0.6",
"core-js": "^3.6.5",
"echarts": "^4.9.0",
"element-plus": "1.0.1-beta.11",
"element-plus": "1.0.1-beta.14",
"highlight.js": "^10.4.0",
"lodash-es": "^4.17.15",
"mitt": "^2.1.0",
"mockjs": "^1.1.0",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0",
"qs": "^6.9.4",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.2",
"vditor": "^3.7.0",
"vue": "3.0.4",

View File

@ -0,0 +1,84 @@
<template>
<div class="more__item clearfix">
<p class="more__item--text">{{ content }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed, unref, onMounted, nextTick, ref } from 'vue'
export default defineComponent({
name: 'More',
props: {
//
content: {
type: String as PropType<string>,
default: ''
},
//
lineClamp: {
type: Number as PropType<number>,
default: 0
},
//
width: {
type: String as PropType<string>,
default: '300px'
},
// style
style: {
type: Object as PropType<object>,
default: () => {
return {
width: '300px',
float: 'left'
}
}
}
},
setup(props) {
const styleObj = computed(() => {
})
}
})
</script>
<style lang="less" scoped>
.more__item {
width: 528px;
height: 122px;
float: left;
.more__item--text {
width: 476px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
font-size: 14px;
color: #545c63;
line-height: 28px;
transition: all .1s;
text-align: left;
&:hover {
background: #fff;
height: auto;
position: relative;
z-index: 5;
border-radius: 8px;
box-shadow: 0 8px 16px 0 rgba(7,17,27,.2);
-webkit-line-clamp: inherit;
padding: 10px;
margin-top: -10px;
margin-left: -10px;
}
}
}
.clearfix:after {
content: "";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
</style>

View File

@ -35,14 +35,6 @@
<div class="setting__title">顶部菜单主题</div> -->
<!-- <div class="setting__title">界面功能</div> -->
<!-- <div class="setting__item">
<span>固定顶部操作栏</span>
<el-switch v-model="fixedNavbar" @change="setFixedNavbar" />
</div>
<div class="setting__item">
<span>固定标签页</span>
<el-switch v-model="fixedTags" @change="setFixedTags" />
</div> -->
<div class="setting__title">界面显示</div>
<div class="setting__item">
@ -116,16 +108,6 @@ export default defineComponent({
appStore.SetCollapsed(false)
}
// const fixedNavbar = ref<boolean>(appStore.fixedNavbar)
// function setFixedNavbar(fixedNavbar: boolean) {
// appStore.SetFixedNavbar(fixedNavbar)
// }
// const fixedTags = ref<boolean>(appStore.fixedTags)
// function setFixedTags(fixedTags: boolean) {
// appStore.SetFixedTags(fixedTags)
// }
const fixedHeader = ref<boolean>(appStore.fixedHeader)
function setFixedHeader(fixedHeader: boolean) {
appStore.SetFixedHeader(fixedHeader)
@ -179,8 +161,6 @@ export default defineComponent({
return {
drawer, toggleClick,
layout, setLayout,
// fixedNavbar, setFixedNavbar,
// fixedTags, setFixedTags,
fixedHeader, setFixedHeader,
navbar, setNavbar,
hamburger, setHamburger,

View File

@ -26,7 +26,7 @@ export default defineComponent({
<style lang="less" scoped>
.app-main {
overflow: hidden;
// overflow: hidden;
padding: 20px;
}
</style>

View File

@ -24,6 +24,8 @@ const Layout = () => import('../layout/index.vue')
affix: true truetag项中( false)
noTagsView: true truetag中( false)
activeMenu: '/dashboard'
followAuth: '/dashboard'
showMainRoute: true true即使hidden为true( false)
}
**/
@ -178,6 +180,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
meta: {
title: '弹窗'
}
},
{
path: 'more',
component: () => import('_p/index/views/components-demo/more/index.vue'),
name: 'MoreDemo',
meta: {
title: '显示更多'
}
}
]
},
@ -499,11 +509,45 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
},
children: [
{
path: 'example',
component: () => import('_p/index/views/example-demo/example/index.vue'),
name: 'Example',
path: 'example-dialog',
component: () => import('_p/index/views/example-demo/example-dialog/index.vue'),
name: 'ExampleDialog',
meta: {
title: '列表综合实例'
title: '列表综合实例-弹窗'
}
},
{
path: 'example-page',
component: () => import('_p/index/views/example-demo/example-page/index.vue'),
name: 'ExamplePage',
meta: {
title: '列表综合实例-页面'
}
},
{
path: 'example-add',
component: () => import('_p/index/views/example-demo/example-page/example-add.vue'),
name: 'ExampleAdd',
meta: {
title: '列表综合实例-新增',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example-demo/example-page'
}
},
{
path: 'example-edit',
component: () => import('_p/index/views/example-demo/example-page/example-edit.vue'),
name: 'ExampleEdit',
meta: {
title: '列表综合实例-编辑',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example-demo/example-page'
}
}
]

View File

@ -10,6 +10,8 @@ export interface RouteMeta {
activeMenu?: string
parent?: string
noTagsView?: boolean
followAuth?: string
showMainRoute?: boolean
}
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {

View File

@ -7,8 +7,6 @@ export interface AppState {
showLogo: Boolean
showNavbar: Boolean
fixedHeader: Boolean
// fixedTags: Boolean
// fixedNavbar: Boolean
layout: String
showBreadcrumb: Boolean
showHamburger: Boolean
@ -25,8 +23,6 @@ class App extends VuexModule implements AppState {
public showLogo = true // 是否显示logo
public showTags = true // 是否显示标签栏
public showNavbar = true // 是否显示navbar
// public fixedTags = true // 是否固定标签栏
// public fixedNavbar = true // 是否固定navbar
public fixedHeader = true // 是否固定header
public layout = 'Classic' // layout布局
public showBreadcrumb = true // 是否显示面包屑
@ -53,14 +49,6 @@ class App extends VuexModule implements AppState {
private SET_NAVBAR(showNavbar: boolean): void {
this.showNavbar = showNavbar
}
// @Mutation
// private SET_FIXEDTAGS(fixedTags: boolean): void {
// this.fixedTags = fixedTags
// }
// @Mutation
// private SET_FIXEDNAVBAR(fixedNavbar: boolean): void {
// this.fixedNavbar = fixedNavbar
// }
@Mutation
private SET_FIXEDHEADER(fixedHeader: boolean): void {
this.fixedHeader = fixedHeader
@ -114,14 +102,6 @@ class App extends VuexModule implements AppState {
public SetFixedHeader(fixedHeader: boolean): void {
this.SET_FIXEDHEADER(fixedHeader)
}
// @Action
// public SetFixedTags(fixedTags: boolean): void {
// this.SET_FIXEDTAGS(fixedTags)
// }
// @Action
// public SetFixedNavbar(fixedNavbar: boolean): void {
// this.SET_FIXEDNAVBAR(fixedNavbar)
// }
@Action
public SetLayout(layout: 'Classic' | 'LeftTop' | 'Top' | 'Test'): void {
this.SET_LAYOUT(layout)

View File

@ -51,45 +51,13 @@ class Permission extends VuexModule implements PermissionState {
}
}
// // 二级以上的菜单降级成二级菜单
// function formatRouter(routes: AppRouteRecordRaw[], basePath = '/', list: AppRouteRecordRaw[] = [], parent: AppRouteRecordRaw) {
// routes.map((item: AppRouteRecordRaw) => {
// item.path = path.resolve(basePath, item.path)
// const meta: RouteMeta = item.meta || {}
// if (!meta.parent && parent) {
// meta.parent = parent.path
// item.meta = meta
// }
// if (item.redirect && item.redirect !== 'noredirect') {
// item.redirect = path.resolve(basePath, item.redirect as any)
// }
// if (item.children && item.children.length > 0) {
// const arr: AppRouteRecordRaw[] = formatRouter(item.children, item.path, list, item)
// delete item.children
// list.concat(arr)
// }
// list.push(item)
// })
// return list
// }
// // 菜单降级
// function getFlatRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] {
// return routes.map((child: AppRouteRecordRaw) => {
// if (child.children && child.children.length > 0) {
// child.children = formatRouter(child.children, child.path, [], child)
// }
// return child
// })
// }
// 路由过滤,主要用于权限控制
function generateRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] {
const res: AppRouteRecordRaw[] = []
for (const route of routes) {
// skip some route
if (route.meta && route.meta.hidden) {
if (route.meta && route.meta.hidden && !route.meta.showMainRoute) {
continue
}

View File

@ -0,0 +1,46 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="显示更多组件。"
type="info"
style="margin-bottom: 20px;"
/>
<el-alert
effect="dark"
:closable="false"
title="默认显示一行,超出隐藏。"
type="info"
style="margin-bottom: 20px;margin-top: 20px;"
/>
<div>
<More :line-clamp="3" content="2020版是根据前端全栈工程师岗位要求结合时下技术热点及未来前端术发展趋势全新制作的课程让你实现从零基础到具备全栈能力的就业水平。相比之前的版本我们有几大重要内容1. 两大核心企业项目贯穿整个体系实现项目闭环开发亲历从0到1的企业级项目。项目之间强关联还原企业开发模式渐进式实现过程更顺滑。第一个旅游网项目还原企业迭代开发同一个项目从PC端演变到移动webapp掌握跨端开发能力。 第二个电商全栈项目用Vue.js 实现前端node.js+koa2+MongoDB实现后端打造6大核心业务15+精美网页12+真实数据接口并打通前后端数据联调。2. 项目驱动式教学案例融入课程讲解中让学习不枯燥。多领域案例帮你开拓眼界和经验。3. 不用担心版本问题课程截止上线使用最新版本。比如Vue3.0等让大家学到新技术。4. 技术覆盖更实用更全面从0基础到全栈能力循序渐进的培养慕课网宗旨只学有用的。帮你打通跨端和全栈技能。5. 慕课网网红讲师亲授讲解更细致阶梯式习题整门课程代码量将达到6万行相当于1~2年工作经验所敲代码。" />
</div>
<el-alert
effect="dark"
:closable="false"
title="默认显示3行超出隐藏。"
type="info"
style="margin-bottom: 20px;margin-top: 20px;"
/>
<div>
<More :line-clamp="3" content="2020版是根据前端全栈工程师岗位要求结合时下技术热点及未来前端术发展趋势全新制作的课程让你实现从零基础到具备全栈能力的就业水平。相比之前的版本我们有几大重要内容1. 两大核心企业项目贯穿整个体系实现项目闭环开发亲历从0到1的企业级项目。项目之间强关联还原企业开发模式渐进式实现过程更顺滑。第一个旅游网项目还原企业迭代开发同一个项目从PC端演变到移动webapp掌握跨端开发能力。 第二个电商全栈项目用Vue.js 实现前端node.js+koa2+MongoDB实现后端打造6大核心业务15+精美网页12+真实数据接口并打通前后端数据联调。2. 项目驱动式教学案例融入课程讲解中让学习不枯燥。多领域案例帮你开拓眼界和经验。3. 不用担心版本问题课程截止上线使用最新版本。比如Vue3.0等让大家学到新技术。4. 技术覆盖更实用更全面从0基础到全栈能力循序渐进的培养慕课网宗旨只学有用的。帮你打通跨端和全栈技能。5. 慕课网网红讲师亲授讲解更细致阶梯式习题整门课程代码量将达到6万行相当于1~2年工作经验所敲代码。" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import More from '_c/More/index.vue'
export default defineComponent({
// name: 'MoreDemo',
components: {
More
}
})
</script>
<style>
</style>

View File

@ -112,7 +112,7 @@ const columns = [
]
export default defineComponent({
// name: 'Example',
// name: 'ExampleDialog',
components: {
IfnoWrite
},

View File

@ -0,0 +1,22 @@
import { fetch } from '_p/index/axios-config/axios'
interface PropsData {
params?: any
data?: any
}
export const getExampleListApi = ({ params }: PropsData): any => {
return fetch({ url: '/example/list', method: 'get', params })
}
export const delsExampApi = ({ data }: PropsData): any => {
return fetch({ url: '/example/delete', method: 'post', data })
}
export const saveExampApi = ({ data }: PropsData): any => {
return fetch({ url: '/example/save', method: 'post', data })
}
export const getExampDetApi = ({ params }: PropsData): any => {
return fetch({ url: '/example/detail', method: 'get', params })
}

View File

@ -0,0 +1,188 @@
<template>
<div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-row>
<el-col :span="24">
<el-form-item prop="title" label="标题">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="author" label="作者">
<el-input v-model="form.author" placeholder="请输入作者" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="display_time" label="创建时间">
<el-date-picker
v-model="form.display_time"
type="datetime"
placeholder="请选择创建时间"
style="width: 100%;"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="importance" label="重要性">
<el-select v-model="form.importance" placeholder="请选择重要性" style="width: 100%;">
<el-option label="重要" value="3" />
<el-option label="良好" value="2" />
<el-option label="一般" value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="pageviews" label="阅读数">
<el-input-number
v-model="form.pageviews"
:min="0"
:max="99999999"
style="width: 100%;"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="content" label="内容">
<editor ref="editorRef" :value="form.content" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="dialong__button--wrap">
<el-button @click="close">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, unref, PropType } from 'vue'
import { useRouter } from 'vue-router'
import Editor from '_c/Editor/index.vue'
import { Message } from '_c/Message'
import { formatTime } from '@/utils'
import { InfoWriteParams, InfoWriteRules } from './types'
import { saveExampApi, getExampDetApi } from '../api'
const requiredRule = {
required: true,
message: '该项为必填项'
}
export default defineComponent({
name: 'IfnoWrite',
components: {
Editor
},
props: {
id: {
type: String as PropType<string>,
default: () => ''
}
},
emits: ['close', 'success'],
setup(props, { emit }) {
const { push } = useRouter()
const formRef = ref<HTMLElement | null>(null)
const editorRef = ref<HTMLElement | null>(null)
const subLoading = ref<boolean>(false)
const form = reactive<InfoWriteParams>({
id: '', // id
author: '', //
title: '', //
content: '', //
importance: '', //
display_time: '', //
pageviews: 0 //
})
const rules = reactive<InfoWriteRules>({
title: [requiredRule],
author: [requiredRule],
content: [requiredRule],
importance: [requiredRule],
display_time: [requiredRule],
pageviews: [requiredRule]
})
async function getDet() {
if (props.id) {
const id = props.id
try {
const res = await getExampDetApi({
params: {
id: id
}
})
if (res.code === '0000') {
for (const key in form) {
if (key === 'importance') {
form[key] = res.data[key].toString()
} else {
form[key] = res.data[key]
}
}
}
} catch (e) {
console.log(e)
}
}
}
getDet()
//
function setListData() {
const formRefWrap = unref(formRef as any)
const editorRefWrap = unref(editorRef as any)
form.content = editorRefWrap.getHtml()
try {
subLoading.value = true
formRefWrap.validate(async(valid: boolean) => {
if (valid) {
const formData = unref(form)
formData.display_time = formatTime(formData.display_time, 'yyyy-MM-dd HH:mm:ss')
const res = await saveExampApi({
data: formData
})
if (res.code === '0000') {
Message.success(form.id ? '编辑成功' : '新增成功')
emit('success', form.id ? 'edit' : 'add')
}
} else {
console.log('error submit!!')
return false
}
})
} catch (err) {
console.log(err)
} finally {
subLoading.value = false
}
}
function close() {
push('/example-demo/example-page')
}
return {
formRef, editorRef,
subLoading,
form,
rules,
setListData,
close,
getDet
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,18 @@
export interface InfoWriteParams {
title: string
id?: string
author: string
content: string
importance: string
display_time: string
pageviews: number
}
export interface InfoWriteRules {
title?: any[]
author?: any[]
content?: any[]
importance?: any[]
display_time?: any[]
pageviews?: any[]
}

View File

@ -0,0 +1,30 @@
<template>
<ifno-write @success="success" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import IfnoWrite from './components/IfnoWrite.vue'
import vueBus from '@/vue-bus'
export default defineComponent({
// name: 'ExampleAdd',
components: {
IfnoWrite
},
setup() {
//
function success(type: string) {
// 使vueBus
vueBus.$emit('success', type)
}
return {
success
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,34 @@
<template>
<ifno-write :id="id" @success="success" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import IfnoWrite from './components/IfnoWrite.vue'
import vueBus from '@/vue-bus'
import { useRoute } from 'vue-router'
export default defineComponent({
// name: 'ExampleEdit',
components: {
IfnoWrite
},
setup() {
const { query } = useRoute()
//
function success(type: string) {
// 使vueBus
vueBus.$emit('success', type)
}
return {
success,
id: query.id
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,229 @@
<template>
<div>
<div class="search__example--wrap">
<com-search
:data="searchData"
@search-submit="searchSubmit"
@reset-submit="resetSubmit"
/>
</div>
<div class="button__example--wrap">
<el-button type="primary" icon="el-icon-circle-plus-outline" @click="open(false)">新增</el-button>
<el-button
type="danger"
icon="el-icon-delete"
@click="dels"
>删除</el-button>
</div>
<com-table
v-loading="loading"
selection
:columns="columns"
:data="tableData"
:pagination="{
currentPage: defalutParams.pageIndex,
total: total,
onSizeChange: handleSizeChange,
onCurrentChange: handleCurrentChange
}"
@selection-change="handleSelectionChange"
>
<template #importance="scope">
<el-tag
:type="scope.row.importance === 3
? 'success'
: (scope.row.importance === 2
? 'warning'
: 'danger')"
>{{ scope.row.importance === 3
? '重要'
: (scope.row.importance === 2
? '良好'
: '一般') }}
</el-tag>
</template>
<template #action="scope">
<el-button type="primary" size="mini" @click="open(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="dels(scope.row)">删除</el-button>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import vueBus from '@/vue-bus'
import { useExample } from '@/hooks/useExample'
import { Message } from '_c/Message'
import { getExampleListApi, delsExampApi } from './api'
const searchData = [
{
label: '标题',
value: '',
itemType: 'input',
field: 'title',
placeholder: '请输入标题',
clearable: true
}
]
const columns = [
{
key: 'title',
label: '标题',
showOverflowTooltip: true
},
{
key: 'author',
label: '作者'
},
{
key: 'display_time',
label: '创建时间'
},
{
key: 'importance',
label: '重要性',
slots: {
default: 'importance'
}
},
{
key: 'pageviews',
label: '阅读数'
},
{
key: 'action',
label: '操作',
width: '150px',
slots: {
default: 'action'
}
}
]
export default defineComponent({
name: 'ExamplePage',
setup() {
const { push } = useRouter()
const {
defalutParams,
tableData,
loading,
total,
currentChange,
sizeChange,
handleSelectionChange,
selectionData,
delData
} = useExample()
//
async function getExampleList(data?: any): Promise<void> {
try {
const res = await getExampleListApi({
params: Object.assign(defalutParams, data || {})
})
if (res.code === '0000') {
total.value = res.data.total
tableData.value = res.data.list
}
} finally {
loading.value = false
}
}
//
function searchSubmit(data: any) {
//
currentChange(1)
getExampleList(data)
}
//
function resetSubmit(data: any) {
//
currentChange(1)
getExampleList(data)
}
//
function handleSizeChange(val: number) {
//
sizeChange(val)
getExampleList()
}
//
function handleCurrentChange(val: number) {
//
currentChange(val)
getExampleList()
}
//
function dels(item?: any) {
delData(async() => {
let ids: string[]
if (item.id) {
ids = [item.id]
} else {
ids = selectionData.value.map((v: any) => {
return v.id
})
}
const res = await delsExampApi({
data: { ids }
})
if (res.code === '0000') {
Message.success('删除成功!')
getExampleList()
}
}, { hiddenVerify: item.id })
}
//
function open(row: any) {
push(row ? `/example-demo/example-edit?id=${row.id}` : `/example-demo/example-add`)
}
getExampleList()
onMounted(() => {
vueBus.$on('success', (type: string) => {
if (type === 'add') {
currentChange(1)
}
getExampleList()
})
})
onUnmounted(() => {
vueBus.$off('success')
})
return {
open,
searchData, searchSubmit, resetSubmit,
columns,
defalutParams,
loading,
tableData,
total,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
dels
}
}
})
</script>
<style>
</style>

13
src/vue-bus/index.ts Normal file
View File

@ -0,0 +1,13 @@
// 通过mitt实现vue-bus通信
import mitt from 'mitt'
const bus: any = {}
const emitter = mitt()
bus.$on = emitter.on
bus.$off = emitter.off
bus.$emit = emitter.emit
export default bus

View File

@ -3990,10 +3990,10 @@ electron-to-chromium@^1.3.621:
resolved "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.622.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.622.tgz#9726bd2e67a5462154750ce9701ca6af07d07877"
integrity sha1-lya9LmelRiFUdQzpcBymrwfQeHc=
element-plus@1.0.1-beta.11:
version "1.0.1-beta.11"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.0.1-beta.11.tgz#bcced187b44748b8d597c2681923c5097ee95fbe"
integrity sha512-qmael6DKlhJsOaEi/n7Zpw10qOCRO1irCXJ1LO8eqtl/7UCPZQNiOIIu6Gd1SgsJWbiW55NrFbHiRpfx37ZLIg==
element-plus@1.0.1-beta.14:
version "1.0.1-beta.14"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.0.1-beta.14.tgz#67e6742ef0a380156d306d519d474220f8c3e03e"
integrity sha512-iqc8lAmj4yYTVQFlxwm5IWj3vxufgmF8FVwKgEKJfy1qQQVqA34R81IgywQpYh3jO/d+ofmHXhsm+z3ojXVp0A==
dependencies:
"@popperjs/core" "^2.4.4"
async-validator "^3.4.0"