Commit 4de2993b by Sweet Zhang

联系人管理、发件人管理对接

parent d86fbaed
......@@ -34,19 +34,7 @@
</header>
<!-- 使用router-view显示当前路由组件 -->
<router-view
:senders="senders"
:contacts="contacts"
:variables="variables"
:variable-templates="variableTemplates"
:emails="emails"
@save-email="saveEmail"
@update-contacts="updateContacts"
@update-senders="updateSenders"
@update-variables="updateVariables"
@update-variable-templates="updateVariableTemplates"
@reuse-email="reuseEmail"
/>
<router-view />
</main>
</div>
</div>
......@@ -59,7 +47,6 @@ import { useRoute, useRouter } from 'vue-router'
import LoginPage from './views/LoginPage.vue'
import Sidebar from './views/Sidebar.vue'
import MobileSidebar from './views/MobileSidebar.vue'
import type { Contact, Sender, Variable, VariableTemplate, Email } from './types'
const route = useRoute()
const router = useRouter()
......@@ -80,13 +67,6 @@ watch(
},
)
// 数据存储(保持不变)
const contacts = ref<Contact[]>([])
const senders = ref<Sender[]>([])
const variables = ref<Variable[]>([])
const variableTemplates = ref<VariableTemplate[]>([])
const emails = ref<Email[]>([])
// 页面标题映射(保持不变)
const pageTitles = {
compose: '写邮件',
......@@ -100,7 +80,6 @@ const pageTitles = {
const handleLogin = () => {
isAuthenticated.value = true
isLoginPage.value = false
loadInitialData()
router.push('/compose') // 登录后跳转到写邮件页面
}
......@@ -110,64 +89,10 @@ const handleLogout = () => {
router.push('/login') // 退出后跳转到登录页面
}
const handlePageChange = (page: string) => {
currentPage.value = page as 'compose' | 'contacts' | 'senders' | 'variables' | 'emails'
}
const handleMobilePageChange = (page: string) => {
currentPage.value = page as 'compose' | 'contacts' | 'senders' | 'variables' | 'emails'
showMobileMenu.value = false
}
const updateContacts = (newContacts: Contact[]) => {
contacts.value = newContacts
}
const updateSenders = (newSenders: Sender[]) => {
senders.value = newSenders
}
const updateVariables = (newVariables: Variable[]) => {
variables.value = newVariables
}
const updateVariableTemplates = (newTemplates: VariableTemplate[]) => {
variableTemplates.value = newTemplates
}
const saveEmail = (email: Email) => {
emails.value.push(email)
}
const reuseEmail = (emailData: Email) => {
currentPage.value = 'compose'
// 这里可以传递需要复用的邮件数据到ComposeEmail组件
// 实际实现中可以使用状态管理或props
}
const loadInitialData = () => {
// 模拟加载初始数据
// 联系人
contacts.value = []
// 发件人
senders.value = []
// 变量
variables.value = []
// 变量模板
variableTemplates.value = []
// 邮件记录
emails.value = []
}
// 初始化
onMounted(() => {
// 检查是否已登录(实际项目中应该检查本地存储或令牌)
if (isAuthenticated.value) {
loadInitialData()
}
})
</script>
......@@ -35,7 +35,7 @@ export const editContact = (data) => {
* @returns
*/
export const deleteContact = (id: number) => {
return request.delete('/emailContact/del?contactBizId=' + id, { params: { contactBizId: id } })
return request.delete('/emailContact/del?contactBizId=' + id)
}
// 获取联系人详情
......@@ -74,6 +74,28 @@ export const getContactList = (params: {
return request.post('/emailContact/page', params)
}
/**服务商列表 */
/**
*
* @param params {
"providerName": "", //邮箱服务商名称
"pageNo": 1,
"pageSize": 1,
"sortField": "",
"sortOrder": ""
}
* @returns
*/
export const getEmailProviderList = (params: {
providerName?: string
pageNo?: number
pageSize?: number
sortField?: string
sortOrder?: string
}) => {
return request.post('/emailProviderConfig/page', params)
}
// 新增发送配置
/**
*
......@@ -81,13 +103,11 @@ export const getContactList = (params: {
* @returns
*/
export const addEmailSenderConfig = (data: {
emailSenderConfigBizId?: number
emailSenderConfigName?: string
emailSenderConfigEmail?: string
emailSenderConfigType?: string
emailSenderConfigAppellation?: string
emailSenderConfigOther?: string
emailSenderConfigCcEmailList?: string[]
email?: number
password?: string
providerBizId?: string
displayName?: string
active?: boolean
}) => {
return request.post('/emailSenderConfig/add', data)
}
......@@ -99,13 +119,12 @@ export const addEmailSenderConfig = (data: {
* @returns
*/
export const editEmailSenderConfig = (data: {
emailSenderConfigBizId?: number
emailSenderConfigName?: string
emailSenderConfigEmail?: string
emailSenderConfigType?: string
emailSenderConfigAppellation?: string
emailSenderConfigOther?: string
emailSenderConfigCcEmailList?: string[]
senderBizId?: number
email?: string
password?: string
providerBizId?: string
displayName?: string
active?: number
}) => {
return request.put('/emailSenderConfig/edit', data)
}
......@@ -116,8 +135,8 @@ export const editEmailSenderConfig = (data: {
* @param id 发送配置id
* @returns
*/
export const deleteEmailSenderConfig = (id: number) => {
return request.delete('/emailSenderConfig/delete', { params: { emailSenderConfigBizId: id } })
export const deleteEmailSenderConfig = (id: string) => {
return request.delete('/emailSenderConfig/del?senderBizId=' + id)
}
// 获取发送配置详情
......@@ -126,8 +145,8 @@ export const deleteEmailSenderConfig = (id: number) => {
* @param id 发送配置id
* @returns
*/
export const getEmailSenderConfigDetail = (id: number) => {
return request.get('/emailSenderConfig/detail', { params: { emailSenderConfigBizId: id } })
export const getEmailSenderConfigDetail = (id: string) => {
return request.get('/emailSenderConfig/detail', { params: { senderBizId: id } })
}
// 获取发送配置列表
......
......@@ -6,7 +6,7 @@ const router = createRouter({
routes: [
{
path: '/',
redirect: '/compose',
redirect: '/login',
},
{
path: '/compose',
......
// 联系人类型
export interface Contact {
id: string
name: string
type: string
companyName: string
email: string
ccEmailList: string[]
other: string
appellation: string
contactBizId?: string
name?: string
type?: string
companyName?: string
email?: string
ccEmailList?: string[]
other?: string
appellation?: string
}
// 发件人类型
export interface Sender {
id: string
email: string
password: string
smtpServer: string
smtpPort: string
senderBizId?: string
email?: string
password?: string
displayName?: string
providerBizId?: string
active?: number
}
// 变量类型
......
......@@ -8,7 +8,7 @@ const request = axios.create({
'Content-Type': 'application/json',
// Authorization: 'Bearer ' + localStorage.getItem('authToken'),
Authorization:
'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyXzEwMDEiLCJyb2xlcyI6W10sImlhdCI6MTc1ODY5OTA3NywiZXhwIjoxNzU4Nzg1NDc3fQ.LR2fGy0aO6EHsHe9Que8rzCaJ0TSAB9KtJndYMSYvvKOSeNvGawCmjE8kgDeRmyFFOFJ2kt0sk-fGaExgzQHSw',
'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyXzEwMDEiLCJyb2xlcyI6W10sImlhdCI6MTc1ODc4NTY3NCwiZXhwIjoxNzU4ODcyMDc0fQ.tjTO6vdpwLpNjVa1DhxRBdpjZsdhbx6g1TdtpAm7BZBRMwanM_ci7dsnbc8FNXpyfSb-ifXW7ccxwyQbtCaKiQ',
},
})
......
......@@ -250,33 +250,14 @@ import {
ImportRecord,
} from '../types'
const props = defineProps({
senders: {
type: Array as () => Sender[],
required: true,
},
contacts: {
type: Array as () => Contact[],
required: true,
},
variables: {
type: Array as () => Variable[],
required: true,
},
variableTemplates: {
type: Array as () => VariableTemplate[],
required: true,
},
emails: {
type: Array as () => Email[],
required: true,
},
})
const emits = defineEmits(['save-email', 'save-import-record'])
const senders = ref<Sender[]>([])
const contacts = ref<Contact[]>([])
const variables = ref<Variable[]>([])
const variableTemplates = ref<VariableTemplate[]>([])
const emails = ref<Email[]>([])
// 状态
const currentSender = ref<Sender | null>(props.senders.length > 0 ? props.senders[0] : null)
const currentSender = ref<Sender | null>(senders.value.length > 0 ? senders.value[0] : null)
const emailForm = ref<EmailForm>({
to: '',
cc: '',
......@@ -307,8 +288,8 @@ watch(
},
)
// 计算属性
const variablePrefix = '{{'
const variableNextfix = '}}'
// 方法
const handleFileUpload = (e: Event) => {
......@@ -325,16 +306,19 @@ const removeAttachment = (index: number) => {
const applyVariableTemplate = () => {
if (!selectedVariableTemplate.value) return
const variableKeys = selectedVariableTemplate.value.variableIds.map((id) => {
const variable = props.variables.find((v) => v.id === id)
return variable ? variable.key : ''
})
const variablesText = variableKeys.map((key) => `${variablePrefix}${key}}`).join(', ')
const variableKeys =
selectedVariableTemplate.value.variableBizIdList.map((id) => {
const variable = variables.value.find((v) => v.variableBizId === id)
return variable ? variable.variableNameEn : ''
}) || []
const variablesText = variableKeys
.map((key) => `${variablePrefix}${key}${variableNextfix}`)
.join(', ')
emailForm.value.content = `【使用了模板变量:${variablesText}】\n\n${emailForm.value.content}`
}
const insertVariable = (variable: Variable) => {
emailForm.value.content += `${variablePrefix}${variable.key}}`
emailForm.value.content += `${variablePrefix}${variable.variableNameEn}${variableNextfix}`
showVariableSelector.value = false
}
......@@ -366,8 +350,6 @@ const saveImportRecord = (to: string, cc: string) => {
}
importRecords.value.push(newRecord)
}
emits('save-import-record', importRecords.value)
}
// 更新导入记录
......@@ -375,14 +357,12 @@ const updateImportRecord = (updatedRecord: ImportRecord) => {
const index = importRecords.value.findIndex((record) => record.id === updatedRecord.id)
if (index !== -1) {
importRecords.value[index] = { ...updatedRecord }
emits('save-import-record', importRecords.value)
}
}
// 删除导入记录
const deleteImportRecord = (id: string) => {
importRecords.value = importRecords.value.filter((record) => record.id !== id)
emits('save-import-record', importRecords.value)
}
// 修改handleImportContacts方法
......@@ -413,7 +393,6 @@ const saveAsDraft = () => {
status: 'draft',
attachments: attachments.value.map((file) => ({ name: file.name })),
}
emits('save-email', draft)
alert('草稿已保存')
}
......@@ -447,7 +426,6 @@ const confirmSendEmail = () => {
status: emailForm.value.scheduleSend ? 'scheduled' : 'sent',
attachments: attachments.value.map((file) => ({ name: file.name })),
}
emits('save-email', email)
emailForm.value = { to: '', cc: '', subject: '', content: '', scheduleSend: false, sendTime: '' }
attachments.value = []
selectedVariableTemplate.value = null
......
<template>
<!-- 模板内容与之前保持一致 -->
<div class="p-4 md:p-6">
<div class="max-w-7xl mx-auto">
<div>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6 gap-4">
<h3 class="text-lg font-semibold">添加联系人</h3>
<button v-if="editingSenderId" @click="resetForm" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i> 取消编辑
</button>
</div>
<!-- 页面标题和操作按钮 -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6 gap-4">
<div>
<h1 class="text-2xl font-bold text-gray-800">联系人管理</h1>
<p class="text-gray-500 mt-1">管理您的所有联系人信息</p>
</div>
<div class="flex gap-3">
<button @click="showImportModal = true" class="btn-outline flex items-center">
<i class="fas fa-upload mr-2"></i> 批量导入
......@@ -18,171 +18,367 @@
</button>
</div>
</div>
</div>
<!-- 搜索和筛选区域 -->
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
<div class="flex flex-col md:flex-row gap-4">
<div class="relative flex-1">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
<input
v-model="searchQuery"
type="text"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg"
placeholder="搜索联系人姓名、公司或邮箱..."
@keyup.enter="searchContacts"
/>
</div>
<div class="flex gap-3 w-full md:w-auto">
<select
v-model="sortBy"
class="w-full md:w-auto px-4 py-2 border border-gray-300 rounded-lg"
@change="searchContacts"
>
<option value="name">按姓名排序</option>
<option value="company">按公司排序</option>
<option value="addedAt">按添加时间排序</option>
</select>
<button @click="resetSearch" class="btn-outline px-4">重置</button>
</div>
<!-- 搜索和筛选区域 -->
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
<div class="flex flex-col md:flex-row gap-4">
<div class="relative flex-1">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
<input
v-model="searchQuery"
type="text"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg"
placeholder="搜索联系人姓名、公司或邮箱..."
@keyup.enter="searchContacts"
/>
</div>
<div class="flex gap-3 w-full md:w-auto">
<select
v-model="sortBy"
class="w-full md:w-auto px-4 py-2 border border-gray-300 rounded-lg"
@change="searchContacts"
>
<option value="name">按姓名排序</option>
<option value="company">按公司排序</option>
<option value="addedAt">按添加时间排序</option>
</select>
<button @click="resetSearch" class="btn-outline px-4">重置</button>
</div>
</div>
</div>
<!-- 联系人列表 -->
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="bg-gray-50 border-b border-gray-200">
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
姓名
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
称谓
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
公司
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
收件人邮箱
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
抄送人邮箱
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
其他信息
</th>
<th
class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"
>
操作
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr
v-for="contact in filteredContacts"
:key="contact.id"
class="hover:bg-gray-50 transition-colors"
<!-- 联系人列表 -->
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="bg-gray-50 border-b border-gray-200">
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div
class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 mr-3"
>
<span class="text-sm font-medium">{{ contact.name.charAt(0) }}</span>
</div>
<div>
<div class="text-sm font-medium text-gray-900">{{ contact.name }}</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500">{{ contact.title || '-' }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500">{{ contact.company || '-' }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500">{{ contact.toEmail }}</div>
</td>
<td class="px-6 py-4">
<div class="flex flex-wrap gap-1">
<span
v-for="(email, index) in contact.ccEmails"
:key="index"
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
>
{{ email }}
</span>
<span v-if="contact.ccEmails.length === 0" class="text-sm text-gray-500"
>-</span
>
姓名
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
称谓
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
公司
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
收件人邮箱
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
抄送人邮箱
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
其他信息
</th>
<th
class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"
>
操作
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr
v-for="contact in filteredContacts"
:key="contact.contactBizId"
class="hover:bg-gray-50 transition-colors"
>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div
class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 mr-3"
>
<span class="text-sm font-medium">{{ contact.name?.charAt(0) || '-' }}</span>
</div>
</td>
<td class="px-6 py-4">
<div class="text-sm text-gray-500 line-clamp-2">
{{ contact.otherInfo || '-' }}
<div>
<div class="text-sm font-medium text-gray-900">{{ contact.name || '-' }}</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
@click="editContact(contact)"
class="text-blue-600 hover:text-blue-900 mr-4"
title="编辑"
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500">{{ contact.appellation || '-' }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500">{{ contact.companyName || '-' }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500">{{ contact.email || '-' }}</div>
</td>
<td class="px-6 py-4">
<div class="flex flex-wrap gap-1">
<span
v-for="(email, index) in contact.ccEmailList || []"
:key="index"
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
>
<i class="fas fa-edit"></i>
</button>
{{ email }}
</span>
<span v-if="contact.ccEmailList?.length === 0" class="text-sm text-gray-500"
>-</span
>
</div>
</td>
<td class="px-6 py-4">
<div class="text-sm text-gray-500 line-clamp-2">
{{ contact.other || '-' }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
@click="editContactModal(contact)"
class="text-blue-600 hover:text-blue-900 mr-4"
title="编辑"
>
<i class="fas fa-edit"></i>
</button>
<button
@click="deleteContactModal(contact)"
class="text-red-600 hover:text-red-900"
title="删除"
>
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 空状态、加载状态和分页组件与之前保持一致 -->
</div>
<!-- 联系人模态框 -->
<div
v-if="showContactModal"
class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4"
>
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-lg font-semibold">
{{ isEditing ? '编辑联系人' : '新建联系人' }}
</h3>
<button @click="closeContactModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4 flex-1 overflow-y-auto">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- 姓名 -->
<div class="md:col-span-2">
<label class="block text-gray-700 mb-1 text-sm">姓名 *</label>
<input
v-model="form.name"
type="text"
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="请输入联系人姓名"
/>
<p v-if="errors.name" class="text-red-500 text-xs mt-1">{{ errors.name }}</p>
</div>
<!-- 联系人类型 -->
<div>
<label class="block text-gray-700 mb-1 text-sm">联系人类型</label>
<select
v-model="form.type"
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"
>
<option value="">请选择类型</option>
<option value="customer">客户</option>
<option value="partner">合作伙伴</option>
<option value="supplier">供应商</option>
<option value="internal">内部员工</option>
<option value="other">其他</option>
</select>
</div>
<!-- 称谓 -->
<div>
<label class="block text-gray-700 mb-1 text-sm">称谓</label>
<input
v-model="form.appellation"
type="text"
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>
<!-- 公司 -->
<div class="md:col-span-2">
<label class="block text-gray-700 mb-1 text-sm">公司</label>
<input
v-model="form.companyName"
type="text"
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>
<!-- 收件人邮箱 -->
<div class="md:col-span-2">
<label class="block text-gray-700 mb-1 text-sm">收件人邮箱 *</label>
<input
v-model="form.email"
type="email"
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="example@company.com"
/>
<p v-if="errors.email" class="text-red-500 text-xs mt-1">{{ errors.email }}</p>
</div>
<!-- 抄送人邮箱 -->
<div class="md:col-span-2">
<label class="block text-gray-700 mb-1 text-sm">抄送人邮箱</label>
<div class="flex gap-2 mb-2">
<input
v-model="newCcEmail"
type="email"
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="添加抄送邮箱"
@keyup.enter="addCcEmail"
/>
<button
@click="addCcEmail"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-700"
>
添加
</button>
</div>
<div class="flex flex-wrap gap-2">
<span
v-for="(email, index) in form.ccEmailList"
:key="index"
class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm"
>
{{ email }}
<button
@click="deleteContact(contact.id)"
class="text-red-600 hover:text-red-900"
title="删除"
@click="removeCcEmail(index)"
class="ml-2 text-blue-600 hover:text-blue-900"
>
<i class="fas fa-trash"></i>
<i class="fas fa-times"></i>
</button>
</td>
</tr>
</tbody>
</table>
</span>
</div>
</div>
<!-- 其他信息 -->
<div class="md:col-span-2">
<label class="block text-gray-700 mb-1 text-sm">其他信息</label>
<textarea
v-model="form.other"
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"
rows="3"
placeholder="备注信息"
></textarea>
</div>
</div>
</div>
<!-- 空状态、加载状态和分页组件与之前保持一致 -->
<div class="p-4 border-t border-gray-200 flex justify-end gap-3">
<button
@click="closeContactModal"
class="px-6 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
>
取消
</button>
<button
@click="saveContact"
:disabled="isSubmitting"
class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-700 transition-colors"
>
{{ isSubmitting ? '保存中...' : isEditing ? '更新' : '保存' }}
</button>
</div>
</div>
</div>
<!-- 模态框组件与之前保持一致 -->
<CommonModal
v-model:visible="modalVisible"
:trigger-key="modalConfig.triggerKey"
:title="modalConfig.title"
type="confirm"
:message="modalConfig.message"
:show-cancel-button="modalConfig.showCancel"
@confirm="handleConfirm"
@cancel="handleCancel"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// 引入我们创建的api拦截器
import { editContact } from '@/api/api'
import {
addContact,
editContact,
deleteContact,
getContactDetail,
getContactList,
getEmailSenderConfigList,
} from '@/api/api'
import { Contact, Sender } from '@/types/index'
// 引入弹窗组件
import CommonModal from '@/components/CommonModal.vue'
import { isTypeOnlyImportOrExportDeclaration } from 'typescript'
// 弹窗提示信息对象
const modalVisible = ref(false)
const modalConfig = ref({
showCancel: false,
title: '操作确认',
message: '确定要执行此操作吗?',
triggerKey: 'templateModal',
})
// 联系人数据结构
interface Contact {
id: number
name: string
title?: string
company?: string
toEmail: string
ccEmailList: string[]
other?: string
appellation?: string
addedAt: string
const openModal = (
config: { triggerKey?: string; showCancel?: boolean; title?: string; message?: string } = {},
) => {
modalConfig.value = {
showCancel: config.showCancel ?? false,
title: config.title ?? '操作确认',
message: config.message ?? '确定要执行此操作吗?',
triggerKey: config.triggerKey ?? modalConfig.value.triggerKey,
}
modalVisible.value = true
}
const handleConfirm = (triggerKey: string) => {
modalVisible.value = false
console.log('用户确认操作', triggerKey)
if (triggerKey === 'deleteContactModal') {
deleteContact(form.value.contactBizId).then((res) => {
if (res.code === 200) {
fetchContacts()
openModal({
triggerKey: '',
title: '成功',
message: '联系人删除成功',
})
} else {
openModal({
triggerKey: '',
title: '错误',
message: res.message || '联系人删除失败',
})
}
})
}
}
const handleCancel = (triggerKey: string) => {
modalVisible.value = false
console.log('用户取消操作', triggerKey)
}
// 导入错误数据结构
interface ImportError {
row: number
......@@ -209,7 +405,6 @@ const currentPage = ref(1)
const pageSize = ref(10)
const totalContacts = ref(0)
const isLoading = ref(false)
// 模态框状态
const showContactModal = ref(false)
const isEditing = ref(false)
......@@ -218,20 +413,20 @@ const showImportResultModal = ref(false)
// 表单数据
const form = ref<Partial<Contact>>({
id: 0,
name: '',
title: '',
company: '',
toEmail: '',
ccEmails: [],
otherInfo: '',
type: '',
companyName: '',
email: '',
ccEmailList: [],
other: '',
appellation: '',
})
const newCcEmail = ref('')
// 表单错误
const errors = ref({
name: '',
toEmail: '',
email: '',
})
// 导入相关状态
......@@ -264,21 +459,24 @@ const totalPages = computed(() => {
return Math.ceil(totalContacts.value / pageSize.value)
})
// 获取联系人列表 - 使用api拦截器
// 获取联系人列表
const fetchContacts = async () => {
try {
isLoading.value = true
// 使用我们的api实例,会自动添加Authorization头
const data = await api.get(
`/contacts?page=${currentPage.value}&size=${pageSize.value}&search=${searchQuery.value}&sort=${sortBy.value}`,
)
contacts.value = data.items
totalContacts.value = data.total
const data = await getContactList({
pageNo: currentPage.value,
pageSize: pageSize.value,
sortField: sortBy.value,
sortOrder: 'asc',
})
contacts.value = data.data.records
totalContacts.value = data.data.total
} catch (error) {
console.error('获取联系人失败:', error)
showError('获取联系人失败,请稍后重试')
openModal({
title: '获取联系人失败',
message: '获取联系人失败,请稍后重试',
})
} finally {
isLoading.value = false
}
......@@ -309,14 +507,15 @@ const changePage = (page: number) => {
const openAddContactModal = () => {
form.value = {
name: '',
title: '',
company: '',
toEmail: '',
ccEmails: [],
otherInfo: '',
type: '',
companyName: '',
email: '',
ccEmailList: [],
other: '',
appellation: '',
}
newCcEmail.value = ''
errors.value = { name: '', toEmail: '' }
errors.value = { name: '', email: '' }
isEditing.value = false
showContactModal.value = true
}
......@@ -327,10 +526,10 @@ const closeContactModal = () => {
}
// 编辑联系人
const editContact = (contact: Contact) => {
const editContactModal = (contact: Contact) => {
form.value = { ...contact }
newCcEmail.value = ''
errors.value = { name: '', toEmail: '' }
errors.value = { name: '', email: '' }
isEditing.value = true
showContactModal.value = true
}
......@@ -338,18 +537,18 @@ const editContact = (contact: Contact) => {
// 验证表单
const validateForm = (): boolean => {
let isValid = true
errors.value = { name: '', toEmail: '' }
errors.value = { name: '', email: '' }
if (!form.value.name?.trim()) {
errors.value.name = '请输入联系人姓名'
isValid = false
}
if (!form.value.toEmail?.trim()) {
errors.value.toEmail = '请输入收件人邮箱'
if (!form.value.email?.trim()) {
errors.value.email = '请输入收件人邮箱'
isValid = false
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.toEmail)) {
errors.value.toEmail = '请输入有效的邮箱地址'
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.email)) {
errors.value.email = '请输入有效的邮箱地址'
isValid = false
}
......@@ -367,50 +566,74 @@ const addCcEmail = () => {
}
// 检查是否已存在
if (form.value.ccEmails?.includes(email)) {
if (form.value.ccEmailList?.includes(email)) {
showError('该邮箱已在抄送人列表中')
return
}
form.value.ccEmails = [...(form.value.ccEmails || []), email]
form.value.ccEmailList = [...(form.value.ccEmailList || []), email]
newCcEmail.value = ''
}
// 移除抄送人邮箱
const removeCcEmail = (index: number) => {
if (form.value.ccEmails) {
form.value.ccEmails.splice(index, 1)
if (form.value.ccEmailList) {
form.value.ccEmailList.splice(index, 1)
}
}
// 保存联系人 - 使用api拦截器
// 保存联系人
const saveContact = async () => {
if (!validateForm()) return
try {
isSubmitting.value = true
const contactData = {
contactBizId: form.value.contactBizId || undefined,
type: form.value.type,
name: form.value.name,
title: form.value.title,
company: form.value.company,
toEmail: form.value.toEmail,
ccEmails: form.value.ccEmails,
otherInfo: form.value.otherInfo,
companyName: form.value.companyName,
email: form.value.email,
ccEmailList: form.value.ccEmailList,
other: form.value.other,
appellation: form.value.appellation,
}
if (isEditing.value && form.value.id) {
if (isEditing.value && form.value.contactBizId) {
// 更新现有联系人
await api.put(`/contacts/${form.value.id}`, contactData)
editContact({ ...contactData }).then((res) => {
if (res.code === 200) {
openModal({
title: '更新联系人成功',
message: '联系人已更新',
})
closeContactModal()
fetchContacts()
} else {
openModal({
title: '更新联系人失败',
message: res.msg || '保存联系人失败',
})
}
})
} else {
// 添加新联系人
await api.post('/contacts', contactData)
await addContact(contactData).then((res) => {
if (res.code === 200) {
openModal({
title: '添加联系人成功',
message: '联系人已添加',
})
closeContactModal()
fetchContacts()
} else {
openModal({
title: '添加联系人失败',
message: res.msg || '保存联系人失败',
})
}
})
}
// 保存成功,刷新列表并关闭模态框
showSuccess(isEditing.value ? '联系人已更新' : '联系人已添加')
closeContactModal()
fetchContacts()
} catch (error) {
console.error('保存联系人失败:', error)
showError(error instanceof Error ? error.message : '保存联系人失败,请稍后重试')
......@@ -420,19 +643,14 @@ const saveContact = async () => {
}
// 删除联系人 - 使用api拦截器
const deleteContact = async (id: number) => {
if (!confirm('确定要删除这个联系人吗?此操作不可撤销。')) {
return
}
try {
await api.delete(`/contacts/${id}`)
showSuccess('联系人已删除')
fetchContacts()
} catch (error) {
console.error('删除联系人失败:', error)
showError('删除联系人失败,请稍后重试')
const deleteContactModal = async (contact: Contact) => {
form.value = contact
if (form.value) {
openModal({
triggerKey: 'deleteContactModal',
title: '删除联系人',
message: `确定要删除联系人 ${form.value.name} 吗?`,
})
}
}
......
......@@ -122,22 +122,14 @@
import { ref, computed, defineProps, defineEmits } from 'vue'
import { Email } from '../types'
const props = defineProps({
emails: {
type: Array as () => Email[],
required: true,
},
})
const emits = defineEmits(['reuse-email'])
// 状态
const emails = ref<Email[]>([])
const searchTerm = ref('')
const filterStatus = ref('')
// 计算属性
const filteredEmails = computed(() => {
return props.emails
return emails.value
.filter((email) => {
const matchesSearch =
email.subject.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
......@@ -164,9 +156,5 @@ const viewEmailDetail = (email: Email) => {
const reuseEmailContent = (email: Email) => {
// 触发复用邮件内容事件
emits('reuse-email', {
subject: email.subject,
content: email.content,
})
}
</script>
......@@ -27,31 +27,35 @@
/>
</div>
<div>
<label class="block text-gray-700 mb-1 text-sm">SMTP服务器 *</label>
<label class="block text-gray-700 mb-1 text-sm">发件人姓名</label>
<input
v-model="formData.smtpServer"
v-model="formData.displayName"
type="text"
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="例如:smtp.example.com"
placeholder="例如:XX公司"
/>
</div>
<div>
<label class="block text-gray-700 mb-1 text-sm">SMTP端口 *</label>
<input
v-model="formData.smtpPort"
type="number"
<label class="block text-gray-700 mb-1 text-sm">邮件服务商 *</label>
<select
v-model="formData.providerBizId"
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="例如:587"
/>
>
<option
v-for="provider in providers"
:value="provider.providerBizId"
:key="provider.providerBizId"
>
{{ provider.providerName }}
</option>
</select>
</div>
</div>
<div class="mt-4 flex justify-end">
<button
@click="saveSender"
class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-700 transition-colors"
:disabled="
!formData.email || !formData.password || !formData.smtpServer || !formData.smtpPort
"
:disabled="!formData.email || !formData.password || !formData.providerBizId"
>
{{ editingSenderId ? '更新发件人' : '添加发件人' }}
</button>
......@@ -74,12 +78,12 @@
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
SMTP服务器
邮箱服务商
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
SMTP端口
发件人姓名
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
......@@ -96,8 +100,13 @@
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="sender in senders" :key="sender.id">
<td class="px-6 py-4 whitespace-nowrap">{{ sender.email }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ sender.smtpServer }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ sender.smtpPort }}</td>
<td class="px-6 py-4 whitespace-nowrap">
{{
providers.find((provider) => provider.providerBizId === sender.providerBizId)
?.providerName || '未知'
}}
</td>
<td class="px-6 py-4 whitespace-nowrap">{{ sender.displayName }}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
......@@ -109,7 +118,10 @@
<button @click="editSender(sender)" class="text-blue-600 hover:text-blue-900 mr-3">
编辑
</button>
<button @click="deleteSender(sender.id)" class="text-red-600 hover:text-red-900">
<button
@click="deleteSender(sender.senderBizId)"
class="text-red-600 hover:text-red-900"
>
删除
</button>
</td>
......@@ -122,31 +134,94 @@
<p>暂无发件人邮箱,请添加发件人</p>
</div>
</div>
<CommonModal
v-model:visible="modalVisible"
:trigger-key="modalConfig.triggerKey"
:title="modalConfig.title"
type="confirm"
:message="modalConfig.message"
:show-cancel-button="modalConfig.showCancel"
@confirm="handleConfirm"
@cancel="handleCancel"
/>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits } from 'vue'
import { Sender } from '../types'
const props = defineProps({
senders: {
type: Array as () => Sender[],
required: true,
},
import { ref, defineProps, defineEmits, onMounted } from 'vue'
import {
getEmailSenderConfigList,
getEmailProviderList,
deleteEmailSenderConfig,
addEmailSenderConfig,
editEmailSenderConfig,
} from '@/api/api'
import { Provider, Sender } from '@/types'
// 引入弹窗组件
import CommonModal from '@/components/CommonModal.vue'
// 弹窗提示信息对象
const modalVisible = ref(false)
const modalConfig = ref({
showCancel: false,
title: '操作确认',
message: '确定要执行此操作吗?',
triggerKey: 'templateModal',
})
const emits = defineEmits(['update-senders'])
const openModal = (
config: { triggerKey?: string; showCancel?: boolean; title?: string; message?: string } = {},
) => {
modalConfig.value = {
showCancel: config.showCancel ?? false,
title: config.title ?? '操作确认',
message: config.message ?? '确定要执行此操作吗?',
triggerKey: config.triggerKey ?? modalConfig.value.triggerKey,
}
modalVisible.value = true
}
const handleConfirm = (triggerKey: string) => {
modalVisible.value = false
console.log('用户确认操作', triggerKey)
if (triggerKey === 'deleteModal') {
deleteEmailSenderConfig(editingSenderId.value).then((res) => {
if (res.code === 200) {
openModal({
title: '删除确认',
message: '发件人删除成功',
triggerKey: '',
})
getSenders()
resetForm()
} else {
openModal({
title: '删除失败',
message: res.msg || '删除失败',
triggerKey: '',
})
return
}
})
}
}
const handleCancel = (triggerKey: string) => {
modalVisible.value = false
console.log('用户取消操作', triggerKey)
}
// 状态
const senders = ref<Sender[]>([...props.senders])
const senders = ref<Sender[]>([])
const editingSenderId = ref('')
const formData = ref<Partial<Sender>>({
email: '',
password: '',
smtpServer: '',
smtpPort: '',
providerBizId: '',
displayName: '',
active: 1,
})
// 服务商列表
const providers = ref<Provider[]>([])
// 方法
const resetForm = () => {
......@@ -154,58 +229,108 @@ const resetForm = () => {
formData.value = {
email: '',
password: '',
smtpServer: '',
smtpPort: '',
providerBizId: '',
displayName: '',
active: 1,
}
}
const getProviders = () => {
getEmailProviderList({
providerName: '',
pageNo: 1,
pageSize: 100,
}).then((res) => {
providers.value = res.data?.records || []
})
}
const getSenders = () => {
getEmailSenderConfigList({
emailSenderConfigName: '',
pageNo: 1,
pageSize: 100,
}).then((res) => {
senders.value = res.data?.records || []
})
}
const saveSender = () => {
if (
!formData.value.email ||
!formData.value.password ||
!formData.value.smtpServer ||
!formData.value.smtpPort
)
console.log(formData.value)
if (!formData.value.email || !formData.value.password || !formData.value.providerBizId) {
openModal({
title: '添加失败',
message: '请填写完整邮箱、密码和服务商',
})
return
}
if (editingSenderId.value) {
// 更新现有发件人
const index = senders.value.findIndex((s) => s.id === editingSenderId.value)
if (index > -1) {
senders.value[index] = {
...senders.value[index],
...formData.value,
} as Sender
emits('update-senders', [...senders.value])
alert('发件人更新成功')
}
editEmailSenderConfig({
senderBizId: editingSenderId.value,
...formData.value,
}).then((res) => {
if (res.code === 200) {
getSenders()
openModal({
title: '更新确认',
message: '发件人更新成功',
})
} else {
openModal({
title: '更新失败',
message: res.msg || '更新失败',
})
}
})
} else {
// 添加新发件人
const newSender: Sender = {
id: Date.now().toString(),
email: formData.value.email || '',
password: formData.value.password || '',
smtpServer: formData.value.smtpServer || '',
smtpPort: formData.value.smtpPort || '',
providerBizId: formData.value.providerBizId || '',
displayName: formData.value.displayName || '',
active: formData.value.active ?? 1,
}
senders.value.push(newSender)
emits('update-senders', [...senders.value])
alert('发件人添加成功')
addEmailSenderConfig(newSender).then((res) => {
// 补充异常处理
if (res.code === 200) {
getSenders()
openModal({
title: '添加确认',
message: '发件人添加成功',
})
} else {
openModal({
title: '添加失败',
message: res.msg || '添加失败',
})
}
})
}
resetForm()
}
const editSender = (sender: Sender) => {
editingSenderId.value = sender.id
console.log('编辑发件人', sender)
editingSenderId.value = sender.senderBizId || ''
formData.value = { ...sender }
}
const deleteSender = (id: string) => {
if (confirm('确定要删除这个发件人吗?')) {
senders.value = senders.value.filter((sender) => sender.id !== id)
emits('update-senders', [...senders.value])
}
console.log('删除发件人', id)
editingSenderId.value = id
openModal({
showCancel: true,
title: '删除确认',
message: '确定要删除这个发件人吗?',
triggerKey: 'deleteModal',
})
}
onMounted(() => {
// 初始化服务商列表
getProviders()
// 初始化发件人列表
getSenders()
})
</script>
......@@ -340,22 +340,10 @@ const handleCancel = (triggerKey: string) => {
console.log('用户取消操作', triggerKey)
}
const props = defineProps({
variables: {
type: Array as () => Variable[],
required: true,
},
variableTemplates: {
type: Array as () => VariableTemplate[],
required: true,
},
})
const emits = defineEmits(['update-variables', 'update-variable-templates'])
const variables = ref<Variable[]>([])
const variableTemplates = ref<VariableTemplate[]>([])
// 状态
const variables = ref<Variable[]>([...props.variables])
const variableTemplates = ref<VariableTemplate[]>([...props.variableTemplates])
const variablePrefix = '{{'
const variableNextfix = '}}'
......@@ -405,13 +393,6 @@ const saveVariable = () => {
description: variableForm.value.description,
}).then(() => {
// 更新本地变量列表
const index = variables.value.findIndex((v) => v.id === editingVariableId.value)
if (index > -1) {
variables.value[index] = {
...variables.value[index],
...variableForm.value,
}
}
fetchVariables()
openModal({
title: '成功',
......@@ -638,7 +619,6 @@ const saveVariableTemplate = () => {
.then(() => {
// 刷新变量模版列表
fetchVariableTemplates()
emits('update-variable-templates', [...variableTemplates.value])
})
.catch((error) => {
console.error('更新变量模版失败:', error)
......@@ -664,7 +644,6 @@ const saveVariableTemplate = () => {
.then(() => {
// 刷新变量模版列表
fetchVariableTemplates()
emits('update-variable-templates', [...variableTemplates.value])
})
.catch((error) => {
console.error('创建变量模版失败:', error)
......@@ -692,7 +671,6 @@ const deleteVariableTemplate = (id: string, type?: string) => {
.then(() => {
// 刷新变量模版列表
fetchVariableTemplates()
emits('update-variable-templates', [...variableTemplates.value])
})
.catch((error) => {
console.error('删除变量模版失败:', error)
......@@ -710,7 +688,7 @@ const deleteVariableTemplate = (id: string, type?: string) => {
})
} else {
openModal({
showClose: true,
showCancel: true,
title: '确认删除',
message: '确定删除此变量模版吗?',
triggerKey: 'templeteDelete',
......
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