<template>
|
<div class="p-2">
|
<vxe-toolbar>
|
<template #buttons>
|
<span style="font-size: 1.25rem; font-weight: 600">个人邮箱绑定</span>
|
</template>
|
<template #tools>
|
<a-button style="margin-right: 20px" @click="fnCheckAll">检查全部邮箱</a-button>
|
<a-button type="primary" @click="openAccount('add', '')">新建邮箱</a-button>
|
</template>
|
</vxe-toolbar>
|
<vxe-table
|
ref="xTable"
|
id="key"
|
style="margin: 10px 0"
|
:data="demo.tableData"
|
min-height="40px"
|
row-key
|
keep-source
|
:filter-config="{ showIcon: false }"
|
:row-config="{ isHover: true }"
|
:column-config="{ resizable: true }"
|
>
|
<vxe-column width="60">
|
<template #default>
|
<span class="drag-btn">
|
<HolderOutlined />
|
</span>
|
</template>
|
</vxe-column>
|
<vxe-column show-overflow field="email" title="邮箱账号" min-width="250">
|
<template #default="{ row }">
|
<!-- <HolderOutlined /> -->
|
<span style="margin-left: 10px; color: #3081fe; font-weight: 500">{{ row.email }}</span>
|
</template>
|
</vxe-column>
|
<vxe-column show-overflow field="companyName" title="显示名称" min-width="250">
|
<template #default="{ row }">
|
<span style="color: #999">{{ row.companyName }}</span>
|
</template>
|
</vxe-column>
|
<vxe-column show-overflow field="status" title="邮箱状态" min-width="250">
|
<template #default="{ row }">
|
<div v-if="!isCheckAll">
|
<a-tag color="orange" v-if="row.status === '正常'">正常</a-tag>
|
<a-tag color="red" v-else>异常</a-tag>
|
</div>
|
<div v-else>
|
<a-spin :indicator="indicator" />
|
</div>
|
</template>
|
</vxe-column>
|
<vxe-column show-overflow field="action" title="操作" min-width="250">
|
<template #default="{ row }">
|
<a style="margin-right: 10px" @click="fnOpenAlias(row)">添加别名邮箱</a>
|
<a style="margin-right: 10px" @click="openIsEmailValid(row)">检测</a>
|
<a style="margin-right: 10px" @click="openAccount('update', row.email)">修改</a>
|
<a style="margin-right: 10px" @click="openDelete(row)">解绑</a>
|
</template>
|
</vxe-column>
|
</vxe-table>
|
<a-modal
|
:bodyStyle="{ maxHeight: '70vh', overflow: 'auto' }"
|
v-model:open="open"
|
:title="`${title}邮箱`"
|
:confirmLoading="loading"
|
@ok="fnHandleOk"
|
>
|
<div class="p-2">
|
<a-form
|
style="margin-top: 20px"
|
layout="vertical"
|
:model="formData"
|
v-bind="{ span: 8 }"
|
ref="formRef"
|
:rules="rules"
|
>
|
<a-form-item v-if="isShow" label="收发件服务器验证">
|
<a-select
|
ref="select"
|
v-model:value="isCustom"
|
style="width: 120px"
|
@change="fnHandleChange"
|
>
|
<a-select-option key="onCustom" value="onCustom"> 与邮箱相同 </a-select-option>
|
<a-select-option key="custom" value="custom"> 自定义 </a-select-option>
|
</a-select>
|
</a-form-item>
|
<a-form-item name="email">
|
<template v-slot:label>
|
邮箱账号
|
<a-tooltip placement="right">
|
<template #title>
|
<span>工作邮箱是您处理公司事务时所使用的办公邮箱 建议您专用于办公目的</span>
|
</template>
|
<ExclamationCircleOutlined style="margin-left: 5px" />
|
</a-tooltip>
|
</template>
|
<a-input :disabled="typeAccount === 2" v-model:value="formData.email" />
|
</a-form-item>
|
<a-form-item v-if="isCustom == 'custom'" name="password" label="邮箱密码">
|
<a-input-password
|
type="password"
|
v-model:value="formData.password"
|
placeholder="输入邮箱密码或者授权码"
|
/>
|
</a-form-item>
|
<a-form-item v-if="isShow" name="receiveProtocol" label="协议类型">
|
<a-radio-group v-model:value="formData.receiveProtocol" name="radioGroup">
|
<a-radio value="imap">IMAP</a-radio>
|
<a-radio value="pop3">POP3(不推荐,无法同步发件)</a-radio>
|
<!-- <a-radio value="exchange">Exchange</a-radio> -->
|
</a-radio-group>
|
<div style="color: red; font-size: 12px"
|
>由于exchange协议支持问题,网易邮箱推荐使用IMAP协议。
|
如需调整协议类型,请联系小满客服</div
|
>
|
</a-form-item>
|
<!-- <a-form-item v-if="formData.receiveProtocol === 'exchange'" :name="['user', 'email']" label="Exchange服务器" :rules="[{ type: 'email' }]">
|
<a-row style="display: flex; align-items: center">
|
<a-col :span="18">
|
<a-input v-model:value="formData.email" placeholder="Exchange服务器"
|
/></a-col>
|
<a-col :span="3">
|
<a-checkbox v-model:value="formData.email">SSL</a-checkbox>
|
</a-col>
|
</a-row>
|
</a-form-item>
|
<a-form-item v-if="formData.receiveProtocol === 'exchange'" :name="['user', 'email']" label="域" :rules="[{ type: 'email' }]">
|
<a-row style="display: flex; align-items: center">
|
<a-col :span="18">
|
<a-input v-model:value="formData.email" placeholder="Exchange服务器"
|
/></a-col>
|
<a-col :span="3">
|
<a-checkbox v-model:value="formData.email">SSL</a-checkbox>
|
</a-col>
|
</a-row>
|
</a-form-item> -->
|
<a-form-item v-if="isShow" name="receiveHost" label="收邮件服务器">
|
<a-row style="display: flex; align-items: center">
|
<a-col :span="12">
|
<a-input v-model:value="formData.receiveHost" placeholder="收邮件服务器" />
|
</a-col>
|
<a-col :span="1" style="margin-right: 10px; margin-left: 5px">:</a-col>
|
<a-col :span="6" style="margin-right: 10px">
|
<a-form-item-rest
|
><a-input width="50px" v-model:value="formData.receivePort" placeholder="端口"
|
/></a-form-item-rest>
|
</a-col>
|
<a-col :span="3">
|
<a-form-item-rest>
|
<a-checkbox v-model:checked="formData.receiveSSL"
|
>SSL</a-checkbox
|
></a-form-item-rest
|
>
|
</a-col>
|
</a-row>
|
</a-form-item>
|
<a-form-item
|
style="margin-top: 22px"
|
v-if="isCustom == 'custom'"
|
name="receiveEmail"
|
label="收件账号"
|
>
|
<a-input v-model:value="formData.receiveEmail" />
|
</a-form-item>
|
<a-form-item v-if="isCustom == 'custom'" name="receivePassword" label="收件密码">
|
<a-input v-model:value="formData.receivePassword" />
|
</a-form-item>
|
<a-form-item v-if="isShow" name="smtpHost" label="发邮件服务器">
|
<a-row style="display: flex; align-items: center">
|
<a-col :span="12">
|
<a-input v-model:value="formData.smtpHost" placeholder="发邮件服务器" />
|
</a-col>
|
<a-col :span="1" style="margin-right: 10px; margin-left: 5px">:</a-col>
|
<a-col :span="6" style="margin-right: 10px">
|
<a-form-item-rest
|
><a-input width="50px" v-model:value="formData.smtpPort" placeholder="端口"
|
/></a-form-item-rest>
|
</a-col>
|
<a-col :span="3">
|
<a-form-item-rest
|
><a-checkbox v-model:checked="formData.smtpSSL">SSL</a-checkbox></a-form-item-rest
|
>
|
</a-col>
|
</a-row>
|
</a-form-item>
|
<a-form-item
|
style="margin-top: 22px"
|
v-if="isCustom == 'custom'"
|
name="smtpEmail"
|
label="发件账号"
|
>
|
<a-input v-model:value="formData.smtpEmail" />
|
</a-form-item>
|
<a-form-item v-if="isCustom == 'custom'" name="smtpPassword" label="发件密码">
|
<a-input v-model:value="formData.smtpPassword" />
|
</a-form-item>
|
<a-form-item v-if="isShow" name="proxyFlag">
|
<template v-slot:label>
|
<a-tooltip placement="right">
|
<template #title>
|
<span>开启后网络提速</span>
|
</template>
|
<QuestionCircleOutlined style="margin-right: 5px" />
|
</a-tooltip>
|
自定义代理
|
</template>
|
<a-radio-group v-model:value="formData.proxyFlag" name="radioGroup">
|
<a-radio :value="true">开启</a-radio>
|
<a-radio :value="false">关闭</a-radio>
|
</a-radio-group>
|
</a-form-item>
|
<a-form-item v-if="isShow" name="biSyncFlag">
|
<template v-slot:label>
|
<a-tooltip placement="right">
|
<template #title>
|
<span
|
>开启后,对于新绑定的邮箱,全量同步文件夹及文件夹内的邮件。对于已经绑定的邮箱,全量同步文件夹及文件夹新收取的邮件,历史邮件不移动</span
|
>
|
</template>
|
<QuestionCircleOutlined style="margin-right: 5px" />
|
</a-tooltip>
|
同步文件夹
|
</template>
|
<a-radio-group v-model:value="formData.biSyncFlag" name="radioGroup">
|
<a-radio :value="true">开启</a-radio>
|
<a-radio :value="false">关闭</a-radio>
|
</a-radio-group>
|
</a-form-item>
|
</a-form>
|
<a @click="fnIsShow" v-if="!isShow"> 手动配置</a>
|
<a @click="fnIsShow" v-else> 收起手动配置</a>
|
|
<a-divider style="margin-top: 50px" />
|
<div style="font-size: 18px">帮助文档</div>
|
<div
|
><a href="https://www.yuque.com/help.xiaoman/qwwqei/vkr8p7" target="_blank" rel="noopener"
|
>1、查看绑定邮箱失败的常见原因及解决方案</a
|
></div
|
>
|
<div
|
><a href="https://www.yuque.com/help.xiaoman/qwwqei/sl9xuk" target="_blank" rel="noopener"
|
>2、了解常见几类邮箱的具体绑定方法</a
|
></div
|
></div
|
>
|
</a-modal>
|
|
<a-modal
|
v-model:open="openDrawerDetail"
|
:destroyOnClose="true"
|
title="确认解绑"
|
:loading="loading"
|
@cancel="fnHandleDetailCancel"
|
>
|
<div style="padding: 20px 20px 20px 0; color: #000; font-size: 18px">
|
解绑<span style="padding: 0 5px"> {{ deleteEmail }} </span>后
|
</div>
|
<ul>
|
<li v-for="(warning, index) in removalWarnings" :key="index">
|
<span class="bullet">•</span> {{ warning }}
|
</li>
|
</ul>
|
<div style="margin-top: 20px">
|
因绑定异常需调整,可在邮箱「修改」处更改 如修改时遇到困难,可联系客服
|
</div>
|
<template #footer>
|
<a-button @click="fnHandleDetailCancel">取消</a-button>
|
|
<a-button
|
type="primary"
|
danger
|
@click="fnHandleDetailOk"
|
:disabled="countdown !== 0"
|
:loading="loading"
|
>解绑<span v-show="countdown > 0"> ({{ countdown }}) </span></a-button
|
>
|
</template>
|
</a-modal>
|
<a-modal
|
v-model:open="openDrawerIsEmailValid"
|
:destroyOnClose="true"
|
title="邮箱检测"
|
v-model:value="isEmailValid"
|
>
|
<div style="margin-top: 20px; margin-right: 10%" class="p-4">
|
<a-form labelAlign="right" :labelCol="{ span: 8 }">
|
<a-form-item label="邮箱">
|
{{ isEmailValid.email }}
|
</a-form-item>
|
<a-form-item label="邮箱密码">
|
<div class="form-item">
|
<div class="left" :class="checkStatus ? 'isColor' : 'isRed'"> ********</div>
|
<div class="right">
|
<a-spin v-if="!isCheck" :indicator="indicator" />
|
<CheckCircleOutlined v-else-if="checkStatus" class="isColor" />
|
<CloseCircleOutlined v-else class="isRed" />
|
</div>
|
</div>
|
</a-form-item>
|
<a-form-item label="协议类型">
|
{{ isEmailValid.email }}
|
</a-form-item>
|
<a-form-item label="收邮件服务器">
|
<div class="form-item">
|
<div class="left" :class="checkStatus ? 'isColor' : 'isRed'">
|
{{ isEmailValid.receiveHost }}
|
</div>
|
<div class="right"
|
><a-spin v-if="!isCheck" :indicator="indicator" /><CheckCircleOutlined
|
v-else-if="checkStatus"
|
class="isColor"
|
/>
|
<CloseCircleOutlined v-else class="isRed" />
|
</div> </div
|
></a-form-item>
|
<a-form-item label="发邮件服务器">
|
<div class="form-item">
|
<div class="left" :class="checkStatus ? 'isColor' : 'isRed'">
|
{{ isEmailValid.smtpHost }}
|
</div>
|
<div class="right"
|
><a-spin v-if="!isCheck" :indicator="indicator" />
|
<CheckCircleOutlined v-else-if="checkStatus" class="isColor" />
|
<CloseCircleOutlined v-else class="isRed" />
|
</div> </div
|
></a-form-item>
|
<a-form-item label="自定义代理">
|
<span style="margin-right: 5px"> {{ isEmailValid.proxyFlag ? '开启' : '关闭' }} </span>
|
<a-tooltip placement="right">
|
<template #title>
|
<span>开启后网络提速</span>
|
</template>
|
<QuestionCircleOutlined style="margin-right: 5px" />
|
</a-tooltip>
|
</a-form-item>
|
<a-form-item label="同步文件夹">
|
<span style="margin-right: 5px"> {{ isEmailValid.biSyncFlag ? '开启' : '关闭' }} </span>
|
<a-tooltip placement="right">
|
<template #title>
|
<span
|
>开启后,对于新绑定的邮箱,全量同步文件夹及文件夹内的邮件。<br />对于已经绑定的邮箱,全量同步文件夹及文件夹新收取的邮件,历史邮件不移动</span
|
>
|
</template>
|
<QuestionCircleOutlined style="margin-right: 5px" />
|
</a-tooltip>
|
</a-form-item>
|
</a-form>
|
</div>
|
<template #footer>
|
<div style="text-align: center">
|
<a-button type="primary" @click="openIsEmailValid(isEmailValid)">重新检测</a-button>
|
</div>
|
</template>
|
</a-modal>
|
<a-modal
|
v-model:open="openAlias"
|
:title="`${titleAlias}添加别名邮箱`"
|
:confirmLoading="loading"
|
@ok="fnHandleAliasOk"
|
>
|
<a-form
|
ref="aliasRef"
|
style="margin-top: 20px"
|
layout="vertical"
|
:model="aliasFormData"
|
v-bind="{ span: 8 }"
|
>
|
<a-form-item
|
style="margin-top: 22px"
|
name="aliasEmailName"
|
label="别名邮箱"
|
:rules="[{ required: true, message: '请输入别名邮箱', trigger: 'blur' }]"
|
>
|
<a-input v-model:value="aliasFormData.aliasEmailName" />
|
</a-form-item>
|
</a-form>
|
</a-modal>
|
</div>
|
</template>
|
|
<script lang="ts" setup>
|
name: 'mailboxManagement';
|
import {
|
HolderOutlined,
|
ExclamationCircleOutlined,
|
QuestionCircleOutlined,
|
CheckCircleOutlined,
|
CloseCircleOutlined,
|
} from '@ant-design/icons-vue';
|
import { ref, reactive, onUnmounted } from 'vue';
|
import {
|
addAccountApi,
|
getAccountApi,
|
updateAccountApi,
|
deleteAccountApi,
|
getAccountListApi,
|
isEmailValidApi,
|
} from '@/api/email/userList';
|
const loading = ref(false);
|
import Sortable from 'sortablejs';
|
let sortable: any;
|
const demo = reactive({
|
showHelpTip: false,
|
tableData: [],
|
});
|
const xTable = ref();
|
const rowDrop = () => {
|
const $table = xTable.value;
|
sortable = Sortable.create($table.$el.querySelector('.body--wrapper>.vxe-table--body tbody'), {
|
handle: '.drag-btn',
|
onEnd: (sortableEvent) => {
|
const newIndex = sortableEvent.newIndex as number;
|
const oldIndex = sortableEvent.oldIndex as number;
|
const currRow = demo.tableData.splice(oldIndex, 1)[0];
|
demo.tableData.splice(newIndex, 0, currRow);
|
},
|
});
|
};
|
|
let initTime: any;
|
nextTick(() => {
|
// 加载完成之后在绑定拖动事件
|
initTime = setTimeout(() => {
|
rowDrop();
|
}, 500);
|
});
|
|
onUnmounted(() => {
|
clearTimeout(initTime);
|
if (sortable) {
|
sortable.destroy();
|
}
|
});
|
|
function fnMailList() {
|
getAccountListApi()
|
.then((res) => {
|
if (res.code == 0) {
|
demo.tableData = res.data;
|
}
|
console.log(res);
|
})
|
.catch((err) => {
|
console.log(err);
|
demo.tableData = [];
|
});
|
}
|
fnMailList();
|
const defaultFormData = {
|
email: '',
|
password: '',
|
aliasEmail: '',
|
biSyncFlag: false,
|
proxyFlag: true,
|
receiveProtocol: 'imap',
|
receiveSSL: false,
|
receivePort: '',
|
receiveHost: '',
|
smtpSSL: false,
|
smtpPort: '',
|
smtpHost: '',
|
invalid: '',
|
mailType: 0,
|
};
|
const formData = ref<Record<string, any>>(defaultFormData);
|
const isCustom = ref('onCustom');
|
function fnHandleChange(e) {
|
console.log(e, 'iririririr');
|
|
isCustom.value = e;
|
}
|
const checkReceivePort = async (value) => {
|
console.log(formData.value.receivePort, '=-3--3', value);
|
if (value === '') {
|
return Promise.reject('请输入收件服务器');
|
}
|
if (!formData.value.receivePort) {
|
return Promise.reject('请输入收件服务器、端口');
|
}
|
return Promise.resolve();
|
};
|
const checkSmtpHost = async (value) => {
|
if (value === '') {
|
return Promise.reject('请输入收件服务器');
|
}
|
if (!formData.value.smtpHost) {
|
return Promise.reject('请输入收件服务器、端口');
|
}
|
return Promise.resolve();
|
};
|
const rules = ref({
|
email: [
|
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
|
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] },
|
],
|
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
receiveHost: [{ required: true, validator: checkReceivePort }],
|
smtpHost: [{ required: true, validator: checkSmtpHost }],
|
smtpPassword: [{ required: true, message: '请输入发件密码', trigger: 'blur' }],
|
smtpEmail: [{ required: true, message: '请输入发件账号', trigger: 'blur' }],
|
receiveEmail: [{ required: true, message: '请输入收件密码', trigger: 'blur' }],
|
receivePassword: [{ required: true, message: '请输入收件账号', trigger: 'blur' }],
|
});
|
import { useMessage } from '@/hooks/web/useMessage';
|
const { createMessage } = useMessage();
|
const formRef = ref();
|
|
const open = ref(false);
|
const fnHandleOk = () => {
|
formRef.value.validate().then(() => {
|
const data = formData.value;
|
if (isShow.value == true) {
|
data.mailType = isCustom.value === 'onCustom' ? 1 : 2;
|
}
|
const api = typeAccount.value === 2 ? updateAccountApi : addAccountApi;
|
loading.value = true;
|
api(data)
|
.then((res) => {
|
if (res.code === 0) {
|
createMessage.success(res.msg);
|
}
|
loading.value = false;
|
fnMailList();
|
})
|
.catch((err) => {
|
loading.value = false;
|
});
|
open.value = false;
|
});
|
};
|
|
const isShow = ref(false);
|
function fnIsShow() {
|
isShow.value = !isShow.value;
|
}
|
const title = ref('添加');
|
const typeAccount = ref(1);
|
function openAccount(type, email) {
|
formData.value = { ...defaultFormData };
|
try {
|
if (type == 'add') {
|
title.value = '添加';
|
// formData.value = {};
|
isShow.value = false;
|
typeAccount.value = 1;
|
} else {
|
title.value = '修改';
|
typeAccount.value = 2;
|
isShow.value = true;
|
getAccountApi({ mail: email }).then((res) => {
|
formData.value = res.data;
|
});
|
}
|
open.value = true;
|
} catch (error) {
|
open.value = false;
|
}
|
}
|
|
// 删除
|
|
const removalWarnings = [
|
'此邮箱下的历史邮件不可查看',
|
'您不能使用此邮箱收发件',
|
'同步删除自定义文件夹、标签、收发件规则和绑定的别名邮箱。重新绑定后也不能恢复',
|
'相关邮件线索仅可查看不可操作',
|
];
|
const deleteEmail = ref();
|
const openDrawerDetail = ref<boolean>(false);
|
const accountId = ref();
|
|
const fnHandleDetailCancel = () => {
|
countdown.value = 10;
|
openDrawerDetail.value = false;
|
clearInterval(intervalId);
|
};
|
function openDelete(row) {
|
openDrawerDetail.value = true;
|
deleteEmail.value = row.email;
|
accountId.value = row.accountId;
|
countdown.value = 10;
|
clearInterval(intervalId);
|
startCountdown();
|
}
|
function fnHandleDetailOk() {
|
openDrawerDetail.value = false;
|
deleteAccountApi({ accountId: accountId.value })
|
.then((res) => {
|
if (res.code === 0) {
|
createMessage.success(res.msg);
|
fnMailList();
|
}
|
})
|
.catch((err) => {
|
// createMessage.error(err.msg);
|
});
|
}
|
const countdown = ref(10);
|
let intervalId: any;
|
|
const startCountdown = () => {
|
if (countdown.value > 0) {
|
intervalId = setInterval(() => {
|
countdown.value--;
|
if (countdown.value === 0) {
|
clearInterval(intervalId);
|
}
|
}, 1000);
|
}
|
};
|
|
import { LoadingOutlined } from '@ant-design/icons-vue';
|
import { h, nextTick } from 'vue';
|
// 邮箱检测
|
const isCheck = ref(false);
|
const checkStatus = ref(false);
|
|
const openDrawerIsEmailValid = ref(false);
|
function openIsEmailValid(row) {
|
openDrawerIsEmailValid.value = true;
|
isEmailValid.value = row;
|
const email = { email: row.email };
|
isCheck.value = false;
|
isEmailValidApi(email).then((res) => {
|
if (res.code == 0) {
|
isCheck.value = true;
|
checkStatus.value = res.data.status;
|
}
|
});
|
}
|
const indicator = h(LoadingOutlined, {
|
style: {
|
fontSize: '24px',
|
},
|
spin: true,
|
});
|
|
const isEmailValid = ref<Record<string, any>>({});
|
|
// 别名处理
|
const aliasFormData = ref<Record<string, any>>({});
|
const openAlias = ref(false);
|
const titleAlias = ref('');
|
function fnOpenAlias(row) {
|
openAlias.value = true;
|
titleAlias.value = row.email;
|
}
|
const aliasRef = ref();
|
function fnHandleAliasOk() {
|
nextTick(() => {
|
aliasRef.value.validate().then(() => {
|
openAlias.value = false;
|
});
|
});
|
}
|
|
// 全部检测
|
const isCheckAll = ref(false);
|
function fnCheckAll() {
|
isCheckAll.value = true;
|
setTimeout(() => {
|
isCheckAll.value = false;
|
}, 3000);
|
}
|
</script>
|
<style scoped lang="less">
|
.bullet {
|
display: inline-block;
|
margin-right: 5px;
|
color: #000;
|
font-size: 16px;
|
}
|
|
.form-item {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.isColor {
|
color: #18a561;
|
}
|
|
.isRed {
|
color: red;
|
}
|
</style>
|