保存
This commit is contained in:
409
mail-vue/src/layout/account/index.vue
Normal file
409
mail-vue/src/layout/account/index.vue
Normal file
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<div class="account-box">
|
||||
<div class="head-opt">
|
||||
<Icon v-if="settingStore.settings.addEmail === 0" class="icon" icon="ion:add-outline" width="23" height="23" @click="add" />
|
||||
<Icon class="icon" icon="ion:reload" width="18" height="18" @click="refresh" />
|
||||
</div>
|
||||
<el-scrollbar class="scrollbar">
|
||||
<div v-infinite-scroll="getAccountList" :infinite-scroll-distance="600" :infinite-scroll-immediate="false">
|
||||
<el-card class="item" :class="itemBg(item.accountId)" v-for="item in accounts" :key="item.accountId" @click="changeAccount(item)">
|
||||
<div class="account">
|
||||
{{ item.email }}
|
||||
</div>
|
||||
<div class="opt">
|
||||
<div class="send-email" @click.stop>
|
||||
<Icon icon="eva:email-fill" width="22" height="22" color="#fbbd08" />
|
||||
</div>
|
||||
<div class="settings" @click.stop>
|
||||
<Icon v-if="item.accountId === userStore.user.accountId" icon="fluent:settings-24-filled" width="20" height="20" color="#909399" />
|
||||
<el-dropdown v-else >
|
||||
<Icon icon="fluent:settings-24-filled" width="20" height="20" color="#909399" />
|
||||
<template #dropdown >
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="remove(item)">删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- Initial Loading Skeleton -->
|
||||
<template v-if="loading">
|
||||
<el-skeleton v-for="i in 3" :key="i" animated>
|
||||
<template #template>
|
||||
<el-card class="item">
|
||||
<el-skeleton-item variant="p" style="width: 70%; height: 20px; margin-bottom: 20px" />
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<el-skeleton-item variant="text" style="width: 20px" />
|
||||
<el-skeleton-item variant="text" style="width: 20px" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</template>
|
||||
|
||||
<!-- Follow Loading Skeleton -->
|
||||
<template v-if="accounts.length > 0 && !noLoading">
|
||||
<el-skeleton animated>
|
||||
<template #template>
|
||||
<el-card class="item">
|
||||
<el-skeleton-item variant="p" style="width: 70%; height: 20px; margin-bottom: 20px" />
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<el-skeleton-item variant="text" style="width: 20px" />
|
||||
<el-skeleton-item variant="text" style="width: 20px" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</template>
|
||||
|
||||
<div class="noLoading" v-if="noLoading && accounts.length > 0">
|
||||
<div>没有更多数据了</div>
|
||||
</div>
|
||||
<div class="empty" v-if="noLoading && accounts.length === 0">
|
||||
<el-empty description="没有任何邮件" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-scrollbar>
|
||||
<el-dialog v-model="showAdd" title="添加邮箱" >
|
||||
<form>
|
||||
<div class="container">
|
||||
<el-input v-model="addForm.email" type="text" placeholder="邮箱" autocomplete="off">
|
||||
<template #append>
|
||||
<div @click.stop="openSelect">
|
||||
<el-select
|
||||
ref="mySelect"
|
||||
v-model="addForm.suffix"
|
||||
placeholder="请选择"
|
||||
class="select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in domainList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
<div style="color: #333">
|
||||
<span >{{addForm.suffix}}</span>
|
||||
<Icon class="setting-icon" icon="mingcute:down-small-fill" width="20" height="20" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button class="btn" type="primary" @click="submit" :loading="addLoading"
|
||||
>添加
|
||||
</el-button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
class="add-email-turnstile"
|
||||
:class="verifyShow ? 'turnstile-show' : 'turnstile-hide'"
|
||||
:data-sitekey="settingStore.settings.siteKey"
|
||||
data-callback="onTurnstileSuccess"
|
||||
></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {nextTick, reactive, ref} from "vue";
|
||||
import {accountList, accountAdd, accountDelete} from "@/request/account.js";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {isEmail} from "@/utils/verify-utils.js";
|
||||
import {useSettingStore} from "@/store/setting.js";
|
||||
import {useAccountStore} from "@/store/account.js";
|
||||
import {useUserStore} from "@/store/user.js";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const accountStore = useAccountStore();
|
||||
const settingStore = useSettingStore();
|
||||
const showAdd = ref(false)
|
||||
const addLoading = ref(false);
|
||||
const domainList = settingStore.domainList
|
||||
const accounts = reactive([])
|
||||
const noLoading = ref(false)
|
||||
const loading = ref(false)
|
||||
const followLoading = ref(false);
|
||||
const verifyShow = ref(false)
|
||||
let turnstileId = false
|
||||
let verifyToken = ''
|
||||
const addForm = reactive({
|
||||
email: '',
|
||||
suffix: settingStore.domainList[0]
|
||||
})
|
||||
const queryParams = {
|
||||
accountId: 0,
|
||||
size: 12
|
||||
}
|
||||
|
||||
const mySelect = ref()
|
||||
|
||||
getAccountList()
|
||||
|
||||
const openSelect = () => {
|
||||
mySelect.value.toggleMenu()
|
||||
}
|
||||
|
||||
window.onTurnstileSuccess = (token) => {
|
||||
verifyToken = token;
|
||||
setTimeout(() => {
|
||||
verifyShow.value = false
|
||||
},1500)
|
||||
};
|
||||
|
||||
function itemBg(accountId) {
|
||||
return accountStore.currentAccountId === accountId ? 'item-choose' : ''
|
||||
}
|
||||
|
||||
function remove(account) {
|
||||
ElMessageBox.confirm(`确认删除${account.email}吗?`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
accountDelete(account.accountId).then(() => {
|
||||
const index = accounts.findIndex(item => item.accountId === account.accountId);
|
||||
accounts.splice(index, 1);
|
||||
if (accounts.length < queryParams.size) {
|
||||
getAccountList()
|
||||
}
|
||||
ElMessage({
|
||||
message: '删除成功',
|
||||
type: 'success',
|
||||
plain: true,
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (loading.value) {
|
||||
return
|
||||
}
|
||||
loading.value = false
|
||||
followLoading.value = false
|
||||
noLoading.value = false
|
||||
queryParams.accountId = 0
|
||||
accounts.splice(0, accounts.length)
|
||||
getAccountList()
|
||||
}
|
||||
function changeAccount(account) {
|
||||
accountStore.currentAccountId = account.accountId
|
||||
}
|
||||
|
||||
function add() {
|
||||
showAdd.value = true
|
||||
}
|
||||
|
||||
function getAccountList() {
|
||||
|
||||
if (loading.value || followLoading.value || noLoading.value) return;
|
||||
|
||||
if (accounts.length === 0) {
|
||||
loading.value = true
|
||||
}else {
|
||||
followLoading.value = true
|
||||
}
|
||||
|
||||
accountList(queryParams.accountId,queryParams.size).then(list => {
|
||||
if (list.length < queryParams.size) {
|
||||
noLoading.value = true
|
||||
}
|
||||
if (accounts.length === 0) {
|
||||
accountStore.currentAccount = list[0].accountId
|
||||
}
|
||||
queryParams.accountId = list.at(-1).accountId
|
||||
accounts.push(...list)
|
||||
|
||||
loading.value = false
|
||||
followLoading.value = false
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
followLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function submit() {
|
||||
|
||||
if (!addForm.email){
|
||||
ElMessage({
|
||||
message: "邮箱不能为空",
|
||||
type: "error",
|
||||
plain: true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!isEmail(addForm.email+addForm.suffix)) {
|
||||
ElMessage({
|
||||
message: "非法邮箱",
|
||||
type: "error",
|
||||
plain: true
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!verifyToken && settingStore.settings.addEmailVerify === 0) {
|
||||
verifyShow.value = true
|
||||
if (!turnstileId) {
|
||||
nextTick(() => {
|
||||
turnstileId = window.turnstile.render('.add-email-turnstile')
|
||||
})
|
||||
} else {
|
||||
window.turnstile.reset(turnstileId)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
addLoading.value = true
|
||||
accountAdd(addForm.email+addForm.suffix,verifyToken).then(account => {
|
||||
addLoading.value = false
|
||||
showAdd.value = false
|
||||
addForm.email = ''
|
||||
accounts.push(account)
|
||||
verifyToken = ''
|
||||
ElMessage({
|
||||
message: "添加成功",
|
||||
type: "success",
|
||||
plain: true
|
||||
})
|
||||
}).catch(res => {
|
||||
if (res.code === 400) {
|
||||
verifyToken = ''
|
||||
window.turnstile.reset(turnstileId)
|
||||
verifyShow.value = true
|
||||
}
|
||||
addLoading.value = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.account-box {
|
||||
|
||||
border-right: 1px solid var(--el-border-color) !important;
|
||||
background-color: #FFF;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
.head-opt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
box-shadow: inset 0 -1px 0 0 rgba(100, 121, 143, 0.12);
|
||||
.icon{
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.icon:nth-child(2) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
.scrollbar {
|
||||
width: 100%;
|
||||
height: calc(100% - 38px);
|
||||
overflow: auto;
|
||||
@media (max-width: 767px) {
|
||||
height: calc(100% - 98px);
|
||||
}
|
||||
.empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.noLoading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.item {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
.account {
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.opt {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.item:first-child{
|
||||
margin-top: 10px ;
|
||||
}
|
||||
|
||||
.item-choose {
|
||||
background: var(--el-color-primary-light-8);
|
||||
}
|
||||
}
|
||||
|
||||
.setting-icon {
|
||||
position: relative;
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-input-group__append) {
|
||||
padding: 0 !important;
|
||||
padding-left: 8px !important;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
:deep(.el-dialog) {
|
||||
width: 400px !important;
|
||||
@media (max-width: 440px) {
|
||||
width: calc(100% - 40px) !important;
|
||||
margin-right: 20px !important;
|
||||
margin-left: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
width: 100px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.add-email-turnstile {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.turnstile-show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.turnstile-hide {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
</style>
|
||||
132
mail-vue/src/layout/aside/index.vue
Normal file
132
mail-vue/src/layout/aside/index.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<el-scrollbar class="scroll">
|
||||
<div>
|
||||
<div class="title" >
|
||||
<Icon icon="mdi:email-outline" width="24" height="24" />
|
||||
<div>{{settingStore.settings.title}}</div>
|
||||
</div>
|
||||
<el-menu :collapse="false" text-color="#fff" active-text-color="#fff" style="margin-top: 10px">
|
||||
<el-menu-item @click="router.push('/email')" index="email"
|
||||
:class="route.meta.name === 'email' ? 'choose-item' : ''">
|
||||
<Icon icon="hugeicons:mailbox-01" width="20" height="20" />
|
||||
<span class="menu-name" style="margin-left: 21px">收件箱</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item @click="router.push('/star')" index="star"
|
||||
:class="route.meta.name === 'star' ? 'choose-item' : ''">
|
||||
<Icon icon="solar:star-line-duotone" width="20" height="20" />
|
||||
<span class="menu-name" style="margin-left: 20px">星标邮件</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item @click="router.push('/setting')" index="setting"
|
||||
:class="route.meta.name === 'setting' ? 'choose-item' : ''">
|
||||
<Icon icon="fluent:settings-48-regular" width="20" height="20" />
|
||||
<span class="menu-name" style="margin-left: 20px">个人设置</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item @click="router.push('/sys-setting')" index="sys-setting" v-if="userStore.user.type === 0"
|
||||
:class="route.meta.name === 'sys-setting' ? 'choose-item' : ''">
|
||||
<Icon icon="eos-icons:system-ok-outlined" width="18" height="18" />
|
||||
<span class="menu-name" style="margin-left: 23px">系统设置</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useUserStore} from "@/store/user.js";
|
||||
import router from "@/router/index.js";
|
||||
import { useRoute } from "vue-router";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {useSettingStore} from "@/store/setting.js";
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
const userStore = useUserStore();
|
||||
const route = useRoute();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.title {
|
||||
margin: 15px 10px;
|
||||
height: 50px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(135deg, #1890ff, #1c6dd0);
|
||||
box-shadow: 0 0 12px rgba(24, 144, 255, 0.6), 0 0 20px rgba(24, 144, 255, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
:deep(.el-icon) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.user-right-icon {
|
||||
align-self: center;
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
right: 8px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.el-menu-item {
|
||||
margin: 5px 10px !important;
|
||||
border-radius: 8px;
|
||||
height: 36px;
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.choose-item {
|
||||
font-weight: bold;
|
||||
background: rgba(255, 255, 255, 0.08) !important;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.el-menu-item:hover {
|
||||
background: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-name {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-scrollbar__wrap--hidden-default ) {
|
||||
background: #001529 !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item) {
|
||||
background: #001529;
|
||||
}
|
||||
|
||||
:deep(.el-menu) {
|
||||
background: #001529;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-right: 0;
|
||||
width: 250px;
|
||||
@media (max-width: 1199px) {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-divider__text) {
|
||||
background: #001529;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
box-shadow: 6px 0 20px rgba(0, 21, 41, 0.35);
|
||||
}
|
||||
</style>
|
||||
131
mail-vue/src/layout/header/index.vue
Normal file
131
mail-vue/src/layout/header/index.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="header">
|
||||
<div class="btn">
|
||||
<hanburger @click="changeAside"></hanburger>
|
||||
<span class="breadcrumb-item">{{ route.meta.title }}</span>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<div class="email">{{ userStore.user.email }}</div>
|
||||
<el-dropdown>
|
||||
<div class="avatar">
|
||||
<div class="avatar-text">
|
||||
<div>{{ formatName(userStore.user.email) }}</div>
|
||||
</div>
|
||||
<Icon class="setting-icon" icon="mingcute:down-small-fill" width="24" height="24" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="router.push('/setting')">
|
||||
个人设置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="clickLogout">
|
||||
退出登录
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import router from "@/router";
|
||||
import hanburger from '@/components/hamburger/index.vue'
|
||||
import { logout} from "@/request/login.js";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {useUiStore} from "@/store/ui.js";
|
||||
import {useUserStore} from "@/store/user.js";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const userStore = useUserStore();
|
||||
const uiStore = useUiStore();
|
||||
function changeAside() {
|
||||
uiStore.asideShow = !uiStore.asideShow
|
||||
}
|
||||
|
||||
function clickLogout() {
|
||||
logout().then(() => {
|
||||
localStorage.removeItem("token")
|
||||
router.push('/login')
|
||||
})
|
||||
}
|
||||
|
||||
function formatName(email) {
|
||||
return email[0]?.toUpperCase() || ''
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.breadcrumb-item {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.setting-icon {
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
display: grid;
|
||||
height: 100%;
|
||||
gap: 10px;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
margin-left: auto;
|
||||
gap: 10px;
|
||||
.email {
|
||||
align-self: center;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.avatar-text {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.setting-icon {
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.el-tooltip__trigger:first-child:focus-visible {
|
||||
outline: unset;
|
||||
}
|
||||
</style>
|
||||
121
mail-vue/src/layout/index.vue
Normal file
121
mail-vue/src/layout/index.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-container class="layout">
|
||||
<el-aside
|
||||
class="aside"
|
||||
:class="uiStore.asideShow ? 'aside-show' : 'el-aside-hide'">
|
||||
<Aside />
|
||||
</el-aside>
|
||||
<div
|
||||
:class="(uiStore.asideShow && isMobile)? 'overlay-show':'overlay-hide'"
|
||||
@click="uiStore.asideShow = false"
|
||||
></div>
|
||||
|
||||
<el-container class="main-container">
|
||||
<el-main>
|
||||
<el-header>
|
||||
<Header />
|
||||
</el-header>
|
||||
<Main />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Aside from '@/layout/aside/index.vue'
|
||||
import Header from '@/layout/header/index.vue'
|
||||
import Main from '@/layout/main/index.vue'
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import {useUiStore} from "@/store/ui.js";
|
||||
|
||||
const uiStore = useUiStore();
|
||||
const isMobile = ref(window.innerWidth < 768)
|
||||
|
||||
|
||||
const handleResize = () => {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
uiStore.asideShow = window.innerWidth >= 992;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
handleResize()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-aside-hide {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
transform: translateX(-100%);
|
||||
transition: all 100ms ease;
|
||||
}
|
||||
|
||||
.aside-show {
|
||||
transform: translateX(0);
|
||||
transition: all 100ms ease;
|
||||
z-index: 101;
|
||||
@media (max-width: 767px) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 101;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
width: auto;
|
||||
transition: all 100ms ease;
|
||||
}
|
||||
|
||||
.layout {
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
min-height: 100%;
|
||||
background: #FFFFFF;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.el-main {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-header {
|
||||
background: #FFFFFF;
|
||||
border-bottom: solid 1px var(--el-menu-border-color);
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
.overlay-show {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 99;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.overlay-hide {
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
129
mail-vue/src/layout/main/index.vue
Normal file
129
mail-vue/src/layout/main/index.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div :class=" accountShow ? 'main-box-show' : 'main-box-hide'">
|
||||
<div :class="accountShow ? 'block-show' : 'block-hide'" @click="uiStore.accountShow = false"></div>
|
||||
<account :class="accountShow ? 'show' : 'hide'" />
|
||||
<router-view class="main-view" v-slot="{ Component,route }">
|
||||
<keep-alive :include="['email','sys-setting','star']">
|
||||
<component :is="Component" :key="route.name"/>
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import account from '@/layout/account/index.vue'
|
||||
import {useUiStore} from "@/store/ui.js";
|
||||
import {useSettingStore} from "@/store/setting.js";
|
||||
import {computed, onBeforeUnmount, onMounted} from "vue";
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
openSend: Function
|
||||
})
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const uiStore = useUiStore();
|
||||
const route = useRoute()
|
||||
let innerWidth = window.innerWidth
|
||||
|
||||
const accountShow = computed(() => {
|
||||
return uiStore.accountShow && settingStore.settings.manyEmail === 0
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
handleResize()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
const handleResize = () => {
|
||||
if (['content','email'].includes(route.meta.name)) {
|
||||
if (innerWidth !== window.innerWidth) {
|
||||
innerWidth = window.innerWidth;
|
||||
uiStore.accountShow = window.innerWidth >= 768;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.block-show {
|
||||
position: fixed;
|
||||
@media (max-width: 767px) {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
border: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #000000;
|
||||
opacity: 0.6;
|
||||
z-index: 10;
|
||||
transition: all 300ms;
|
||||
}
|
||||
}
|
||||
|
||||
.block-hide {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
transition: all 300ms;
|
||||
}
|
||||
|
||||
.show {
|
||||
transition: all 100ms;
|
||||
@media (max-width: 767px) {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
width: 199px;
|
||||
}
|
||||
}
|
||||
|
||||
.hide {
|
||||
transition: all 100ms;
|
||||
position: fixed;
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
@media (max-width: 767px) {
|
||||
width: 199px;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.main-box-show {
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
@media (max-width: 767px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.main-box-hide {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
|
||||
|
||||
.main-view {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
.navigation {
|
||||
height: 30px;
|
||||
border-bottom: solid 1px var(--el-menu-border-color);
|
||||
display: inline-flex;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
.tag {
|
||||
background: #FFFFFF;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user