Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
frontend-yd-email
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Sweet Zhang
frontend-yd-email
Commits
19046586
Commit
19046586
authored
Sep 26, 2025
by
Sweet Zhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
调通发邮件
parent
70b9e0d4
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
420 additions
and
193 deletions
+420
-193
src/api/api.ts
+24
-0
src/types/index.ts
+47
-32
src/utils/fileUpload.ts
+0
-0
src/utils/request.ts
+1
-1
src/views/ComposeEmail.vue
+145
-58
src/views/ContactSelector.vue
+1
-7
src/views/EmailManagement.vue
+24
-22
src/views/ImportRecordManager.vue
+177
-72
src/views/SenderManagement.vue
+1
-1
No files found.
src/api/api.ts
View file @
19046586
...
@@ -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
)
},
}
}
src/types/index.ts
View file @
19046586
...
@@ -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
}
src/utils/fileUpload.ts
0 → 100644
View file @
19046586
src/utils/request.ts
View file @
19046586
...
@@ -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.eyJzdWIiOiJ1c2VyXzEwMDEiLCJyb2xlcyI6W10sImlhdCI6MTc1OD
c4NTY3NCwiZXhwIjoxNzU4ODcyMDc0fQ.tjTO6vdpwLpNjVa1DhxRBdpjZsdhbx6g1TdtpAm7BZBRMwanM_ci7dsnbc8FNXpyfSb-ifXW7ccxwyQbtCaKiQ
'
,
'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyXzEwMDEiLCJyb2xlcyI6W10sImlhdCI6MTc1OD
g3MjMyOSwiZXhwIjoxNzU4OTU4NzI5fQ.McyflIoI_ltve_uy2-mZTjOfxYfBGNMEuOoIVfeEtXdAuoycggGErq8yU3mc15npsIWJy2a8zJ5cNpx_NVtGIw
'
,
},
},
})
})
...
...
src/views/ComposeEmail.vue
View file @
19046586
...
@@ -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.s
end
Time"
v-model=
"emailForm.s
chedule
Time"
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
.
cc
EmailList
)
{
emailForm
.
value
.
cc
=
matchedRecord
.
cc
emailForm
.
value
.
cc
EmailList
=
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
:
Send
Email
=
{
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
>
src/views/ContactSelector.vue
View file @
19046586
...
@@ -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
>
src/views/EmailManagement.vue
View file @
19046586
...
@@ -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.i
d"
>
<tr
v-for=
"email in
emails"
:key=
"email.taskBizI
d"
>
<td
class=
"px-6 py-4 whitespace-nowrap"
>
{{
email
.
send
er
}}
</td>
<td
class=
"px-6 py-4 whitespace-nowrap"
>
{{
email
.
send
Email
}}
</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=
"
filteredE
mails.length === 0"
class=
"p-8 text-center text-gray-500"
>
<div
v-if=
"
e
mails.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
<
Email
Task
[]
>
([])
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
:
Email
Task
)
=>
{
// 显示邮件详情
// 显示邮件详情
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
:
Email
Task
)
=>
{
// 触发复用邮件内容事件
// 触发复用邮件内容事件
}
}
// 发送任务列表查询
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
>
src/views/ImportRecordManager.vue
View file @
19046586
...
@@ -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
.
cc
Email
?
.
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
.
i
mportBizI
d
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
,
i
mportBizI
d
:
editingRecordId
.
value
,
to
:
editingRecord
.
value
.
to
,
receiveEmail
:
editingRecord
.
value
.
receiveEmail
,
cc
:
editingRecord
.
value
.
cc
||
''
,
cc
Email
:
editingRecord
.
value
.
ccEmail
||
''
,
update
dAt
:
new
Date
().
toISOString
(),
update
Time
:
new
Date
().
toISOString
(),
})
})
editingRecordId
.
value
=
null
editingRecordId
.
value
=
null
editingRecord
.
value
=
{}
editingRecord
.
value
=
{}
ccTags
.
value
=
[]
newCcTag
.
value
=
''
}
}
}
}
...
...
src/views/SenderManagement.vue
View file @
19046586
...
@@ -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'
// 引入弹窗组件
// 引入弹窗组件
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment