mailWrite.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. <template>
  2. <div v-loading="loading" style="padding: 10px">
  3. <div style="width: 100%">
  4. <el-form ref="submit" :model="formData.data" :rules="rules" label-width="65px" labelPosition="right">
  5. <el-form-item>
  6. <el-button type="primary" @click="handleSend()"> 发 送 </el-button>
  7. </el-form-item>
  8. <el-form-item label="收件人" prop="to">
  9. <div style="width: 100%">
  10. <div style="display: flex; width: 100%">
  11. <el-autocomplete v-model="formData.data.to" clearable placeholder="请输入" style="width: 100%" @select="(data)=>handlePerson(data,10)"
  12. :fetch-suggestions="querySearchPerson" @keyup.enter.native="handleAdd(10)">
  13. </el-autocomplete>
  14. <!-- <el-input v-model="formData.data.to" placeholder="请输入" @keyup.enter.native="handleAdd(10)" /> -->
  15. </div>
  16. <div style="margin-top: 15px" v-if="to && to.length > 0">
  17. <el-tag style="margin-right: 10px" class="ml-2" type="info" v-for="(item, index) in to" :key="index" closable
  18. @close="handleClose(index, 10)">{{ item.address }}</el-tag>
  19. </div>
  20. </div>
  21. </el-form-item>
  22. <el-form-item label="" prop="">
  23. <div style="display: flex">
  24. <span style="color: #666666; cursor: pointer" @click="handleShowOther(1)">抄送</span>
  25. <span style="color: ##dddddd; margin: 0 8px">|</span>
  26. <span style="color: #666666; cursor: pointer" @click="handleShowOther(2)">密送</span>
  27. </div>
  28. </el-form-item>
  29. <el-form-item label="抄送人" prop="cc" v-if="showcc">
  30. <div style="width: 100%">
  31. <div style="display: flex; width: 100%">
  32. <el-autocomplete v-model="formData.data.cc" clearable placeholder="请输入" style="width: 100%" @select="(data)=>handlePerson(data,20)"
  33. :fetch-suggestions="querySearchPerson" @keyup.enter.native="handleAdd(20)">
  34. </el-autocomplete>
  35. <!-- <el-input v-model="formData.data.cc" placeholder="请输入" @keyup.enter.native="handleAdd(20)" /> -->
  36. </div>
  37. <div style="margin-top: 15px" v-if="cc && cc.length > 0">
  38. <el-tag style="margin-right: 10px" class="ml-2" type="info" v-for="(item, index) in cc" :key="index" closable
  39. @close="handleClose(index, 20)">{{ item.address }}</el-tag>
  40. </div>
  41. </div>
  42. </el-form-item>
  43. <el-form-item label="密送人" prop="bcc" v-if="showbcc">
  44. <div style="width: 100%">
  45. <div style="display: flex; width: 100%">
  46. <el-autocomplete v-model="formData.data.bcc" clearable placeholder="请输入" style="width: 100%" @select="(data)=>handlePerson(data,30)"
  47. :fetch-suggestions="querySearchPerson" @keyup.enter.native="handleAdd(30)">
  48. </el-autocomplete>
  49. <!-- <el-input v-model="formData.data.bcc" placeholder="请输入" @keyup.enter.native="handleAdd(30)" /> -->
  50. </div>
  51. <div style="margin-top: 15px" v-if="bcc && bcc.length > 0">
  52. <el-tag style="margin-right: 10px" class="ml-2" type="info" v-for="(item, index) in bcc" :key="index" closable
  53. @close="handleClose(index, 30)">{{ item.address }}</el-tag>
  54. </div>
  55. </div>
  56. </el-form-item>
  57. <el-form-item label="主题" prop="subject">
  58. <el-input v-model="formData.data.subject" placeholder="请输入" />
  59. </el-form-item>
  60. <el-form-item label="附件">
  61. <div style="width: 100%">
  62. <el-upload v-model:fileList="fileList" class="upload-demo" :action="uploadUrl" :data="uploadData" drag multiple :show-file-list="false"
  63. :before-upload="handleBeforeUpload">
  64. <div>
  65. <img src="@/assets/images/icon_attachment.svg" class="att-img" fit="scale-down" />
  66. <span style="padding-left: 8px">将文件拖到此处,或<span style="color: #409eff">点击上传</span></span>
  67. </div>
  68. </el-upload>
  69. <div class="att-box" v-if="fileListCopy && fileListCopy.length > 0">
  70. <div v-for="(itemFile, index) in fileListCopy" :key="index">
  71. <div class="att-item">
  72. <img src="@/assets/images/icon_dz.svg" style="cursor: pointer" fit="scale-down" />
  73. <div class="att-name">
  74. {{ itemFile.fileName }}
  75. </div>
  76. <img src="@/assets/images/icon_delete.svg" class="att-img" fit="scale-down" />
  77. </div>
  78. </div>
  79. </div>
  80. </div>
  81. </el-form-item>
  82. <el-form-item label="正文" prop="content">
  83. <div style="width: 100%">
  84. <!-- <Editor
  85. :value="formData.data.content"
  86. @updateValue="updateContent"
  87. ref="contentEditor"
  88. /> -->
  89. <TinymceEditor :value="formData.data.content" @updateValue="updateContent" ref="contentEditor" />
  90. </div>
  91. </el-form-item>
  92. <!-- <el-form-item label="发件人" prop="replyTo">
  93. <el-input
  94. v-model="formData.data.replyTo"
  95. placeholder="请输入"
  96. style="width: 50%"
  97. />
  98. </el-form-item> -->
  99. <el-row>
  100. <el-col :span="6">
  101. <el-form-item label="发件人" prop="replyTo">
  102. <el-select v-model="formData.data.replyTo" filterable placeholder="请选择" style="width: 100%" @change="handleChangeReply">
  103. <el-option v-for="item in userMailList" :key="item.mailUser" :label="item.mailUser" :value="item.mailUser" />
  104. </el-select>
  105. </el-form-item>
  106. </el-col>
  107. <el-col :span="6">
  108. <el-form-item label="签 名:">
  109. <el-select v-model="formData.data.templateId" filterable placeholder="请选择" style="width: 100%" @change="handleChangeTemplate">
  110. <el-option v-for="item in templateList" :key="item.id" :label="item.templateName" :value="item.id" />
  111. </el-select>
  112. </el-form-item>
  113. </el-col>
  114. </el-row>
  115. <el-form-item>
  116. <el-button type="primary" @click="handleSend()"> 发 送 </el-button>
  117. </el-form-item>
  118. </el-form>
  119. </div>
  120. </div>
  121. </template>
  122. <script setup>
  123. import byForm from "@/components/byForm/index";
  124. import { ElMessage, ElMessageBox } from "element-plus";
  125. import Editor from "@/components/Editor/index.vue";
  126. import TinymceEditor from "@/components/Editor/TinymceEditor.vue";
  127. import { validEmail } from "@/utils/validate.js";
  128. import useMailStore from "@/store/modules/mail";
  129. import $bus from "@/bus/index.js";
  130. const mailStore = useMailStore();
  131. const { proxy } = getCurrentInstance();
  132. const userMailList = computed(() => mailStore.userMailList);
  133. let currentMail = ref({});
  134. const loading = ref(false);
  135. let uploadData = ref({});
  136. const fileList = ref([]);
  137. const fileListCopy = ref([]);
  138. const showcc = ref(false);
  139. const showbcc = ref(false);
  140. const formOption = reactive({
  141. inline: true,
  142. labelWidth: 65,
  143. itemWidth: 100,
  144. labelPosition: "right",
  145. });
  146. const formConfig = computed(() => {
  147. return [
  148. {
  149. type: "slot",
  150. slotName: "to",
  151. label: "收件人",
  152. prop: "to",
  153. },
  154. {
  155. type: "slot",
  156. slotName: "bbcc",
  157. },
  158. {
  159. type: "slot",
  160. slotName: "cc",
  161. label: "抄送人",
  162. prop: "cc",
  163. },
  164. {
  165. type: "slot",
  166. slotName: "bcc",
  167. label: "密送人",
  168. prop: "bcc",
  169. },
  170. {
  171. type: "input",
  172. prop: "subject",
  173. label: "主题",
  174. required: true,
  175. itemWidth: 50.1,
  176. itemType: "text",
  177. },
  178. {
  179. type: "slot",
  180. slotName: "fileSlot",
  181. label: "附件",
  182. },
  183. {
  184. type: "slot",
  185. slotName: "contentSlot",
  186. label: "正文",
  187. },
  188. {
  189. type: "input",
  190. prop: "replyTo",
  191. label: "发件人",
  192. required: true,
  193. itemWidth: 50,
  194. itemType: "text",
  195. },
  196. ];
  197. });
  198. const rules = ref({
  199. subject: [{ required: true, message: "请输入主题", trigger: "blur" }],
  200. // replyTo: [{ required: true, message: "请输入发件人", trigger: "blur" }],
  201. replyTo: [{ required: true, message: "请输入发件人", trigger: "change" }],
  202. });
  203. const formData = reactive({
  204. data: {
  205. content: "",
  206. },
  207. });
  208. const to = ref([]);
  209. const cc = ref([]);
  210. const bcc = ref([]);
  211. const replyTo = ref([]);
  212. const submit = ref(null);
  213. const contentEditor = ref(null);
  214. const handleChangeReply = (val) => {
  215. currentMail.value = userMailList.value.find((x) => x.mailUser === val);
  216. };
  217. const handleReset = () => {
  218. formData.data = {
  219. to: "",
  220. cc: "",
  221. bcc: "",
  222. content: "",
  223. subject: "",
  224. replyTo: "",
  225. };
  226. to.value = [];
  227. cc.value = [];
  228. bcc.value = [];
  229. fileList.value = [];
  230. fileListCopy.value = [];
  231. contentEditor.value.changeHtml("");
  232. };
  233. const handleSend = () => {
  234. submit.value.validate((valid) => {
  235. if (valid) {
  236. const data = { ...formData.data };
  237. if (!to.value.length > 0) {
  238. return ElMessage({
  239. message: "请添加收件人",
  240. type: "info",
  241. });
  242. }
  243. // if (!validEmail(formData.data.replyTo)) {
  244. // return ElMessage({
  245. // message: "发件人邮箱格式不正确",
  246. // type: "info",
  247. // });
  248. // }
  249. if (data.content) {
  250. loading.value = true;
  251. let replyTo = [
  252. {
  253. address: formData.data.replyTo,
  254. personal: null,
  255. },
  256. ];
  257. const submitData = {
  258. type: currentMail.value.type,
  259. mailboxId: currentMail.value.id,
  260. subject: data.subject,
  261. content: data.content,
  262. to: to.value,
  263. cc: cc.value,
  264. bcc: bcc.value,
  265. replyTo: replyTo,
  266. fileList: fileListCopy.value.map((x) => ({
  267. id: x.id,
  268. fileName: x.fileName,
  269. fileUrl: x.fileUrl,
  270. })),
  271. };
  272. proxy.post("/mailService/sendMail", submitData).then((res) => {
  273. ElMessage({
  274. message: "发送成功",
  275. type: "success",
  276. });
  277. handleReset();
  278. loading.value = false;
  279. });
  280. } else {
  281. return ElMessage({
  282. message: "请输入正文",
  283. type: "info",
  284. });
  285. }
  286. }
  287. });
  288. };
  289. const updateContent = (val) => {
  290. formData.data.content = val;
  291. };
  292. const handleBeforeUpload = async (file) => {
  293. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  294. uploadData.value = res.uploadBody;
  295. fileListCopy.value.push({
  296. id: res.id,
  297. fileName: res.fileName,
  298. path: res.fileUrl,
  299. fileUrl: res.fileUrl,
  300. });
  301. };
  302. const handleShowOther = (type) => {
  303. if (type == 1) {
  304. if (!showcc.value) {
  305. showcc.value = true;
  306. } else {
  307. formData.data.cc = "";
  308. cc.value = [];
  309. showcc.value = false;
  310. }
  311. } else {
  312. if (!showbcc.value) {
  313. showbcc.value = true;
  314. } else {
  315. formData.data.bcc = "";
  316. bcc.value = [];
  317. showbcc.value = false;
  318. }
  319. }
  320. };
  321. const handleClose = (index, val) => {
  322. switch (val) {
  323. case 10:
  324. to.value.splice(index, 1);
  325. break;
  326. case 20:
  327. cc.value.splice(index, 1);
  328. break;
  329. case 30:
  330. bcc.value.splice(index, 1);
  331. break;
  332. }
  333. };
  334. const handleAdd = (val, name = null) => {
  335. switch (val) {
  336. case 10: {
  337. if (!validEmail(formData.data.to)) {
  338. return ElMessage({
  339. message: "收件人邮箱格式不正确",
  340. type: "info",
  341. });
  342. }
  343. to.value.push({
  344. address: formData.data.to,
  345. personal: name,
  346. });
  347. formData.data.to = "";
  348. break;
  349. }
  350. case 20: {
  351. if (!validEmail(formData.data.cc)) {
  352. return ElMessage({
  353. message: "抄送人邮箱格式不正确",
  354. type: "info",
  355. });
  356. }
  357. cc.value.push({
  358. address: formData.data.cc,
  359. personal: name,
  360. });
  361. formData.data.cc = "";
  362. break;
  363. }
  364. case 30: {
  365. if (!validEmail(formData.data.bcc)) {
  366. return ElMessage({
  367. message: "密送人邮箱格式不正确",
  368. type: "info",
  369. });
  370. }
  371. bcc.value.push({
  372. address: formData.data.bcc,
  373. personal: name,
  374. });
  375. formData.data.bcc = "";
  376. break;
  377. }
  378. }
  379. };
  380. const handlePerson = (data, type) => {
  381. if (data && data.email) {
  382. handleAdd(type, data.name);
  383. }
  384. };
  385. const querySearchPerson = (queryString, callback) => {
  386. proxy
  387. .post("/customerUser/page", {
  388. pageNum: 1,
  389. pageSize: 15,
  390. keyword: queryString,
  391. })
  392. .then((res) => {
  393. if (res.rows && res.rows.length > 0) {
  394. res.rows = res.rows.map((item) => {
  395. return {
  396. ...item,
  397. value: item.email,
  398. };
  399. });
  400. callback(res.rows);
  401. } else {
  402. callback([]);
  403. }
  404. });
  405. };
  406. const pushTo = () => {
  407. if (mailStore.currentMenu.reMail && mailStore.currentMenu.reMail.email) {
  408. to.value = [
  409. {
  410. address: mailStore.currentMenu.reMail.email,
  411. personal: mailStore.currentMenu.reMail.personal,
  412. },
  413. ];
  414. } else {
  415. to.value = [];
  416. }
  417. };
  418. const handleReplyInit = (allData, pageType) => {
  419. let data = allData.details;
  420. console.log(data, "asd");
  421. // 回复功能:则发件人是回复的收件人
  422. if (pageType === "10") {
  423. if (data.messageAddressList && data.messageAddressList.length > 0) {
  424. to.value = data.messageAddressList
  425. .filter((x) => x.type === 4)
  426. .map((x) => ({
  427. address: x.email,
  428. personal: x.personalName,
  429. }));
  430. }
  431. formData.data.subject = "Re: " + data.subject;
  432. } else if (pageType === "20") {
  433. to.value = [];
  434. formData.data.subject = "Fw: " + data.subject;
  435. } else if (pageType === "30") {
  436. // 全部回复
  437. if (data.messageAddressList && data.messageAddressList.length > 0) {
  438. to.value = data.messageAddressList
  439. .filter((x) => x.type !== 1)
  440. .map((x) => ({
  441. address: x.email,
  442. personal: x.personalName,
  443. }));
  444. }
  445. formData.data.subject = "Re: " + data.subject;
  446. } else if (pageType === "40") {
  447. if (data.messageAddressList && data.messageAddressList.length > 0) {
  448. to.value = data.messageAddressList
  449. .filter((x) => x.type === 1)
  450. .map((x) => ({
  451. address: x.email,
  452. personal: x.personalName,
  453. }));
  454. const cc = data.messageAddressList
  455. .filter((x) => x.type === 2)
  456. .map((x) => ({
  457. address: x.email,
  458. personal: x.personalName,
  459. }));
  460. if (cc.length > 0) {
  461. showcc.value = true;
  462. cc.value = cc;
  463. }
  464. const bcc = data.messageAddressList
  465. .filter((x) => x.type === 2)
  466. .map((x) => ({
  467. address: x.email,
  468. personal: x.personalName,
  469. }));
  470. if (bcc.length > 0) {
  471. showbcc.value = true;
  472. bcc.value = bcc;
  473. }
  474. }
  475. formData.data.subject = data.subject;
  476. } else if (pageType === "50") {
  477. handleReset();
  478. pushTo();
  479. }
  480. if (pageType !== "50") {
  481. contentEditor.value.changeHtml(`
  482. <p><br></p>
  483. <p><br></p>
  484. <p><br></p>
  485. <div style="background:#eee;padding:10px;font-size:12px">
  486. <div><span style="color:#000;font-weight:700">FROM:</span><span>${data.fromEmail}</span></div>
  487. <div><span style="color:#000;font-weight:700;margin-top:5px">DATE:</span><span>${data.sendDate}</span></div>
  488. <div><span style="color:#000;font-weight:700;margin-top:5px">TO:</span><span>${mailStore.selectMail.mailUser}</span></div>
  489. <div><span style="color:#000;font-weight:700;margin-top:5px">SUBJECT:</span><span>${data.subject}</span></div>
  490. </div>
  491. ${data.content}`);
  492. // <p><br></p><p><br></p>
  493. // nextTick(() => {
  494. // contentEditor.value.getFocus();
  495. // });
  496. formData.data.replyTo = mailStore.selectMail.mailUser;
  497. handleChangeReply(formData.data.replyTo);
  498. }
  499. };
  500. // pageType 10为回复 20为转发 30为全部回复 40为再次编辑 0为写信 50为只回填收件人
  501. const init = () => {
  502. if (mailStore.currentMenu.pageType === "0") {
  503. handleReset();
  504. pushTo();
  505. formData.data.replyTo = mailStore.selectMail.mailUser;
  506. handleChangeReply(formData.data.replyTo);
  507. } else if (mailStore.currentMenu.pageType === "10") {
  508. handleReplyInit(mailStore.currentMenu, "10");
  509. } else if (mailStore.currentMenu.pageType === "20") {
  510. handleReplyInit(mailStore.currentMenu, "20");
  511. } else if (mailStore.currentMenu.pageType === "30") {
  512. handleReplyInit(mailStore.currentMenu, "30");
  513. } else if (mailStore.currentMenu.pageType === "40") {
  514. handleReplyInit(mailStore.currentMenu, "40");
  515. } else if (mailStore.currentMenu.pageType === "50") {
  516. handleReplyInit(mailStore.currentMenu, "50");
  517. }
  518. };
  519. watch(
  520. () => mailStore.currentMenu.pageType,
  521. (val) => {
  522. if (val === "0") {
  523. handleReset();
  524. pushTo();
  525. formData.data.replyTo = mailStore.selectMail.mailUser;
  526. handleChangeReply(formData.data.replyTo);
  527. } else if (val === "10") {
  528. handleReplyInit(mailStore.currentMenu, "10");
  529. } else if (val === "20") {
  530. handleReplyInit(mailStore.currentMenu, "20");
  531. } else if (val === "30") {
  532. handleReplyInit(mailStore.currentMenu, "30");
  533. } else if (val === "40") {
  534. handleReplyInit(mailStore.currentMenu, "40");
  535. } else if (val === "50") {
  536. handleReplyInit(mailStore.currentMenu, "50");
  537. }
  538. }
  539. );
  540. watch(
  541. () => mailStore.currentMenu.reMail,
  542. (val) => {
  543. if (val && mailStore.currentMenu.pageType === "0") {
  544. handleReset();
  545. pushTo();
  546. formData.data.replyTo = mailStore.selectMail.mailUser;
  547. handleChangeReply(formData.data.replyTo);
  548. }
  549. }
  550. );
  551. const templateList = ref([]);
  552. const getTemplateList = () => {
  553. proxy
  554. .post("/mailSignature/page", {
  555. pageNum: 1,
  556. pageSize: 9999,
  557. })
  558. .then((res) => {
  559. templateList.value = res.rows;
  560. });
  561. };
  562. getTemplateList();
  563. const handleChangeTemplate = (val) => {
  564. if (val) {
  565. const current = templateList.value.find((x) => x.id === val);
  566. contentEditor.value.changeHtml(
  567. `${formData.data.content}<br><div style="width: 50%;padding: 15px;overflow: auto;box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);">${current.content}</div>`
  568. );
  569. }
  570. };
  571. onMounted(() => {
  572. $bus.on("uploadFile", (fileList) => {
  573. for (let i = 0; i < fileList.length; i++) {
  574. const file = fileList[i];
  575. fileListCopy.value.push({
  576. id: file.id,
  577. fileName: file.fileName,
  578. path: file.fileUrl,
  579. fileUrl: file.fileUrl,
  580. });
  581. }
  582. });
  583. });
  584. onBeforeUnmount(() => {
  585. // 取消订阅特定事件
  586. $bus.off("uploadFile");
  587. });
  588. defineExpose({
  589. initFn: init,
  590. });
  591. </script>
  592. <style lang="scss" scoped>
  593. .att-img {
  594. height: 18px;
  595. width: 18px;
  596. transform: translateY(4px);
  597. }
  598. .signature-content {
  599. width: 50%;
  600. padding: 15px;
  601. overflow: auto;
  602. box-shadow: 1px 1px 1px 1px;
  603. }
  604. .att-box {
  605. padding-bottom: 8px;
  606. display: flex;
  607. flex-wrap: wrap;
  608. .att-item {
  609. width: 200px;
  610. background-color: #eeeeee;
  611. height: 28px;
  612. line-height: 26px;
  613. margin-right: 16px;
  614. padding-left: 8px;
  615. display: flex;
  616. margin-top: 10px;
  617. // margin-bottom: 8px;
  618. }
  619. .att-name {
  620. padding-left: 8px;
  621. width: 150px;
  622. white-space: nowrap;
  623. overflow: hidden;
  624. text-overflow: ellipsis;
  625. cursor: pointer;
  626. }
  627. }
  628. :deep(.el-upload-dragger) {
  629. width: 250px;
  630. height: 40px;
  631. line-height: 40px;
  632. padding: 0 8px;
  633. }
  634. // .el-form-item--default {
  635. // margin-bottom: 0px;
  636. // }
  637. </style>