新增最近联系人功能

This commit is contained in:
eoao
2025-10-10 22:22:00 +08:00
parent 8ee140743a
commit 1b441774a4
10 changed files with 238 additions and 28 deletions

View File

@@ -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,

View File

@@ -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

View File

@@ -291,7 +291,9 @@ const zh = {
clear: '清除',
include: '包含',
delAllEmailConfirm: '确定要删除吗?',
s3Configuration: 'S3 配置'
s3Configuration: 'S3 配置',
confirmDeletionOfContacts: '确认清除这些联系人吗?',
recentContacts: '最近联系人',
selectContacts: '选中'
}
export default zh

View File

@@ -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>"
}
}
})

View File

@@ -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>

View File

@@ -1,7 +1,10 @@
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
export const useWriterStore = defineStore('writer', {
state: () => ({
})
})
sendRecipientRecord: []
}),
persist: {
pick: ['sendRecipientRecord'],
},
})

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);`
];