新增最近联系人功能
This commit is contained in:
@@ -32,7 +32,7 @@ const props = defineProps({
|
||||
|
||||
|
||||
const {locale} = useI18n()
|
||||
const emit = defineEmits(['change']);
|
||||
const emit = defineEmits(['change','focus']);
|
||||
const editor = shallowRef(null);
|
||||
const isInitialized = ref(false);
|
||||
const editorRef = ref(null);
|
||||
@@ -124,6 +124,9 @@ function initEditor() {
|
||||
const text = ed.getContent({format: 'text'});
|
||||
emit('change', content, text);
|
||||
});
|
||||
ed.on('focus', () => {
|
||||
emit('focus', focus);
|
||||
})
|
||||
},
|
||||
autofocus: true,
|
||||
branding: false,
|
||||
|
||||
@@ -291,7 +291,10 @@ const en = {
|
||||
clear: 'Clear',
|
||||
include: 'Include',
|
||||
delAllEmailConfirm: 'Do you really want to delete it?',
|
||||
s3Configuration: 'S3 Configuration'
|
||||
s3Configuration: 'S3 Configuration',
|
||||
confirmDeletionOfContacts: 'Confirm clearing contacts?',
|
||||
recentContacts: 'Recent contacts',
|
||||
selectContacts: 'Select'
|
||||
}
|
||||
|
||||
export default en
|
||||
|
||||
@@ -291,7 +291,9 @@ const zh = {
|
||||
clear: '清除',
|
||||
include: '包含',
|
||||
delAllEmailConfirm: '确定要删除吗?',
|
||||
s3Configuration: 'S3 配置'
|
||||
|
||||
s3Configuration: 'S3 配置',
|
||||
confirmDeletionOfContacts: '确认清除这些联系人吗?',
|
||||
recentContacts: '最近联系人',
|
||||
selectContacts: '选中'
|
||||
}
|
||||
export default zh
|
||||
|
||||
@@ -609,4 +609,40 @@ addCollection({
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
addCollection({
|
||||
"prefix": "mage",
|
||||
"lastModified": 1754899875,
|
||||
"aliases": {},
|
||||
"width": 24,
|
||||
"height": 24,
|
||||
"icons": {
|
||||
"user": {
|
||||
"body": "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M19.618 21.25c0-3.602-4.016-6.53-7.618-6.53s-7.618 2.928-7.618 6.53M12 11.456a4.353 4.353 0 1 0 0-8.706a4.353 4.353 0 0 0 0 8.706\"/>"
|
||||
}
|
||||
}
|
||||
})
|
||||
addCollection({
|
||||
"prefix": "fa7-solid",
|
||||
"lastModified": 1759381039,
|
||||
"aliases": {},
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"icons": {
|
||||
"user-plus": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M136 128a120 120 0 1 1 240 0a120 120 0 1 1-240 0M48 482.3C48 383.8 127.8 304 226.3 304h59.4c98.5 0 178.3 79.8 178.3 178.3c0 16.4-13.3 29.7-29.7 29.7H77.7C61.3 512 48 498.7 48 482.3M544 96c13.3 0 24 10.7 24 24v48h48c13.3 0 24 10.7 24 24s-10.7 24-24 24h-48v48c0 13.3-10.7 24-24 24s-24-10.7-24-24v-48h-48c-13.3 0-24-10.7-24-24s10.7-24 24-24h48v-48c0-13.3 10.7-24 24-24\"/>",
|
||||
"width": 640
|
||||
}
|
||||
}
|
||||
})
|
||||
addCollection({
|
||||
"prefix": "line-md",
|
||||
"lastModified": 1754899771,
|
||||
"aliases": {},
|
||||
"width": 24,
|
||||
"height": 24,
|
||||
"icons": {
|
||||
"loading-loop": {
|
||||
"body": "<path fill=\"none\" stroke=\"currentColor\" stroke-dasharray=\"16\" stroke-dashoffset=\"16\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 3c4.97 0 9 4.03 9 9\"><animate fill=\"freeze\" attributeName=\"stroke-dashoffset\" dur=\"0.2s\" values=\"16;0\"/><animateTransform attributeName=\"transform\" dur=\"1.5s\" repeatCount=\"indefinite\" type=\"rotate\" values=\"0 12 12;360 12 12\"/></path>"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="send" v-show="show">
|
||||
<div class="write-box" @click.stop>
|
||||
<div class="write-box">
|
||||
<div class="title">
|
||||
<div class="title-left">
|
||||
<span class="title-text">
|
||||
@@ -15,12 +15,31 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<el-input-tag @add-tag="addTagChange" tag-type="primary" size="default" v-model="form.receiveEmail"
|
||||
:placeholder="$t('ruleEmailsInputDesc')">
|
||||
<el-input-tag @add-tag="addTagChange" tag-type="primary" @input="inputChange" size="default" v-model="form.receiveEmail"
|
||||
:placeholder="ruleEmailsInputDesc">
|
||||
<template #prefix>
|
||||
<div class="item-title">{{ $t('recipient') }}</div>
|
||||
<div class="item-title" >{{ $t('recipient') }}</div>
|
||||
<el-select
|
||||
ref="mySelect"
|
||||
class="select"
|
||||
:show-arrow="false"
|
||||
:no-match-text="' '"
|
||||
:no-data-text="' '"
|
||||
@visible-change="selectStatusChange"
|
||||
@change="selectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in selectRecipientList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<div style="display: flex;">
|
||||
<Icon icon="fa7-solid:user-plus" width="20" height="20" class="add-contact" @click.stop="openContacts" />
|
||||
</div>
|
||||
<span class="distribute" :class="form.manyType ? 'checked' : ''"
|
||||
@click.stop="checkDistribute">{{ $t('sendSeparately') }}</span>
|
||||
</template>
|
||||
@@ -30,7 +49,7 @@
|
||||
<div class="item-title">{{ $t('subject') }}</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<tinyEditor :def-value="defValue" ref="editor" @change="change"/>
|
||||
<tinyEditor :def-value="defValue" ref="editor" @change="change" @focus="focusChange" />
|
||||
<div class="button-item">
|
||||
<div class="att-add" @click="chooseFile">
|
||||
<Icon icon="iconamoon:attachment-fill" width="24" height="24"/>
|
||||
@@ -54,11 +73,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog top="10vh" v-model="showContacts" @closed="clearSelectContact" :title="t('recentContacts')">
|
||||
<el-table ref="contactsTabRef" row-key="email" :data="contacts" style="height: 445px">
|
||||
<el-table-column type="selection" width="32" />
|
||||
<el-table-column property="email" :label="t('emailAccount')" >
|
||||
<template #default="props">
|
||||
<div class="email-row">{{ props.row.email }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="55" label="" >
|
||||
<template #default>
|
||||
<div style="display: flex;">
|
||||
<Icon icon="mage:user" style="color: var(--el-text-color-primary)" width="22" height="22" color="#606266" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="contacts-bottom">
|
||||
<el-button type="default" @click="deleteContact">{{t('clear')}}</el-button>
|
||||
<el-button type="primary" @click="chooseContact">{{t('selectContacts')}}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import tinyEditor from '@/components/tiny-editor/index.vue'
|
||||
import {h, nextTick, onMounted, onUnmounted, reactive, ref, toRaw} from "vue";
|
||||
import {h, nextTick, onMounted, onUnmounted, reactive, ref, toRaw, computed} from "vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {useUserStore} from "@/store/user.js";
|
||||
import {emailSend} from "@/request/email.js";
|
||||
@@ -72,6 +112,7 @@ import {toOssDomain} from "@/utils/convert.js";
|
||||
import {formatDetailDate} from "@/utils/day.js";
|
||||
import {useSettingStore} from "@/store/setting.js";
|
||||
import {userDraftStore} from "@/store/draft.js";
|
||||
import {useWriterStore} from "@/store/writer.js";
|
||||
import db from "@/db/db.js";
|
||||
import dayjs from "dayjs";
|
||||
import {useI18n} from "vue-i18n";
|
||||
@@ -83,6 +124,7 @@ defineExpose({
|
||||
})
|
||||
|
||||
const {t} = useI18n()
|
||||
const writerStore = useWriterStore();
|
||||
const draftStore = userDraftStore()
|
||||
const settingStore = useSettingStore()
|
||||
const emailStore = useEmailStore();
|
||||
@@ -94,6 +136,11 @@ const percent = ref(0)
|
||||
let percentMessage = null
|
||||
let sending = false
|
||||
const defValue = ref('')
|
||||
const contactsTabRef = ref({})
|
||||
const showContacts = ref(false)
|
||||
const mySelect = ref()
|
||||
let selectStatus = false
|
||||
const ruleEmailsInputDesc = ref(t('ruleEmailsInputDesc'))
|
||||
const backReply = reactive({
|
||||
receiveEmail: [],
|
||||
subject: '',
|
||||
@@ -115,6 +162,78 @@ const form = reactive({
|
||||
draftId: null,
|
||||
})
|
||||
|
||||
const selectRecipientList = ref([])
|
||||
|
||||
const contacts = computed(() => writerStore.sendRecipientRecord.map(item => ({email: item})))
|
||||
|
||||
function openContacts() {
|
||||
form.receiveEmail.forEach(item => {
|
||||
if (writerStore.sendRecipientRecord.includes(item)) {
|
||||
contactsTabRef.value.toggleRowSelection({email: item});
|
||||
}
|
||||
})
|
||||
showContacts.value = true
|
||||
}
|
||||
|
||||
function deleteContact() {
|
||||
ElMessageBox.confirm(t('confirmDeletionOfContacts'), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const contactList = contactsTabRef.value.getSelectionRows().map(item => item.email);
|
||||
form.receiveEmail = form.receiveEmail.filter(item => !contactList.includes(item));
|
||||
writerStore.sendRecipientRecord = writerStore.sendRecipientRecord.filter(item => !contactList.includes(item));
|
||||
})
|
||||
}
|
||||
|
||||
function chooseContact() {
|
||||
|
||||
const contactList = contactsTabRef.value.getSelectionRows().map(item => item.email);
|
||||
contactList.forEach(item => {
|
||||
if (!form.receiveEmail.includes(item)) {
|
||||
form.receiveEmail.push(item);
|
||||
}
|
||||
})
|
||||
|
||||
form.receiveEmail = form.receiveEmail.filter(item => {
|
||||
return contactList.includes(item) || !writerStore.sendRecipientRecord.includes(item);
|
||||
});
|
||||
|
||||
showContacts.value = false
|
||||
}
|
||||
|
||||
function clearSelectContact() {
|
||||
contactsTabRef.value.clearSelection();
|
||||
}
|
||||
|
||||
function selectChange(value) {
|
||||
form.receiveEmail.push(value)
|
||||
}
|
||||
|
||||
function selectStatusChange(status) {
|
||||
selectStatus = status
|
||||
ruleEmailsInputDesc.value = status ? '' : ruleEmailsInputDesc.value = t('ruleEmailsInputDesc')
|
||||
}
|
||||
|
||||
const openSelect = () => {
|
||||
mySelect.value.toggleMenu()
|
||||
}
|
||||
|
||||
function inputChange(value) {
|
||||
|
||||
selectRecipientList.value = writerStore.sendRecipientRecord.filter(item => value && !form.receiveEmail.includes(item) && item.startsWith(value)).slice(0, 10);
|
||||
|
||||
if (!selectStatus && selectRecipientList.value.length > 0) {
|
||||
openSelect()
|
||||
}
|
||||
|
||||
if (selectStatus && selectRecipientList.value.length === 0) {
|
||||
openSelect()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function addTagChange(val) {
|
||||
|
||||
const emails = Array.from(new Set(
|
||||
@@ -123,12 +242,14 @@ function addTagChange(val) {
|
||||
|
||||
form.receiveEmail.splice(form.receiveEmail.length - 1, 1)
|
||||
|
||||
let has = false
|
||||
emails.forEach(email => {
|
||||
if (isEmail(email) && !form.receiveEmail.includes(email)) {
|
||||
form.receiveEmail.push(email)
|
||||
has = true
|
||||
}
|
||||
})
|
||||
|
||||
if (selectStatus && has) openSelect()
|
||||
}
|
||||
|
||||
function checkDistribute() {
|
||||
@@ -272,6 +393,13 @@ async function sendEmail() {
|
||||
percentMessage.close()
|
||||
percent.value = 0
|
||||
sending = false
|
||||
|
||||
writerStore.sendRecipientRecord = writerStore.sendRecipientRecord.filter(
|
||||
email => !form.receiveEmail.includes(email)
|
||||
);
|
||||
|
||||
writerStore.sendRecipientRecord.unshift(...form.receiveEmail);
|
||||
writerStore.sendRecipientRecord = writerStore.sendRecipientRecord.slice(0, 500);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -297,6 +425,10 @@ function change(content, text) {
|
||||
form.text = text
|
||||
}
|
||||
|
||||
function focusChange() {
|
||||
if (selectStatus) openSelect()
|
||||
}
|
||||
|
||||
function openReply(email) {
|
||||
|
||||
resetForm();
|
||||
@@ -378,6 +510,8 @@ onUnmounted(() => {
|
||||
|
||||
function close() {
|
||||
|
||||
if (selectStatus) openSelect();
|
||||
|
||||
if (form.draftId) {
|
||||
draftStore.setDraft = {...toRaw(form)}
|
||||
show.value = false
|
||||
@@ -521,13 +655,6 @@ function close() {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
.distribute:hover {
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary) !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
}
|
||||
|
||||
@@ -578,6 +705,40 @@ function close() {
|
||||
|
||||
}
|
||||
|
||||
.email-row {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.el-dialog) {
|
||||
width: 420px !important;
|
||||
@media (max-width: 460px) {
|
||||
width: calc(100% - 40px) !important;
|
||||
margin-right: 20px !important;
|
||||
margin-left: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.contacts-bottom {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.add-contact {
|
||||
color: var(--regular-text-color)
|
||||
}
|
||||
|
||||
.select {
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
left: 60px;
|
||||
z-index: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.el-input-tag__suffix) {
|
||||
padding-right: 4px;
|
||||
}
|
||||
@@ -585,4 +746,4 @@ function close() {
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
export const useWriterStore = defineStore('writer', {
|
||||
state: () => ({
|
||||
|
||||
})
|
||||
})
|
||||
sendRecipientRecord: []
|
||||
}),
|
||||
persist: {
|
||||
pick: ['sendRecipientRecord'],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@ html, body {
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
font-family: -apple-system, Inter, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
line-height: 1.5;
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 14px;
|
||||
|
||||
@@ -669,7 +669,7 @@ defineOptions({
|
||||
name: 'sys-setting'
|
||||
})
|
||||
|
||||
const currentVersion = 'v2.1.0'
|
||||
const currentVersion = 'v2.2.0'
|
||||
const hasUpdate = ref(false)
|
||||
let getUpdateErrorCount = 1;
|
||||
const {t, locale} = useI18n();
|
||||
|
||||
@@ -13,6 +13,7 @@ import timezone from 'dayjs/plugin/timezone';
|
||||
import roleService from '../service/role-service';
|
||||
import verifyUtils from '../utils/verify-utils';
|
||||
import r2Service from '../service/r2-service';
|
||||
import userService from '../service/user-service';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
@@ -58,7 +59,9 @@ export async function email(message, env, ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (account && account.email !== env.admin) {
|
||||
const userRow = await userService.selectById({ env: env }, account.userId);
|
||||
|
||||
if (account && userRow.email !== env.admin) {
|
||||
|
||||
let { banEmail, banEmailType, availDomain } = await roleService.selectByUserId({ env: env }, account.userId);
|
||||
|
||||
|
||||
@@ -83,7 +83,6 @@ const init = {
|
||||
`ALTER TABLE setting ADD COLUMN notice INTEGER NOT NULL DEFAULT 0;`,
|
||||
`ALTER TABLE setting ADD COLUMN no_recipient INTEGER NOT NULL DEFAULT 1;`,
|
||||
`UPDATE role SET avail_domain = '' WHERE role.avail_domain LIKE '@%';`,
|
||||
`UPDATE role SET ban_email = '';`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_email_user_id_account_id ON email(user_id, account_id);`
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user