feat: add useDesign

This commit is contained in:
vben
2020-12-07 21:17:24 +08:00
parent bd6b203fa9
commit 74e62cbc71
52 changed files with 260 additions and 385 deletions

View File

@@ -1,6 +1,10 @@
import AppLocalePickerLib from './src/AppLocalePicker.vue';
import AppLogoLib from './src/AppLogo.vue';
import AppLocalePicker from './src/AppLocalePicker.vue';
import AppLogo from './src/AppLogo.vue';
import AppProvider from './src/AppProvider.vue';
import { withInstall } from '../util';
export const AppLocalePicker = withInstall(AppLocalePickerLib);
export const AppLogo = withInstall(AppLogoLib);
withInstall(AppLocalePicker, AppLogo, AppProvider);
export { useAppProviderContext } from './src/useAppContext';
export { AppLocalePicker, AppLogo, AppProvider };

View File

@@ -69,8 +69,8 @@
});
</script>
<style lang="less">
.app-locale-picker-overlay {
<style lang="less" scoped>
:global(.app-locale-picker-overlay) {
.ant-dropdown-menu-item {
min-width: 160px;
}

View File

@@ -4,12 +4,14 @@
-->
<template>
<div
class="app-logo anticon"
:class="{ theme, 'collapsed-show-title': getCollapsedShowTitle }"
class="anticon"
:class="[prefixCls, theme, { 'collapsed-show-title': getCollapsedShowTitle }]"
@click="handleGoHome"
>
<img src="/@/assets/images/logo.png" />
<div class="app-logo__title ml-2 ellipsis" v-show="showTitle">{{ globSetting.title }}</div>
<div class="ml-2 ellipsis" :class="[`${prefixCls}__title`]" v-show="showTitle">
{{ globSetting.title }}
</div>
</div>
</template>
<script lang="ts">
@@ -23,6 +25,8 @@
import { propTypes } from '/@/utils/propTypes';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'AppLogo',
props: {
@@ -34,6 +38,8 @@
showTitle: propTypes.bool.def(true),
},
setup() {
const { prefixCls } = useDesign('app-logo');
const { getCollapsedShowTitle } = useMenuSetting();
const globSetting = useGlobSetting();
@@ -48,17 +54,19 @@
handleGoHome,
globSetting,
getCollapsedShowTitle,
prefixCls,
};
},
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-app-logo';
.app-logo {
.@{prefix-cls} {
display: flex;
align-items: center;
padding-left: 10px;
padding-left: 12px;
cursor: pointer;
&.collapsed-show-title {

View File

@@ -0,0 +1,24 @@
<template>
<slot />
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent, toRefs } from 'vue';
import { createAppProviderContext } from './useAppContext';
export default defineComponent({
name: 'AppProvider',
inheritAttrs: false,
props: {
prefixCls: {
type: String as PropType<string>,
default: 'vben',
},
},
setup(props) {
const { prefixCls } = toRefs(props);
createAppProviderContext({ prefixCls });
return {};
},
});
</script>

View File

@@ -0,0 +1,16 @@
import { InjectionKey, Ref } from 'vue';
import { createContext, useContext } from '/@/hooks/core/useContext';
export interface AppProviderContextProps {
prefixCls: Ref<string>;
}
const key: InjectionKey<AppProviderContextProps> = Symbol();
export function createAppProviderContext(context: AppProviderContextProps) {
return createContext<AppProviderContextProps>(context, key);
}
export function useAppProviderContext() {
return useContext<AppProviderContextProps>(key);
}

View File

@@ -1,5 +1,7 @@
import AuthorityLib from './src/index.vue';
import Authority from './src/index.vue';
import { withInstall } from '../util';
export const Authority = withInstall(AuthorityLib);
withInstall(Authority);
export { Authority };

View File

@@ -1,9 +1,9 @@
import BasicArrowLib from './src/BasicArrow.vue';
import BasicHelpLib from './src/BasicHelp.vue';
import BasicTitleLib from './src/BasicTitle.vue';
import BasicArrow from './src/BasicArrow.vue';
import BasicHelp from './src/BasicHelp.vue';
import BasicTitle from './src/BasicTitle.vue';
import { withInstall } from '../util';
export const BasicArrow = withInstall(BasicArrowLib);
export const BasicHelp = withInstall(BasicHelpLib);
export const BasicTitle = withInstall(BasicTitleLib);
withInstall(BasicArrow, BasicHelp, BasicTitle);
export { BasicArrow, BasicHelp, BasicTitle };

View File

@@ -1,4 +1,6 @@
import ButtonLib from './src/BasicButton.vue';
import Button from './src/BasicButton.vue';
import { withInstall } from '../util';
export const Button = withInstall(ButtonLib);
withInstall(Button);
export { Button };

View File

@@ -1,4 +1,6 @@
import ClickOutSideLib from './src/index.vue';
import ClickOutSide from './src/index.vue';
import { withInstall } from '../util';
export const ClickOutSide = withInstall(ClickOutSideLib);
withInstall(ClickOutSide);
export { ClickOutSide };

View File

@@ -1,10 +1,10 @@
import ScrollContainerLib from './src/ScrollContainer.vue';
import CollapseContainerLib from './src/collapse/CollapseContainer.vue';
import LazyContainerLib from './src/LazyContainer.vue';
import ScrollContainer from './src/ScrollContainer.vue';
import CollapseContainer from './src/collapse/CollapseContainer.vue';
import LazyContainer from './src/LazyContainer.vue';
import { withInstall } from '../util';
withInstall(ScrollContainer, CollapseContainer, LazyContainer);
export * from './src/types';
export const ScrollContainer = withInstall(ScrollContainerLib);
export const CollapseContainer = withInstall(CollapseContainerLib);
export const LazyContainer = withInstall(LazyContainerLib);
export { ScrollContainer, CollapseContainer, LazyContainer };

View File

@@ -1,6 +1,7 @@
// Transform vue-count-to to support vue3 version
import CountToLib from './src/index.vue';
import CountTo from './src/index.vue';
import { withInstall } from '../util';
export const CountTo = withInstall(CountToLib);
withInstall(CountTo);
export { CountTo };

View File

@@ -1,8 +1,9 @@
import DescriptionLib from './src/index';
import Description from './src/index';
import { withInstall } from '../util';
withInstall(Description);
export * from './src/types';
export { useDescription } from './src/useDescription';
export const Description = withInstall(DescriptionLib);
export { Description };

View File

@@ -1,6 +1,7 @@
import BasicDrawerLib from './src/BasicDrawer';
import BasicDrawer from './src/BasicDrawer';
import { withInstall } from '../util';
withInstall(BasicDrawer);
export * from './src/types';
export { useDrawer, useDrawerInner } from './src/useDrawer';
export const BasicDrawer = withInstall(BasicDrawerLib);
export { BasicDrawer };

View File

@@ -1,6 +1,7 @@
import DropdownLib from './src/Dropdown';
import Dropdown from './src/Dropdown';
import { withInstall } from '../util';
withInstall(Dropdown);
export * from './src/types';
export const Dropdown = withInstall(DropdownLib);
export { Dropdown };

View File

@@ -1,11 +1,12 @@
import ImportExcelLib from './src/ImportExcel.vue';
import ExportExcelModelLib from './src/ExportExcelModel.vue';
import ImportExcel from './src/ImportExcel.vue';
import ExportExcelModel from './src/ExportExcelModel.vue';
import { withInstall } from '../util';
withInstall(ImportExcel, ExportExcelModel);
export * from './src/types';
export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel';
export const ImportExcel = withInstall(ImportExcelLib);
export const ExportExcelModel = withInstall(ExportExcelModelLib);
export { ImportExcel, ExportExcelModel };

View File

@@ -1,10 +1,12 @@
import BasicFormLib from './src/BasicForm.vue';
import BasicForm from './src/BasicForm.vue';
import { withInstall } from '../util';
withInstall(BasicForm);
export * from './src/types/form';
export * from './src/types/formItem';
export { useComponentRegister } from './src/hooks/useComponentRegister';
export { useForm } from './src/hooks/useForm';
export const BasicForm = withInstall(BasicFormLib);
export { BasicForm };

View File

@@ -1,8 +1,9 @@
import './src/indicator';
import LoadingLib from './src/index.vue';
import Loading from './src/index.vue';
import { withInstall } from '../util';
withInstall(Loading);
export { useLoading } from './src/useLoading';
export { createLoading } from './src/createLoading';
export const Loading = withInstall(LoadingLib);
export { Loading };

View File

@@ -1,7 +1,9 @@
import MarkDownLib from './src/index.vue';
import MarkDown from './src/index.vue';
import { withInstall } from '../util';
withInstall(MarkDown);
export * from './src/types';
export const MarkDown = withInstall(MarkDownLib);
export { MarkDown };

View File

@@ -1,4 +1,5 @@
import BasicMenuLib from './src/BasicMenu';
import BasicMenu from './src/BasicMenu';
import { withInstall } from '../util';
export const BasicMenu = withInstall(BasicMenuLib);
withInstall(BasicMenu);
export { BasicMenu };

View File

@@ -15,7 +15,6 @@ import {
ComputedRef,
} from 'vue';
import { Menu } from 'ant-design-vue';
import SearchInput from './SearchInput.vue';
import MenuContent from './MenuContent';
// import { ScrollContainer } from '/@/components/Container';
@@ -24,8 +23,7 @@ import { ThemeEnum } from '/@/enums/appEnum';
import { appStore } from '/@/store/modules/app';
import { useSearchInput } from './hooks/useSearchInput';
import { useOpenKeys } from './hooks/useOpenKeys';
import { useOpenKeys } from './useOpenKeys';
import { useRouter } from 'vue-router';
import { isFunction } from '/@/utils/is';
@@ -43,51 +41,39 @@ export default defineComponent({
emits: ['menuClick'],
setup(props, { slots, emit }) {
const currentParentPath = ref('');
const menuState = reactive<MenuState>({
defaultSelectedKeys: [],
mode: props.mode,
theme: computed(() => props.theme) as ComputedRef<ThemeEnum>,
openKeys: [],
searchValue: '',
selectedKeys: [],
collapsedOpenKeys: [],
});
const { getCollapsed } = useMenuSetting();
const { currentRoute } = useRouter();
const { items, flatItems, isAppMenu, mode, accordion } = toRefs(props);
const { handleInputChange, handleInputClick } = useSearchInput({
flatMenusRef: flatItems,
emit: emit,
menuState,
handleMenuChange,
});
const { items, flatItems, mode, accordion } = toRefs(props);
const { handleOpenChange, resetKeys, setOpenKeys } = useOpenKeys(
menuState,
items,
flatItems,
isAppMenu,
mode,
accordion
);
const getOpenKeys = computed(() => {
if (props.isAppMenu) {
return unref(getCollapsed) ? menuState.collapsedOpenKeys : menuState.openKeys;
}
return menuState.openKeys;
return unref(getCollapsed) ? menuState.collapsedOpenKeys : menuState.openKeys;
});
// menu外层样式
const getMenuWrapStyle = computed((): any => {
const { showLogo, search } = props;
const { showLogo } = props;
let offset = 0;
if (search) {
offset += 54;
}
if (showLogo) {
offset += 46;
}
@@ -110,7 +96,7 @@ export default defineComponent({
cls.push('basic-menu__sidebar-hor');
}
if (!props.isHorizontal && props.isAppMenu && appStore.getProjectConfig.menuSetting.split) {
if (!props.isHorizontal && appStore.getProjectConfig.menuSetting.split) {
cls.push('basic-menu__second');
}
return cls;
@@ -197,7 +183,6 @@ export default defineComponent({
level={index}
isHorizontal={props.isHorizontal}
showTitle={unref(showTitle)}
searchValue={menuState.searchValue}
/>,
]}
</Menu.Item>
@@ -212,7 +197,6 @@ export default defineComponent({
item={menu}
level={index}
isHorizontal={props.isHorizontal}
searchValue={menuState.searchValue}
/>,
],
default: () => renderMenuItem(menu.children, index + 1),
@@ -227,11 +211,9 @@ export default defineComponent({
const { selectedKeys, defaultSelectedKeys, mode, theme } = menuState;
const inlineCollapsedObj = isInline
? props.isAppMenu
? {
inlineCollapsed: unref(getCollapsed),
}
: { inlineCollapsed: props.inlineCollapsed }
? {
inlineCollapsed: unref(getCollapsed),
}
: {};
return (
<Menu
@@ -267,17 +249,8 @@ export default defineComponent({
) : (
<section class={[`basic-menu-wrap`, !unref(showTitle) && 'hide-title']}>
{getSlot(slots, 'header')}
<SearchInput
class={!props.search ? 'hidden' : ''}
theme={props.theme as ThemeEnum}
onChange={handleInputChange}
onClick={handleInputClick}
collapsed={unref(getCollapsed)}
/>
{/* <section style={unref(getMenuWrapStyle)}> */}
<section style={unref(getMenuWrapStyle)} class="basic-menu__content">
{/* <ScrollContainer>{() => renderMenu()}</ScrollContainer> */}
{renderMenu()}
</section>
</section>

View File

@@ -0,0 +1,11 @@
<template>
<div> </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
return {};
},
});
</script>

View File

@@ -8,11 +8,6 @@ import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({
name: 'MenuContent',
props: {
searchValue: {
type: String as PropType<string>,
default: '',
},
item: {
type: Object as PropType<MenuType>,
default: null,
@@ -35,11 +30,7 @@ export default defineComponent({
setup(props) {
const { t } = useI18n();
const getI18nName = computed(() => {
const { name } = props.item;
return t(name);
});
const getI18nName = computed(() => t(props.item?.name));
/**
* @description: 渲染图标
*/
@@ -71,28 +62,18 @@ export default defineComponent({
const { showTitle } = props;
const { icon } = props.item;
const name = unref(getI18nName);
const searchValue = props.searchValue || '';
const index = name.indexOf(searchValue);
const beforeStr = name.substr(0, index);
const afterStr = name.substr(index + searchValue.length);
const cls = showTitle ? ['show-title'] : ['basic-menu__name'];
return (
<>
{renderIcon(icon!)}
{index > -1 && searchValue ? (
<span class={cls}>
{beforeStr}
<span class={`basic-menu__keyword`}>{searchValue}</span>
{afterStr}
</span>
) : (
{
<span class={[cls]}>
{name}
{renderTag()}
</span>
)}
}
</>
);
};

View File

@@ -1,116 +0,0 @@
<template>
<section class="menu-search-input" @Click="handleClick" :class="searchClass">
<a-input-search
:placeholder="t('component.menu.search')"
class="menu-search-input__search"
allowClear
@change="handleChange"
/>
</section>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent, computed } from 'vue';
import { ThemeEnum } from '/@/enums/appEnum';
// hook
import { useDebounce } from '/@/hooks/core/useDebounce';
import { useI18n } from '/@/hooks/web/useI18n';
//
export default defineComponent({
name: 'BasicMenuSearchInput',
props: {
// Whether to expand, used in the left menu
collapsed: {
type: Boolean as PropType<boolean>,
default: true,
},
theme: {
type: String as PropType<ThemeEnum>,
},
},
setup(props, { emit }) {
const { t } = useI18n();
const [debounceEmitChange] = useDebounce(emitChange, 200);
function emitChange(value?: string): void {
emit('change', value);
}
function handleChange(e: ChangeEvent): void {
const { collapsed } = props;
if (collapsed) return;
debounceEmitChange(e.target.value);
}
function handleClick(): void {
emit('click');
}
const searchClass = computed(() => {
const cls: string[] = [];
cls.push(props.theme ? `menu-search-input__search--${props.theme}` : '');
cls.push(props.collapsed ? 'hide-search-icon' : '');
return cls;
});
return { handleClick, searchClass, handleChange, t };
},
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
// 输入框背景颜色 深
@input-dark-bg-color: #516085;
@icon-color: #c0c4cc;
.menu-search-input {
margin: 12px 8px;
&.hide-search-icon {
.ant-input,
.ant-input-suffix {
opacity: 0;
transition: all 0.5s;
}
}
&__search--dark {
.ant-input-affix-wrapper,
.ant-input {
.set-bg();
}
.ant-input-search-icon,
.ant-input-clear-icon {
color: rgba(255, 255, 255, 0.4) !important;
}
}
&__search--light {
.ant-input-affix-wrapper,
.ant-input {
color: @text-color-base;
background: #fff;
outline: none;
}
.ant-input-search-icon {
color: @icon-color;
}
}
}
.set-bg() {
color: #fff;
background: @sider-dark-lighten-1-bg-color;
border: 0;
outline: none;
}
.hide-outline() {
border: none;
outline: none;
box-shadow: none;
}
</style>

View File

@@ -1,58 +0,0 @@
import type { Menu as MenuType } from '/@/router/types';
import type { MenuState } from '../types';
import type { Ref } from 'vue';
import { isString } from '/@/utils/is';
import { unref } from 'vue';
import { es6Unique } from '/@/utils';
import { getAllParentPath } from '/@/router/helper/menuHelper';
interface UseSearchInputOptions {
menuState: MenuState;
flatMenusRef: Ref<MenuType[]>;
emit: EmitType;
handleMenuChange: Fn;
}
export function useSearchInput({
menuState,
flatMenusRef,
handleMenuChange,
emit,
}: UseSearchInputOptions) {
/**
* @description: 输入框搜索
*/
function handleInputChange(value?: string): void {
if (!isString(value)) {
value = (value as any).target.value;
}
if (!value) {
handleMenuChange && handleMenuChange();
}
menuState.searchValue = value || '';
if (!value) {
menuState.openKeys = [];
return;
}
const flatMenus = unref(flatMenusRef);
let openKeys: string[] = [];
for (const menu of flatMenus) {
const { name, path } = menu;
if (!name.includes(value)) {
continue;
}
openKeys = openKeys.concat(getAllParentPath(flatMenus, path));
}
openKeys = es6Unique(openKeys);
menuState.openKeys = openKeys;
}
// 搜索框点击
function handleInputClick(e: any): void {
emit('clickSearchInput', e);
}
return { handleInputChange, handleInputClick };
}

View File

@@ -20,11 +20,7 @@ export const basicProps = {
type: Boolean as PropType<boolean>,
default: false,
},
// 是否显示搜索框
search: {
type: Boolean as PropType<boolean>,
default: true,
},
// 最好是4 倍数
inlineIndent: {
type: Number as PropType<number>,
@@ -51,10 +47,7 @@ export const basicProps = {
type: Boolean as PropType<boolean>,
default: false,
},
isAppMenu: {
type: Boolean as PropType<boolean>,
default: true,
},
isHorizontal: {
type: Boolean as PropType<boolean>,
default: false,

View File

@@ -17,9 +17,6 @@ export interface MenuState {
// 展开数组
openKeys: string[];
// 搜索值
searchValue: string;
// 当前选中的菜单项 key 数组
selectedKeys: string[];

View File

@@ -1,6 +1,6 @@
import { MenuModeEnum } from '/@/enums/menuEnum';
import type { Menu as MenuType } from '/@/router/types';
import type { MenuState } from '../types';
import type { MenuState } from './types';
import type { Ref } from 'vue';
import { unref } from 'vue';
@@ -12,14 +12,11 @@ export function useOpenKeys(
menuState: MenuState,
menus: Ref<MenuType[]>,
flatMenusRef: Ref<MenuType[]>,
isAppMenu: Ref<boolean>,
mode: Ref<MenuModeEnum>,
accordion: Ref<boolean>
) {
const { getCollapsed } = useMenuSetting();
/**
* @description:
*/
function setOpenKeys(menu: MenuType) {
const flatMenus = unref(flatMenusRef);
if (!unref(accordion)) {
@@ -50,7 +47,7 @@ export function useOpenKeys(
rootSubMenuKeys.push(path);
}
}
if (!unref(getCollapsed) || !unref(isAppMenu)) {
if (!unref(getCollapsed)) {
const latestOpenKey = openKeys.find((key) => menuState.openKeys.indexOf(key) === -1);
if (rootSubMenuKeys.indexOf(latestOpenKey as string) === -1) {
menuState.openKeys = openKeys;

View File

@@ -1,8 +1,10 @@
import './src/index.less';
import BasicModalLib from './src/BasicModal';
import BasicModal from './src/BasicModal';
import { withInstall } from '../util';
withInstall(BasicModal);
export { useModalContext } from './src/useModalContext';
export { useModal, useModalInner } from './src/useModal';
export * from './src/types';
export const BasicModal = withInstall(BasicModalLib);
export { BasicModal };

View File

@@ -1,4 +1,5 @@
import PageFooterLib from './src/PageFooter.vue';
import PageFooter from './src/PageFooter.vue';
import { withInstall } from '../util';
export const PageFooter = withInstall(PageFooterLib);
withInstall(PageFooter);
export { PageFooter };

View File

@@ -1,4 +1,5 @@
import StrengthMeterLib from './src/index';
import StrengthMeter from './src/index';
import { withInstall } from '../util';
export const StrengthMeter = withInstall(StrengthMeterLib);
withInstall(StrengthMeter);
export { StrengthMeter };

View File

@@ -1,4 +1,5 @@
import TinymceLib from './src/Editor.vue';
import Tinymce from './src/Editor.vue';
import { withInstall } from '../util';
export const Tinymce = withInstall(TinymceLib);
withInstall(Tinymce);
export { Tinymce };

View File

@@ -1,8 +1,5 @@
import type { App } from 'vue';
import BasicUpload from './src/BasicUpload.vue';
import { withInstall } from '../util';
export default (app: App): void => {
app.component(BasicUpload.name, BasicUpload);
};
withInstall(BasicUpload);
export { BasicUpload };

View File

@@ -1,8 +1,9 @@
import BasicDragVerifyLib from './src/DragVerify';
import RotateDragVerifyLib from './src/ImgRotate';
import BasicDragVerify from './src/DragVerify';
import RotateDragVerify from './src/ImgRotate';
import { withInstall } from '../util';
withInstall(BasicDragVerify, RotateDragVerify);
export * from './src/types';
export const RotateDragVerify = withInstall(RotateDragVerifyLib);
export const BasicDragVerify = withInstall(BasicDragVerifyLib);
export { BasicDragVerify, RotateDragVerify };

View File

@@ -1,4 +1,5 @@
import VirtualScrollLib from './src/index';
import VirtualScroll from './src/index';
import { withInstall } from '../util';
export const VirtualScroll = withInstall(VirtualScrollLib);
withInstall(VirtualScroll);
export { VirtualScroll };

View File

@@ -1,12 +1,12 @@
import type { VNodeChild, Plugin } from 'vue';
import type { VNodeChild } from 'vue';
import type { App } from 'vue';
export function withInstall<T>(component: T) {
const comp = component as any;
comp.install = (app: App) => {
app.component(comp.displayName || comp.name, comp);
};
return comp as T & Plugin;
export function withInstall(...components: any[]) {
components.forEach((comp) => {
comp.install = (app: App) => {
app.component(comp.displayName || comp.name, comp);
};
});
}
export function convertToUnit(