first commit
This commit is contained in:
52
src/components/_base/breadcrumb/index.vue
Normal file
52
src/components/_base/breadcrumb/index.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<!--
|
||||
* @Author: 田鑫
|
||||
* @Date: 2023-03-05 18:14:16
|
||||
* @LastEditors: 田鑫
|
||||
* @LastEditTime: 2023-03-05 19:17:52
|
||||
* @Description:
|
||||
-->
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const matched = computed(() => {
|
||||
if (route.matched.length === 1 && route.matched[0].path === '/') {
|
||||
return [];
|
||||
} else {
|
||||
return route.matched.reduce((t: RouteLocationNormalized[], o) => {
|
||||
const isExist = t.find((c) => c.name === o.name);
|
||||
return isExist ? t : [...t, router.resolve(o)];
|
||||
}, []);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view></view>
|
||||
<!-- <a-breadcrumb class="container-breadcrumb">
|
||||
<a-breadcrumb-item v-for="{ meta, name } in matched" :key="name">
|
||||
<router-link v-slot="{ href, navigate }" :to="{ name }" custom>
|
||||
<a-link v-if="meta.needNavigate" :href="href" @click="navigate">{{
|
||||
meta.locale ? meta.locale : '主页'
|
||||
}}</a-link>
|
||||
<a-link v-else disabled>{{ meta.locale ? meta.locale : '主页' }}</a-link>
|
||||
</router-link>
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb> -->
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container-breadcrumb {
|
||||
margin: 16px 0;
|
||||
:deep(.arco-breadcrumb-item) {
|
||||
> a {
|
||||
color: rgb(var(--gray-6));
|
||||
}
|
||||
&:last-child {
|
||||
color: rgb(var(--gray-8));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
5
src/components/_base/index.ts
Normal file
5
src/components/_base/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export { default as Navbar } from './navbar/index.vue';
|
||||
export { default as Menu } from './menu/index.vue';
|
||||
export { default as TabBar } from './tab-bar/index.vue';
|
||||
export { default as Breadcrumb } from './breadcrumb/index.vue';
|
||||
export { default as ModalSimple } from './modal/index.vue';
|
||||
140
src/components/_base/menu/index.vue
Normal file
140
src/components/_base/menu/index.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<script lang="tsx">
|
||||
import type { RouteMeta, RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { useAppStore } from '@/stores';
|
||||
import { listenerRouteChange } from '@/utils/route-listener';
|
||||
import { openWindow, regexUrl } from '@/utils';
|
||||
import useMenuTree from './use-menu-tree';
|
||||
|
||||
export default defineComponent({
|
||||
emit: ['collapse'],
|
||||
setup() {
|
||||
const appStore = useAppStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { menuTree } = useMenuTree();
|
||||
const collapsed = computed({
|
||||
get() {
|
||||
if (appStore.device === 'desktop') return appStore.menuCollapse;
|
||||
return false;
|
||||
},
|
||||
set(value: boolean) {
|
||||
appStore.updateSettings({ menuCollapse: value });
|
||||
},
|
||||
});
|
||||
const topMenu = computed(() => appStore.topMenu);
|
||||
const openKeys = ref<string[]>([]);
|
||||
const selectedKey = ref<string[]>([]);
|
||||
const goto = (item: RouteRecordRaw) => {
|
||||
// Open external link
|
||||
if (regexUrl.test(item.path)) {
|
||||
openWindow(item.path);
|
||||
selectedKey.value = [item.name as string];
|
||||
return;
|
||||
}
|
||||
// Eliminate external link side effects
|
||||
const { hideInMenu, activeMenu } = item.meta as RouteMeta;
|
||||
if (route.name === item.name && !hideInMenu && !activeMenu) {
|
||||
selectedKey.value = [item.name as string];
|
||||
return;
|
||||
}
|
||||
// Trigger router change
|
||||
router.push({
|
||||
name: item.name,
|
||||
});
|
||||
};
|
||||
const findMenuOpenKeys = (target: string) => {
|
||||
const result: string[] = [];
|
||||
let isFind = false;
|
||||
const backtrack = (item: RouteRecordRaw, keys: string[]) => {
|
||||
if (item.name === target) {
|
||||
isFind = true;
|
||||
result.push(...keys);
|
||||
return;
|
||||
}
|
||||
if (item.children?.length) {
|
||||
item.children.forEach((el) => {
|
||||
backtrack(el, [...keys, el.name as string]);
|
||||
});
|
||||
}
|
||||
};
|
||||
menuTree.value.forEach((el: RouteRecordRaw) => {
|
||||
if (isFind) return; // Performance optimization
|
||||
backtrack(el, [el.name as string]);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
listenerRouteChange((newRoute) => {
|
||||
const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta;
|
||||
if (requiresAuth && (!hideInMenu || activeMenu)) {
|
||||
const menuOpenKeys = findMenuOpenKeys((activeMenu || newRoute.name) as string);
|
||||
const keySet = new Set([...menuOpenKeys, ...openKeys.value]);
|
||||
openKeys.value = [...keySet];
|
||||
selectedKey.value = [activeMenu || menuOpenKeys[menuOpenKeys.length - 1]];
|
||||
}
|
||||
}, true);
|
||||
const setCollapse = (val: boolean) => {
|
||||
if (appStore.device === 'desktop') appStore.updateSettings({ menuCollapse: val });
|
||||
};
|
||||
const renderSubMenu = () => {
|
||||
function travel(_route: RouteRecordRaw[], nodes = []) {
|
||||
if (_route) {
|
||||
_route.forEach((element) => {
|
||||
// This is demo, modify nodes as needed
|
||||
const icon = element?.meta?.icon ? () => h(element?.meta?.icon as object) : null;
|
||||
const node =
|
||||
element?.children && element?.children.length !== 0 ? (
|
||||
<a-sub-menu
|
||||
key={element?.name}
|
||||
v-slots={{
|
||||
icon,
|
||||
title: () => element?.meta?.locale || '',
|
||||
}}
|
||||
>
|
||||
{travel(element?.children)}
|
||||
</a-sub-menu>
|
||||
) : (
|
||||
<a-menu-item key={element?.name} v-slots={{ icon }} onClick={() => goto(element)}>
|
||||
{element?.meta?.locale || ''}
|
||||
</a-menu-item>
|
||||
);
|
||||
nodes.push(node as never);
|
||||
});
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
return travel(menuTree.value);
|
||||
};
|
||||
return () => (
|
||||
<a-menu
|
||||
mode={topMenu.value ? 'horizontal' : 'vertical'}
|
||||
v-model:collapsed={collapsed.value}
|
||||
v-model:open-keys={openKeys.value}
|
||||
show-collapse-button={appStore.device !== 'mobile'}
|
||||
auto-open={false}
|
||||
selected-keys={selectedKey.value}
|
||||
auto-open-selected={true}
|
||||
level-indent={34}
|
||||
style="height: 100%;width:100%;"
|
||||
onCollapse={setCollapse}
|
||||
>
|
||||
{renderSubMenu()}
|
||||
</a-menu>
|
||||
);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-menu-inner) {
|
||||
.arco-menu-inline-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.arco-icon {
|
||||
&:not(.arco-icon-down) {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
60
src/components/_base/menu/use-menu-tree.ts
Normal file
60
src/components/_base/menu/use-menu-tree.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { RouteRecordRaw, RouteRecordNormalized } from 'vue-router';
|
||||
|
||||
import { useAppStore } from '@/stores';
|
||||
import appClientMenus from '@/router/app-menus';
|
||||
|
||||
export default function useMenuTree() {
|
||||
const appStore = useAppStore();
|
||||
const appRoute = computed(() => {
|
||||
if (appStore.menuFromServer) {
|
||||
// return appClientMenus.concat(toRaw(appStore.appAsyncMenus));
|
||||
return toRaw(appStore.appAsyncMenus);
|
||||
}
|
||||
return appClientMenus;
|
||||
});
|
||||
const menuTree = computed(() => {
|
||||
const copyRouter = cloneDeep(appRoute.value) as RouteRecordNormalized[];
|
||||
copyRouter.sort((a: RouteRecordNormalized, b: RouteRecordNormalized) => {
|
||||
return (a.meta.order || 0) - (b.meta.order || 0);
|
||||
});
|
||||
function travel(_routes: RouteRecordRaw[], layer: number) {
|
||||
if (!_routes) return null;
|
||||
|
||||
const collector: any = _routes.map((element) => {
|
||||
// leaf node
|
||||
if (element.meta?.hideChildrenInMenu || !element.children) {
|
||||
element.children = [];
|
||||
return element;
|
||||
}
|
||||
|
||||
// route filter hideInMenu true
|
||||
element.children = element.children.filter((x) => x.meta?.hideInMenu !== true);
|
||||
|
||||
// Associated child node
|
||||
const subItem = travel(element.children, layer + 1);
|
||||
|
||||
if (subItem.length) {
|
||||
element.children = subItem;
|
||||
return element;
|
||||
}
|
||||
// the else logic
|
||||
if (layer > 1) {
|
||||
element.children = subItem;
|
||||
return element;
|
||||
}
|
||||
|
||||
if (element.meta?.hideInMenu === false) {
|
||||
return element;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return collector.filter(Boolean);
|
||||
}
|
||||
return travel(copyRouter, 0);
|
||||
});
|
||||
|
||||
return {
|
||||
menuTree,
|
||||
};
|
||||
}
|
||||
32
src/components/_base/modal/index.vue
Normal file
32
src/components/_base/modal/index.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import type { Component, DefineComponent } from 'vue';
|
||||
|
||||
import IconHover from '@arco-design/web-vue/es/_components/icon-hover';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
content?: string | (() => DefineComponent | Component);
|
||||
}>();
|
||||
|
||||
defineEmits(['close']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot name="header">
|
||||
<div class="flex justify-end mb7">
|
||||
<slot name="close">
|
||||
<icon-hover @click="$emit('close')">
|
||||
<icon-close />
|
||||
</icon-hover>
|
||||
</slot>
|
||||
</div>
|
||||
</slot>
|
||||
<slot>
|
||||
<div class="flex flex-col text-center">
|
||||
<div v-if="title" class="mb4 text-lg font-600">{{ title }}</div>
|
||||
<template v-else />
|
||||
<component :is="content" v-if="typeof content === 'function'" />
|
||||
<div v-else>{{ content }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
114
src/components/_base/navbar/index.vue
Normal file
114
src/components/_base/navbar/index.vue
Normal file
@ -0,0 +1,114 @@
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/stores';
|
||||
import { IconExport, IconFile, IconCaretDown } from '@arco-design/web-vue/es/icon';
|
||||
import { fetchMenusTree } from '@/api/all';
|
||||
const lists = ref([]);
|
||||
const getMenus = async () => {
|
||||
const res = await fetchMenusTree();
|
||||
lists.value = res;
|
||||
};
|
||||
onMounted(() => {
|
||||
getMenus();
|
||||
});
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
|
||||
const avatar = computed(
|
||||
() => '//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/dfdba5317c0c20ce20e64fac803d52bc.svg~tplv-49unhts6dw-image.image',
|
||||
);
|
||||
const topMenu = computed(() => appStore.topMenu && appStore.menu);
|
||||
const toggleDrawerMenu = inject('toggleDrawerMenu') as () => void;
|
||||
|
||||
function setServerMenu() {
|
||||
appStore.fetchServerMenuConfig();
|
||||
console.log(appStore.serverMenu);
|
||||
}
|
||||
const handleSelect = (index: any) => {
|
||||
console.log(index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<div class="left-side">
|
||||
<a-space>
|
||||
<img src="@/assets/LOGO.svg" alt="" />
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="center-side">
|
||||
<div class="menu-demo">
|
||||
<a-menu mode="horizontal" :default-selected-keys="['1']">
|
||||
<a-menu-item :key="'1'">
|
||||
<view>工作台</view>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-for="(item, index) in lists" :key="index + 2">
|
||||
<a-dropdown @select="handleSelect" :popup-max-height="false">
|
||||
<a-button>{{ item.name }}<icon-caret-down /></a-button>
|
||||
<template #content>
|
||||
<a-doption v-for="(child, index) in item.children" :key="index">{{ child.name }}</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="right-side">
|
||||
<li>
|
||||
<a-dropdown trigger="click">
|
||||
<a-avatar class="cursor-pointer" :size="32">
|
||||
<img alt="avatar" :src="avatar" />
|
||||
</a-avatar>
|
||||
</a-dropdown>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
background-color: var(--color-bg-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.left-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.center-side {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
margin-left: 40px;
|
||||
}
|
||||
.cneter-tip {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.menu-demo {
|
||||
flex: 1;
|
||||
}
|
||||
.right-side {
|
||||
display: flex;
|
||||
padding-right: 20px;
|
||||
list-style: none;
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: var(--color-text-1);
|
||||
text-decoration: none;
|
||||
}
|
||||
.nav-btn {
|
||||
border-color: rgb(var(--gray-2));
|
||||
color: rgb(var(--gray-8));
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
83
src/components/_base/tab-bar/index.vue
Normal file
83
src/components/_base/tab-bar/index.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
import { listenerRouteChange, removeRouteListener } from '@/utils/route-listener';
|
||||
import { useAppStore, useTabBarStore } from '@/stores';
|
||||
import TabItem from './tab-item.vue';
|
||||
const appStore = useAppStore();
|
||||
const tabBarStore = useTabBarStore();
|
||||
const affixRef = ref();
|
||||
const tagList = computed(() => {
|
||||
return tabBarStore.getTabList;
|
||||
});
|
||||
const offsetTop = computed(() => {
|
||||
return appStore.navbar ? 60 : 0;
|
||||
});
|
||||
watch(
|
||||
() => appStore.navbar,
|
||||
() => {
|
||||
affixRef.value.updatePosition();
|
||||
},
|
||||
);
|
||||
listenerRouteChange((route: RouteLocationNormalized) => {
|
||||
if (!route.meta.noAffix && !tagList.value.some((tag) => tag.fullPath === route.fullPath)) {
|
||||
tabBarStore.updateTabList(route);
|
||||
}
|
||||
}, true);
|
||||
onUnmounted(() => {
|
||||
removeRouteListener();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tab-bar-container">
|
||||
<a-affix ref="affixRef" :offset-top="offsetTop">
|
||||
<div class="tab-bar-box">
|
||||
<div class="tab-bar-scroll">
|
||||
<div class="tags-wrap">
|
||||
<tab-item v-for="(tag, index) in tagList" :key="tag.fullPath" :index="index" :item-data="tag" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag-bar-operation"></div>
|
||||
</div>
|
||||
</a-affix>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tab-bar-container {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-2);
|
||||
.tab-bar-box {
|
||||
display: flex;
|
||||
padding: 0 0 0 20px;
|
||||
background-color: var(--color-bg-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
.tab-bar-scroll {
|
||||
height: 32px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
.tags-wrap {
|
||||
padding: 4px 0;
|
||||
height: 48px;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
:deep(.arco-tag) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: 6px;
|
||||
cursor: pointer;
|
||||
&:first-child {
|
||||
.arco-tag-close-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tag-bar-operation {
|
||||
width: 100px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
177
src/components/_base/tab-bar/tab-item.vue
Normal file
177
src/components/_base/tab-bar/tab-item.vue
Normal file
@ -0,0 +1,177 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import type { TagProps } from '@/stores/modules/tab-bar/types';
|
||||
|
||||
import { useTabBarStore } from '@/stores';
|
||||
import { DEFAULT_ROUTE_NAME, REDIRECT_ROUTE_NAME } from '@/router/constants';
|
||||
|
||||
const props = defineProps({
|
||||
itemData: {
|
||||
type: Object as PropType<TagProps>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line no-shadow
|
||||
enum Eaction {
|
||||
reload = 'reload',
|
||||
current = 'current',
|
||||
left = 'left',
|
||||
right = 'right',
|
||||
others = 'others',
|
||||
all = 'all',
|
||||
}
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const tabBarStore = useTabBarStore();
|
||||
const goto = (tag: TagProps) => {
|
||||
router.push({ ...tag });
|
||||
};
|
||||
const tagList = computed(() => {
|
||||
return tabBarStore.getTabList;
|
||||
});
|
||||
const disabledReload = computed(() => {
|
||||
return props.itemData.fullPath !== route.fullPath;
|
||||
});
|
||||
const disabledCurrent = computed(() => {
|
||||
return props.index === 0;
|
||||
});
|
||||
const disabledLeft = computed(() => {
|
||||
return [0, 1].includes(props.index);
|
||||
});
|
||||
const disabledRight = computed(() => {
|
||||
return props.index === tagList.value.length - 1;
|
||||
});
|
||||
const tagClose = (tag: TagProps, idx: number) => {
|
||||
tabBarStore.deleteTag(idx, tag);
|
||||
if (props.itemData.fullPath === route.fullPath) {
|
||||
const latest = tagList.value[idx - 1]; // 获取队列的前一个tab
|
||||
router.push({ name: latest.name });
|
||||
}
|
||||
};
|
||||
const findCurrentRouteIndex = () => {
|
||||
return tagList.value.findIndex((el) => el.fullPath === route.fullPath);
|
||||
};
|
||||
const actionSelect = async (value: any) => {
|
||||
const { itemData, index } = props;
|
||||
const copyTagList = [...tagList.value];
|
||||
if (value === Eaction.current) {
|
||||
tagClose(itemData, index);
|
||||
} else if (value === Eaction.left) {
|
||||
const currentRouteIdx = findCurrentRouteIndex();
|
||||
copyTagList.splice(1, props.index - 1);
|
||||
tabBarStore.freshTabList(copyTagList);
|
||||
if (currentRouteIdx < index) {
|
||||
router.push({ name: itemData.name });
|
||||
}
|
||||
} else if (value === Eaction.right) {
|
||||
const currentRouteIdx = findCurrentRouteIndex();
|
||||
copyTagList.splice(props.index + 1);
|
||||
tabBarStore.freshTabList(copyTagList);
|
||||
if (currentRouteIdx > index) {
|
||||
router.push({ name: itemData.name });
|
||||
}
|
||||
} else if (value === Eaction.others) {
|
||||
const filterList = tagList.value.filter((el, idx) => {
|
||||
return idx === 0 || idx === props.index;
|
||||
});
|
||||
tabBarStore.freshTabList(filterList);
|
||||
router.push({ name: itemData.name });
|
||||
} else if (value === Eaction.reload) {
|
||||
tabBarStore.deleteCache(itemData);
|
||||
await router.push({
|
||||
name: REDIRECT_ROUTE_NAME,
|
||||
params: {
|
||||
path: route.fullPath,
|
||||
},
|
||||
});
|
||||
tabBarStore.addCache(itemData.name);
|
||||
} else {
|
||||
tabBarStore.resetTabList();
|
||||
router.push({ name: DEFAULT_ROUTE_NAME });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-dropdown trigger="contextMenu" :popup-max-height="false" @select="actionSelect">
|
||||
<span
|
||||
:class="[
|
||||
'arco-tag arco-tag-size-medium arco-tag-checked',
|
||||
{ 'link-activated': itemData.fullPath === $route.fullPath },
|
||||
]"
|
||||
@click="goto(itemData)"
|
||||
>
|
||||
<span class="tag-link">{{ itemData.title }}</span>
|
||||
<span
|
||||
class="arco-icon-hover arco-tag-icon-hover arco-icon-hover-size-medium arco-tag-close-btn"
|
||||
@click.stop="tagClose(itemData, index)"
|
||||
>
|
||||
<icon-close />
|
||||
</span>
|
||||
</span>
|
||||
<template #content>
|
||||
<a-doption :disabled="disabledReload" :value="Eaction.reload">
|
||||
<icon-refresh />
|
||||
<span>重新加载</span>
|
||||
</a-doption>
|
||||
<a-doption class="sperate-line" :disabled="disabledCurrent" :value="Eaction.current">
|
||||
<icon-close />
|
||||
<span>关闭当前标签页</span>
|
||||
</a-doption>
|
||||
<a-doption :disabled="disabledLeft" :value="Eaction.left">
|
||||
<icon-to-left />
|
||||
<span>关闭左侧标签页</span>
|
||||
</a-doption>
|
||||
<a-doption class="sperate-line" :disabled="disabledRight" :value="Eaction.right">
|
||||
<icon-to-right />
|
||||
<span>关闭右侧标签页</span>
|
||||
</a-doption>
|
||||
<a-doption :value="Eaction.others">
|
||||
<icon-swap />
|
||||
<span>关闭其它标签页</span>
|
||||
</a-doption>
|
||||
<a-doption :value="Eaction.all">
|
||||
<icon-folder-delete />
|
||||
<span>关闭全部标签页</span>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tag-link {
|
||||
color: var(--color-text-2);
|
||||
text-decoration: none;
|
||||
}
|
||||
.link-activated {
|
||||
color: rgb(var(--link-6));
|
||||
.tag-link {
|
||||
color: rgb(var(--link-6));
|
||||
}
|
||||
& + .arco-tag-close-btn {
|
||||
color: rgb(var(--link-6));
|
||||
}
|
||||
}
|
||||
:deep(.arco-dropdown-option-content) {
|
||||
span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.arco-dropdown-open {
|
||||
.tag-link {
|
||||
color: rgb(var(--danger-6));
|
||||
}
|
||||
.arco-tag-close-btn {
|
||||
color: rgb(var(--danger-6));
|
||||
}
|
||||
}
|
||||
.sperate-line {
|
||||
border-bottom: 1px solid var(--color-neutral-3);
|
||||
}
|
||||
</style>
|
||||
70
src/components/confirm-button/index.vue
Normal file
70
src/components/confirm-button/index.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<!--
|
||||
* @Author: 田鑫
|
||||
* @Date: 2023-02-16 11:58:01
|
||||
* @LastEditors: 田鑫
|
||||
* @LastEditTime: 2023-02-16 16:56:27
|
||||
* @Description: 二次确认框
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-popconfirm
|
||||
:content="content"
|
||||
:position="position"
|
||||
:ok-text="okText"
|
||||
:cancel-text="cancelText"
|
||||
:type="popupType"
|
||||
@ok="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<slot></slot>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue-demi';
|
||||
|
||||
type Position = 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'left' | 'lt' | 'lb' | 'right' | 'rt' | 'rb';
|
||||
|
||||
type PopupType = 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
default: '是否确认?',
|
||||
},
|
||||
position: {
|
||||
type: String as PropType<Position>,
|
||||
default: 'top',
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default: '确定',
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消',
|
||||
},
|
||||
popupType: {
|
||||
type: String as PropType<PopupType>,
|
||||
default: 'info',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['confirmEmit', 'cancelEmit']);
|
||||
|
||||
/**
|
||||
* 确定事件
|
||||
*/
|
||||
function handleConfirm() {
|
||||
emit('confirmEmit');
|
||||
}
|
||||
/**
|
||||
* 确定事件
|
||||
*/
|
||||
function handleCancel() {
|
||||
emit('cancelEmit');
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
5
src/components/wyg-form/README.md
Normal file
5
src/components/wyg-form/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# 动态配置form表单
|
||||
示例见 views/components/form
|
||||
参数 fieldList:配置项,包括arco.design Form.Item和Input、Select以及自定义属性component等
|
||||
参数 model: 传默认值
|
||||
通过ref获取实例,调用子组件实例updateFieldsList方法更新配置项,调用setModel方法更新数据,调用setForm方法更新Form属性,自定义事件change处理逻辑
|
||||
40
src/components/wyg-form/constants.ts
Normal file
40
src/components/wyg-form/constants.ts
Normal file
@ -0,0 +1,40 @@
|
||||
// form.item的属性名称集合
|
||||
export const formItemKeys = [
|
||||
'field',
|
||||
'label',
|
||||
'tooltip',
|
||||
'showColon',
|
||||
'noStyle',
|
||||
'disabled',
|
||||
'help',
|
||||
'extra',
|
||||
'required',
|
||||
'asteriskPosition',
|
||||
'rules',
|
||||
'validateStatus',
|
||||
'validateTrigger',
|
||||
'wrapperColProps',
|
||||
'hideLabel',
|
||||
'hideAsterisk',
|
||||
'labelColStyle',
|
||||
'wrapperColStyle',
|
||||
'rowProps',
|
||||
'rowClass',
|
||||
'contentClass',
|
||||
'contentFlex',
|
||||
'labelColFlex',
|
||||
'feedback',
|
||||
'labelComponent',
|
||||
'labelAttrs',
|
||||
];
|
||||
// 自定义属性名称集合
|
||||
export const customKeys = ['component', 'lists'];
|
||||
// 响应式栅格默认配置
|
||||
export const COL_PROPS = {
|
||||
xs: 12,
|
||||
sm: 12,
|
||||
md: 8,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
xxl: 6,
|
||||
};
|
||||
271
src/components/wyg-form/index.vue
Normal file
271
src/components/wyg-form/index.vue
Normal file
@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<slot name="header"></slot>
|
||||
<a-form v-bind="_options" ref="formRef" :model="model" @submit.prevent>
|
||||
<a-row v-bind="rowProps" :gutter="20">
|
||||
<template
|
||||
v-for="{ field, component, formItemProps, componentProps, lists, colProps } in newFieldList"
|
||||
:key="field"
|
||||
>
|
||||
<!-- 单选框 -->
|
||||
<a-col v-if="component === 'radio'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-radio-group v-bind="componentProps" v-model="model[field]">
|
||||
<a-radio v-for="val in lists" :key="val['value']" :label="val['value']" size="large">
|
||||
{{ val['label'] }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 复选框 -->
|
||||
<a-col v-if="component === 'checkbox'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-checkbox-group v-bind="componentProps" v-model="model[field]">
|
||||
<a-checkbox v-for="c in lists" :key="c['value']" :label="c['value']">{{ c['label'] }}</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 下拉框 -->
|
||||
<a-col v-if="component === 'select'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-select v-bind="componentProps" v-model="model[field]">
|
||||
<a-option v-for="s in lists" :key="s['value']" :label="s['label']" :value="s['value']" />
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 文本域 -->
|
||||
<a-col v-if="component === 'textarea'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-textarea v-bind="componentProps" v-model="model[field]" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 时间选择器 -->
|
||||
<a-col v-if="component === 'time'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-time-picker v-bind="componentProps" v-model="model[field]" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 日期选择器 -->
|
||||
<a-col v-if="component === 'date'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-date-picker v-bind="componentProps" v-model="model[field]" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 日期范围选择器 -->
|
||||
<a-col v-if="component === 'rangeDate'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-range-picker v-bind="componentProps" v-model="model[field]" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 级联选择器 -->
|
||||
<a-col v-if="component === 'cascader'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-cascader v-bind="componentProps" v-model="model[field]" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 数字输入框 -->
|
||||
<a-col v-if="component === 'inputNumber'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-input-number v-bind="componentProps" v-model="model[field]" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 输入框 -->
|
||||
<a-col v-if="component === 'input'" v-bind="colProps">
|
||||
<a-form-item v-bind="formItemProps">
|
||||
<a-input v-bind="componentProps" v-model="model[field]" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 标题模块 -->
|
||||
<a-col v-if="component === 'title'" :span="24">
|
||||
<div class="title">
|
||||
<div class="bar"></div>
|
||||
<h4 class="text">{{ formItemProps.label }}</h4>
|
||||
</div>
|
||||
</a-col>
|
||||
<!-- 自定义插槽slot -->
|
||||
<a-col v-if="component === 'slot'" :span="24">
|
||||
<slot :name="field"></slot>
|
||||
</a-col>
|
||||
</template>
|
||||
<a-col :span="24">
|
||||
<a-form-item>
|
||||
<slot name="buttons" :model="model" :formRef="formRef">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="onSubmit(formRef)">{{ _options.submitButtonText }}</a-button>
|
||||
<a-button v-if="_options.showResetButton" @click="resetForm(formRef)">
|
||||
{{ _options.resetButtonText }}
|
||||
</a-button>
|
||||
<a-button v-if="_options.showCancelButton" @click="emit('cancel')">
|
||||
{{ _options.cancelButtonText }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</slot>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, RowProps, ValidatedError } from '@arco-design/web-vue';
|
||||
import type { ComputedRef } from 'vue';
|
||||
import type { Form } from './interface';
|
||||
import { formItemKeys, customKeys, COL_PROPS } from './constants';
|
||||
import { changeFormList } from './utils';
|
||||
import type { FieldData } from '@arco-design/web-vue/es/form/interface';
|
||||
// 父组件传递的值
|
||||
interface Props {
|
||||
fieldList: Form.FieldItem[];
|
||||
model?: Record<string, any>;
|
||||
options?: Form.Options;
|
||||
rowProps?: RowProps;
|
||||
}
|
||||
interface EmitEvent {
|
||||
(e: 'submit' | 'change', params: any): void;
|
||||
(e: 'reset' | 'cancel'): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<EmitEvent>();
|
||||
// 表单的数据
|
||||
let model = ref<Record<string, any>>({});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
// 初始化处理Form组件属性options
|
||||
const _options = ref<Record<string, any>>({});
|
||||
const initOptions = () => {
|
||||
const option = {
|
||||
layout: 'vertical',
|
||||
disabled: false,
|
||||
submitButtonText: '提交',
|
||||
resetButtonText: '重置',
|
||||
cancelButtonText: '取消',
|
||||
showResetButton: true,
|
||||
};
|
||||
Object.assign(option, props?.options);
|
||||
_options.value = option;
|
||||
};
|
||||
initOptions();
|
||||
// 初始化处理model
|
||||
const initFormModel = () => {
|
||||
props.fieldList.forEach((item: Form.FieldItem) => {
|
||||
// 如果类型为checkbox,默认值需要设置一个空数组
|
||||
const value = item.component === 'checkbox' ? [] : '';
|
||||
const { field, component } = item;
|
||||
if (component !== 'slot' && component !== 'title') {
|
||||
model.value[item.field] = props?.model?.[field] || value;
|
||||
}
|
||||
});
|
||||
};
|
||||
initFormModel();
|
||||
// 初始化处理fieldList
|
||||
const newFieldList: any = ref(null);
|
||||
const initFieldList = () => {
|
||||
const list = props?.fieldList.map((item: Form.FieldItem) => {
|
||||
const customProps = pick(item, customKeys);
|
||||
const formItemProps = pick(item, formItemKeys);
|
||||
const componentProps = omit(item, [...formItemKeys, ...customKeys, 'field', 'colProps']);
|
||||
const { colProps = {}, field, placeholder, component = 'input', label } = item;
|
||||
componentProps.onChange = (val: any) => onChange(field, val);
|
||||
const newColProps = {
|
||||
...colProps,
|
||||
...COL_PROPS,
|
||||
};
|
||||
const obj = {
|
||||
field,
|
||||
colProps: newColProps,
|
||||
...customProps,
|
||||
formItemProps,
|
||||
componentProps,
|
||||
};
|
||||
if ((component === 'input' || component === 'textarea') && !placeholder) {
|
||||
componentProps.placeholder = `请输入${label}`;
|
||||
}
|
||||
if (component === 'select' && !placeholder) {
|
||||
componentProps.placeholder = `请选择${label}`;
|
||||
}
|
||||
if (component === 'rangeDate') {
|
||||
componentProps.value = [null, null];
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
newFieldList.value = list;
|
||||
return list;
|
||||
};
|
||||
initFieldList();
|
||||
// 提交
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
let flag = false;
|
||||
await formEl.validate((errors: undefined | Record<string, ValidatedError>) => {
|
||||
if (!errors) {
|
||||
emit('submit', model.value);
|
||||
flag = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return (flag && model.value) || null;
|
||||
};
|
||||
// 提交--父组件调用
|
||||
const submit = () => {
|
||||
return onSubmit(formRef.value);
|
||||
};
|
||||
// 重置
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
};
|
||||
// 表单变化
|
||||
const onChange = (key: string, val: any) => {
|
||||
emit('change', { key, val });
|
||||
};
|
||||
// 设置
|
||||
const setModel = (data: Record<string, FieldData>) => {
|
||||
const newData = {
|
||||
...model.value,
|
||||
...data,
|
||||
};
|
||||
model.value = newData;
|
||||
};
|
||||
// 设置Form
|
||||
const setForm = (data: Form.Options) => {
|
||||
const options = {
|
||||
..._options.value,
|
||||
...data,
|
||||
};
|
||||
_options.value = options;
|
||||
};
|
||||
// 更新配置项
|
||||
const updateFieldsList = (updateList: any[]) => {
|
||||
const list = changeFormList(newFieldList.value, updateList);
|
||||
newFieldList.value = list;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
submit,
|
||||
setModel,
|
||||
setForm,
|
||||
updateFieldsList,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-picker) {
|
||||
width: 100%;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.bar {
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
border-radius: 1px;
|
||||
margin-right: 8px;
|
||||
background-color: rgb(var(--primary-6));
|
||||
}
|
||||
.text {
|
||||
font-size: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
src/components/wyg-form/interface.d.ts
vendored
Normal file
95
src/components/wyg-form/interface.d.ts
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
import type { FieldRule } from '@arco-design/web-vue/es/form/interface';
|
||||
import { Size, ColProps, CascaderOption, InputProps } from '@arco-design/web-vue';
|
||||
console.log(InputProps, 'InputProps=====');
|
||||
|
||||
export namespace Form {
|
||||
/** 表单项自身Props */
|
||||
interface FormItem<T = string> {
|
||||
field: string;
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
showColon?: boolean;
|
||||
noStyle?: boolean;
|
||||
disabled?: boolean;
|
||||
help?: string;
|
||||
extra?: string;
|
||||
required?: boolean;
|
||||
asteriskPosition?: 'start' | 'end';
|
||||
rules?: FieldRule | FieldRule[];
|
||||
validateStatus?: 'success' | 'warning' | 'error' | 'validating';
|
||||
validateTrigger?: 'change' | 'input' | 'focus' | 'blur';
|
||||
labelColProps?: object;
|
||||
wrapperColProps?: object;
|
||||
hideLabel?: boolean;
|
||||
hideAsterisk?: boolean;
|
||||
labelColStyle?: object;
|
||||
wrapperColStyle?: object;
|
||||
rowProps?: object;
|
||||
rowClass?: string | Array<T> | object;
|
||||
contentClass?: string | Array<T> | object;
|
||||
contentFlex?: boolean;
|
||||
labelColFlex?: number | string;
|
||||
feedback?: boolean;
|
||||
labelComponent?: string;
|
||||
labelAttrs?: object;
|
||||
}
|
||||
// 当前 fieldItem 的类型 默认值'input'
|
||||
type ComponentType =
|
||||
| 'input'
|
||||
| 'textarea'
|
||||
| 'radio'
|
||||
| 'checkbox'
|
||||
| 'select'
|
||||
| 'time'
|
||||
| 'date'
|
||||
| 'rangeDate'
|
||||
| 'inputNumber'
|
||||
| 'cascader'
|
||||
| 'title'
|
||||
| 'slot';
|
||||
/** 自定义Props */
|
||||
interface CustomProps {
|
||||
component?: ComponentType;
|
||||
lists?: object; // 如果 type='checkbox' / 'radio' / 'select'时,需传入此配置项。格式参考FieldItemOptions配置项
|
||||
}
|
||||
/** Input、Select组件等的Props */
|
||||
interface ComponentProps {
|
||||
placeholder?: string; // 输入框占位文本
|
||||
readonly?: boolean; // 是否只读 false
|
||||
allowClear?: boolean; // 是否可清空 false
|
||||
onChange?: Function;
|
||||
options?: CascaderOption[];
|
||||
}
|
||||
/** 每一项配置项的属性 */
|
||||
interface FieldItem extends FormItem, CustomProps, ComponentProps {
|
||||
colProps?: ColProps;
|
||||
}
|
||||
/** 处理后的配置项属性 */
|
||||
interface NewFieldItem extends CustomProps {
|
||||
formItemProps: FormItem;
|
||||
componentProps: ComponentProps;
|
||||
field: string;
|
||||
colProps?: ColProps;
|
||||
}
|
||||
interface FieldItemOptions {
|
||||
label: string | number;
|
||||
value: string | number;
|
||||
}
|
||||
/** 表单Form自身Props */
|
||||
interface Options {
|
||||
layout?: 'horizontal' | 'vertical' | 'inline'; // 表单的布局方式,包括水平、垂直、多列
|
||||
size?: Size; // 用于控制该表单内组件的尺寸
|
||||
labelColProps?: object; // 标签元素布局选项。参数同 <col> 组件一致,默认值span: 5, offset: 0
|
||||
wrapperColProps?: object; // 表单控件布局选项。参数同 <col> 组件一致,默认值span: 19, offset: 0
|
||||
labelAlign?: 'left' | 'right'; // 标签的对齐方向,默认值'right'
|
||||
disabled?: boolean; // 是否禁用表单
|
||||
rules?: Record<string, FieldRule | FieldRule[]>; // 表单项校验规则
|
||||
autoLabelWidth?: boolean; // 是否开启自动标签宽度,仅在 layout="horizontal" 下生效。默认值false
|
||||
|
||||
showResetButton?: boolean; // 是否展示重置按钮
|
||||
showCancelButton?: boolean; // 是否展示取消按钮
|
||||
submitButtonText?: string;
|
||||
resetButtonText?: string;
|
||||
cancelButtonText?: string;
|
||||
}
|
||||
}
|
||||
25
src/components/wyg-form/utils.ts
Normal file
25
src/components/wyg-form/utils.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { formItemKeys, customKeys, COL_PROPS } from './constants';
|
||||
import type { Form } from './interface';
|
||||
// fieldList更新
|
||||
export const changeFormList = (formList: Form.NewFieldItem[], updateList: Form.FieldItem[]): Form.NewFieldItem[] => {
|
||||
let list: any = formList;
|
||||
list.forEach((item: any, index: string | number) => {
|
||||
updateList.forEach((ele: any) => {
|
||||
if (item.field === ele.field) {
|
||||
list[index] = { ...item, ...ele };
|
||||
const keys: string[] = Object.keys(ele);
|
||||
keys.forEach((key: string) => {
|
||||
const val = ele[key];
|
||||
if (formItemKeys.includes(key)) {
|
||||
list[index].formItemProps[key] = val;
|
||||
} else if (customKeys.includes(key) || key === 'colProps') {
|
||||
list[index][key] = val;
|
||||
} else {
|
||||
list[index].componentProps[key] = val;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return list;
|
||||
};
|
||||
73
src/components/wyg-table/index.vue
Normal file
73
src/components/wyg-table/index.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<!--
|
||||
* @Author: 田鑫
|
||||
* @Date: 2023-02-16 14:40:38
|
||||
* @LastEditors: 田鑫
|
||||
* @LastEditTime: 2023-02-16 16:37:52
|
||||
* @Description: table公用封装
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-table
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="tableData"
|
||||
:bordered="{ cell: borderCell }"
|
||||
:pagination="setPagination"
|
||||
page-position="br"
|
||||
v-bind="propsRes"
|
||||
v-on="propsEvent"
|
||||
>
|
||||
<template #columns>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PaginationProps, TableData } from '@arco-design/web-vue';
|
||||
import type { PropType } from 'vue-demi';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
|
||||
type Size = 'mini' | 'small' | 'medium' | 'large';
|
||||
|
||||
const props = defineProps({
|
||||
size: {
|
||||
type: String as PropType<Size>,
|
||||
default: 'large',
|
||||
},
|
||||
tableData: {
|
||||
type: Array as PropType<TableData[]>,
|
||||
default: () => [],
|
||||
},
|
||||
borderCell: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
pagination: {
|
||||
type: Object as PropType<PaginationProps>,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const loading = ref(false);
|
||||
|
||||
const setPagination = computed(() => {
|
||||
const defaultPagination: PaginationProps = {
|
||||
showPageSize: true,
|
||||
showTotal: true,
|
||||
showMore: true,
|
||||
size: 'large',
|
||||
};
|
||||
return Object.assign(defaultPagination, props.pagination);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.tableData.length === 0) {
|
||||
loading.value = true;
|
||||
} else {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
Reference in New Issue
Block a user