Commit 19046586 by Sweet Zhang

调通发邮件

parent 70b9e0d4
...@@ -7,6 +7,8 @@ import type { ...@@ -7,6 +7,8 @@ import type {
Sender, Sender,
Variable, Variable,
VariableTemplate, VariableTemplate,
SubTask,
EmailTask,
} from '@/types/index' } from '@/types/index'
import type { ApiResponse } from '@/utils/request' import type { ApiResponse } from '@/utils/request'
...@@ -129,6 +131,10 @@ export const importContactApi = { ...@@ -129,6 +131,10 @@ export const importContactApi = {
getEmailContactImportList: (params: EditContactImport): Promise<ApiResponse> => { getEmailContactImportList: (params: EditContactImport): Promise<ApiResponse> => {
return request.post('/emailContactImport/page', params) return request.post('/emailContactImport/page', params)
}, },
// 详情会话信息前端展示收件人,抄送人
getEmailContactImportDetail: (id: string): Promise<ApiResponse> => {
return request.get('/emailContactImport/detail/sessionId?sessionId=' + id)
},
} }
/** /**
* 发送邮件 * 发送邮件
...@@ -142,4 +148,22 @@ export const sendEmailApi = { ...@@ -142,4 +148,22 @@ export const sendEmailApi = {
testSendEmail: (data: SendEmail): Promise<ApiResponse> => { testSendEmail: (data: SendEmail): Promise<ApiResponse> => {
return request.post('/email/test/send', data) return request.post('/email/test/send', data)
}, },
// 发送任务列表查询
getEmailTaskList: (params: SubTask): Promise<ApiResponse> => {
return request.post('/emailTaskRecipients/page', params)
},
// 主线任务列表查询
getEmailTaskMainList: (params: EmailTask): Promise<ApiResponse> => {
return request.post('/emailTask/page', params)
},
}
/**
* 文件服务接口
*/
export const uploadApi = {
// 上传文件
uploadFile: (data: FormData): Promise<ApiResponse> => {
return request.post('/oss/upload', data)
},
} }
...@@ -9,7 +9,7 @@ export interface Pagination<T> { ...@@ -9,7 +9,7 @@ export interface Pagination<T> {
} }
// 联系人类型 // 联系人类型
export interface Contact extends Pagination<Contact> { export interface Contact<T> extends Pagination<Contact> {
contactBizId?: string contactBizId?: string
name?: string name?: string
type?: string type?: string
...@@ -50,29 +50,6 @@ export interface VariableTemplate extends Pagination<VariableTemplate> { ...@@ -50,29 +50,6 @@ export interface VariableTemplate extends Pagination<VariableTemplate> {
variableNameEns?: string[] variableNameEns?: string[]
} }
// 邮件类型
export interface Email extends Pagination<Email> {
id?: string
sender?: string
to?: string[]
cc?: string[]
subject?: string
content?: string
sendTime?: string
status?: 'sent' | 'scheduled' | 'draft' | 'failed'
attachments?: { name: string }[]
}
// 邮件表单类型
export interface EmailForm extends Pagination<EmailForm> {
to: string
cc: string
subject: string
content: string
scheduleSend: boolean
sendTime: string
}
// 忘记密码表单类型 // 忘记密码表单类型
export interface ForgotPasswordForm { export interface ForgotPasswordForm {
email: string email: string
...@@ -81,18 +58,16 @@ export interface ForgotPasswordForm { ...@@ -81,18 +58,16 @@ export interface ForgotPasswordForm {
} }
// 导入记录类型 // 导入记录类型
export interface ImportRecord { export interface ImportRecord extends Contact<ImportRecord> {
id: string sessionId?: string
to: string receiveEmailList?: string[]
cc: string ccEmailList?: string[]
createdAt: string
updatedAt: string
} }
// 选择联系人时,调用接口,获取sessionId // 选择联系人时,调用接口,获取sessionId
export interface ContactSessionId { export interface ContactSessionId {
sessionId?: string sessionId?: string
apiEmailContactDtoList?: Contact[] apiEmailContactDtoList?: Contact<unknown>[]
} }
// 编辑-邮件联系人导入信息 // 编辑-邮件联系人导入信息
...@@ -104,11 +79,32 @@ export interface EditContactImport extends Pagination<EditContactImport> { ...@@ -104,11 +79,32 @@ export interface EditContactImport extends Pagination<EditContactImport> {
// 发送邮件 // 发送邮件
export interface SendEmail { export interface SendEmail {
senderBizId?: string senderBizId?: string
sendEmail?: string
subject?: string
content?: string
scheduleTime?: string
attachmentPath?: string
variableGroupBizId?: string
sessionId?: string
recipientEmailList?: string[] recipientEmailList?: string[]
ccEmailList?: string[] ccEmailList?: string[]
bccEmailList?: string[] bccEmailList?: string[]
receiveEmailList?: string[]
}
export interface EmailForm {
senderBizId?: string
sendEmail?: string
variableGroupBizId?: string
receiveEmail?: string
ccEmailList?: string[]
subject?: string subject?: string
body?: string content?: string
attachmentPath?: string
sessionId?: string
ccEmails?: string
scheduleSend?: boolean
scheduleTime?: string
} }
//邮件服务商类型 //邮件服务商类型
export interface EmailProvider extends Pagination<EmailProvider> { export interface EmailProvider extends Pagination<EmailProvider> {
...@@ -120,3 +116,22 @@ export interface EmailProvider extends Pagination<EmailProvider> { ...@@ -120,3 +116,22 @@ export interface EmailProvider extends Pagination<EmailProvider> {
active?: number active?: number
description?: string description?: string
} }
// 发送任务列表查询参数
export interface SubTask extends Pagination<SubTask> {
taskBizId?: string
receiveEmail?: string
status?: string
}
// 主线任务列表查询参数
export interface EmailTask extends Pagination<EmailTask> {
queryContent?: string
status?: string
taskBizId?: string
taskName?: string
senderBizId?: string
sendEmail?: string
receiveEmails?: string
subject?: string
scheduleTime?: string
sendTime?: string
}
...@@ -17,7 +17,7 @@ const request = axios.create({ ...@@ -17,7 +17,7 @@ const request = axios.create({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
// Authorization: 'Bearer ' + localStorage.getItem('authToken'), // Authorization: 'Bearer ' + localStorage.getItem('authToken'),
Authorization: Authorization:
'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyXzEwMDEiLCJyb2xlcyI6W10sImlhdCI6MTc1ODc4NTY3NCwiZXhwIjoxNzU4ODcyMDc0fQ.tjTO6vdpwLpNjVa1DhxRBdpjZsdhbx6g1TdtpAm7BZBRMwanM_ci7dsnbc8FNXpyfSb-ifXW7ccxwyQbtCaKiQ', 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyXzEwMDEiLCJyb2xlcyI6W10sImlhdCI6MTc1ODg3MjMyOSwiZXhwIjoxNzU4OTU4NzI5fQ.McyflIoI_ltve_uy2-mZTjOfxYfBGNMEuOoIVfeEtXdAuoycggGErq8yU3mc15npsIWJy2a8zJ5cNpx_NVtGIw',
}, },
}) })
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</select> </select>
<!-- 当选择模版有值时,显示导入数据按钮 --> <!-- 当选择模版有值时,显示导入数据按钮 -->
<button <button
@click="showImportContacts = true" @click="((showImportContacts = true), (importSource = 1))"
class="bg-blue-50 hover:bg-blue-100 text-blue-600 px-4 py-2 rounded-md border border-blue-200 transition-colors flex items-center" class="bg-blue-50 hover:bg-blue-100 text-blue-600 px-4 py-2 rounded-md border border-blue-200 transition-colors flex items-center"
v-if="selectedVariableTemplate" v-if="selectedVariableTemplate"
> >
...@@ -38,18 +38,32 @@ ...@@ -38,18 +38,32 @@
<label class="block text-gray-700 mb-2 font-medium">收件人</label> <label class="block text-gray-700 mb-2 font-medium">收件人</label>
<div class="flex flex-col sm:flex-row gap-2"> <div class="flex flex-col sm:flex-row gap-2">
<!-- 多个邮箱用tag的样式展示 --> <!-- 多个邮箱用tag的样式展示 -->
<input <div
v-model="emailForm.to" v-if="emailForm.receiveEmail"
type="text"
class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="输入收件人邮箱,多个邮箱用逗号分隔" >
:disabled="!!selectedVariableTemplate" <span
/> v-for="email in emailForm.receiveEmail.split(',').filter((e) => e.trim())"
:key="email"
class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium"
>
{{ email.trim() }}
</span>
</div>
<div
v-else
class="flex-1 px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
暂无收件人
</div>
<!-- 当选择模版有值时,不显示选择联系人按钮 --> <!-- 当选择模版有值时,不显示选择联系人按钮 -->
<button <button
@click="showContactSelector = true" @click="((showContactSelector = true), (importSource = 0))"
class="bg-blue-50 hover:bg-blue-100 text-blue-600 px-4 py-2 rounded-md border border-blue-200 transition-colors flex items-center" class="bg-blue-50 hover:bg-blue-100 text-blue-600 px-4 py-2 rounded-md border border-blue-200 transition-colors flex items-center"
v-if="!selectedVariableTemplate" v-if="
!selectedVariableTemplate ||
selectedVariableTemplate?.variableGroupBizId == 'email_variable_group_a2Z0lJLQlCuO81ZE'
"
> >
<i class="fas fa-address-book mr-1"></i> 选择联系人 <i class="fas fa-address-book mr-1"></i> 选择联系人
</button> </button>
...@@ -65,13 +79,30 @@ ...@@ -65,13 +79,30 @@
<div class="mb-4"> <div class="mb-4">
<label class="block text-gray-700 mb-2 font-medium">抄送人</label> <label class="block text-gray-700 mb-2 font-medium">抄送人</label>
<input <div
v-model="emailForm.cc" v-if="emailForm.ccEmails"
type="text" class="flex flex-wrap gap-2 p-3 border border-gray-300 rounded-md bg-gray-50 min-h-[42px]"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" >
placeholder="输入抄送人邮箱,多个邮箱用逗号分隔" <div
:disabled="!!selectedVariableTemplate" v-for="emailGroup in emailForm.ccEmails.split(',').filter((e) => e.trim())"
/> :key="emailGroup"
class="inline-flex flex-wrap gap-1 px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium"
>
<span
v-for="email in emailGroup.split(';').filter((e) => e.trim())"
:key="email"
class="inline-flex items-center px-2 py-0.5 bg-orange-50 text-yellow-800 rounded-md text-xs"
>
{{ email.trim() }}
</span>
</div>
</div>
<div
v-else
class="flex items-center p-3 border border-gray-300 rounded-md bg-gray-50 text-gray-500 min-h-[42px]"
>
暂无抄送人
</div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
...@@ -93,6 +124,19 @@ ...@@ -93,6 +124,19 @@
> >
<i class="fas fa-plus"></i> 插入字段 <i class="fas fa-plus"></i> 插入字段
</button> </button>
<!-- 点击插入姓名,正文插入 {{姓名}} -->
<button
@click="insertContent('name')"
class="text-sm bg-orange-50 hover:bg-orange-100 text-orange-600 px-3 py-1 rounded border border-orange-200 transition-colors"
>
<i class="fas fa-plus"></i> 插入姓名
</button>
<button
@click="insertContent('appellation')"
class="text-sm bg-orange-50 hover:bg-orange-100 text-orange-600 px-3 py-1 rounded border border-orange-200 transition-colors"
>
<i class="fas fa-plus"></i> 插入称谓
</button>
</div> </div>
<textarea <textarea
v-model="emailForm.content" v-model="emailForm.content"
...@@ -142,7 +186,7 @@ ...@@ -142,7 +186,7 @@
<div v-if="emailForm.scheduleSend" class="flex items-center"> <div v-if="emailForm.scheduleSend" class="flex items-center">
<input <input
type="datetime-local" type="datetime-local"
v-model="emailForm.sendTime" v-model="emailForm.scheduleTime"
class="px-3 py-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" class="px-3 py-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/> />
</div> </div>
...@@ -258,12 +302,12 @@ import type { ...@@ -258,12 +302,12 @@ import type {
Contact, Contact,
Variable, Variable,
VariableTemplate, VariableTemplate,
Email, SendEmail,
EmailForm,
ImportRecord, ImportRecord,
EmailForm,
} from '../types' } from '../types'
// 引入api接口,获取联系人列表、发件人列表、变量模版列表 // 引入api接口,获取联系人列表、发件人列表、变量模版列表
import { senderApi, variableGroupApi, contactApi, getContactList } from '../api/api' import { senderApi, variableGroupApi, contactApi, sendEmailApi, importContactApi } from '../api/api'
// 引入弹窗组件 // 引入弹窗组件
import CommonModal from '@/components/CommonModal.vue' import CommonModal from '@/components/CommonModal.vue'
// 弹窗提示信息对象 // 弹窗提示信息对象
...@@ -328,21 +372,27 @@ const getGroups = () => { ...@@ -328,21 +372,27 @@ const getGroups = () => {
}) })
} }
const importSource = ref(0)
const senders = ref<Sender[]>([]) const senders = ref<Sender[]>([])
const contacts = ref<Contact[]>([]) const contacts = ref<Contact<unknown>[]>([])
const groups = ref<VariableTemplate[]>([]) const groups = ref<VariableTemplate[]>([])
const variables = ref<Variable[]>([]) const variables = ref<Variable[]>([])
// 状态 // 状态
const currentSender = ref<Sender | null>(senders.value.length > 0 ? senders.value[0] : null) const currentSender = ref<Sender | null>(senders.value.length > 0 ? senders.value[0] : null)
const emailForm = ref<EmailForm>({ const emailForm = ref<EmailForm>({
to: '', senderBizId: '',
cc: '', sendEmail: '',
variableGroupBizId: '',
receiveEmail: '',
ccEmailList: [],
subject: '', subject: '',
content: '', content: '',
scheduleSend: false, attachmentPath: '',
sendTime: '', sessionId: '',
ccEmails: '',
}) })
const selectedVariableTemplate = ref<VariableTemplate | null>(null) const selectedVariableTemplate = ref<VariableTemplate | null>(null)
const attachments = ref<File[]>([]) const attachments = ref<File[]>([])
const showContactSelector = ref(false) const showContactSelector = ref(false)
...@@ -354,12 +404,12 @@ const importRecords = ref<ImportRecord[]>([]) ...@@ -354,12 +404,12 @@ const importRecords = ref<ImportRecord[]>([])
// 监听收件人变化,自动匹配抄送人 // 监听收件人变化,自动匹配抄送人
watch( watch(
() => emailForm.value.to, () => emailForm.value.receiveEmail,
(newTo) => { (newTo) => {
if (newTo) { if (newTo) {
const matchedRecord = importRecords.value.find((record) => record.to === newTo) const matchedRecord = importRecords.value.find((record) => record.receiveEmail === newTo)
if (matchedRecord && matchedRecord.cc) { if (matchedRecord && matchedRecord.ccEmailList) {
emailForm.value.cc = matchedRecord.cc emailForm.value.ccEmailList = matchedRecord.ccEmailList
} }
} }
}, },
...@@ -410,7 +460,7 @@ const insertVariable = (variable: Variable) => { ...@@ -410,7 +460,7 @@ const insertVariable = (variable: Variable) => {
if (textarea && document.activeElement === textarea) { if (textarea && document.activeElement === textarea) {
const start = textarea.selectionStart const start = textarea.selectionStart
const end = textarea.selectionEnd const end = textarea.selectionEnd
const currentValue = emailForm.value.content const currentValue = emailForm.value.content || ''
emailForm.value.content = emailForm.value.content =
currentValue.substring(0, start) + variableText + currentValue.substring(end) currentValue.substring(0, start) + variableText + currentValue.substring(end)
...@@ -427,34 +477,31 @@ const insertVariable = (variable: Variable) => { ...@@ -427,34 +477,31 @@ const insertVariable = (variable: Variable) => {
showVariableSelector.value = false showVariableSelector.value = false
} }
const confirmContactSelection = (selected: { to: string; cc: string }) => { const insertContent = (i: string) => {
emailForm.value.to = selected.to emailForm.value.content += `${variablePrefix}${i}${variableNextfix}`
emailForm.value.cc = selected.cc
showContactSelector.value = false
// 保存导入记录
saveImportRecord(selected.to, selected.cc)
} }
// 保存导入记录 const confirmContactSelection = (selected) => {
const saveImportRecord = (to: string, cc: string) => { emailForm.value.ccEmailList = selected.cc
const existingRecord = importRecords.value.find((record) => record.to === to) const params = {
sessionId: '',
if (existingRecord) { apiEmailContactDtoList: selected,
// 更新现有记录
existingRecord.cc = cc
existingRecord.updatedAt = new Date().toISOString()
} else {
// 添加新记录
const newRecord: ImportRecord = {
id: Date.now().toString(),
to,
cc,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
importRecords.value.push(newRecord)
} }
showContactSelector.value = false
importContactApi.getEmailContactSessionId(params).then((res) => {
if (res.code === 200) {
emailForm.value.sessionId = res.data?.sessionId || ''
if (res.data?.sessionId) {
importContactApi.getEmailContactImportDetail(res.data?.sessionId || '').then((res) => {
if (res.code === 200) {
emailForm.value.receiveEmail = res.data?.receiveEmails || ''
emailForm.value.ccEmails = res.data?.ccEmails || ''
}
})
}
getImportedContacts(emailForm.value.sessionId || '')
}
})
} }
// 更新导入记录 // 更新导入记录
...@@ -478,7 +525,6 @@ const handleImportContacts = (event: { file: File; content: string }) => { ...@@ -478,7 +525,6 @@ const handleImportContacts = (event: { file: File; content: string }) => {
lines.forEach((line) => { lines.forEach((line) => {
const [to, cc] = line.split(',') const [to, cc] = line.split(',')
if (to && to.includes('@')) { if (to && to.includes('@')) {
saveImportRecord(to.trim(), cc ? cc.trim() : '')
} }
}) })
} }
...@@ -487,8 +533,8 @@ const saveAsDraft = () => { ...@@ -487,8 +533,8 @@ const saveAsDraft = () => {
alert('请添加并选择发件人') alert('请添加并选择发件人')
return return
} }
const draft: Email = { const draft: SendEmail = {
id: Date.now().toString(), senderBizId: currentSender.value.senderBizId || '',
sender: currentSender.value.email || '', sender: currentSender.value.email || '',
to: emailForm.value.to ? emailForm.value.to.split(',') : [], to: emailForm.value.to ? emailForm.value.to.split(',') : [],
cc: emailForm.value.cc ? emailForm.value.cc.split(',') : [], cc: emailForm.value.cc ? emailForm.value.cc.split(',') : [],
...@@ -503,6 +549,21 @@ const saveAsDraft = () => { ...@@ -503,6 +549,21 @@ const saveAsDraft = () => {
const sendEmail = () => { const sendEmail = () => {
showPreview.value = true showPreview.value = true
const params = {
...emailForm.value,
variableGroupBizId: selectedVariableTemplate.value?.variableGroupBizId || '',
senderBizId: currentSender.value?.senderBizId,
sendEmail: currentSender.value?.email || '',
}
console.log(params)
// 确认发送邮件
sendEmailApi.sendEmail(params).then((res) => {
if (res.code === 200) {
alert('邮件发送成功')
} else {
alert('邮件发送失败')
}
})
} }
const confirmSendEmail = () => { const confirmSendEmail = () => {
...@@ -537,4 +598,30 @@ const confirmSendEmail = () => { ...@@ -537,4 +598,30 @@ const confirmSendEmail = () => {
showPreview.value = false showPreview.value = false
alert(emailForm.value.scheduleSend ? '邮件已安排定时发送' : '邮件发送成功') alert(emailForm.value.scheduleSend ? '邮件已安排定时发送' : '邮件发送成功')
} }
// 通过sessionId获取导入的联系人
const getImportedContacts = (sessionId: string) => {
const params = {
sessionId: sessionId || '',
source: importSource.value,
}
importContactApi.getEmailContactImportList(params).then((res) => {
if (res.code === 200) {
console.log('导入的联系人:', res.data)
importRecords.value = res.data.records || []
}
})
}
// 编辑数据
const editImportRecord = (record: ImportRecord) => {
console.log('编辑导入记录:', record)
// 这里可以添加编辑逻辑,例如打开编辑弹窗
importContactApi.editEmailContactImport(record).then((res) => {
if (res.code === 200) {
console.log('编辑导入记录成功:', res.data)
updateImportRecord(res.data)
}
})
}
</script> </script>
...@@ -99,12 +99,6 @@ const confirmSelection = () => { ...@@ -99,12 +99,6 @@ const confirmSelection = () => {
const selected = props.contacts.filter((contact) => const selected = props.contacts.filter((contact) =>
selectedContacts.value.includes(contact.contactBizId || ''), selectedContacts.value.includes(contact.contactBizId || ''),
) )
const to = selected.map((contact) => contact.email).join(',') emits('confirm-selection', selected)
const cc = selected
.map((contact) => contact.ccEmailList?.join(',') || '')
.filter((email) => email)
.join(',')
emits('confirm-selection', { to, cc })
} }
</script> </script>
...@@ -65,9 +65,9 @@ ...@@ -65,9 +65,9 @@
</tr> </tr>
</thead> </thead>
<tbody class="bg-white divide-y divide-gray-200"> <tbody class="bg-white divide-y divide-gray-200">
<tr v-for="email in filteredEmails" :key="email.id"> <tr v-for="email in emails" :key="email.taskBizId">
<td class="px-6 py-4 whitespace-nowrap">{{ email.sender }}</td> <td class="px-6 py-4 whitespace-nowrap">{{ email.sendEmail }}</td>
<td class="px-6 py-4 whitespace-nowrap max-w-xs truncate">{{ email.to }}</td> <td class="px-6 py-4 whitespace-nowrap max-w-xs truncate">{{ email.receiveEmails }}</td>
<td class="px-6 py-4 max-w-xs truncate">{{ email.subject }}</td> <td class="px-6 py-4 max-w-xs truncate">{{ email.subject }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ formatDate(email.sendTime) }} {{ formatDate(email.sendTime) }}
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div v-if="filteredEmails.length === 0" class="p-8 text-center text-gray-500"> <div v-if="emails.length === 0" class="p-8 text-center text-gray-500">
<i class="fas fa-history text-4xl mb-3 opacity-30"></i> <i class="fas fa-history text-4xl mb-3 opacity-30"></i>
<p>暂无邮件发送记录</p> <p>暂无邮件发送记录</p>
</div> </div>
...@@ -119,27 +119,17 @@ ...@@ -119,27 +119,17 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, defineProps, defineEmits } from 'vue' import { ref, computed, onMounted } from 'vue'
import type { Email } from '@/types/index' import type { EmailTask } from '@/types/index'
import { sendEmailApi } from '@/api/api'
// 状态 // 状态
const emails = ref<Email[]>([]) const emails = ref<EmailTask[]>([])
const searchTerm = ref('') const searchTerm = ref('')
const filterStatus = ref('') const filterStatus = ref('')
// 计算属性 onMounted(() => {
const filteredEmails = computed(() => { getEmailTaskMainList()
return emails.value
.filter((email) => {
const matchesSearch =
email.subject.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
email.to.toLowerCase().includes(searchTerm.value.toLowerCase())
const matchesStatus = !filterStatus.value || email.status === filterStatus.value
return matchesSearch && matchesStatus
})
.sort((a, b) => new Date(b.sendTime).getTime() - new Date(a.sendTime).getTime())
}) })
// 方法 // 方法
...@@ -148,7 +138,7 @@ const formatDate = (dateString: string) => { ...@@ -148,7 +138,7 @@ const formatDate = (dateString: string) => {
return date.toLocaleString() return date.toLocaleString()
} }
const viewEmailDetail = (email: Email) => { const viewEmailDetail = (email: EmailTask) => {
// 显示邮件详情 // 显示邮件详情
alert( alert(
`邮件主题: ${email.subject || '无'}\n收件人: ${email.to || '无'}\n发送时间: ${formatDate(email.sendTime || '')}`, `邮件主题: ${email.subject || '无'}\n收件人: ${email.to || '无'}\n发送时间: ${formatDate(email.sendTime || '')}`,
...@@ -156,7 +146,19 @@ const viewEmailDetail = (email: Email) => { ...@@ -156,7 +146,19 @@ const viewEmailDetail = (email: Email) => {
// 实际项目中可以打开详情弹窗 // 实际项目中可以打开详情弹窗
} }
const reuseEmailContent = (email: Email) => { const reuseEmailContent = (email: EmailTask) => {
// 触发复用邮件内容事件 // 触发复用邮件内容事件
} }
// 发送任务列表查询
const getEmailTaskMainList = async () => {
const params: EmailTask = {
pageNum: 1,
pageSize: 100,
}
const res = await sendEmailApi.getEmailTaskMainList(params)
if (res.code === 200) {
emails.value = res.data.records || []
}
}
</script> </script>
...@@ -30,73 +30,127 @@ ...@@ -30,73 +30,127 @@
<p>未找到匹配的导入记录</p> <p>未找到匹配的导入记录</p>
</div> </div>
<div v-else class="space-y-3"> <div v-else class="overflow-x-auto">
<div <table class="min-w-full divide-y divide-gray-200">
v-for="record in filteredRecords" <thead class="bg-gray-50">
:key="record.id" <tr>
class="border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow" <th
> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
<div class="flex justify-between items-start mb-3">
<div class="flex-1">
<div class="font-medium text-gray-900">收件人: {{ record.to }}</div>
<div class="text-sm text-gray-600 mt-1">抄送人: {{ record.cc || '无' }}</div>
<div class="text-xs text-gray-400 mt-2">
创建时间: {{ formatDate(record.createdAt) }}
</div>
</div>
<div class="flex gap-2">
<button
@click="editRecord(record)"
class="px-3 py-1 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 transition-colors text-sm"
>
编辑
</button>
<button
@click="deleteRecord(record.id)"
class="px-3 py-1 bg-red-100 text-red-700 rounded-md hover:bg-red-200 transition-colors text-sm"
> >
删除 收件人
</button> </th>
</div> <th
</div> class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
<div v-if="editingRecordId === record.id" class="mt-3 p-3 bg-gray-50 rounded-md">
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">收件人</label>
<input
v-model="editingRecord.to"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="收件人邮箱"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">抄送人</label>
<input
v-model="editingRecord.cc"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="抄送人邮箱,多个用逗号分隔"
/>
</div>
</div>
<div class="flex justify-end gap-2 mt-3">
<button
@click="cancelEdit"
class="px-3 py-1 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors text-sm"
> >
取消 抄送人
</button> </th>
<button <th
@click="saveEdit" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
class="px-3 py-1 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors text-sm"
> >
保存 操作
</button> </th>
</div> </tr>
</div> </thead>
</div> <tbody class="bg-white divide-y divide-gray-200">
<tr
v-for="record in filteredRecords"
:key="record.importBizId"
class="hover:bg-gray-50"
>
<td class="px-6 py-4 whitespace-nowrap">
<div v-if="editingRecordId === record.importBizId" class="w-full">
<input
v-model="editingRecord.receiveEmail"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="收件人邮箱"
/>
</div>
<div v-else class="text-sm text-gray-900">
{{ record.receiveEmail || '无' }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div v-if="editingRecordId === record.importBizId" class="w-full">
<!-- 抄送人tag输入区域 -->
<div class="flex flex-wrap gap-1 mb-2">
<span
v-for="(tag, index) in ccTags"
:key="index"
class="inline-flex items-center px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full"
>
{{ tag }}
<button
@click="removeCcTag(index)"
class="ml-1 text-blue-600 hover:text-blue-800"
>
×
</button>
</span>
</div>
<!-- 抄送人输入框 -->
<div class="flex gap-2">
<input
v-model="newCcTag"
type="text"
@keydown="handleCcInputKeydown"
class="flex-1 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="输入抄送人邮箱后按回车"
/>
<button
@click="addCcTag"
class="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors text-sm"
>
添加
</button>
</div>
</div>
<div v-else class="text-sm text-gray-900">
<div v-if="record.ccEmail" class="flex flex-wrap gap-1">
<span
v-for="(tag, index) in parseCcTags(record.ccEmail)"
:key="index"
class="inline-flex items-center px-2 py-1 bg-gray-100 text-gray-800 text-xs rounded-full"
>
{{ tag }}
</span>
</div>
<span v-else></span>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div v-if="editingRecordId === record.id" class="flex gap-2">
<button
@click="cancelEdit"
class="px-3 py-1 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors text-sm"
>
取消
</button>
<button
@click="saveEdit"
class="px-3 py-1 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors text-sm"
>
保存
</button>
</div>
<div v-else class="flex gap-2">
<button
@click="editRecord(record)"
class="px-3 py-1 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 transition-colors text-sm"
>
编辑
</button>
<button
@click="deleteRecord(record.id)"
class="px-3 py-1 bg-red-100 text-red-700 rounded-md hover:bg-red-200 transition-colors text-sm"
>
删除
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
...@@ -128,40 +182,91 @@ const emits = defineEmits(['update-record', 'delete-record', 'close']) ...@@ -128,40 +182,91 @@ const emits = defineEmits(['update-record', 'delete-record', 'close'])
const searchTerm = ref('') const searchTerm = ref('')
const editingRecordId = ref<string | null>(null) const editingRecordId = ref<string | null>(null)
const editingRecord = ref<Partial<ImportRecord>>({}) const editingRecord = ref<Partial<ImportRecord>>({})
const ccTags = ref<string[]>([])
const newCcTag = ref('')
const filteredRecords = computed(() => { const filteredRecords = computed(() => {
if (!searchTerm.value) return props.records if (!searchTerm.value) return props.records
return props.records.filter( return props.records.filter(
(record) => (record) =>
record.to.toLowerCase().includes(searchTerm.value.toLowerCase()) || record.receiveEmail?.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
record.cc.toLowerCase().includes(searchTerm.value.toLowerCase()), record.ccEmail?.toLowerCase().includes(searchTerm.value.toLowerCase()),
) )
}) })
// 将分号分隔的字符串转换为数组
const parseCcTags = (ccString: string) => {
if (!ccString) return []
return ccString
.split(';')
.filter((tag) => tag.trim())
.map((tag) => tag.trim())
}
// 将数组转换为分号分隔的字符串
const joinCcTags = (tags: string[]) => {
return tags.join(';')
}
// 添加新的抄送人tag
const addCcTag = () => {
if (newCcTag.value.trim()) {
ccTags.value.push(newCcTag.value.trim())
newCcTag.value = ''
updateEditingRecordCc()
}
}
// 删除抄送人tag
const removeCcTag = (index: number) => {
ccTags.value.splice(index, 1)
updateEditingRecordCc()
}
// 更新编辑记录中的抄送人字段
const updateEditingRecordCc = () => {
editingRecord.value.ccEmail = joinCcTags(ccTags.value)
}
// 处理输入框回车事件
const handleCcInputKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
addCcTag()
}
}
const formatDate = (dateString: string) => { const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString('zh-CN') return new Date(dateString).toLocaleString('zh-CN')
} }
const editRecord = (record: ImportRecord) => { const editRecord = (record: ImportRecord) => {
editingRecordId.value = record.id editingRecordId.value = record.importBizId
editingRecord.value = { ...record } editingRecord.value = { ...record }
// 初始化抄送人tag数组
ccTags.value = parseCcTags(record.ccEmail || '')
newCcTag.value = ''
} }
const cancelEdit = () => { const cancelEdit = () => {
editingRecordId.value = null editingRecordId.value = null
editingRecord.value = {} editingRecord.value = {}
ccTags.value = []
newCcTag.value = ''
} }
const saveEdit = () => { const saveEdit = () => {
if (editingRecordId.value && editingRecord.value.to) { if (editingRecordId.value && editingRecord.value.receiveEmail) {
emits('update-record', { emits('update-record', {
id: editingRecordId.value, importBizId: editingRecordId.value,
to: editingRecord.value.to, receiveEmail: editingRecord.value.receiveEmail,
cc: editingRecord.value.cc || '', ccEmail: editingRecord.value.ccEmail || '',
updatedAt: new Date().toISOString(), updateTime: new Date().toISOString(),
}) })
editingRecordId.value = null editingRecordId.value = null
editingRecord.value = {} editingRecord.value = {}
ccTags.value = []
newCcTag.value = ''
} }
} }
......
...@@ -148,7 +148,7 @@ ...@@ -148,7 +148,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, defineProps, defineEmits, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { emailProviderApi, senderApi, getEmailSenderConfigList } from '@/api/api' import { emailProviderApi, senderApi, getEmailSenderConfigList } from '@/api/api'
import type { Sender } from '@/types/index' import type { Sender } from '@/types/index'
// 引入弹窗组件 // 引入弹窗组件
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment