From db42d08c39ae6129e2b95cd24c0d57c6769282e5 Mon Sep 17 00:00:00 2001 From: huangyinfeng <1244041895@qq.com> Date: 星期日, 29 九月 2024 15:32:51 +0800 Subject: [PATCH] 邮件右键菜单 --- src/views/email/folder/index.vue | 61 + src/components/MyUpload/hooks/useUploadHook.ts | 56 + src/views/email/Utils/folder.vue | 52 src/views/email/components/ContextMenu/CustomTimePicker.vue | 49 + pnpm-lock.yaml | 8 src/components/Tinymce/src/Editor.vue | 109 -- vite.config.ts | 2 src/views/email/components/ListPage/table.vue | 139 ++- src/views/email/components/ContextMenu/index.vue | 327 +++++++++ src/views/email/components/LeftMenu/MyMenu.vue | 121 +++ src/views/email/components/ListPage/drawerDetail.vue | 112 +++ src/views/email/label/index.vue | 61 + src/settings/projectSetting.ts | 2 src/components/MyUpload/index.vue | 205 +++++ src/views/email/components/ListPage/list.vue | 90 + src/views/email/components/ListPage/TooltipAndDropdown .vue | 36 src/views/email/components/LeftMenu/EmailMenuItem.vue | 44 + src/views/email/components/LeftMenu/index.vue | 282 +++++-- src/router/routes/modules/email.ts | 23 /dev/null | 181 ----- src/views/email/Edit/index.vue | 46 + src/views/email/Utils/mailboxManagement.vue | 47 + src/api/email/userList.ts | 23 package.json | 1 src/App.vue | 2 25 files changed, 1,563 insertions(+), 516 deletions(-) diff --git a/package.json b/package.json index 2d5ea9a..85cbe06 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "echarts": "^5.5.1", "exceljs": "^4.4.0", "html2canvas": "^1.4.1", + "jsencrypt": "^3.3.2", "lodash-es": "^4.17.21", "mockjs": "^1.1.0", "nprogress": "^0.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1f5291..fd17aeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ html2canvas: specifier: ^1.4.1 version: 1.4.1 + jsencrypt: + specifier: ^3.3.2 + version: 3.3.2 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -5200,6 +5203,9 @@ peerDependenciesMeta: canvas: optional: true + + jsencrypt@3.3.2: + resolution: {integrity: sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==} jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} @@ -13689,6 +13695,8 @@ - supports-color - utf-8-validate + jsencrypt@3.3.2: {} + jsesc@2.5.2: {} json-buffer@3.0.1: {} diff --git a/src/App.vue b/src/App.vue index 0e74772..04eb4bd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,7 +26,7 @@ // 鍏堟鏌roxy鏄惁瀛樺湪锛屽啀杩涜鎿嶄綔 if (proxy && proxy.$cookies) { proxy.$cookies.remove('JSESSIONID'); - proxy.$cookies.set('JSESSIONID', '741E9E2AAD7578915B16287A5ECAE1DF.jvm_59_9010', '1d'); + proxy.$cookies.set('JSESSIONID', '071339196A579D1A2D374739F29D7521.jvm_59_9010', '1d'); } else { console.error('proxy瀵硅薄鏈垵濮嬪寲鎴栦笉鍖呭惈$cookies灞炴��'); } diff --git a/src/api/email/userList.ts b/src/api/email/userList.ts index 2b67de7..c3aa42f 100644 --- a/src/api/email/userList.ts +++ b/src/api/email/userList.ts @@ -49,6 +49,7 @@ DELETE_BLACKLIST = '/crm/mail/blacklist/deleteBlackList.do', GET_BLACKLIST = '/crm/mail/blacklist/getBlackList.do', GET_EMAIL_MODULE_BELOW = '/crm/mail/getEmailModuleBelow.do', + ADD_LIAS_EMAIL = '/crm/mail/account/addAliasEmail.do', } // 鑾峰彇閭欢璺敱鍒楄〃 export const getEmailModuleApi = () => defHttp.get({ url: Api.GET_EMAIL_MODULE }); @@ -73,7 +74,8 @@ // 淇敼閭閰嶇疆 export const updateAccountApi = (params) => defHttp.post<{}>({ url: Api.UPDATE_ACCOUNT, params }); // 鍒犻櫎閭閰嶇疆 -export const deleteAccountApi = (params) => defHttp.post<{}>({ url: Api.DELETE_ACCOUNT, params }); +export const deleteAccountApi = (query) => + defHttp.post<{}>({ url: Api.DELETE_ACCOUNT + '?accountId=' + query }); // 鑾峰彇閭鍒楄〃 export const getAccountListApi = () => defHttp.get<{}>({ url: Api.GET_ACCOUNT_LIST }); @@ -158,7 +160,7 @@ }); // 璁剧疆瀹屾垚鏃堕棿 export const updateHandleAPi = (params) => - defHttp.get({ + defHttp.post({ url: Api.UPDATE_HANDLE, params, }); @@ -252,7 +254,6 @@ params, }); - // 鏂板榛戝悕鍗� export const addBlackListApi = (params) => defHttp.post({ @@ -281,9 +282,15 @@ params, }); +// 鏂囦欢澶圭粨鏋� +export const getEmailModuleBelowApi = () => + defHttp.get({ + url: Api.GET_EMAIL_MODULE_BELOW, + }); - export const getEmailModuleBelowApi = () => - defHttp.get({ - url: Api.GET_EMAIL_MODULE_BELOW, - }); - \ No newline at end of file +//閭鍒悕 +export const addLiasEmailApi = (params) => + defHttp.get({ + url: Api.ADD_LIAS_EMAIL, + params, + }); diff --git a/src/components/MyUpload/hooks/useUploadHook.ts b/src/components/MyUpload/hooks/useUploadHook.ts new file mode 100644 index 0000000..a6993f2 --- /dev/null +++ b/src/components/MyUpload/hooks/useUploadHook.ts @@ -0,0 +1,56 @@ +import { ref } from 'vue'; +import JSEncrypt from 'jsencrypt'; // 瀵煎叆 JSEncrypt 搴� + +export const useUploadHook = (baseUrl: string) => { + const computedUploadUrl = ref(baseUrl); + + // RSA 鍔犲瘑鐢ㄧ殑鍏挜 + const publicKey = `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRupiYcKVGGUtDBDoR1t/1zm3ZtZgnte39iTJW6hlqjdY0UagKjpNiIv7J6XjtgfX7SgsR4AWnivqQHAICIvdPKfGZZzIs62OQ19MqrDTMoB/LvK5teNWhClv23WMUfRbP+EHgprT6hTw8U5apw1IB6i/y57NkLav792wiYBYRU4X45NoTaT+aiTSLFEflbfm94EXnhSS3vFkBmrZGy5BRNI8gmzafroslGx2Hk90CqlNdeKYxgZQ6xtvj+u33yrszWvPT6F9fsJT8aMjtvH050iYKRVct+x6Q7VRJgCI4MgvAexnTKdxW54YzvXCuO5bDiy5la7CgerWkTAq9dzXwIDAQAB + -----END PUBLIC KEY-----`; + + // 鍥哄畾鐨勫煙鍚嶅拰ID锛堝皢鏉ュ彲浠� Vuex 涓幏鍙栵級 + const domain = 'https://img.onbus.cn'; + const dbid = 82; // 杩欓噷鍙互浠� Vuex 鑾峰彇 + const formid = 0; // 杩欓噷涔熷彲浠ヤ粠 Vuex 鑾峰彇 + const uuid = ref(undefined); // 杩欓噷涔熷彲浠ヤ粠 Vuex 鑾峰彇 + + // 浣跨敤 RSA 鍔犲瘑鏁版嵁 + const encryptData = (data: Record<string, any>) => { + const encryptor = new JSEncrypt(); + encryptor.setPublicKey(publicKey); + + const encryptedData: Record<string, string> = {}; + for (const key in data) { + if (data.hasOwnProperty(key)) { + const encryptedValue = encryptor.encrypt(data[key]); + if (encryptedValue) { + encryptedData[key] = encodeURIComponent(encryptedValue); // URL 缂栫爜 + } + } + } + return encryptedData; + }; + + // 鑾峰彇涓婁紶鏁版嵁骞跺姞瀵� + const getUploadData = (uuid) => { + const data = { + dbid: dbid.toString(), + username: 'huang', + usercode: 'z9000137', + }; + + const newData = encryptData(data); + return { ...newData, type: 3, fieldid: '333', uuid: uuid }; + }; + + // 鐢熸垚鏂囦欢 URL + const generateFileUrl = (response: any) => { + const unid = response.uuid.split(';')[0] + '@P@' + response.uuid.split(';')[1]; + const fileExt = response.fileType; + + return `${domain}/uploads/attachment/${dbid}/${formid}/${unid}.${fileExt}`; + }; + + return { computedUploadUrl, getUploadData, generateFileUrl }; +}; diff --git a/src/components/MyUpload/index.vue b/src/components/MyUpload/index.vue new file mode 100644 index 0000000..c8a9818 --- /dev/null +++ b/src/components/MyUpload/index.vue @@ -0,0 +1,205 @@ +<template> + <div> + <Upload + v-if="type == 'png'" + name="file" + multiple + @change="handleUploadChange" + :action="computedUploadUrl" + :showUploadList="false" + :accept="accept" + :beforeUpload="beforeUpload" + > + <a-button type="text" size="small" v-bind="{ ...getButtonProps }"> + <PictureOutlined /> {{ title }} + </a-button> + </Upload> + + <a-upload + v-if="type == 'file'" + v-model:file-list="fileListTemp" + :action="computedUploadUrl" + list-type="picture" + class="upload-list-inline" + :before-upload="beforeUpload" + @change="handleFileUploadChange" + > + <a-button type="text" size="small" style="margin: 0 auto"> + <UploadOutlined /> + 闄勪欢 + </a-button> + <template #iconRender> + <PaperClipOutlined /> + </template> + <template #itemRender="{ file, fileList, actions }"> + <a-space class="ant-upload-list-picture-card"> + <span style="display: flex; flex-wrap: wrap"> + <PaperClipOutlined style="margin-right: 4px" /> + <span v-if="!file.editor" :style="file.status === 'error' ? 'color: red' : ''"> + {{ file.name }} + </span> + <span v-else> + <a-input size="small" v-model:value="file.tempName"></a-input> + </span> + </span> + <span v-if="!file.editor"> + <a href="javascript:;" @click="fnPreview(file)">棰勮</a> + <a href="javascript:;" @click="fnRename(file)">閲嶅懡鍚�</a> + <a href="javascript:;" @click="actions.remove">鍒犻櫎</a> + </span> + <span v-else> + <a href="javascript:;" @click="fnSaveRename(file)">淇濆瓨</a> + <a href="javascript:;" @click="fnOffRename(file)">鍙栨秷</a> + </span> + </a-space> + </template> + </a-upload> + <div id="previewContainer"></div> + </div> +</template> + +<script lang="ts" setup> + import { computed, ref, watch } from 'vue'; + import { PictureOutlined, UploadOutlined, PaperClipOutlined } from '@ant-design/icons-vue'; + import { Upload } from 'ant-design-vue'; + import { useGlobSetting } from '@/hooks/setting'; + import { useUploadHook } from './hooks/useUploadHook'; + + defineOptions({ name: 'TinymceImageUpload' }); + + const props = defineProps({ + fullscreen: { + type: Boolean, + }, + disabled: { + type: Boolean, + default: false, + }, + accept: { + type: String, + default: '.jpg,.jpeg,.gif,.png,.webp', + }, + title: { + type: String, + default: '鍥剧墖', + }, + type: { + type: String, + default: 'png', + }, + }); + + const emit = defineEmits(['uploading', 'done', 'error', 'fileListChange']); + let uploading = false; + + const { uploadUrl: baseUrl } = useGlobSetting(); + const { computedUploadUrl, getUploadData, generateFileUrl } = useUploadHook(baseUrl); + + const fileListTemp = ref([]); // 鐢ㄤ簬闄勪欢鐨勬枃浠跺垪琛� + + const getButtonProps = computed(() => ({ + disabled: props.disabled, + })); + const uuid = ref(''); + + // beforeUpload 鎷︽埅鍣紝鐢ㄤ簬鍔ㄦ�佷慨鏀� URL + const beforeUpload = () => { + const encryptedParams = getUploadData(uuid.value); + const queryString = new URLSearchParams(encryptedParams).toString(); + computedUploadUrl.value = `${baseUrl}?${queryString}`; + }; + // 澶勭悊涓婁紶鍙樺寲 + const handleUploadChange = (info: Record<string, any>) => { + const file = info.file; + const status = file?.status; + + if (status === 'uploading') { + if (!uploading) { + emit('uploading', file.name); + uploading = true; + } + } else if (status === 'done') { + const newUrl = generateFileUrl(file.response); // 鐢熸垚鏂囦欢 URL + debugger; + if (!uuid.value) { + uuid.value = file.response.uuid.split(';')[0]; + } + emit('done', file.name, newUrl); + uploading = false; + } else if (status === 'error') { + emit('error'); + uploading = false; + } + }; + function handleFileUploadChange(info: Record<string, any>) { + const file = info.file; + const status = file?.status; + if (status === 'done') { + if (!uuid.value) { + uuid.value = file.response.uuid.split(';')[0]; + } + uploading = false; + } + } + // 閲嶅懡鍚嶅姛鑳� + const fnRename = (file) => { + file.editor = true; + }; + + // 淇濆瓨閲嶅懡鍚� + const fnSaveRename = (file) => { + file.editor = false; + file.name = file.tempName; + }; + + // 鍙栨秷閲嶅懡鍚� + const fnOffRename = (file) => { + file.editor = false; + }; + + // 棰勮 + const fnPreview = (file) => { + if (!file || !file.response) { + console.error('Invalid file or response'); + return; + } + + // 鐢熸垚瀹夊叏鐨勬枃浠� URL + const safeUrl = generateFileUrl(file.response); + + // 鑾峰彇鏂囦欢绫诲瀷锛堥�氳繃鏂囦欢鎵╁睍鍚嶆垨 MIME 绫诲瀷锛� + const fileExt = file.response.fileType || file.name.split('.').pop().toLowerCase(); // 鑾峰彇鏂囦欢鎵╁睍鍚� + const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExt); // 鍒ゆ柇鏄惁涓哄浘鐗� + + if (isImage) { + // 鐩存帴鎵撳紑鍥剧墖 + window.open(safeUrl, '_blank'); + } else { + // 闈炲浘鐗囩被鍨嬩娇鐢� Office Online Viewer 鎴栧叾浠栧伐鍏烽瑙� + const iframeSrc = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(safeUrl)}`; + window.open(iframeSrc, '_blank'); + } + }; + + watch( + () => fileListTemp.value, + (newValue) => { + emit('fileListChange', fileListTemp.value); + }, + ); +</script> + +<style lang="less" scoped> + @prefix-cls: ~'@{namespace}-tinymce-img-upload'; + + .@{prefix-cls} { + z-index: 20; + top: 4px; + right: 10px; + + &.fullscreen { + position: fixed; + z-index: 10000; + } + } +</style> diff --git a/src/components/Tinymce/src/Editor.vue b/src/components/Tinymce/src/Editor.vue index 53ed7c9..025753a 100644 --- a/src/components/Tinymce/src/Editor.vue +++ b/src/components/Tinymce/src/Editor.vue @@ -8,42 +8,20 @@ ></textarea> <slot v-else></slot> <div class="p-2 tox-statusbar"> - <a-upload - v-if="isElse" - v-model:file-list="fileListTemp" - action="https://www.mocky.io/v2/5cc8019d300000980a055e76" - list-type="picture" - class="upload-list-inline" - > - <a-button type="text" size="small" style="margin: 0 auto"> - <upload-outlined></upload-outlined> - 闄勪欢 - </a-button> - <template #iconRender><PaperClipOutlined /></template> - <template #itemRender="{ file, fileList, actions }"> - <a-space class="ant-upload-list-picture-card"> - <span style="display: flex; flex-wrap: wrap"> - <PaperClipOutlined style="margin-right: 4px" /> - <span v-if="!file.editor" :style="file.status === 'error' ? 'color: red' : ''">{{ - file.name - }}</span> - <span v-else> - <a-input size="small" v-model:value="file.tempName"></a-input> - </span> - </span> - <span v-if="!file.editor"> - <a href="javascript:;" @click="actions.preview">棰勮</a> - <a href="javascript:;" @click="fnRename(file, fileList)">閲嶅懡鍚�</a> - <a href="javascript:;" @click="actions.remove">鍒犻櫎</a> - </span> - <span v-else> - <a href="javascript:;" @click="fnSaveRename(file, fileList)">淇濆瓨</a> - <a href="javascript:;" @click="fnOffRename(file, fileList)">鍙栨秷</a> - </span> - </a-space> - </template> - </a-upload> - <div :class="fileListTemp.length > 0 ? 'my-upload-list' : ''"> + <ImgUpload + ref="fileRef" + :fullscreen="fullscreen" + :fileListTemp="fileListTemp" + @uploading="handleImageUploading" + @done="handleDone" + @fileListChange="fileListChange" + v-if="isImg" + v-show="editorRef" + :title="'闄勪欢'" + :type="'file'" + :disabled="disabled" + /> + <div class="my-upload-list"> <div style="display: flex"> <ImgUpload :fullscreen="fullscreen" @@ -52,6 +30,7 @@ v-if="isImg" v-show="editorRef" :title="'鍥剧墖'" + :type="'png'" :disabled="disabled" :accept="'.jpg,.jpeg,.gif,.png,.webp'" /> @@ -68,6 +47,8 @@ <script lang="ts" setup> import type { Editor, RawEditorSettings } from 'tinymce'; import { PaperClipOutlined, UploadOutlined, SnippetsOutlined } from '@ant-design/icons-vue'; + import { useGlobSetting } from '@/hooks/setting'; + import tinymce from 'tinymce/tinymce'; import 'tinymce/themes/silver'; import 'tinymce/icons/default/icons'; @@ -144,7 +125,7 @@ PropType, useAttrs, } from 'vue'; - import ImgUpload from './ImgUpload.vue'; + import ImgUpload from '@/components/MyUpload/index.vue'; import { plugins as defaultPlugins, toolbar as defaultToolbar, @@ -157,6 +138,8 @@ import { isNumber } from '@/utils/is'; import { useLocale } from '@/locales/useLocale'; import { useAppStore } from '@/store/modules/app'; + const { uploadUrl } = useGlobSetting(); + // console.log(uploadUrl,'uploadUrl'); defineOptions({ name: 'Tinymce', inheritAttrs: false }); @@ -370,6 +353,7 @@ editor.setContent(val); } } + const fileRef = ref(); function bindModelHandlers(editor: any) { const modelEvents = attrs.modelEvents ? attrs.modelEvents : null; @@ -432,54 +416,9 @@ } // 闄勪欢 - const fileListTemp = ref<UploadProps['fileList']>([ - // { - // uid: '-1', - // name: 'xxx.png', - // tempName: 'xxx', - // status: 'done', - // url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', - // thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', - // editor: false, - // }, - // { - // uid: '-2', - // name: 'yyy.png', - // tempName: 'yyy', - // status: 'done', - // url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', - // thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', - // editor: false, - // }, - ]); - function fnRename(file, fileList) { - console.log(file, fileList); - fileListTemp.value = fileList.map((item) => { - // item.tempName = item.name.split('.').slice(0,-1) - if (file.uid == item.uid) { - item.editor = true; - } else { - item.editor = false; - } - return item; - }); - } - function fnSaveRename(file, fileList) { - fileListTemp.value = fileList.map((item) => { - if (file.uid == item.uid) { - item.name = item.tempName; - item.editor = false; - } - return item; - }); - } - function fnOffRename(file, fileList) { - fileListTemp.value = fileList.map((item) => { - if (file.uid == item.uid) { - item.editor = false; - } - return item; - }); + const fileListTemp = ref([]); + function fileListChange(data) { + fileListTemp.value = data; } </script> <style lang="less" scope> diff --git a/src/components/Tinymce/src/ImgUpload.vue b/src/components/Tinymce/src/ImgUpload.vue deleted file mode 100644 index 61aa1e2..0000000 --- a/src/components/Tinymce/src/ImgUpload.vue +++ /dev/null @@ -1,94 +0,0 @@ -<template> - <div :class="[prefixCls, { fullscreen }]"> - <Upload - name="file" - multiple - @change="handleChange" - :action="uploadUrl" - :showUploadList="false" - :accept="accept" - > - <a-button type="text" size='small' v-bind="{ ...getButtonProps }"> - <PictureOutlined /> {{ title }} - </a-button> - </Upload> - </div> -</template> -<script lang="ts" setup> - import { computed } from 'vue'; - import {PictureOutlined } from '@ant-design/icons-vue'; - import { Upload } from 'ant-design-vue'; - import { useDesign } from '@/hooks/web/useDesign'; - import { useGlobSetting } from '@/hooks/setting'; - import { useI18n } from '@/hooks/web/useI18n'; - - defineOptions({ name: 'TinymceImageUpload' }); - - const props = defineProps({ - fullscreen: { - type: Boolean, - }, - disabled: { - type: Boolean, - default: false, - }, - accept:{ - type: String, - default: '.jpg,.jpeg,.gif,.png,.webp' - }, - title:{ - type: String, - default: '鍥剧墖' - } - }); - - const emit = defineEmits(['uploading', 'done', 'error']); - - let uploading = false; - - const { uploadUrl } = useGlobSetting(); - const { t } = useI18n(); - const { prefixCls } = useDesign('tinymce-img-upload'); - - const getButtonProps = computed(() => { - const { disabled } = props; - return { - disabled, - }; - }); - - function handleChange(info: Record<string, any>) { - const file = info.file; - const status = file?.status; - const url = file?.response?.url; - const name = file?.name; - - if (status === 'uploading') { - if (!uploading) { - emit('uploading', name); - uploading = true; - } - } else if (status === 'done') { - emit('done', name, url); - uploading = false; - } else if (status === 'error') { - emit('error'); - uploading = false; - } - } -</script> -<style lang="less" scoped> - @prefix-cls: ~'@{namespace}-tinymce-img-upload'; - - .@{prefix-cls} { - // position: absolute; - z-index: 20; - top: 4px; - right: 10px; - - &.fullscreen { - position: fixed; - z-index: 10000; - } - } -</style> diff --git a/src/router/routes/modules/email.ts b/src/router/routes/modules/email.ts index a362a3a..7da5c22 100644 --- a/src/router/routes/modules/email.ts +++ b/src/router/routes/modules/email.ts @@ -108,7 +108,8 @@ meta: { title: '鍏ㄩ儴鍙戜欢', }, - }] + }, + ], }, // { // path: 'MassMailbox', @@ -120,6 +121,26 @@ // currentActiveMenu: '/email/index', // }, // }, + { + path: 'folder', + name: 'Folder', + component: () => import('@/views/email/folder/index.vue'), + meta: { + title: '鏂囦欢澶�', + hideTab: true, + currentActiveMenu: '/email/index', + }, + }, + { + path: 'label', + name: 'Label', + component: () => import('@/views/email/label/index.vue'), + meta: { + title: '鏍囩', + hideTab: true, + currentActiveMenu: '/email/index', + }, + }, ], }; diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts index 12b128f..bb30f0c 100644 --- a/src/settings/projectSetting.ts +++ b/src/settings/projectSetting.ts @@ -235,7 +235,7 @@ // Use error-handler-plugin // 鏄惁浣跨敤鍏ㄥ眬閿欒鎹曡幏 - useErrorHandle: true, + useErrorHandle: false, // Whether to open back to top // 鏄惁寮�鍚洖鍒伴《閮� diff --git a/src/views/email/Edit/index.vue b/src/views/email/Edit/index.vue index ae47131..f683557 100644 --- a/src/views/email/Edit/index.vue +++ b/src/views/email/Edit/index.vue @@ -393,9 +393,47 @@ }; function fnChangeContent(e) { - modelRef.content = e.content; - modelRef.attachmentList = e.attachmentList; + if (!isValidEvent(e)) { + console.error('Invalid event:', e); + return; } + + console.log('Event:', e, '----------------'); + + modelRef.content = e.content; + modelRef.attachmentList = mergeArrayWithPrefix(fnBuildAttachmentList(e.fileUNID)); + + console.log('Updated modelRef:', modelRef); +} + +function isValidEvent(e) { + return e && typeof e === 'object' && typeof e.content === 'string'; +} + +function fnBuildAttachmentList(data) { + if (!Array.isArray(data)) { + console.error('Invalid argument: data must be an array'); + return []; + } + + return data.reduce((acc, item) => { + const uuid = item?.response?.uuid; + if (uuid) { + acc.push(uuid); + } else { + console.warn('Invalid item:', item); + } + return acc; + }, []); +} + +function mergeArrayWithPrefix(arr) { + if (!arr || arr.length === 0) return ''; + const [prefix] = arr[0].split(';'); + const suffixes = arr.map(item => item.split(';')[1]).filter(Boolean).join(';'); + + return `${prefix};${suffixes}`; +} function fnBuildingCommitData() { return { @@ -405,7 +443,7 @@ bcc: modelRef.bccRecipients, subject: modelRef.subject, content: modelRef.content, - attachmentList: '', + attachmentList: modelRef.attachmentList, docCode: docCode.value, }; } @@ -441,7 +479,7 @@ loading.value = false; if (res.code === 0) { createMessage.success(res.msg); - router.push('/email/list'); + router.push('/email/index'); } }) .catch((error) => { diff --git a/src/views/email/Utils/folder.vue b/src/views/email/Utils/folder.vue index 09769c5..c373435 100644 --- a/src/views/email/Utils/folder.vue +++ b/src/views/email/Utils/folder.vue @@ -114,31 +114,32 @@ function fnGetList() { getFolderApi({}).then((res) => { - console.log(res); demo.tableData = convertToTableData(res.data); - console.log(demo.tableData, '3333333333333'); }); } function convertToTableData(data, parentId = null) { - let tableData = []; + try { + let tableData = []; - data.forEach((item) => { - let tableItem = { - folderId: item.folderId, - parentRowId: parentId, - rowId: item.rowId, - folderName: item.folderName, - treeControl: item.treeControl, - }; + data.forEach((item) => { + let tableItem = { + folderId: item.folderId, + parentRowId: parentId, + rowId: item.rowId, + folderName: item.folderName, + treeControl: item.treeControl, + }; - if (item.list && item.list.length > 0) { - let children = convertToTableData(item.list, item.rowId); - tableData = tableData.concat(children); - } + if (item.list && item.list.length > 0) { + let children = convertToTableData(item.list, item.rowId); + tableData = tableData.concat(children); + } - tableData.push(tableItem); - }); - return tableData; + tableData.push(tableItem); + }); + return tableData; + + } catch (error) { return [];} } const inputRefs = ref<{ [key: number]: HTMLElement | null }>({}); @@ -146,7 +147,7 @@ const $table = xTable.value; const rid = Date.now(); const record = { - folderName: `鏂版暟鎹�${rid}`, + folderName: `鏂版枃浠跺す`, id: rid, }; $table.insert(record).then(({ row }) => $table.setEditRow(row)); @@ -158,9 +159,9 @@ const { createMessage } = useMessage(); function fnInputHandle(row) { - console.log(row, '----333'); if (row.folderName == '') { - editRowEvent(row) + editRowEvent(row); + fnGetList(); return createMessage.error('璇疯緭鍏ユ枃浠跺す鍚嶇О'); } const data = @@ -184,6 +185,7 @@ fnGetList(); } else { createMessage.error(res.msg); + fnGetList(); } }); } @@ -191,15 +193,16 @@ const $table = xTable.value; const rid = Date.now(); const record = { - folderName: `鏂版暟鎹�${rid}`, + folderName: `鏂板瓙鏂囦欢澶筦, id: rid, parentRowId: row.rowId, // 闇�瑕佹寚瀹氱埗鑺傜偣锛岃嚜鍔ㄦ彃鍏ヨ鑺傜偣涓� }; - console.log(record, '99999993'); const { row: newRow } = await $table.insert(record); - await $table.setTreeExpand(row, true); // 灏嗙埗鑺傜偣灞曞紑 await $table.setEditRow(newRow); // 鎻掑叆瀛愯妭鐐� + setTimeout(() => { + inputRefs.value[rid].focus(); + }, 300); } function fnDelete(row) { deleteFolderApi({ folderId: row.folderId }) @@ -216,7 +219,6 @@ function editRowEvent(row) { const $table = xTable.value; - console.log(row, '---30494'); row.opType = 'edit'; $table.setEditRow(row); } diff --git a/src/views/email/Utils/mailboxManagement.vue b/src/views/email/Utils/mailboxManagement.vue index 2809d78..7a68cfb 100644 --- a/src/views/email/Utils/mailboxManagement.vue +++ b/src/views/email/Utils/mailboxManagement.vue @@ -20,6 +20,7 @@ :filter-config="{ showIcon: false }" :row-config="{ isHover: true }" :column-config="{ resizable: true }" + :edit-config="{ trigger: 'click', mode: 'cell' }" > <vxe-column width="60"> <template #default> @@ -34,15 +35,29 @@ <span style="margin-left: 10px; color: #3081fe; font-weight: 500">{{ row.email }}</span> </template> </vxe-column> - <vxe-column show-overflow field="companyName" title="鏄剧ず鍚嶇О" min-width="250"> + <vxe-column + show-overflow + field="aliasEmail" + title="鏄剧ず鍚嶇О" + min-width="250" + :edit-render="{}" + > <template #default="{ row }"> - <span style="color: #999">{{ row.companyName }}</span> + <span style="color: #999">{{ row.aliasEmail }}</span> + </template> + <template #edit="{ row }"> + <vxe-input + ref="inputRef" + v-model="row.aliasEmail" + type="text" + @blur="fnInputHandle(row)" + ></vxe-input> </template> </vxe-column> <vxe-column show-overflow field="status" title="閭鐘舵��" min-width="250"> <template #default="{ row }"> <div v-if="!isCheckAll"> - <a-tag color="orange" v-if="row.status === '姝e父'">姝e父</a-tag> + <a-tag color="success" v-if="row.mailStatus === '姝e父'">姝e父</a-tag> <a-tag color="red" v-else>寮傚父</a-tag> </div> <div v-else> @@ -98,7 +113,8 @@ </template> <a-input :disabled="typeAccount === 2" v-model:value="formData.email" /> </a-form-item> - <a-form-item v-if="isCustom == 'custom'" name="password" label="閭瀵嗙爜"> + <!-- v-if="isCustom == 'custom'" --> + <a-form-item name="password" label="閭瀵嗙爜"> <a-input-password type="password" v-model:value="formData.password" @@ -397,6 +413,7 @@ deleteAccountApi, getAccountListApi, isEmailValidApi, + addLiasEmailApi, } from '@/api/email/userList'; const loading = ref(false); import Sortable from 'sortablejs'; @@ -454,11 +471,11 @@ aliasEmail: '', biSyncFlag: false, proxyFlag: true, - receiveProtocol: 'imap', + receiveProtocol: 'imaps', receiveSSL: false, receivePort: '', receiveHost: '', - smtpSSL: false, + smtpSSL: true, smtpPort: '', smtpHost: '', invalid: '', @@ -510,7 +527,12 @@ const open = ref(false); const fnHandleOk = () => { formRef.value.validate().then(() => { - const data = formData.value; + const data = !isShow + ? formData.value + : { + email: formData.value.email, + password: formData.value.password, + }; if (isShow.value == true) { data.mailType = isCustom.value === 'onCustom' ? 1 : 2; } @@ -523,11 +545,12 @@ } loading.value = false; fnMailList(); + open.value = false; }) .catch((err) => { loading.value = false; + open.value = false; }); - open.value = false; }); }; @@ -586,7 +609,7 @@ } function fnHandleDetailOk() { openDrawerDetail.value = false; - deleteAccountApi({ accountId: accountId.value }) + deleteAccountApi(accountId.value ) .then((res) => { if (res.code === 0) { createMessage.success(res.msg); @@ -626,7 +649,7 @@ isEmailValidApi(email).then((res) => { if (res.code == 0) { isCheck.value = true; - checkStatus.value = res.data.status; + checkStatus.value = res.data.code == 0 ? true : false; } }); } @@ -664,6 +687,10 @@ isCheckAll.value = false; }, 3000); } + + function fnInputHandle(row) { + addLiasEmailApi({ aliasEmail: row.aliasEmail, accountId: row.accountId }).then((res) => {}); + } </script> <style scoped lang="less"> .bullet { diff --git a/src/views/email/components/ContextMenu/CustomTimePicker.vue b/src/views/email/components/ContextMenu/CustomTimePicker.vue new file mode 100644 index 0000000..515012d --- /dev/null +++ b/src/views/email/components/ContextMenu/CustomTimePicker.vue @@ -0,0 +1,49 @@ +<template> + <a-form :model="form" ref="formRef"> + <a-form-item + label="鏃ユ湡" + name="date" + :rules="[{ required: true, message: '璇烽�夋嫨鏃ユ湡' }]" + > + <a-date-picker + format="YYYY/MM/DD" + :disabledDate="disabledDate" + v-model:value="form.date" + /> + </a-form-item> + <a-form-item + label="鏃堕棿" + name="time" + :rules="[{ required: true, message: '璇烽�夋嫨鏃堕棿' }]" + > + <a-time-picker v-model:value="form.time" /> + </a-form-item> + <a-form-item> + <a-button @click="$emit('cancel')">鍙栨秷</a-button> + <a-button + style="margin-left: 10px" + type="primary" + @click="$emit('submit')" + >纭畾</a-button> + </a-form-item> + </a-form> +</template> + +<script setup> +import { defineProps, defineEmits, reactive } from 'vue'; + +const props = defineProps(); +const emit = defineEmits(); +const form = reactive({ + date: '', + time: '', +}); + +const disabledDate = (currentDate) => { + return currentDate && currentDate < dayjs().startOf('day'); +}; +</script> + +<style scoped> +/* 娣诲姞鑷畾涔夋牱寮� */ +</style> diff --git a/src/views/email/components/ContextMenu/index.vue b/src/views/email/components/ContextMenu/index.vue new file mode 100644 index 0000000..54ad3db --- /dev/null +++ b/src/views/email/components/ContextMenu/index.vue @@ -0,0 +1,327 @@ +<template> + <div :style="style" class="custom-dropdown"> + <!-- <template #overlay> --> + <a-menu> + <a-menu-item key="reply" @click="handleMenuAction('reply')">鍥炲</a-menu-item> + <a-menu-item key="replyAll" @click="handleMenuAction('replyAll')">鍥炲鍏ㄩ儴</a-menu-item> + <a-menu-item key="replyWithAttachment" @click="handleMenuAction('replyWithAttachment')"> + 甯﹂檮浠跺洖澶� + </a-menu-item> + <a-menu-item key="replyAllWithAttachment" @click="handleMenuAction('replyAllWithAttachment')"> + 甯﹂檮浠跺洖澶嶅叏閮� + </a-menu-item> + <a-menu-item key="forward" @click="handleMenuAction('forward')">杞彂</a-menu-item> + <a-menu-item key="forwardAsAttachment" @click="handleMenuAction('forwardAsAttachment')"> + 浣滀负闄勪欢杞彂 + </a-menu-item> + <a-menu-item key="reEdit" @click="handleMenuAction('reEdit')">鍒嗗彂</a-menu-item> + <a-menu-item key="addNote" @click="handleMenuAction('addNote')">璁剧疆澶囨敞</a-menu-item> + <a-divider style="margin: 5px" /> + + <!-- <a-menu-item key="toProcess" @click="handleMenuAction('toProcess')"> + <div class="my-display"> + <a-dropdown> + <span>寰呭鐞� <FieldTimeOutlined /></span> + <template #overlay> + <a-card title="閫夋嫨绋嶅悗澶勭悊鏃堕棿锛�" style="width: 250px" size="small"> + <div + class="date p-1" + v-for="item in dateList" + :key="item.key" + @click="fnSelectDate(item)" + > + <div class="date-left">{{ item.name }}</div> + <div class="date-right"> + <span v-if="item.key !== 'today'">{{ item.dayOfWeek }}</span> + <span style="margin-left: 5px">{{ item.time }}</span> + </div> + </div> + <a-divider style="margin: 5px 0" /> + <div class="date p-1"> + <a-popover + :trigger="trigger" + title="鑷畾涔夋椂闂�" + v-model:open="customTimeDropdownOpen" + @confirm="onSubmitCustomTime" + > + <template #content> + <CustomTimePicker + :form="form" + @cancel="cancelCustomTime" + @submit="submitCustomTime" + /> + </template> + + <div class="date-left" @click="toggleCustomTime">鑷畾涔夋椂闂�</div> + </a-popover> + </div> + </a-card> + </template> + </a-dropdown> + </div> + </a-menu-item> --> + <a-menu-item key="markAs" @click="handleMenuAction('markAs')">{{ + `鏍囪${row.readFlag ? '鏈�' : '宸�'}璇籤 + }}</a-menu-item> + <a-menu-item key="markAs" @click="handleMenuAction('markAs')">鏍囪涓�</a-menu-item> + <a-divider style="margin: 5px" /> + <a-menu-item key="createRule" @click="handleMenuAction('createRule')" + >鏂板缓鏀跺彂浠惰鍒�</a-menu-item + > + <a-menu-item key="moveTo" @click="handleMenuAction('moveTo')">绉诲姩鍒�</a-menu-item> + <a-divider style="margin: 5px" /> + <a-menu-item key="exportEmail" @click="handleMenuAction('exportEmail')">瀵煎嚭閭欢</a-menu-item> + <a-menu-item key="trackCustomer" @click="handleMenuAction('trackCustomer')" + >寤轰负瀹㈡埛璺熻繘</a-menu-item + > + <a-menu-item key="createSchedule" @click="handleMenuAction('createSchedule')" + >鏂板缓鏃ョ▼</a-menu-item + > + <a-divider style="margin: 5px" /> + + <a-menu-item key="delete" @click="handleMenuAction('delete')">鏍囦负鍨冨溇閭欢</a-menu-item> + <a-menu-item key="delete" @click="handleMenuAction('delete')">鍒犻櫎</a-menu-item> + </a-menu> + <!-- </template> --> + </div> +</template> + +<script setup> + import { defineProps, defineEmits, computed, ref, reactive, inject } from 'vue'; + import CustomTimePicker from './CustomTimePicker.vue'; + const props = defineProps({ + style: Object, + selectedCell: Object, + }); + + const row = computed(() => props.selectedCell.row); + const emit = defineEmits(['close-menu']); + import { useRouter } from 'vue-router'; + const router = useRouter(); + import { updateReadApi, updateHandleAPi, deleteEmailAPi } from '@/api/email/userList'; + const getDataList = inject('getDataList'); + const handleMenuAction = (action) => { + const res = props.selectedCell.row; + // 鏍规嵁涓嶅悓鐨刟ction鎵ц瀵瑰簲鐨勬搷浣� + switch (action) { + case 'reply': + router.push({ path: '/email/edit', query: { docCode: res.docCode, type: 'reply' } }); + break; + case 'replyAll': + router.push({ path: '/email/edit', query: { docCode: res.docCode, type: 'reply' } }); + break; + case 'replyWithAttachment': + router.push({ path: '/email/edit', query: { docCode: res.docCode, type: 'reply' } }); + break; + case 'replyAllWithAttachment': + router.push({ path: '/email/edit', query: { docCode: res.docCode, type: 'reply' } }); + break; + case 'forward': + console.log('Forward action triggered'); + break; + case 'addNote': + console.log('Add Note action triggered'); + break; + case 'forwardAsAttachment': + console.log('Forward as Attachment action triggered'); + break; + case 'reEdit': + console.log('Re-edit action triggered'); + break; + case 'toProcess': + console.log('Add to Process action triggered'); + break; + case 'markAs': + const data = { + status: !res.readFlag, + list: [res.docCode], + }; + updateReadApi(data).then((res) => { + if (res.code == 0) { + getDataList({}); + } + }); + console.log('Mark as action triggered'); + break; + case 'createRule': + console.log('Create rule action triggered'); + break; + case 'moveTo': + console.log('Move to action triggered'); + break; + case 'exportEmail': + console.log('Export Email action triggered'); + break; + case 'trackCustomer': + console.log('Track Customer action triggered'); + break; + case 'createSchedule': + console.log('Create Schedule action triggered'); + break; + case 'delete': + deleteEmailAPi([res.docCode]).then((res) => { + if (res.code == 0) { + getDataList({}); + } + }); + break; + default: + console.warn('Unknown action:', action); + } + + // 鎿嶄綔瀹屾垚鍚庡叧闂彍鍗� + emit('close-menu'); + }; + + // 寰呭鐞� + import { FieldTimeOutlined, PushpinOutlined } from '@ant-design/icons-vue'; + function processDateList() { + const dateList = [ + { name: '浠婂ぉ绋嶆櫄', key: 'today' }, + { name: '鏄庡ぉ', key: 'tomorrow' }, + { name: '鏈懆绋嶆櫄', key: 'thisWeek' }, + { name: '鏈懆鏈�', key: 'thisWeekend' }, + { name: '涓嬪懆', key: 'nextWeek' }, + ]; + + const now = new Date(); + + const dateArray = dateList.map((item) => { + let date = new Date(); // 鍒濆鍖栧綋鍓嶆棩鏈� + let dayOfWeekString = ''; // 鍒濆鍖栨槦鏈熷嚑 + let timeString = ''; // 鍒濆鍖栨椂闂� + + switch (item.key) { + case 'today': + // 浠婂ぉ绋嶆櫄鏄粖澶╃殑 16:00 + date.setHours(16, 0, 0, 0); + break; + case 'tomorrow': + // 鏄庡ぉ 08:00 + date.setDate(now.getDate() + 1); + date.setHours(8, 0, 0, 0); + break; + case 'thisWeek': + // 鏈懆绋嶆櫄锛氬懆浜� 08:00 + const dayOfWeek = now.getDay(); + const daysUntilFriday = (5 - dayOfWeek + 7) % 7; // 璁$畻鍒板懆浜旂殑澶╂暟 + date.setDate(now.getDate() + daysUntilFriday); + date.setHours(8, 0, 0, 0); + break; + case 'thisWeekend': + // 鏈懆鏈細鍛ㄦ棩 08:00 + const daysUntilSunday = (7 - now.getDay()) % 7; // 璁$畻鍒板懆鏃ョ殑澶╂暟 + date.setDate(now.getDate() + daysUntilSunday); + date.setHours(8, 0, 0, 0); + break; + case 'nextWeek': + // 涓嬪懆涓� 08:00 + const daysUntilNextMonday = ((1 - now.getDay() + 7) % 7) + 7; // 璁$畻鍒颁笅鍛ㄤ竴鐨勫ぉ鏁� + date.setDate(now.getDate() + daysUntilNextMonday); + date.setHours(8, 0, 0, 0); + break; + default: + break; + } + + // 鑾峰彇鏄熸湡鍑犵殑瀛楃涓茶〃绀� + dayOfWeekString = date.toLocaleDateString('zh-CN', { weekday: 'long' }); + + // 鑾峰彇浠呮椂闂撮儴鍒� + timeString = date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); + + // 杩斿洖鏃ユ湡銆佹槦鏈熷嚑鍜屾椂闂翠俊鎭� + return { + name: item.name, + key: item.key, + date: date, + dayOfWeek: dayOfWeekString, + time: timeString, // 淇濆瓨浠呮椂闂撮儴鍒� + }; + }); + + return dateArray; + } + const dateList = processDateList(); + const customTimeDropdownOpen = ref(false); + function onSubmitCustomTime() { + customTimeDropdownOpen.value = true; + } + + const disabledDate = (currentDate) => { + return currentDate && currentDate < dayjs().startOf('day'); + }; + const cancelCustomTime = () => { + customTimeDropdownOpen.value = false; + dropdownOpen.value = true; + }; + const submitCustomTime = () => { + formRef.value.validate().then((valid) => { + if (valid) { + customTimeDropdownOpen.value = false; + const date = form.date ? dayjs(form.date).format('YYYY-MM-DD') : ''; + const time = form.time ? dayjs(form.time).format('HH:mm') : ''; + const data = { + handleTime: date + ' ' + time, + docCode: props.docCodeS, + }; + pushUpdateHandle(data); + } else { + return false; + } + }); + }; + + const toggleCustomTime = () => { + customTimeDropdownOpen.value = !customTimeDropdownOpen.value; + dropdownOpen.value = false; + }; + // 琛ㄥ崟鏁版嵁 + const form = reactive({ + date: '', + time: '', + }); + + import { useMessage } from '@/hooks/web/useMessage'; + import { formatToDateDay } from '@/utils/dateUtil'; + + function fnSelectDate(item) { + dropdownOpen.value = false; + const date = formatToDateDay(new Date(item.date)); + const data = { + handleTime: date, + docCode: props.docCodeS, + }; + pushUpdateHandle(data); + } + function pushUpdateHandle(data) { + updateHandleAPi(data) + .then((res) => { + if (res.code == 0) { + createMessage.success(res.msg); + // getDataList({}); + } + }) + .catch((err) => {}); + } +</script> + +<style scoped> + .custom-dropdown { + position: absolute; + z-index: 9900; + width: 250px; + padding: 10px; + border: 1px solid #0505050f; + background-color: #fff; + } + + .custom-dropdown :deep(.ant-menu-vertical) { + border-inline-end: 0; + } + + .custom-dropdown :deep(.ant-menu-item) { + height: 32px; + line-height: 32px; + } +</style> diff --git a/src/views/email/components/LeftMenu/EmailMenuItem.vue b/src/views/email/components/LeftMenu/EmailMenuItem.vue new file mode 100644 index 0000000..11eaa7d --- /dev/null +++ b/src/views/email/components/LeftMenu/EmailMenuItem.vue @@ -0,0 +1,44 @@ +<template> + <div> + <a-sub-menu v-if="item.children.length > 0" :key="item.key"> + <template #title> + <div class="my-display"> + <span>{{ item.title }}</span> + </div> + </template> + <a-menu-item + v-for="child in item.children" + :key="child.key" + @click="$emit('click', child)" + style="display: flex; justify-content: space-between; padding-left: 28px" + > + <div class="my-display"> + <span>{{ child.title }}</span> + <span v-if="child.total > 0">{{ child.total }}</span> + </div> + </a-menu-item> + </a-sub-menu> + <a-menu-item v-else :key="item.key" @click.stop="$emit('click', item)"> + <div class="my-display"> + <span>{{ item.title }}</span> + <span v-if="item.total > 0" class="my-left">{{ item.total }}</span> + </div> + </a-menu-item> + </div> +</template> + +<script setup> + defineProps(['item']); +</script> + +<style scoped> + .my-display { + display: flex; + align-items: center; + justify-content: space-between; + } + + .my-left { + margin-left: 5px; + } +</style> diff --git a/src/views/email/components/LeftMenu/MyMenu.vue b/src/views/email/components/LeftMenu/MyMenu.vue new file mode 100644 index 0000000..930a8f2 --- /dev/null +++ b/src/views/email/components/LeftMenu/MyMenu.vue @@ -0,0 +1,121 @@ +<template> + <div> + <a-sub-menu :key="item.key" :open="isOpen"> + <template #title> + <div> + <div v-if="!isSearch" class="my-display"> + <span>{{ item.title }}</span> + <div class="handle-icon"> + <SearchOutlined v-if="item.key == 'moduleBelowA'" @click.stop="handleSearch(item)" /> + <MoreOutlined /> + </div> + </div> + <a-select + v-if="isSearch" + ref="inputRef" + v-model:value="value" + style="width: 100%" + show-search + placeholder="璇疯緭鍏ユ枃浠跺す鍏抽敭瀛楁悳绱�" + :options="options" + :field-names="{ label: 'title', value: 'key' }" + @change="handleChange" + @blur="blurSearch" + ></a-select> + </div> + </template> + <template v-for="child in item.children" :key="child.key"> + <template v-if="!child.children || child.children.length === 0"> + <a-menu-item :key="child.key" @click="$emit('click', child)"> + <span>{{ child.title }}</span> + <span v-if="child.total > 0" class="my-left">{{ child.total }}</span> + </a-menu-item> + </template> + <template v-else> + <!-- 閫掑綊璋冪敤鑷韩 --> + <MyMenu :item="child" @click="$emit('click', child)" /> + </template> + </template> + </a-sub-menu> + </div> +</template> + +<script lang="ts" setup> + import { defineProps, defineEmits, ref, computed } from 'vue'; + import { SearchOutlined, MoreOutlined } from '@ant-design/icons-vue'; + + interface MenuItem { + title: string; + key: string; + total: number; + children?: MenuItem[]; + } + + const props = defineProps<{ item: MenuItem }>(); + const emit = defineEmits(); + const options = ref([]); + const isSearch = ref(false); + const inputRef = ref(); + const value = ref(); + + // 璁$畻灞炴�э紝鍒ゆ柇褰撳墠鑿滃崟椤规槸鍚﹀簲灞曞紑 + const isOpen = computed(() => { + return true; // 榛樿涓哄睍寮�鐘舵�� + }); + + function handleSearch(item: MenuItem) { + console.log('handleSearch', item); + options.value = flattenMenuItems(item.children); + console.log('options', options.value); + isSearch.value = true; + setTimeout(() => { + inputRef.value.focus(); + }, 100); + emit('handleSearch', item); + } + + function blurSearch() { + isSearch.value = false; + } + + const handleChange = (value: string) => { + console.log(`selected ${value}`, isSearch.value); + isSearch.value = false; + emit('updateSelectedKeys', value); + }; + + const flattenMenuItems = (items: MenuItem[]): MenuItem[] => { + let result: MenuItem[] = []; + const recursiveFlatten = (items: MenuItem[]) => { + for (const item of items) { + result.push({ ...item, children: undefined }); // 涓嶄繚鐣� children + if (item.children && item.children.length > 0) { + recursiveFlatten(item.children); + } + } + }; + recursiveFlatten(items); + return result; + }; +</script> + +<style scoped> + .my-display { + display: flex; + align-items: center; + justify-content: space-between; + } + + .my-left { + margin-left: 5px; + } + + .handle-icon { + display: none; + font-size: 16px; + } + + .my-display:hover .handle-icon { + display: block; + } +</style> diff --git a/src/views/email/components/LeftMenu/index.vue b/src/views/email/components/LeftMenu/index.vue index ad5e369..24483b3 100644 --- a/src/views/email/components/LeftMenu/index.vue +++ b/src/views/email/components/LeftMenu/index.vue @@ -27,22 +27,34 @@ v-model:open-keys="openKeys" v-model:selected-keys="selectedKeys" mode="inline" - :popupClassName="popupClassName" > <template v-for="item in menuItems" :key="item.key"> - <render-menu-item :item="item" @click="handleClick" /> + <EmailMenuItem :item="item" @click="handleClick" /> </template> </a-menu> <a-divider /> <a-menu id="email-left-nav2" - v-model:open-keys="openKeys" - v-model:selected-keys="selectedKeys" + v-model:open-keys="openKeys2" + v-model:selected-keys="selectedKeys2" mode="inline" - :popupClassName="popupClassName" > <template v-for="item in menuItems2" :key="item.key"> - <render-menu-item :item="item" @click="handleClick" /> + <template v-if="!item.children"> + <a-menu-item :key="item.key" @click="$emit('click', item)"> + <div class="my-display"> + <span>{{ item.title }}</span> + <span v-if="item.total > 0" class="my-left">{{ item.total }}</span> + </div> + </a-menu-item> + </template> + <template v-else> + <MyMenu + :item="item" + @click.stop="handleClickMyMenu(item)" + @updateSelectedKeys="updateSelectedKeys" + /> + </template> </template> </a-menu> </div> @@ -51,112 +63,178 @@ </template> <script lang="ts" setup> -import { ref, onMounted, computed } from 'vue'; -import { PageWrapper } from '@/components/Page'; -import { MailOutlined, UserOutlined } from '@ant-design/icons-vue'; -import { getEmailModuleApi, getEmailModuleBelowApi } from '@/api/email/userList'; -import { useRouter } from 'vue-router'; + import { ref, onMounted } from 'vue'; + import { PageWrapper } from '@/components/Page'; + import { DingdingOutlined, MailOutlined, UserOutlined } from '@ant-design/icons-vue'; + import { getEmailModuleApi, getEmailModuleBelowApi } from '@/api/email/userList'; + import { useRouter } from 'vue-router'; + import MyMenu from './MyMenu.vue'; + import EmailMenuItem from './EmailMenuItem.vue'; -interface MenuItem { - key: string; - title: string; - total?: number; - children?: MenuItem[]; -} + const selectedKeys = ref<string[]>(['Index']); + const openKeys = ref<string[]>(['Inbox']); + const openKeys2 = ref(['0049LM']); + const selectedKeys2 = ref([]); -const selectedKeys = ref<string[]>(['Index']); -const openKeys = ref<string[]>(['Inbox']); -const items = ref<MenuItem[]>([]); -const items2 = ref<MenuItem[]>([]); + const menuItems = ref([]); + const menuItems2 = ref([]); -const fetchEmailModules = async () => { - try { - const [res, res2] = await Promise.all([getEmailModuleApi(), getEmailModuleBelowApi()]); - items.value = convertRoutesToMenuItems(res.data); - items2.value = convertRoutesToMenuItems2(res2.data); - } catch (error) { - console.error('鑾峰彇閭妯″潡澶辫触:', error); + const fetchEmailModules = async () => { + try { + const [res, res2] = await Promise.all([getEmailModuleApi(), getEmailModuleBelowApi()]); + menuItems.value = convertRoutesToMenuItems(res.data); + menuItems2.value = convertRoutesToMenuItems2(res2.data); + console.log('menuItems:', menuItems2.value); + } catch (error) { + console.error('鑾峰彇閭妯″潡澶辫触:', error); + } + }; + + const convertRoutesToMenuItems = (routes: any[]) => { + return routes + .map((route) => ({ + key: route.key, + title: route.mailName, + total: route.total, + children: route.children ? convertRoutesToMenuItems(route.children) : undefined, + })) + .filter(Boolean); + }; + + const convertRoutesToMenuItems2 = (routes: any[], path) => { + return routes + .map((route) => ({ + key: route.key, + title: route.name, + total: route.number, + path: path ? path : route.key, + children: route.list + ? convertRoutesToMenuItems2(route.list, path ? path : route.key) + : undefined, + })) + .filter(Boolean); + }; + + onMounted(fetchEmailModules); + + const routesConfig = { + InboxPage1: '/email/index', + receiver: '/email/Inbox/list', + sender: '/email/outbox/list', + IndexPage1: '/email/outbox', + moduleBelowA: '/email/index', + moduleBelowB: '/email/index', + moduleBelowC: '/email/index', + }; + + const router = useRouter(); + // const handleClick = (item: any) => { + // selectedKeys2.value = []; + // debugger + // const routePath = + // routesConfig[item.key] || router.getRoutes().find((r) => r.name === item.key)?.path; + // if (routePath) { + + // router.push(routePath); + // } else { + // console.warn(`Unknown key: ${item.key}`); + // } + // }; + const handleClick = (e: any) => { + selectedKeys2.value = []; + + let matched = false; + const route = router.getRoutes() || []; + route.forEach((item) => { + if (item.name === e.key) { + router.push(item.path); + matched = true; + return; // 璺冲嚭褰撳墠寰幆 + } + + if (!matched) { + switch (e.key) { + case 'InboxPage1': + router.push(routesConfig[e.key]); + matched = true; + return; // 璺冲嚭褰撳墠寰幆 + case 'receiver': + router.push(`${routesConfig[e.key]}?${e.title}`); + matched = true; + return; // 璺冲嚭褰撳墠寰幆 + case 'sender': + router.push(`${routesConfig[e.key]}?${e.title}`); + matched = true; + return; // 璺冲嚭褰撳墠寰幆 + case 'IndexPage1': + router.push(`${routesConfig[e.key]}`); + matched = true; + return; // 璺冲嚭褰撳墠寰幆 + default: + // 澶勭悊榛樿鎯呭喌锛屼緥濡傝褰曟棩蹇楁垨鎶涘嚭璀﹀憡 + console.warn(`Unknown key: ${e.key}`); + } + } + }); + }; + + const updateSelectedKeys = (keys) => { + openKeys2.value = findParentKeys(menuItems2.value[0], keys); + selectedKeys2.value = [keys]; + }; + + function findParentKeys(data, targetKey, path = []) { + path.push(data.key); + if (data.key === targetKey) { + return path; + } + + for (const item of data.children || []) { + const result = findParentKeys(item, targetKey, path.slice()); + if (result) { + return result; + } + } + return null; } -}; - -const convertRoutesToMenuItems = (routes: any[]): MenuItem[] => { - return routes - .map(route => ({ - key: route.key, - title: route.mailName, - total: route.total, - children: route.children ? convertRoutesToMenuItems(route.children) : undefined, - })) - .filter(Boolean) as MenuItem[]; -}; - -const convertRoutesToMenuItems2 = (routes: any[]): MenuItem[] => { - return routes - .map(route => ({ - key: route.key, - title: route.name, - total: route.number, - children: route.list ? convertRoutesToMenuItems2(route.list) : undefined, - })) - .filter(Boolean) as MenuItem[]; -}; - -onMounted(fetchEmailModules); - -const routesConfig = { - InboxPage1: '/email/index', - receiver: '/email/Inbox/list', - sender: '/email/outbox/list', - IndexPage1: '/email/outbox', -}; - -const router = useRouter(); -const handleClick = (item: MenuItem) => { - const routePath = routesConfig[item.key] || router.getRoutes().find(r => r.name === item.key)?.path; - if (routePath) { - router.push(routePath); - } else { - console.warn(`Unknown key: ${item.key}`); - } -}; - -const popupClassName = { - display: 'flex', - 'align-items': 'center', - 'justify-content': 'space-between', -}; + const handleClickMyMenu = (item: any) => { + selectedKeys.value = []; + // console.log('handleClickMyMenu:', item); + router.push({ path: '/email/folder' }); + console.log('updateSelectedKeys111111112333:', openKeys2.value); + }; </script> <style lang="less" scoped> -.header-container { - height: 15vh; - padding: 20px 40px; - text-align: center; -} + .header-container { + height: 15vh; + padding: 20px 40px; + text-align: center; + } -.button-group { - display: flex; - justify-content: space-around; -} + .button-group { + display: flex; + justify-content: space-around; + } -.write-button { - width: 100%; - margin-top: 10px; - padding: 0 30px; -} + .write-button { + width: 100%; + margin-top: 10px; + padding: 0 30px; + } -.menu-container { - height: 70vh; - overflow: auto; -} + .menu-container { + height: 75vh; + overflow: auto; + } -.my-display { - display: flex; - align-items: center; - justify-content: space-between; -} + .my-display { + display: flex; + align-items: center; + justify-content: space-between; + } -.my-left { - margin-left: 5px; -} + .my-left { + margin-left: 5px; + } </style> diff --git a/src/views/email/components/LeftNav.vue b/src/views/email/components/LeftNav.vue deleted file mode 100644 index ebb5161..0000000 --- a/src/views/email/components/LeftNav.vue +++ /dev/null @@ -1,181 +0,0 @@ -锘�<template> - <PageWrapper dense contentFullHeight fixedHeight> - <div> - <div style="height: 15vh; padding: 20px 40px; text-align: center"> - <span style="display: flex; justify-content: space-around"> - <a-button shape="circle" size="large"> - <MailOutlined /> - </a-button> - <a-button shape="circle" size="large"> - <UserOutlined /> - </a-button> - </span> - <a-button - style="width: 100%; margin-top: 10px; padding: 0 30px" - type="primary" - size="large" - shape="round" - @click="$router.push('/email/edit')" - > - 鍐欎俊 - </a-button> - </div> - - <div class="menu-container"> - <a-menu - id="email-left-nav" - v-model:open-keys="openKeys" - v-model:selected-keys="selectedKeys" - mode="inline" - :popupClassName="popupClassName" - > - <template v-for="item in items" :key="item.key"> - <a-sub-menu v-if="item.children" :key="item.key"> - <template #title> - <div class="my-display"> - <span>{{ item.title }}</span> - <!-- <span class="my-left" v-if="item.total > 0">{{ item.total }}</span> --> - </div> - </template> - <a-menu-item - style="display: flex; justify-content: space-between; padding-left: 28px" - v-for="(child, index) in item.children" - :key="index" - @click="handleClick(child)" - > - <div class="my-display"> - <span>{{ child.title }}</span> - <span v-if="item.total > 0"> {{ child.total }}</span> - </div> - </a-menu-item> - </a-sub-menu> - <a-menu-item v-else :key="item.key" @click="handleClick(item)"> - <div class="my-display"> - <span>{{ item.title }}</span> - <span class="my-left" v-if="item.total > 0">{{ item.total }}</span> - </div> - </a-menu-item> - </template> - </a-menu> - </div> - </div> - </PageWrapper> -</template> - -<script lang="ts" setup> - import { ref, onMounted } from 'vue'; - import { PageWrapper } from '@/components/Page'; - import { MailOutlined, UserOutlined } from '@ant-design/icons-vue'; - import { getEmailModuleApi } from '@/api/email/userList'; - import { useRouter } from 'vue-router'; - - const selectedKeys = ref<string[]>(['Index']); - const openKeys = ref<string[]>(['Inbox']); - const items = ref([]); // 瀹氫箟 items 绫诲瀷 - - const fnGetEmailModule = async () => { - try { - const res = await getEmailModuleApi(); - items.value = convertRoutesToMenuItems(res.data); - } catch (error) { - console.error('鑾峰彇閭妯″潡澶辫触:', error); // 澶勭悊閿欒 - } - }; - - const convertRoutesToMenuItems = (routes: any[]) => { - return routes - .map((route) => { - if (route.children && route.children.length > 0) { - return { - key: route.key, - title: route.mailName, - total: route.total, - children: convertRoutesToMenuItems(route.children), - }; - } - if (!route.hideMenu) { - return { - key: route.key, - title: route.mailName, - total: route.total, - }; - } - }) - .filter(Boolean); // 杩囨护鎺� undefined - }; - - // 鐢熷懡鍛ㄦ湡閽╁瓙锛屽姞杞借彍鍗曟暟鎹� - onMounted(() => { - fnGetEmailModule(); - }); - const routesConfig = { - InboxPage1: '/email/index', - receiver: '/email/Inbox/list', - sender: '/email/outbox', - IndexPage1:'/email/outbox' - }; - // 鐐瑰嚮浜嬩欢澶勭悊 - const router = useRouter(); - const handleClick = (e: any) => { - console.log(e, '------4'); - let matched = false; - const route = router.getRoutes() || []; - route.forEach((item) => { - if (item.name === e.key) { - router.push(item.path); - matched = true; - return; // 璺冲嚭褰撳墠寰幆 - } - - if (!matched) { - switch (e.key) { - case 'InboxPage1': - router.push(routesConfig[e.key]); - matched = true; - return; // 璺冲嚭褰撳墠寰幆 - case 'receiver': - router.push(`${routesConfig[e.key]}?${e.title}`); - matched = true; - return; // 璺冲嚭褰撳墠寰幆 - case 'sender': - router.push(`${routesConfig[e.key]}?${e.title}`); - matched = true; - return; // 璺冲嚭褰撳墠寰幆 - case 'IndexPage1': - router.push(`${routesConfig[e.key]}`); - matched = true; - return; // 璺冲嚭褰撳墠寰幆 - default: - // 澶勭悊榛樿鎯呭喌锛屼緥濡傝褰曟棩蹇楁垨鎶涘嚭璀﹀憡 - console.warn(`Unknown key: ${e.key}`); - } - } - }); - }; - const popupClassName = { - display: 'flex', - 'align-items': 'center', - 'justify-content': 'space-between', - }; -</script> - -<style lang="less" scoped> - :deep(.ant-menu-inline) { - border-inline-end: 0 solid rgb(5 5 5 / 6%) !important; - } - - .menu-container { - height: 70vh; - overflow: auto; - } - - .my-display { - display: flex; - align-items: center; - justify-content: space-between; - } - - .my-left { - margin-left: 5px; - } -</style> diff --git a/src/views/email/components/ListPage/TooltipAndDropdown .vue b/src/views/email/components/ListPage/TooltipAndDropdown .vue index d179450..059b322 100644 --- a/src/views/email/components/ListPage/TooltipAndDropdown .vue +++ b/src/views/email/components/ListPage/TooltipAndDropdown .vue @@ -43,7 +43,7 @@ <a-divider style="margin: 5px 0" /> <div class="date p-1"> <a-popover - trigger="click" + :trigger="trigger" title="鑷畾涔夋椂闂�" v-model:open="customTimeDropdownOpen" @confirm="onSubmitCustomTime" @@ -167,6 +167,10 @@ initialTooltipOpen: Boolean, // Tooltip 鍒濆鎵撳紑鐘舵�� row: Object, // 褰撳墠琛屽璞� docCodeS: Array, + trigger: { + type: String, + default: 'click', + }, }); const emit = defineEmits(['updateHandleTime', 'completeAction', 'customTimeSubmit', 'tagRow']); @@ -183,6 +187,7 @@ handleTime: date, docCode: props.docCodeS, }; + pushUpdateHandle(data); } function pushUpdateHandle(data) { @@ -226,27 +231,26 @@ const onComplete = () => { const data = { - handleTime: '', docCode: props.docCodeS, }; pushUpdateHandle(data); }; const formRef = ref(); const submitCustomTime = () => { - formRef.value.validate().then((valid) => { - if (valid) { - customTimeDropdownOpen.value = false; - const date = form.date ? dayjs(form.date).format('YYYY-MM-DD') : ''; - const time = form.time ? dayjs(form.time).format('HH:mm') : ''; - const data = { - handleTime: date + ' ' + time, - docCode: props.docCodeS, - }; - pushUpdateHandle(data); - } else { - return false; - } - }); + formRef.value.validate().then((valid) => { + if (valid) { + customTimeDropdownOpen.value = false; + const date = form.date ? dayjs(form.date).format('YYYY-MM-DD') : ''; + const time = form.time ? dayjs(form.time).format('HH:mm') : ''; + const data = { + handleTime: date + ' ' + time, + docCode: props.docCodeS, + }; + pushUpdateHandle(data); + } else { + return false; + } + }); }; const cancelCustomTime = () => { diff --git a/src/views/email/components/ListPage/drawerDetail.vue b/src/views/email/components/ListPage/drawerDetail.vue index 862ff7c..e4a020f 100644 --- a/src/views/email/components/ListPage/drawerDetail.vue +++ b/src/views/email/components/ListPage/drawerDetail.vue @@ -53,9 +53,19 @@ <span style="margin-right: 10px; font-size: 16px"> <PushpinOutlined /> </span> + <a-tag + v-if="tableRowData.attachmentPath?.length > 0" + style="margin-right: 10px; font-size: 12px" + > + <PaperClipOutlined style="margin-right: 4px; color: #ffac00" />{{ + tableRowData.attachmentPath?.length + }} + </a-tag> </div> <div class="right"> - <div class="tate">{{ formatToDateDay(tableRowData.receiveTime || tableRowData.updateTime) }}</div> + <div class="tate">{{ + formatToDateDay(tableRowData.receiveTime || tableRowData.updateTime) + }}</div> <div> <a-dropdown-button> <div v-if="!isDrafts"> @@ -142,6 +152,28 @@ <span>鏆傛湭鏌ヨ鍒拌瀹㈡埛鐨勫綋鍦版椂闂�</span> <!-- <span>2024-06-08 22:22</span> --> </div> + + <div v-if="tableRowData.attachmentPath?.length>0" class="p-2 f-z-14" style="margin-top: 10px"> + <div style="display: flex; align-items: center"> + <span style="margin-right: 10px">{{ + `闄勪欢(${tableRowData.attachmentPath?.length})` + }}</span> + <a type="link" @click="fnAllDownload">鍏ㄩ儴涓嬭浇</a> + </div> + <div class="my-d-f" style="width: 100%; margin-top: 10px"> + <div + class="file-item my-d-f" + v-for="item in tableRowData.attachmentPath" + :key="item" + > + <div class="icon"><FileExcelOutlined /></div> + <div class="name" @click="fnPreview(item)">{{ item.name }}</div> + <div class="size"> {{ item.size + 'k' }} </div> + <div class="download" @click="fnDownload(item)"><CloudDownloadOutlined /></div> + <!-- <div class="delete">鍒犻櫎</div> --> + </div> + </div> + </div> <div class="ct"> <div v-if="tableRowData.content"> <TinymcePw ref="TinymcePwRef" v-model="tableRowData.content" /> @@ -177,6 +209,9 @@ CopyOutlined, UserOutlined, RollbackOutlined, + FileExcelOutlined, + CloudDownloadOutlined, + PaperClipOutlined, } from '@ant-design/icons-vue'; import pageHeadLeft from './pageHeadLeft.vue'; import UserTips from '@/views/email/components/userTips/index.vue'; @@ -322,6 +357,40 @@ function replyEmail(row, type) { router.push({ path: '/email/edit', query: { docCode: row.docCode, type: type } }); } + + const fnPreview = (item) => { + if (!item) { + console.error('Invalid file or response'); + return; + } + + // // 鑾峰彇鏂囦欢绫诲瀷锛堥�氳繃鏂囦欢鎵╁睍鍚嶆垨 MIME 绫诲瀷锛� + const fileExt = item.fileType; // 鑾峰彇鏂囦欢鎵╁睍鍚� + const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExt); // 鍒ゆ柇鏄惁涓哄浘鐗� + + if (isImage) { + // 鐩存帴鎵撳紑鍥剧墖 + window.open(item.url, '_blank'); + } else { + const iframeSrc = `https://view.officeapps.live.com/op/view.aspx?src=${item.url}`; + window.open(iframeSrc, '_blank'); + } + }; + + const fnDownload = (item) => { + const link = document.createElement('a'); + link.href = item.url; + link.download = item.name; // 鎻愬彇鏂囦欢鍚� + document.body.appendChild(link); // 灏嗛摼鎺ユ坊鍔犲埌 DOM + link.click(); // 妯℃嫙鐐瑰嚮涓嬭浇 + document.body.removeChild(link); // 涓嬭浇鍚庣Щ闄ら摼鎺� + }; + + const fnAllDownload = () => { + const urls = tableRowData.value.attachmentPath.map((item) => item.url); + const url = urls.join(','); + fnDownload(url); + }; </script> <style scoped lang="less"> @@ -445,4 +514,45 @@ .f-z-14 { font-size: 12px; } + + .file-item { + width: 240px; + margin-right: 10px; + padding: 10px; + border-radius: 4px; + background-color: #f0f0f0; + + & .icon { + width: 20px; + margin-right: 5px; + } + + & .name { + flex: 1; + margin-right: 5px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + & .download { + display: none; + width: 20px; + } + } + + .file-item:hover .name { + transition: 0.3s; + color: #0960bd; + } + + .file-item:hover .size { + display: none; + transition: 0.3s; + } + + .file-item:hover .download { + display: block; + transition: 0.3s; + } </style> diff --git a/src/views/email/components/ListPage/list.vue b/src/views/email/components/ListPage/list.vue index 3ac5a6e..ccaa7fb 100644 --- a/src/views/email/components/ListPage/list.vue +++ b/src/views/email/components/ListPage/list.vue @@ -23,10 +23,11 @@ </div> <div class="right p-3" - >鍏�<span style="padding: 0 5px">{{page.total}}</span>灏� + >鍏�<span style="padding: 0 5px">{{ page.total }}</span + >灏� <a-pagination v-model:current="pageCurrent" - v-model:page-size='page.limit' + v-model:page-size="page.limit" simple :total="page.total" style="margin-left: 10px" @@ -61,27 +62,39 @@ </a-switch> </div> </div> - <div v-if="checked" style="height: 30px;" class="left-bt p-3"> + <div v-if="checked" style="height: 30px" class="left-bt p-3"> 宸查�夋嫨姝ら〉闈笂鎵�鏈� 20 灏侀偖浠� 锛� 閫夋嫨鍏ㄩ儴 335 灏侀偖浠� </div> - <div class="p-4" style="height: 90%; overflow: hidden"> - <a-tabs v-model:activeKey="activeKey"> - <a-tab-pane - v-for="item in tabsList" - :key="item.key" - :tab="`${item.label}${item.num ? '(' + item.num + ')' : ''}`" - style="height: 200px" - > - <Table - ref="tableRef" - :page="pageCurrent" - :pageList="newList" - :isDrafts="isDrafts" - @selectAll="fnSelectAll" - @updateSelectAll="updateSelectAll" - /> - </a-tab-pane> - </a-tabs> + <div class="p-4" style="height: 90%; overflow: auto"> + <div v-if="isTabs"> + <a-tabs v-model:activeKey="activeKey"> + <a-tab-pane + v-for="item in tabsList" + :key="item.key" + :tab="`${item.label}${item.num ? '(' + item.num + ')' : ''}`" + style="height: 200px" + > + <Table + ref="tableRef" + :page="pageCurrent" + :pageList="newList" + :isDrafts="isDrafts" + @selectAll="fnSelectAll" + @updateSelectAll="updateSelectAll" + /> + </a-tab-pane> + </a-tabs> + </div> + <div v-else> + <Table + ref="tableRef" + :page="pageCurrent" + :pageList="newList" + :isDrafts="isDrafts" + @selectAll="fnSelectAll" + @updateSelectAll="updateSelectAll" + /> + </div> </div> </div> </PageWrapper> @@ -98,7 +111,7 @@ import pageHeadLeft from './pageHeadLeft.vue'; import { PageWrapper } from '@/components/Page'; - import { ref, watch, defineProps, defineEmits, computed, reactive, onMounted,inject } from 'vue'; + import { ref, watch, defineProps, defineEmits, computed, reactive, onMounted, inject } from 'vue'; // 瀹氫箟灞炴�� interface Props { @@ -106,8 +119,30 @@ pageData?: any; mailType?: number; isDrafts?: boolean; + isTabs?: boolean; } - const props = defineProps<Props>(); + const props = defineProps({ + isTabs: { + type: Boolean, + default: true, + }, + pageList: { + type: Array, + default: () => [], + }, + pageData: { + type: Object, + default: () => {}, + }, + mailType: { + type: Number, + default: 0, + }, + isDrafts: { + type: Boolean, + default: false, + }, + }); const newList = ref([]); const selectAllRow = ref([]); watch( @@ -117,7 +152,7 @@ }, ); -const page = computed(() => props.pageData); + const page = computed(() => props.pageData); const checked = computed(() => selectAllRow.value.length > 0); const pageCurrent = ref(1); const tableRef = ref(); @@ -189,9 +224,8 @@ }); const getDataList = inject('getDataList'); - function handlePageChange(page, pageSize){ - getDataList(page) - + function handlePageChange(page, pageSize) { + getDataList(page); } </script> <style scoped lang="less"> @@ -212,7 +246,7 @@ align-items: center; justify-content: space-flex-start; width: 100%; - height: 100%;; + height: 100%; & .icon { margin-right: 15px; diff --git a/src/views/email/components/ListPage/table.vue b/src/views/email/components/ListPage/table.vue index 9e107d4..141caa4 100644 --- a/src/views/email/components/ListPage/table.vue +++ b/src/views/email/components/ListPage/table.vue @@ -11,10 +11,10 @@ size="small" min-height="40px" :row-config="{ isCurrent: true, isHover: true }" - :menu-config="tableMenu" - @menu-click="contextMenuClickEvent" + :menu-config="{ enabled: true }" @cell-click="cellClickEvent" @checkbox-change="selectChangeEvent" + @cell-menu="onCellContextMenu" > <vxe-column type="checkbox" width="30"></vxe-column> <vxe-column field="sender" title="鍙戜欢浜�" data-index="sender" min-width="300px"> @@ -78,7 +78,7 @@ <a-button type="link" size="small">寰�鏉ラ偖浠�</a-button></div > </template> - <div class="title-dot" :class="fnIsItHighlighted(row) ? 'title-dot-color' : ''"> + <div :class="fnIsItHighlighted(row) ? 'title-dot-color' : ''"> <span style="font-weight: 700">{{ row.senderName }}</span ><span style="padding: 0 8px">|</span> <span style="font-weight: 500">{{ row.sender }}</span> @@ -87,6 +87,26 @@ </div> </template> </vxe-column> + <vxe-column + show-overflow + field="icon" + title="琛ㄩ" + data-index="icon" + width="100" + align="right" + > + <template #default="{ row }"> + <a-tooltip placement="bottom"> + <template #title> + <span>鏈夐檮浠�</span> + </template> + <span v-show="row.attachmentList?.length > 0"> + <PaperClipOutlined /> + </span> + </a-tooltip> + </template> + </vxe-column> + <vxe-column show-overflow field="subject" @@ -110,7 +130,7 @@ <span style="display: flex; justify-content: space-around"> <span>{{ row.mailType !== 0 - ? formatToDateDay(row.receiveTime) + ? formatToDateDay(row.receiveTime || row.createTime) : formatToDateDay(row.createTime) }}</span> @@ -126,7 +146,13 @@ </span> </template> </vxe-column> - </vxe-table> </div + </vxe-table> + <ContextMenu + v-if="showMenu" + :style="menuStyle" + :selected-cell="selectedCell" + @close-menu="showMenu = false" + /> </div ></div> <div v-else style="display: flex; align-items: center; justify-content: center; height: 70vh"> @@ -151,6 +177,7 @@ PushpinOutlined, CopyOutlined, DownOutlined, + PaperClipOutlined, } from '@ant-design/icons-vue'; import { ref, watch, defineProps, defineEmits, computed, defineExpose, inject } from 'vue'; @@ -268,57 +295,63 @@ } // 鍙抽敭鑿滃崟 - const tableMenu = { - className: 'my-menus', - body: { - options: [ - [ - { - code: 'reply', - name: '鍥炲', - }, - { code: 'replyAll', name: '鍥炲鍏ㄩ儴' }, - { code: 'replyWithAttachment', name: '甯﹂檮浠跺洖澶�' }, - { code: 'replyAllWithAttachment', name: '甯﹂檮浠跺洖澶嶅叏閮�' }, - { code: 'forward', name: '杞彂' }, - { code: 'forwardAsAttachment', name: '浣滀负闄勪欢杞彂' }, - { code: 'distribute', name: '鍒嗗彂' }, - { code: 'setRemark', name: '璁剧疆澶囨敞' }, - ], - [ - { code: 'toHandle', name: '寰呭鐞�' }, - { code: 'markAsUnread', name: '鏍囦负鏈' }, - { code: 'labelAs', name: '鏍囨敞涓�' }, - ], - [ - { code: 'newRule', name: '鏂板缓鏀跺彂浠惰鍒�' }, - { code: 'moveTo', name: '绉诲姩鍒�' }, - ], - [ - { code: 'exportEmail', name: '瀵煎嚭閭欢' }, - { code: 'createFollowUp', name: '寤轰负瀹㈡埛璺熻繘' }, - { code: 'createSchedule', name: '鏂板缓鏃ョ▼' }, - ], - [ - { code: 'markAsSpam', name: '鏍囦负鍨冨溇閭欢' }, - { code: 'delete', name: '鍒犻櫎' }, - ], - ], - }, + import ContextMenu from '@/views/email/components/ContextMenu/index.vue'; + const showMenu = ref(false); + const menuStyle = ref({}); + const selectedCell = ref(null); + const onCellContextMenu = ({ + type, + row, + rowIndex, + $rowIndex, + column, + columnIndex, + $columnIndex, + $event, + }) => { + $event.preventDefault(); // 闃绘榛樿鐨勬祻瑙堝櫒鍙抽敭鑿滃崟? + selectedCell.value = { row, column, columnIndex }; // 淇濆瓨褰撳墠閫変腑鐨勫崟鍏冩牸鏁版嵁 + + // 璁$畻鑿滃崟鍒濆浣嶇疆 + let menuX = $event.clientX; + let menuY = $event.clientY; + + // 鑾峰彇鑿滃崟鐨勫搴﹀拰楂樺害 + const menuWidth = 200; // 鍋囪鑿滃崟瀹藉害涓�200px锛屽彲浠ユ牴鎹疄闄呮儏鍐佃皟鏁� + const menuHeight = 800; // 鍋囪鑿滃崟楂樺害涓�150px锛屽彲浠ユ牴鎹疄闄呮儏鍐佃皟鏁� + // 鑾峰彇绐楀彛瀹藉害鍜岄珮搴� + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + // 妫�鏌ヨ彍鍗曟槸鍚﹁秴鍑虹獥鍙g殑鍙宠竟鐣岋紝濡傛灉鏄紝鍒欏悜宸﹁皟鏁� + if (menuX + menuWidth > windowWidth) { + menuX = windowWidth - menuWidth; + } + + // 妫�鏌ヨ彍鍗曟槸鍚﹁秴鍑虹獥鍙g殑涓嬭竟鐣岋紝濡傛灉鏄紝鍒欏悜涓婅皟鏁� + if (menuY + menuHeight > windowHeight) { + menuY = windowHeight - menuHeight; + } + menuStyle.value = { + position: 'fixed', + top: `${menuY}px`, + left: `${menuX}px`, + }; + showMenu.value = true; }; - function contextMenuClickEvent({ menu, row, column }) { - switch (menu.code) { - case 'copy': - if (row && column) { - } - break; - default: - } - } + // 鐩戝惉鍏ㄥ眬鐐瑰嚮浜嬩欢锛岀偣鍑婚〉闈㈠叾浠栧湴鏂瑰叧闂彍鍗� + document.addEventListener('click', () => { + showMenu.value = false; + }); + const vxeTableRef = ref(); function fnIsItHighlighted(row) { - return row.readFlag && props.isDrafts; + if (props.isDrafts) { + return row.readFlag && props.isDrafts; + } else { + return row.readFlag; + } } function fnSelectAll(is) { try { @@ -462,9 +495,11 @@ .span-title { width: 100%; + height: 30px; padding: 5px; color: #000; font-weight: 700; + line-height: 30px; text-align: left; } diff --git a/src/views/email/folder/index.vue b/src/views/email/folder/index.vue new file mode 100644 index 0000000..af20b1c --- /dev/null +++ b/src/views/email/folder/index.vue @@ -0,0 +1,61 @@ +<template> + <div> + <a-spin :spinning="loading" class="p-1" style="height: 100%;"> + <PageIndex :pageList="pageList" :mailType="1" :pageData="pageData" :isTabs="false"> </PageIndex> + </a-spin> + </div> +</template> + +<script lang="ts" setup> + name: 'Inbox'; + import { ref, onMounted, computed, provide } from 'vue'; + import PageIndex from '@/views/email/components/ListPage/list.vue'; + import { useRoute } from 'vue-router'; + + const route = useRoute(); + // 鑾峰彇褰撳墠瀹屾暣 URL + const routerId = computed(() => { + try { + const url = window.location.href; + if (!url) { + throw new Error('Invalid URL'); + } + const basePart = url.split('?')[1]; + console.log(basePart, 'basePart'); + return basePart; + } catch (error) { + console.error('Error processing URL:', error); + // 杩斿洖榛樿鍊兼垨绌哄瓧绗︿覆 + return ''; + } + }); + import { getMailListApi } from '@/api/email/userList'; + const pageList = ref([]); + const loading = ref(false); + const pageData = ref({ + page: 1, + limit: 20, + total: 0, + }); + function getDataList(page:1) { + loading.value = true; + getMailListApi({ mail: routerId.value, mailType: 1,page }) + .then((res) => { + loading.value = false; + if (res.code == 0) { + pageList.value = res.data.list; + pageData.value.total = res.data.total; + } + }) + .catch(() => { + loading.value = false; + }); + } + + + provide('getDataList', getDataList); + onMounted(() => { + getDataList(); + }); +</script> +<style scoped lang="less"></style> diff --git a/src/views/email/label/index.vue b/src/views/email/label/index.vue new file mode 100644 index 0000000..af20b1c --- /dev/null +++ b/src/views/email/label/index.vue @@ -0,0 +1,61 @@ +<template> + <div> + <a-spin :spinning="loading" class="p-1" style="height: 100%;"> + <PageIndex :pageList="pageList" :mailType="1" :pageData="pageData" :isTabs="false"> </PageIndex> + </a-spin> + </div> +</template> + +<script lang="ts" setup> + name: 'Inbox'; + import { ref, onMounted, computed, provide } from 'vue'; + import PageIndex from '@/views/email/components/ListPage/list.vue'; + import { useRoute } from 'vue-router'; + + const route = useRoute(); + // 鑾峰彇褰撳墠瀹屾暣 URL + const routerId = computed(() => { + try { + const url = window.location.href; + if (!url) { + throw new Error('Invalid URL'); + } + const basePart = url.split('?')[1]; + console.log(basePart, 'basePart'); + return basePart; + } catch (error) { + console.error('Error processing URL:', error); + // 杩斿洖榛樿鍊兼垨绌哄瓧绗︿覆 + return ''; + } + }); + import { getMailListApi } from '@/api/email/userList'; + const pageList = ref([]); + const loading = ref(false); + const pageData = ref({ + page: 1, + limit: 20, + total: 0, + }); + function getDataList(page:1) { + loading.value = true; + getMailListApi({ mail: routerId.value, mailType: 1,page }) + .then((res) => { + loading.value = false; + if (res.code == 0) { + pageList.value = res.data.list; + pageData.value.total = res.data.total; + } + }) + .catch(() => { + loading.value = false; + }); + } + + + provide('getDataList', getDataList); + onMounted(() => { + getDataList(); + }); +</script> +<style scoped lang="less"></style> diff --git a/vite.config.ts b/vite.config.ts index fe823e1..8ba3d87 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -31,7 +31,7 @@ // secure: false }, '/upload': { - target: 'http://localhost:3300/upload', + target: 'http://yingchen.onbus.cn:9010/attachment/uploadAttachmentV2.do', changeOrigin: true, ws: true, rewrite: (path) => path.replace(new RegExp(`^/upload`), ''), -- Gitblit v1.8.0