u
This commit is contained in:
1 parent
4aaa93df7d
commit
c330e52fd3
66 files changed
+7386
-4
No files matched your search
@@ -0,0 +1,109 @@
|
||||
<script setup>
|
||||
import { useTabStore } from "@/store/tab";
|
||||
import emitter from "@/utils/emitter";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const tabStore = useTabStore();
|
||||
|
||||
const refreshKey = ref(0);
|
||||
|
||||
// keep-alive include keys
|
||||
const includeKeys = computed(() => tabStore.cachedKeys);
|
||||
|
||||
// 缓存 wrapper 组件实例
|
||||
const wrapperMap = new Map();
|
||||
|
||||
// 刷新函数
|
||||
function refresh() {
|
||||
refreshKey.value += 1;
|
||||
}
|
||||
|
||||
// 生成 wrapper 组件,用于 keep-alive
|
||||
function formatComponentInstance(component, fullPath) {
|
||||
if (!component) return null;
|
||||
|
||||
let wrapper = wrapperMap.get(fullPath);
|
||||
|
||||
if (!wrapper) {
|
||||
wrapper = defineComponent({
|
||||
name: fullPath,
|
||||
inheritAttrs: false,
|
||||
setup(_, { attrs }) {
|
||||
return () => h(component, attrs); // 保证 props 响应式
|
||||
},
|
||||
});
|
||||
|
||||
wrapperMap.set(fullPath, wrapper);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
// watch tabs 变化,自动清理 wrapper
|
||||
watch(
|
||||
() => tabStore.tabs,
|
||||
(tabs) => {
|
||||
const alive = new Set(tabs.map((t) => t.fullPath));
|
||||
for (const key of wrapperMap.keys()) {
|
||||
if (!alive.has(key)) {
|
||||
wrapperMap.delete(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 监听刷新事件
|
||||
onMounted(() => {
|
||||
emitter.on("view.refresh", refresh);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off("view.refresh", refresh);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="g3-content">
|
||||
<RouterView v-slot="{ Component, route: childRoute }">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<template v-if="childRoute.meta.keepAlive">
|
||||
<keep-alive :include="includeKeys">
|
||||
<component
|
||||
:is="formatComponentInstance(Component, childRoute.fullPath)"
|
||||
:key="childRoute.fullPath + '?' + refreshKey"
|
||||
/>
|
||||
</keep-alive>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<component :is="Component" :key="childRoute.fullPath + '?' + refreshKey" />
|
||||
</template>
|
||||
</Transition>
|
||||
</RouterView>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.g3-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-to,
|
||||
.fade-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<el-sub-menu v-if="hasChildren" :index="menuIndex">
|
||||
<template #title>
|
||||
<el-icon v-if="item.icon">
|
||||
<G3Icon :icon="item.icon" />
|
||||
</el-icon>
|
||||
<span class="menu-label">{{ item.label }}</span>
|
||||
</template>
|
||||
|
||||
<MenuItem
|
||||
v-for="(child, idx) in item.children"
|
||||
:key="child.path || idx"
|
||||
:item="child"
|
||||
:parent-index="menuIndex + '-' + idx"
|
||||
/>
|
||||
</el-sub-menu>
|
||||
|
||||
<el-menu-item
|
||||
v-else
|
||||
:index="item.path"
|
||||
:route="{
|
||||
path: item.path,
|
||||
}"
|
||||
>
|
||||
<el-icon v-if="item.icon">
|
||||
<G3Icon :icon="item.icon" />
|
||||
</el-icon>
|
||||
<span class="menu-label">{{ item.label }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
item: { type: Object, required: true },
|
||||
parentIndex: { type: String, default: "0" },
|
||||
});
|
||||
|
||||
const hasChildren = props.item.children && props.item.children.length > 0;
|
||||
|
||||
// 父菜单没有 path,用索引生成唯一 index,子菜单 path 就用真实路由
|
||||
const menuIndex = props.item.path || props.parentIndex;
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu-label {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,162 @@
|
||||
<script lang="js" setup>
|
||||
import { loadDataApi } from "@/request/api";
|
||||
import MenuItem from "./menu-item.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const title = ref("G3后台管理系统");
|
||||
const route = useRoute();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
onMounted(() => {
|
||||
loadDataApi("s_keyvalue", `b_key='bodycaption'`, ``, ["b_value"]).then((res) => {
|
||||
if (res.success) {
|
||||
title.value = res.data[0].b_value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const menuList = ref([
|
||||
{
|
||||
label: "首页",
|
||||
path: "/",
|
||||
icon: "lucide:house",
|
||||
},
|
||||
{
|
||||
label: "业务管理",
|
||||
icon: "lucide:briefcase-business",
|
||||
children: [
|
||||
{
|
||||
label: "海运管理",
|
||||
path: "/business",
|
||||
},
|
||||
{
|
||||
label: "装箱统计",
|
||||
path: "/business/field",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "系统管理",
|
||||
icon: "lucide:settings",
|
||||
children: [
|
||||
{
|
||||
label: "字段配置",
|
||||
path: "/system/field",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const selectedKey = computed(() => route.fullPath);
|
||||
|
||||
function handleMenuChange(val) {
|
||||
router.push(val);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="g3-menu">
|
||||
<div class="g3-logo">
|
||||
<div class="logo-inner">
|
||||
<img src="@/assets/logo.png" alt="logo" />
|
||||
<div class="title">{{ title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-menu
|
||||
class="el-menu-vertical"
|
||||
:collapse-transition="false"
|
||||
:default-active="selectedKey"
|
||||
@select="handleMenuChange"
|
||||
>
|
||||
<MenuItem
|
||||
v-for="(item, idx) in menuList"
|
||||
:key="item.path || idx"
|
||||
:item="item"
|
||||
:parent-index="idx.toString()"
|
||||
/>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.g3-menu {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.g3-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 42px;
|
||||
padding: 16px 4px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
|
||||
.logo-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 24px;
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #70cfb1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: calc(100% - 90px);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-vertical {
|
||||
overflow-y: auto;
|
||||
border: none;
|
||||
margin: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
:deep(.el-sub-menu__title) {
|
||||
height: 36px !important;
|
||||
line-height: 36px !important;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item) {
|
||||
height: 36px !important;
|
||||
line-height: 36px !important;
|
||||
border-radius: 6px;
|
||||
|
||||
&.is-active {
|
||||
background-color: var(--el-color-primary-light-8);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu) {
|
||||
.el-sub-menu__title {
|
||||
border-radius: 6px;
|
||||
}
|
||||
.el-menu-item {
|
||||
padding-left: 48px !important;
|
||||
}
|
||||
&.is-active {
|
||||
.el-sub-menu__title {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,352 @@
|
||||
<script lang="js" setup>
|
||||
import { useTabStore } from "@/store/tab";
|
||||
import { useRoute } from "vue-router";
|
||||
import emitter from "@/utils/emitter";
|
||||
|
||||
const { refs, setRefs } = useRefs();
|
||||
const showScroll = ref(false);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const tabStore = useTabStore();
|
||||
|
||||
const fullScreen = ref(false);
|
||||
|
||||
const emits = defineEmits(["fullScreen-change"]);
|
||||
|
||||
watch(
|
||||
() => tabStore.activeTab,
|
||||
(newVal) => {
|
||||
nextTick(() => {
|
||||
updateScroll();
|
||||
|
||||
const tabIndex = tabStore.findTabIndex(newVal);
|
||||
moveScroll(tabIndex);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
function useRefs() {
|
||||
const refs = reactive({});
|
||||
|
||||
function setRefs(name) {
|
||||
return (el) => {
|
||||
refs[name] = el;
|
||||
return () => refs[name];
|
||||
};
|
||||
}
|
||||
|
||||
return { refs, setRefs };
|
||||
}
|
||||
|
||||
function updateScroll() {
|
||||
const wrap = refs.scroller;
|
||||
if (!wrap) return;
|
||||
showScroll.value = wrap.scrollWidth > wrap.clientWidth;
|
||||
}
|
||||
|
||||
function moveScroll(index) {
|
||||
nextTick(() => {
|
||||
const el = refs[`item-${index}`];
|
||||
const wrap = refs.scroller;
|
||||
if (!el || !wrap) return;
|
||||
|
||||
let attempts = 0;
|
||||
const maxAttempts = 5;
|
||||
|
||||
const scrollToActiveTab = () => {
|
||||
const scrollLeft =
|
||||
el.getBoundingClientRect().left -
|
||||
wrap.getBoundingClientRect().left +
|
||||
wrap.scrollLeft -
|
||||
wrap.clientWidth / 2 +
|
||||
el.clientWidth / 2;
|
||||
|
||||
wrap.scrollLeft = scrollLeft;
|
||||
|
||||
if (
|
||||
Math.abs(wrap.scrollLeft - scrollLeft) > 1 &&
|
||||
attempts < maxAttempts
|
||||
) {
|
||||
attempts++;
|
||||
requestAnimationFrame(scrollToActiveTab);
|
||||
}
|
||||
};
|
||||
|
||||
scrollToActiveTab();
|
||||
});
|
||||
}
|
||||
|
||||
// 切换标签
|
||||
function switchTab(tab, index) {
|
||||
router.push(tab.fullPath);
|
||||
moveScroll(index);
|
||||
}
|
||||
|
||||
// 关闭标签
|
||||
async function closeTab(tab) {
|
||||
const activePath = await tabStore.closeTab(tab.fullPath);
|
||||
if (activePath && route.fullPath !== activePath) {
|
||||
router.push(activePath);
|
||||
} else if (!activePath) {
|
||||
router.push("/");
|
||||
}
|
||||
}
|
||||
|
||||
function closeLeft(data) {
|
||||
const currentIndex = tabStore.tabs.findIndex(
|
||||
(item) => item.fullPath === data.fullPath,
|
||||
);
|
||||
if (currentIndex === -1) return;
|
||||
|
||||
// 关闭左侧所有标签
|
||||
tabStore.tabs.slice(0, currentIndex).forEach((item) => {
|
||||
const index = tabStore.tabs.findIndex((t) => t.fullPath === item.fullPath);
|
||||
if (index !== -1) tabStore.tabs.splice(index, 1);
|
||||
if (item.fullPath === route.fullPath) {
|
||||
router.push(data.fullPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeRight(data) {
|
||||
const currentIndex = tabStore.tabs.findIndex(
|
||||
(item) => item.fullPath === data.fullPath,
|
||||
);
|
||||
if (currentIndex === -1) return;
|
||||
|
||||
tabStore.tabs.slice(currentIndex + 1).forEach((item) => {
|
||||
const index = tabStore.tabs.findIndex((t) => t.fullPath === item.fullPath);
|
||||
if (index !== -1) tabStore.tabs.splice(index, 1);
|
||||
if (item.fullPath === route.fullPath) {
|
||||
router.push(data.fullPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeOther(tab) {
|
||||
tabStore.tabs = tabStore.tabs.filter((t) => t.fullPath === tab.fullPath);
|
||||
|
||||
router.push(tab.fullPath);
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
tabStore.tabs = [];
|
||||
router.push("/");
|
||||
}
|
||||
|
||||
function toHome() {
|
||||
router.push("/");
|
||||
}
|
||||
|
||||
function refreshPage() {
|
||||
emitter.emit("view.refresh");
|
||||
}
|
||||
|
||||
function fullScreenPage() {
|
||||
fullScreen.value = !fullScreen.value;
|
||||
|
||||
emits("fullScreen-change", fullScreen.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="g3-tabs">
|
||||
<div class="g3-tab-op">
|
||||
<el-tooltip content="刷新">
|
||||
<div class="g3-action-icon" @click="refreshPage">
|
||||
<G3Icon icon="lucide:refresh-ccw" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="首页">
|
||||
<div class="g3-action-icon" @click="toHome">
|
||||
<G3Icon icon="lucide:house" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip :content="fullScreen ? '恢复' : '全屏'">
|
||||
<div class="g3-action-icon" @click="fullScreenPage">
|
||||
<G3Icon :icon="fullScreen ? 'lucide:minimize' : 'lucide:maximize'" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="g3-divider"></div>
|
||||
|
||||
<div v-if="showScroll" class="g3-action-icon g3-scroll-more">
|
||||
<G3Icon icon="lucide:chevrons-left" />
|
||||
</div>
|
||||
|
||||
<div class="g3-tab-container">
|
||||
<div :ref="setRefs('scroller')" class="app-tab-scroller">
|
||||
<el-dropdown
|
||||
v-for="(item, index) in tabStore.tabs"
|
||||
:key="item.fullPath"
|
||||
trigger="contextmenu"
|
||||
:persistent="false"
|
||||
>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu v-if="item.fullPath !== '/'">
|
||||
<el-dropdown-item @click="closeTab(item)">
|
||||
关闭
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="closeOther(item)">
|
||||
关闭其他
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="closeLeft(item)">
|
||||
关闭左侧
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="closeRight(item)">
|
||||
关闭右侧
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="closeAll"> 关闭所有 </el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
<div
|
||||
:ref="setRefs(`item-${index}`)"
|
||||
class="app-tab-item"
|
||||
:class="{ active: item.fullPath === tabStore.activeTab }"
|
||||
@click="switchTab(item)"
|
||||
>
|
||||
<span class="label">{{ item.title }}</span>
|
||||
<G3Icon
|
||||
v-if="item.fullPath !== '/'"
|
||||
icon="lucide:x"
|
||||
size="14"
|
||||
class="icon"
|
||||
@click.stop="closeTab(item)"
|
||||
/>
|
||||
</div>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showScroll"
|
||||
class="g3-action-icon g3-scroll-more"
|
||||
style="margin: 0px 10px"
|
||||
>
|
||||
<G3Icon icon="lucide:chevrons-right" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.g3-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
padding-left: 10px;
|
||||
user-select: none;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e4e4e7;
|
||||
}
|
||||
|
||||
.g3-tab-op {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.g3-action-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
&:hover {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.g3-divider {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background-color: #ddd;
|
||||
margin: 0px 12px;
|
||||
}
|
||||
|
||||
.g3-tab-container {
|
||||
height: 30px;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
:deep(.el-dropdown) {
|
||||
margin-right: 12px;
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-tab-scroller {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
scrollbar-width: none;
|
||||
overflow: hidden;
|
||||
scroll-behavior: smooth;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-tab-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 28px;
|
||||
padding: 0px 8px;
|
||||
cursor: pointer;
|
||||
color: #18181b;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: #fff;
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-8);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--el-color-primary-light-8);
|
||||
.label {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.icon {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.g3-scroll-more {
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<script lang="js" setup>
|
||||
const props = defineProps({
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(["collapse"]);
|
||||
|
||||
function onCollapse() {
|
||||
emits("collapse");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="g3-topbar">
|
||||
<div class="g3-topbar-left">
|
||||
<div class="g3-action-icon" @click="onCollapse()">
|
||||
<G3Icon
|
||||
:icon="
|
||||
props.collapse
|
||||
? 'lucide:list-indent-increase'
|
||||
: 'lucide:list-indent-decrease'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="g3-topbar-right">
|
||||
<div class="user-card compact">
|
||||
<G3Icon icon="lucide:circle-user-round" size="18" />
|
||||
<div class="user-info">
|
||||
<span class="user-name">001</span>
|
||||
<!-- <span class="divider">·</span>
|
||||
<span class="user-org">g3cd</span> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.g3-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 100%;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #e4e4e7;
|
||||
padding: 0 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.g3-action-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 14px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-org {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0 4px;
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user