style(系统设置): 用户组页面样式优化
This commit is contained in:
parent
097ca36387
commit
e3fd24cdba
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<a-popover
|
<a-popover
|
||||||
|
ref="popoverRef"
|
||||||
:popup-visible="currentVisible"
|
:popup-visible="currentVisible"
|
||||||
position="bl"
|
position="bl"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
|
@ -7,44 +8,45 @@
|
||||||
:content-class="props.id ? 'move-left' : ''"
|
:content-class="props.id ? 'move-left' : ''"
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="form">
|
<div v-outer="handleOutsideClick">
|
||||||
<a-form
|
<div class="form">
|
||||||
ref="formRef"
|
<a-form
|
||||||
:model="form"
|
ref="formRef"
|
||||||
size="large"
|
:model="form"
|
||||||
layout="vertical"
|
size="large"
|
||||||
:label-col-props="{ span: 0 }"
|
layout="vertical"
|
||||||
:wrapper-col-props="{ span: 24 }"
|
:label-col-props="{ span: 0 }"
|
||||||
>
|
:wrapper-col-props="{ span: 24 }"
|
||||||
<a-form-item>
|
>
|
||||||
<div class="text-[14px] text-[var(--color-text-1)]">{{
|
<div class="mb-[8px] text-[14px] font-medium text-[var(--color-text-1)]">{{
|
||||||
props.id ? t('system.userGroup.rename') : t('system.userGroup.createUserGroup')
|
props.id ? t('system.userGroup.rename') : t('system.userGroup.createUserGroup')
|
||||||
}}</div>
|
}}</div>
|
||||||
</a-form-item>
|
<a-form-item field="name" :rules="[{ validator: validateName }]">
|
||||||
<a-form-item field="name" :rules="[{ validator: validateName }]">
|
<a-input
|
||||||
<a-input
|
v-model="form.name"
|
||||||
v-model="form.name"
|
class="w-[243px]"
|
||||||
class="w-[243px]"
|
:placeholder="t('system.userGroup.pleaseInputUserGroupName')"
|
||||||
:placeholder="t('system.userGroup.pleaseInputUserGroupName')"
|
allow-clear
|
||||||
@press-enter="handleBeforeOk"
|
@press-enter="handleBeforeOk"
|
||||||
@keyup.esc="handleCancel"
|
@keyup.esc="handleCancel"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row flex-nowrap justify-end gap-2">
|
<div class="flex flex-row flex-nowrap justify-end gap-2">
|
||||||
<a-button type="secondary" size="mini" :disabled="loading" @click="handleCancel">
|
<a-button type="secondary" size="mini" :disabled="loading" @click="handleCancel">
|
||||||
{{ t('common.cancel') }}
|
{{ t('common.cancel') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="mini"
|
size="mini"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:disabled="form.name.length === 0"
|
:disabled="form.name.length === 0"
|
||||||
@click="handleBeforeOk"
|
@click="handleBeforeOk"
|
||||||
>
|
>
|
||||||
{{ props.id ? t('common.rename') : t('common.create') }}
|
{{ props.id ? t('common.rename') : t('common.create') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
@ -82,6 +84,7 @@
|
||||||
|
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
const currentVisible = ref(props.visible);
|
const currentVisible = ref(props.visible);
|
||||||
|
// trigger相关变量
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -154,6 +157,12 @@
|
||||||
currentVisible.value = props.visible;
|
currentVisible.value = props.visible;
|
||||||
form.name = props.defaultName || '';
|
form.name = props.defaultName || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleOutsideClick = () => {
|
||||||
|
if (currentVisible.value) {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ms-ug-left flex h-full flex-col px-[24px] pb-[24px]">
|
<div class="flex flex-col px-[24px] pb-[24px]">
|
||||||
<div class="sticky top-0 z-[999] w-[252px] bg-white pt-[24px]">
|
<div class="sticky top-0 z-[999] bg-white pt-[24px]">
|
||||||
<a-input-search
|
<a-input-search
|
||||||
:placeholder="t('system.userGroup.searchHolder')"
|
:placeholder="t('system.userGroup.searchHolder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
@search="searchData"
|
@search="searchData"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showSystem" class="mt-2 w-[252px]">
|
<div v-if="showSystem" class="mt-2">
|
||||||
<CreateUserGroupPopup
|
<CreateUserGroupPopup
|
||||||
:list="systemUserGroupList"
|
:list="systemUserGroupList"
|
||||||
:visible="systemUserGroupVisible"
|
:visible="systemUserGroupVisible"
|
||||||
|
@ -36,8 +36,8 @@
|
||||||
{{ t('system.userGroup.systemUserGroup') }}
|
{{ t('system.userGroup.systemUserGroup') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MsMoreAction :list="createSystemUGActionItem" @select="systemUserGroupVisible = true">
|
<MsMoreAction :list="createSystemUGActionItem" @select="handleCreateUG(AuthScopeEnum.SYSTEM)">
|
||||||
<icon-plus-circle-fill class="text-[rgb(var(--primary-7))]" size="20" />
|
<icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" />
|
||||||
</MsMoreAction>
|
</MsMoreAction>
|
||||||
</div>
|
</div>
|
||||||
</CreateUserGroupPopup>
|
</CreateUserGroupPopup>
|
||||||
|
@ -56,10 +56,10 @@
|
||||||
@cancel="handleRenameCancel(element)"
|
@cancel="handleRenameCancel(element)"
|
||||||
@submit="handleRenameCancel(element, element.id)"
|
@submit="handleRenameCancel(element, element.id)"
|
||||||
>
|
>
|
||||||
<div class="flex grow flex-row items-center justify-between">
|
<div class="flex max-w-[100%] grow flex-row items-center justify-between">
|
||||||
<a-tooltip :content="element.name">
|
<a-tooltip :content="element.name">
|
||||||
<div
|
<div
|
||||||
class="one-line-text max-w-[156px] text-[var(--color-text-1)]"
|
class="one-line-text text-[var(--color-text-1)]"
|
||||||
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
|
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
|
||||||
>{{ element.name }}</div
|
>{{ element.name }}</div
|
||||||
>
|
>
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showOrg" class="mt-2 w-[252px]">
|
<div v-if="showOrg" class="mt-2">
|
||||||
<CreateUserGroupPopup
|
<CreateUserGroupPopup
|
||||||
:list="orgUserGroupList"
|
:list="orgUserGroupList"
|
||||||
:visible="orgUserGroupVisible"
|
:visible="orgUserGroupVisible"
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MsMoreAction :list="createOrgUGActionItem" @select="orgUserGroupVisible = true">
|
<MsMoreAction :list="createOrgUGActionItem" @select="orgUserGroupVisible = true">
|
||||||
<icon-plus-circle-fill class="text-[rgb(var(--primary-7))]" size="20" />
|
<icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" />
|
||||||
</MsMoreAction>
|
</MsMoreAction>
|
||||||
</div>
|
</div>
|
||||||
</CreateUserGroupPopup>
|
</CreateUserGroupPopup>
|
||||||
|
@ -138,10 +138,10 @@
|
||||||
@cancel="handleRenameCancel(element)"
|
@cancel="handleRenameCancel(element)"
|
||||||
@submit="handleRenameCancel(element, element.id)"
|
@submit="handleRenameCancel(element, element.id)"
|
||||||
>
|
>
|
||||||
<div class="flex grow flex-row items-center justify-between">
|
<div class="flex max-w-[100%] grow flex-row items-center justify-between">
|
||||||
<a-tooltip :content="element.name">
|
<a-tooltip :content="element.name">
|
||||||
<div
|
<div
|
||||||
class="one-line-text max-w-[156px] text-[var(--color-text-1)]"
|
class="one-line-text text-[var(--color-text-1)]"
|
||||||
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
|
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
|
||||||
>{{ element.name }}</div
|
>{{ element.name }}</div
|
||||||
>
|
>
|
||||||
|
@ -172,7 +172,7 @@
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showProject" class="mt-2 w-[252px]">
|
<div v-if="showProject" class="mt-2">
|
||||||
<CreateUserGroupPopup
|
<CreateUserGroupPopup
|
||||||
:list="projectUserGroupList"
|
:list="projectUserGroupList"
|
||||||
:visible="projectUserGroupVisible"
|
:visible="projectUserGroupVisible"
|
||||||
|
@ -201,7 +201,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MsMoreAction :list="createProjectUGActionItem" @select="projectUserGroupVisible = true">
|
<MsMoreAction :list="createProjectUGActionItem" @select="projectUserGroupVisible = true">
|
||||||
<icon-plus-circle-fill class="text-[rgb(var(--primary-7))]" size="20" />
|
<icon-plus-circle-fill class="cursor-pointer text-[rgb(var(--primary-7))]" size="20" />
|
||||||
</MsMoreAction>
|
</MsMoreAction>
|
||||||
</div>
|
</div>
|
||||||
</CreateUserGroupPopup>
|
</CreateUserGroupPopup>
|
||||||
|
@ -220,10 +220,10 @@
|
||||||
@cancel="handleRenameCancel(element)"
|
@cancel="handleRenameCancel(element)"
|
||||||
@submit="handleRenameCancel(element, element.id)"
|
@submit="handleRenameCancel(element, element.id)"
|
||||||
>
|
>
|
||||||
<div class="flex grow flex-row items-center justify-between">
|
<div class="flex max-w-[100%] grow flex-row items-center justify-between">
|
||||||
<a-tooltip :content="element.name">
|
<a-tooltip :content="element.name">
|
||||||
<div
|
<div
|
||||||
class="one-line-text max-w-[156px] text-[var(--color-text-1)]"
|
class="one-line-text text-[var(--color-text-1)]"
|
||||||
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
|
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
|
||||||
>{{ element.name }}</div
|
>{{ element.name }}</div
|
||||||
>
|
>
|
||||||
|
@ -254,7 +254,7 @@
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AddUserModal :visible="userModalVisible" :current-id="currentItem.id" @cancel="userModalVisible = false" />
|
<AddUserModal :visible="userModalVisible" :current-id="currentItem.id" @cancel="handleAddUserCancel" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -286,6 +286,7 @@
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'handleSelect', element: UserGroupItem): void;
|
(e: 'handleSelect', element: UserGroupItem): void;
|
||||||
|
(e: 'addUserSuccess', id: string): void;
|
||||||
}>();
|
}>();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
@ -383,16 +384,13 @@
|
||||||
}
|
}
|
||||||
if (res.length > 0) {
|
if (res.length > 0) {
|
||||||
userGroupList.value = res;
|
userGroupList.value = res;
|
||||||
let tmpItem = res[0];
|
|
||||||
if (id) {
|
|
||||||
tmpItem = res.find((i) => i.id === id) || res[0];
|
|
||||||
}
|
|
||||||
if (isSelect) {
|
if (isSelect) {
|
||||||
// leftCollapse 切换时不重复数据请求
|
// leftCollapse 切换时不重复数据请求
|
||||||
handleListItemClick(tmpItem);
|
if (id) {
|
||||||
} else {
|
handleListItemClick(res.find((i) => i.id === id) || res[0]);
|
||||||
// leftCollapse 切换时调用
|
} else {
|
||||||
currentId.value = id || '';
|
handleListItemClick(res[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 弹窗赋值
|
// 弹窗赋值
|
||||||
const tmpObj: PopVisible = {};
|
const tmpObj: PopVisible = {};
|
||||||
|
@ -452,7 +450,7 @@
|
||||||
|
|
||||||
function enterData(eve: Event) {
|
function enterData(eve: Event) {
|
||||||
if (!(eve.target as HTMLInputElement).value) {
|
if (!(eve.target as HTMLInputElement).value) {
|
||||||
initData();
|
initData('', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const keyword = (eve.target as HTMLInputElement).value;
|
const keyword = (eve.target as HTMLInputElement).value;
|
||||||
|
@ -461,7 +459,7 @@
|
||||||
}
|
}
|
||||||
function searchData(value: string) {
|
function searchData(value: string) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
initData();
|
initData('', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const keyword = value;
|
const keyword = value;
|
||||||
|
@ -480,6 +478,21 @@
|
||||||
defineExpose({
|
defineExpose({
|
||||||
initData,
|
initData,
|
||||||
});
|
});
|
||||||
|
const handleCreateUG = (scoped: AuthScopeEnum) => {
|
||||||
|
if (scoped === AuthScopeEnum.SYSTEM) {
|
||||||
|
systemUserGroupVisible.value = true;
|
||||||
|
} else if (scoped === AuthScopeEnum.ORGANIZATION) {
|
||||||
|
orgUserGroupVisible.value = true;
|
||||||
|
} else if (scoped === AuthScopeEnum.PROJECT) {
|
||||||
|
projectUserGroupVisible.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleAddUserCancel = (shouldSearch: boolean) => {
|
||||||
|
userModalVisible.value = false;
|
||||||
|
if (shouldSearch) {
|
||||||
|
emit('addUserSuccess', currentId.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
|
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(getRequestBySystemType(), {
|
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(getRequestBySystemType(), {
|
||||||
columns: userGroupUsercolumns,
|
columns: userGroupUsercolumns,
|
||||||
scroll: { x: '100%' },
|
scroll: { x: '100%', minWidth: 700 },
|
||||||
selectable: false,
|
selectable: false,
|
||||||
noDisable: true,
|
noDisable: true,
|
||||||
showSetting: false,
|
showSetting: false,
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
:disabled="props.disabled"
|
:disabled="props.disabled"
|
||||||
:filter-option="false"
|
:filter-option="false"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
:loading="loading"
|
||||||
@change="change"
|
@change="change"
|
||||||
@search="debouncedSearch"
|
@search="debouncedSearch"
|
||||||
>
|
>
|
||||||
|
@ -97,6 +98,8 @@
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
allOptions.value = [];
|
allOptions.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { computed, defineComponent, reactive, ref, toRefs, watch } from 'vue';
|
|
||||||
|
|
||||||
import Pager from './page-item.vue';
|
import Pager from './page-item.vue';
|
||||||
import EllipsisPager from './page-item-ellipsis.vue';
|
import EllipsisPager from './page-item-ellipsis.vue';
|
||||||
import StepPager from './page-item-step.vue';
|
import StepPager from './page-item-step.vue';
|
||||||
|
|
|
@ -166,7 +166,9 @@
|
||||||
|
|
||||||
// 获取当前标题的样式
|
// 获取当前标题的样式
|
||||||
const titleClass = computed(() => {
|
const titleClass = computed(() => {
|
||||||
return props.isDelete ? 'ml-2 font-semibold' : 'mb-[8px] font-medium text-[var(--color-text-1)] text-[14px]';
|
return props.isDelete
|
||||||
|
? 'ml-2 font-[14px] text-[var(--color-text-1)]'
|
||||||
|
: 'mb-[8px] font-medium text-[var(--color-text-1)] text-[14px]';
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
|
@ -1,53 +1,55 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import '@halo-dev/richtext-editor/dist/style.css';
|
|
||||||
// import { unified } from 'unified';
|
// import { unified } from 'unified';
|
||||||
// import rehypeParse from 'rehype-parse';
|
// import rehypeParse from 'rehype-parse';
|
||||||
// import rehypeFormat from 'rehype-format';
|
// import rehypeFormat from 'rehype-format';
|
||||||
// import rehypeStringify from 'rehype-stringify';
|
// import rehypeStringify from 'rehype-stringify';
|
||||||
import { useLocalStorage } from '@vueuse/core';
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
|
|
||||||
import useLocale from '@/locale/useLocale';
|
import useLocale from '@/locale/useLocale';
|
||||||
|
|
||||||
|
import '@halo-dev/richtext-editor/dist/style.css';
|
||||||
import {
|
import {
|
||||||
|
ExtensionAudio,
|
||||||
ExtensionBlockquote,
|
ExtensionBlockquote,
|
||||||
ExtensionBold,
|
ExtensionBold,
|
||||||
ExtensionBulletList,
|
ExtensionBulletList,
|
||||||
ExtensionCode,
|
ExtensionCode,
|
||||||
|
ExtensionCodeBlock,
|
||||||
|
ExtensionColor,
|
||||||
|
ExtensionColumn,
|
||||||
|
ExtensionColumns,
|
||||||
|
ExtensionCommands,
|
||||||
ExtensionDocument,
|
ExtensionDocument,
|
||||||
|
ExtensionDraggable,
|
||||||
ExtensionDropcursor,
|
ExtensionDropcursor,
|
||||||
|
ExtensionFontSize,
|
||||||
ExtensionGapcursor,
|
ExtensionGapcursor,
|
||||||
ExtensionHardBreak,
|
ExtensionHardBreak,
|
||||||
ExtensionHeading,
|
ExtensionHeading,
|
||||||
|
ExtensionHighlight,
|
||||||
ExtensionHistory,
|
ExtensionHistory,
|
||||||
ExtensionHorizontalRule,
|
ExtensionHorizontalRule,
|
||||||
ExtensionItalic,
|
ExtensionIframe,
|
||||||
ExtensionOrderedList,
|
|
||||||
ExtensionStrike,
|
|
||||||
ExtensionText,
|
|
||||||
ExtensionImage,
|
ExtensionImage,
|
||||||
ExtensionTaskList,
|
ExtensionIndent,
|
||||||
|
ExtensionItalic,
|
||||||
ExtensionLink,
|
ExtensionLink,
|
||||||
ExtensionTextAlign,
|
ExtensionNodeSelected,
|
||||||
ExtensionUnderline,
|
ExtensionOrderedList,
|
||||||
ExtensionTable,
|
ExtensionPlaceholder,
|
||||||
|
ExtensionStrike,
|
||||||
ExtensionSubscript,
|
ExtensionSubscript,
|
||||||
ExtensionSuperscript,
|
ExtensionSuperscript,
|
||||||
ExtensionPlaceholder,
|
ExtensionTable,
|
||||||
ExtensionHighlight,
|
ExtensionTaskList,
|
||||||
ExtensionCommands,
|
ExtensionText,
|
||||||
ExtensionIframe,
|
ExtensionTextAlign,
|
||||||
|
ExtensionTrailingNode,
|
||||||
|
ExtensionUnderline,
|
||||||
ExtensionVideo,
|
ExtensionVideo,
|
||||||
ExtensionAudio,
|
|
||||||
ExtensionCodeBlock,
|
|
||||||
ExtensionColor,
|
|
||||||
ExtensionFontSize,
|
|
||||||
lowlight,
|
lowlight,
|
||||||
RichTextEditor,
|
RichTextEditor,
|
||||||
useEditor,
|
useEditor,
|
||||||
ExtensionIndent,
|
|
||||||
ExtensionDraggable,
|
|
||||||
ExtensionColumns,
|
|
||||||
ExtensionColumn,
|
|
||||||
ExtensionNodeSelected,
|
|
||||||
ExtensionTrailingNode,
|
|
||||||
} from '@halo-dev/richtext-editor';
|
} from '@halo-dev/richtext-editor';
|
||||||
|
|
||||||
const content = useLocalStorage('content', '');
|
const content = useLocalStorage('content', '');
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits(['update:width']);
|
const emit = defineEmits(['update:width', 'expandChange']);
|
||||||
|
|
||||||
const innerWidth = ref(props.width || '300px');
|
const innerWidth = ref(props.width || '300px');
|
||||||
|
|
||||||
|
@ -71,8 +71,10 @@
|
||||||
isExpandedLeft.value = !isExpandedLeft.value;
|
isExpandedLeft.value = !isExpandedLeft.value;
|
||||||
if (isExpandedLeft.value) {
|
if (isExpandedLeft.value) {
|
||||||
innerWidth.value = props.width || '300px';
|
innerWidth.value = props.width || '300px';
|
||||||
|
emit('expandChange', true);
|
||||||
} else {
|
} else {
|
||||||
innerWidth.value = '0px';
|
innerWidth.value = '0px';
|
||||||
|
emit('expandChange', false);
|
||||||
}
|
}
|
||||||
// 动画结束,去掉动画类
|
// 动画结束,去掉动画类
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -33,5 +33,9 @@ export default {
|
||||||
modify: 'Modify{name}',
|
modify: 'Modify{name}',
|
||||||
nameIsNotNull: 'Name cannot be empty',
|
nameIsNotNull: 'Name cannot be empty',
|
||||||
nameIsExist: '{name} already exists',
|
nameIsExist: '{name} already exists',
|
||||||
|
empty: 'No Content',
|
||||||
|
loading: 'Loading, please wait',
|
||||||
|
errorStatus: 'Failed to load data, please',
|
||||||
|
retry: 'Retry',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
|
|
||||||
|
import outerClick from './outerClick';
|
||||||
import permission from './permission';
|
import permission from './permission';
|
||||||
import validateLicense from './validateLicense';
|
import validateLicense from './validateLicense';
|
||||||
|
|
||||||
|
@ -7,5 +8,6 @@ export default {
|
||||||
install(Vue: App) {
|
install(Vue: App) {
|
||||||
Vue.directive('permission', permission);
|
Vue.directive('permission', permission);
|
||||||
Vue.directive('xpack', validateLicense);
|
Vue.directive('xpack', validateLicense);
|
||||||
|
Vue.directive('outer', outerClick);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { isServerRendering } from '@/utils/dom';
|
||||||
|
|
||||||
|
import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue';
|
||||||
|
|
||||||
|
export const isElement = (e: unknown): e is Element => {
|
||||||
|
if (typeof Element === 'undefined') return false;
|
||||||
|
return e instanceof Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void;
|
||||||
|
type FlushList = Map<
|
||||||
|
HTMLElement,
|
||||||
|
{
|
||||||
|
documentHandler: DocumentHandler;
|
||||||
|
bindingFn: (...args: unknown[]) => unknown;
|
||||||
|
}[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
const nodeList: FlushList = new Map();
|
||||||
|
|
||||||
|
let startClick: MouseEvent;
|
||||||
|
|
||||||
|
if (!isServerRendering) {
|
||||||
|
document.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
|
startClick = e;
|
||||||
|
});
|
||||||
|
document.addEventListener('mouseup', (e: MouseEvent) => {
|
||||||
|
nodeList.forEach((handlers) => {
|
||||||
|
handlers.forEach(({ documentHandler }) => {
|
||||||
|
documentHandler(e as MouseEvent, startClick);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler {
|
||||||
|
let excludes: HTMLElement[] = [];
|
||||||
|
if (Array.isArray(binding.arg)) {
|
||||||
|
excludes = binding.arg;
|
||||||
|
} else if (isElement(binding.arg)) {
|
||||||
|
// due to current implementation on binding type is wrong the type casting is necessary here
|
||||||
|
excludes.push(binding.arg as unknown as HTMLElement);
|
||||||
|
}
|
||||||
|
return (mouseup, mousedown) => {
|
||||||
|
const { popperRef } = binding.instance as ComponentPublicInstance<{
|
||||||
|
popperRef: HTMLElement;
|
||||||
|
}>;
|
||||||
|
const mouseUpTarget = mouseup.target as Node;
|
||||||
|
const mouseDownTarget = mousedown?.target as Node;
|
||||||
|
const isBound = !binding || !binding.instance;
|
||||||
|
const isTargetExists = !mouseUpTarget || !mouseDownTarget;
|
||||||
|
const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
|
||||||
|
const isSelf = el === mouseUpTarget;
|
||||||
|
|
||||||
|
const isTargetExcluded =
|
||||||
|
(excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
|
||||||
|
(excludes.length && excludes.includes(mouseDownTarget as HTMLElement));
|
||||||
|
const isContainedByPopper = popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget));
|
||||||
|
if (isBound || isTargetExists || isContainedByEl || isSelf || isTargetExcluded || isContainedByPopper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
binding.value(mouseup, mousedown);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClickOutside: ObjectDirective = {
|
||||||
|
beforeMount(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
// there could be multiple handlers on the element
|
||||||
|
if (!nodeList.has(el)) {
|
||||||
|
nodeList.set(el, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeList.get(el)?.push({
|
||||||
|
documentHandler: createDocumentHandler(el, binding),
|
||||||
|
bindingFn: binding.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updated(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
let handlers = nodeList.get(el);
|
||||||
|
if (!handlers) {
|
||||||
|
handlers = [];
|
||||||
|
nodeList.set(el, handlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldHandlerIndex = handlers.findIndex((item) => item.bindingFn === binding.oldValue);
|
||||||
|
const newHandler = {
|
||||||
|
documentHandler: createDocumentHandler(el, binding),
|
||||||
|
bindingFn: binding.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (oldHandlerIndex >= 0) {
|
||||||
|
// replace the old handler to the new handler
|
||||||
|
handlers.splice(oldHandlerIndex, 1, newHandler);
|
||||||
|
} else {
|
||||||
|
handlers.push(newHandler);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unmounted(el: HTMLElement) {
|
||||||
|
// remove all listeners when a component unmounted
|
||||||
|
nodeList.delete(el);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClickOutside;
|
|
@ -19,4 +19,16 @@ export enum MenuEnum {
|
||||||
uiTest = 'uiTest',
|
uiTest = 'uiTest',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {};
|
export enum ShapeFlags {
|
||||||
|
ELEMENT = 1,
|
||||||
|
FUNCTIONAL_COMPONENT = 2,
|
||||||
|
STATEFUL_COMPONENT = 4,
|
||||||
|
COMPONENT = FUNCTIONAL_COMPONENT + STATEFUL_COMPONENT,
|
||||||
|
TEXT_CHILDREN = 8,
|
||||||
|
ARRAY_CHILDREN = 16,
|
||||||
|
SLOTS_CHILDREN = 32,
|
||||||
|
TELEPORT = 64,
|
||||||
|
SUSPENSE = 128,
|
||||||
|
COMPONENT_SHOULD_KEEP_ALIVE = 256,
|
||||||
|
COMPONENT_KEPT_ALIVE = 512,
|
||||||
|
}
|
||||||
|
|
|
@ -18,3 +18,46 @@ export function scrollIntoView(targetRef: HTMLElement | Element | null, options:
|
||||||
|
|
||||||
targetRef?.scrollIntoView(scrollOptions);
|
targetRef?.scrollIntoView(scrollOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const NOOP = () => {
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否为服务端渲染
|
||||||
|
export const isServerRendering = (() => {
|
||||||
|
try {
|
||||||
|
return !(typeof window !== 'undefined' && document !== undefined);
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 监听事件
|
||||||
|
export const on = (() => {
|
||||||
|
if (isServerRendering) {
|
||||||
|
return NOOP;
|
||||||
|
}
|
||||||
|
return <K extends keyof HTMLElementEventMap>(
|
||||||
|
element: HTMLElement | Window,
|
||||||
|
event: K,
|
||||||
|
handler: (ev: HTMLElementEventMap[K]) => void,
|
||||||
|
options: boolean | AddEventListenerOptions = false
|
||||||
|
) => {
|
||||||
|
element.addEventListener(event, handler as EventListenerOrEventListenerObject, options);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 移除监听事件
|
||||||
|
export const off = (() => {
|
||||||
|
if (isServerRendering) {
|
||||||
|
return NOOP;
|
||||||
|
}
|
||||||
|
return <K extends keyof HTMLElementEventMap>(
|
||||||
|
element: HTMLElement | Window,
|
||||||
|
type: K,
|
||||||
|
handler: (ev: HTMLElementEventMap[K]) => void,
|
||||||
|
options: boolean | EventListenerOptions = false
|
||||||
|
) => {
|
||||||
|
element.removeEventListener(type, handler as EventListenerOrEventListenerObject, options);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
|
@ -143,7 +143,7 @@
|
||||||
description: '',
|
description: '',
|
||||||
resourcePoolIds: [],
|
resourcePoolIds: [],
|
||||||
enable: true,
|
enable: true,
|
||||||
moduleIds: [],
|
moduleIds: ['workstation', 'testPlan', 'bugManagement', 'caseManagement', 'apiTest', 'uiTest', 'loadTest'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentVisible = ref(props.visible);
|
const currentVisible = ref(props.visible);
|
||||||
|
@ -162,7 +162,7 @@
|
||||||
form.organizationId = currentOrgId.value;
|
form.organizationId = currentOrgId.value;
|
||||||
form.description = '';
|
form.description = '';
|
||||||
form.enable = true;
|
form.enable = true;
|
||||||
form.moduleIds = [];
|
form.moduleIds = ['workstation', 'testPlan', 'bugManagement', 'caseManagement', 'apiTest', 'uiTest', 'loadTest'];
|
||||||
form.resourcePoolIds = [];
|
form.resourcePoolIds = [];
|
||||||
};
|
};
|
||||||
const handleCancel = (shouldSearch: boolean) => {
|
const handleCancel = (shouldSearch: boolean) => {
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex h-full flex-row">
|
<div class="flex h-full flex-row">
|
||||||
<Transition>
|
<div v-if="leftCollapse" class="user-group-left ms-scroll-bar">
|
||||||
<div v-if="leftCollapse" class="user-group-left ms-scroll-bar">
|
<UserGroupLeft ref="ugLeftRef" @handle-select="handleSelect" @add-user-success="handleAddMember" />
|
||||||
<UserGroupLeft ref="ugLeftRef" @handle-select="handleSelect" />
|
</div>
|
||||||
</div>
|
<div class="usergroup-collapse" :style="{ left: leftCollapse ? '300px' : '0' }" @click="handleCollapse">
|
||||||
</Transition>
|
<icon-double-left v-if="leftCollapse" class="text-[12px] text-[var(--color-text-brand)]" />
|
||||||
<Transition>
|
<icon-double-right v-else class="text-[12px] text-[var(--color-text-brand)]" />
|
||||||
<div class="usergroup-collapse" :style="{ left: leftCollapse ? '300px' : '0' }" @click="handleCollapse">
|
</div>
|
||||||
<icon-double-left v-if="leftCollapse" class="text-[12px] text-[var(--color-text-brand)]" />
|
|
||||||
<icon-double-right v-else class="text-[12px] text-[var(--color-text-brand)]" />
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
<div class="p-[24px]" :style="{ width: leftCollapse ? 'calc(100% - 300px)' : '100%' }">
|
<div class="p-[24px]" :style="{ width: leftCollapse ? 'calc(100% - 300px)' : '100%' }">
|
||||||
<div class="flex flex-row items-center justify-between">
|
<div class="flex flex-row items-center justify-between">
|
||||||
<a-tooltip :content="currentUserGroupItem.name">
|
<a-tooltip :content="currentUserGroupItem.name">
|
||||||
|
@ -125,7 +121,7 @@
|
||||||
leftCollapse.value = !leftCollapse.value;
|
leftCollapse.value = !leftCollapse.value;
|
||||||
if (leftCollapse.value) {
|
if (leftCollapse.value) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
ugLeftRef.value?.initData(currentUserGroupItem.value.id, false);
|
ugLeftRef.value?.initData(currentUserGroupItem.value.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -143,6 +139,11 @@
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
authRef.value?.handleSave();
|
authRef.value?.handleSave();
|
||||||
};
|
};
|
||||||
|
const handleAddMember = (id: string) => {
|
||||||
|
if (id === currentUserGroupItem.value.id) {
|
||||||
|
tableSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
const canSave = computed(() => {
|
const canSave = computed(() => {
|
||||||
if (currentTable.value === 'auth') {
|
if (currentTable.value === 'auth') {
|
||||||
return authRef.value?.canSave;
|
return authRef.value?.canSave;
|
||||||
|
@ -195,12 +196,4 @@
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.v-enter-active,
|
|
||||||
.v-leave-active {
|
|
||||||
transition: opacity 0.5s ease;
|
|
||||||
}
|
|
||||||
.v-enter-from,
|
|
||||||
.v-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -141,7 +141,7 @@
|
||||||
organizationId: '',
|
organizationId: '',
|
||||||
description: '',
|
description: '',
|
||||||
enable: true,
|
enable: true,
|
||||||
moduleIds: [],
|
moduleIds: ['workstation', 'testPlan', 'bugManagement', 'caseManagement', 'apiTest', 'uiTest', 'loadTest'],
|
||||||
resourcePoolIds: [],
|
resourcePoolIds: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@
|
||||||
form.organizationId = '';
|
form.organizationId = '';
|
||||||
form.description = '';
|
form.description = '';
|
||||||
form.enable = true;
|
form.enable = true;
|
||||||
form.moduleIds = [];
|
form.moduleIds = ['workstation', 'testPlan', 'bugManagement', 'caseManagement', 'apiTest', 'uiTest', 'loadTest'];
|
||||||
};
|
};
|
||||||
const handleCancel = (shouldSearch: boolean) => {
|
const handleCancel = (shouldSearch: boolean) => {
|
||||||
emit('cancel', shouldSearch);
|
emit('cancel', shouldSearch);
|
||||||
|
|
|
@ -1,48 +1,47 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex h-full flex-row">
|
<MsSplitBox @expand-change="handleCollapse">
|
||||||
<Transition>
|
<template #left>
|
||||||
<div v-if="leftCollapse" class="user-group-left ms-scroll-bar">
|
<UserGroupLeft ref="ugLeftRef" @handle-select="handleSelect" @add-user-success="handleAddMember" />
|
||||||
<UserGroupLeft ref="ugLeftRef" @handle-select="handleSelect" />
|
</template>
|
||||||
</div>
|
<template #right>
|
||||||
</Transition>
|
<div class="p-[24px]">
|
||||||
<Transition>
|
<div class="flex flex-row items-center justify-between">
|
||||||
<div class="usergroup-collapse" :style="{ left: leftCollapse ? '300px' : '0' }" @click="handleCollapse">
|
<a-tooltip :content="currentUserGroupItem.name">
|
||||||
<icon-double-left v-if="leftCollapse" class="text-[12px] text-[var(--color-text-brand)]" />
|
<div class="one-line-text max-w-[300px]">{{ currentUserGroupItem.name }}</div>
|
||||||
<icon-double-right v-if="!leftCollapse" class="text-[12px] text-[var(--color-text-brand)]" />
|
</a-tooltip>
|
||||||
</div>
|
<div class="flex items-center">
|
||||||
</Transition>
|
<a-input-search
|
||||||
<div class="p-[24px]" :style="{ width: leftCollapse ? 'calc(100% - 300px)' : '100%' }">
|
v-if="currentTable === 'user'"
|
||||||
<div class="flex flex-row items-center justify-between">
|
:placeholder="t('system.user.searchUser')"
|
||||||
<a-tooltip :content="currentUserGroupItem.name">
|
class="w-[240px]"
|
||||||
<div class="one-line-text max-w-[300px]">{{ currentUserGroupItem.name }}</div>
|
allow-clear
|
||||||
</a-tooltip>
|
@press-enter="handleEnter"
|
||||||
<div class="flex items-center">
|
@search="handleSearch"
|
||||||
<a-input-search
|
></a-input-search>
|
||||||
v-if="currentTable === 'user'"
|
<a-radio-group
|
||||||
:placeholder="t('system.user.searchUser')"
|
v-if="couldShowUser && couldShowAuth"
|
||||||
class="w-[240px]"
|
v-model="currentTable"
|
||||||
allow-clear
|
class="ml-[14px]"
|
||||||
@press-enter="handleEnter"
|
type="button"
|
||||||
@search="handleSearch"
|
>
|
||||||
></a-input-search>
|
<a-radio v-if="couldShowAuth" value="auth">{{ t('system.userGroup.auth') }}</a-radio>
|
||||||
<a-radio-group v-if="couldShowUser && couldShowAuth" v-model="currentTable" class="ml-[14px]" type="button">
|
<a-radio v-if="couldShowUser" value="user">{{ t('system.userGroup.user') }}</a-radio>
|
||||||
<a-radio v-if="couldShowAuth" value="auth">{{ t('system.userGroup.auth') }}</a-radio>
|
</a-radio-group>
|
||||||
<a-radio v-if="couldShowUser" value="user">{{ t('system.userGroup.user') }}</a-radio>
|
</div>
|
||||||
</a-radio-group>
|
</div>
|
||||||
|
<div class="mt-[16px]">
|
||||||
|
<UserTable
|
||||||
|
v-if="currentTable === 'user' && couldShowUser"
|
||||||
|
ref="userRef"
|
||||||
|
:keyword="currentKeyword"
|
||||||
|
:current="currentUserGroupItem"
|
||||||
|
/>
|
||||||
|
<AuthTable v-if="currentTable === 'auth' && couldShowAuth" ref="authRef" :current="currentUserGroupItem" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-[16px]">
|
</template>
|
||||||
<UserTable
|
</MsSplitBox>
|
||||||
v-if="currentTable === 'user' && couldShowUser"
|
|
||||||
ref="userRef"
|
|
||||||
:keyword="currentKeyword"
|
|
||||||
:current="currentUserGroupItem"
|
|
||||||
/>
|
|
||||||
<AuthTable v-if="currentTable === 'auth' && couldShowAuth" ref="authRef" :current="currentUserGroupItem" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="currentTable === 'auth'"
|
v-if="currentTable === 'auth'"
|
||||||
|
@ -63,6 +62,7 @@
|
||||||
import { computed, nextTick, onMounted, provide, ref, watchEffect } from 'vue';
|
import { computed, nextTick, onMounted, provide, ref, watchEffect } from 'vue';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
import AuthTable from '@/components/business/ms-user-group-comp/authTable.vue';
|
import AuthTable from '@/components/business/ms-user-group-comp/authTable.vue';
|
||||||
import UserGroupLeft from '@/components/business/ms-user-group-comp/msUserGroupLeft.vue';
|
import UserGroupLeft from '@/components/business/ms-user-group-comp/msUserGroupLeft.vue';
|
||||||
import UserTable from '@/components/business/ms-user-group-comp/userTable.vue';
|
import UserTable from '@/components/business/ms-user-group-comp/userTable.vue';
|
||||||
|
@ -119,13 +119,18 @@
|
||||||
currentUserGroupItem.value = item;
|
currentUserGroupItem.value = item;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddMember = (id: string) => {
|
||||||
|
if (id === currentUserGroupItem.value.id) {
|
||||||
|
tableSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const couldShowUser = computed(() => currentUserGroupItem.value.type === AuthScopeEnum.SYSTEM);
|
const couldShowUser = computed(() => currentUserGroupItem.value.type === AuthScopeEnum.SYSTEM);
|
||||||
const couldShowAuth = computed(() => currentUserGroupItem.value.id !== 'admin');
|
const couldShowAuth = computed(() => currentUserGroupItem.value.id !== 'admin');
|
||||||
const handleCollapse = () => {
|
const handleCollapse = (collapse: boolean) => {
|
||||||
leftCollapse.value = !leftCollapse.value;
|
if (collapse) {
|
||||||
if (leftCollapse.value) {
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
ugLeftRef.value?.initData(currentUserGroupItem.value.id, false);
|
ugLeftRef.value?.initData(currentUserGroupItem.value.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -177,6 +182,7 @@
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
|
padding-bottom: 24px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -195,12 +201,4 @@
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.v-enter-active,
|
|
||||||
.v-leave-active {
|
|
||||||
transition: opacity 0.5s ease;
|
|
||||||
}
|
|
||||||
.v-enter-from,
|
|
||||||
.v-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue