新增邮件显示已读未读功能
This commit is contained in:
@@ -18,10 +18,10 @@ http.interceptors.response.use((res) => {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
const showMsg = res.config.noMsg;
|
const noMsg = res.config.noMsg;
|
||||||
const data = res.data
|
const data = res.data
|
||||||
|
|
||||||
if (showMsg) {
|
if (noMsg) {
|
||||||
|
|
||||||
data.code === 200 ? resolve(data.data) : reject(data)
|
data.code === 200 ? resolve(data.data) : reject(data)
|
||||||
|
|
||||||
@@ -76,9 +76,9 @@ http.interceptors.response.use((res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showMsg = error.config.noMsg;
|
const noMsg = error.config.noMsg;
|
||||||
|
|
||||||
if (showMsg) {
|
if (noMsg) {
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
} else if (error.message.includes('Network Error')) {
|
} else if (error.message.includes('Network Error')) {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
<Icon v-perm="'email:delete'" class="icon delete" icon="uiw:delete" width="16" height="16"
|
<Icon v-perm="'email:delete'" class="icon delete" icon="uiw:delete" width="16" height="16"
|
||||||
v-if="getSelectedMailsIds().length > 0"
|
v-if="getSelectedMailsIds().length > 0"
|
||||||
@click="handleDelete"/>
|
@click="handleDelete"/>
|
||||||
|
<Icon v-perm="'email:delete'" class="icon delete" icon="fluent:mail-read-20-regular" width="21" height="21"
|
||||||
|
v-if="getSelectedMailsIds().length > 0"
|
||||||
|
@click="handleRead"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
@@ -42,11 +45,10 @@
|
|||||||
<div v-if="!showStar"></div>
|
<div v-if="!showStar"></div>
|
||||||
<div class="title" :class="accountShow ? 'title-column' : 'title-column'">
|
<div class="title" :class="accountShow ? 'title-column' : 'title-column'">
|
||||||
|
|
||||||
<div class="email-sender" :style=" showStatus ? 'gap: 10px;' : ''">
|
<div class="email-sender" :style=" (showStatus ? 'gap: 10px;' : '') + ((item.unread === EmailUnreadEnum.UNREAD && showUnread) ? 'font-weight: bold' : '')">
|
||||||
<div class="email-status" v-if="showStatus">
|
<div class="email-status" v-if="showStatus">
|
||||||
<el-tooltip v-if="item.status === 0" effect="dark" :content="$t('received')">
|
<el-tooltip v-if="item.status === 0" effect="dark" :content="$t('received')">
|
||||||
<Icon icon="ic:round-mark-email-read" style="color: #51C76B" width="20" height="20"/>
|
<Icon icon="ic:round-mark-email-read" style="color: #51C76B" width="20" height="20"/>
|
||||||
/>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip v-if="item.status === 1" effect="dark" :content="$t('sent')">
|
<el-tooltip v-if="item.status === 1" effect="dark" :content="$t('sent')">
|
||||||
<Icon icon="bi:send-arrow-up-fill" style="color: #51C76B" width="20" height="20"/>
|
<Icon icon="bi:send-arrow-up-fill" style="color: #51C76B" width="20" height="20"/>
|
||||||
@@ -75,6 +77,7 @@
|
|||||||
<div v-else></div>
|
<div v-else></div>
|
||||||
<span class="name">
|
<span class="name">
|
||||||
<span>
|
<span>
|
||||||
|
<div class="unread" v-if="isMobile && (item.unread === EmailUnreadEnum.UNREAD && showUnread) "/>
|
||||||
<slot name="name" :email="item"> {{ item.name }}</slot>
|
<slot name="name" :email="item"> {{ item.name }}</slot>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
@@ -85,8 +88,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="email-text">
|
<div class="email-text">
|
||||||
<span class="email-subject">
|
<span class="email-subject" :style="(item.unread === EmailUnreadEnum.UNREAD && showUnread) ? 'font-weight: bold' : ''">
|
||||||
<slot name="subject" :email="item">
|
<div class="unread" v-if="!isMobile && (item.unread === EmailUnreadEnum.UNREAD && showUnread) "/>
|
||||||
|
<slot name="subject" :email="item" >
|
||||||
{{ item.subject || '\u200B' }}
|
{{ item.subject || '\u200B' }}
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
@@ -109,7 +113,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="email-right" :style="showUserInfo ? 'align-self: start;':''">
|
<div class="email-right" :style="showUserInfo ? 'align-self: start;':''">
|
||||||
<span class="email-time">{{ fromNow(item.createTime) }}</span>
|
<span class="email-time" :style="(item.unread === EmailUnreadEnum.UNREAD && showUnread) ? 'font-weight: bold' : ''">{{ fromNow(item.createTime) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,10 +174,12 @@ import {useSettingStore} from "@/store/setting.js";
|
|||||||
import {sleep} from "@/utils/time-utils.js"
|
import {sleep} from "@/utils/time-utils.js"
|
||||||
import {fromNow} from "@/utils/day.js";
|
import {fromNow} from "@/utils/day.js";
|
||||||
import {useI18n} from "vue-i18n";
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {EmailUnreadEnum} from "@/enums/email-enum.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
getEmailList: Function,
|
getEmailList: Function,
|
||||||
emailDelete: Function,
|
emailDelete: Function,
|
||||||
|
emailRead: Function,
|
||||||
starAdd: Function,
|
starAdd: Function,
|
||||||
starCancel: Function,
|
starCancel: Function,
|
||||||
cancelSuccess: Function,
|
cancelSuccess: Function,
|
||||||
@@ -217,6 +223,10 @@ const props = defineProps({
|
|||||||
showFirstLoading: {
|
showFirstLoading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
showUnread: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -238,7 +248,7 @@ let scrollTop = 0
|
|||||||
const latestEmail = ref(null)
|
const latestEmail = ref(null)
|
||||||
const scrollbarRef = ref(null)
|
const scrollbarRef = ref(null)
|
||||||
let reqLock = false
|
let reqLock = false
|
||||||
let isMobile = innerWidth < 1367
|
let isMobile = ref(innerWidth < 1367)
|
||||||
let skeletonRows = 0
|
let skeletonRows = 0
|
||||||
const queryParam = reactive({
|
const queryParam = reactive({
|
||||||
emailId: 0,
|
emailId: 0,
|
||||||
@@ -266,6 +276,10 @@ onBeforeRouteLeave(() => {
|
|||||||
scrollTop = scroll.value.scrollTop
|
scrollTop = scroll.value.scrollTop
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.onresize = () => {
|
||||||
|
isMobile = innerWidth < 1367
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => emailList.map(item => item.checked),
|
() => emailList.map(item => item.checked),
|
||||||
() => {
|
() => {
|
||||||
@@ -372,6 +386,18 @@ function changeAccountShow() {
|
|||||||
uiStore.accountShow = !uiStore.accountShow;
|
uiStore.accountShow = !uiStore.accountShow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRead = () => {
|
||||||
|
const emailIds = getSelectedMailsIds();
|
||||||
|
props.emailRead(emailIds);
|
||||||
|
emailIds.forEach(emailId => {
|
||||||
|
const index = emailList.findIndex(email => email.emailId === emailId);
|
||||||
|
if (index > -1) {
|
||||||
|
emailList[index].unread = EmailUnreadEnum.READ;
|
||||||
|
emailList[index].checked = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
ElMessageBox.confirm(t('delEmailsConfirm'), {
|
ElMessageBox.confirm(t('delEmailsConfirm'), {
|
||||||
confirmButtonText: t('confirm'),
|
confirmButtonText: t('confirm'),
|
||||||
@@ -701,7 +727,6 @@ function loadData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.email-sender {
|
.email-sender {
|
||||||
font-weight: bold;;
|
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
@@ -875,7 +900,7 @@ function loadData() {
|
|||||||
|
|
||||||
.header-actions {
|
.header-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
padding: 3px 15px;
|
padding: 3px 15px;
|
||||||
@@ -886,7 +911,7 @@ function loadData() {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
column-gap: 18px;
|
column-gap: 20px;
|
||||||
row-gap: 8px;
|
row-gap: 8px;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
color: var(--el-text-color-primary);;
|
color: var(--el-text-color-primary);;
|
||||||
@@ -925,6 +950,17 @@ function loadData() {
|
|||||||
bottom: 1px;
|
bottom: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unread {
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
background: var(--el-color-primary);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
4
mail-vue/src/enums/email-enum.js
Normal file
4
mail-vue/src/enums/email-enum.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const EmailUnreadEnum = {
|
||||||
|
UNREAD: 0,
|
||||||
|
READ: 1
|
||||||
|
}
|
||||||
@@ -12,6 +12,10 @@ export function emailLatest(emailId, accountId) {
|
|||||||
return http.get('/email/latest', {params: {emailId, accountId}, noMsg: true })
|
return http.get('/email/latest', {params: {emailId, accountId}, noMsg: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emailRead(emailIds) {
|
||||||
|
return http.put('/email/read', {emailIds}, {noMsg: true})
|
||||||
|
}
|
||||||
|
|
||||||
export function emailSend(form,progress) {
|
export function emailSend(form,progress) {
|
||||||
return http.post('/email/send', form,{
|
return http.post('/email/send', form,{
|
||||||
onUploadProgress: (e) => {
|
onUploadProgress: (e) => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export const useEmailStore = defineStore('email', {
|
|||||||
delType: null,
|
delType: null,
|
||||||
showStar: true,
|
showStar: true,
|
||||||
showReply: true,
|
showReply: true,
|
||||||
|
showUnread: false
|
||||||
},
|
},
|
||||||
sendScroll: null,
|
sendScroll: null,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -74,10 +74,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import ShadowHtml from '@/components/shadow-html/index.vue'
|
import ShadowHtml from '@/components/shadow-html/index.vue'
|
||||||
import {reactive, ref, watch} from "vue";
|
import {reactive, ref, watch, onMounted, onUnmounted} from "vue";
|
||||||
import {useRouter} from 'vue-router'
|
import {useRouter} from 'vue-router'
|
||||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||||
import {emailDelete} from "@/request/email.js";
|
import {emailDelete, emailRead} from "@/request/email.js";
|
||||||
import {Icon} from "@iconify/vue";
|
import {Icon} from "@iconify/vue";
|
||||||
import {useEmailStore} from "@/store/email.js";
|
import {useEmailStore} from "@/store/email.js";
|
||||||
import {useAccountStore} from "@/store/account.js";
|
import {useAccountStore} from "@/store/account.js";
|
||||||
@@ -90,6 +90,7 @@ import {useSettingStore} from "@/store/setting.js";
|
|||||||
import {allEmailDelete} from "@/request/all-email.js";
|
import {allEmailDelete} from "@/request/all-email.js";
|
||||||
import {useUiStore} from "@/store/ui.js";
|
import {useUiStore} from "@/store/ui.js";
|
||||||
import {useI18n} from "vue-i18n";
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {EmailUnreadEnum} from "@/enums/email-enum.js";
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
@@ -105,6 +106,17 @@ watch(() => accountStore.currentAccountId, () => {
|
|||||||
handleBack()
|
handleBack()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (emailStore.contentData.showUnread && email.unread === EmailUnreadEnum.UNREAD) {
|
||||||
|
email.unread = EmailUnreadEnum.READ;
|
||||||
|
emailRead([email.emailId]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
emailStore.contentData.showUnread = false;
|
||||||
|
})
|
||||||
|
|
||||||
function openReply() {
|
function openReply() {
|
||||||
uiStore.writerRef.openReply(email)
|
uiStore.writerRef.openReply(email)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
:star-add="starAdd"
|
:star-add="starAdd"
|
||||||
:star-cancel="starCancel"
|
:star-cancel="starCancel"
|
||||||
:time-sort="params.timeSort"
|
:time-sort="params.timeSort"
|
||||||
|
:email-read="emailRead"
|
||||||
|
:show-unread="true"
|
||||||
actionLeft="4px"
|
actionLeft="4px"
|
||||||
@jump="jumpContent"
|
@jump="jumpContent"
|
||||||
>
|
>
|
||||||
@@ -25,7 +27,7 @@ import {useAccountStore} from "@/store/account.js";
|
|||||||
import {useEmailStore} from "@/store/email.js";
|
import {useEmailStore} from "@/store/email.js";
|
||||||
import {useSettingStore} from "@/store/setting.js";
|
import {useSettingStore} from "@/store/setting.js";
|
||||||
import emailScroll from "@/components/email-scroll/index.vue"
|
import emailScroll from "@/components/email-scroll/index.vue"
|
||||||
import {emailList, emailDelete, emailLatest} from "@/request/email.js";
|
import {emailList, emailDelete, emailLatest, emailRead} from "@/request/email.js";
|
||||||
import {starAdd, starCancel} from "@/request/star.js";
|
import {starAdd, starCancel} from "@/request/star.js";
|
||||||
import {defineOptions, onMounted, reactive, ref, watch} from "vue";
|
import {defineOptions, onMounted, reactive, ref, watch} from "vue";
|
||||||
import {sleep} from "@/utils/time-utils.js";
|
import {sleep} from "@/utils/time-utils.js";
|
||||||
@@ -62,6 +64,7 @@ function changeTimeSort() {
|
|||||||
function jumpContent(email) {
|
function jumpContent(email) {
|
||||||
emailStore.contentData.email = email
|
emailStore.contentData.email = email
|
||||||
emailStore.contentData.delType = 'logic'
|
emailStore.contentData.delType = 'logic'
|
||||||
|
emailStore.contentData.showUnread = true
|
||||||
emailStore.contentData.showStar = true
|
emailStore.contentData.showStar = true
|
||||||
emailStore.contentData.showReply = true
|
emailStore.contentData.showReply = true
|
||||||
router.push('/message')
|
router.push('/message')
|
||||||
|
|||||||
@@ -29,3 +29,8 @@ app.post('/email/send', async (c) => {
|
|||||||
return c.json(result.ok(email));
|
return c.json(result.ok(email));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.put('/email/read', async (c) => {
|
||||||
|
await emailService.read(c, await c.req.json(), userContext.getUserId(c));
|
||||||
|
return c.json(result.ok());
|
||||||
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export const emailConst = {
|
|||||||
SAVING: 6,
|
SAVING: 6,
|
||||||
NOONE: 7,
|
NOONE: 7,
|
||||||
FAILED: 8
|
FAILED: 8
|
||||||
|
},
|
||||||
|
unread: {
|
||||||
|
UNREAD: 0,
|
||||||
|
READ: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const email = sqliteTable('email', {
|
|||||||
status: integer('status').default(0).notNull(),
|
status: integer('status').default(0).notNull(),
|
||||||
resendEmailId: text('resend_email_id'),
|
resendEmailId: text('resend_email_id'),
|
||||||
message: text('message'),
|
message: text('message'),
|
||||||
|
unread: integer('unread').default(0).notNull(),
|
||||||
createTime: text('create_time').default(sql`CURRENT_TIMESTAMP`).notNull(),
|
createTime: text('create_time').default(sql`CURRENT_TIMESTAMP`).notNull(),
|
||||||
isDel: integer('is_del').default(0).notNull()
|
isDel: integer('is_del').default(0).notNull()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,11 +30,22 @@ const init = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async v2_5DB(c) {
|
async v2_5DB(c) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN email_prefix_filter text NOT NULL DEFAULT '';`).run();
|
await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN email_prefix_filter text NOT NULL DEFAULT '';`).run();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await c.env.db.batch([
|
||||||
|
c.env.db.prepare(`ALTER TABLE email ADD COLUMN unread INTEGER NOT NULL DEFAULT 0;`).run(),
|
||||||
|
c.env.db.prepare(`UPDATE email SET unread = 1;`).run()
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async v2_4DB(c) {
|
async v2_4DB(c) {
|
||||||
|
|||||||
@@ -671,6 +671,11 @@ const emailService = {
|
|||||||
async physicsDeleteByAccountId(c, accountId) {
|
async physicsDeleteByAccountId(c, accountId) {
|
||||||
await attService.removeByAccountId(c, accountId);
|
await attService.removeByAccountId(c, accountId);
|
||||||
await orm(c).delete(email).where(eq(email.accountId, accountId)).run();
|
await orm(c).delete(email).where(eq(email.accountId, accountId)).run();
|
||||||
|
},
|
||||||
|
|
||||||
|
async read(c, params, userId) {
|
||||||
|
const { emailIds } = params;
|
||||||
|
await orm(c).update(email).set({ unread: emailConst.unread.READ }).where(and(eq(email.userId, userId), inArray(email.emailId, emailIds)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user