Commit 79fddc42 by Sweet Zhang

分页对接,编辑数据对接

parent efadec82
VITE_API_BASE_URL='/email/api'
VITE_REMOTE_API_BASE_URL=''
\ No newline at end of file
......@@ -110,6 +110,12 @@ export const variableGroupApi = {
getEmailVariableGroupList: (params: VariableTemplate): Promise<ApiResponse> => {
return request.post(`${baseEmailUrl}/emailVariableGroup/page`, params)
},
// 导出变量模版
exportEmailVariableGroup: (id: string): Promise<ApiResponse> => {
return request.post(`${baseEmailUrl}/emailFile/export/excel/variable`, {
variableGroupBizId: id,
})
},
}
/**
......@@ -136,6 +142,10 @@ export const importContactApi = {
getEmailContactImportDetail: (id: string): Promise<ApiResponse> => {
return request.get(`${baseEmailUrl}/emailContactImport/detail/sessionId?sessionId=${id}`)
},
// 删除导入联系人
deleteEmailContactImport: (id: string): Promise<ApiResponse> => {
return request.delete(`${baseEmailUrl}/emailContactImport/del?importBizId=${id}`)
},
}
/**
* 发送邮件
......
......@@ -24,8 +24,8 @@
<p class="text-gray-900">{{ emailData?.sendEmail || '无' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">发送时间</label>
<p class="text-gray-900">{{ formatDate(emailData?.sendTime) || '无' }}</p>
<label class="block text-sm font-medium text-gray-700 mb-1">定时时间</label>
<p class="text-gray-900">{{ formatDate(emailData?.scheduleTime) || '无' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">总收件人数</label>
......@@ -98,7 +98,7 @@
{{ formatDate(record.sendTime) || '--' }}
</td>
<td class="px-6 py-4 text-sm text-gray-500 max-w-xs">
{{ record.failReason || '--' }}
{{ record.errorMsg || '--' }}
</td>
</tr>
</tbody>
......@@ -122,13 +122,14 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import type { DictItem } from '@/types/index'
// 定义props
interface EmailRecord {
receiveEmail?: string
status?: string
sendTime?: string
failReason?: string
errorMsg?: string
}
interface EmailTask {
......@@ -137,11 +138,13 @@ interface EmailTask {
sendEmail?: string
sendTime?: string
records?: EmailRecord[]
scheduleTime?: string
}
const props = defineProps<{
visible: boolean
emailData?: EmailTask
statusOptions?: DictItem[]
}>()
const emit = defineEmits<{
......@@ -155,8 +158,14 @@ const emailRecords = ref<EmailRecord[]>([])
watch(
() => props.emailData,
(newEmailData) => {
if (newEmailData && newEmailData.records) {
if (newEmailData && newEmailData.records && props.statusOptions) {
emailRecords.value = newEmailData.records
// 为每个邮件设置状态标签
emailRecords.value.forEach((email) => {
email.statusLabel =
props.statusOptions.find((item) => item.itemValue === email.status)?.itemLabel ||
'未知状态'
})
} else {
emailRecords.value = []
}
......
<template>
<div
class="pagination-container flex items-center justify-between px-4 py-3 bg-white border-t border-gray-200"
>
<!-- 左侧信息 -->
<div class="pagination-info flex items-center text-sm text-gray-700">
<span>显示第 {{ startItem }} 到第 {{ endItem }} 条,共 {{ total }} 条记录</span>
</div>
<!-- 右侧分页控件 -->
<div class="pagination-controls flex items-center space-x-2">
<!-- 每页显示数量选择器 -->
<div class="page-size-selector flex items-center space-x-2">
<span class="text-sm text-gray-700">每页显示</span>
<el-select
v-model="pageSize"
:disabled="disabled"
size="small"
style="width: 100px"
@change="handlePageSizeChange"
>
<el-option
v-for="size in pageSizeOptions"
:key="size"
:label="`${size} 条`"
:value="size"
/>
</el-select>
</div>
<!-- 分页器 -->
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
:disabled="disabled"
:background="background"
:layout="layout"
:page-sizes="pageSizeOptions"
:pager-count="pagerCount"
@size-change="handlePageSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
// 定义组件属性
interface Props {
total: number
current?: number
pageSize?: number
pageSizes?: number[]
layout?: string
background?: boolean
disabled?: boolean
pagerCount?: number
}
// 定义组件事件
interface Emits {
(e: 'update:current', value: number): void
(e: 'update:pageSize', value: number): void
(e: 'change', page: number, pageSize: number): void
}
// 默认属性值
const props = withDefaults(defineProps<Props>(), {
current: 1,
pageSize: 10,
pageSizes: () => [10, 20, 50, 100],
layout: 'prev, pager, next, jumper',
background: true,
disabled: false,
pagerCount: 7,
})
const emit = defineEmits<Emits>()
// 响应式数据
const currentPage = ref(props.current)
const pageSize = ref(props.pageSize)
const pageSizeOptions = ref(props.pageSizes)
// 计算属性
const startItem = computed(() => {
return (currentPage.value - 1) * pageSize.value + 1
})
const endItem = computed(() => {
const end = currentPage.value * pageSize.value
return end > props.total ? props.total : end
})
// 监听外部属性变化
watch(
() => props.current,
(newVal) => {
currentPage.value = newVal
},
)
watch(
() => props.pageSize,
(newVal) => {
pageSize.value = newVal
},
)
watch(
() => props.pageSizes,
(newVal) => {
pageSizeOptions.value = newVal
},
)
// 事件处理
const handlePageSizeChange = (newSize: number) => {
pageSize.value = newSize
emit('update:pageSize', newSize)
emit('change', currentPage.value, newSize)
}
const handleCurrentChange = (newPage: number) => {
currentPage.value = newPage
emit('update:current', newPage)
emit('change', newPage, pageSize.value)
}
</script>
<style scoped>
.pagination-container {
min-height: 56px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.pagination-container {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
.pagination-controls {
justify-content: space-between;
}
}
</style>
......@@ -48,6 +48,7 @@ export interface VariableTemplate extends Pagination<VariableTemplate> {
description?: string
variableBizIdList?: string[]
variableNameEns?: string[]
variableNameEnList?: string[]
}
// 忘记密码表单类型
......@@ -62,6 +63,7 @@ export interface ImportRecord extends Contact<ImportRecord> {
sessionId?: string
receiveEmailList?: string[]
ccEmailList?: string[]
ccEmail?: string
}
// 选择联系人时,调用接口,获取sessionId
......
......@@ -60,10 +60,6 @@
<button
@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"
v-if="
!selectedVariableTemplate ||
selectedVariableTemplate?.variableGroupBizId == 'email_variable_group_a2Z0lJLQlCuO81ZE'
"
>
<i class="fas fa-address-book mr-1"></i> 选择联系人
</button>
......@@ -85,14 +81,14 @@
>
<div class="flex flex-wrap gap-2">
<div
v-for="(tag, index) in emailForm.ccEmails.split(';').filter((e) => e.trim())"
v-for="(tag, index) in emailForm.ccEmails.split(',').filter((e) => e.trim())"
:key="index"
class="inline-flex flex-wrap items-center gap-1 px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium"
>
<span
v-for="subtag in tag
.trim()
.split(',')
.split(';')
.filter((e) => e.trim())"
:key="subtag"
class="bg-orange-100 text-orange-800 px-2 py-0.5 rounded-full"
......@@ -200,7 +196,7 @@
:records="importRecords"
@update-record="updateImportRecord"
@delete-record="deleteImportRecord"
@close="showImportRecordManager = false"
@close="((showImportRecordManager = false), getImportedContacts())"
/>
<!-- 导入数据弹窗 -->
......@@ -210,22 +206,6 @@
accept=".csv,.txt,.xlsx"
@file-selected="handleImportContacts"
/>
<div
v-if="showImportContacts"
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 p-6 w-full max-w-md">
<h3 class="text-lg font-semibold mb-4">导入联系人</h3>
<div class="flex justify-end gap-3">
<button
@click="showImportContacts = false"
class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50"
>
取消
</button>
</div>
</div>
</div>
<!-- 联系人选择弹窗 -->
<ContactSelector
v-if="showContactSelector"
......@@ -308,7 +288,6 @@ const emailForm = ref<EmailForm>({
})
const selectedVariableTemplate = ref<VariableTemplate | null>(null)
const attachments = ref<File[]>([])
const showContactSelector = ref(false)
const showVariableSelector = ref(false)
const showImportContacts = ref(false)
......@@ -320,11 +299,11 @@ watch(
() => emailForm.value.receiveEmail,
(newReceiveEmail) => {
if (newReceiveEmail) {
const matchedRecord = importRecords.value.find(
(record) => record.receiveEmail === newReceiveEmail,
const matchedRecord = importRecords.value.find((record) =>
record.receiveEmailList?.includes(newReceiveEmail),
)
if (matchedRecord && matchedRecord.ccEmailList) {
emailForm.value.ccEmailList = matchedRecord.ccEmailList
emailForm.value.ccEmailList = matchedRecord.ccEmailList || []
}
}
},
......@@ -406,36 +385,53 @@ const confirmContactSelection = (selected: Contact<unknown>[]) => {
// 更新导入记录
const updateImportRecord = (updatedRecord: ImportRecord) => {
const index = importRecords.value.findIndex((record) => record.id === updatedRecord.id)
if (index !== -1) {
importRecords.value[index] = { ...updatedRecord }
const params = {
...updatedRecord,
ccEmailList: updatedRecord.ccEmail.split(';') || '',
ccEmail: undefined,
}
importContactApi.editEmailContactImport(params).then((res) => {
if (res.code === 200) {
ElMessage({
message: '导入记录更新成功',
type: 'success',
})
// 更新成功之后,刷新导入记录列表
getImportedContacts(emailForm.value.sessionId || '')
} else {
ElMessage({
message: '导入记录更新失败',
type: 'error',
})
}
})
}
// 删除导入记录
const deleteImportRecord = (id: string) => {
importRecords.value = importRecords.value.filter((record) => record.id !== id)
const deleteImportRecord = (importBizId: string) => {
importContactApi.deleteEmailContactImport(importBizId).then((res) => {
if (res.code === 200) {
ElMessage({
message: '导入记录删除成功',
type: 'success',
})
// 删除成功之后,刷新导入记录列表
getImportedContacts(emailForm.value.sessionId || '')
} else {
ElMessage({
message: '导入记录删除失败',
type: 'error',
})
}
})
}
// 修改handleImportContacts方法
const handleImportContacts = (event: { file: File; content: string }) => {
const { content } = event
// 解析CSV或文本文件,这里简化处理
const lines = content.split('\n')
lines.forEach((line) => {
const [to, cc] = line.split(',')
if (to && to.includes('@')) {
importRecords.value.push({
id: Date.now().toString(),
receiveEmail: to.trim(),
ccEmailList: cc ? cc.split(',').map((email) => email.trim()) : [],
contactBizId: '',
name: '',
type: '',
pageNo: 1,
})
const handleImportContacts = (results) => {
console.log(results)
if (results.data) {
emailForm.value.sessionId = results.data.sessionId || ''
}
})
}
// 发送邮件
......@@ -454,6 +450,14 @@ const sendEmail = () => {
message: '邮件发送成功',
type: 'success',
})
// 发送成功之后,清空表单
emailForm.value = {
receiveEmail: '',
ccEmailList: [],
content: '',
attachmentPath: '',
sessionId: '',
}
} else {
ElMessage({
message: '邮件发送失败',
......@@ -464,30 +468,22 @@ const sendEmail = () => {
}
// 通过sessionId获取导入的联系人
const getImportedContacts = (sessionId: string) => {
const getImportedContacts = (sessionId?: string) => {
const params = {
sessionId: sessionId || '',
sessionId: sessionId || emailForm.value.sessionId || '',
source: importSource.value,
}
importContactApi.getEmailContactImportList(params).then((res) => {
if (res.code === 200) {
console.log('导入的联系人:', res.data)
importRecords.value = res.data.records || []
// 更新页面展示的抄送人和收件人
// emailForm.value.receiveEmail = res.data?.receiveEmails || ''
// emailForm.value.ccEmails = res.data?.ccEmails || ''
}
})
}
// 编辑数据导入记录
const editImportRecord = (record: ImportRecord) => {
console.log('编辑导入记录:', record)
// 这里可以添加编辑逻辑,例如打开编辑弹窗
importContactApi.editEmailContactImport(record).then((res) => {
if (res.code === 200) {
console.log('编辑导入记录成功:', res.data)
updateImportRecord(res.data)
}
})
}
/**
* 文件上传配置
*/
......@@ -495,6 +491,7 @@ const editImportRecord = (record: ImportRecord) => {
import { ElMessage } from 'element-plus'
import FileUploadComponent from '@/components/FileUploadComponent.vue'
import { UploadResult } from '@/utils/fileUpload'
import { get } from 'http'
// 上传成功的文件
const uploadedFiles = ref<any[]>([])
......@@ -504,7 +501,7 @@ const handleDocumentUploadSuccess = (results: UploadResult[]) => {
// 发送邮件时,接口入参attachmentPath,填写附件路径,多个有分号隔开,路径在result里面的data,里面的accessUrl
// 过滤出成功上传的文件
const successFiles = results.filter((r) => r.success)
const attachmentPath = successFiles.map((r) => r.data?.data.accessUrl || '').join(';')
const attachmentPath = successFiles.map((r) => r.data?.data.url || '').join(';')
emailForm.value.attachmentPath = attachmentPath
ElMessage.success(`成功上传 ${successCount} 个文档`)
......
......@@ -157,8 +157,15 @@
</tbody>
</table>
</div>
<!-- 空状态、加载状态和分页组件与之前保持一致 -->
<!-- 分页组件 -->
<Pagination
:total="total"
:current="currentPage"
:page-size="pageSize"
@change="handlePageChange"
@update:current="handleCurrentUpdate"
@update:page-size="handlePageSizeUpdate"
/>
</div>
<!-- 联系人模态框 -->
<div
......@@ -321,6 +328,31 @@ import { ref, computed, onMounted } from 'vue'
// 引入我们创建的api拦截器
import { contactApi } from '@/api/api'
import type { Contact } from '@/types/index'
// 引入分页组件
import Pagination from '@/components/Pagination.vue'
// 初始数据
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
// 处理分页变化
const handlePageChange = (page: number, size: number) => {
console.log('分页变化:', page, size)
fetchContacts()
// 这里可以发起API请求获取新数据
}
const handleCurrentUpdate = (page: number) => {
currentPage.value = page
}
const handlePageSizeUpdate = (size: number) => {
pageSize.value = size
currentPage.value = 1 // 重置到第一页
}
// 引入弹窗组件
import CommonModal from '@/components/CommonModal.vue'
// 弹窗提示信息对象
......@@ -371,37 +403,17 @@ const handleCancel = (triggerKey: string) => {
modalVisible.value = false
console.log('用户取消操作', triggerKey)
}
// 导入错误数据结构
interface ImportError {
row: number
message: string
}
// 导入结果数据结构
interface ImportResult {
success: boolean
message: string
stats?: {
success: number
skipped: number
failed: number
}
errors?: ImportError[]
}
const editingSenderId = ref('')
// 页面状态
const contacts = ref<Contact[]>([])
const searchQuery = ref('')
const sortBy = ref('name')
const currentPage = ref(1)
const pageSize = ref(10)
const totalContacts = ref(0)
const isLoading = ref(false)
// 模态框状态
const showContactModal = ref(false)
const isEditing = ref(false)
const showImportModal = ref(false)
const showImportResultModal = ref(false)
// 表单数据
const form = ref<Partial<Contact>>({
......@@ -435,20 +447,8 @@ const resetForm = () => {
isEditing.value = false
showContactModal.value = true
}
// 导入相关状态
const importFile = ref<File | null>(null)
const isImporting = ref(false)
const isSubmitting = ref(false)
const importResult = ref<ImportResult>({
success: false,
message: '',
})
// 提示信息状态
const showSuccessToast = ref(false)
const showErrorToast = ref(false)
const successMessage = ref('')
const errorMessage = ref('')
const isSubmitting = ref(false)
// 初始化页面
onMounted(() => {
......@@ -460,11 +460,6 @@ const filteredContacts = computed<Contact[]>(() => {
return contacts.value
})
// 总页数
const totalPages = computed(() => {
return Math.ceil(totalContacts.value / pageSize.value)
})
// 获取联系人列表
const fetchContacts = async () => {
try {
......@@ -476,7 +471,9 @@ const fetchContacts = async () => {
sortOrder: 'asc',
})
contacts.value = data.data.records
totalContacts.value = data.data.total
total.value = data.data.total
currentPage.value = data.data.current
pageSize.value = data.data.size
} catch (error) {
console.error('获取联系人失败:', error)
openModal({
......@@ -502,13 +499,6 @@ const resetSearch = () => {
fetchContacts()
}
// 切换页码
const changePage = (page: number) => {
if (page < 1 || page > totalPages.value) return
currentPage.value = page
fetchContacts()
}
// 打开添加联系人模态框
const openAddContactModal = () => {
form.value = {
......@@ -659,133 +649,6 @@ const deleteContactModal = async (contact: Contact) => {
})
}
}
// 处理导入文件选择
const handleFileSelected = (e: Event) => {
const input = e.target as HTMLInputElement
if (!input.files || input.files.length === 0) return
importFile.value = input.files[0]
}
// 移除导入文件
const removeImportFile = () => {
importFile.value = null
// 重置文件输入
const input = document.getElementById('contact-import-file') as HTMLInputElement
if (input) input.value = ''
}
// 格式化文件大小
const formatFileSize = (bytes: number) => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}
// 提交导入文件 - 使用api拦截器
const submitImportFile = async () => {
if (!importFile.value) return
try {
isImporting.value = true
const formData = new FormData()
formData.append('file', importFile.value as File)
// 调用后端接口处理文件导入,会自动添加Authorization头
const result = await api.post('/contacts/import', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
// 导入成功,显示结果
importResult.value = {
success: true,
message: `导入完成,共处理 ${result.stats.success + result.stats.skipped + result.stats.failed} 条记录`,
stats: result.stats,
errors: result.errors,
}
showImportModal.value = false
showImportResultModal.value = true
// 刷新联系人列表
fetchContacts()
} catch (error) {
console.error('导入失败:', error)
importResult.value = {
success: false,
message: error instanceof Error ? error.message : '导入过程中发生错误,请稍后重试',
}
showImportModal.value = false
showImportResultModal.value = true
} finally {
isImporting.value = false
}
}
// 下载导入模板 - 使用api拦截器
const downloadImportTemplate = async () => {
try {
// 调用后端接口下载模板文件
const response = await api.get('/contacts/import/template', {
responseType: 'blob',
})
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = '联系人导入模板.xlsx'
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
} catch (error) {
console.error('下载模板失败:', error)
showError('下载模板失败,请稍后重试')
}
}
// 关闭导入模态框
const closeImportModal = () => {
showImportModal.value = false
importFile.value = null
// 重置文件输入
const input = document.getElementById('contact-import-file') as HTMLInputElement
if (input) input.value = ''
}
// 关闭导入结果模态框
const closeImportResultModal = () => {
showImportResultModal.value = false
}
// 显示成功提示
const showSuccess = (message: string) => {
successMessage.value = message
showSuccessToast.value = true
setTimeout(() => {
showSuccessToast.value = false
}, 3000)
}
// 显示错误提示
const showError = (message: string) => {
errorMessage.value = message
showErrorToast.value = true
setTimeout(() => {
showErrorToast.value = false
}, 3000)
}
</script>
<style scoped>
......
......@@ -18,12 +18,12 @@
<select
v-model="filterStatus"
class="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
@change="getEmailTaskMainList"
>
<option value="">全部状态</option>
<option value="sent">已发送</option>
<option value="scheduled">已定时</option>
<option value="draft">草稿</option>
<option value="failed">发送失败</option>
<option v-for="item in statusOptions" :key="item.itemValue" :value="item.itemValue">
{{ item.itemLabel }}
</option>
</select>
</div>
</div>
......@@ -55,6 +55,11 @@
<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
......@@ -70,7 +75,10 @@
<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 whitespace-nowrap text-sm text-gray-500">
{{ formatDate(email.sendTime) }}
{{ email.sendTime ? formatDate(email.sendTime) : '无' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ email.scheduleTime ? formatDate(email.scheduleTime) : '无' }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
......@@ -107,9 +115,19 @@
<i class="fas fa-history text-4xl mb-3 opacity-30"></i>
<p>暂无邮件发送记录</p>
</div>
<!-- 分页组件 -->
<Pagination
:total="total"
:current="currentPage"
:page-size="pageSize"
@change="handlePageChange"
@update:current="handleCurrentUpdate"
@update:page-size="handlePageSizeUpdate"
/>
</div>
<!-- 查看详情 -->
<EmailDetailModal
:statusOptions="statusOptions"
:visible="detailModalVisible"
:email-data="selectedEmail"
@close="detailModalVisible = false"
......@@ -123,6 +141,32 @@ import { sendEmailApi, dictApi } from '@/api/api'
import type { DictItem } from '@/types/index'
import EmailDetailModal from '@/components/EmailDetailModal.vue'
// 引入分页组件
import Pagination from '@/components/Pagination.vue'
// 初始数据
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
// 处理分页变化
const handlePageChange = (page: number, size: number) => {
console.log('分页变化:', page, size)
currentPage.value = page
pageSize.value = size
getEmailTaskMainList()
}
const handleCurrentUpdate = (page: number) => {
currentPage.value = page
}
const handlePageSizeUpdate = (size: number) => {
pageSize.value = size
currentPage.value = 1 // 重置到第一页
getEmailTaskMainList()
}
// 状态
const emails = ref<EmailTask[]>([])
const detailModalVisible = ref(false)
......@@ -130,10 +174,27 @@ const selectedEmail = ref<EmailTask>()
const searchTerm = ref('')
const filterStatus = ref('')
const statusOptions = ref<DictItem[]>([])
const isDictLoading = ref(false) // 新增:状态选项加载状态
const isEmailListLoading = ref(false) // 新增:邮件列表加载状态
onMounted(() => {
getEmailTaskMainList()
loadDataSequentially()
})
// 顺序加载数据:先状态选项,后邮件列表
const loadDataSequentially = async () => {
try {
isDictLoading.value = true
await getDictLists()
isDictLoading.value = false
// 状态选项加载完成后,自动加载邮件列表
await getEmailTaskMainList()
} catch (error) {
console.error('数据加载失败:', error)
isDictLoading.value = false
isEmailListLoading.value = false
}
}
const viewDetail = (item: EmailTask) => {
console.log(item)
......@@ -159,14 +220,15 @@ const viewDetail = (item: EmailTask) => {
// 匹配发送状态
const getDictLists = async () => {
try {
const res = await dictApi.getDictList(['email_task_status'])
if (res.code === 200) {
console.log(res)
statusOptions.value = res.data[0].dictItemList || []
emails.value.forEach((email) => {
email.statusLabel =
statusOptions.value.find((item) => item.itemValue === email.status)?.itemLabel || '未知状态'
})
console.log('状态选项加载完成:', statusOptions.value)
}
} catch (error) {
console.error('加载状态选项失败:', error)
throw error // 抛出错误以便上层处理
}
}
......@@ -180,16 +242,48 @@ const reuseEmailContent = (email: EmailTask) => {
// 触发复用邮件内容事件
}
// 发送任务列表查询
// 发送任务列表查询(改进版本)
const getEmailTaskMainList = async () => {
// 如果状态选项正在加载,等待加载完成
if (isDictLoading.value) {
console.log('等待状态选项加载完成...')
// 可以添加一个简单的等待机制
await new Promise((resolve) => setTimeout(resolve, 100))
return getEmailTaskMainList() // 递归调用直到状态选项加载完成
}
// 如果状态选项为空,先加载状态选项
if (statusOptions.value.length === 0) {
console.log('状态选项为空,先加载状态选项')
await getDictLists()
}
try {
isEmailListLoading.value = true
const params: EmailTask = {
pageNum: 1,
pageSize: 100,
pageNo: currentPage.value,
pageSize: pageSize.value,
status: filterStatus.value,
}
const res = await sendEmailApi.getEmailTaskMainList(params)
if (res.code === 200) {
emails.value = res.data.records || []
getDictLists()
total.value = res.data.total || 0
// 为每个邮件设置状态标签
emails.value.forEach((email) => {
email.statusLabel =
statusOptions.value.find((item) => item.itemValue === email.status)?.itemLabel ||
'未知状态'
})
console.log('邮件列表加载完成,共', emails.value.length, '条记录')
}
} catch (error) {
console.error('获取邮件列表失败:', error)
} finally {
isEmailListLoading.value = false
}
}
</script>
......@@ -5,11 +5,10 @@
>
<div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md">
<h3 class="text-lg font-semibold mb-4">{{ title }}</h3>
<input
type="file"
:accept="accept"
@change="handleFileSelect"
class="w-full px-3 py-2 border border-gray-300 rounded-md mb-4"
<FileUploadComponent
v-model="uploadedFiles"
:config="uploadConfig"
@success="handleDocumentUploadSuccess"
/>
<div class="flex justify-end gap-3">
<button
......@@ -37,25 +36,33 @@ const props = defineProps({
},
accept: {
type: String,
default: '.csv,.txt',
default: '.csv,.xlsx',
},
})
/**
* 文件上传配置
*/
// 上传附件
import { ElMessage } from 'element-plus'
import FileUploadComponent from '@/components/FileUploadComponent.vue'
import { UploadResult, UploadConfig } from '@/utils/fileUpload'
const emit = defineEmits(['update:visible', 'file-selected'])
const handleFileSelect = (e: Event) => {
const input = e.target as HTMLInputElement
if (input.files && input.files[0]) {
const file = input.files[0]
const reader = new FileReader()
reader.onload = (e) => {
const content = e.target?.result as string
emit('file-selected', { file, content })
const uploadConfig: UploadConfig = {
url: `${import.meta.env.VITE_REMOTE_API_BASE_URL}/email/api/emailFile/import/excel/variable`,
fieldName: 'file',
maxSize: 10,
allowedTypes: [],
maxCount: 1,
multiple: false,
}
// 上传成功的文件
const uploadedFiles = ref<any[]>([])
// 处理文档上传成功
const handleDocumentUploadSuccess = (results: UploadResult[]) => {
emit('update:visible', false)
}
reader.readAsText(file)
}
emit('file-selected', results[0])
}
const emit = defineEmits(['update:visible', 'file-selected'])
const handleCancel = () => {
emit('update:visible', false)
......
......@@ -119,7 +119,7 @@
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div v-if="editingRecordId === record.id" class="flex gap-2">
<div v-if="editingRecordId === record.importBizId" 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"
......@@ -236,10 +236,6 @@ const handleCcInputKeydown = (event: KeyboardEvent) => {
}
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString('zh-CN')
}
const editRecord = (record: ImportRecord) => {
editingRecordId.value = record.importBizId
editingRecord.value = { ...record }
......@@ -257,11 +253,11 @@ const cancelEdit = () => {
const saveEdit = () => {
if (editingRecordId.value && editingRecord.value.receiveEmail) {
console.log(editingRecord.value)
emits('update-record', {
importBizId: editingRecordId.value,
receiveEmail: editingRecord.value.receiveEmail,
ccEmail: editingRecord.value.ccEmail || '',
updateTime: new Date().toISOString(),
})
editingRecordId.value = null
editingRecord.value = {}
......
......@@ -133,6 +133,15 @@
<i class="fas fa-envelope text-4xl mb-3 opacity-30"></i>
<p>暂无发件人邮箱,请添加发件人</p>
</div>
<!-- 分页组件 -->
<Pagination
:total="total"
:current="currentPage"
:page-size="pageSize"
@change="handlePageChange"
@update:current="handleCurrentUpdate"
@update:page-size="handlePageSizeUpdate"
/>
</div>
<CommonModal
v-model:visible="modalVisible"
......@@ -151,6 +160,29 @@
import { ref, onMounted } from 'vue'
import { emailProviderApi, senderApi, getEmailSenderConfigList } from '@/api/api'
import type { Sender } from '@/types/index'
// 引入分页组件
import Pagination from '@/components/Pagination.vue'
// 初始数据
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
// 处理分页变化
const handlePageChange = (page: number, size: number) => {
console.log('分页变化:', page, size)
getSenders()
// 这里可以发起API请求获取新数据
}
const handleCurrentUpdate = (page: number) => {
currentPage.value = page
}
const handlePageSizeUpdate = (size: number) => {
pageSize.value = size
currentPage.value = 1 // 重置到第一页
}
// 引入弹窗组件
import CommonModal from '@/components/CommonModal.vue'
// 弹窗提示信息对象
......@@ -241,11 +273,12 @@ const getSenders = () => {
senderApi
.getEmailSenderConfigList({
emailSenderConfigName: '',
pageNo: 1,
pageSize: 100,
pageNo: currentPage.value,
pageSize: pageSize.value,
})
.then((res) => {
senders.value = res.data?.records || []
total.value = res.data?.total || 0
})
}
const saveSender = () => {
......
......@@ -106,11 +106,11 @@
<p class="text-sm text-gray-600 mb-3">{{ template.description || '无描述' }}</p>
<div class="flex flex-wrap gap-2">
<span
v-for="variableId in template.variableBizIdList"
:key="variableId"
v-for="s in template.variableNameEnList || []"
:key="s"
class="px-2 py-1 bg-blue-50 text-blue-700 rounded text-xs"
>
{{ variablePrefix }}{{ variableId }}{{ variableNextfix }}
{{ variablePrefix }}{{ s }}{{ variableNextfix }}
</span>
</div>
<div class="mt-3 flex justify-end">
......@@ -291,7 +291,7 @@ import { ref, defineProps, defineEmits, onMounted } from 'vue'
import type { Variable, VariableTemplate } from '../types'
import { variableApi, variableGroupApi } from '@/api/api'
import { ElMessage } from 'element-plus'
// 引入弹窗组件
import CommonModal from '@/components/CommonModal.vue'
// 弹窗提示信息对象
......@@ -672,16 +672,20 @@ const getVariableKeyById = (id: string) => {
}
const generateExcelTemplate = (template: VariableTemplate) => {
// 模拟生成Excel模板
const variableNames = (template.variableBizIdList || []).map((id) => {
const variable = variables.value.find((v) => v.variableBizId === id)
return variable ? variable.variableNameEn || '' : ''
variableGroupApi
.exportEmailVariableGroup(template.variableGroupBizId || '')
.then((response) => {
// 处理成功响应,例如下载文件
// 自动下载excel文件, 并命名为变量模版.xlsx
const a = document.createElement('a')
a.href = response.data.url
a.download = `${template.groupName || '变量模版'}.xlsx`
a.click()
// 实际项目中这里应该生成并下载Excel文件
})
openModal({
title: '成功',
message: `已生成包含以下变量的Excel模板:\n${variableNames.join(', ')}`,
.catch((error) => {
console.error('导出失败:', error)
ElMessage.error(error.response?.data?.message || '导出变量模版失败')
})
// 实际项目中这里应该生成并下载Excel文件
}
</script>
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