项目初始化
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div class="quick-login-card">
|
||||
<div class="card-title">
|
||||
<span>快捷登录</span>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<el-button
|
||||
class="quick-login-nav quick-login-nav--left"
|
||||
circle
|
||||
link
|
||||
type="primary"
|
||||
:icon="ArrowLeftBold"
|
||||
:disabled="!canScroll"
|
||||
@click="scrollPrev"
|
||||
/>
|
||||
<div ref="viewportRef" class="quick-login-viewport" @mouseenter="pauseAutoScroll" @mouseleave="startAutoScroll">
|
||||
<div
|
||||
class="quick-login-track"
|
||||
:style="{
|
||||
transform: `translateX(-${currentOffset}px)`,
|
||||
}"
|
||||
>
|
||||
<button
|
||||
v-for="item in displayList"
|
||||
:key="item.renderKey"
|
||||
class="quick-login-item"
|
||||
type="button"
|
||||
@click="handleLogin(item)"
|
||||
>
|
||||
<div class="quick-login-item__image-wrap">
|
||||
<img class="quick-login-item__image" :src="item.logo" :alt="item.name" />
|
||||
</div>
|
||||
<div class="quick-login-item__name">{{ item.name }}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
class="quick-login-nav quick-login-nav--right"
|
||||
circle
|
||||
link
|
||||
type="primary"
|
||||
:icon="ArrowRightBold"
|
||||
:disabled="!canScroll"
|
||||
@click="scrollNext"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
|
||||
interface QuickLoginItem {
|
||||
id: string;
|
||||
name: string;
|
||||
logo: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface RenderQuickLoginItem extends QuickLoginItem {
|
||||
renderKey: string;
|
||||
}
|
||||
|
||||
const baseList = ref<QuickLoginItem[]>([
|
||||
{
|
||||
id: 'oa',
|
||||
name: '协同办公',
|
||||
logo: 'https://dummyimage.com/96x96/2563eb/ffffff.png&text=OA',
|
||||
url: '/oa',
|
||||
},
|
||||
{
|
||||
id: 'erp',
|
||||
name: 'ERP系统',
|
||||
logo: 'https://dummyimage.com/96x96/0891b2/ffffff.png&text=ERP',
|
||||
url: '/erp',
|
||||
},
|
||||
{
|
||||
id: 'mes',
|
||||
name: 'MES平台',
|
||||
logo: 'https://dummyimage.com/96x96/7c3aed/ffffff.png&text=MES',
|
||||
url: '/mes',
|
||||
},
|
||||
{
|
||||
id: 'crm',
|
||||
name: 'CRM系统',
|
||||
logo: 'https://dummyimage.com/96x96/ea580c/ffffff.png&text=CRM',
|
||||
url: '/crm',
|
||||
},
|
||||
{
|
||||
id: 'hr',
|
||||
name: '人资门户',
|
||||
logo: 'https://dummyimage.com/96x96/16a34a/ffffff.png&text=HR',
|
||||
url: '/hr',
|
||||
},
|
||||
{
|
||||
id: 'bi',
|
||||
name: '数据驾驶舱',
|
||||
logo: 'https://dummyimage.com/96x96/db2777/ffffff.png&text=BI',
|
||||
url: '/bi',
|
||||
},
|
||||
]);
|
||||
|
||||
const itemWidth = 104;
|
||||
const gapWidth = 2;
|
||||
const stepWidth = itemWidth + gapWidth;
|
||||
const visibleCount = ref(1);
|
||||
const currentIndex = ref(0);
|
||||
const viewportRef = ref<HTMLElement>();
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
let autoScrollTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const canScroll = computed(() => baseList.value.length > visibleCount.value);
|
||||
|
||||
const maxIndex = computed(() => {
|
||||
const value = baseList.value.length - visibleCount.value;
|
||||
return value > 0 ? value : 0;
|
||||
});
|
||||
|
||||
const currentOffset = computed(() => currentIndex.value * stepWidth);
|
||||
|
||||
const displayList = computed<RenderQuickLoginItem[]>(() => {
|
||||
return baseList.value.map((item, index) => ({
|
||||
...item,
|
||||
renderKey: `${item.id}-${index}`,
|
||||
}));
|
||||
});
|
||||
|
||||
function updateVisibleCount() {
|
||||
const width = viewportRef.value?.clientWidth || 0;
|
||||
if (!width) return;
|
||||
const count = Math.max(1, Math.floor((width + gapWidth) / stepWidth));
|
||||
visibleCount.value = count;
|
||||
if (currentIndex.value > maxIndex.value) {
|
||||
currentIndex.value = maxIndex.value;
|
||||
}
|
||||
}
|
||||
|
||||
function scrollNext() {
|
||||
if (!canScroll.value) return;
|
||||
currentIndex.value = currentIndex.value >= maxIndex.value ? 0 : currentIndex.value + 1;
|
||||
}
|
||||
|
||||
function scrollPrev() {
|
||||
if (!canScroll.value) return;
|
||||
currentIndex.value = currentIndex.value <= 0 ? maxIndex.value : currentIndex.value - 1;
|
||||
}
|
||||
|
||||
function startAutoScroll() {
|
||||
if (!canScroll.value) return;
|
||||
pauseAutoScroll();
|
||||
autoScrollTimer = setInterval(() => {
|
||||
scrollNext();
|
||||
}, 2600);
|
||||
}
|
||||
|
||||
function pauseAutoScroll() {
|
||||
if (autoScrollTimer) {
|
||||
clearInterval(autoScrollTimer);
|
||||
autoScrollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleLogin(item: QuickLoginItem) {
|
||||
ElMessage.success(`准备进入:${item.name}`);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
updateVisibleCount();
|
||||
startAutoScroll();
|
||||
});
|
||||
|
||||
if (viewportRef.value) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
updateVisibleCount();
|
||||
});
|
||||
resizeObserver.observe(viewportRef.value);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
resizeObserver?.disconnect();
|
||||
pauseAutoScroll();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.quick-login-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: rgb(51 65 85);
|
||||
border-bottom: 1px solid rgb(226 232 240);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 6px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 2px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.quick-login-nav {
|
||||
flex-shrink: 0;
|
||||
width: 24px;
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.quick-login-viewport {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quick-login-track {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
transition: transform 0.45s ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.quick-login-item {
|
||||
flex: 0 0 104px;
|
||||
width: 104px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
padding: 4px 4px 3px;
|
||||
border: 1px solid rgb(226 232 240);
|
||||
border-radius: 10px;
|
||||
background: rgb(248 250 252);
|
||||
box-shadow: 0 8px 22px rgb(148 163 184 / 14%);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
border-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgb(147 197 253);
|
||||
box-shadow: 0 12px 28px rgb(96 165 250 / 18%);
|
||||
}
|
||||
|
||||
&__image-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 100%;
|
||||
min-height: 26px;
|
||||
width: 100%;
|
||||
margin-top: 2px;
|
||||
padding-top: 2px;
|
||||
border-top: 1px solid rgb(226 232 240);
|
||||
color: rgb(51 65 85);
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .quick-login-card {
|
||||
.card-title {
|
||||
color: rgb(203 213 225);
|
||||
border-bottom-color: rgb(51 65 85);
|
||||
}
|
||||
|
||||
.quick-login-item {
|
||||
border-color: rgb(51 65 85);
|
||||
background: rgb(20, 20, 20);
|
||||
box-shadow: 0 10px 24px rgb(0 0 0 / 24%);
|
||||
|
||||
&:hover {
|
||||
border-color: rgb(96 165 250);
|
||||
box-shadow: 0 14px 30px rgb(37 99 235 / 20%);
|
||||
}
|
||||
|
||||
&__name {
|
||||
border-top-color: rgb(51 65 85);
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.quick-login-card {
|
||||
.card-content {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.quick-login-item {
|
||||
flex-basis: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.quick-login-nav {
|
||||
width: 22px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -16,7 +16,9 @@
|
||||
</div>
|
||||
<div class="analysis-right">
|
||||
<div class="analysis-right-top">
|
||||
<section class="analysis-panel">右上-1</section>
|
||||
<section class="analysis-panel">
|
||||
<QuickLogin />
|
||||
</section>
|
||||
<section class="analysis-panel">右上-2</section>
|
||||
</div>
|
||||
<section class="analysis-panel">右下</section>
|
||||
@@ -31,6 +33,8 @@
|
||||
import HostInfo from './components/HostInfo.vue';
|
||||
import TodoInfo from './components/TodoInfo.vue';
|
||||
import NoticeInfo from './components/NoticeInfo.vue';
|
||||
import QuickLogin from './components/QuickLogin.vue';
|
||||
|
||||
</script>
|
||||
<style lang="less">
|
||||
@dark-bg: #141414;
|
||||
|
||||
Reference in New Issue
Block a user