651 lines
17 KiB
Vue
651 lines
17 KiB
Vue
<!--
|
||
基于 SCUI 重构适配 vite 加载和适配相关业务页面。https://gitee.com/lolicode/scui/tree/master/src/views/home
|
||
-->
|
||
<template>
|
||
<div ref="main" :class="['widgets-home', customizing ? 'customizing' : '']">
|
||
<div class="widgets-content">
|
||
<!-- <TopNav /> -->
|
||
<TopNavSpdm />
|
||
<div class="widgets-top">
|
||
<div class="flex justify-end custom_btn">
|
||
<el-button v-if="customizing" type="primary" round @click="save">{{ $t('widgets.index.0910663-0') }}</el-button>
|
||
<el-button v-else type="primary" round @click="custom">{{ $t('Crontab.index.7634731-6') }}</el-button>
|
||
</div>
|
||
</div>
|
||
<div ref="widgets" class="widgets">
|
||
<div class="widgets-wrapper">
|
||
<div v-if="nowComponentList.length <= 0" class="no-widgets">
|
||
<el-empty :description="$t('widgets.index.0910663-2')" :image-size="280"></el-empty>
|
||
</div>
|
||
<!-- 默认布局 SPDM CODE -->
|
||
<el-row v-if="isDefaultLayout" :gutter="16">
|
||
<!-- 第一列 -->
|
||
<el-col :md="8" :xs="24">
|
||
<draggable
|
||
v-model="grid.copmsList[0]"
|
||
animation="200"
|
||
handle=".customize-overlay"
|
||
group="people"
|
||
item-key="com"
|
||
dragClass="aaaaa"
|
||
force-fallback
|
||
fallbackOnBody
|
||
class="draggable-box"
|
||
>
|
||
<template #item="{ element }">
|
||
<div class="widgets-item" :class="{ 'inline-flex': allComps[element]?.isInline }">
|
||
<component :is="allComps[element]"></component>
|
||
<div v-if="customizing" class="customize-overlay">
|
||
<el-button class="close" type="danger" plain icon="Close" size="small" @click="remove(element)"></el-button>
|
||
<label>
|
||
<el-icon>
|
||
<component :is="allComps[element].icon" />
|
||
</el-icon>
|
||
{{ $t(allComps[element].title) }}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</draggable>
|
||
</el-col>
|
||
<!-- 第二列 要分两块-->
|
||
<el-col :md="16" :xs="24">
|
||
<!-- 第一行 分成左右两块 -->
|
||
<el-row :gutter="16">
|
||
<el-col :md="16" :xs="24">
|
||
<draggable
|
||
v-model="grid.copmsList[1]"
|
||
animation="200"
|
||
handle=".customize-overlay"
|
||
group="people"
|
||
item-key="com"
|
||
dragClass="aaaaa"
|
||
force-fallback
|
||
fallbackOnBody
|
||
class="draggable-box"
|
||
>
|
||
<template #item="{ element }">
|
||
<div class="widgets-item" :class="{ 'inline-flex': allComps[element]?.isInline }">
|
||
<component :is="allComps[element]"></component>
|
||
<div v-if="customizing" class="customize-overlay">
|
||
<el-button class="close" type="danger" plain icon="Close" size="small" @click="remove(element)"></el-button>
|
||
<label>
|
||
<el-icon>
|
||
<component :is="allComps[element].icon" />
|
||
</el-icon>
|
||
{{ $t(allComps[element].title) }}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</draggable>
|
||
</el-col>
|
||
<el-col :md="8" :xs="24">
|
||
<draggable
|
||
v-model="grid.copmsList[2]"
|
||
animation="200"
|
||
handle=".customize-overlay"
|
||
group="people"
|
||
item-key="com"
|
||
dragClass="aaaaa"
|
||
force-fallback
|
||
fallbackOnBody
|
||
class="draggable-box"
|
||
>
|
||
<template #item="{ element }">
|
||
<div class="widgets-item" :class="{ 'inline-flex': allComps[element]?.isInline }">
|
||
<component :is="allComps[element]"></component>
|
||
<div v-if="customizing" class="customize-overlay">
|
||
<el-button class="close" type="danger" plain icon="Close" size="small" @click="remove(element)"></el-button>
|
||
<label>
|
||
<el-icon>
|
||
<component :is="allComps[element].icon" />
|
||
</el-icon>
|
||
{{ $t(allComps[element].title) }}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</draggable>
|
||
</el-col>
|
||
</el-row>
|
||
<!-- 第二行 -->
|
||
<el-row :gutter="16">
|
||
<el-col :md="24" :xs="24">
|
||
<draggable
|
||
v-model="grid.copmsList[3]"
|
||
animation="200"
|
||
handle=".customize-overlay"
|
||
group="people"
|
||
item-key="com"
|
||
dragClass="aaaaa"
|
||
force-fallback
|
||
fallbackOnBody
|
||
class="draggable-box"
|
||
>
|
||
<template #item="{ element }">
|
||
<div class="widgets-item" :class="{ 'inline-flex': allComps[element]?.isInline }">
|
||
<component :is="allComps[element]"></component>
|
||
<div v-if="customizing" class="customize-overlay">
|
||
<el-button class="close" type="danger" plain icon="Close" size="small" @click="remove(element)"></el-button>
|
||
<label>
|
||
<el-icon>
|
||
<component :is="allComps[element].icon" />
|
||
</el-icon>
|
||
{{ $t(allComps[element].title) }}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</draggable>
|
||
</el-col>
|
||
</el-row>
|
||
</el-col>
|
||
</el-row>
|
||
<!-- 自定义布局 -->
|
||
<el-row v-else :gutter="16">
|
||
<el-col v-for="(item, index) in grid.layout" v-bind:key="index" :gutter="16" :md="item" :xs="24">
|
||
<draggable
|
||
v-model="grid.copmsList[index]"
|
||
animation="200"
|
||
handle=".customize-overlay"
|
||
group="people"
|
||
item-key="com"
|
||
dragClass="aaaaa"
|
||
force-fallback
|
||
fallbackOnBody
|
||
class="draggable-box"
|
||
>
|
||
<template #item="{ element }">
|
||
<div class="widgets-item" :class="{ 'inline-flex': allComps[element]?.isInline }">
|
||
<component :is="allComps[element]"></component>
|
||
<div v-if="customizing" class="customize-overlay">
|
||
<el-button class="close" type="danger" plain icon="Close" size="small" @click="remove(element)"></el-button>
|
||
<label>
|
||
<el-icon>
|
||
<component :is="allComps[element].icon" />
|
||
</el-icon>
|
||
{{ $t(allComps[element].title) }}</label
|
||
>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</draggable>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-if="customizing" class="widgets-aside">
|
||
<el-container>
|
||
<el-header>
|
||
<div class="widgets-aside-title">{{ $t('widgets.index.0910663-3') }}</div>
|
||
<div class="widgets-aside-close" @click="close()">
|
||
<el-icon><Close /></el-icon>
|
||
</div>
|
||
</el-header>
|
||
<el-header style="height: auto">
|
||
<div class="selectLayout">
|
||
<div class="selectLayout-item item01" :class="{ active: grid.layout.join(',') === '12,6,6' }" @click="setLayout([12, 6, 6])">
|
||
<el-row :gutter="2">
|
||
<el-col :span="7"><span></span></el-col>
|
||
<el-col :span="7"><span></span></el-col>
|
||
<el-col :span="10"><span></span></el-col>
|
||
</el-row>
|
||
</div>
|
||
<div class="selectLayout-item item02" :class="{ active: grid.layout.join(',') === '24,16,8' }" @click="setLayout([24, 16, 8])">
|
||
<el-row :gutter="2">
|
||
<el-col :span="24"><span></span></el-col>
|
||
<el-col :span="16"><span></span></el-col>
|
||
<el-col :span="8"><span></span></el-col>
|
||
</el-row>
|
||
</div>
|
||
<!-- <div class="selectLayout-item item03" :class="{ active: grid.layout.join(',') === '24' }" @click="setLayout([24])">
|
||
<el-row :gutter="2">
|
||
<el-col :span="24"><span></span></el-col>
|
||
<el-col :span="24"><span></span></el-col>
|
||
<el-col :span="24"><span></span></el-col>
|
||
</el-row>
|
||
</div> -->
|
||
</div>
|
||
</el-header>
|
||
<el-main class="nopadding">
|
||
<div class="widgets-list">
|
||
<div v-if="myCompsList.length <= 0" class="widgets-list-nodata">
|
||
<el-empty :description="$t('widgets.index.0910663-2')" :image-size="60"></el-empty>
|
||
</div>
|
||
<div v-for="item in myCompsList" :key="item.title" class="widgets-list-item">
|
||
<div class="item-logo">
|
||
<el-icon>
|
||
<component :is="item.icon" />
|
||
</el-icon>
|
||
</div>
|
||
<div class="item-info">
|
||
<h2>{{ $t(item.title) }}</h2>
|
||
<p>{{ $t(item.description) }}</p>
|
||
</div>
|
||
<div class="item-actions">
|
||
<el-button type="primary" icon="el-icon-plus" size="small" @click="push(item)"></el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-main>
|
||
<el-footer style="height: 51px">
|
||
<el-button size="small" @click="backDefault()">{{ $t('widgets.index.0910663-4') }}</el-button>
|
||
</el-footer>
|
||
</el-container>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" name="widgets" setup>
|
||
const TopNav = defineAsyncComponent(() => import('./components/TopNav.vue'));
|
||
// SPDM CODE
|
||
const TopNavSpdm = defineAsyncComponent(() => import('../../../spdm/views/home/components/TopNavSpdm.vue'));
|
||
|
||
import draggable from 'vuedraggable';
|
||
import allComps from './components/index';
|
||
import { Local } from '/@/utils/storage';
|
||
import { useUserInfo } from '/@/stores/userInfo';
|
||
import { cloneDeep, flatten } from 'lodash';
|
||
|
||
// todo 默认布局设置
|
||
const defaultGrid = ref({
|
||
// layout: [8, 16],
|
||
layout: [8, 8, 8],
|
||
// SPDM CODE
|
||
// copmsList: [['ToDoCalendar', 'favorite-menu'], ['TaskMessage'], ['TaskList']],
|
||
copmsList: [
|
||
['ToDoCalendar'], // 第一列,占8
|
||
['TaskMessage'], // 第二列,占8
|
||
['favorite-menu'], // 第三列,占8
|
||
['TaskList'], // 第四列,占8
|
||
],
|
||
});
|
||
const customizing = ref(false);
|
||
const widgets = ref();
|
||
const widgetsKey = ref('widgets');
|
||
const grid = ref(cloneDeep(toValue(defaultGrid)));
|
||
// 默认布局是4组
|
||
// const isDefaultLayout = ref(true);
|
||
const isDefaultLayout = ref(grid.value.copmsList.length === 4);
|
||
|
||
const allComponentList = computed(() => {
|
||
const list = [];
|
||
for (const [key, { title, icon, description, isInline }] of Object.entries(allComps)) {
|
||
list.push({ key, title, icon, description, isInline });
|
||
}
|
||
|
||
const myComponentList = grid.value.copmsList.flat();
|
||
list.forEach((comp) => {
|
||
const existingItem = myComponentList.find((item) => item === comp.key);
|
||
comp.disabled = !!existingItem;
|
||
});
|
||
|
||
return list;
|
||
});
|
||
|
||
const myCompsList = computed(() => {
|
||
// 支持列表 SPDM CODE
|
||
const myGrid = [
|
||
'docking-tianyu',
|
||
'docking-zw-3d',
|
||
'docking-intesim-cae',
|
||
'calendar',
|
||
'current-user',
|
||
'news',
|
||
'audit-log',
|
||
'sys-log',
|
||
'flow-data',
|
||
'favorite-menu',
|
||
'favorite-flow',
|
||
'sys-log-line',
|
||
'demo-chart1',
|
||
'demo-chart2',
|
||
'task-info',
|
||
'task-list',
|
||
'task-compare-chart',
|
||
'task-trend-chart',
|
||
'TaskMessage',
|
||
'TaskList',
|
||
'ToDoCalendar',
|
||
];
|
||
return allComponentList.value.filter((item) => !item.disabled && myGrid.includes(item.key));
|
||
});
|
||
|
||
// setTimeout(() => {
|
||
// console.log('-----------myCompsList>>>>>>>>>', myCompsList.value);
|
||
// console.log('-----------allComponentList>>>>>>>>>', allComponentList.value);
|
||
// console.log('allComps>>>>>>', allComps) // 对象形式
|
||
// }, 5000)
|
||
|
||
const nowComponentList = computed(() => flatten(grid.value.copmsList));
|
||
|
||
const custom = () => {
|
||
customizing.value = true;
|
||
nextTick(() => {
|
||
const oldWidth = widgets.value.offsetWidth;
|
||
const scale = widgets.value.offsetWidth / oldWidth;
|
||
widgets.value.style.transform = `scale(${scale})`;
|
||
});
|
||
};
|
||
|
||
const setLayout = (layout: Array<number>) => {
|
||
grid.value.layout = layout;
|
||
// 默认布局是4组,非默认布局是3组,所以要合并,不然会丢组件
|
||
if (isDefaultLayout.value && grid.value.copmsList.length === 4) {
|
||
grid.value.copmsList[1] = [...grid.value.copmsList[1], ...grid.value.copmsList[3]];
|
||
grid.value.copmsList.splice(3, 1);
|
||
}
|
||
isDefaultLayout.value = false;
|
||
// if (layout.join(',') === '24') {
|
||
// if (grid.value.copmsList[1]) {
|
||
// grid.value.copmsList[0].push(...grid.value.copmsList[1]);
|
||
// }
|
||
// if (grid.value.copmsList[2]) {
|
||
// grid.value.copmsList[0].push(...grid.value.copmsList[2]);
|
||
// }
|
||
// grid.value.copmsList.splice(1, 2);
|
||
// }
|
||
};
|
||
|
||
const push = (item: any) => {
|
||
grid.value.copmsList[0].push(item.key);
|
||
};
|
||
|
||
const remove = (item: any) => {
|
||
grid.value.copmsList = grid.value.copmsList.map((obj) => obj.filter((o) => o !== item));
|
||
};
|
||
|
||
const save = () => {
|
||
customizing.value = false;
|
||
widgets.value.style.removeProperty('transform');
|
||
Local.set(widgetsKey.value, JSON.stringify(grid.value));
|
||
};
|
||
|
||
const backDefault = () => {
|
||
isDefaultLayout.value = true;
|
||
customizing.value = false;
|
||
widgets.value.style.removeProperty('transform');
|
||
grid.value = cloneDeep(toValue(defaultGrid));
|
||
Local.remove(widgetsKey.value);
|
||
};
|
||
|
||
const close = () => {
|
||
customizing.value = false;
|
||
widgets.value.style.removeProperty('transform');
|
||
};
|
||
|
||
onMounted(() => {
|
||
// 初始化key
|
||
const data = useUserInfo().userInfos;
|
||
widgetsKey.value = `${window.location.host}_${data.user.userId}_widgets`;
|
||
let widgets = Local.get(widgetsKey.value);
|
||
grid.value = widgets ? JSON.parse(widgets) : cloneDeep(toValue(defaultGrid));
|
||
});
|
||
</script>
|
||
<style scoped lang="scss">
|
||
$rowHeight: 192px;
|
||
$rowSpace: 16px;
|
||
|
||
.custom_btn {
|
||
position: absolute;
|
||
top: 7px;
|
||
right: 5px;
|
||
z-index: 9;
|
||
color: white;
|
||
}
|
||
|
||
.widgets-home {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex: 1;
|
||
height: 100%;
|
||
}
|
||
|
||
.widgets-content {
|
||
flex: 1;
|
||
overflow: auto;
|
||
overflow-x: hidden;
|
||
padding: $rowSpace;
|
||
}
|
||
|
||
.widgets-aside {
|
||
width: 360px;
|
||
background: #fff;
|
||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||
position: relative;
|
||
overflow: auto;
|
||
}
|
||
|
||
.widgets-aside-title {
|
||
font-size: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.widgets-aside-title i {
|
||
margin-right: 10px;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.widgets-aside-close {
|
||
font-size: 18px;
|
||
width: 30px;
|
||
height: 30px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 3px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.widgets-aside-close:hover {
|
||
background: rgba(180, 180, 180, 0.1);
|
||
}
|
||
|
||
.widgets-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.widgets-top-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.widgets {
|
||
transform-origin: top left;
|
||
transition: transform 0.15s;
|
||
}
|
||
|
||
.draggable-box {
|
||
height: 100%;
|
||
}
|
||
|
||
.customizing .widgets-wrapper {
|
||
margin-right: -360px;
|
||
}
|
||
|
||
.customizing .widgets-wrapper .el-col {
|
||
padding-bottom: 15px;
|
||
}
|
||
|
||
.customizing .widgets-wrapper .draggable-box {
|
||
border: 1px dashed var(--el-color-primary);
|
||
padding: 15px;
|
||
}
|
||
|
||
.customizing .widgets-wrapper .no-widgets {
|
||
display: none;
|
||
}
|
||
|
||
.widgets-item {
|
||
position: relative;
|
||
margin-bottom: $rowSpace;
|
||
&-rows-1 {
|
||
height: $rowHeight;
|
||
}
|
||
&-rows-2 {
|
||
height: $rowHeight * 2 + $rowSpace;
|
||
}
|
||
}
|
||
|
||
.customize-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
left: 0;
|
||
z-index: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
cursor: move;
|
||
}
|
||
|
||
.customize-overlay label {
|
||
background: var(--el-color-primary);
|
||
color: #fff;
|
||
height: 40px;
|
||
padding: 0 30px;
|
||
border-radius: 40px;
|
||
font-size: 18px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: move;
|
||
}
|
||
|
||
.customize-overlay label i {
|
||
margin-right: 15px;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.customize-overlay .close {
|
||
position: absolute;
|
||
top: 15px;
|
||
left: 15px;
|
||
}
|
||
|
||
.widgets-list-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
padding: 15px;
|
||
align-items: center;
|
||
}
|
||
|
||
.widgets-list-item .item-logo {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
background: rgba(180, 180, 180, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
margin-right: 15px;
|
||
color: #6a8bad;
|
||
}
|
||
|
||
.widgets-list-item .item-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.widgets-list-item .item-info h2 {
|
||
font-size: 16px;
|
||
font-weight: normal;
|
||
cursor: default;
|
||
}
|
||
|
||
.widgets-list-item .item-info p {
|
||
font-size: 12px;
|
||
color: #999;
|
||
cursor: default;
|
||
}
|
||
|
||
.widgets-list-item:hover {
|
||
background: rgba(180, 180, 180, 0.1);
|
||
}
|
||
|
||
.widgets-wrapper .sortable-ghost {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.selectLayout {
|
||
width: 100%;
|
||
display: flex;
|
||
}
|
||
|
||
.selectLayout-item {
|
||
width: 60px;
|
||
height: 60px;
|
||
border: 2px solid var(--el-border-color-light);
|
||
padding: 5px;
|
||
cursor: pointer;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.selectLayout-item span {
|
||
display: block;
|
||
background: var(--el-border-color-light);
|
||
height: 46px;
|
||
}
|
||
|
||
.selectLayout-item.item02 span {
|
||
height: 30px;
|
||
}
|
||
|
||
.selectLayout-item.item02 .el-col:nth-child(1) span {
|
||
height: 14px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.selectLayout-item.item03 span {
|
||
height: 14px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.selectLayout-item:hover {
|
||
border-color: var(--el-color-primary);
|
||
}
|
||
|
||
.selectLayout-item.active {
|
||
border-color: var(--el-color-primary);
|
||
}
|
||
|
||
.selectLayout-item.active span {
|
||
background: var(--el-color-primary);
|
||
}
|
||
|
||
.dark {
|
||
.widgets-aside {
|
||
background: #2b2b2b;
|
||
}
|
||
|
||
.customize-overlay {
|
||
background: rgba(43, 43, 43, 0.9);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 992px) {
|
||
.customizing .widgets {
|
||
transform: scale(1) !important;
|
||
}
|
||
.customizing .widgets-aside {
|
||
width: 100%;
|
||
position: absolute;
|
||
top: 50%;
|
||
right: 0;
|
||
bottom: 0;
|
||
}
|
||
.customizing .widgets-wrapper {
|
||
margin-right: 0;
|
||
}
|
||
}
|
||
</style>
|