mailWrite.vue 18 KB

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