| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671 |
- <template>
- <div class="send-email" style="display: flex;" v-loading="dataLoadng">
- <div class="header">
- <div class="header-btn">
- <el-button type="primary" @click="saveEmail" v-loading>保存</el-button>
- </div>
- </div>
- <div class="content" style="display: flex;">
- <div class="send-content">
- <div class="send-input">
- <span class="send-label">收件人</span>
- <el-input v-model="form.recipient" disabled></el-input>
- </div>
- <div class="send-input">
- <span class="send-label">抄送</span>
- <el-input v-model="form.recipientCc" disabled></el-input>
- </div>
- <div class="send-input">
- <span class="send-label">密件抄送</span>
- <el-input v-model="form.recipientBcc" disabled></el-input>
- </div>
- <div class="send-input">
- <span class="send-label">主题</span>
- <el-input v-model="form.subject"></el-input>
- </div>
- <div class="msg-item" style="display: flex;flex: 1">
- <div style="display: flex;flex-direction:column; flex: 1">
- <ckeditor
- class="send-ckeditor"
- placeholder="智能写信新体验,试试AI润色让文字焕发活力"
- @sendContnet="getContent"
- :content="form.content"
- ref="ckeditorRef"
- ></ckeditor>
- <!-- <div class="ai-button" v-if="aiShow">
- <el-button v-for="item in aiButton" @click="aiButtonClick(item)">
- <img :src="getImageUrl(item.value)" />
- {{ item.label }}
- </el-button>
- </div>
- <div class="ai-div">
- <p>智能写信新体验,试试AI润色让文字焕发活力</p>
- <el-button type="primary" @click="aiShow = !aiShow">AI润色</el-button>
- </div> -->
- </div>
- <div class="file-div" v-loading="fileLoading">
- <div class="file-header">
- <span>附件列表</span>
- <span>{{ (form.fileIds.length || 0) + (form.oldfileIds?.length || 0)}}个文件 已上传{{ fileSize }}K</span>
- </div>
- <el-upload
- class="file-upload"
- ref="uploadRef"
- multiple
- :file-list="fileList"
- action=""
- :http-request="uploadFile"
- :on-change="changeFiles"
- :on-remove="removeFile"
- :auto-upload="false"
- drag
- >
- <p style="margin-top: 50px" v-if="(form.fileIds.length + form.oldfileIds?.length) == 0">点击上传</p>
- </el-upload>
- </div>
- </div>
- </div>
- </div>
- </div>
- <el-dialog title="AI润色" v-model="aiVisible" width="780px" append-to-body>
- <div
- v-loading="aiLoading"
- element-loading-text="生成中..."
- style="min-height: 100px;"
- >
- <p v-html="aiMsg"></p>
- </div>
- <template #footer>
- <el-button v-if="!aiDone" @click="aiCancel">停止生成</el-button>
- <div v-else style="display: flex;justify-content: space-between;">
- <div>
- <el-button @click="setAIContent('replace')">替换现有内容</el-button>
- <el-button @click="setAIContent('insert')">插入到正文</el-button>
- <el-button @click="sendSocketMessage(aiPrompt)">重新生成</el-button>
- </div>
- <div>
- <el-button @click="copyText">复制</el-button>
- <el-button @click="aiCancel">关闭</el-button>
- </div>
- </div>
- <p v-if="aiDone" class="ai-tips">AI产出内容仅供参考,请详细阅读后使用</p>
- </template>
- </el-dialog>
- </template>
- <script setup>
- import { ref, reactive, onMounted } from 'vue'
- import { useRouter } from 'vue-router'
- import { getEmailDetail, editEmail } from '@/api/marketing'
- import ckeditor from '@/components/ckeditor/index.vue'
- const getContent = (val) => {
- form.value.content = val
- }
- const router = useRouter();
- let queryData = router.currentRoute.value.query
- const getData = () => {
- getEmailDetail({ id: queryData.id }).then(res => {
- form.value.recipient = res.result.recipient
- form.value.subject = res.result.subject
- form.value.content = res.result.content
- })
- }
- const listType = ref('1')
- const copyFlag = ref(true)
- const themeFlag = ref(true)
- const loading = ref(false)
- const dataLoadng = ref(false)
-
- const emailsData = ref([])
- const selectedEmail = ref('')
- const copySelectedEmail = ref('')
- const fileList = ref([])
- const editContent = ref('')
- const getUserEmail = () => {
- // getUserEmails().then(res => {
- // if(res.success) {
- // if(res.result.length != 0) {
- // emailsData.value = res.result
- // selectedEmail.value = res.result.filter(item => item.useDefault == 1)[0].id || ''
- // copySelectedEmail.value = selectedEmail.value
- // if(queryData.id) {
- // dataLoadng.value = true
- // getEmailDetailContent({ mailId: queryData.id }).then(res => {
- // if(res.success) {
- // let result = res.result
- // let recipient = queryData.type == 'edit' ? result.recipient : result.from
- // if(queryData.type != 'forward') inputRef.value.recipientls.addMail(recipient)
- // // 回复全部的话,把原邮件收件人跟发件人都作为这封邮件的收件人
- // if(queryData.type == 'replyAll') inputRef.value.recipientls.addMail(result.recipient)
- // if(queryData.type == 'edit') {
- // editContent.value += result.content
- // form.value.subject = result.subject
- // inputRef.value.recipientCcls.addMail(result.recipientCc)
- // inputRef.value.recipientBccls.addMail(result.recipientBcc)
- // fileList.value = (result.attachments || []).map(item => {
- // let obj = {
- // name: item.fileName + '.' + item.fileExt,
- // url: item.filePath,
- // id: item.id,
- // fileSize: item.fileSize,
- // type: 'old'
- // }
- // return obj
- // })
- // changeFile(fileList.value)
- // if(result.folder != 'SENT') {
- // form.value.id = result.id
- // }
- // }else{
- // editContent.value = `
- // <p>--------------Original Message--------------</p>
- // <p>From:${ recipient }</p>
- // <p>To:${ result.recipient }</p>
- // <p>Cc:${ result.recipientCc || '' }</p>
- // <p>Subject:${ result.subject }</p>
- // <p>Date:${ result.recipientDate }</p>
- // `
- // if(queryData.type == 'forward') { // 转发
- // fileList.value = (result.attachments || []).map(item => {
- // let obj = {
- // name: item.fileName + '.' + item.fileExt,
- // url: item.filePath,
- // id: item.id,
- // fileSize: item.fileSize,
- // type: 'old'
- // }
- // return obj
- // })
- // changeFile(fileList.value)
- // form.value.subject = 'Fw:' + result.subject
- // editContent.value += ('<br />' + result.content)
- // }else{
- // form.value.replyMsgId = queryData.id
- // form.value.subject = 'Re:' + result.subject
- // if(queryData.type == 'reply' || queryData.type == 'replyAll'){ // 回复带上内容
- // editContent.value += ('<br />' + result.content)
- // }
- // if(queryData.type == 'replyAll'){ // 回复全部带上抄送人
- // inputRef.value.recipientCcls.addMail(result.recipientCc)
- // }
- // }
- // }
- // previewFileList.value = fileList.value
- // form.value.fileIds = []
- // form.value.oldfileIds = fileList.value.map(item => item.id)
- // setTimeout(() => {
- // form.value.content = editContent.value
- // }, 500)
- // }
- // }).finally(() => {
- // dataLoadng.value = false
- // })
- // }
- // if(queryData.email) {
- // addMail(queryData.email)
- // }
- // }else {
- // ElMessage.error('请先前往配置邮箱')
- // }
- // }
- // })
- }
- const ckeditorRef = ref()
- const personList = ref([])
- const personListData = ref([])
- const colleagueList = ref([])
- const isLoadAll = ref(false)
- const loadingData = ref(false)
- const custParam = reactive({
- scopeMenuId: null,
- pageSize: 50,
- pageIndex: 1,
- blurry: ''
- })
- const getUser = () => {
- // if(loadingData.value) {
- // return;
- // }
- // loadingData.value = true
- // getCustomerEmails(custParam).then(res => {
- // if(res.success) {
- // res.result.records.map(item => {
- // personList.value.push(item)
- // for(let i = 1; i < 4; i++){
- // if(item['email' + i]){
- // personList.value.push({
- // liaisonName: item.liaisonName + '(备用' + i + ')',
- // email: item['email' + i],
- // customerName: item.customerName,
- // country: item.country
- // })
- // }
- // }
- // })
- // if(custParam.pageIndex >= res.result.pages){
- // isLoadAll.value = true
- // }
- // custParam.pageIndex ++
- // }
- // loadingData.value = false
- // })
- // getColleagueEmails().then(res => {
- // if(res.success) {
- // colleagueList.value = res.result.map(item => {
- // item.show = false
- // return item
- // })
- // }
- // })
- }
- const customerName = ref()
- const serachCustomer = (val) =>{
- custParam.pageIndex = 1
- personList.value= []
- isLoadAll.value = false
- getUser()
- // getUser(val)
- // if(!val) {
- // personList.value = personListData.value
- // return
- // }
- // personList.value = personList.value.filter(function(item){
- // return item.customerName.includes(val)||item.email.includes(val)||item.liaisonName.includes(val)
- // })
- }
- const isDelaySend = ref(0)
- const form = ref({
- content: '',
- recipient: '',
- recipientCc: '',
- recipientBcc: '',
- subject: '',
- fileIds: [],
- replyMsgId: '',
- id: undefined,
- isReceipt: 0,
- isTrack: 0,
- readRemind: 0,
- delaySendTime: '',
- oldfileIds: undefined
- })
- const saveLoading = ref(false)
- const saveEmail = () => {
- saveLoading.value = true
- editEmail({ id: queryData.id, content: form.value.content, subject: form.value.subject }).then(res => {
- ElMessage.success('保存成功')
- }).catch((err) => {
- ElMessage.error(err.message || '保存失败')
- }).finally(() => {
- saveLoading.value = false
- })
- }
- // 上传附件部分
- // const token = getToken()
- const uploadRef = ref()
- const timer = ref()
- const files = ref([])
- const fileLoading = ref(false)
- const previewFileList = ref([])
- const changeFiles = (file) => {
- if(file.raw) {
- files.value.push(file.raw)
- previewFileList.value.push(file.raw)
- }
- clearTimeout(timer.value)
- timer.value = setTimeout(() => {
- uploadFile()
- }, 500)
- }
- const uploadFile = () => {
- fileLoading.value = true
- let formData = new FormData()
- files.value.map(item => {
- formData.append('files', item)
- })
- request({
- url: '/mail/file/cache_mail_file',
- method: 'post',
- data: formData,
- headers: {
- "Content-Type": "multipart/form-data",
- token
- }
- }).then(res => {
- if(res.success) {
- changeFile(res.result)
- }
- }).catch(() => {
- fileList.value = []
- ElMessage.error('上传失败,请稍后再试')
- }).finally(() => {
- fileLoading.value = false
- files.value = []
- })
- }
- const fileSize = ref(0)
- const changeFile = (files) => {
- files.map(item => {
- form.value.fileIds.push(item.id)
- fileSize.value = Number((fileSize.value + Number((Number(item.fileSize) / 1024))).toFixed(2))
- })
- }
- const removeFile = (file) => {
- fileSize.value = Number((fileSize.value - Number(((file.fileSize || file.size) / 1024).toFixed(2))).toFixed(2))
- if(file.type == 'old') {
- form.value.oldfileIds.splice(form.value.oldfileIds.findIndex(item => item == file.id), 1)
- }else {
- form.value.fileIds.splice(form.value.fileIds.findIndex(item => item == file.id), 1)
- previewFileList.value.splice(previewFileList.value.findIndex(item => item.id == file.id), 1)
- }
- }
- const preview = () => {
- // let params = JSON.parse(JSON.stringify(form.value))
- // params.from = emailsData.value.filter(item => item.useDefault == 1)[0].mailAccount
- // params.nowDate = moment().format('YYYY-MM-DD HH:mm:ss')
- // params.fileList = previewFileList.value.map(item => {
- // return { name: item.name || item.fileShowName }
- // })
- // let previewData = JSON.stringify(params)
- // sessionStorage.setItem('previewData', previewData)
- // const { href } = router.resolve({
- // path: '/email/preview'
- // })
- // window.open(href, '_blank')
- }
- const aiButton = ref([
- { label: '润色', value: 'polish' },
- { label: '续写', value: 'rewrite' },
- { label: '内容纠错', value: 'error' },
- // { label: '格式优化', value: 'format' },
- { label: '关闭润色', value: 'close' }
- ])
- const getImageUrl = (value) => {
- // 注意:这里的路径是相对于当前 .vue 文件的路径
- return new URL(`../../assets/images/email-${value}.png`, import.meta.url).href
- }
- const aiShow = ref(false)
- const aiButtonClick = (item) => {
- switch(item.value) {
- case 'close':
- aiShow.value = false
- break;
- default:
- if(!form.value.content){
- ElMessage.error('请输入邮件内容')
- return false
- }
- getPromptText(item)
- break;
- }
- }
- const aiPrompt = ref('')
- let customerParams = {
- customerId: [],
- customerName: [],
- liaisonName: []
- }
- const getPromptText = (item) => {
- let selectionText = ckeditorRef.value?.ckeditor.getSelection().getSelectedText()
- let templateCode = {
- 'polish': 'EMAIL_POLISHING',
- 'rewrite': 'EMAIL_CONTINUATION',
- 'error': 'EMAIL_CORRECTION'
- }
- let content = selectionText || form.value.content
- let mailParams = {
- content: content,
- subject: form.value.subject,
- templateCode: templateCode[item.value]
- }
- let params = { ...mailParams }
- if(customerParams.customerId.length > 0) {
- params.customerId = customerParams.customerId.join(",")
- params.customerName = customerParams.customerName.join(",")
- params.liaisonName = customerParams.liaisonName.join(",")
- }
- getPrompt(params).then(res => {
- if(res.success) {
- aiVisible.value = true
- sendSocketMessage(res.result)
- aiPrompt.value = res.result
- }
- })
- }
- const aiVisible = ref(false)
- const aiLoading = ref(false)
- const ws = ref(null)
- const socketOpen = ref(false)
- const aiMsg = ref('')
- const aiDone = ref(false)
- const initWebSocket = (msg) => {
- aiLoading.value = true
- let tokenConfig = localStorage.getItem('Admin-Token')
- ws.value = new WebSocket(import.meta.env.VITE_APP_WS_HOST, [tokenConfig])
- //监听是否连接成功
- ws.value.onopen = () => {
- console.log('ws连接状态:' + ws.value.readyState);
- //连接成功则发送一个数据
- // this.ws.send('连接成功')
- sendSocketMessage(msg)
- socketOpen.value = true
- }
- //接听服务器发回的信息并处理展示
- ws.value.onmessage = (res) => {
- aiLoading.value = false
- if(res.data == '[done]') {
- aiDone.value = true
- if(ws.value) {
- ws.value.close();
- }
- return false
- }
- aiMsg.value += res.data
- }
- //监听连接关闭事件
- ws.value.onclose = () => {
- //监听整个过程中websocket的状态
- console.log('ws连接状态:' + ws.value.readyState);
- socketOpen.value = false
- ws.value = null
- }
- //监听并处理error事件
- ws.value.onerror = function (error) {
- console.log(error);
- socketOpen.value = false
- ws.value = null
- }
- }
- const sendSocketMessage = (msg) => {
- aiMsg.value = ''
- aiLoading.value = true
- aiDone.value = false
- if(ws.value == null) {
- //如果ws断开连接需要重新连接
- initWebSocket(msg)
- return
- }
- ws.value.send(msg)
- }
- const aiCancel = () => {
- aiVisible.value = false
- if(ws.value) {
- ws.value.close();
- }
- aiMsg.value = ''
- aiLoading.value = true
- aiDone.value = false
- }
- const copyText = async() => {
- await navigator.clipboard.writeText(aiMsg.value);
- ElMessage.success('复制成功')
- }
- const setAIContent = (type) => {
- if(type == 'insert') {
- let editor = ckeditorRef.value?.ckeditor
- const range = editor.createRange();
- range.selectNodeContents(editor.editable())
- range.collapse(false)
- // 选择范围,这将设置光标位置
- editor.getSelection().selectRanges([range]);
- ckeditorRef.value?.ckeditor.insertHtml(aiMsg.value)
- }else {
- let replacedContent = aiMsg.value
- let selectionText = ckeditorRef.value?.ckeditor.getSelection().getSelectedText()
- if(selectionText) {
- replacedContent = form.value.content.replace(selectionText, aiMsg.value);
- }
- ckeditorRef.value?.ckeditor.setData(replacedContent)
- }
- aiCancel()
- }
- onMounted(() => {
- getData()
- })
- </script>
- <style scoped>
- .send-email{
- height: 100vh;
- flex-direction: column;
- }
- .send-email .header{
- height: 56px;
- padding: 0 16px;
- background: #FFF;
- box-shadow: 0px 1px 0px 0px #F0F0F0;
- justify-content: space-between;
- display: flex;
- align-items: center;
- }
- .send-email .content{
- padding: 16px;
- column-gap: 16px;
- flex: 1;
- height: 0;
- background: #f2f3f5
- }
- .send-email .content .send-content{
- background: #FFF;
- flex: 1;
- padding: 16px;
- display: flex;
- flex-direction: column;
- }
- .send-email .content .send-input{
- margin-bottom: 12px;
- display: flex;
- align-items: center;
- }
- .send-email .content .send-input .send-label{
- color: #666666;
- width: 60px;
- margin-right: 8px;
- text-align: right;
- }
- .send-email .content .send-input .el-input{
- height: 32px;
- }
- .send-email .content .send-content .editor-div{
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- .send-email .content .send-content .editor-div :deep(.editor){
- height: 100%;
- display: flex;
- flex-direction: column;
- }
- .send-email .content .send-content .editor-div :deep(.editor) .ql-toolbar{
- background: #F8F8F8;
- }
- .msg-item .file-div{
- width: 220px;
- border: 1px solid #DCDEE2;
- border-left: 0;
- display: flex;
- flex-direction: column;
- }
- .msg-item .file-div .file-header{
- display: flex;
- align-items: center;
- height: 41px;
- background: #f8f8f8;
- color: #000;
- justify-content: space-between;
- padding: 0 10px;
- border-bottom: 1px solid #DCDEE2;
- }
- .msg-item .file-div :deep(.file-upload){
- flex: 1;
- position: relative;
- }
- .msg-item .file-div :deep(.file-upload) .el-upload{
- height: 100%;
- }
- .msg-item .file-div :deep(.file-upload) .el-upload .el-upload-dragger{
- height: 100%;
- padding: 10px;
- border: none;
- border-radius: 0;
- /* background: #0F172B; */
- }
- .msg-item .file-div :deep(.file-upload) .el-upload-list{
- position: absolute;
- top: 0;
- width: 100%;
- }
- .msg-item .ai-div{
- height: 48px;
- border: 1px solid #d1d1d1;
- padding: 0 16px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- color: #333;
- }
- .msg-item .ai-button{
- padding: 16px 15px;
- border: 1px solid #d1d1d1;
- border-top: none;
- border-bottom: none;
- }
- .msg-item .ai-button :deep(.el-button){
- background: #6366F1;
- color: #FFF;
- border: none;
- }
- .msg-item .ai-button :deep(.el-button) img{
- margin-right: 5px;
- }
- .msg-item .ai-button :deep(.el-button):hover{
- background: #3441569a;
- }
- </style>
|