mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 14:13:40 +08:00
perf(import): perf components import
This commit is contained in:
@@ -1,15 +1,14 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
import AppLogo from './src/AppLogo.vue';
|
import AppLogo from './src/AppLogo.vue';
|
||||||
|
import AppProvider from './src/AppProvider.vue';
|
||||||
|
import { withInstall } from '../util';
|
||||||
|
|
||||||
export const AppLocalePicker = createAsyncComponent(() => import('./src/AppLocalePicker.vue'));
|
export const AppLocalePicker = createAsyncComponent(() => import('./src/AppLocalePicker.vue'));
|
||||||
export const AppProvider = createAsyncComponent(() => import('./src/AppProvider.vue'));
|
|
||||||
export const AppSearch = createAsyncComponent(() => import('./src/search/AppSearch.vue'), {
|
export const AppSearch = createAsyncComponent(() => import('./src/search/AppSearch.vue'), {
|
||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
// export const AppLogo = createAsyncComponent(() => import('./src/AppLogo.vue'));
|
|
||||||
|
|
||||||
withInstall(AppLocalePicker, AppLogo, AppProvider, AppSearch);
|
|
||||||
|
|
||||||
export { useAppProviderContext } from './src/useAppContext';
|
export { useAppProviderContext } from './src/useAppContext';
|
||||||
export { AppLogo };
|
export { AppLogo, AppProvider };
|
||||||
|
|
||||||
|
withInstall(AppLogo, AppProvider);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { withInstall } from '../util';
|
import { withInstall } from '../util';
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
|
||||||
|
|
||||||
export const Authority = createAsyncComponent(() => import('./src/index.vue'));
|
import Authority from './src/index.vue';
|
||||||
|
|
||||||
withInstall(Authority);
|
withInstall(Authority);
|
||||||
|
|
||||||
|
export { Authority };
|
||||||
|
@@ -1,8 +1,5 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
export const BasicArrow = createAsyncComponent(() => import('./src/BasicArrow.vue'));
|
export const BasicArrow = createAsyncComponent(() => import('./src/BasicArrow.vue'));
|
||||||
export const BasicHelp = createAsyncComponent(() => import('./src/BasicHelp.vue'));
|
export const BasicHelp = createAsyncComponent(() => import('./src/BasicHelp.vue'));
|
||||||
export const BasicTitle = createAsyncComponent(() => import('./src/BasicTitle.vue'));
|
export const BasicTitle = createAsyncComponent(() => import('./src/BasicTitle.vue'));
|
||||||
|
|
||||||
withInstall(BasicArrow, BasicHelp, BasicTitle);
|
|
||||||
|
@@ -2,5 +2,4 @@ import Button from './src/BasicButton.vue';
|
|||||||
import { withInstall } from '../util';
|
import { withInstall } from '../util';
|
||||||
|
|
||||||
withInstall(Button);
|
withInstall(Button);
|
||||||
|
|
||||||
export { Button };
|
export { Button };
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { withInstall } from '../util';
|
import { withInstall } from '../util';
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
|
||||||
|
|
||||||
export const ClickOutSide = createAsyncComponent(() => import('./src/index.vue'));
|
import ClickOutSide from './src/index.vue';
|
||||||
|
|
||||||
withInstall(ClickOutSide);
|
withInstall(ClickOutSide);
|
||||||
|
|
||||||
|
export { ClickOutSide };
|
||||||
|
@@ -1,14 +1,8 @@
|
|||||||
import { withInstall } from '../util';
|
import { withInstall } from '../util';
|
||||||
import CollapseContainer from './src/collapse/CollapseContainer.vue';
|
import CollapseContainer from './src/collapse/CollapseContainer.vue';
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import ScrollContainer from './src/ScrollContainer.vue';
|
||||||
export const ScrollContainer = createAsyncComponent(() => import('./src/ScrollContainer.vue'));
|
import LazyContainer from './src/LazyContainer.vue';
|
||||||
|
|
||||||
// export const CollapseContainer = createAsyncComponent(
|
|
||||||
// () => import('./src/collapse/CollapseContainer.vue')
|
|
||||||
// );
|
|
||||||
export const LazyContainer = createAsyncComponent(() => import('./src/LazyContainer.vue'));
|
|
||||||
|
|
||||||
withInstall(ScrollContainer, CollapseContainer, LazyContainer);
|
withInstall(ScrollContainer, CollapseContainer, LazyContainer);
|
||||||
|
export { CollapseContainer, ScrollContainer, LazyContainer };
|
||||||
export { CollapseContainer };
|
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
// Transform vue-count-to to support vue3 version
|
// Transform vue-count-to to support vue3 version
|
||||||
|
|
||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
export const CountTo = createAsyncComponent(() => import('./src/index.vue'));
|
export const CountTo = createAsyncComponent(() => import('./src/index.vue'));
|
||||||
|
|
||||||
withInstall(CountTo);
|
|
||||||
|
@@ -1,9 +1,5 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
export const Description = createAsyncComponent(() => import('./src/index'));
|
export const Description = createAsyncComponent(() => import('./src/index'));
|
||||||
|
|
||||||
withInstall(Description);
|
|
||||||
|
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
export { useDescription } from './src/useDescription';
|
export { useDescription } from './src/useDescription';
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import { withInstall } from '../util';
|
import { withInstall } from '../util';
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import BasicDrawer from './src/BasicDrawer';
|
||||||
export const BasicDrawer = createAsyncComponent(() => import('./src/BasicDrawer'));
|
|
||||||
|
|
||||||
withInstall(BasicDrawer);
|
export { BasicDrawer };
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
export { useDrawer, useDrawerInner } from './src/useDrawer';
|
export { useDrawer, useDrawerInner } from './src/useDrawer';
|
||||||
|
|
||||||
|
withInstall(BasicDrawer);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { withInstall } from '../util';
|
import { withInstall } from '../util';
|
||||||
|
|
||||||
import Dropdown from './src/Dropdown';
|
import Dropdown from './src/Dropdown.vue';
|
||||||
|
|
||||||
withInstall(Dropdown);
|
withInstall(Dropdown);
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
|
@@ -1,57 +0,0 @@
|
|||||||
import type { Trigger } from './types';
|
|
||||||
|
|
||||||
import { defineComponent, computed, unref } from 'vue';
|
|
||||||
import { Dropdown, Menu } from 'ant-design-vue';
|
|
||||||
import Icon from '/@/components/Icon/index';
|
|
||||||
|
|
||||||
import { basicDropdownProps } from './props';
|
|
||||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Dropdown',
|
|
||||||
props: basicDropdownProps,
|
|
||||||
emits: ['menuEvent'],
|
|
||||||
setup(props, { slots, emit, attrs }) {
|
|
||||||
const getMenuList = computed(() => props.dropMenuList);
|
|
||||||
|
|
||||||
function handleClickMenu({ key }: any) {
|
|
||||||
const menu = unref(getMenuList).find((item) => `${item.event}` === `${key}`);
|
|
||||||
emit('menuEvent', menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMenus() {
|
|
||||||
return (
|
|
||||||
<Menu onClick={handleClickMenu} selectedKeys={props.selectedKeys}>
|
|
||||||
{() => (
|
|
||||||
<>
|
|
||||||
{unref(getMenuList).map((item) => {
|
|
||||||
const { disabled, icon, text, divider, event } = item;
|
|
||||||
return [
|
|
||||||
<Menu.Item key={`${event}`} disabled={disabled}>
|
|
||||||
{() => (
|
|
||||||
<>
|
|
||||||
{icon && <Icon icon={icon} />}
|
|
||||||
<span class="ml-1">{text}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Menu.Item>,
|
|
||||||
// @ts-ignore
|
|
||||||
divider && <Menu.Divider key={`d-${event}`} />,
|
|
||||||
];
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => (
|
|
||||||
<Dropdown trigger={props.trigger as Trigger[]} {...attrs}>
|
|
||||||
{{
|
|
||||||
default: () => <span>{getSlot(slots)}</span>,
|
|
||||||
overlay: () => renderMenus(),
|
|
||||||
}}
|
|
||||||
</Dropdown>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
70
src/components/Dropdown/src/Dropdown.vue
Normal file
70
src/components/Dropdown/src/Dropdown.vue
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<a-dropdown :trigger="trigger" v-bind="$attrs">
|
||||||
|
<span>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu :selectedKeys="selectedKeys">
|
||||||
|
<template v-for="item in getMenuList" :key="`${item.event}`">
|
||||||
|
<a-menu-item @click="handleClickMenu({ key: item.event })" :disabled="item.disabled">
|
||||||
|
<Icon :icon="item.icon" v-if="item.icon" />
|
||||||
|
<span class="ml-1">{{ item.text }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-divider v-if="item.divider" :key="`d-${item.event}`" />
|
||||||
|
</template>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
import type { DropMenu } from './types';
|
||||||
|
|
||||||
|
import { defineComponent, computed, unref } from 'vue';
|
||||||
|
import { Dropdown, Menu } from 'ant-design-vue';
|
||||||
|
import Icon from '/@/components/Icon/index';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BasicDropdown',
|
||||||
|
components: {
|
||||||
|
[Dropdown.name]: Dropdown,
|
||||||
|
[Menu.name]: Menu,
|
||||||
|
[Menu.Item.name]: Menu.Item,
|
||||||
|
[Menu.Divider.name]: Menu.Divider,
|
||||||
|
Icon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* the trigger mode which executes the drop-down action
|
||||||
|
* @default ['hover']
|
||||||
|
* @type string[]
|
||||||
|
*/
|
||||||
|
trigger: {
|
||||||
|
type: [Array] as PropType<string[]>,
|
||||||
|
default: () => {
|
||||||
|
return ['contextmenu'];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dropMenuList: {
|
||||||
|
type: Array as PropType<DropMenu[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
selectedKeys: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['menuEvent'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const getMenuList = computed(() => props.dropMenuList);
|
||||||
|
|
||||||
|
function handleClickMenu({ key }: { key: string }) {
|
||||||
|
const menu = unref(getMenuList).find((item) => `${item.event}` === `${key}`);
|
||||||
|
emit('menuEvent', menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { handleClickMenu, getMenuList };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@@ -1,26 +0,0 @@
|
|||||||
import type { PropType } from 'vue';
|
|
||||||
import type { DropMenu } from './types';
|
|
||||||
|
|
||||||
export const dropdownProps = {
|
|
||||||
/**
|
|
||||||
* the trigger mode which executes the drop-down action
|
|
||||||
* @default ['hover']
|
|
||||||
* @type string[]
|
|
||||||
*/
|
|
||||||
trigger: {
|
|
||||||
type: [Array] as PropType<string[]>,
|
|
||||||
default: () => {
|
|
||||||
return ['contextmenu'];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export const basicDropdownProps = Object.assign({}, dropdownProps, {
|
|
||||||
dropMenuList: {
|
|
||||||
type: Array as PropType<DropMenu[]>,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
selectedKeys: {
|
|
||||||
type: Array as PropType<string[]>,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
@@ -7,4 +7,4 @@ export interface DropMenu {
|
|||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Trigger = 'click' | 'hover' | 'contextMenu';
|
// export type Trigger = 'click' | 'hover' | 'contextMenu';
|
||||||
|
@@ -1,12 +1,8 @@
|
|||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
export const ImpExcel = createAsyncComponent(() => import('./src/ImportExcel.vue'));
|
export const ImpExcel = createAsyncComponent(() => import('./src/ImportExcel.vue'));
|
||||||
export const ExpExcelModel = createAsyncComponent(() => import('./src/ExportExcelModel.vue'));
|
export const ExpExcelModel = createAsyncComponent(() => import('./src/ExportExcelModel.vue'));
|
||||||
|
|
||||||
withInstall(ImpExcel, ExpExcelModel);
|
|
||||||
|
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
|
|
||||||
export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel';
|
export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel';
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
@import (reference) '../../design/index.less';
|
|
||||||
|
|
||||||
.app-iconify {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.iconify {
|
|
||||||
display: block;
|
|
||||||
min-width: 1em;
|
|
||||||
min-height: 1em;
|
|
||||||
background: @iconify-bg-color;
|
|
||||||
border-radius: 100%;
|
|
||||||
}
|
|
7
src/components/Icon/index.ts
Normal file
7
src/components/Icon/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { withInstall } from '../util';
|
||||||
|
import Icon from './src/index.vue';
|
||||||
|
|
||||||
|
withInstall(Icon);
|
||||||
|
|
||||||
|
export { Icon };
|
||||||
|
export default Icon;
|
@@ -1,83 +0,0 @@
|
|||||||
import './index.less';
|
|
||||||
|
|
||||||
import type { PropType } from 'vue';
|
|
||||||
import {
|
|
||||||
defineComponent,
|
|
||||||
ref,
|
|
||||||
watch,
|
|
||||||
onMounted,
|
|
||||||
nextTick,
|
|
||||||
unref,
|
|
||||||
computed,
|
|
||||||
CSSProperties,
|
|
||||||
} from 'vue';
|
|
||||||
import Iconify from '@purge-icons/generated';
|
|
||||||
import { isString } from '/@/utils/is';
|
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'GIcon',
|
|
||||||
props: {
|
|
||||||
// icon name
|
|
||||||
icon: propTypes.string,
|
|
||||||
// icon color
|
|
||||||
color: propTypes.string,
|
|
||||||
// icon size
|
|
||||||
size: {
|
|
||||||
type: [String, Number] as PropType<string | number>,
|
|
||||||
default: 16,
|
|
||||||
},
|
|
||||||
prefix: propTypes.string.def(''),
|
|
||||||
},
|
|
||||||
setup(props, { attrs }) {
|
|
||||||
const elRef = ref<ElRef>(null);
|
|
||||||
|
|
||||||
const getIconRef = computed(() => {
|
|
||||||
const { icon, prefix } = props;
|
|
||||||
return `${prefix ? prefix + ':' : ''}${icon}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const update = async () => {
|
|
||||||
const el = unref(elRef);
|
|
||||||
if (el) {
|
|
||||||
await nextTick();
|
|
||||||
const icon = unref(getIconRef);
|
|
||||||
|
|
||||||
const svg = Iconify.renderSVG(icon, {});
|
|
||||||
|
|
||||||
if (svg) {
|
|
||||||
el.textContent = '';
|
|
||||||
el.appendChild(svg);
|
|
||||||
} else {
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.className = 'iconify';
|
|
||||||
span.dataset.icon = icon;
|
|
||||||
el.textContent = '';
|
|
||||||
el.appendChild(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapStyleRef = computed(
|
|
||||||
(): CSSProperties => {
|
|
||||||
const { size, color } = props;
|
|
||||||
let fs = size;
|
|
||||||
if (isString(size)) {
|
|
||||||
fs = parseInt(size, 10);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
fontSize: `${fs}px`,
|
|
||||||
color,
|
|
||||||
display: 'inline-flex',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(() => props.icon, update, { flush: 'post' });
|
|
||||||
|
|
||||||
onMounted(update);
|
|
||||||
|
|
||||||
return () => (
|
|
||||||
<span ref={elRef} class={[attrs.class, 'app-iconify anticon']} style={unref(wrapStyleRef)} />
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
100
src/components/Icon/src/index.vue
Normal file
100
src/components/Icon/src/index.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<span ref="elRef" :class="[$attrs.class, 'app-iconify anticon']" :style="getWrapStyle" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
onMounted,
|
||||||
|
nextTick,
|
||||||
|
unref,
|
||||||
|
computed,
|
||||||
|
CSSProperties,
|
||||||
|
} from 'vue';
|
||||||
|
import Iconify from '@purge-icons/generated';
|
||||||
|
import { isString } from '/@/utils/is';
|
||||||
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'GIcon',
|
||||||
|
props: {
|
||||||
|
// icon name
|
||||||
|
icon: propTypes.string,
|
||||||
|
// icon color
|
||||||
|
color: propTypes.string,
|
||||||
|
// icon size
|
||||||
|
size: {
|
||||||
|
type: [String, Number] as PropType<string | number>,
|
||||||
|
default: 16,
|
||||||
|
},
|
||||||
|
prefix: propTypes.string.def(''),
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const elRef = ref<ElRef>(null);
|
||||||
|
|
||||||
|
const getIconRef = computed(() => {
|
||||||
|
const { icon, prefix } = props;
|
||||||
|
return `${prefix ? prefix + ':' : ''}${icon}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const update = async () => {
|
||||||
|
const el = unref(elRef);
|
||||||
|
if (el) {
|
||||||
|
await nextTick();
|
||||||
|
const icon = unref(getIconRef);
|
||||||
|
|
||||||
|
const svg = Iconify.renderSVG(icon, {});
|
||||||
|
|
||||||
|
if (svg) {
|
||||||
|
el.textContent = '';
|
||||||
|
el.appendChild(svg);
|
||||||
|
} else {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'iconify';
|
||||||
|
span.dataset.icon = icon;
|
||||||
|
el.textContent = '';
|
||||||
|
el.appendChild(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWrapStyle = computed(
|
||||||
|
(): CSSProperties => {
|
||||||
|
const { size, color } = props;
|
||||||
|
let fs = size;
|
||||||
|
if (isString(size)) {
|
||||||
|
fs = parseInt(size, 10);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fontSize: `${fs}px`,
|
||||||
|
color,
|
||||||
|
display: 'inline-flex',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(() => props.icon, update, { flush: 'post' });
|
||||||
|
|
||||||
|
onMounted(update);
|
||||||
|
|
||||||
|
return { elRef, getWrapStyle };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
@import (reference) '../../../design/index.less';
|
||||||
|
|
||||||
|
.app-iconify {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.iconify {
|
||||||
|
display: block;
|
||||||
|
min-width: 1em;
|
||||||
|
min-height: 1em;
|
||||||
|
background: @iconify-bg-color;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -1,9 +1,5 @@
|
|||||||
import './src/indicator';
|
|
||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
export const Loading = createAsyncComponent(() => import('./src/index.vue'));
|
export const Loading = createAsyncComponent(() => import('./src/index.vue'));
|
||||||
|
|
||||||
withInstall(Loading);
|
|
||||||
export { useLoading } from './src/useLoading';
|
export { useLoading } from './src/useLoading';
|
||||||
export { createLoading } from './src/createLoading';
|
export { createLoading } from './src/createLoading';
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
// If you need to modify the default icon, you can open the comment and modify it here
|
|
||||||
|
|
||||||
// import { Spin } from 'ant-design-vue';
|
|
||||||
// import { LoadingOutlined } from '@ant-design/icons-vue';
|
|
||||||
// Spin.setDefaultIndicator({
|
|
||||||
// indicator: () => {
|
|
||||||
// return <LoadingOutlined spin />;
|
|
||||||
// },
|
|
||||||
// });
|
|
@@ -8,6 +8,10 @@ export interface UseLoadingOptions {
|
|||||||
props?: Partial<LoadingProps>;
|
props?: Partial<LoadingProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Fn {
|
||||||
|
(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export function useLoading(props: Partial<LoadingProps>): [Fn, Fn];
|
export function useLoading(props: Partial<LoadingProps>): [Fn, Fn];
|
||||||
export function useLoading(opt: Partial<UseLoadingOptions>): [Fn, Fn];
|
export function useLoading(opt: Partial<UseLoadingOptions>): [Fn, Fn];
|
||||||
|
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
export const MarkDown = createAsyncComponent(() => import('./src/index.vue'));
|
export const MarkDown = createAsyncComponent(() => import('./src/index.vue'));
|
||||||
|
|
||||||
withInstall(MarkDown);
|
|
||||||
|
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
|
@@ -1,17 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="markdown" ref="wrapRef" />
|
<div ref="wrapRef" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { defineComponent, ref, onMounted, unref, onUnmounted, nextTick, computed } from 'vue';
|
||||||
defineComponent,
|
|
||||||
ref,
|
|
||||||
onMounted,
|
|
||||||
unref,
|
|
||||||
onUnmounted,
|
|
||||||
nextTick,
|
|
||||||
// watch,
|
|
||||||
computed,
|
|
||||||
} from 'vue';
|
|
||||||
import Vditor from 'vditor';
|
import Vditor from 'vditor';
|
||||||
import 'vditor/dist/index.css';
|
import 'vditor/dist/index.css';
|
||||||
|
|
||||||
@@ -75,21 +66,6 @@
|
|||||||
initedRef.value = true;
|
initedRef.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// watch(
|
|
||||||
// () => props.value,
|
|
||||||
// () => {
|
|
||||||
// nextTick(() => {
|
|
||||||
// const vditor = unref(vditorRef);
|
|
||||||
// if (unref(initedRef) && props.value && vditor) {
|
|
||||||
// vditor.setValue(props.value);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// immediate: true,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
init();
|
init();
|
||||||
|
@@ -1,13 +1,5 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu.vue'), {
|
export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu.vue'));
|
||||||
loading: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const MenuTag = createAsyncComponent(() => import('./src/components/MenuItemTag.vue'), {
|
export const MenuTag = createAsyncComponent(() => import('./src/components/MenuItemTag.vue'));
|
||||||
loading: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
withInstall(BasicMenu);
|
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import './src/index.less';
|
import './src/index.less';
|
||||||
import { withInstall } from '../util';
|
import { withInstall } from '../util';
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import BasicModal from './src/BasicModal';
|
||||||
|
|
||||||
export const BasicModal = createAsyncComponent(() => import('./src/BasicModal'));
|
|
||||||
|
|
||||||
withInstall(BasicModal);
|
withInstall(BasicModal);
|
||||||
|
|
||||||
|
export { BasicModal };
|
||||||
export { useModalContext } from './src/useModalContext';
|
export { useModalContext } from './src/useModalContext';
|
||||||
export { useModal, useModalInner } from './src/useModal';
|
export { useModal, useModalInner } from './src/useModal';
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
|
@@ -1,6 +1,2 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
export const PageFooter = createAsyncComponent(() => import('./src/PageFooter.vue'));
|
export const PageFooter = createAsyncComponent(() => import('./src/PageFooter.vue'));
|
||||||
|
|
||||||
withInstall(PageFooter);
|
|
||||||
|
@@ -1,7 +1,4 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
export const QrCode = createAsyncComponent(() => import('./src/index.vue'));
|
export const QrCode = createAsyncComponent(() => import('./src/index.vue'));
|
||||||
|
|
||||||
withInstall(QrCode);
|
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
|
@@ -3,10 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { withInstall } from '../util';
|
import { withInstall } from '../util';
|
||||||
|
import Scrollbar from './src/index.vue';
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
|
||||||
export const Scrollbar = createAsyncComponent(() => import('./src/index.vue'));
|
|
||||||
|
|
||||||
withInstall(Scrollbar);
|
withInstall(Scrollbar);
|
||||||
|
|
||||||
|
export { Scrollbar };
|
||||||
export type { ScrollbarType } from './src/types';
|
export type { ScrollbarType } from './src/types';
|
||||||
|
@@ -1,7 +1,3 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
export const StrengthMeter = createAsyncComponent(() => import('./src/index'));
|
export const StrengthMeter = createAsyncComponent(() => import('./src/index.vue'));
|
||||||
|
|
||||||
withInstall(StrengthMeter);
|
|
@@ -1,69 +0,0 @@
|
|||||||
@import (reference) '../../../design/index.less';
|
|
||||||
|
|
||||||
.strength-meter {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&-bar {
|
|
||||||
position: relative;
|
|
||||||
height: 4px;
|
|
||||||
margin: 10px auto 6px;
|
|
||||||
background: @disabled-color;
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
display: block;
|
|
||||||
width: 20%;
|
|
||||||
height: inherit;
|
|
||||||
background: transparent;
|
|
||||||
border-color: @white;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 0 5px 0 5px;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
left: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
right: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__fill {
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
height: inherit;
|
|
||||||
background: transparent;
|
|
||||||
border-radius: inherit;
|
|
||||||
transition: width 0.5s ease-in-out, background 0.25s;
|
|
||||||
|
|
||||||
&[data-score='0'] {
|
|
||||||
width: 20%;
|
|
||||||
background: darken(@error-color, 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-score='1'] {
|
|
||||||
width: 40%;
|
|
||||||
background: @error-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-score='2'] {
|
|
||||||
width: 60%;
|
|
||||||
background: @warning-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-score='3'] {
|
|
||||||
width: 80%;
|
|
||||||
background: fade(@success-color, 50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-score='4'] {
|
|
||||||
width: 100%;
|
|
||||||
background: @success-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,77 +0,0 @@
|
|||||||
import './index.less';
|
|
||||||
|
|
||||||
import { PropType } from 'vue';
|
|
||||||
|
|
||||||
import { defineComponent, computed, ref, watch, unref, watchEffect } from 'vue';
|
|
||||||
|
|
||||||
import { Input } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import zxcvbn from 'zxcvbn';
|
|
||||||
import { extendSlots } from '/@/utils/helper/tsxHelper';
|
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
|
||||||
|
|
||||||
const prefixCls = 'strength-meter';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'StrengthMeter',
|
|
||||||
props: {
|
|
||||||
value: propTypes.string,
|
|
||||||
|
|
||||||
userInputs: {
|
|
||||||
type: Array as PropType<string[]>,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
|
|
||||||
showInput: propTypes.bool.def(true),
|
|
||||||
disabled: propTypes.bool,
|
|
||||||
},
|
|
||||||
emits: ['score-change', 'change'],
|
|
||||||
setup(props, { emit, attrs, slots }) {
|
|
||||||
const innerValueRef = ref('');
|
|
||||||
const getPasswordStrength = computed(() => {
|
|
||||||
const { userInputs, disabled } = props;
|
|
||||||
if (disabled) return null;
|
|
||||||
const innerValue = unref(innerValueRef);
|
|
||||||
const score = innerValue
|
|
||||||
? zxcvbn(unref(innerValueRef), (userInputs as string[]) || null).score
|
|
||||||
: null;
|
|
||||||
emit('score-change', score);
|
|
||||||
return score;
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleChange(e: ChangeEvent) {
|
|
||||||
innerValueRef.value = e.target.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
innerValueRef.value = props.value || '';
|
|
||||||
});
|
|
||||||
watch(
|
|
||||||
() => unref(innerValueRef),
|
|
||||||
(val) => {
|
|
||||||
emit('change', val);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const { showInput, disabled } = props;
|
|
||||||
return (
|
|
||||||
<div class={prefixCls}>
|
|
||||||
{showInput && (
|
|
||||||
<Input.Password
|
|
||||||
{...attrs}
|
|
||||||
allowClear={true}
|
|
||||||
value={unref(innerValueRef)}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
{extendSlots(slots)}
|
|
||||||
</Input.Password>
|
|
||||||
)}
|
|
||||||
<div class={`${prefixCls}-bar`}>
|
|
||||||
<div class={`${prefixCls}-bar__fill`} data-score={unref(getPasswordStrength)}></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
156
src/components/StrengthMeter/src/index.vue
Normal file
156
src/components/StrengthMeter/src/index.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="prefixCls">
|
||||||
|
<InputPassword
|
||||||
|
v-if="showInput"
|
||||||
|
v-bind="$attrs"
|
||||||
|
allowClear
|
||||||
|
:value="innerValueRef"
|
||||||
|
@change="handleChange"
|
||||||
|
:disabled="disabled"
|
||||||
|
>
|
||||||
|
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||||
|
<slot :name="item" v-bind="data" />
|
||||||
|
</template>
|
||||||
|
</InputPassword>
|
||||||
|
<div :class="`${prefixCls}-bar`">
|
||||||
|
<div :class="`${prefixCls}-bar--fill`" :data-score="getPasswordStrength"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
import { defineComponent, computed, ref, watch, unref, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { Input } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import zxcvbn from 'zxcvbn';
|
||||||
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'StrengthMeter',
|
||||||
|
components: { InputPassword: Input.Password },
|
||||||
|
props: {
|
||||||
|
value: propTypes.string,
|
||||||
|
|
||||||
|
userInputs: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
|
||||||
|
showInput: propTypes.bool.def(true),
|
||||||
|
disabled: propTypes.bool,
|
||||||
|
},
|
||||||
|
emits: ['score-change', 'change'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const innerValueRef = ref('');
|
||||||
|
const { prefixCls } = useDesign('strength-meter');
|
||||||
|
|
||||||
|
const getPasswordStrength = computed(() => {
|
||||||
|
const { userInputs, disabled } = props;
|
||||||
|
if (disabled) return null;
|
||||||
|
const innerValue = unref(innerValueRef);
|
||||||
|
const score = innerValue
|
||||||
|
? zxcvbn(unref(innerValueRef), (userInputs as string[]) || null).score
|
||||||
|
: null;
|
||||||
|
emit('score-change', score);
|
||||||
|
return score;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleChange(e: ChangeEvent) {
|
||||||
|
innerValueRef.value = e.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
innerValueRef.value = props.value || '';
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => unref(innerValueRef),
|
||||||
|
(val) => {
|
||||||
|
emit('change', val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getPasswordStrength,
|
||||||
|
handleChange,
|
||||||
|
prefixCls,
|
||||||
|
innerValueRef,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import (reference) '../../../design/index.less';
|
||||||
|
@prefix-cls: ~'@{namespace}-strength-meter';
|
||||||
|
|
||||||
|
.@{prefix-cls} {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&-bar {
|
||||||
|
position: relative;
|
||||||
|
height: 4px;
|
||||||
|
margin: 10px auto 6px;
|
||||||
|
background: @disabled-color;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
display: block;
|
||||||
|
width: 20%;
|
||||||
|
height: inherit;
|
||||||
|
background: transparent;
|
||||||
|
border-color: @white;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 5px 0 5px;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
left: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
right: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--fill {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: inherit;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: inherit;
|
||||||
|
transition: width 0.5s ease-in-out, background 0.25s;
|
||||||
|
|
||||||
|
&[data-score='0'] {
|
||||||
|
width: 20%;
|
||||||
|
background: darken(@error-color, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-score='1'] {
|
||||||
|
width: 40%;
|
||||||
|
background: @error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-score='2'] {
|
||||||
|
width: 60%;
|
||||||
|
background: @warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-score='3'] {
|
||||||
|
width: 80%;
|
||||||
|
background: fade(@success-color, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-score='4'] {
|
||||||
|
width: 100%;
|
||||||
|
background: @success-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -1,6 +1,2 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
export const Tinymce = createAsyncComponent(() => import('./src/Editor.vue'));
|
export const Tinymce = createAsyncComponent(() => import('./src/Editor.vue'));
|
||||||
|
|
||||||
withInstall(Tinymce);
|
|
||||||
|
@@ -1,9 +1,6 @@
|
|||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
export const BasicTree = createAsyncComponent(() => import('./src/BasicTree'));
|
export const BasicTree = createAsyncComponent(() => import('./src/BasicTree'));
|
||||||
|
|
||||||
withInstall(BasicTree);
|
|
||||||
|
|
||||||
export type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
|
export type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
|
@@ -1,6 +1,2 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
export const BasicUpload = createAsyncComponent(() => import('./src/BasicUpload.vue'));
|
export const BasicUpload = createAsyncComponent(() => import('./src/BasicUpload.vue'));
|
||||||
|
|
||||||
withInstall(BasicUpload);
|
|
||||||
|
@@ -1,10 +1,6 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
export const BasicDragVerify = createAsyncComponent(() => import('./src/DragVerify'));
|
export const BasicDragVerify = createAsyncComponent(() => import('./src/DragVerify'));
|
||||||
export const RotateDragVerify = createAsyncComponent(() => import('./src/ImgRotate'));
|
export const RotateDragVerify = createAsyncComponent(() => import('./src/ImgRotate'));
|
||||||
|
|
||||||
withInstall(BasicDragVerify, RotateDragVerify);
|
|
||||||
|
|
||||||
export * from './src/types';
|
export * from './src/types';
|
||||||
|
@@ -1,7 +1,3 @@
|
|||||||
import { withInstall } from '../util';
|
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
export const VScroll = createAsyncComponent(() => import('./src/index'));
|
export const VScroll = createAsyncComponent(() => import('./src/index'));
|
||||||
|
|
||||||
withInstall(VScroll);
|
|
||||||
|
@@ -84,6 +84,7 @@
|
|||||||
} from './components';
|
} from './components';
|
||||||
import { useAppInject } from '/@/hooks/web/useAppInject';
|
import { useAppInject } from '/@/hooks/web/useAppInject';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'LayoutHeader',
|
name: 'LayoutHeader',
|
||||||
|
Reference in New Issue
Block a user