From 2af71bcf522c485ea005184c977986374a7dcc4a Mon Sep 17 00:00:00 2001 From: Sanakey <714737083@qq.com> Date: 星期六, 28 九月 2024 09:47:10 +0800 Subject: [PATCH] Merge branch 'feng-v1-editor' of http://192.168.100.20:9090/r/onbus_crm into li-v1 --- src/utils/dateUtil.ts | 10 src/views/email/UnreadEmail/index.vue | 59 src/views/email/Utils/index.vue | 31 src/views/email/HandlingEmailsOnBehalfOfOthers/index.vue | 60 src/components/CButton/index.vue | 249 + src/router/routes/index.ts | 6 src/views/email/Utils/blacklist.vue | 242 + src/components/Tinymce/src/index.vue | 540 ++++ src/views/email/Drafts/index.vue | 58 src/views/email/Utils/quickText.vue | 202 + src/views/email/Utils/convention.vue | 152 + src/views/email/components/ListPage/TooltipAndDropdown .vue | 304 ++ uno.config.ts | 1 src/router/routes/modules/email.ts | 122 src/components/ColorPicker/index.vue | 160 + src/views/email/components/emailDetail.vue | 11 src/views/email/components/ListPage/pageHeadLeft.vue | 166 + src/views/email/Edit/index.vue | 577 ++++ src/layouts/default/email.vue | 91 src/views/email/UnreadEmail/user.vue | 18 src/layouts/page/email.vue | 86 src/store/modules/useCollapseStore.ts | 19 src/views/email/preview/index..vue | 86 src/store/modules/emailRouter.ts | 30 src/views/email/Utils/folder.vue | 200 + types/axios.d.ts | 4 src/views/email/index.vue | 196 + src/components/Tinymce/src/Editor.vue | 91 src/views/email/outbox/index.vue | 61 src/components/Tinymce/index.ts | 4 src/views/email/components/ListPage/table.vue | 476 +++ src/utils/tool/inedx.ts | 5 src/views/email/components/ListPage/drawerDetail.vue | 420 +++ src/router/constant.ts | 1 src/utils/http/axios/index.ts | 11 src/views/email/HandlingEmailsOnBehalfOfOthers/components/list.vue | 220 + src/views/email/Utils/label.vue | 260 ++ src/views/email/components/ListPage/list.vue | 234 + src/layouts/default/content/email.vue | 57 src/main.ts | 1 src/api/email/model/userListModel.ts | 34 /dev/null | 261 -- src/views/email/Utils/mailboxManagement.vue | 689 +++++ src/api/email/userList.ts | 278 ++ src/views/email/components/LeftNav.vue | 268 + src/router/routes/modules/preview.ts | 31 src/views/email/Inbox/index.vue | 61 src/views/email/components/SelectUser/index.vue | 367 ++ src/App.vue | 19 49 files changed, 7,045 insertions(+), 484 deletions(-) diff --git a/src/App.vue b/src/App.vue index 56b077d..401b213 100644 --- a/src/App.vue +++ b/src/App.vue @@ -17,20 +17,19 @@ import { computed, ref } from 'vue'; import { getCurrentInstance } from 'vue'; -console.log(getCurrentInstance,'=====-'); // 鑾峰彇褰撳墠瀹炰緥 -const instance = getCurrentInstance(); -const proxy = instance?.proxy; + const instance = getCurrentInstance(); + const proxy = instance?.proxy; const setCookie = () => { -// 鍏堟鏌roxy鏄惁瀛樺湪锛屽啀杩涜鎿嶄綔 -if (proxy && proxy.$cookies) { - proxy.$cookies.remove('JSESSIONID'); - proxy.$cookies.set('JSESSIONID', 'C1371DD46D2B480CC815F329B2833C99.jvm_59_9010', '1d'); -} else { - console.error('proxy瀵硅薄鏈垵濮嬪寲鎴栦笉鍖呭惈$cookies灞炴��'); -} + // 鍏堟鏌roxy鏄惁瀛樺湪锛屽啀杩涜鎿嶄綔 + if (proxy && proxy.$cookies) { + proxy.$cookies.remove('JSESSIONID'); + proxy.$cookies.set('JSESSIONID', '23542F948D2C450599CF5850631B432D.jvm_59_9010', '1d'); + } else { + console.error('proxy瀵硅薄鏈垵濮嬪寲鎴栦笉鍖呭惈$cookies灞炴��'); + } }; setCookie(); // support Multi-language diff --git a/src/api/email/model/userListModel.ts b/src/api/email/model/userListModel.ts index ac9cd19..8c64bf9 100644 --- a/src/api/email/model/userListModel.ts +++ b/src/api/email/model/userListModel.ts @@ -1,11 +1,7 @@ - - -import { BasicPageParams,BasicFetchResult } from '@/api/model/baseModel'; +import { BasicPageParams, BasicFetchResult } from '@/api/model/baseModel'; export type tableParams = Partial<BasicPageParams>; - -export interface GetUserListItem -{ +export interface GetUserListItem { id: string; name: string; email: string; @@ -15,15 +11,29 @@ } export interface sendingMailModel { sender: string; - receiver: string; - cc: string; - bcc: string; + receiver: Object; + cc: Object; + bcc: Object; subject: string; content: string; - fileUNID: string; + attachmentList: string; docCode: string; - } +export interface addAccountParams { + email: string; + password: string; + aliasEmail: string; + biSyncFlag: boolean; + proxyFlag: boolean; + receiveProtocol: string; + receiveSSL: boolean; + receivePort: string; + receiveHost: string; + smtpSSL: boolean; + smtpPort: string; + smtpHost: string; + invalid: string; +} -export type GetUserListModel = BasicFetchResult<GetUserListItem>; \ No newline at end of file +export type GetUserListModel = BasicFetchResult<GetUserListItem>; diff --git a/src/api/email/userList.ts b/src/api/email/userList.ts index 98cacfe..675a1ee 100644 --- a/src/api/email/userList.ts +++ b/src/api/email/userList.ts @@ -1,25 +1,281 @@ - import { defHttp } from '@/utils/http/axios'; import { - GetUserListModel,tableParams,sendingMailModel + GetUserListModel, + tableParams, + sendingMailModel, + addAccountParams, } from './model/userListModel'; enum Api { GET_USER_LIST = '/crm/email/getUserList', SENDING_MAIL_DO = '/crm/mail/sendingMail.do', UPDATE_READ = '/crm/mail/updateRead.do', - RECEIVE = '/crm/mail/receive.do' - + RECEIVE = '/crm/mail/receive.do', + SAVE_MAIL_DRAFTS = '/crm/mail/saveMailDrafts.do', + ADD_ACCOUNT = '/crm/mail/account/addAccount.do', + GET_ACCOUNT = '/crm/mail/account/getAccount.do', + UPDATE_ACCOUNT = '/crm/mail/account/updateAccount.do', + DELETE_ACCOUNT = '/crm/mail/account/deleteAccount.do', + GET_ACCOUNT_LIST = '/crm/mail/account/getAccountList.do', + IS_EMAIL_VALID = '/crm/mail/account/isEmailValid.do', + GET_EMAIL_MODULE = '/crm/mail/getEmailModule.do', + ADD_SIGNATURE = '/crm/mail/signature/addSignature.do', + UPDATE_SIGNATURE = '/crm/mail/signature/updateSignature.do', + GET_SIGNATURE = '/crm/mail/signature/getSignature.do', + ADD_QUICK_TEXT = '/crm/mail/quickText/addQuickText.do', + UPDATE_QUICK_TEXT = '/crm/mail/quickText/updateQuickText.do', + GET_QUICK_TEXT = '/crm/mail/quickText/getQuickText.do', + DELETE_QUICK_TEXT = '/crm/mail/quickText/deleteQuickText.do', + GET_MAIL_LIST = '/crm/mail/getMailList.do', + GET_USER_INFO = '/crm/base/userInfo.do', + GET_MAIL_INFO = '/crm/mail/getMailInfo.do', + EMAIL_LIST = '/crm/base/emailList.do', + UPDATE_HANDLE = '/crm/mail/updateHandle.do', + DELETE_EMAIL = '/crm/mail/deleteEmail.do', + SET_QUICK_REPLY = '/crm/mail/setQuickReply.do', + CONTACT_LIST = '/crm/clues/contactList.do', + GET_HANDLE_MAIL_LIST = '/crm/mail/getHandleMailList.do', + ADD_FOLDER = '/crm/mail/folder/addFolder.do', + UPDATE_FOLDER = '/crm/mail/folder/updateFolder.do', + DELETE_FOLDER = '/crm/mail/folder/deleteFolder.do', + GET_FOLDER = '/crm/mail/folder/getFolder.do', + GET_ROW_ID = '/getRowid.do', + ADD_TAG = '/crm/mail/blacklist/getBlackList.do', + UPDATE_TAG = '/crm/mail/tag/updateTag.do', + DELETE_TAG = '/crm/mail/tag/deleteTag.do', + GET_TAG = '/crm/mail/tag/getTagList.do', + ADD_BLACKLIST = '/crm/mail/blacklist/addBlackList.do', + UPDATE_BLACKLIST = '/crm/mail/blacklist/updateBlackList.do', + DELETE_BLACKLIST = '/crm/mail/blacklist/deleteBlackList.do', + GET_BLACKLIST = '/crm/mail/blacklist/getBlackList.do', } +// 鑾峰彇閭欢璺敱鍒楄〃 +export const getEmailModuleApi = () => defHttp.get({ url: Api.GET_EMAIL_MODULE }); -export const receiveApi = (params) => - defHttp.get({ url: Api.RECEIVE,params}); -// 鍙戦�侀偖浠� +export const receiveApi = (params) => defHttp.get({ url: Api.RECEIVE, params }); //鑾峰彇鑱旂郴浜哄垪琛� export const getUserListApi = (params: tableParams) => - defHttp.post<GetUserListModel[]>({ url: Api.GET_USER_LIST, params}); + defHttp.post<GetUserListModel[]>({ url: Api.GET_USER_LIST, params }); // 鍙戦�侀偖浠� export const sendingMailApi = (params) => - defHttp.post<sendingMailModel[]>({ url: Api.SENDING_MAIL_DO, params}); -export const updateReadApi = (params) => - defHttp.post<{}>({ url: Api.UPDATE_READ , params}); \ No newline at end of file + defHttp.post<sendingMailModel[]>({ url: Api.SENDING_MAIL_DO, params }); +export const updateReadApi = (params) => defHttp.post<{}>({ url: Api.UPDATE_READ, params }); +// 鑽夌绠� +export const saveMailDraftsApi = (params) => + defHttp.post<{}>({ url: Api.SAVE_MAIL_DRAFTS, params }); + +// 娣诲姞閭閰嶇疆 +export const addAccountApi = (params: addAccountParams) => + defHttp.post<{}>({ url: Api.ADD_ACCOUNT, params }); +// 鑾峰彇閭閰嶇疆 +export const getAccountApi = (params) => defHttp.get<{}>({ url: Api.GET_ACCOUNT, params }); +// 淇敼閭閰嶇疆 +export const updateAccountApi = (params) => defHttp.post<{}>({ url: Api.UPDATE_ACCOUNT, params }); +// 鍒犻櫎閭閰嶇疆 +export const deleteAccountApi = (params) => defHttp.post<{}>({ url: Api.DELETE_ACCOUNT, params }); + +// 鑾峰彇閭鍒楄〃 +export const getAccountListApi = () => defHttp.get<{}>({ url: Api.GET_ACCOUNT_LIST }); +// 妫�娴嬮偖绠� +export const isEmailValidApi = (params) => + defHttp.get({ + url: Api.IS_EMAIL_VALID, + params, + }); + +// 娣诲姞绛惧悕 +export const addSignatureApi = (params) => + defHttp.post({ + url: Api.ADD_SIGNATURE, + params, + }); +// 淇敼绛惧悕 +export const updateSignatureApi = (params) => + defHttp.post({ + url: Api.UPDATE_SIGNATURE, + params, + }); + +// 鏌ヨ绛惧悕 +export const getSignatureApi = (params) => + defHttp.get({ + url: Api.GET_SIGNATURE, + params, + }); + +// 鏌ヨ蹇�熸枃鏈� +export const getQuickTextApi = (params) => + defHttp.get({ + url: Api.GET_QUICK_TEXT, + params, + }); +// 鏂板蹇�熸枃鏈� +export const addQuickTextApi = (params) => + defHttp.post({ + url: Api.ADD_QUICK_TEXT, + params, + }); +// 鏇存柊蹇�熸枃鏈� +export const updateQuickTextApi = (params) => + defHttp.post({ + url: Api.UPDATE_QUICK_TEXT, + params, + }); +// 鍒犻櫎蹇�熸枃鏈� +export const deleteQuickTextApi = (params) => + defHttp.post({ + url: Api.DELETE_QUICK_TEXT, + params, + }); + +// 鑾峰彇閭欢鍒楄〃 +export const getMailListApi = (params) => + defHttp.get({ + url: Api.GET_MAIL_LIST, + params, + }); + +// 鑾峰彇浼佷笟鍚屼簨鍒楄〃 +export const getUserInfoApi = (params) => + defHttp.post({ + url: Api.GET_USER_INFO, + params, + }); + +// 鑾峰彇閭欢璇︽儏 +export const getMailInfoApi = (params) => + defHttp.get({ + url: Api.GET_MAIL_INFO, + params, + }); + +// 鑾峰彇妯$硦鎼滅储浜哄憳 +export const emailListAPi = (params) => + defHttp.post({ + url: Api.EMAIL_LIST, + params, + }); +// 璁剧疆瀹屾垚鏃堕棿 +export const updateHandleAPi = (params) => + defHttp.get({ + url: Api.UPDATE_HANDLE, + params, + }); +// 鍒犻櫎閭欢 +export const deleteEmailAPi = (params) => + defHttp.post({ + url: Api.DELETE_EMAIL, + params, + }); + +// 蹇�熷洖澶� +export const setQuickReplyAPi = (params) => + defHttp.post({ + url: Api.SET_QUICK_REPLY, + params, + }); + +// 鑾峰彇绾跨储鑱旂郴浜哄垪琛� +export const contactListAPi = (params) => + defHttp.post({ + url: Api.CONTACT_LIST, + params, + }); + +// 鑾峰彇绾跨储鑱旂郴浜哄垪琛� +export const getHandleMailListApi = (params) => + defHttp.get({ + url: Api.GET_HANDLE_MAIL_LIST, + params, + }); + +// 鏂板鏂囦欢澶� +export const addFolderApi = (params) => + defHttp.post({ + url: Api.ADD_FOLDER, + params, + }); + +// 淇敼鏂囦欢澶� +export const updateFolderApi = (params) => + defHttp.post({ + url: Api.UPDATE_FOLDER, + params, + }); + +// 鍒犻櫎鏂囦欢澶� +export const deleteFolderApi = (params) => + defHttp.post({ + url: Api.DELETE_FOLDER, + params, + }); + +// 鏌ヨ鏂囦欢澶� +export const getFolderApi = (params) => + defHttp.get({ + url: Api.GET_FOLDER, + params, + }); + +// 鑾峰彇rowId +export const getRowIdApi = () => + defHttp.get({ + url: Api.GET_ROW_ID, + }); + +// 鏂板鏍囩 +export const addTagApi = (params) => + defHttp.post({ + url: Api.ADD_TAG, + params, + }); + +// 淇敼鏍囩 +export const updateTagApi = (params) => + defHttp.post({ + url: Api.UPDATE_TAG, + params, + }); + +// 鍒犻櫎鏍囩 +export const deleteTagApi = (params) => + defHttp.post({ + url: Api.DELETE_TAG, + params, + }); + +// 鏌ヨ鏍囩 +export const getTagApi = (params) => + defHttp.get({ + url: Api.GET_TAG, + params, + }); + + +// 鏂板榛戝悕鍗� +export const addBlackListApi = (params) => + defHttp.post({ + url: Api.ADD_BLACKLIST, + params, + }); + +// 淇敼榛戝悕鍗� +export const updateBlackListApi = (params) => + defHttp.post({ + url: Api.UPDATE_BLACKLIST, + params, + }); + +// 鍒犻櫎榛戝悕鍗� +export const deleteBlackListApi = (params) => + defHttp.post({ + url: Api.DELETE_BLACKLIST, + params, + }); + +// 鏌ヨ榛戝悕鍗� +export const getBlackListApi = (params) => + defHttp.get({ + url: Api.GET_BLACKLIST, + params, + }); diff --git a/src/components/CButton/index.vue b/src/components/CButton/index.vue new file mode 100644 index 0000000..9c21a14 --- /dev/null +++ b/src/components/CButton/index.vue @@ -0,0 +1,249 @@ +<!--鎸夐挳棰滆壊缁勪欢---> +<template> + <a-button :type="customType" :class="customClass" :size="customSize" :disabled="disabled"> + <template v-if="iconType" #icon> + <component :is="iconType" /> + </template> + <slot /> + </a-button> +</template> +<script> + import { defineComponent, ref, watch } from 'vue'; + export default defineComponent({ + name: 'CButtonIndex', + props: { + type: { type: String, default: '' }, + size: { type: String, default: '' }, + icon: { type: String, default: '' }, + disabled: { type: Boolean, default: false }, + permission: { type: [String, Boolean], default: true }, + }, + setup(props) { + const customClass = ref('c-button-primary'); + const customType = ref(''); + const customSize = ref('middle'); + const iconType = ref(''); + watch( + () => props.type, + (v) => { + switch (v) { + case 'warning': + customClass.value = 'c-button-warning'; + customType.value = 'default'; + break; + case 'error': + customClass.value = 'c-button-error'; + customType.value = 'default'; + break; + case 'success': + customClass.value = 'c-button-success'; + customType.value = 'default'; + break; + case 'primary': + customClass.value = 'c-button-primary'; + customType.value = 'primary'; + break; + case 'cyan': + customClass.value = 'c-button-cyan'; + customType.value = 'default'; + break; + case 'black': + customClass.value = 'c-button-black'; + customType.value = 'default'; + break; + case 'purple': + customClass.value = 'c-button-purple'; + customType.value = 'default'; + break; + case 'text': + customClass.value = ''; + customType.value = 'text'; + break; + case 'link': + customClass.value = ''; + customType.value = 'link'; + break; + default: + customClass.value = ''; + customType.value = 'default'; + break; + } + }, + { immediate: true }, + ); + watch( + () => props.size, + (v) => { + customSize.value = !v ? 'middle' : v; + }, + { immediate: true }, + ); + watch( + () => props.icon, + (v) => { + iconType.value = v; + }, + { immediate: true }, + ); + watch( + () => props, + () => {}, + { immediate: true }, + ); + return { + customClass, + customType, + customSize, + iconType, + }; + }, + }); +</script> +<style scoped> + .c-button-primary { + color: #fff; + background-color: #2db7f5; + border-color: #2db7f5; + } + .c-button-primary:hover { + color: #fff; + background-color: #3dc1fc; + border-color: #2db7f5; + } + .c-button-primary[disabled], + .c-button-primary[disabled]:hover, + .c-button-primary[disabled]:focus, + .c-button-primary[disabled]:active { + color: rgba(0, 0, 0, 0.25); + background: #f5f5f5; + border-color: #d9d9d9; + text-shadow: none; + box-shadow: none; + } + + .c-button-warning { + color: #fff; + background-color: #ff9900; + border-color: #ff9900; + } + .c-button-warning:hover { + color: #fff; + background-color: #fcac35; + border-color: #ff9900; + } + .c-button-warning[disabled], + .c-button-warning[disabled]:hover, + .c-button-warning[disabled]:focus, + .c-button-warning[disabled]:active { + color: rgba(0, 0, 0, 0.25); + background: #f5f5f5; + border-color: #d9d9d9; + text-shadow: none; + box-shadow: none; + } + + .c-button-error { + color: #fff; + background-color: #ff3300; + border-color: #ff3300; + } + .c-button-error:hover { + color: #fff; + background-color: #fc653f; + border-color: #ff3300; + } + .c-button-error[disabled], + .c-button-error[disabled]:hover, + .c-button-error[disabled]:focus, + .c-button-error[disabled]:active { + color: rgba(0, 0, 0, 0.25); + background: #f5f5f5; + border-color: #d9d9d9; + text-shadow: none; + box-shadow: none; + } + + .c-button-success { + color: #fff; + background-color: #00cc66; + border-color: #00cc66; + } + .c-button-success:hover { + color: #fff; + background-color: #03e071; + border-color: #00cc66; + } + .c-button-success[disabled], + .c-button-success[disabled]:hover, + .c-button-success[disabled]:focus, + .c-button-success[disabled]:active { + color: rgba(0, 0, 0, 0.25); + background: #f5f5f5; + border-color: #d9d9d9; + text-shadow: none; + box-shadow: none; + } + + .c-button-cyan { + color: #fff; + background-color: #04c1e1; + border-color: #04c1e1; + } + .c-button-cyan:hover { + color: #fff; + background-color: #0ad5f8; + border-color: #04c1e1; + } + .c-button-cyan[disabled], + .c-button-cyan[disabled]:hover, + .c-button-cyan[disabled]:focus, + .c-button-cyan[disabled]:active { + color: rgba(0, 0, 0, 0.25); + background: #f5f5f5; + border-color: #d9d9d9; + text-shadow: none; + box-shadow: none; + } + + .c-button-black { + color: #fff; + background-color: #131313; + border-color: #131313; + } + .c-button-black:hover { + color: #fff; + background-color: #313131; + border-color: #131313; + } + .c-button-black[disabled], + .c-button-black[disabled]:hover, + .c-button-black[disabled]:focus, + .c-button-black[disabled]:active { + color: rgba(0, 0, 0, 0.25); + background: #f5f5f5; + border-color: #d9d9d9; + text-shadow: none; + box-shadow: none; + } + + .c-button-purple { + color: #fff; + background-color: #b500fe; + border-color: #b500fe; + } + .c-button-purple:hover { + color: #fff; + background-color: #c951fa; + border-color: #b500fe; + } + .c-button-purple[disabled], + .c-button-purple[disabled]:hover, + .c-button-purple[disabled]:focus, + .c-button-purple[disabled]:active { + color: rgba(0, 0, 0, 0.25); + background: #f5f5f5; + border-color: #d9d9d9; + text-shadow: none; + box-shadow: none; + } +</style> diff --git a/src/components/ColorPicker/index.vue b/src/components/ColorPicker/index.vue new file mode 100644 index 0000000..2bffb93 --- /dev/null +++ b/src/components/ColorPicker/index.vue @@ -0,0 +1,160 @@ +<template> + <div class="my-radio-group" v-if="type == 1"> + <a-radio-group v-model:value="localValue" button-style="solid" size="small" name="color"> + <a-radio-button + v-for="item in colors" + :key="item.type_id" + class="mr-5px" + :value="item.color" + :style="{ backgroundColor: item.color, borderColor: item.color }" + > + <span :class="localValue === item.color ? 'c-white' : 'c-white select-none op0'">鉁�</span> + </a-radio-button> + </a-radio-group> + </div> + <div v-else> + <a-dropdown :trigger="['click']"> + <div @click.prevent> + <div style="display: flex; align-items: center"> + <div class="bookmark" :style="{ backgroundColor: localValue }"></div> + <DownOutlined /> + </div> + </div> + <template #overlay> + <a-menu class="my-radio-group" style="width: 154px"> + <a-menu-item key="0"> + <a-radio-group + v-model:value="localValue" + button-style="solid" + size="small" + name="color" + > + <a-radio-button + v-for="item in colors" + :key="item.type_id" + class="mr-5px" + :value="item.color" + :style="{ backgroundColor: item.color, borderColor: item.color }" + > + <span :class="localValue === item.color ? 'c-white' : 'c-white select-none op0'" + >鉁�</span + > + </a-radio-button> + </a-radio-group> + </a-menu-item> + </a-menu> + </template> + </a-dropdown> + </div> +</template> + +<script lang="ts" setup> + import { ref, watch, defineProps, defineEmits } from 'vue'; + import { DownOutlined } from '@ant-design/icons-vue'; + // 瀹氫箟 props锛屾帴鏀剁埗缁勪欢浼犻�掔殑 modelValue + const props = defineProps({ + modelValue: { + type: String, + default:'#000000', + required: true, + }, + // 1鏅�氾紝2閫夋嫨鍣ㄦā寮� + type: { + type: Number, + default: 0, + required: true, + }, + }); + + // 瀹氫箟 emits锛岀敤浜庡弻鍚戠粦瀹� + const emit = defineEmits(['update:modelValue','change']); + + // 棰滆壊閫夐」 + const colors = ref([ + { type_id: 1, color: '#000000', name: '榛�' }, + { type_id: 2, color: '#bc5959', name: '鏆楃孩' }, + { type_id: 3, color: '#d87538', name: '姗欑孩' }, + { type_id: 4, color: '#209890', name: '闈掔豢' }, // 琚�変腑鐨勯鑹� + { type_id: 5, color: '#4b679d', name: '娣辫摑' }, + { type_id: 6, color: '#595dbf', name: '钃濈传' }, + { type_id: 7, color: '#333333', name: '娣辩伆' }, + { type_id: 8, color: '#e43e3e', name: '绾�' }, + { type_id: 9, color: '#eb9955', name: '娴呮' }, + { type_id: 10, color: '#61bc81', name: '娴呯豢' }, + { type_id: 11, color: '#5d89e9', name: '娴呰摑' }, + { type_id: 12, color: '#8d54bd', name: '绱�' }, + { type_id: 13, color: '#7b8291', name: '钃濈伆' }, + { type_id: 14, color: '#ee7b7b', name: '绮夌孩' }, + { type_id: 15, color: '#e2ad28', name: '閲戦粍' }, + { type_id: 16, color: '#80c463', name: '鑽夌豢' }, + { type_id: 17, color: '#4aa8eb', name: '澶╄摑' }, + { type_id: 18, color: '#acacac', name: '娴呯伆' }, + ]); + + // 瀹氫箟鏈湴鐘舵�侊紝浠ヤ究鐩戝惉鍜屾洿鏂� + const localValue = ref(props.modelValue); + + // 鐩戝惉 modelValue 鐨勫彉鍖栵紝骞跺悓姝ユ洿鏂版湰鍦板�� + watch( + () => props.modelValue, + (newValue) => { + localValue.value = newValue; + }, + ); + + // 褰撴湰鍦板�煎彂鐢熷彉鍖栨椂锛屽彂鍑� update:modelValue 浜嬩欢锛岄�氱煡鐖剁粍浠舵洿鏂� + watch(localValue, (newValue) => { + emit('update:modelValue', newValue); + emit('change', newValue); + }); +</script> + +<style lang="less" scoped> + .mr-5px { + margin-right: 5px; + } + + .c-white { + color: white; + } + + .select-none { + user-select: none; + } + + .op0 { + opacity: 0; + } + + .my-radio-group { + :deep(.ant-radio-group) .ant-radio-button-wrapper::before { + width: 0; + } + } + + .mr-5px { + margin-top: 4px; + border-radius: 4px; + } + + .bookmark { + display: inline-block; + position: relative; + width: 14px; + height: 18px; + margin-right: 5px; + border-radius: 2px 2px 0 0; /* Rounded top corners */ + } + + .bookmark::after { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 8px; + background-color: #fff; + clip-path: polygon(50% 60%, 0% 100%, 100% 100%); /* Triangle at the bottom */ + + } +</style> diff --git a/src/components/Tinymce/index.ts b/src/components/Tinymce/index.ts index dca44f1..57ce47a 100644 --- a/src/components/Tinymce/index.ts +++ b/src/components/Tinymce/index.ts @@ -1,4 +1,8 @@ import { withInstall } from '@/utils'; import tinymce from './src/Editor.vue'; +import tinymcePw from './src/index.vue'; + export const Tinymce = withInstall(tinymce); +export const TinymcePw = withInstall(tinymcePw); + diff --git a/src/components/Tinymce/src/Editor.vue b/src/components/Tinymce/src/Editor.vue index 0090041..53ed7c9 100644 --- a/src/components/Tinymce/src/Editor.vue +++ b/src/components/Tinymce/src/Editor.vue @@ -9,6 +9,7 @@ <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" @@ -21,38 +22,40 @@ <template #iconRender><PaperClipOutlined /></template> <template #itemRender="{ file, fileList, actions }"> <a-space class="ant-upload-list-picture-card"> - <span style="display: flex"> + <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 > + <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'> + <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 > + <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 style="position: absolute; left: 78px"> + <div :class="fileListTemp.length > 0 ? 'my-upload-list' : ''"> <div style="display: flex"> <ImgUpload :fullscreen="fullscreen" @uploading="handleImageUploading" @done="handleDone" - v-if="showImageUpload" + v-if="isImg" v-show="editorRef" :title="'鍥剧墖'" :disabled="disabled" :accept="'.jpg,.jpeg,.gif,.png,.webp'" /> - <a-button type="text" size="small"> + <a-button v-if="isText" type="text" size="small"> <SnippetsOutlined /> 蹇�熸枃鏈�</a-button > @@ -197,6 +200,18 @@ }, fontsize: { type: String, + }, + isElse: { + type: Boolean, + default: true, + }, + isText: { + type: Boolean, + default: true, + }, + isImg: { + type: Boolean, + default: true, }, }); @@ -382,8 +397,8 @@ emit('update:modelValue', content); const data = { content, - fileUNID:fileListTemp.value - } + fileUNID: fileListTemp.value, + }; emit('change', data); }); @@ -418,24 +433,24 @@ // 闄勪欢 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 - }, + // { + // 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); @@ -446,26 +461,25 @@ } else { item.editor = false; } - return item + return item; }); } function fnSaveRename(file, fileList) { fileListTemp.value = fileList.map((item) => { if (file.uid == item.uid) { - item.name = item.tempName; + item.name = item.tempName; item.editor = false; } - return item + return item; }); } - function fnOffRename(file, fileList){ + function fnOffRename(file, fileList) { fileListTemp.value = fileList.map((item) => { if (file.uid == item.uid) { item.editor = false; } - return item + return item; }); - } </script> <style lang="less" scope> @@ -481,9 +495,17 @@ } } + .my-upload-list { + // 杩囨浮 + position: absolute; + left: 78px; + transition: all 0.3s; + } + .tox-statusbar { display: flex; // position: absolute; + min-height: 40px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background: #f0f2f5; @@ -496,6 +518,7 @@ .ant-upload-list-picture { display: flex; + flex-wrap: wrap; } .ant-upload-list-picture-card { diff --git a/src/components/Tinymce/src/index.vue b/src/components/Tinymce/src/index.vue new file mode 100644 index 0000000..9015a80 --- /dev/null +++ b/src/components/Tinymce/src/index.vue @@ -0,0 +1,540 @@ +<template> + <div :class="prefixCls" :style="{ width: containerWidth }"> + <textarea + :id="tinymceId" + ref="elPwRef" + :style="{ visibility: 'hidden' }" + v-if="!initOptions.inline" + ></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' : ''"> + <div style="display: flex"> + <ImgUpload + :fullscreen="fullscreen" + @uploading="handleImageUploading" + @done="handleDone" + v-if="isImg" + v-show="editorRef" + :title="'鍥剧墖'" + :disabled="disabled" + :accept="'.jpg,.jpeg,.gif,.png,.webp'" + /> + <a-button v-if="isText" type="text" size="small"> + <SnippetsOutlined /> + 蹇�熸枃鏈�</a-button + > + </div> + </div> + </div> --> + </div> +</template> + +<script lang="ts" setup> + import type { Editor, RawEditorSettings } from 'tinymce'; + import { PaperClipOutlined, UploadOutlined, SnippetsOutlined } from '@ant-design/icons-vue'; + import tinymce from 'tinymce/tinymce'; + import 'tinymce/themes/silver'; + import 'tinymce/icons/default/icons'; + import 'tinymce/plugins/advlist'; + import 'tinymce/plugins/anchor'; + import 'tinymce/plugins/autolink'; + import 'tinymce/plugins/autosave'; + import 'tinymce/plugins/code'; + import 'tinymce/plugins/codesample'; + import 'tinymce/plugins/directionality'; + import 'tinymce/plugins/fullscreen'; + import 'tinymce/plugins/hr'; + import 'tinymce/plugins/insertdatetime'; + import 'tinymce/plugins/link'; + import 'tinymce/plugins/lists'; + import 'tinymce/plugins/media'; + import 'tinymce/plugins/nonbreaking'; + import 'tinymce/plugins/noneditable'; + import 'tinymce/plugins/pagebreak'; + import 'tinymce/plugins/paste'; + import 'tinymce/plugins/preview'; + import 'tinymce/plugins/print'; + import 'tinymce/plugins/save'; + import 'tinymce/plugins/searchreplace'; + import 'tinymce/plugins/spellchecker'; + import 'tinymce/plugins/tabfocus'; + // import 'tinymce/plugins/table'; + import 'tinymce/plugins/template'; + import 'tinymce/plugins/textpattern'; + import 'tinymce/plugins/visualblocks'; + import 'tinymce/plugins/visualchars'; + import 'tinymce/plugins/wordcount'; + + import 'tinymce/plugins/image'; + import 'tinymce/plugins/table'; + import 'tinymce/plugins/charmap'; + import 'tinymce/plugins/imagetools'; + import 'tinymce/plugins/help'; + import 'tinymce/plugins/emoticons'; + import 'tinymce/plugins/emoticons/js/emojis'; + // import 'tinymce/plugins/bdmap'; + // import 'tinymce/plugins/indent2em'; + import 'tinymce/plugins/autoresize'; + // import 'tinymce/plugins/formatpainter'; + // import 'tinymce/plugins/axupimgs'; + + // import 'tinymce/plugins/powerpaste'; + // import 'tinymce/plugins/casechange'; + import 'tinymce/plugins/importcss'; + // import 'tinymce/plugins/tinyddrive'; + // import 'tinymce/plugins/advcode'; + // import 'tinymce/plugins/mediaembed'; + import 'tinymce/plugins/toc'; + // import 'tinymce/plugins/checklist'; + // import 'tinymce/plugins/tinycespellchecker'; + // import 'tinymce/plugins/a11ychecker'; + // import 'tinymce/plugins/permanentpen'; + // import 'tinymce/plugins/pageembed'; + // import 'tinymce/plugins/tinycomments'; + // import 'tinymce/plugins/mentions'; + import 'tinymce/plugins/quickbars'; + // import 'tinymce/plugins/linkchecker'; + // import 'tinymce/plugins/advtable'; + // import 'tinymce/plugins/export'; + + import { + computed, + nextTick, + ref, + unref, + watch, + onDeactivated, + onBeforeUnmount, + PropType, + useAttrs, + } from 'vue'; + import ImgUpload from './ImgUpload.vue'; + import { + plugins as defaultPlugins, + toolbar as defaultToolbar, + toolbar_groups as defaultStyleFormats, + } from './tinymce'; + import { buildShortUUID } from '@/utils/uuid'; + import { bindHandlers } from './helper'; + import { onMountedOrActivated } from '@vben/hooks'; + import { useDesign } from '@/hooks/web/useDesign'; + import { isNumber } from '@/utils/is'; + import { useLocale } from '@/locales/useLocale'; + import { useAppStore } from '@/store/modules/app'; + + defineOptions({ name: 'Tinymce', inheritAttrs: false }); + + const props = defineProps({ + options: { + type: Object as PropType<Partial<RawEditorSettings>>, + default: () => ({}), + }, + value: { + type: String, + }, + + toolbar: { + type: Array as PropType<string[]>, + default: defaultToolbar, + }, + plugins: { + type: Array as PropType<string[]>, + default: defaultPlugins, + }, + toolbar_groups: { + type: Object as PropType<{}>, + default: defaultStyleFormats, + }, + modelValue: { + type: String, + }, + height: { + type: [Number, String] as PropType<string | number>, + required: false, + default: 400, + }, + width: { + type: [Number, String] as PropType<string | number>, + required: false, + default: 'auto', + }, + showImageUpload: { + type: Boolean, + default: true, + }, + fontsize: { + type: String, + }, + isElse: { + type: Boolean, + default: true, + }, + isText: { + type: Boolean, + default: true, + }, + isImg: { + type: Boolean, + default: true, + }, + }); + + const emit = defineEmits(['change', 'update:modelValue', 'inited', 'init-error']); + + const attrs = useAttrs(); + const editorRef = ref<Editor | null>(null); + const fullscreen = ref(false); + const tinymceId = ref<string>(buildShortUUID('tiny-vue-pw')); + const elPwRef = ref<HTMLElement | null>(null); + + const { prefixCls } = useDesign('tinymce-container'); + + const appStore = useAppStore(); + + const containerWidth = computed(() => { + const width = props.width; + if (isNumber(width)) { + return `${width}px`; + } + return width; + }); + + const skinName = computed(() => { + return appStore.getDarkMode === 'light' ? 'oxide' : 'oxide-dark'; + }); + + const langName = computed(() => { + const lang = useLocale().getLocale.value; + return ['zh_CN', ''].includes(lang) ? lang : 'zh_CN'; + }); + + const initOptions = computed((): RawEditorSettings => { + const { height, options, toolbar, plugins, toolbar_groups } = props; + + const publicPath = import.meta.env.VITE_PUBLIC_PATH || '/'; + return { + selector: `#${unref(tinymceId)}`, + height, + min_height: 450, + font_formats: + '寰蒋闆呴粦=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;鑻规灉鑻规柟=PingFang SC,Microsoft YaHei,sans-serif;瀹嬩綋=simsun,serif;浠垮畫浣�=FangSong,serif;榛戜綋=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats', + fontsize_formats: '10px 11px 12px 14px 16px 18px 24px 36px 48px 48px 56px 72px', + image_advtab: true, + importcss_append: true, // 鍏佽鏍峰紡鐢熸晥 + menubar: false, + branding: false, + elementpath: false, + toolbar: [], + // quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quickimage quicktable', + plugins, + language_url: publicPath + 'resource/tinymce/langs/' + langName.value + '.js', + language: langName.value, + default_link_target: '_blank', + link_title: false, + statusbar: false, + object_resizing: false, + auto_focus: true, //璁╃紪杈戝櫒鍔犺浇瀹屾垚鍚庤嚜鍔ㄨ幏寰楀厜鏍囩劍鐐� + autosave_ask_before_unload: true, + autosave_interval: '30s', + skin: skinName.value, + skin_url: publicPath + 'resource/tinymce/skins/ui/' + skinName.value, + content_css: publicPath + 'resource/tinymce/skins/ui/' + skinName.value + '/content.min.css', + ...options, + readonly: true, + setup: (editor: Editor) => { + editorRef.value = editor; + editor.on('init', (e) => initSetup(e)); + }, + }; + }); + + const disabled = computed(() => { + const { options } = props; + const getdDisabled = options && Reflect.get(options, 'readonly'); + const editor = unref(editorRef); + if (editor) { + editor.setMode(getdDisabled ? 'readonly' : 'design'); + } + return getdDisabled ?? false; + }); + + watch( + () => attrs.disabled, + () => { + const editor = unref(editorRef); + if (!editor) { + return; + } + editor.setMode(attrs.disabled ? 'readonly' : 'design'); + }, + ); + + onMountedOrActivated(() => { + if (!initOptions.value.inline) { + tinymceId.value = buildShortUUID('tiny-vue'); + } + nextTick(() => { + setTimeout(() => { + initEditor(); + }, 30); + }); + }); + + onBeforeUnmount(() => { + destory(); + }); + + onDeactivated(() => { + destory(); + }); + + function destory() { + if (tinymce !== null) { + tinymce?.remove?.(unref(initOptions).selector!); + } + } + + function initEditor() { + const el = unref(elPwRef); + if (el) { + el.style.visibility = ''; + } + tinymce + .init(unref(initOptions)) + .then((editor) => { + emit('inited', editor); + }) + .catch((err) => { + emit('init-error', err); + }); + } + + function initSetup(e) { + const editor = unref(editorRef); + if (!editor) { + return; + } + const value = props.modelValue || ''; + + editor.setContent(value); + bindModelHandlers(editor); + bindHandlers(e, attrs, unref(editorRef)); + } + + function setValue(editor: Record<string, any>, val?: string, prevVal?: string) { + if (!val) { + editor.setContent(''); + } + if ( + editor && + typeof val === 'string' && + val !== prevVal && + val !== editor.getContent({ format: attrs.outputFormat }) + ) { + editor.setContent(val); + } + + } + + function bindModelHandlers(editor: any) { + const modelEvents = attrs.modelEvents ? attrs.modelEvents : null; + const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents; + + watch( + () => props.modelValue, + (val, prevVal) => { + if(val){ + setValue(editor, val, prevVal); + } + }, + ); + + watch( + () => props.value, + (val, prevVal) => { + setValue(editor, val, prevVal); + }, + { + immediate: true, + }, + ); + + editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => { + const content = editor.getContent({ format: attrs.outputFormat }); + emit('update:modelValue', content); + const data = { + content, + fileUNID: fileListTemp.value, + }; + emit('change', data); + }); + + editor.on('FullscreenStateChanged', (e) => { + fullscreen.value = e.state; + }); + } + + function handleImageUploading(name: string) { + const editor = unref(editorRef); + if (!editor) { + return; + } + editor.execCommand('mceInsertContent', false, getUploadingImgName(name)); + const content = editor?.getContent() ?? ''; + setValue(editor, content); + } + + function handleDone(name: string, url: string) { + const editor = unref(editorRef); + if (!editor) { + return; + } + const content = editor?.getContent() ?? ''; + const val = content?.replace(getUploadingImgName(name), `<img src="${url}"/>`) ?? ''; + setValue(editor, val); + } + + function getUploadingImgName(name: string) { + return `[uploading:${name}]`; + } + + // 闄勪欢 + 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; + }); + } +</script> +<style lang="less" scope> + @prefix-cls: ~'@{namespace}-tinymce-container'; + + .@{prefix-cls} { + position: relative; + line-height: normal; + + textarea { + visibility: hidden; + z-index: -1; + } + } + + .my-upload-list { + // 杩囨浮 + position: absolute; + left: 78px; + transition: all 0.3s; + } + + .tox-statusbar { + display: flex; + // position: absolute; + min-height: 40px; + border-bottom-right-radius: 8px; + border-bottom-left-radius: 8px; + background: #f0f2f5; + } + + .icon-text { + margin-right: 10px; + font-size: 14px; + } + + .ant-upload-list-picture { + display: flex; + flex-wrap: wrap; + } + + .ant-upload-list-picture-card { + display: flex; + justify-content: space-between; + width: 24vw; + margin-right: 10px; + padding: 5px 10px; + background-color: #edf3f9; + + a { + padding: 0 5px; + font-size: 12px; + } + } +</style> diff --git a/src/layouts/default/content/email.vue b/src/layouts/default/content/email.vue new file mode 100644 index 0000000..851be20 --- /dev/null +++ b/src/layouts/default/content/email.vue @@ -0,0 +1,57 @@ +<template> + <div + :class="[prefixCls, getLayoutContentMode]" + v-loading="getOpenPageLoading && getPageLoading" + ref="content" + > + <PageLayout /> + <BackTop v-if="getUseOpenBackTop" :target="() => content" :visibilityHeight="100" /> + </div> +</template> +<script lang="ts" setup> + import { ref } from 'vue'; + import { BackTop } from 'ant-design-vue'; + + import PageLayout from '@/views/email/index.vue'; + import { useDesign } from '@/hooks/web/useDesign'; + import { useRootSetting } from '@/hooks/setting/useRootSetting'; + import { useTransitionSetting } from '@/hooks/setting/useTransitionSetting'; + import { useContentViewHeight } from './useContentViewHeight'; + + defineOptions({ name: 'LayoutContent' }); + + const { prefixCls } = useDesign('layout-content'); + const { getOpenPageLoading } = useTransitionSetting(); + const { getLayoutContentMode, getPageLoading, getUseOpenBackTop } = useRootSetting(); + + useContentViewHeight(); + + const content = ref(); +</script> +<style lang="less"> + @prefix-cls: ~'@{namespace}-layout-content'; + + .@{prefix-cls} { + display: flex; + position: relative; + flex-direction: column; + flex-grow: 1; + width: 100%; + height: 0; + min-height: 0; + overflow: auto; + + // begin: 涓嬮潰杩欏潡浠g爜 鍦ㄦ垜鐨勯」鐩墦鍖呭悗鍦ㄦ瘮杈冨鐨勫睆骞�(2K 31 瀵�)鏈夋樉绀� bug 鏈夊伓鍙戞�� 娓呯紦瀛橀娆¤繘鍏ヤ細鍑虹幇 , 鍒锋柊灏辨病浜�, 杩欓噷涓轰粈涔堣鎸囧畾瀹藉害 ? + &.fixed { + width: 1200px; + margin: 0 auto; + } + // end + + &-loading { + position: absolute; + z-index: @page-loading-z-index; + top: 200px; + } + } +</style> diff --git a/src/layouts/default/email.vue b/src/layouts/default/email.vue new file mode 100644 index 0000000..1802b5f --- /dev/null +++ b/src/layouts/default/email.vue @@ -0,0 +1,91 @@ +<template> + <Layout :class="prefixCls" v-bind="lockEvents"> + <LayoutFeatures /> + <LayoutHeader fixed v-if="getShowFullHeaderRef" /> + <Layout :class="[layoutClass, `${prefixCls}-out`]"> + <LayoutSideBar v-if="getShowSidebar || getIsMobile" /> + <Layout :class="`${prefixCls}-main`"> + <LayoutMultipleHeader /> + <LayoutContent /> + <LayoutFooter /> + </Layout> + </Layout> + </Layout> +</template> + +<script lang="ts" setup> + import { computed, unref } from 'vue'; + import { Layout } from 'ant-design-vue'; + import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'; + + import LayoutHeader from './header/index.vue'; + import LayoutContent from './content/email.vue'; + import LayoutSideBar from './sider/index.vue'; + import LayoutMultipleHeader from './header/MultipleHeader.vue'; + + import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'; + import { useMenuSetting } from '@/hooks/setting/useMenuSetting'; + import { useDesign } from '@/hooks/web/useDesign'; + import { useLockPage } from '@/hooks/web/useLockPage'; + + import { useAppInject } from '@/hooks/web/useAppInject'; + + import { useMultipleTabSetting } from '@/hooks/setting/useMultipleTabSetting'; + + const LayoutFeatures = createAsyncComponent(() => import('@/layouts/default/feature/index.vue')); + const LayoutFooter = createAsyncComponent(() => import('@/layouts/default/footer/index.vue')); + + defineOptions({ name: 'DefaultLayout' }); + + const { prefixCls } = useDesign('default-layout'); + const { getIsMobile } = useAppInject(); + const { getShowFullHeaderRef } = useHeaderSetting(); + const { getShowSidebar, getIsMixSidebar, getShowMenu } = useMenuSetting(); + const { getAutoCollapse } = useMultipleTabSetting(); + + // Create a lock screen monitor + const lockEvents = useLockPage(); + + const layoutClass = computed(() => { + let cls: string[] = ['ant-layout']; + if (unref(getIsMixSidebar) || unref(getShowMenu)) { + cls.push('ant-layout-has-sider'); + } + + if (!unref(getShowMenu) && unref(getAutoCollapse)) { + cls.push('ant-layout-auto-collapse-tabs'); + } + + return cls; + }); +</script> +<style lang="less"> + @prefix-cls: ~'@{namespace}-default-layout'; + + .@{prefix-cls} { + display: flex; + flex-direction: column; + width: 100%; + min-height: 100%; + background-color: @content-bg; + + > .ant-layout { + min-height: 100%; + } + + &-main { + width: 100%; + margin-left: 1px; + } + } + + .@{prefix-cls}-out { + &.ant-layout-has-sider { + .@{prefix-cls} { + &-main { + margin-left: 1px; + } + } + } + } +</style> diff --git a/src/layouts/page/email.vue b/src/layouts/page/email.vue new file mode 100644 index 0000000..7bf5dc7 --- /dev/null +++ b/src/layouts/page/email.vue @@ -0,0 +1,86 @@ +<template> + <div> + <PageWrapper :class="`${prefixCls}`" dense contentFullHeight fixedHeight> + <div class="default-theme" style="display: flex;height: 100%;background-color: #fff;"> + <div style="width: 10%;height: 100%;"> + <LeftNav></LeftNav> + </div> + <div style="width: 84%;height: 100%;"> + <RouterView> + <template #default="{ Component, route }"> + <transition + :name=" + getTransitionName({ + route, + openCache, + enableTransition: getEnableTransition, + cacheTabs: getCaches, + def: getBasicTransition, + }) + " + mode="out-in" + appear + > + <keep-alive v-if="openCache" :include="getCaches"> + <component :is="Component" :key="route.fullPath" /> + </keep-alive> + <component v-else :is="Component" :key="route.fullPath" /> + </transition> + </template> + </RouterView> + </div> + </div> + </PageWrapper> + <FrameLayout v-if="getCanEmbedIFramePage" /> + </div> +</template> + +<script lang="ts" setup> +import {PageWrapper} from '@/components/Page'; +import { Splitpanes, Pane } from 'splitpanes'; + + import LeftNav from '@/views/email/components/LeftNav.vue'; + import { computed, unref } from 'vue'; + + import FrameLayout from '@/layouts/iframe/index.vue'; + + import { useRootSetting } from '@/hooks/setting/useRootSetting'; + + import { useTransitionSetting } from '@/hooks/setting/useTransitionSetting'; + import { useMultipleTabSetting } from '@/hooks/setting/useMultipleTabSetting'; + import { getTransitionName } from './transition'; + + import { useMultipleTabStore } from '@/store/modules/multipleTab'; + + defineOptions({ name: 'PageLayout' }); + + const { getShowMultipleTab } = useMultipleTabSetting(); + const tabStore = useMultipleTabStore(); + + const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting(); + + const { getBasicTransition, getEnableTransition } = useTransitionSetting(); + + const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab)); + + const getCaches = computed((): string[] => { + if (!unref(getOpenKeepAlive)) { + return []; + } + return tabStore.getCachedTabList; + }); + import { useDesign } from '@/hooks/web/useDesign'; + + const { prefixCls } = useDesign('email'); +</script> +<style lang="less" scoped> + @prefix-cls: ~'@{namespace}-email'; + .@{prefix-cls} { + .splitpanes__pane { + display: flex; + align-items: center; + justify-content: center; + background-color: #fff; + } + } +</style> diff --git a/src/main.ts b/src/main.ts index 29bea12..c6d9183 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,7 +15,6 @@ import { router, setupRouter } from '@/router'; import { setupRouterGuard } from '@/router/guard'; import { setupStore } from '@/store'; - import VueCookies from 'vue-cookies' import Antd from 'ant-design-vue'; diff --git a/src/router/constant.ts b/src/router/constant.ts index e57bc26..a9d7be7 100644 --- a/src/router/constant.ts +++ b/src/router/constant.ts @@ -10,6 +10,7 @@ * @description: default layout */ export const LAYOUT = () => import('@/layouts/default/index.vue'); +export const EMAILLAYOUT = () => import('@/layouts/default/email.vue'); /** * @description: parent-layout diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts index 0bb349a..3255b09 100644 --- a/src/router/routes/index.ts +++ b/src/router/routes/index.ts @@ -1,6 +1,7 @@ import type { AppRouteRecordRaw, AppRouteModule } from '@/router/types'; import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes/basic'; +import { useEmailRouterStoreWithout } from '@/store/modules/emailRouter'; import { mainOutRoutes } from './mainOut'; import { PageEnum } from '@/enums/pageEnum'; @@ -8,6 +9,7 @@ // import.meta.glob() 鐩存帴寮曞叆鎵�鏈夌殑妯″潡 Vite 鐙湁鐨勫姛鑳� const modules = import.meta.glob('./modules/**/*.ts', { eager: true }); + const routeModuleList: AppRouteModule[] = []; // 鍔犲叆鍒拌矾鐢遍泦鍚堜腑 @@ -15,6 +17,10 @@ const mod = (modules as Recordable)[key].default || {}; const modList = Array.isArray(mod) ? [...mod] : [mod]; routeModuleList.push(...modList); + if (mod.name === 'Email'){ + const emailRouter = useEmailRouterStoreWithout(); + emailRouter.setEmailRouters(mod.children) + } }); export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]; diff --git a/src/router/routes/modules/email.ts b/src/router/routes/modules/email.ts index a7600b2..06dfdce 100644 --- a/src/router/routes/modules/email.ts +++ b/src/router/routes/modules/email.ts @@ -1,40 +1,128 @@ 锘縤mport type { AppRouteModule } from '@/router/types'; -import { LAYOUT } from '@/router/constant'; +import { EMAILLAYOUT } from '@/router/constant'; -const steps: AppRouteModule = { +const email: AppRouteModule = { path: '/email', name: 'Email', - component: LAYOUT, + component: EMAILLAYOUT, redirect: '/email/index', meta: { - orderNo: 20, - hideChildrenInMenu: true, + orderNo: 10, icon: 'mdi:email', title: '閭欢', + hideTab: true, + hideChildrenInMenu: true, + // hideMenu:true }, children: [ { - path: 'index', - name: 'EmailPage', - component: () => import('@/views/email/index.vue'), + path: 'Inbox', + name: 'Inbox', + component: () => import('@/views/email/Inbox/index.vue'), meta: { - title: '閭欢', - icon: 'mdi:email-open', - hideMenu: true, + title: '鏀朵欢绠�', + }, + children: [ + { + path: 'list', + name: 'InboxPage1', + component: () => import('@/views/email/Inbox/index.vue'), + meta: { + title: '鍏ㄩ儴閭欢', + }, + }, + // { + // path: 'list/:id', + // name: 'EmailPage2', + // component: () => import('@/views/email/UnreadEmail/user.vue'), + // meta: { + // title: '222', + // currentActiveMenu: '/email/index', + // }, + // }, + ], + }, + { + path: 'index', + name: 'Index', + component: () => import('@/views/email/UnreadEmail/index.vue'), + meta: { + title: '鏈閭欢', }, }, { - path: 'customer', - name: 'EmailCustomerPage', - component: () => import('@/views/email/customer.vue'), + path: 'utils', + name: 'Utils', + component: () => import('@/views/email/Utils/index.vue'), meta: { - title: '瀹㈡埛', - icon: 'mdi:email-open', + title: '閭欢閰嶇疆', + hideTab: true, hideMenu: true, + currentActiveMenu: '/email/index', }, }, + { + path: 'edit', + name: 'Edit', + component: () => import('@/views/email/Edit/index.vue'), + meta: { + title: '缂栬緫閭欢', + hideTab: true, + hideMenu: true, + currentActiveMenu: '/email/index', + }, + }, + { + path: 'HandlingEmailsOnBehalfOfOthers', + name: 'HandlingEmailsOnBehalfOfOthers', + component: () => import('@/views/email/HandlingEmailsOnBehalfOfOthers/index.vue'), + meta: { + title: '寰呭鐞嗛偖浠�', + hideTab: true, + }, + }, + + { + path: 'Drafts', + name: 'Drafts', + component: () => import('@/views/email/Drafts/index.vue'), + meta: { + title: '鑽夌绠�', + hideTab: true, + }, + }, + { + path: 'outbox', + name: 'Outbox', + component: () => import('@/views/email/outbox/index.vue'), + meta: { + title: '鍙戜欢绠�', + hideTab: true, + currentActiveMenu: '/email/index', + }, + children: [ + { + path: 'list', + name: 'Outbox', + component: () => import('@/views/email/outbox/index.vue'), + meta: { + title: '鍏ㄩ儴閭欢', + currentActiveMenu: '/email/index', + }, + }] + }, + // { + // path: 'MassMailbox', + // name: 'MassMailbox', + // component: () => import('@/views/email/Edit/index.vue'), + // meta: { + // title: '缇ゅ彂绠�', + // hideTab: true, + // currentActiveMenu: '/email/index', + // }, + // }, ], }; -export default steps; +export default email; diff --git a/src/router/routes/modules/preview.ts b/src/router/routes/modules/preview.ts new file mode 100644 index 0000000..81b94b5 --- /dev/null +++ b/src/router/routes/modules/preview.ts @@ -0,0 +1,31 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; + + +const preview: AppRouteModule = { + path: '/preview', + name: 'preview', + component: LAYOUT, + redirect: '/preview/index', + meta: { + orderNo: 10, + icon: 'ion:grid-outline', + title: '閭欢棰勮鈥�', + }, + children: [ + { + path: 'index', + name: 'previewIndex', + component: () => import('@/views/email/preview/index..vue'), + meta: { + // affix: true, + title: '棰勮閭欢', + hideTab: true, + hideMenu: true, + }, + }, + ], +}; + +export default preview; diff --git a/src/store/modules/emailRouter.ts b/src/store/modules/emailRouter.ts new file mode 100644 index 0000000..a6132c5 --- /dev/null +++ b/src/store/modules/emailRouter.ts @@ -0,0 +1,30 @@ +import { defineStore } from 'pinia'; +import { store } from '@/store'; + +// 浣犲彲浠ヤ换鎰忓懡鍚� `defineStore()` 鐨勮繑鍥炲�硷紝浣嗘渶濂戒娇鐢� store 鐨勫悕瀛楋紝 +// 鍚屾椂浠� `use` 寮�澶翠笖浠� `Store` 缁撳熬銆� +// (姣斿 `useUserStore`锛宍useCartStore`锛宍useProductStore`) +// 绗竴涓弬鏁版槸浣犵殑搴旂敤涓� Store 鐨勫敮涓� ID銆� +export const useEmailRouterStore = defineStore({ + id: 'emailRouter', + state: () => ({ + children: [] + }), + getters: { + getEmailRouters: (state) => state.children + }, + actions: { + setEmailRouters(children: any[]) { + this.children = [...children]; + } + } +}); + +export function useEmailRouterStoreWithout() { + try { + return useEmailRouterStore(store); + } catch (error) { + console.error('Failed to initialize email router store:', error); + throw error; + } +} \ No newline at end of file diff --git a/src/store/modules/useCollapseStore.ts b/src/store/modules/useCollapseStore.ts new file mode 100644 index 0000000..d47c26f --- /dev/null +++ b/src/store/modules/useCollapseStore.ts @@ -0,0 +1,19 @@ +import { defineStore } from 'pinia'; + +// 瀹氫箟 Pinia Store +export const useCollapseStore = defineStore('collapse', { + state: () => ({ + isOpen: true as boolean, // 鎶樺彔鐘舵�� + }), + actions: { + toggle(is) { + this.isOpen = is; // 鍒囨崲鐘舵�� + }, + open() { + this.isOpen = true; // 灞曞紑 + }, + close() { + this.isOpen = false; // 鎶樺彔 + }, + }, +}); diff --git a/src/utils/dateUtil.ts b/src/utils/dateUtil.ts index e18387d..65c693d 100644 --- a/src/utils/dateUtil.ts +++ b/src/utils/dateUtil.ts @@ -5,6 +5,8 @@ const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; const DATE_FORMAT = 'YYYY-MM-DD'; +const DATE_DAY_FORMAT = 'YYYY-MM-DD HH:mm'; + export function formatToDateTime(date?: dayjs.ConfigType, format = DATE_TIME_FORMAT): string { return dayjs(date).format(format); @@ -13,5 +15,13 @@ export function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): string { return dayjs(date).format(format); } +export function formatToDateDay(date?: dayjs.ConfigType, format = DATE_DAY_FORMAT): string { + return dayjs(date).format(format); +} +export function isBeforeToDay(date?: dayjs.ConfigType): boolean { + //鑾峰彇浠婂ぉ鐨勬棩鏈� + const now = dayjs(); + return dayjs(date).isBefore(dayjs(now)); +} export const dateUtil = dayjs; diff --git a/src/utils/http/axios/index.ts b/src/utils/http/axios/index.ts index cfb5051..5a9a098 100644 --- a/src/utils/http/axios/index.ts +++ b/src/utils/http/axios/index.ts @@ -19,6 +19,7 @@ import { useUserStoreWithOut } from '@/store/modules/user'; import { AxiosRetry } from '@/utils/http/axios/axiosRetry'; import axios from 'axios'; +import DragBar from '@/layouts/default/sider/DragBar.vue'; const globSetting = useGlobSetting(); const urlPrefix = globSetting.urlPrefix; @@ -44,17 +45,19 @@ return res.data; } // 閿欒鐨勬椂鍊欒繑鍥� - const { data } = res; if (!data) { // return '[HTTP] Request has no return value'; throw new Error(t('sys.api.apiRequestFailed')); } // 杩欓噷 code锛宺esult锛宮essage涓� 鍚庡彴缁熶竴鐨勫瓧娈碉紝闇�瑕佸湪 types.ts鍐呬慨鏀逛负椤圭洰鑷繁鐨勬帴鍙h繑鍥炴牸寮� - const { code, result, message } = data; + const { code, result, state } = data; + const { msg: message } = data; // 杩欓噷閫昏緫鍙互鏍规嵁椤圭洰杩涜淇敼 - const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS; + const isPass = code === ResultEnum.SUCCESS || state === ResultEnum.SUCCESS; + const isReflect = Reflect.has(data, 'code') || Reflect.has(data, 'state'); + const hasSuccess = data && isReflect && isPass; if (hasSuccess) { let successMsg = message; @@ -67,7 +70,7 @@ } else if (options.successMessageMode === 'message') { createMessage.success(successMsg); } - return result; + return result || data; } // 鍦ㄦ澶勬牴鎹嚜宸遍」鐩殑瀹為檯鎯呭喌瀵逛笉鍚岀殑code鎵ц涓嶅悓鐨勬搷浣� diff --git a/src/utils/tool/inedx.ts b/src/utils/tool/inedx.ts new file mode 100644 index 0000000..12f284f --- /dev/null +++ b/src/utils/tool/inedx.ts @@ -0,0 +1,5 @@ +const queryString = window.location.search; // 鑾峰彇 ? 鍚庨潰鐨勫瓧绗︿覆 +const params = new URLSearchParams(queryString); + +const emailParam = params.toString(); // 鑾峰彇鏁翠釜鏌ヨ閮ㄥ垎鐨勫瓧绗︿覆 +console.log(emailParam); // 杈撳嚭 1244041895@qq.com=test@example.com diff --git a/src/views/email/Drafts/index.vue b/src/views/email/Drafts/index.vue new file mode 100644 index 0000000..b56bbee --- /dev/null +++ b/src/views/email/Drafts/index.vue @@ -0,0 +1,58 @@ +<template> + <div> + <a-spin :spinning="loading" class="p-1" style="height: 100%"> + <PageIndex :pageList="pageList" :mailType="1" :pageData="pageData"> </PageIndex> + </a-spin> + </div> +</template> + +<script lang="ts" setup> + name: 'Drafts'; + 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 { receiveApi, 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) { + getMailListApi({ mail: routerId.value, mailType: 0, 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/Edit/index.vue b/src/views/email/Edit/index.vue new file mode 100644 index 0000000..c855cd3 --- /dev/null +++ b/src/views/email/Edit/index.vue @@ -0,0 +1,577 @@ +<template> + <div class="p-3"> + <a-spin :spinning="loading" class="p-1"> + <a-form ref="formRef" :label-col="{ span: 2 }" :wrapper-col="{ span: 24 }" :model="modelRef"> + <a-form-item :wrapper-col="{ span: 8 }"> + <div style="display: flex; justify-content: space-evenly"> + <a-button type="primary" shape="round" @click="fnHandleSubmit(modelRef)">鍙戦��</a-button> + <a-button shape="round" @click="fnSaveMailDrafts">瀛樿崏绋�</a-button> + <a-button shape="round" @click="fnPreview">棰勮</a-button> + <a-button shape="round">鎻愪氦瀹℃壒</a-button> + <a-button shape="round">鍙栨秷</a-button></div + > + </a-form-item> + <a-form-item label="鍙戜欢浜�" v-bind="validateInfos.sender" :wrapper-col="{ span: 12 }"> + <a-select + v-model:value="modelRef.sender" + placeholder="閫夋嫨鍙戜欢浜�" + show-search + :field-names="{ label: 'email', value: 'email' }" + :options="state.senderList" + :filter-option="fnFilterOption" + > + </a-select> + </a-form-item> + <a-form-item label="鏀朵欢浜�" v-bind="validateInfos.recipients" :wrapper-col="{ span: 18 }"> + <a-row> + <a-col class="gutter-row" :span="16"> + <a-select + mode="tags" + placeholder="璇烽�夋嫨鏀朵欢浜烘垨鑰呰緭鍏ユ敹浠朵汉閭" + v-model:value="modelRef.recipients" + :options="state.recipientsList" + :field-names="{ label: 'email', value: 'email' }" + :maxTagCount="4" + @search="fetchUser" + > + <template #tagRender="{ label, closable, onClose, option }"> + <a-tag + :closable="closable" + style="margin-right: 3px" + @close="onClose" + :color="validateEmail(label) ? 'default' : 'red'" + > + {{ label }} + </a-tag> + </template> + </a-select> + </a-col> + <a-col + class="gutter-row" + :span="2" + style="display: flex; align-items: center; justify-content: center" + > + <plus-circle-outlined + style="color: rgb(0 0 0 / 45%)" + @click="fnHandleSelect('recipients')" + /> + </a-col> + <a-col class="gutter-row" :span="1"> + <a-form-item-rest> + <a-button + shape="round" + type="link" + block + size="small" + @click="ccCheckboxChange($event)" + > + {{ !isSecretDeliveryPerson ? '鎶勯��' : '鍙栨秷鎶勯��' }} + </a-button> + </a-form-item-rest> + </a-col> + <a-col class="gutter-row" :span="1"> + <a-form-item-rest> + <a-button + shape="round" + type="link" + block + size="small" + @click="bccCheckboxChange($event)" + > + {{ !isCRecipient ? '瀵嗛��' : '鍙栨秷瀵嗛��' }} + </a-button> + </a-form-item-rest> + </a-col> + </a-row> + </a-form-item> + <span style="position: relative; top: -18px; left: 9%" v-if="modelRef.recipients.length"> + {{ userParticulars && `${userParticulars.userName}<${userParticulars.email}` }}> + <span v-if="!userParticulars.title" style="margin-right: 10px; margin-left: 5px"> + 鏆傛湭鏌ヨ鍒拌瀹㈡埛鐨勫綋鍦版椂闂� + <a @click="fnHandleTimeZone(userParticulars, 'add')">琛ュ厖鏃跺尯</a> + </span> + <span v-else> + {{ userParticulars.title }} + <a @click="fnHandleTimeZone(userParticulars, 'update')">鏃跺尯涓嶅锛�</a> + </span> + + <a v-if="modelRef.recipients.length > 2">鏌ョ湅鍏ㄩ儴</a> + </span> + + <a-form-item + label="鎶勯�佷汉" + v-bind="validateInfos.ccRecipients" + v-if="isSecretDeliveryPerson" + :wrapper-col="{ span: 18 }" + > + <a-row> + <a-col class="gutter-row" :span="16"> + <a-select + mode="tags" + placeholder="璇烽�夋嫨鎶勯�佷汉鎴栬�呰緭鍏ユ妱閫佷汉閭" + v-model:value="modelRef.ccRecipients" + :options="state.recipientsList" + :field-names="{ label: 'email', value: 'email' }" + :maxTagCount="4" + > + <template #tagRender="{ label, closable, onClose }"> + <a-tag + :closable="closable" + style="margin-right: 3px" + @close="onClose" + :color="validateEmail(label) ? 'default' : 'red'" + > + {{ label }} + </a-tag> + </template> + </a-select> + </a-col> + <a-col + class="gutter-row" + :span="2" + style="display: flex; align-items: center; justify-content: center" + > + <plus-circle-outlined + style="color: rgb(0 0 0 / 45%)" + @click="fnHandleSelect('ccRecipients')" + /> + </a-col> + </a-row> + </a-form-item> + <a-form-item + label="瀵嗛�佷汉" + v-bind="validateInfos.bccRecipients" + v-if="isCRecipient" + :wrapper-col="{ span: 18 }" + > + <a-row> + <a-col class="gutter-row" :span="16"> + <a-select + mode="tags" + placeholder="璇烽�夋嫨鎶勯�佷汉鎴栬�呰緭鍏ユ妱閫佷汉閭" + v-model:value="modelRef.bccRecipients" + :options="state.recipientsList" + :field-names="{ label: 'email', value: 'email' }" + :maxTagCount="4" + > + <template #tagRender="{ label, closable, onClose }"> + <a-tag + :closable="closable" + style="margin-right: 3px" + @close="onClose" + :color="validateEmail(label) ? 'default' : 'red'" + > + {{ label }} + </a-tag> + </template> + </a-select> + </a-col> + <a-col + class="gutter-row" + :span="2" + style="display: flex; align-items: center; justify-content: center" + > + <plus-circle-outlined + style="color: rgb(0 0 0 / 45%)" + @click="fnHandleSelect('bccRecipients')" + /> + </a-col> + </a-row> + </a-form-item> + <div + style="position: relative; top: -20px; left: 9%; color: #909090; font-size: 12px" + v-if="modelRef.recipients.length" + > + {{ `${userLength}/100 鏀朵欢浜恒�佹妱閫佷汉鍜屽瘑閫佺殑鑱旂郴浜烘�绘暟涓嶅彲瓒呰繃100` }} + </div> + <a-form-item label="涓婚" :wrapper-col="{ span: 12 }" :label-col="{ span: 2 }"> + <a-input placeholder="璇疯緭鍏ラ偖浠朵富棰�" v-model:value="modelRef.subject" /> + </a-form-item> + <a-form-item + v-bind="validateInfos.content" + :wrapper-col="{ span: 24 }" + :label-col="{ span: 2 }" + > + <Tinymce v-model="modelRef.content" @change="fnChangeContent"></Tinymce> + </a-form-item> + </a-form> + </a-spin> + <SelectUser + ref="selectUserRef" + v-model="openSelectUser" + :selectIds="selectIds" + :idName="idName" + :title="selectUserTitle" + @updateData="fnSelectUser" + /> + <a-modal + v-model:open="openTimeZone" + :title="`${titleTimeZone}鏃跺尯`" + :confirm-loading="confirmLoading" + @ok="fnHandleTimeZoneOk" + > + <a-form + ref="formTimeZoneRef" + :label-col="{ span: 6 }" + :wrapper-col="{ span: 24 }" + :model="modelRef" + > + <a-form-item label="鍥藉鍦板尯锛�" :wrapper-col="{ span: 12 }"> + <a-select + v-model:value="modelRef.sender" + placeholder="閫夋嫨鍥藉鍦板尯" + show-search + :field-names="{ label: 'name', value: 'id' }" + :options="state.data" + > + </a-select> + </a-form-item> + <a-form-item label="鏃跺尯锛�" :wrapper-col="{ span: 12 }"> + <a-select + v-model:value="modelRef.sender" + placeholder="閫夋嫨鏃跺尯" + show-search + :field-names="{ label: 'name', value: 'id' }" + :options="state.data" + > + </a-select> + </a-form-item> + <a-form-item :label-col="{ span: 6 }"> + <a-row> + <a-col span="6"> </a-col> + <a-col span="18"> + <div style="color: #909090" + >褰撳湴瀹炴椂鏃堕棿鏍规嵁鏃跺尯淇℃伅鏄剧ず<br /> + 鍥藉鍦板尯/鏃跺尯淇℃伅鍦ㄥ鎴疯祫鏂�-鐗瑰緛淇℃伅鏌ョ湅</div + > + </a-col> + </a-row> + </a-form-item> + </a-form> + </a-modal> + </div> +</template> + +<script lang="ts" setup> + name: 'Edit'; + import { useDesign } from '@/hooks/web/useDesign'; + + import { ref, reactive, computed, onMounted } from 'vue'; + import { useMessage } from '@/hooks/web/useMessage'; + import { Tinymce } from '@/components/Tinymce'; + import { PlusCircleOutlined } from '@ant-design/icons-vue'; + import SelectUser from '../components/SelectUser/index.vue'; + import { Form } from 'ant-design-vue'; + const modelRef = reactive({ + sender: '', + recipients: [], + ccRecipients: [], + bccRecipients: [], + attachmentList: [], + subject: '', + content: '', + }); + const loading = ref(false); + const rulesRef = reactive({ + sender: [ + { + required: true, + message: '鍙戜欢浜轰笉鑳戒负绌�', + }, + ], + recipients: [ + { + required: true, + message: '鏀朵欢浜轰笉鑳戒负绌�', + }, + ], + content: [ + { + required: true, + message: '鍐呭涓嶈兘涓虹┖', + }, + ], + }); + const TYPE = computed(() => { + return router.currentRoute.value.query.type || 'send'; + }); + onMounted(() => { + fnGetRecipientsList(); + if (TYPE.value === 'reply') { + fuGetReplyEmailData(); + } + }); + const useForm = Form.useForm; + const { validate, validateInfos } = useForm(modelRef, rulesRef); + + const formRef = ref(); + + let isSecretDeliveryPerson = ref(false); + + let isCRecipient = ref(false); + const handleCheckboxChange = (ref, e) => { + ref.value = !ref.value; + }; + + const ccCheckboxChange = (e) => { + handleCheckboxChange(isSecretDeliveryPerson, e); + }; + const bccCheckboxChange = (e) => { + handleCheckboxChange(isCRecipient, e); + }; + + const { createMessage } = useMessage(); + import { + getAccountListApi, + sendingMailApi, + saveMailDraftsApi, + emailListAPi, + getMailInfoApi + } from '@/api/email/userList'; + // 瀹氫箟鐘舵�佺鐞嗗璞� + const state = reactive({ + data: [], + fetching: false, + error: null, + senderList: [], + recipientsList: [], + }); + + // 鑾峰彇鐢ㄦ埛鍒楄〃鐨勫嚱鏁� + const fnGetUserList = async (params) => { + try { + state.fetching = true; + const res = await getAccountListApi(); + console.log(res, 'res'); + + if (res && res.data && Array.isArray(res.data)) { + state.senderList = res.data; + } else { + console.error('Invalid response format:', res); + } + } catch (error) { + console.error('Failed to fetch user list:', error); + } finally { + state.fetching = false; + } + }; + + // 鍒濆鍖栫敤鎴锋暟鎹� + const fetchData = async () => { + await fnGetUserList({ page: 1, pageSize: 30 }); + }; + console.log(state.data, 'state.data'); + // 璋冪敤鍒濆鍖栧嚱鏁� + fetchData(); + + const fnFilterOption = (input: string, option: any) => { + return option.email.toLowerCase().indexOf(input.toLowerCase()) >= 0; + }; + + const openSelectUser = ref(false); + const selectUserTitle = ref('閫夋嫨鏀朵欢浜�'); + const selectIds = ref([]); + const idName = ref(''); + const fnHandleSelect = (e) => { + if (e === 'recipients') { + selectUserTitle.value = '閫夋嫨鏀朵欢浜�'; + idName.value = e; + } + if (e === 'ccRecipients') { + selectUserTitle.value = '閫夋嫨鎶勯�佷汉'; + idName.value = e; + } + if (e === 'bccRecipients') { + selectUserTitle.value = '閫夋嫨瀵嗛�佷汉'; + idName.value = e; + } + selectIds.value = modelRef[e]; + openSelectUser.value = true; + }; + + function fnChangeContent(e) { + modelRef.content = e.content; + modelRef.attachmentList = e.attachmentList; + } + + function fnBuildingCommitData() { + return { + sender: modelRef.sender, + receiver: modelRef.recipients, + cc: modelRef.ccRecipients, + bcc: modelRef.bccRecipients, + subject: modelRef.subject, + content: modelRef.content, + attachmentList: '', + docCode: docCode.value, + }; + } + const docCode = ref(''); + import { Modal } from 'ant-design-vue'; + function fnHandleSubmit(values: any) { + validate() + .then((res) => { + if (!modelRef.subject) { + Modal.confirm({ + title: '鎻愮ず', + content: '閭欢涓婚涓虹┖锛岀‘瀹氳鍙戦�佸悧锛�', + onOk() { + pushSendingMail(); + }, + onCancel() {}, + }); + } else { + pushSendingMail(); + } + }) + .catch((error) => { + createMessage.error('琛ㄥ崟楠岃瘉澶辫触'); + }); + } + import { useRouter } from 'vue-router'; + const router = useRouter(); + function pushSendingMail() { + const data = fnBuildingCommitData(); + loading.value = true; + sendingMailApi(data) + .then((res) => { + loading.value = false; + if (res.code === 0) { + createMessage.success(res.msg); + router.push('/email/list'); + } + }) + .catch((error) => { + loading.value = false; + }); + } + function fnPreview() { + router.push({ path: '/preview/index', query: { docCode: docCode.value } }); + } + + function fnSaveMailDrafts() { + const data = fnBuildingCommitData(); + loading.value = true; + + saveMailDraftsApi(data) + .then((res) => { + loading.value = false; + if (res.code == 0) { + docCode.value = res.data.docCode; + createMessage.success('淇濆瓨鎴愬姛'); + } + }) + .catch((error) => { + loading.value = false; + }); + } + + const fnSelectUser = (e) => { + modelRef[e.idName] = e.selectedRowKeys; + }; + + const userLength = computed(() => { + return ( + modelRef.recipients.length + modelRef.ccRecipients.length + modelRef.bccRecipients.length + ); + }); + + interface Recipient { + value: string; + email: string; + title: string; + } + + const userParticulars = computed<Recipient | null>(() => { + const recipientId = modelRef.recipients?.[0]; + if (!recipientId) { + console.error('Recipient ID is not defined.'); + return { value: '', email: '', title: '' }; + } + // 浣跨敤 find 鏂规硶鏌ユ壘鍖归厤椤� + const foundItem = state.recipientsList.find((item) => item.email === recipientId); + + // 杩斿洖鎵惧埌鐨勯」锛屽鏋滄湭鎵惧埌鍒欒繑鍥� null + return foundItem || { value: '', email: '', title: '' }; + }); + const fnHandleTimeZone = (e, type) => { + console.log('fnHandleTimeZone'); + if (type == 'add') { + titleTimeZone.value = '娣诲姞'; + typeTimeZone.value = 1; + } else { + titleTimeZone.value = '淇敼'; + typeTimeZone.value = 2; + } + openTimeZone.value = true; + }; + + const openTimeZone = ref<boolean>(false); + const confirmLoading = ref<boolean>(false); + const titleTimeZone = ref<string>('娣诲姞'); + const typeTimeZone = ref<number>(1); + + const fnHandleTimeZoneOk = () => { + confirmLoading.value = true; + setTimeout(() => { + openTimeZone.value = false; + confirmLoading.value = false; + }, 2000); + }; + + function fnGetRecipientsList() { + emailListAPi({ key: '' }).then((body) => { + state.recipientsList = body.data; + }); + } + // 閭鏍¢獙姝e垯琛ㄨ揪寮� + const validateEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); + import { debounce } from 'lodash-es'; + let lastFetchId = 0; + + const fetchUser = debounce((value) => { + lastFetchId += 1; + const fetchId = lastFetchId; + state.data = []; + state.fetching = true; + emailListAPi({ key: value }).then((body) => { + if (fetchId !== lastFetchId) { + // for fetch callback order + return; + } + + state.recipientsList = body.data; + state.fetching = false; + }); + }, 300); + + // 鍥炲 + function fuGetReplyEmailData() { + getMailInfoApi({ docCode: router.currentRoute.value.query.docCode }) + .then((res) => { + console.log(ref.data,'---3022'); + modelRef.sender = res.data.receiver[0] + modelRef.recipients = [res.data.sender] + modelRef.subject = 'Re锛�'+ res.data.subject + modelRef.content = setContent(res.data) + // tableRowData.value = res.data; + }) + .catch(() => {}); + console.log('----------------4'); + } + const setContent = (row) => { + const text = `<div style=\"font-size: 12px; font-family: Arial Narrow,serif; padding: 2px 0 2px 0;\">------------------ Original ------------------</div>\n<div style=\"font-size: 12px; background: #efefef; padding: 8px;\">\n<div><strong>From: </strong> ${row.sender} <<a style=\"color: #1e7bf9; text-decoration: none;\" href=\"mailto:${row.sender}\" target=\"_blank\" rel=\"noopener noreferrer\">${row.sender}</a>></div>\n<div><strong>Send time: </strong> ${row.createTime}</div>\n<div><strong>To: </strong> ${row.userName} <<a style=\"color: #1e7bf9; text-decoration: none;\" href=\"mailto:${row.receiver}\" target=\"_blank\" rel=\"noopener noreferrer\">${row.receiver}</a>></div>\n<div><strong>Subject: </strong> ${row.subject}</div>\n</div>` + return text + row.content + }; +</script> +<style lang="less" scoped> + @prefix-cls: ~'@{namespace}-email'; + .@{prefix-cls} { + .splitpanes__pane { + display: flex; + align-items: stretch; + justify-content: center; + // background-color: var(--component-background-color); + } + } +</style> diff --git a/src/views/email/HandlingEmailsOnBehalfOfOthers/components/list.vue b/src/views/email/HandlingEmailsOnBehalfOfOthers/components/list.vue new file mode 100644 index 0000000..bf9bd5e --- /dev/null +++ b/src/views/email/HandlingEmailsOnBehalfOfOthers/components/list.vue @@ -0,0 +1,220 @@ +<template> + <PageWrapper> + <div style="height: calc(100vh - 84px)"> + <div class="head"> + <div class="left"> + <div class="left-box p-3"> + <!-- 澶氶�� --> + <a-checkbox + class="icon" + style="margin-right: 10px" + v-model:checked="state.checkAll" + :indeterminate="state.indeterminate" + @change="fnCheckedChange" + ></a-checkbox> + <!--鏇存柊 --> + <SyncOutlined class="icon" v-show="!checked" /> + <pageHeadLeft + :checked="checked" + :selectAllRow="selectAllRow" + :parentTableList="newList" + ></pageHeadLeft> + </div> + </div> + + <div class="right p-3" + >鍏�<span style="padding: 0 5px">20</span>灏� + <a-pagination + v-model:current="pageCurrent" + v-model:page-size='page.limit' + simple + :total="page.total" + style="margin-left: 10px" + @change="handlePageChange" + /> + <FilterOutlined style="margin-left: 10px" /> + <a-popover placement="left" trigger="click"> + <template #content> + <div> + <span>寰�鏉ラ偖浠惰仛鍚�</span> + <a-switch style="margin-left: 50px" v-model:checked="checked3"> </a-switch> + </div> + <a-divider style="margin: 10px" /> + <div> + <span>鍒楄〃灞曠ず鍐呭</span> + </div> + <div class="p-2"> + <a-checkbox v-model:checked="checked">閭欢鎽樿</a-checkbox> + </div> + <div class="p-2"> + <a-checkbox v-model:checked="checked">闄勪欢</a-checkbox> + </div> + <div style="text-align: center"> + <a-button @click="$router.push('/email/utils')">鏇村閭璁剧疆</a-button> + </div> + </template> + <SettingOutlined style="margin-left: 10px" /> + </a-popover> + <a-switch style="margin-left: 10px" v-model:checked="checked3"> + <template #checkedChildren><PushpinOutlined style="color: #0a6aff" /></template> + <template #unCheckedChildren><PushpinOutlined /></template> + </a-switch> + </div> + </div> + <div v-if="checked" 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" + @selectAll="fnSelectAll" + @updateSelectAll="updateSelectAll" + /> + </a-tab-pane> + </a-tabs> + </div> + </div> + </PageWrapper> +</template> + +<script lang="ts" setup> + name: 'ListPage'; + import { + SyncOutlined, + SettingOutlined, + FilterOutlined, + PushpinOutlined, + } from '@ant-design/icons-vue'; + import pageHeadLeft from '@/views/email/components/ListPage/pageHeadLeft.vue'; + import { PageWrapper } from '@/components/Page'; + + import { ref, watch, defineProps, defineEmits, computed, reactive, onMounted,inject } from 'vue'; + + // 瀹氫箟灞炴�� + interface Props { + pageList: []; + pageData?:any; + } + const props = defineProps<Props>(); + const newList = ref([]); + const selectAllRow = ref([]); + watch( + () => props.pageList, + (newValue) => { + newList.value = newValue; + }, + ); + + const checked = computed(() => selectAllRow.value.length > 0); + const pageCurrent = ref(1); + const tableRef = ref(); + const state = reactive({ + indeterminate: false, + checkAll: false, + }); + function fnCheckedChange(e) { + Object.assign(state, { + indeterminate: false, + }); + tableRef.value[0].fnSelectAll(e.target.checked); + checked.value = e.target.checked; + } + function updateSelectAll(data) { + selectAllRow.value = data.records; + if (!data.isAll) { + state.indeterminate = true; + state.checkAll = false; + if (data.records.length === 0) { + state.indeterminate = false; + } + } else { + state.indeterminate = false; + state.checkAll = true; + } + } + const tabsList = computed(() => { + return [ + { + key: '1', + label: '闇�瑕佸鐞�', + num: 20, + }, + { + key: '2', + label: '灏氭湭鍒版湡', + num: 0, + }, + { + key: '3', + label: '宸插畬鎴�', + num: 0, + }, + ]; + }) + const activeKey = ref('1'); + const checked3 = ref(false); + import Table from '@/views/email/components/ListPage/table.vue'; + onMounted(() => { + console.log('tableRef:', tableRef.value[0]); + }); + function fnSelectAll() { + console.log('44444444444'); + } + + const page = computed(() => props.pageData); + const getDataList = inject('getDataList'); + function handlePageChange(page, pageSize){ + getDataList(page) + + } +</script> +<style scoped lang="less"> + .head { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: 60px; + border-bottom: 1px solid rgb(5 5 5 / 6%); + + /* 澧炲姞閫夋嫨鍣ㄧ壒寮傛�� */ + & .left { + width: 20%; + + & .left-box { + display: flex; + align-items: center; + justify-content: space-flex-start; + width: 100%; + height: 100%;; + + & .icon { + margin-right: 15px; + font-size: 16px; + } + } + } + + & .right { + display: flex; + align-items: center; + } + } + + .left-bt { + display: flex; + align-items: center; + justify-content: center; + padding-left: 27px; + background: #fffbe6; + } +</style> diff --git a/src/views/email/HandlingEmailsOnBehalfOfOthers/index.vue b/src/views/email/HandlingEmailsOnBehalfOfOthers/index.vue new file mode 100644 index 0000000..cc217ac --- /dev/null +++ b/src/views/email/HandlingEmailsOnBehalfOfOthers/index.vue @@ -0,0 +1,60 @@ +<template> + <div> + <a-spin :spinning="loading" class="p-1" style="height: 100%"> + <PageIndex :pageList="pageList" :mailType="1" :pageData="pageData"> </PageIndex> + </a-spin> + </div> +</template> + +<script lang="ts" setup> + name: 'Inbox'; + import { ref, onMounted, computed, provide } from 'vue'; + import PageIndex from './components/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 { getHandleMailListApi } 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; + + getHandleMailListApi({ 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/Inbox/index.vue b/src/views/email/Inbox/index.vue new file mode 100644 index 0000000..b03eeb8 --- /dev/null +++ b/src/views/email/Inbox/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" > </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/UnreadEmail/index.vue b/src/views/email/UnreadEmail/index.vue new file mode 100644 index 0000000..68f9ad3 --- /dev/null +++ b/src/views/email/UnreadEmail/index.vue @@ -0,0 +1,59 @@ +<template> + <div> + <a-spin :spinning="loading" class="p-1" style="height: 100%;"> + <PageIndex :pageList="pageList" :mailType='1' :pageData='pageData' ></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'; +import { limit } from 'packages/hooks/src/useRequest/utils/limit'; + const pageList = ref([]); + const pageData = ref({ + page: 1, + limit: 20, + total: 0, + }) + const loading = ref(false); + + function getDataList() { + getMailListApi({ mail: routerId.value,mailType:1,isNoRead:true ,limit:pageData.value.limit}).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/UnreadEmail/user.vue b/src/views/email/UnreadEmail/user.vue new file mode 100644 index 0000000..584af91 --- /dev/null +++ b/src/views/email/UnreadEmail/user.vue @@ -0,0 +1,18 @@ +<template> + <PageWrapper dense contentFullHeight fixedHeight> +<div> +sdfdsfds +</div> +<div class='table-list'> +sdffsdfsdffdsdsfdsf +</div> + + + </PageWrapper> +</template> +<script lang="ts" setup> +name: 'UnreadEmail' +import {PageWrapper} from '@/components/Page'; + import { ref, watch } from 'vue'; + +</script> diff --git a/src/views/email/Utils/blacklist.vue b/src/views/email/Utils/blacklist.vue new file mode 100644 index 0000000..cd379f0 --- /dev/null +++ b/src/views/email/Utils/blacklist.vue @@ -0,0 +1,242 @@ +<template> + <div class="p-2"> + <div> + <vxe-toolbar> + <template #buttons> + <div style="display: flex; align-items: flex-end"> + <span style="font-size: 1.25rem; font-weight: 600">閭鍦板潃榛戝悕鍗�</span + ><span style="margin-left: 5px; padding-bottom: 4px; font-size: 12px" + >榛戝悕鍗曚腑閭瀵瑰簲鐨勯偖浠跺皢鑷姩鏀跺彇鍒板瀮鍦鹃偖浠讹紙涓嶄細鏀跺埌鏂伴偖浠舵彁閱掞級</span + > + </div> + </template> + <template #tools> + <a-button type="primary" @click="showModal('mail')">娣诲姞閭鍦板潃</a-button> + </template> + </vxe-toolbar> + <a-input-search + v-model:value="searchMail" + placeholder="璇疯緭鍏ュ叧閿瓧鎼滅储" + style="width: 300px; margin-bottom: 20px" + @search="onSearch($event, 'mail')" + /> + <a-table :columns="columns" :data-source="demo.mail" :scroll="{ y: 220 }" size="small"> + <template #bodyCell="{ column, record }"> + <template v-if="column.field == 'operate'"> + <a class="ant-dropdown-link" @click="fnDelete(record)">鍒犻櫎</a> + </template> + <template v-else-if="column.field == 'mail'"> + {{ record.blackContent }} + </template> + </template> + </a-table> + </div> + <div> + <vxe-toolbar> + <template #buttons> + <div style="display: flex; align-items: flex-end"> + <span style="font-size: 1.25rem; font-weight: 600">鍩熷悕榛戝悕鍗�</span + ><span style="margin-left: 5px; padding-bottom: 4px; font-size: 12px" + >榛戝悕鍗曚腑鍩熷悕瀵瑰簲鐨勯偖浠跺皢鑷姩鏀跺彇鍒板瀮鍦鹃偖浠讹紙涓嶄細鏀跺埌鏂伴偖浠舵彁閱掞級</span + > + </div> + </template> + <template #tools> + <a-button type="primary" @click="showModal('domainName')">娣诲姞鍩熷悕</a-button> + </template> + </vxe-toolbar> + <a-input-search + v-model:value="searchDomainName" + placeholder="璇疯緭鍏ュ叧閿瓧鎼滅储" + style="width: 300px; margin-bottom: 20px" + @search="onSearch($event, 'domainName')" + /> + <a-table + :columns="columnsDomainName" + :data-source="demo.domain" + :scroll="{ y: 220 }" + size="small" + > + <template #bodyCell="{ column, record }"> + <template v-if="column.field == 'operate'"> + <a class="ant-dropdown-link" @click="fnDelete(record)">鍒犻櫎</a> + </template> + <template v-else-if="column.field == 'domainName'"> + {{ record.blackContent }} + </template> + </template> + </a-table> + </div> + + <a-modal :width="600" v-model:open="open" :title="`鏂板缓${formTypeName}榛戝悕鍗昤" @ok="handleOk"> + <a-form ref="formRef" :model="form" style="margin-top: 20px"> + <a-form-item + v-if="formType === 'mail'" + label="閭鍦板潃" + name="blackContent" + :rules="[{ required: true, type: 'email', message: '璇疯緭鍏ラ偖绠卞湴鍧�', trigger: 'blur' }]" + > + <a-input v-model:value="form.blackContent" placeholder="璇疯緭鍏ラ偖绠卞湴鍧�" /> + </a-form-item> + <a-form-item + v-else + label="鍩熷悕鍦板潃" + name="blackContent" + :rules="[{ required: true, validator: validatorDomainName }]" + > + <a-input v-model:value="form.blackContent" placeholder="璇疯緭鍏ュ煙鍚嶅湴鍧�" /> + </a-form-item> + </a-form> + </a-modal> + </div> +</template> + +<script lang="ts" setup> + import { ref, onMounted, onUnmounted, reactive } from 'vue'; + import { getBlackListApi, addBlackListApi, deleteBlackListApi } from '@/api/email/userList'; + onUnmounted(() => {}); + + function fnGetList() { + getBlackListApi({}).then((res) => { + console.log(res); + demo.mail = res.data.mail; + demo.domain = res.data.domain; + }); + } + + import { useMessage } from '@/hooks/web/useMessage'; + const { createMessage } = useMessage(); + + const open = ref(false); + const formRef = ref(); + const columns = [ + { + title: '閭鍦板潃', + field: 'mail', + minWidth: 200, + }, + { + title: '鎿嶄綔', + field: 'operate', + width: 200, + }, + ]; + const columnsDomainName = [ + { + title: '鍩熷悕鍦板潃', + field: 'domainName', + minWidth: 200, + }, + { + title: '鎿嶄綔', + field: 'operate', + width: 200, + }, + ]; + function validatorDomainName(rule, value, callback) { + if (!value) { + callback(new Error('璇疯緭鍏ュ煙鍚嶅湴鍧�')); + } else { + if (!/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/.test(value)) { + callback(new Error('鍩熷悕鍦板潃鏍煎紡涓嶆纭�!')); + } else { + callback(); + } + } + } + const form = reactive({ + blackContent: '', + }); + const formTypeName = ref('閭鍦板潃'); + const formType = ref('mail'); + const demo = reactive({ + mail: [], + domain: [], + }); + + const searchMail = ref(''); + const searchDomainName = ref(''); + function showModal(blackType) { + open.value = true; + if (blackType === 'mail') { + formTypeName.value = '閭鍦板潃'; + formType.value = 'mail'; + } else { + formTypeName.value = '鍩熷悕鍦板潃'; + formType.value = 'domainName'; + } + } + function handleOk() { + formRef.value.validate().then(() => { + addBlackListApi({ + blackContent: form.blackContent, + blackType: formType.value === 'mail' ? false : true, + }).then((res) => { + createMessage.success('娣诲姞鎴愬姛'); + open.value = false; + fnGetList(); + }); + }); + } + function fnDelete(row) { + deleteBlackListApi({ + blackId: row.blackId, + }).then((res) => { + createMessage.success('鍒犻櫎鎴愬姛'); + fnGetList(); + }); + } +function onSearch(e, type) { + console.log(e, type,'---3333'); + + const data = { + search: e, + type: type === 'mail' ? 0 : 1, + }; + if (type === 'mail') { + getBlackListApi(data).then((res) => { + demo.mail = res.data.mail; + }); + } else { + getBlackListApi(data).then((res) => { + demo.domain = res.data.domain; + }); + } + } + + onMounted(() => { + fnGetList(); + }); +</script> + +<style scoped> + .sortable-row-demo .drag-btn { + font-size: 12px; + cursor: move; + } + + .sortable-row-demo .vxe-body--row.sortable-ghost, + .sortable-row-demo .vxe-body--row.sortable-chosen { + background-color: #dfecfb; + } + + .bookmark { + display: inline-block; + position: relative; + width: 14px; + height: 18px; + margin-right: 5px; + border-radius: 2px 2px 0 0; /* Rounded top corners */ + } + + .bookmark::after { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 8px; + background-color: #fff; + clip-path: polygon(50% 60%, 0% 100%, 100% 100%); /* Triangle at the bottom */ + } +</style> diff --git a/src/views/email/Utils/convention.vue b/src/views/email/Utils/convention.vue new file mode 100644 index 0000000..172ea88 --- /dev/null +++ b/src/views/email/Utils/convention.vue @@ -0,0 +1,152 @@ +<template> + <div class="p-2" style="margin-left: 20px"> + <h2 class="title" style="font-size: 20px">甯歌</h2> + + <a-form style="width: 60%" :labelCol="{ span: 2 }"> + <div> + <h2 class="title">璐﹀彿</h2> + <a-form-item label="榛樿閭"> + <a-select class='w-200'> + <a-select-option v-for="(item, index) in emailList" :key="index" :value="item.email"> + {{ item.email }} + </a-select-option> + </a-select> + <span style="font-size: 12px">鍦ㄧ粦瀹氬涓偖绠辩殑鎯呭喌涓嬶紝鍙戜俊鏃堕粯璁ら�夋嫨璇ラ偖绠便��</span> + </a-form-item> + </div> + + <div> + <h2 class="title">绛惧悕</h2> + <a-form-item label="涓�х鍚�"> + <div style="display: flex"> + <a-select @change="fnSignatureChange" v-model:value="signature"> + <a-select-option + v-for="(item, index) in signatureList" + :key="index" + :value="item.signId" + > + {{ item.signName }} + </a-select-option> + </a-select> + <PlusCircleOutlined style="margin-left: 20px" @click="showModal('add')" /> + </div> + </a-form-item> + <a-form-item :labelCol="{ span: 2 }" label="鍐呭"> + <div style="height: 200px; overflow: hidden; border-bottom: 1px solid #d9d9d9"> + <TinymcePw v-model="signContent"></TinymcePw> + </div> + <div style="margin-top: 20px; text-align: right"> + <a-button type="primary" size="small" @click="showModal('update')">缂栬緫</a-button> + <a-button size="small" style="margin-left: 20px">鍒犻櫎</a-button> + </div> + </a-form-item> + </div> + </a-form> + <a-modal :width="700" v-model:open="open" :title="`${title}涓�х鍚峘" @ok="handleOk"> + <a-form ref="formRef" :model="form" style="margin-top: 20px"> + <a-form-item + label="鍚嶇О" + name="signName" + :rules="[{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }]" + > + <a-input v-model:value="form.signName" placeholder="璇疯緭鍏ュ悕绉�" /> + </a-form-item> + <a-form-item label="鍐呭"> + <Tinymce v-model="form.signContent" :isElse="false" :isText="false"></Tinymce> + </a-form-item> + </a-form> + </a-modal> + </div> +</template> + +<script lang="ts" setup> + name: 'convention'; + import { nextTick, onMounted, ref } from 'vue'; + import { getAccountListApi } from '@/api/email/userList'; + import { PlusCircleOutlined } from '@ant-design/icons-vue'; + import { TinymcePw, Tinymce } from '@/components/Tinymce'; + import { addSignatureApi, getSignatureApi, updateSignatureApi } from '@/api/email/userList'; + + interface EmailItem { + email: string; + } + const emailList = ref<EmailItem[]>([]); + const signatureList = ref<any[]>([]); + function getEmailSelect() { + getAccountListApi().then((res) => { + emailList.value = res.data; + console.log(res); + }); + + getSignatureApi({}).then((res) => { + signatureList.value = res.data; + }); + } + onMounted(() => { + getEmailSelect(); + }); + const open = ref(false); + const signType = ref('add'); + const form = ref<Record<string, any>>({}); + const title = ref('鏂板缓'); + const showModal = (type) => { + signType.value = type; + open.value = true; + if (type == 'add') { + form.value = { + signName: '', + signContent: '', + }; + } else { + title.value = '缂栬緫'; + nextTick(() => { + formRef.value.resetFields(); + getSign(signature.value); + }); + } + }; + function getSign(id) { + const matchedItem = signatureList.value.find((item) => item.signId === id); + if (matchedItem) { + form.value = Object.assign(form.value, matchedItem); + } + } + import { useMessage } from '@/hooks/web/useMessage'; + const { createMessage } = useMessage(); + const formRef = ref(); + const signContent = ref(); + function fnSignatureChange(e) { + const matchedItem = signatureList.value.find((item) => item.signId === e); + if (matchedItem) { + signContent.value = matchedItem.signContent; + } else { + signContent.value = ''; + } + } + const signature = ref(); + const handleOk = (e: MouseEvent) => { + formRef.value + .validate() + .then(() => { + const data = form.value; + const api = signType.value == 'update' ? updateSignatureApi : addSignatureApi; + // const api = addSignatureApi; + api(data).then((res) => { + if (res.code === 0) { + createMessage.success(res.msg); + open.value = false; + } + }); + }) + .catch((e) => { + console.log(e); + }); + }; +</script> +<style scoped lang="less"> + .title { + margin-bottom: 20px; + font-size: 16px; + font-weight: 600; + } +</style> diff --git a/src/views/email/Utils/folder.vue b/src/views/email/Utils/folder.vue new file mode 100644 index 0000000..65e1ce5 --- /dev/null +++ b/src/views/email/Utils/folder.vue @@ -0,0 +1,200 @@ +<template> + <div class="p-2"> + <vxe-toolbar> + <template #buttons> + <div style="display: flex; align-items: flex-end"> + <span style="font-size: 1.25rem; font-weight: 600">鏂囦欢澶�</span + ><span style="margin-left: 5px; padding-bottom: 4px; font-size: 12px" + >绠$悊鎮ㄧ殑涓汉鏂囦欢澶�</span + > + </div> + </template> + <template #tools> + <a-button type="primary" @click="insertEvent">鏂板缓鏂囦欢澶�</a-button> + </template> + </vxe-toolbar> + + <vxe-table + ref="xTable" + style="margin: 10px 0" + :data="demo.tableData" + @mounted="onMounted" + :row-config="{ keyField: 'id' }" + :column-config="{ resizable: true }" + :export-config="{}" + :tree-config="{ transform: true,rowField: 'rowId', parentField: 'parentRowId' }" + :edit-config="{ trigger: 'manual', mode: 'row' }" + height="600" + > + <vxe-column width="40"> + <template #default> + <span class="drag-btn"> + <HolderOutlined /> + </span> + </template> + </vxe-column> + <vxe-column field="folderName" title="鏂囦欢澶瑰悕绉�" minWidth="250" tree-node :edit-render="{}"> + <template #edit="{ row }"> + <vxe-input + :ref="el => inputRefs[row.id] = el" + v-model="row.folderName" + type="text" + style="width: 300px" + @blur="fnInputHandle(row)" + ></vxe-input> + </template> + </vxe-column> + <vxe-column field="age" title="鎿嶄綔" width="250"> + <template #default="{ row }"> + <a style="margin-right: 10px" @click="insertRow(row)">娣诲姞瀛愭枃浠跺す</a> + <a style="margin-right: 10px" @click="editRowEvent(row)">缂栬緫</a> + <a style="margin-right: 10px" @click="fnDelete(row)">鍒犻櫎</a> + </template> + </vxe-column> + </vxe-table> + </div> +</template> + +<script lang="ts" setup> + import { ref, computed, onMounted, nextTick, onUnmounted, reactive } from 'vue'; + import { + addFolderApi, + deleteFolderApi, + updateFolderApi, + getFolderApi, + } from '@/api/email/userList'; + + // 鎺掑簭 + import { HolderOutlined } from '@ant-design/icons-vue'; + import Sortable from 'sortablejs'; + let sortable: any; + const demo = reactive({ + showHelpTip: false, + tableData: [], + }); + const xTable = ref(); + const rowDrop = () => { + const $table = xTable.value; + sortable = Sortable.create($table.$el.querySelector('.body--wrapper>.vxe-table--body tbody'), { + handle: '.drag-btn', + onEnd: (sortableEvent) => { + const newIndex = sortableEvent.newIndex as number; + const oldIndex = sortableEvent.oldIndex as number; + const currRow: Record<string, any> = demo.tableData.splice(oldIndex, 1)[0]; + // demo.tableData.splice(newIndex, 0, currRow); + updateFolderApi({ + textId: currRow.textId, + textName: currRow.textName, + content: currRow.content, + sortId: newIndex, + }) + .then(() => { + fnGetList(); + }) + .catch(() => {}); + }, + }); + }; + + let initTime: any; + nextTick(() => { + // 鍔犺浇瀹屾垚涔嬪悗鍦ㄧ粦瀹氭嫋鍔ㄤ簨浠� + initTime = setTimeout(() => { + rowDrop(); + }, 500); + }); + + onUnmounted(() => { + clearTimeout(initTime); + if (sortable) { + sortable.destroy(); + } + }); + + function fnGetList() { + getFolderApi({}).then((res) => { + console.log(res); + demo.tableData = res.data; + }); + } + + const inputRefs = ref<{ [key: number]: HTMLElement | null }>({}); + function insertEvent() { + const $table = xTable.value; + const rid = Date.now(); + const record = { + folderName: `鏂版暟鎹�${rid}`, + id: rid, + }; + $table.insert(record).then(({ row }) => $table.setEditRow(row)); + setTimeout(() => { + inputRefs.value[rid].focus(); + }, 300); + } + import { useMessage } from '@/hooks/web/useMessage'; + + const { createMessage } = useMessage(); + function fnInputHandle(row) { + console.log(row, '----333'); + const data = { + folderName: row.folderName, + parentRowId: row.rowId, + }; + addFolderApi(data).then((res) => { + if (res.code == 0) { + createMessage.success('娣诲姞鎴愬姛'); + fnGetList(); + } else { + createMessage.error(res.msg); + } + }); + } + async function insertRow(row) { + const $table = xTable.value; + const rid = Date.now(); + const record = { + folderName: `鏂版暟鎹�${rid}`, + id: rid, + parentRowId: row.rowId, // 闇�瑕佹寚瀹氱埗鑺傜偣锛岃嚜鍔ㄦ彃鍏ヨ鑺傜偣涓� + }; + const { row: newRow } = await $table.insert(record); + console.log(row,'99999993'); + + await $table.setTreeExpand(row, true); // 灏嗙埗鑺傜偣灞曞紑 + await $table.setEditRow(newRow); // 鎻掑叆瀛愯妭鐐� + } + function fnDelete(row) { + deleteFolderApi({ folderId: row.folderId }) + .then((res) => { + if (res.code == 0) { + fnGetList(); + createMessage.success(res.msg); + } + }) + .catch((err) => { + // createMessage.error(err); + }); + } + + function editRowEvent(row) { + const $table = xTable.value; + console.log(row,'---30494'); + + $table.setEditRow(row); + } + onMounted(() => { + fnGetList(); + }); +</script> + +<style scoped> + .sortable-row-demo .drag-btn { + font-size: 12px; + cursor: move; + } + + .sortable-row-demo .vxe-body--row.sortable-ghost, + .sortable-row-demo .vxe-body--row.sortable-chosen { + background-color: #dfecfb; + } +</style> diff --git a/src/views/email/Utils/index.vue b/src/views/email/Utils/index.vue new file mode 100644 index 0000000..ed65b65 --- /dev/null +++ b/src/views/email/Utils/index.vue @@ -0,0 +1,31 @@ +<template> + <div class="p-2"> + <a-tabs v-model:activeKey="activeKey"> + <a-tab-pane key="1" tab="甯歌"> + <Convention v-if="activeKey === '1'"></Convention> + </a-tab-pane> + <a-tab-pane key="2" tab="閭绠$悊" force-render> + <MailboxManagement v-if="activeKey === '2'"></MailboxManagement> + </a-tab-pane> + <a-tab-pane key="3" tab="蹇�熸枃鏈�" + ><QuickText v-if="activeKey === '3'"></QuickText + ></a-tab-pane> + <a-tab-pane key="4" tab="鏂囦欢澶�"><Folder v-if="activeKey === '4'" /></a-tab-pane> + <a-tab-pane key="5" tab="鏍囩"><Label v-if="activeKey==='5'" /></a-tab-pane> + <a-tab-pane key="6" tab="榛戝悕鍗�"><Blacklist v-if="activeKey === '6'" /></a-tab-pane> + </a-tabs> + </div> +</template> + +<script lang="ts" setup> + name: 'Utils'; + import { ref } from 'vue'; + const activeKey = ref('1'); + import MailboxManagement from './mailboxManagement.vue'; + import Convention from './convention.vue'; + import QuickText from './quickText.vue'; + import Folder from './folder.vue'; +import Label from './label.vue'; + import Blacklist from './blacklist.vue' +</script> +<style scoped lang="less"></style> diff --git a/src/views/email/Utils/label.vue b/src/views/email/Utils/label.vue new file mode 100644 index 0000000..e4039ed --- /dev/null +++ b/src/views/email/Utils/label.vue @@ -0,0 +1,260 @@ +<template> + <div class="p-2"> + <vxe-toolbar> + <template #buttons> + <div style="display: flex; align-items: flex-end"> + <span style="font-size: 1.25rem; font-weight: 600">鎴戠殑鏍囩</span + ><span style="margin-left: 5px; padding-bottom: 4px; font-size: 12px" + >绠$悊鎮ㄧ殑鎵�鏈変釜浜烘爣绛�</span + > + </div> + </template> + <template #tools> + <a-button type="primary" @click="showModal('add')">鏂板缓涓汉鏍囩</a-button> + </template> + </vxe-toolbar> + + <vxe-table + ref="xTable" + style="margin: 10px 0" + :data="demo.tableData" + @mounted="onMounted" + height="600" + > + <vxe-column width="40"> + <template #default> + <span class="drag-btn"> + <HolderOutlined /> + </span> + </template> + </vxe-column> + <vxe-column field="TagName" title="鏂囦欢澶瑰悕绉�" minWidth="250"> + <template #default="{ row }"> + <span class="my-d-f"> + <div + v-if="row.systemFlag" + class="bookmark" + :style="{ backgroundColor: row.tagColor }" + ></div> + <div v-else> + <ColorPicker + v-model="row.tagColor" + :type="2" + @change="fnRowColorChange($event, row)" + ></ColorPicker> + </div> + <a-tag class="ml-5" :color="row.tagColor">{{ row.tagName }}</a-tag> + </span> + </template> + </vxe-column> + <vxe-column field="age" title="鎿嶄綔" width="150"> + <template #default="{ row }"> + <a style="margin-right: 10px" @click="showModal('edit', row)">缂栬緫</a> + <a style="margin-right: 10px" @click="fnDeleteRow(row)">鍒犻櫎</a> + </template> + </vxe-column> + </vxe-table> + <a-modal :width="300" v-model:open="open" :title="`${title}涓汉鏍囩`" @ok="handleOk"> + <a-form ref="formRef" :model="form" style="margin-top: 20px"> + <a-form-item label="鏍囩棰滆壊" name="tagColor"> + <ColorPicker v-model="form.tagColor" :type="2"></ColorPicker> + </a-form-item> + <a-form-item + label="鏍囩鍚嶇О" + name="tagName" + :rules="[{ required: true, message: '璇疯緭鍏ユ爣绛惧悕绉�', trigger: 'blur' }]" + > + <a-input v-model:value="form.tagName" placeholder="璇疯緭鍏ユ爣绛惧悕绉�" /> + </a-form-item> + </a-form> + </a-modal> + </div> +</template> + +<script lang="ts" setup> + import { ref, computed, onMounted, nextTick, onUnmounted, reactive } from 'vue'; + import ColorPicker from '@/components/ColorPicker/index.vue'; + import { addTagApi, deleteTagApi, updateTagApi, getTagApi } from '@/api/email/userList'; + + // 鎺掑簭 + import { HolderOutlined } from '@ant-design/icons-vue'; + import Sortable from 'sortablejs'; + let sortable: any; + const demo = reactive({ + showHelpTip: false, + tableData: [], + }); + const xTable = ref(); + const rowDrop = () => { + const $table = xTable.value; + sortable = Sortable.create($table.$el.querySelector('.body--wrapper>.vxe-table--body tbody'), { + handle: '.drag-btn', + onEnd: (sortableEvent) => { + const newIndex = sortableEvent.newIndex as number; + const oldIndex = sortableEvent.oldIndex as number; + const currRow: Record<string, any> = demo.tableData.splice(oldIndex, 1)[0]; + // demo.tableData.splice(newIndex, 0, currRow); + updateTagApi({ + tagColor: currRow.tagColor, + tagName: currRow.tagName, + sortId: newIndex, + tagId: currRow.tagId, + }) + .then(() => { + fnGetList(); + }) + .catch(() => {}); + }, + }); + }; + + let initTime: any; + nextTick(() => { + // 鍔犺浇瀹屾垚涔嬪悗鍦ㄧ粦瀹氭嫋鍔ㄤ簨浠� + initTime = setTimeout(() => { + rowDrop(); + }, 500); + }); + + onUnmounted(() => { + clearTimeout(initTime); + if (sortable) { + sortable.destroy(); + } + }); + + function fnGetList() { + getTagApi({}).then((res) => { + console.log(res); + demo.tableData = res.data; + }); + } + + import { useMessage } from '@/hooks/web/useMessage'; + + const { createMessage } = useMessage(); + + const open = ref(false); + const formRef = ref(); + + interface formType { + tagColor: string; + tagName: string; + tagType: number; + systemFlag: boolean; + tagId?: number; + } + + const defaultForm: formType = { + tagColor: '#000000', + tagName: '', + tagType: 1, + systemFlag: false, + }; + + const form = ref<formType>({ ...defaultForm }); + + const title = ref('鏂板缓'); + const signType = ref('add'); + + const showModal = (type: string, row) => { + signType.value = type; + open.value = true; + + if (type == 'add') { + form.value = { ...defaultForm }; + } else { + title.value = '缂栬緫'; + nextTick(() => { + formRef.value.resetFields(); + form.value = { + tagColor: row.tagColor, + tagName: row.tagName, + tagType: row.tagType, + systemFlag: row.systemFlag, + tagId: row.tagId, + }; + }); + } + }; + + function fnDeleteRow(row) { + deleteTagApi({ tagId: row.tagId }).then((res) => { + if (res.code === 0) { + createMessage.success(res.msg); + fnGetList(); + } + }); + } + + function handleOk() { + nextTick(() => { + formRef.value + .validate() + .then(() => { + const api = signType.value == 'add' ? addTagApi : updateTagApi; + api(form.value).then((res) => { + if (res.code === 0) { + createMessage.success(res.msg); + fnGetList(); + open.value = false; + } + }); + }) + .catch(() => {}); + }); + } + + function fnRowColorChange(color, row) { + console.log(color, row); + const data = { + tagColor: row.tagColor, + tagName: row.tagName, + tagType: row.tagType, + systemFlag: row.systemFlag, + tagId: row.tagId, + }; + updateTagApi(data).then((res) => { + if (res.code === 0) { + createMessage.success(res.msg); + fnGetList(); + } + }); + } + + onMounted(() => { + fnGetList(); + }); +</script> + +<style scoped> + .sortable-row-demo .drag-btn { + font-size: 12px; + cursor: move; + } + + .sortable-row-demo .vxe-body--row.sortable-ghost, + .sortable-row-demo .vxe-body--row.sortable-chosen { + background-color: #dfecfb; + } + + .bookmark { + display: inline-block; + position: relative; + width: 14px; + height: 18px; + margin-right: 5px; + border-radius: 2px 2px 0 0; /* Rounded top corners */ + } + + .bookmark::after { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 8px; + background-color: #fff; + clip-path: polygon(50% 60%, 0% 100%, 100% 100%); /* Triangle at the bottom */ + } +</style> diff --git a/src/views/email/Utils/mailboxManagement.vue b/src/views/email/Utils/mailboxManagement.vue new file mode 100644 index 0000000..2809d78 --- /dev/null +++ b/src/views/email/Utils/mailboxManagement.vue @@ -0,0 +1,689 @@ +<template> + <div class="p-2"> + <vxe-toolbar> + <template #buttons> + <span style="font-size: 1.25rem; font-weight: 600">涓汉閭缁戝畾</span> + </template> + <template #tools> + <a-button style="margin-right: 20px" @click="fnCheckAll">妫�鏌ュ叏閮ㄩ偖绠�</a-button> + <a-button type="primary" @click="openAccount('add', '')">鏂板缓閭</a-button> + </template> + </vxe-toolbar> + <vxe-table + ref="xTable" + id="key" + style="margin: 10px 0" + :data="demo.tableData" + min-height="40px" + row-key + keep-source + :filter-config="{ showIcon: false }" + :row-config="{ isHover: true }" + :column-config="{ resizable: true }" + > + <vxe-column width="60"> + <template #default> + <span class="drag-btn"> + <HolderOutlined /> + </span> + </template> + </vxe-column> + <vxe-column show-overflow field="email" title="閭璐﹀彿" min-width="250"> + <template #default="{ row }"> + <!-- <HolderOutlined /> --> + <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"> + <template #default="{ row }"> + <span style="color: #999">{{ row.companyName }}</span> + </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="red" v-else>寮傚父</a-tag> + </div> + <div v-else> + <a-spin :indicator="indicator" /> + </div> + </template> + </vxe-column> + <vxe-column show-overflow field="action" title="鎿嶄綔" min-width="250"> + <template #default="{ row }"> + <a style="margin-right: 10px" @click="fnOpenAlias(row)">娣诲姞鍒悕閭</a> + <a style="margin-right: 10px" @click="openIsEmailValid(row)">妫�娴�</a> + <a style="margin-right: 10px" @click="openAccount('update', row.email)">淇敼</a> + <a style="margin-right: 10px" @click="openDelete(row)">瑙g粦</a> + </template> + </vxe-column> + </vxe-table> + <a-modal + :bodyStyle="{ maxHeight: '70vh', overflow: 'auto' }" + v-model:open="open" + :title="`${title}閭`" + :confirmLoading="loading" + @ok="fnHandleOk" + > + <div class="p-2"> + <a-form + style="margin-top: 20px" + layout="vertical" + :model="formData" + v-bind="{ span: 8 }" + ref="formRef" + :rules="rules" + > + <a-form-item v-if="isShow" label="鏀跺彂浠舵湇鍔″櫒楠岃瘉"> + <a-select + ref="select" + v-model:value="isCustom" + style="width: 120px" + @change="fnHandleChange" + > + <a-select-option key="onCustom" value="onCustom"> 涓庨偖绠辩浉鍚� </a-select-option> + <a-select-option key="custom" value="custom"> 鑷畾涔� </a-select-option> + </a-select> + </a-form-item> + <a-form-item name="email"> + <template v-slot:label> + 閭璐﹀彿 + <a-tooltip placement="right"> + <template #title> + <span>宸ヤ綔閭鏄偍澶勭悊鍏徃浜嬪姟鏃舵墍浣跨敤鐨勫姙鍏偖绠� 寤鸿鎮ㄤ笓鐢ㄤ簬鍔炲叕鐩殑</span> + </template> + <ExclamationCircleOutlined style="margin-left: 5px" /> + </a-tooltip> + </template> + <a-input :disabled="typeAccount === 2" v-model:value="formData.email" /> + </a-form-item> + <a-form-item v-if="isCustom == 'custom'" name="password" label="閭瀵嗙爜"> + <a-input-password + type="password" + v-model:value="formData.password" + placeholder="杈撳叆閭瀵嗙爜鎴栬�呮巿鏉冪爜" + /> + </a-form-item> + <a-form-item v-if="isShow" name="receiveProtocol" label="鍗忚绫诲瀷"> + <a-radio-group v-model:value="formData.receiveProtocol" name="radioGroup"> + <a-radio value="imap">IMAP</a-radio> + <a-radio value="pop3">POP3(涓嶆帹鑽�,鏃犳硶鍚屾鍙戜欢)</a-radio> + <!-- <a-radio value="exchange">Exchange</a-radio> --> + </a-radio-group> + <div style="color: red; font-size: 12px" + >鐢变簬exchange鍗忚鏀寔闂锛岀綉鏄撻偖绠辨帹鑽愪娇鐢↖MAP鍗忚銆� + 濡傞渶璋冩暣鍗忚绫诲瀷锛岃鑱旂郴灏忔弧瀹㈡湇</div + > + </a-form-item> + <!-- <a-form-item v-if="formData.receiveProtocol === 'exchange'" :name="['user', 'email']" label="Exchange鏈嶅姟鍣�" :rules="[{ type: 'email' }]"> + <a-row style="display: flex; align-items: center"> + <a-col :span="18"> + <a-input v-model:value="formData.email" placeholder="Exchange鏈嶅姟鍣�" + /></a-col> + <a-col :span="3"> + <a-checkbox v-model:value="formData.email">SSL</a-checkbox> + </a-col> + </a-row> + </a-form-item> + <a-form-item v-if="formData.receiveProtocol === 'exchange'" :name="['user', 'email']" label="鍩�" :rules="[{ type: 'email' }]"> + <a-row style="display: flex; align-items: center"> + <a-col :span="18"> + <a-input v-model:value="formData.email" placeholder="Exchange鏈嶅姟鍣�" + /></a-col> + <a-col :span="3"> + <a-checkbox v-model:value="formData.email">SSL</a-checkbox> + </a-col> + </a-row> + </a-form-item> --> + <a-form-item v-if="isShow" name="receiveHost" label="鏀堕偖浠舵湇鍔″櫒"> + <a-row style="display: flex; align-items: center"> + <a-col :span="12"> + <a-input v-model:value="formData.receiveHost" placeholder="鏀堕偖浠舵湇鍔″櫒" /> + </a-col> + <a-col :span="1" style="margin-right: 10px; margin-left: 5px">:</a-col> + <a-col :span="6" style="margin-right: 10px"> + <a-form-item-rest + ><a-input width="50px" v-model:value="formData.receivePort" placeholder="绔彛" + /></a-form-item-rest> + </a-col> + <a-col :span="3"> + <a-form-item-rest> + <a-checkbox v-model:checked="formData.receiveSSL" + >SSL</a-checkbox + ></a-form-item-rest + > + </a-col> + </a-row> + </a-form-item> + <a-form-item + style="margin-top: 22px" + v-if="isCustom == 'custom'" + name="receiveEmail" + label="鏀朵欢璐﹀彿" + > + <a-input v-model:value="formData.receiveEmail" /> + </a-form-item> + <a-form-item v-if="isCustom == 'custom'" name="receivePassword" label="鏀朵欢瀵嗙爜"> + <a-input v-model:value="formData.receivePassword" /> + </a-form-item> + <a-form-item v-if="isShow" name="smtpHost" label="鍙戦偖浠舵湇鍔″櫒"> + <a-row style="display: flex; align-items: center"> + <a-col :span="12"> + <a-input v-model:value="formData.smtpHost" placeholder="鍙戦偖浠舵湇鍔″櫒" /> + </a-col> + <a-col :span="1" style="margin-right: 10px; margin-left: 5px">:</a-col> + <a-col :span="6" style="margin-right: 10px"> + <a-form-item-rest + ><a-input width="50px" v-model:value="formData.smtpPort" placeholder="绔彛" + /></a-form-item-rest> + </a-col> + <a-col :span="3"> + <a-form-item-rest + ><a-checkbox v-model:checked="formData.smtpSSL">SSL</a-checkbox></a-form-item-rest + > + </a-col> + </a-row> + </a-form-item> + <a-form-item + style="margin-top: 22px" + v-if="isCustom == 'custom'" + name="smtpEmail" + label="鍙戜欢璐﹀彿" + > + <a-input v-model:value="formData.smtpEmail" /> + </a-form-item> + <a-form-item v-if="isCustom == 'custom'" name="smtpPassword" label="鍙戜欢瀵嗙爜"> + <a-input v-model:value="formData.smtpPassword" /> + </a-form-item> + <a-form-item v-if="isShow" name="proxyFlag"> + <template v-slot:label> + <a-tooltip placement="right"> + <template #title> + <span>寮�鍚悗缃戠粶鎻愰��</span> + </template> + <QuestionCircleOutlined style="margin-right: 5px" /> + </a-tooltip> + 鑷畾涔変唬鐞� + </template> + <a-radio-group v-model:value="formData.proxyFlag" name="radioGroup"> + <a-radio :value="true">寮�鍚�</a-radio> + <a-radio :value="false">鍏抽棴</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item v-if="isShow" name="biSyncFlag"> + <template v-slot:label> + <a-tooltip placement="right"> + <template #title> + <span + >寮�鍚悗锛屽浜庢柊缁戝畾鐨勯偖绠憋紝鍏ㄩ噺鍚屾鏂囦欢澶瑰強鏂囦欢澶瑰唴鐨勯偖浠躲�傚浜庡凡缁忕粦瀹氱殑閭锛屽叏閲忓悓姝ユ枃浠跺す鍙婃枃浠跺す鏂版敹鍙栫殑閭欢锛屽巻鍙查偖浠朵笉绉诲姩</span + > + </template> + <QuestionCircleOutlined style="margin-right: 5px" /> + </a-tooltip> + 鍚屾鏂囦欢澶� + </template> + <a-radio-group v-model:value="formData.biSyncFlag" name="radioGroup"> + <a-radio :value="true">寮�鍚�</a-radio> + <a-radio :value="false">鍏抽棴</a-radio> + </a-radio-group> + </a-form-item> + </a-form> + <a @click="fnIsShow" v-if="!isShow"> 鎵嬪姩閰嶇疆</a> + <a @click="fnIsShow" v-else> 鏀惰捣鎵嬪姩閰嶇疆</a> + + <a-divider style="margin-top: 50px" /> + <div style="font-size: 18px">甯姪鏂囨。</div> + <div + ><a href="https://www.yuque.com/help.xiaoman/qwwqei/vkr8p7" target="_blank" rel="noopener" + >1銆佹煡鐪嬬粦瀹氶偖绠卞け璐ョ殑甯歌鍘熷洜鍙婅В鍐虫柟妗�</a + ></div + > + <div + ><a href="https://www.yuque.com/help.xiaoman/qwwqei/sl9xuk" target="_blank" rel="noopener" + >2銆佷簡瑙e父瑙佸嚑绫婚偖绠辩殑鍏蜂綋缁戝畾鏂规硶</a + ></div + ></div + > + </a-modal> + + <a-modal + v-model:open="openDrawerDetail" + :destroyOnClose="true" + title="纭瑙g粦" + :loading="loading" + @cancel="fnHandleDetailCancel" + > + <div style="padding: 20px 20px 20px 0; color: #000; font-size: 18px"> + 瑙g粦<span style="padding: 0 5px"> {{ deleteEmail }} </span>鍚� + </div> + <ul> + <li v-for="(warning, index) in removalWarnings" :key="index"> + <span class="bullet">鈥�</span> {{ warning }} + </li> + </ul> + <div style="margin-top: 20px"> + 鍥犵粦瀹氬紓甯搁渶璋冩暣锛屽彲鍦ㄩ偖绠便�屼慨鏀广�嶅鏇存敼 濡備慨鏀规椂閬囧埌鍥伴毦锛屽彲鑱旂郴瀹㈡湇 + </div> + <template #footer> + <a-button @click="fnHandleDetailCancel">鍙栨秷</a-button> + + <a-button + type="primary" + danger + @click="fnHandleDetailOk" + :disabled="countdown !== 0" + :loading="loading" + >瑙g粦<span v-show="countdown > 0"> ({{ countdown }}) </span></a-button + > + </template> + </a-modal> + <a-modal + v-model:open="openDrawerIsEmailValid" + :destroyOnClose="true" + title="閭妫�娴�" + v-model:value="isEmailValid" + > + <div style="margin-top: 20px; margin-right: 10%" class="p-4"> + <a-form labelAlign="right" :labelCol="{ span: 8 }"> + <a-form-item label="閭"> + {{ isEmailValid.email }} + </a-form-item> + <a-form-item label="閭瀵嗙爜"> + <div class="form-item"> + <div class="left" :class="checkStatus ? 'isColor' : 'isRed'"> ********</div> + <div class="right"> + <a-spin v-if="!isCheck" :indicator="indicator" /> + <CheckCircleOutlined v-else-if="checkStatus" class="isColor" /> + <CloseCircleOutlined v-else class="isRed" /> + </div> + </div> + </a-form-item> + <a-form-item label="鍗忚绫诲瀷"> + {{ isEmailValid.email }} + </a-form-item> + <a-form-item label="鏀堕偖浠舵湇鍔″櫒"> + <div class="form-item"> + <div class="left" :class="checkStatus ? 'isColor' : 'isRed'"> + {{ isEmailValid.receiveHost }} + </div> + <div class="right" + ><a-spin v-if="!isCheck" :indicator="indicator" /><CheckCircleOutlined + v-else-if="checkStatus" + class="isColor" + /> + <CloseCircleOutlined v-else class="isRed" /> + </div> </div + ></a-form-item> + <a-form-item label="鍙戦偖浠舵湇鍔″櫒"> + <div class="form-item"> + <div class="left" :class="checkStatus ? 'isColor' : 'isRed'"> + {{ isEmailValid.smtpHost }} + </div> + <div class="right" + ><a-spin v-if="!isCheck" :indicator="indicator" /> + <CheckCircleOutlined v-else-if="checkStatus" class="isColor" /> + <CloseCircleOutlined v-else class="isRed" /> + </div> </div + ></a-form-item> + <a-form-item label="鑷畾涔変唬鐞�"> + <span style="margin-right: 5px"> {{ isEmailValid.proxyFlag ? '寮�鍚�' : '鍏抽棴' }} </span> + <a-tooltip placement="right"> + <template #title> + <span>寮�鍚悗缃戠粶鎻愰��</span> + </template> + <QuestionCircleOutlined style="margin-right: 5px" /> + </a-tooltip> + </a-form-item> + <a-form-item label="鍚屾鏂囦欢澶�"> + <span style="margin-right: 5px"> {{ isEmailValid.biSyncFlag ? '寮�鍚�' : '鍏抽棴' }} </span> + <a-tooltip placement="right"> + <template #title> + <span + >寮�鍚悗锛屽浜庢柊缁戝畾鐨勯偖绠憋紝鍏ㄩ噺鍚屾鏂囦欢澶瑰強鏂囦欢澶瑰唴鐨勯偖浠躲��<br />瀵逛簬宸茬粡缁戝畾鐨勯偖绠憋紝鍏ㄩ噺鍚屾鏂囦欢澶瑰強鏂囦欢澶规柊鏀跺彇鐨勯偖浠讹紝鍘嗗彶閭欢涓嶇Щ鍔�</span + > + </template> + <QuestionCircleOutlined style="margin-right: 5px" /> + </a-tooltip> + </a-form-item> + </a-form> + </div> + <template #footer> + <div style="text-align: center"> + <a-button type="primary" @click="openIsEmailValid(isEmailValid)">閲嶆柊妫�娴�</a-button> + </div> + </template> + </a-modal> + <a-modal + v-model:open="openAlias" + :title="`${titleAlias}娣诲姞鍒悕閭`" + :confirmLoading="loading" + @ok="fnHandleAliasOk" + > + <a-form + ref="aliasRef" + style="margin-top: 20px" + layout="vertical" + :model="aliasFormData" + v-bind="{ span: 8 }" + > + <a-form-item + style="margin-top: 22px" + name="aliasEmailName" + label="鍒悕閭" + :rules="[{ required: true, message: '璇疯緭鍏ュ埆鍚嶉偖绠�', trigger: 'blur' }]" + > + <a-input v-model:value="aliasFormData.aliasEmailName" /> + </a-form-item> + </a-form> + </a-modal> + </div> +</template> + +<script lang="ts" setup> + name: 'mailboxManagement'; + import { + HolderOutlined, + ExclamationCircleOutlined, + QuestionCircleOutlined, + CheckCircleOutlined, + CloseCircleOutlined, + } from '@ant-design/icons-vue'; + import { ref, reactive, onUnmounted } from 'vue'; + import { + addAccountApi, + getAccountApi, + updateAccountApi, + deleteAccountApi, + getAccountListApi, + isEmailValidApi, + } from '@/api/email/userList'; + const loading = ref(false); + import Sortable from 'sortablejs'; + let sortable: any; + const demo = reactive({ + showHelpTip: false, + tableData: [], + }); + const xTable = ref(); + const rowDrop = () => { + const $table = xTable.value; + sortable = Sortable.create($table.$el.querySelector('.body--wrapper>.vxe-table--body tbody'), { + handle: '.drag-btn', + onEnd: (sortableEvent) => { + const newIndex = sortableEvent.newIndex as number; + const oldIndex = sortableEvent.oldIndex as number; + const currRow = demo.tableData.splice(oldIndex, 1)[0]; + demo.tableData.splice(newIndex, 0, currRow); + }, + }); + }; + + let initTime: any; + nextTick(() => { + // 鍔犺浇瀹屾垚涔嬪悗鍦ㄧ粦瀹氭嫋鍔ㄤ簨浠� + initTime = setTimeout(() => { + rowDrop(); + }, 500); + }); + + onUnmounted(() => { + clearTimeout(initTime); + if (sortable) { + sortable.destroy(); + } + }); + + function fnMailList() { + getAccountListApi() + .then((res) => { + if (res.code == 0) { + demo.tableData = res.data; + } + console.log(res); + }) + .catch((err) => { + console.log(err); + demo.tableData = []; + }); + } + fnMailList(); + const defaultFormData = { + email: '', + password: '', + aliasEmail: '', + biSyncFlag: false, + proxyFlag: true, + receiveProtocol: 'imap', + receiveSSL: false, + receivePort: '', + receiveHost: '', + smtpSSL: false, + smtpPort: '', + smtpHost: '', + invalid: '', + mailType: 0, + }; + const formData = ref<Record<string, any>>(defaultFormData); + const isCustom = ref('onCustom'); + function fnHandleChange(e) { + console.log(e, 'iririririr'); + + isCustom.value = e; + } + const checkReceivePort = async (value) => { + console.log(formData.value.receivePort, '=-3--3', value); + if (value === '') { + return Promise.reject('璇疯緭鍏ユ敹浠舵湇鍔″櫒'); + } + if (!formData.value.receivePort) { + return Promise.reject('璇疯緭鍏ユ敹浠舵湇鍔″櫒銆佺鍙�'); + } + return Promise.resolve(); + }; + const checkSmtpHost = async (value) => { + if (value === '') { + return Promise.reject('璇疯緭鍏ユ敹浠舵湇鍔″櫒'); + } + if (!formData.value.smtpHost) { + return Promise.reject('璇疯緭鍏ユ敹浠舵湇鍔″櫒銆佺鍙�'); + } + return Promise.resolve(); + }; + const rules = ref({ + email: [ + { required: true, message: '璇疯緭鍏ラ偖绠卞湴鍧�', trigger: 'blur' }, + { type: 'email', message: '璇疯緭鍏ユ纭殑閭鍦板潃', trigger: ['blur', 'change'] }, + ], + password: [{ required: true, message: '璇疯緭鍏ュ瘑鐮�', trigger: 'blur' }], + receiveHost: [{ required: true, validator: checkReceivePort }], + smtpHost: [{ required: true, validator: checkSmtpHost }], + smtpPassword: [{ required: true, message: '璇疯緭鍏ュ彂浠跺瘑鐮�', trigger: 'blur' }], + smtpEmail: [{ required: true, message: '璇疯緭鍏ュ彂浠惰处鍙�', trigger: 'blur' }], + receiveEmail: [{ required: true, message: '璇疯緭鍏ユ敹浠跺瘑鐮�', trigger: 'blur' }], + receivePassword: [{ required: true, message: '璇疯緭鍏ユ敹浠惰处鍙�', trigger: 'blur' }], + }); + import { useMessage } from '@/hooks/web/useMessage'; + const { createMessage } = useMessage(); + const formRef = ref(); + + const open = ref(false); + const fnHandleOk = () => { + formRef.value.validate().then(() => { + const data = formData.value; + if (isShow.value == true) { + data.mailType = isCustom.value === 'onCustom' ? 1 : 2; + } + const api = typeAccount.value === 2 ? updateAccountApi : addAccountApi; + loading.value = true; + api(data) + .then((res) => { + if (res.code === 0) { + createMessage.success(res.msg); + } + loading.value = false; + fnMailList(); + }) + .catch((err) => { + loading.value = false; + }); + open.value = false; + }); + }; + + const isShow = ref(false); + function fnIsShow() { + isShow.value = !isShow.value; + } + const title = ref('娣诲姞'); + const typeAccount = ref(1); + function openAccount(type, email) { + formData.value = { ...defaultFormData }; + try { + if (type == 'add') { + title.value = '娣诲姞'; + // formData.value = {}; + isShow.value = false; + typeAccount.value = 1; + } else { + title.value = '淇敼'; + typeAccount.value = 2; + isShow.value = true; + getAccountApi({ mail: email }).then((res) => { + formData.value = res.data; + }); + } + open.value = true; + } catch (error) { + open.value = false; + } + } + + // 鍒犻櫎 + + const removalWarnings = [ + '姝ら偖绠变笅鐨勫巻鍙查偖浠朵笉鍙煡鐪�', + '鎮ㄤ笉鑳戒娇鐢ㄦ閭鏀跺彂浠�', + '鍚屾鍒犻櫎鑷畾涔夋枃浠跺す銆佹爣绛俱�佹敹鍙戜欢瑙勫垯鍜岀粦瀹氱殑鍒悕閭銆傞噸鏂扮粦瀹氬悗涔熶笉鑳芥仮澶�', + '鐩稿叧閭欢绾跨储浠呭彲鏌ョ湅涓嶅彲鎿嶄綔', + ]; + const deleteEmail = ref(); + const openDrawerDetail = ref<boolean>(false); + const accountId = ref(); + + const fnHandleDetailCancel = () => { + countdown.value = 10; + openDrawerDetail.value = false; + clearInterval(intervalId); + }; + function openDelete(row) { + openDrawerDetail.value = true; + deleteEmail.value = row.email; + accountId.value = row.accountId; + countdown.value = 10; + clearInterval(intervalId); + startCountdown(); + } + function fnHandleDetailOk() { + openDrawerDetail.value = false; + deleteAccountApi({ accountId: accountId.value }) + .then((res) => { + if (res.code === 0) { + createMessage.success(res.msg); + fnMailList(); + } + }) + .catch((err) => { + // createMessage.error(err.msg); + }); + } + const countdown = ref(10); + let intervalId: any; + + const startCountdown = () => { + if (countdown.value > 0) { + intervalId = setInterval(() => { + countdown.value--; + if (countdown.value === 0) { + clearInterval(intervalId); + } + }, 1000); + } + }; + + import { LoadingOutlined } from '@ant-design/icons-vue'; + import { h, nextTick } from 'vue'; + // 閭妫�娴� + const isCheck = ref(false); + const checkStatus = ref(false); + + const openDrawerIsEmailValid = ref(false); + function openIsEmailValid(row) { + openDrawerIsEmailValid.value = true; + isEmailValid.value = row; + const email = { email: row.email }; + isCheck.value = false; + isEmailValidApi(email).then((res) => { + if (res.code == 0) { + isCheck.value = true; + checkStatus.value = res.data.status; + } + }); + } + const indicator = h(LoadingOutlined, { + style: { + fontSize: '24px', + }, + spin: true, + }); + + const isEmailValid = ref<Record<string, any>>({}); + + // 鍒悕澶勭悊 + const aliasFormData = ref<Record<string, any>>({}); + const openAlias = ref(false); + const titleAlias = ref(''); + function fnOpenAlias(row) { + openAlias.value = true; + titleAlias.value = row.email; + } + const aliasRef = ref(); + function fnHandleAliasOk() { + nextTick(() => { + aliasRef.value.validate().then(() => { + openAlias.value = false; + }); + }); + } + + // 鍏ㄩ儴妫�娴� + const isCheckAll = ref(false); + function fnCheckAll() { + isCheckAll.value = true; + setTimeout(() => { + isCheckAll.value = false; + }, 3000); + } +</script> +<style scoped lang="less"> + .bullet { + display: inline-block; + margin-right: 5px; + color: #000; + font-size: 16px; + } + + .form-item { + display: flex; + align-items: center; + justify-content: space-between; + } + + .isColor { + color: #18a561; + } + + .isRed { + color: red; + } +</style> diff --git a/src/views/email/Utils/quickText.vue b/src/views/email/Utils/quickText.vue new file mode 100644 index 0000000..a99f052 --- /dev/null +++ b/src/views/email/Utils/quickText.vue @@ -0,0 +1,202 @@ +<template> + <div class="p-2"> + <vxe-toolbar> + <template #buttons> + <div style="display: flex; align-items: flex-end"> + <span style="font-size: 1.25rem; font-weight: 600">蹇�熸枃鏈�</span + ><span style="margin-left: 5px; padding-bottom: 4px; font-size: 12px" + >浣滀负妯℃澘浣跨敤锛屽湪鍐欎俊鏃跺揩閫熷垱寤哄唴瀹�</span + > + </div> + </template> + <template #tools> + <a-button type="primary" @click="showDrawer('add', '')">鏂板缓鏂囨湰</a-button> + </template> + </vxe-toolbar> + + <vxe-table ref="xTable" style="margin: 10px 0" :data="demo.tableData" @mounted="onMounted"> + <vxe-column width="60"> + <template #default> + <span class="drag-btn"> + <HolderOutlined /> + </span> + </template> + </vxe-column> + <vxe-column field="userName" title="鏍囬" width="250"></vxe-column> + <vxe-column field="content" title="鍐呭" min-width="250"></vxe-column> + <vxe-column field="age" title="鎿嶄綔" width="150"> + <template #default="{ row }"> + <a style="margin-right: 10px" @click="showDrawer('update', row)">缂栬緫</a> + <a style="margin-right: 10px" @click="fnDelete(row)">鍒犻櫎</a> + </template> + </vxe-column> + </vxe-table> + <a-drawer :title="`${title}鏂囨湰`" placement="right" :open="open" @close="onClose" width="600"> + <a-form ref="formRef" :model="form" style="margin-top: 20px"> + <a-form-item + label="鏍囬" + name="textName" + :rules="[{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }]" + > + <a-input v-model:value="form.textName" placeholder="璇疯緭鍏ュ悕绉�" /> + </a-form-item> + <a-form-item + label="鍐呭" + name="content" + :rules="[{ required: true, message: '璇疯緭鍏ュ唴瀹�', trigger: 'blur' }]" + > + <Tinymce v-model="form.content" :isElse="false" :isText="false" :isImg="false"></Tinymce> + </a-form-item> + </a-form> + <template #footer> + <div style="margin-top: 20px; text-align: center"> + <div style="margin-bottom: 20px" + >鍐欓偖浠舵椂锛岃緭鍏� + <span style="color: red">&</span>鏍囬鍏抽敭璇嶏紝鑱旀兂鍑哄揩閫熸枃鏈紝蹇嵎鎻掑叆</div + > + <a-button style="margin-right: 8px" @click="onClose">鍙栨秷</a-button> + <a-button type="primary" @click="onOk">淇濆瓨</a-button> + </div> + </template> + </a-drawer> + </div> +</template> + +<script lang="ts" setup> + import { ref, computed, onMounted, nextTick, onUnmounted, reactive } from 'vue'; + import { Tinymce } from '@/components/Tinymce'; + import { + getQuickTextApi, + addQuickTextApi, + updateQuickTextApi, + deleteQuickTextApi, + } from '@/api/email/userList'; + + // 鎺掑簭 + import { HolderOutlined } from '@ant-design/icons-vue'; + import Sortable from 'sortablejs'; + let sortable: any; + const demo = reactive({ + showHelpTip: false, + tableData: [], + }); + const xTable = ref(); + const rowDrop = () => { + const $table = xTable.value; + sortable = Sortable.create($table.$el.querySelector('.body--wrapper>.vxe-table--body tbody'), { + handle: '.drag-btn', + onEnd: (sortableEvent) => { + const newIndex = sortableEvent.newIndex as number; + const oldIndex = sortableEvent.oldIndex as number; + const currRow:Record<string, any> = demo.tableData.splice(oldIndex, 1)[0]; + // demo.tableData.splice(newIndex, 0, currRow); + updateQuickTextApi({ + textId: currRow.textId, + textName: currRow.textName, + content: currRow.content, + sortId: newIndex, + }).then(() => { + fnGetList(); + }).catch(()=>{}); + }, + }); + }; + + let initTime: any; + nextTick(() => { + // 鍔犺浇瀹屾垚涔嬪悗鍦ㄧ粦瀹氭嫋鍔ㄤ簨浠� + initTime = setTimeout(() => { + rowDrop(); + }, 500); + }); + + onUnmounted(() => { + clearTimeout(initTime); + if (sortable) { + sortable.destroy(); + } + }); + + const form = ref<Record<string, any>>({}); + const title = ref('鏂板缓'); + const open = ref<boolean>(false); + const handleType = ref('add'); + const formRef = ref(); + const showDrawer = (type, row) => { + handleType.value = type; + open.value = true; + if (type == 'add') { + form.value = { + textName: '', + content: '', + }; + } else { + title.value = '缂栬緫'; + nextTick(() => { + formRef.value.resetFields(); + form.value = Object.assign(form.value, row); + }); + } + }; + const api = computed(() => { + return handleType.value == 'add' ? addQuickTextApi : updateQuickTextApi; + }); + + function fnDelete(row) { + deleteQuickTextApi({ textId: row.textId }).then((res) => { + if (res.code == 0) { + fnGetList(); + } + }); + } + function fnGetList() { + getQuickTextApi({}).then((res) => { + console.log(res); + demo.tableData = res.data; + }); + } + const onClose = () => { + open.value = false; + }; + import { useMessage } from '@/hooks/web/useMessage'; + const { createMessage } = useMessage(); + const onOk = () => { + formRef.value.validate().then(() => { + const data: Record<string, any> = { + textName: form.value.textName, + content: form.value.content, + }; + if (handleType.value != 'add') { + data.textId = form.value.textId; + } + api + .value(data) + .then((res) => { + if (res.code === 0) { + createMessage.success(res.msg); + } + fnGetList(); + onClose(); + }) + .catch((e) => { + createMessage.warning(e); + }); + }); + }; + + onMounted(() => { + fnGetList(); + }); +</script> + +<style scoped> + .sortable-row-demo .drag-btn { + font-size: 12px; + cursor: move; + } + + .sortable-row-demo .vxe-body--row.sortable-ghost, + .sortable-row-demo .vxe-body--row.sortable-chosen { + background-color: #dfecfb; + } +</style> diff --git a/src/views/email/components/Editor.vue b/src/views/email/components/Editor.vue deleted file mode 100644 index 9e7933d..0000000 --- a/src/views/email/components/Editor.vue +++ /dev/null @@ -1,426 +0,0 @@ -<template> - <div> - <div class="p-1"> - <a-form ref="formRef" :label-col="{ span: 2 }" :wrapper-col="{ span: 24 }" :model="modelRef"> - <a-form-item :wrapper-col="{ span: 10 }"> - <div style="display: flex; justify-content: space-evenly"> - <a-button type="primary" shape="round" @click="fnHandleSubmit(modelRef)">鍙戦��</a-button> - <a-button shape="round">瀛樿崏绋�</a-button> - <a-button shape="round">棰勮</a-button> - <a-button shape="round">鎻愪氦瀹℃壒</a-button> - <a-button shape="round">鍙栨秷</a-button></div - > - </a-form-item> - <a-form-item label="鍙戜欢浜�" v-bind="validateInfos.sender" :wrapper-col="{ span: 12 }"> - <a-select - v-model:value="modelRef.sender" - placeholder="閫夋嫨鍙戜欢浜�" - show-search - :field-names="{ label: 'name', value: 'id' }" - :options="state.data" - :filter-option="fnFilterOption" - > - </a-select> - </a-form-item> - <a-form-item label="鏀朵欢浜�" v-bind="validateInfos.recipients" :wrapper-col="{ span: 18 }"> - <a-row> - <a-col class="gutter-row" :span="16"> - <a-select - mode="multiple" - show-search - placeholder="璇烽�夋嫨鏀朵欢浜烘垨鑰呰緭鍏ユ敹浠朵汉閭" - v-model:value="modelRef.recipients" - :options="state.data" - :field-names="{ label: 'name', value: 'id' }" - :maxTagCount="4" - :filter-option="fnFilterOption" - /> - </a-col> - <a-col - class="gutter-row" - :span="2" - style="display: flex; align-items: center; justify-content: center" - > - <plus-circle-outlined - style="color: rgb(0 0 0 / 45%)" - @click="fnHandleSelect('recipients')" - /> - </a-col> - <a-col class="gutter-row" :span="1"> - <a-form-item-rest> - <a-button - shape="round" - type="link" - block - size="small" - @click="ccCheckboxChange($event)" - > - {{ !isSecretDeliveryPerson ? '鎶勯��' : '鍙栨秷鎶勯��' }} - </a-button> - </a-form-item-rest> - </a-col> - <a-col class="gutter-row" :span="1"> - <a-form-item-rest> - <a-button - shape="round" - type="link" - block - size="small" - @click="bccCheckboxChange($event)" - > - {{ !isCRecipient ? '瀵嗛��' : '鍙栨秷瀵嗛��' }} - </a-button> - </a-form-item-rest> - </a-col> - </a-row> - </a-form-item> - <span style="position: relative; top: -18px; left: 9%" v-if="modelRef.recipients.length"> - {{ userParticulars && `${userParticulars.name}<${userParticulars.email}` }}> - <span v-if="!userParticulars.title" style="margin-right: 10px; margin-left: 5px"> - 鏆傛湭鏌ヨ鍒拌瀹㈡埛鐨勫綋鍦版椂闂� - <a @click="fnHandleTimeZone(userParticulars, 'add')">琛ュ厖鏃跺尯</a> - </span> - <span v-else> - {{ userParticulars.title }} - <a @click="fnHandleTimeZone(userParticulars, 'update')">鏃跺尯涓嶅锛�</a> - </span> - - <a v-if="modelRef.recipients.length > 2">鏌ョ湅鍏ㄩ儴</a> - </span> - - <a-form-item - label="鎶勯�佷汉" - v-bind="validateInfos.ccRecipients" - v-if="isSecretDeliveryPerson" - :wrapper-col="{ span: 18 }" - > - <a-row> - <a-col class="gutter-row" :span="16"> - <a-select - mode="multiple" - show-search - placeholder="璇烽�夋嫨鎶勯�佷汉鎴栬�呰緭鍏ユ妱閫佷汉閭" - v-model:value="modelRef.ccRecipients" - :options="state.data" - :field-names="{ label: 'name', value: 'id' }" - :maxTagCount="4" - :filter-option="fnFilterOption" - /> - </a-col> - <a-col - class="gutter-row" - :span="2" - style="display: flex; align-items: center; justify-content: center" - > - <plus-circle-outlined - style="color: rgb(0 0 0 / 45%)" - @click="fnHandleSelect('ccRecipients')" - /> - </a-col> - </a-row> - </a-form-item> - <a-form-item - label="瀵嗛�佷汉" - v-bind="validateInfos.bccRecipients" - v-if="isCRecipient" - :wrapper-col="{ span: 18 }" - > - <a-row> - <a-col class="gutter-row" :span="16"> - <a-select - mode="multiple" - show-search - placeholder="璇烽�夋嫨鎶勯�佷汉鎴栬�呰緭鍏ユ妱閫佷汉閭" - v-model:value="modelRef.bccRecipients" - :options="state.data" - :field-names="{ label: 'name', value: 'id' }" - :maxTagCount="4" - :filter-option="fnFilterOption" - /> - </a-col> - <a-col - class="gutter-row" - :span="2" - style="display: flex; align-items: center; justify-content: center" - > - <plus-circle-outlined - style="color: rgb(0 0 0 / 45%)" - @click="fnHandleSelect('bccRecipients')" - /> - </a-col> - </a-row> - </a-form-item> - <div - style="position: relative; top: -20px; left: 9%; color: #909090; font-size: 12px" - v-if="modelRef.recipients.length" - > - {{ `${userLength}/100 鏀朵欢浜恒�佹妱閫佷汉鍜屽瘑閫佺殑鑱旂郴浜烘�绘暟涓嶅彲瓒呰繃100` }} - </div> - <a-form-item - label="涓婚" - v-bind="validateInfos.subject" - :wrapper-col="{ span: 12 }" - :label-col="{ span: 2 }" - > - <a-input placeholder="璇疯緭鍏ラ偖浠朵富棰�" v-model:value="modelRef.subject" /> - </a-form-item> - <a-form-item - v-bind="validateInfos.content" - :wrapper-col="{ span: 24 }" - :label-col="{ span: 2 }" - > - <Tinymce @change = "fnChangeContent"></Tinymce> - </a-form-item> - </a-form> - </div> - <SelectUser - ref="selectUserRef" - v-model="openSelectUser" - :selectIds="selectIds" - :idName="idName" - :title="selectUserTitle" - @updateData="fnSelectUser" - /> - <a-modal - v-model:open="openTimeZone" - :title="`${titleTimeZone}鏃跺尯`" - :confirm-loading="confirmLoading" - @ok="fnHandleTimeZoneOk" - > - <a-form - ref="formTimeZoneRef" - :label-col="{ span: 6 }" - :wrapper-col="{ span: 24 }" - :model="modelRef" - > - <a-form-item label="鍥藉鍦板尯锛�" :wrapper-col="{ span: 12 }"> - <a-select - v-model:value="modelRef.sender" - placeholder="閫夋嫨鍥藉鍦板尯" - show-search - :field-names="{ label: 'name', value: 'id' }" - :options="state.data" - > - </a-select> - </a-form-item> - <a-form-item label="鏃跺尯锛�" :wrapper-col="{ span: 12 }"> - <a-select - v-model:value="modelRef.sender" - placeholder="閫夋嫨鏃跺尯" - show-search - :field-names="{ label: 'name', value: 'id' }" - :options="state.data" - > - </a-select> - </a-form-item> - <a-form-item :label-col="{ span: 6 }"> - <a-row> - <a-col span="6"> </a-col> - <a-col span="18"> - <div style="color: #909090" - >褰撳湴瀹炴椂鏃堕棿鏍规嵁鏃跺尯淇℃伅鏄剧ず<br /> - 鍥藉鍦板尯/鏃跺尯淇℃伅鍦ㄥ鎴疯祫鏂�-鐗瑰緛淇℃伅鏌ョ湅</div - > - </a-col> - </a-row> - </a-form-item> - </a-form> - </a-modal> - </div> -</template> - -<script lang="ts" setup> - import { ref, reactive, computed } from 'vue'; - import { useMessage } from '@/hooks/web/useMessage'; - import { Tinymce } from '@/components/Tinymce'; - import { PlusCircleOutlined } from '@ant-design/icons-vue'; - import SelectUser from './SelectUser.vue'; - import { Form } from 'ant-design-vue'; - const modelRef = reactive({ - sender: '', - recipients: [], - ccRecipients: [], - bccRecipients: [], - fileUNID:[], - subject: '', - content: '', - }); - const rulesRef = reactive({ - sender: [ - { - required: true, - message: '鍙戜欢浜轰笉鑳戒负绌�', - }, - ], - recipients: [ - { - required: true, - message: '鏀朵欢浜轰笉鑳戒负绌�', - }, - ], - subject: [ - { - required: true, - message: '涓婚涓嶈兘涓虹┖', - }, - ], - content: [ - { - required: true, - message: '鍐呭涓嶈兘涓虹┖', - }, - ], - }); - const useForm = Form.useForm; - const { validate, validateInfos } = useForm(modelRef, rulesRef); - - const formRef = ref(); - - let isSecretDeliveryPerson = ref(false); - - let isCRecipient = ref(false); - const handleCheckboxChange = (ref, e) => { - ref.value = !ref.value; - }; - - const ccCheckboxChange = (e) => { - handleCheckboxChange(isSecretDeliveryPerson, e); - }; - const bccCheckboxChange = (e) => { - handleCheckboxChange(isCRecipient, e); - }; - - const { createMessage } = useMessage(); - import { getUserListApi,sendingMailApi,receiveApi } from '@/api/email/userList'; - // 瀹氫箟鐘舵�佺鐞嗗璞� - const state = reactive({ - data: [], - fetching: false, - error: null, - }); - - // 鑾峰彇鐢ㄦ埛鍒楄〃鐨勫嚱鏁� - const fnGetUserList = async (params) => { - try { - state.fetching = true; - const res = await getUserListApi(params); - const data = await receiveApi({mail:'574600396@qq.com'}) - console.log(data,'--------'); - - - if (res && res.items && Array.isArray(res.items)) { - state.data = res.items; - } else { - console.error('Invalid response format:', res); - } - } catch (error) { - console.error('Failed to fetch user list:', error); - } finally { - state.fetching = false; - } - }; - - // 鍒濆鍖栫敤鎴锋暟鎹� - const fetchData = async () => { - await fnGetUserList({ page: 1, pageSize: 30 }); - }; - console.log(state.data, 'state.data'); - // 璋冪敤鍒濆鍖栧嚱鏁� - fetchData(); - - const fnFilterOption = (input: string, option: any) => { - return option.name.toLowerCase().indexOf(input.toLowerCase()) >= 0; - }; - - const openSelectUser = ref(false); - const selectUserTitle = ref('閫夋嫨鏀朵欢浜�'); - const selectIds = ref([]); - const idName = ref(''); - const fnHandleSelect = (e) => { - if (e === 'recipients') { - selectUserTitle.value = '閫夋嫨鏀朵欢浜�'; - idName.value = e; - } - if (e === 'ccRecipients') { - selectUserTitle.value = '閫夋嫨鎶勯�佷汉'; - idName.value = e; - } - if (e === 'bccRecipients') { - selectUserTitle.value = '閫夋嫨瀵嗛�佷汉'; - idName.value = e; - } - selectIds.value = modelRef[e]; - openSelectUser.value = true; - }; - - function fnChangeContent(e) { - modelRef.content = e.content; - modelRef.fileUNID = e.fileUNID - } - function fnHandleSubmit(values: any) { - validate() - .then((res) => { - - sendingMailApi(modelRef).then((res=>{ - - })) - createMessage.success('鍙戦�佹垚鍔�'); - }) - .catch((error) => { - createMessage.error('琛ㄥ崟楠岃瘉澶辫触'); - }); - } - const fnSelectUser = (e) => { - modelRef[e.idName] = e.selectedRowKeys; - }; - - const userLength = computed(() => { - return ( - modelRef.recipients.length + modelRef.ccRecipients.length + modelRef.bccRecipients.length - ); - }); - - interface Recipient { - value: string; - email: string; - title: string; - } - - const userParticulars = computed<Recipient | null>(() => { - const recipientId = modelRef.recipients?.[0]; - if (!recipientId) { - console.error('Recipient ID is not defined.'); - return { value: '', email: '', title: '' }; - } - // 浣跨敤 find 鏂规硶鏌ユ壘鍖归厤椤� - const foundItem = state.data.find((item) => item.id === recipientId); - - // 杩斿洖鎵惧埌鐨勯」锛屽鏋滄湭鎵惧埌鍒欒繑鍥� null - return foundItem || { value: '', email: '', title: '' }; - }); - const fnHandleTimeZone = (e, type) => { - console.log('fnHandleTimeZone'); - if (type == 'add') { - titleTimeZone.value = '娣诲姞'; - typeTimeZone.value = 1; - } else { - titleTimeZone.value = '淇敼'; - typeTimeZone.value = 2; - } - openTimeZone.value = true; - }; - - const openTimeZone = ref<boolean>(false); - const confirmLoading = ref<boolean>(false); - const titleTimeZone = ref<string>('娣诲姞'); - const typeTimeZone = ref<number>(1); - - const fnHandleTimeZoneOk = () => { - confirmLoading.value = true; - setTimeout(() => { - openTimeZone.value = false; - confirmLoading.value = false; - }, 2000); - }; -</script> -<style scoped lang="less"></style> diff --git a/src/views/email/components/LeftNav.vue b/src/views/email/components/LeftNav.vue index c9f3ff4..ebb5161 100644 --- a/src/views/email/components/LeftNav.vue +++ b/src/views/email/components/LeftNav.vue @@ -1,107 +1,181 @@ 锘�<template> - <ScrollContainer class="mt-4"> - <a-menu - id="email-left-nav" - v-model:openKeys="openKeys" - v-model:selectedKeys="selectedKeys" - mode="inline" - :items="items" - @click="handleClick" - ></a-menu> - </ScrollContainer> + <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, } from 'vue'; -import type { MenuProps } from 'ant-design-vue'; -import { Menu } from 'ant-design-vue'; -import { ScrollContainer } from '@/components/Container'; + 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[]>(['1']); -const openKeys = ref<string[]>(['sub1']); + const selectedKeys = ref<string[]>(['Index']); + const openKeys = ref<string[]>(['Inbox']); + const items = ref([]); // 瀹氫箟 items 绫诲瀷 -const AMenu = Menu; + const fnGetEmailModule = async () => { + try { + const res = await getEmailModuleApi(); + items.value = convertRoutesToMenuItems(res.data); + } catch (error) { + console.error('鑾峰彇閭妯″潡澶辫触:', error); // 澶勭悊閿欒 + } + }; -const items = ref([ - { - key: '1', - label: 'Navigation One', - title: 'Navigation One', - }, - { - key: '2', - label: 'Navigation Two', - title: 'Navigation Two', - }, - { - key: 'sub1', - label: 'Navigation Three', - title: 'Navigation Three', - children: [ - { - key: '3', - label: 'Option 3', - title: 'Option 3', - }, - { - key: '4', - label: 'Option 4', - title: 'Option 4', - }, - { - key: 'sub1-2', - label: 'Submenu', - title: 'Submenu', - children: [ - { - key: '5', - label: 'Option 5', - title: 'Option 5', - }, - { - key: '6', - label: 'Option 6', - title: 'Option 6', - }, - ], - }, - ], - }, - { - key: 'sub2', - label: 'Navigation Four', - title: 'Navigation Four', - children: [ - { - key: '7', - label: 'Option 7', - title: 'Option 7', - }, - { - key: '8', - label: 'Option 8', - title: 'Option 8', - }, - { - key: '9', - label: 'Option 9', - title: 'Option 9', - }, - { - key: '10', - label: 'Option 10', - title: 'Option 10', - }, - ], - }, -]); + 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 + }; -const handleClick: MenuProps['onClick'] = e => { - console.log('click', e); -}; + // 鐢熷懡鍛ㄦ湡閽╁瓙锛屽姞杞借彍鍗曟暟鎹� + 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; // 璺冲嚭褰撳墠寰幆 + } -// watch(openKeys, val => { -// console.log('openKeys', val); -// }); + 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 new file mode 100644 index 0000000..19b6c55 --- /dev/null +++ b/src/views/email/components/ListPage/TooltipAndDropdown .vue @@ -0,0 +1,304 @@ +<!-- TooltipAndDropdown.vue --> +<template> + <div style="display: flex; justify-content: space-around"> + <!-- Tooltip --> + + <!-- Dropdown --> + <a-dropdown v-model:open="dropdownOpen" :trigger="['click']" placement="bottomLeft"> + <a-tooltip v-if="showTooltip" v-model:open="tooltipOpen" placement="bottomLeft"> + <template #title> + <div class="p-1" style="width: 210px"> + <span>{{ tooltipTitle }}</span> + <div class="p-2" style="font-size: 18px; font-weight: 600">{{ formattedTime }}</div> + <div class="p-2" style="text-align: center"> + <CButton size="small" shape="round" type="success" @click="onUpdate">淇敼</CButton> + <a-button + style="margin-left: 10px" + size="small" + shape="round" + type="primary" + @click="onComplete" + >瀹屾垚</a-button + > + </div> + </div> + </template> + <FieldTimeOutlined v-if="showTooltip" class="color-handle" /> + </a-tooltip> + <FieldTimeOutlined v-else @click.stop="toggleDropdown" /> + <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="click" + title="鑷畾涔夋椂闂�" + v-model:open="customTimeDropdownOpen" + @confirm="onSubmitCustomTime" + > + <template #content> + <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="cancelCustomTime">鍙栨秷</a-button> + <a-button style="margin-left: 10px" type="primary" @click="submitCustomTime" + >纭畾</a-button + > + </a-form-item> + </a-form> + </template> + <div class="date-left" @click="toggleCustomTime">鑷畾涔夋椂闂�</div> + </a-popover> + </div> + </a-card> + </template> + </a-dropdown> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, defineProps, defineEmits, computed, inject, nextTick } from 'vue'; + import { FieldTimeOutlined, PushpinOutlined } from '@ant-design/icons-vue'; + import dayjs from 'dayjs'; + import CButton from '@/components/CButton/index.vue'; + const getDataList = inject('getDataList'); + + 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 props = defineProps({ + tooltipTitle: String, // Tooltip 鏍囬 + initialDropdownOpen: Boolean, // Dropdown 鍒濆鎵撳紑鐘舵�� + showTooltip: Boolean, // 鏄惁灞曠ず Tooltip + initialTooltipOpen: Boolean, // Tooltip 鍒濆鎵撳紑鐘舵�� + row: Object, // 褰撳墠琛屽璞� + docCodeS: Array, + }); +console.log(props,'-59585855'); + + const emit = defineEmits(['updateHandleTime', 'completeAction', 'customTimeSubmit', 'tagRow']); + + import { useMessage } from '@/hooks/web/useMessage'; + import { formatToDateDay } from '@/utils/dateUtil'; + import { updateHandleAPi } from '@/api/email/userList'; + + const { createMessage } = useMessage(); + 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) => {}); + } + + // Dropdown 鍜� Tooltip 鐘舵�佺鐞� + const dropdownOpen = ref(props.initialDropdownOpen); + const tooltipOpen = ref(props.initialTooltipOpen); + const customTimeDropdownOpen = ref(false); + + // 琛ㄥ崟鏁版嵁 + const form = reactive({ + date: '', + time: '', + }); + const onUpdate = () => { + dropdownOpen.value = true; + tooltipOpen.value = false; + }; + // 浜嬩欢澶勭悊 + const toggleDropdown = () => { + dropdownOpen.value = !dropdownOpen.value; + }; + + const toggleTooltip = () => { + tooltipOpen.value = !tooltipOpen.value; + }; + + const toggleCustomTime = () => { + customTimeDropdownOpen.value = !customTimeDropdownOpen.value; + dropdownOpen.value = false; + }; + + 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; + } + }); + }; + + const cancelCustomTime = () => { + customTimeDropdownOpen.value = false; + dropdownOpen.value = true; + }; + + // 澶勭悊鏃堕棿鏍煎紡鍖� + const formattedTime = computed(() => + props.row.handleTime ? dayjs(props.row.handleTime).format('YYYY-MM-DD HH:mm') : '', + ); + + // 鏃ユ湡绂佺敤澶勭悊 + const disabledDate = (currentDate) => { + return currentDate && currentDate < dayjs().startOf('day'); + }; + + function TooltipAndDropdown() {} + + function onSubmitCustomTime() { + customTimeDropdownOpen.value = true; + } +</script> + +<style scoped lang="less"> + .display-flex { + display: flex; + align-items: center; + justify-content: space-between; + } + + .date { + display: flex; + align-items: center; + justify-content: space-between; + } + + .date-left { + flex-grow: 1; + } + + .color-handle { + cursor: pointer; + } + + .date:hover { + transition: all 0.2s; + background-color: #0000000a; + } + + .color-handle { + color: #0a6aff; + } +</style> diff --git a/src/views/email/components/ListPage/drawerDetail.vue b/src/views/email/components/ListPage/drawerDetail.vue new file mode 100644 index 0000000..f3443c4 --- /dev/null +++ b/src/views/email/components/ListPage/drawerDetail.vue @@ -0,0 +1,420 @@ +<template> + <a-drawer + v-model:open="drawerOpen" + :placement="placement" + width="1200" + :body-style="{ paddingBottom: '80px' }" + :footer-style="{ textAlign: 'right' }" + @after-open-change="afterOpenChange" + @close="drawerClose" + > + <template #title> + <div class="ctb"> + <pageHeadLeft + :checked="true" + :selectAllRow="[{ docCode }]" + @nextNum="drawerClose" + ></pageHeadLeft> + <div class="ct-top"> + <div class="title" style="margin-bottom: 20px"> + <div class="left"> + <span style="margin-right: 20px; font-size: 24px; font-weight: 700"> + <a-tooltip placement="bottom"> + <template #title> + <span>{{ tableRowData.subject }}</span> + </template> + {{ truncateString(tableRowData.subject, 23) }} + </a-tooltip> + </span> + <span style="margin-right: 10px; font-size: 16px"> + <PushpinOutlined /> + </span> + </div> + <div class="right"> + <div class="tate">{{ formatToDateDay(tableRowData.receiveTime) }}</div> + <div> + <a-dropdown-button> + <span> + <a-tooltip placement="bottom"> + <template #title> + <span>鍥炲</span> + </template> + <LeftOutlined @click="replyEmail(tableRowData)" /> + </a-tooltip> + </span> + <a-divider type="vertical" /> + <span> + <a-tooltip placement="bottom"> + <template #title> + <span>蹇�熷洖澶�</span> + </template> + <DoubleLeftOutlined /> + </a-tooltip> + </span> + <template #overlay> + <a-menu> + <a-menu-item key="1"> + <UserOutlined /> + 1st menu item + </a-menu-item> + <a-menu-item key="2"> + <UserOutlined /> + 2nd menu item + </a-menu-item> + <a-menu-item key="3"> + <UserOutlined /> + 3rd item + </a-menu-item> + </a-menu> + </template> + </a-dropdown-button> + </div> + </div> + </div> + </div></div> + </template> + <template #extra> + <div style="font-size: 16px"> + 鍏�<span class="m-1">{{ props.allList.length }}</span + >灏� + <LeftOutlined style="padding: 0 20px" @click="fnPrev" /> + <RightOutlined @click="fnNext" /> + </div> + </template> + <template #footer> + <div style="display: flex"> + <a-textarea autoSize size="large" v-model:value="content" placeholder="蹇�熷洖澶�"> + <template #prefix> + <RollbackOutlined style="color: #999" /> + </template> + </a-textarea> + <a-button size="large" type="primary" style="margin-left: 10px" @click="fnQuickReply" + >鍙戦�� + </a-button> + </div> + </template> + <div> + <div class="flex-between"> + <div class="ct-left p-2" :class="isOpen ? 'isOpen' : 'onOpen'"> + <div class="user p-1"> + <div style="display: flex; align-items: center"> + <a-avatar size="small" style="margin-right: 8px" src="#" /> + {{ tableRowData.sender }} + <span>{{ `<${tableRowData.sender}>` }}</span> + <span style="margin: 0 10px">鍙戦��</span> + <a-popover placement="bottom"> + <template #content> + <div + class="p-2" + style=" + display: flex; + align-items: center; + border-bottom: 1px solid rgb(5 5 5 / 6%); + " + > + <a-avatar size="small" style="margin-right: 8px" src="#" /> + <span style="color: #000; font-weight: 700"> + {{ `${tableRowData.receiver}` }}</span + > + <CopyOutlined /> + </div> + <div class="display-flex p-2"> + <a-button type="link" size="small">寰�鏉ラ偖浠�</a-button> + </div> + </template> + <a-avatar size="small" style="margin-right: 8px" src="#" /> + {{ `${tableRowData.receiver}` }}<span>{{ `<${tableRowData.receiver}>` }}</span> + </a-popover> + </div> + <div + type="info" + class="p-2" + style="margin-top: 10px; background-color: #e4f1ff; font-size: 14px" + > + <span>{{ `<${tableRowData.sender}>` }}</span> + <span>鏆傛湭鏌ヨ鍒拌瀹㈡埛鐨勫綋鍦版椂闂�</span> + <!-- <span>2024-06-08 22:22</span> --> + </div> + <div class="ct" v-if="tableRowData.content"> + <TinymcePw ref="TinymcePwRef" v-model="tableRowData.content" /> + </div> + </div> + </div> + <div v-show="isOpen" class="ct-right p-2">sssss</div> + </div> + <div @click="fuToggleContent" class="toggle-btn" :class="isOpen ? 'onIconOpen' : 'iconOpen'"> + <LeftOutlined v-if="!isOpen" /> + <RightOutlined v-else /> + </div> + </div> + </a-drawer> +</template> + +<script lang="ts" setup> + name: 'drawerDetail'; + + import { ref, watch, defineProps, defineEmits, computed } from 'vue'; + import { + LeftOutlined, + RightOutlined, + DoubleLeftOutlined, + PushpinOutlined, + CopyOutlined, + UserOutlined, + RollbackOutlined, + } from '@ant-design/icons-vue'; + import pageHeadLeft from './pageHeadLeft.vue'; + import { TinymcePw } from '@/components/Tinymce'; + import { getMailInfoApi, setQuickReplyAPi } from '@/api/email/userList'; + import { useCollapseStore } from '@/store/modules/useCollapseStore'; + import { nextTick } from 'vue'; + import { formatToDateDay } from '@/utils/dateUtil'; + import { useLoading } from '@/components/Loading'; + + // const [open, close, setTip] = useLoading(); + // 瀹氫箟灞炴�� + interface Props { + modelValue: boolean; + title?: string; + placement?: 'left' | 'right' | 'top' | 'bottom'; + idName?: string; + selectIds?: number[]; + mailId?: string; + selectAllRow: Array<any>; + allList; + } + + const props = defineProps<Props>(); + + const tableRowData = ref<Record<string, any>>({}); + const emit = defineEmits(['update:modelValue', 'updateData']); + const drawerOpen = ref(props.modelValue); + const TinymcePwRef = ref(); + const docCode = ref(props.mailId); + watch( + () => props.mailId, + (newVal) => { + docCode.value = newVal; + }, + ); + // 鐩戝惉灞炴�у彉鍖� + watch( + () => props.modelValue, + (newValue) => { + drawerOpen.value = newValue; + nextTick(() => { + console.log(newValue, '---------4', TinymcePwRef.value); + }); + if (newValue) { + fnGetMailInfo(props.mailId); + } + }, + ); + + function fnGetMailInfo(id) { + getMailInfoApi({ docCode: id }) + .then((res) => { + docCode.value = id; + tableRowData.value = res.data; + }) + .catch(() => {}); + } + // 鏇存柊澶栭儴灞炴�� + watch(drawerOpen, (newValue) => { + emit('update:modelValue', newValue); + }); + + // 鏂规硶 + const collapseStore = useCollapseStore(); + const afterOpenChange = (bool: boolean) => { + if (bool) { + fnGetUserList({ page: 1, pageSize: 30 }); + } + collapseStore.toggle(false); + }; + const drawerClose = (e) => { + drawerOpen.value = false; + }; + + function truncateString(str, maxLength) { + return str.length > maxLength ? str.substring(0, maxLength) + '...' : str; + } + + const fnGetUserList = (params) => { + // 璋冪敤鎺ュ彛閫昏緫 + }; + + // 璁$畻灞炴�� + const placement = computed(() => props.placement || 'right'); + + // 鎶藉眽寮�鍏� + const fnSaveOpenChange = () => { + // 鎶藉眽淇濆瓨閫昏緫 + }; + + const content = ref(''); + const isOpen = ref(false); + + function fuToggleContent() { + isOpen.value = !isOpen.value; + } + + function fnPrev() { + const id = getPrevId(props.allList, docCode.value); + fnGetMailInfo(id); + } + function fnNext() { + const id = getNextId(props.allList, docCode.value); + fnGetMailInfo(id); + } + function getNextId(list, id) { + const index = list.findIndex((item) => item.docCode === id); + if (index < list.length - 1) { + return list[index + 1].docCode; + } else { + return list[0].docCode; + } + } + function getPrevId(list, id) { + const index = list.findIndex((item) => item.docCode === id); + if (index > 0) { + return list[index - 1].docCode; + } else { + return list[list.length - 1].docCode; + } + } + + import { useMessage } from '@/hooks/web/useMessage'; + + const { createMessage } = useMessage(); + function fnQuickReply() { + if (!content.value) { + createMessage.warning('璇疯緭鍏ュ洖澶嶅唴瀹�'); + return; + } + const data = { + docCode: docCode.value, + content: content.value, + }; + setQuickReplyAPi(data).then((res) => { + if (res.code == 0) { + createMessage.success(res.msg); + + fnGetMailInfo(props.mailId); + } + }); + } + import { useRouter } from 'vue-router'; + const router = useRouter(); + function replyEmail(row) { + router.push({ path: '/email/edit', query: { docCode: row.docCode, type: 'reply' } }); + } +</script> + +<style scoped lang="less"> + .table-content { + display: flex; + justify-content: space-between; + border-top: 1px solid #f0f0f0; + + .left { + width: 80%; + height: 100%; + border-right: 1px solid #f0f0f0; + } + + .right { + height: 100%; + } + } + + ::v-deep(.ant-table) { + min-height: 355px !important; + } + + .title { + display: flex; + align-items: center; + justify-content: space-between; + + & .left { + display: flex; + align-items: center; + width: 50%; + } + + & .right { + display: flex; + align-items: center; + justify-content: space-between; + width: 25%; + + & .tate { + color: #999; + font-size: 14px; + } + } + } + + .ct { + margin: 20px 0; + } + + .flex-between { + display: flex; + padding-top: 4%; + } + + .ct-left { + padding-right: 20px; + } + + .ct-right { + border-left: 1px solid #f0f0f0; + } + + .toggle-btn { + display: flex; + position: absolute; + z-index: 99; + top: 50%; + width: 20px; + height: 54px; + padding-left: 5px; + transform: translateY(-50%); + border: 1px solid #f0f0f0; + border-right: none; + border-radius: 10px 0 0 10px; + background: #fafafa; + } + + .onOpen { + width: 100%; + } + + .isOpen { + width: 69%; + } + + .iconOpen { + right: 0%; + } + + .onIconOpen { + right: 32%; + } + + .ctb { + position: relative; + } + + .ctb .ct-top { + position: absolute; + z-index: 99; + top: 41px; + left: -28px; + width: 116%; + padding: 10px; + background: #fff; + } +</style> diff --git a/src/views/email/components/ListPage/list.vue b/src/views/email/components/ListPage/list.vue new file mode 100644 index 0000000..b760c3e --- /dev/null +++ b/src/views/email/components/ListPage/list.vue @@ -0,0 +1,234 @@ +<template> + <PageWrapper> + <div style="height: calc(100vh - 84px)"> + <div class="my-head"> + <div class="left"> + <div class="left-box p-3"> + <!-- 澶氶�� --> + <a-checkbox + class="icon" + style="margin-right: 10px" + v-model:checked="state.checkAll" + :indeterminate="state.indeterminate" + @change="fnCheckedChange" + ></a-checkbox> + <!--鏇存柊 --> + <SyncOutlined class="icon" v-show="!checked" /> + <pageHeadLeft + :checked="checked" + :selectAllRow="selectAllRow" + :parentTableList="newList" + ></pageHeadLeft> + </div> + </div> + + <div class="right p-3" + >鍏�<span style="padding: 0 5px">{{page.total}}</span>灏� + <a-pagination + v-model:current="pageCurrent" + v-model:page-size='page.limit' + simple + :total="page.total" + style="margin-left: 10px" + @change="handlePageChange" + /> + <FilterOutlined style="margin-left: 10px" /> + <a-popover placement="left" trigger="click"> + <template #content> + <div> + <span>寰�鏉ラ偖浠惰仛鍚�</span> + <a-switch style="margin-left: 50px" v-model:checked="checked3"> </a-switch> + </div> + <a-divider style="margin: 10px" /> + <div> + <span>鍒楄〃灞曠ず鍐呭</span> + </div> + <div class="p-2"> + <a-checkbox v-model:checked="checked">閭欢鎽樿</a-checkbox> + </div> + <div class="p-2"> + <a-checkbox v-model:checked="checked">闄勪欢</a-checkbox> + </div> + <div style="text-align: center"> + <a-button @click="$router.push('/email/utils')">鏇村閭璁剧疆</a-button> + </div> + </template> + <SettingOutlined style="margin-left: 10px" /> + </a-popover> + <a-switch style="margin-left: 10px" v-model:checked="checked3"> + <template #checkedChildren><PushpinOutlined style="color: #0a6aff" /></template> + <template #unCheckedChildren><PushpinOutlined /></template> + </a-switch> + </div> + </div> + <div v-if="checked" 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" + @selectAll="fnSelectAll" + @updateSelectAll="updateSelectAll" + /> + </a-tab-pane> + </a-tabs> + </div> + </div> + </PageWrapper> +</template> + +<script lang="ts" setup> + name: 'ListPage'; + import { + SyncOutlined, + SettingOutlined, + FilterOutlined, + PushpinOutlined, + } from '@ant-design/icons-vue'; + import pageHeadLeft from './pageHeadLeft.vue'; + import { PageWrapper } from '@/components/Page'; + + import { ref, watch, defineProps, defineEmits, computed, reactive, onMounted,inject } from 'vue'; + + // 瀹氫箟灞炴�� + interface Props { + pageList?: []; + pageData?:any; + } + const props = defineProps<Props>(); + const newList = ref([]); + const selectAllRow = ref([]); + watch( + () => props.pageList, + (newValue) => { + newList.value = newValue; + }, + ); + +const page = computed(() => props.pageData); + const checked = computed(() => selectAllRow.value.length > 0); + const pageCurrent = ref(1); + const tableRef = ref(); + const state = reactive({ + indeterminate: false, + checkAll: false, + }); + function fnCheckedChange(e) { + Object.assign(state, { + indeterminate: false, + }); + tableRef.value[0].fnSelectAll(e.target.checked); + checked.value = e.target.checked; + } + function updateSelectAll(data) { + selectAllRow.value = data.records; + if (!data.isAll) { + state.indeterminate = true; + state.checkAll = false; + if (data.records.length === 0) { + state.indeterminate = false; + } + } else { + state.indeterminate = false; + state.checkAll = true; + } + } + const tabsList = computed(() => { + return [ + { + key: '1', + label: '鍏ㄩ儴', + num: 0, + }, + { + key: '2', + label: '瀹㈡埛', + num: 0, + }, + { + key: '3', + label: '鍚屼簨', + num: 0, + }, + { + key: '4', + label: '閫氳褰�', + num: 0, + }, + { + key: '5', + label: '鍏朵粬', + num: 0, + }, + ]; + }); + const activeKey = ref('1'); + const checked3 = ref(false); + import Table from './table.vue'; + onMounted(() => { + console.log('tableRef:', tableRef.value[0]); + }); + function fnSelectAll() { + console.log('44444444444'); + } + const emit = defineEmits(['pageChange']); + defineExpose({ + fnSelectAll, + }); + + const getDataList = inject('getDataList'); + function handlePageChange(page, pageSize){ + getDataList(page) + + } +</script> +<style scoped lang="less"> + .my-head { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: 60px; + border-bottom: 1px solid rgb(5 5 5 / 6%); + + /* 澧炲姞閫夋嫨鍣ㄧ壒寮傛�� */ + & .left { + width: 20%; + + & .left-box { + display: flex; + align-items: center; + justify-content: space-flex-start; + width: 100%; + height: 100%;; + + & .icon { + margin-right: 15px; + font-size: 16px; + } + } + } + + & .right { + display: flex; + align-items: center; + } + } + + .left-bt { + display: flex; + align-items: center; + justify-content: center; + padding-left: 27px; + background: #fffbe6; + } +</style> diff --git a/src/views/email/components/ListPage/pageHeadLeft.vue b/src/views/email/components/ListPage/pageHeadLeft.vue new file mode 100644 index 0000000..0280483 --- /dev/null +++ b/src/views/email/components/ListPage/pageHeadLeft.vue @@ -0,0 +1,166 @@ +<template> + <div class="left-box"> + <!-- 鍒嗗彂 --> + <a-tooltip placement="bottom"> + <template #title> + <span>鍒嗗彂</span> + </template> + <ExportOutlined class="icon" /> + </a-tooltip> + <!-- 寰呭鐞� --> + <TooltipAndDropdown + class="icon" + v-show="checked" + :tooltipTitle="'寰呭鐞嗛偖浠�'" + :initialDropdownOpen="false" + :initialTooltipOpen="false" + :showTooltip="false" + :docCodeS="getSelectAllBocCode" + /> + <!-- 鍒犻櫎 --> + <a-tooltip placement="bottom"> + <template #title> + <span>鍒犻櫎</span> + </template> + <DeleteOutlined v-show="checked" class="icon" @click="fuDeleteEmail" /> + </a-tooltip> + + <!-- 鏀惰棌 --> + <a-tooltip placement="bottom"> + <template #title> + <span>鏍囧織</span> + </template> + <TagOutlined class="icon" v-show="checked" /> + </a-tooltip> + + <!-- 鏂囦欢澶� --> + <a-tooltip placement="bottom"> + <template #title> + <span>鏂囦欢澶�</span> + </template> + <FolderOutlined class="icon" v-show="checked" /> + </a-tooltip> + <!-- 鏇村 --> + <a-tooltip placement="bottom"> + <template #title> + <span>鏇村</span> + </template> + <a-dropdown :arrow="{ pointAtCenter: true }" placement="bottom" :trigger="['click']"> + <template #overlay> + <a-menu> + <a-menu-item key="2" @click="fnSelectAllRead(true)"> 鏍囦负宸茶</a-menu-item> + <a-menu-item key="3" @click="fnSelectAllRead(false)"> 鏍囦负鏈</a-menu-item> + <a-menu-item key="4"> 璁句负缃《</a-menu-item> + <a-divider style="margin: 2px; padding: 2px" /> + <a-menu-item key="5"> 鏍囪涓哄瀮鍦鹃偖浠�</a-menu-item> + </a-menu> + </template> + <MoreOutlined v-show="checked" class="icon" /> + </a-dropdown> + <a-dropdown :arrow="{ pointAtCenter: true }" placement="bottom" :trigger="['click']"> + <template #overlay> + <a-menu> + <a-menu-item key="1" @click="fnAllRead">鍏ㄩ儴鏍囪涓哄凡璇�</a-menu-item> + <a-divider style="margin: 2px; padding: 2px" /> + <p style="color: #999; font-size: 12px">鍕鹃�夐偖浠跺嵆鍙煡鐪嬫洿澶氭搷浣�</p> + </a-menu> + </template> + <MoreOutlined v-show="!checked" class="icon" /> + </a-dropdown> + </a-tooltip> + </div> +</template> + +<script lang="ts" setup> + import TooltipAndDropdown from './TooltipAndDropdown .vue'; + import { + ExportOutlined, + ClockCircleOutlined, + DeleteOutlined, + TagOutlined, + FolderOutlined, + MoreOutlined, + } from '@ant-design/icons-vue'; + import { ref, defineProps, defineEmits, computed, reactive, inject } from 'vue'; + interface Props { + checked: boolean; + handleId?: number; + selectAllRow?: Array<any>; + parentTableList?: Array<any>; + } + + const props = defineProps<Props>(); + + const checked = computed(() => props.checked); + import { updateReadApi, deleteEmailAPi } from '@/api/email/userList'; + import { useMessage } from '@/hooks/web/useMessage'; + + const { createMessage } = useMessage(); + const getDataList = inject('getDataList'); + function fnSelectAllRead(is) { + const data = { + status: is, + list: getReadId(), + }; + pushUpdateReadApi(data); + } + const getSelectAllBocCode = computed(() => { + let data = props.selectAllRow.map((item) => item.docCode); + console.log(data, '00003'); + + return data; + }); + function pushUpdateReadApi(data) { + updateReadApi(data).then((res) => { + if (res.code == 0) { + // + getDataList({}); + } + }); + } + function fnAllRead() { + fnGetTableList(); + } + + function fnGetTableList() { + const data = { + status: true, + list: getReadId(), + }; + pushUpdateReadApi(data); + } + function getReadId() { + const ids = []; + props.parentTableList.forEach((item: Record<string, any>) => { + ids.push(item.docCode); + }); + return ids; + } + const emit = defineEmits(['nextNum']); + function fuDeleteEmail() { + deleteEmailAPi(getSelectAllBocCode.value).then((res) => { + if (res.code == 0) { + createMessage.success(res.msg); + // 鍖哄垎鎿嶄綔 + getDataList({}); + if (!props.parentTableList) { + emit('nextNum'); + } + } + }); + } +</script> + +<style lang="less" scoped> + .left-box { + display: flex; + align-items: center; + justify-content: space-flex-start; + width: 100%; + + & .icon { + margin-right: 15px; + font-size: 16px; + } + } +</style> diff --git a/src/views/email/components/ListPage/table.vue b/src/views/email/components/ListPage/table.vue new file mode 100644 index 0000000..cf832e1 --- /dev/null +++ b/src/views/email/components/ListPage/table.vue @@ -0,0 +1,476 @@ +<template> + <div style="overflow: auto"> + <div v-if="groupedEmails.length != 0"> + <div v-for="(item, index) in groupedEmails" :key="index"> + <div class="span-title">{{ `${item.name}(${item.data.length})` }}</div> + <vxe-table + ref="vxeTableRef" + style="margin: 10px 0" + :showHeader="false" + :data="item.data" + size="small" + min-height="40px" + :row-config="{ isCurrent: true, isHover: true }" + :menu-config="tableMenu" + @menu-click="contextMenuClickEvent" + @cell-click="cellClickEvent" + @checkbox-change="selectChangeEvent" + > + <vxe-column type="checkbox" width="30"></vxe-column> + <vxe-column field="sender" title="鍙戜欢浜�" data-index="sender" min-width="300px"> + <template #default="{ row }"> + <div style="display: flex; align-items: center"> + <div + v-if="row.mailType != 0" + class="dot" + :class="row.readFlag ? 'dot-color' : ''" + @click.stop="fnRowUpdateRead(row)" + ></div> + <a-tooltip placement="bottom"> + <template #title> + <span>闄岀敓浜�</span> + </template> + <a-avatar size="small" style="margin-right: 8px" :src="row.avatar" /> + </a-tooltip> + + <a-popover placement="bottom"> + <template #content> + <div + class="p-2" + style=" + display: flex; + align-items: center; + border-bottom: 1px solid rgb(5 5 5 / 6%); + " + > + <a-avatar size="small" style="margin-right: 8px" :src="row.avatar" /> + <span style="color: #000; font-weight: 700">{{ row.sender }}</span> + <CopyOutlined /> + </div> + <div class="display-flex p-2"> + <a-button type="link" size="small">鏂板缓瀹㈡埛</a-button> + <a-dropdown> + <a style="margin-right: 5px" class="ant-dropdown-link" @click.prevent> + <DownOutlined /> + </a> + <template #overlay> + <a-menu> + <a-menu-item> + <a href="javascript:;">娣诲姞鍒板凡鏈夊鎴�</a> + </a-menu-item> + </a-menu> + </template> + </a-dropdown> + <a-button type="link" size="small">娣诲姞涓虹嚎绱�</a-button> + + <a-dropdown style="margin-right: 5px"> + <a style="margin-right: 5px" class="ant-dropdown-link" @click.prevent> + <DownOutlined /> + </a> + <template #overlay> + <a-menu> + <a-menu-item> + <a href="javascript:;">娣诲姞鍒伴�氳褰�</a> + </a-menu-item> + </a-menu> + </template> + </a-dropdown> + <a-button type="link" size="small">寰�鏉ラ偖浠�</a-button></div + > + </template> + <div class="title-dot" :class="row.readFlag ? '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> + </div> + </a-popover> + </div> + </template> + </vxe-column> + <vxe-column + show-overflow + field="subject" + title="琛ㄩ" + data-index="subject" + min-width="250" + > + <template #default="{ row }"> + <span + class="title-dot" + :class="row.readFlag ? 'title-dot-color' : ''" + style="font-weight: 500" + >{{ row.subject || '锛堟棤涓婚锛�' }}</span + > + - + <span style="color: #999">{{ row.subject }}</span> + </template> + </vxe-column> + <vxe-column field="action" title="Action" width="190"> + <template #default="{ row, rowIndex }"> + <span style="display: flex; justify-content: space-around"> + <span>{{ + row.mailType !== 0 + ? formatToDateDay(row.receiveTime) + : formatToDateDay(row.createTime) + }}</span> + + <TooltipAndDropdown + :tooltipTitle="'寰呭鐞嗛偖浠�'" + :initialDropdownOpen="false" + :initialTooltipOpen="false" + :showTooltip="!!row.handleTime" + :row="row" + :docCodeS="[row.docCode]" + /> + <span style="margin-left: 5px"><PushpinOutlined @click.stop="fnTagging" /></span> + </span> + </template> + </vxe-column> + </vxe-table> + </div + ></div> + + <div v-else style="height: 70vh; display: flex; align-items: center; justify-content: center"> + <a-empty /> + </div> + <DrawerDetail + ref="drawerDetailRef" + v-model="openDrawerDetail" + :mailId="rowMailId" + :selectAllRow="selectRow" + :allList="dataSource" + /> + <a-dropdown :trigger="['click']" placement="bottomLeft" ref="dropdownRefs"> </a-dropdown> + </div> +</template> + +<script lang="ts" setup> + name: 'ListPageTable'; + import { + FieldTimeOutlined, + PushpinOutlined, + CopyOutlined, + DownOutlined, + } from '@ant-design/icons-vue'; + + import { ref, watch, defineProps, defineEmits, computed, defineExpose, inject } from 'vue'; + + // 瀹氫箟灞炴�� + interface Props { + pageList: []; + } + const props = defineProps<Props>(); + + const groupedEmails = ref<GroupedDataItem[]>([]); + + const dataSource = ref([]); + watch( + () => props.pageList, + (newValue) => { + dataSource.value = newValue || []; + groupedEmails.value = groupEmailsByDate(newValue || []); + }, + ); + import dayjs from 'dayjs'; + import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; + import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; + import isoWeek from 'dayjs/plugin/isoWeek'; + dayjs.extend(isSameOrAfter); + dayjs.extend(isSameOrBefore); + dayjs.extend(isoWeek); + + interface EmailItem { + id: number; + subject: string; + } + + // 纭繚 groupedData 鐨勭粨鏋勬纭笖 data 鏄� EmailItem 绫诲瀷鐨勬暟缁� + interface GroupedDataItem { + key: string; + data: EmailItem[]; + name: string; + } + + function groupEmailsByDate(dataSource) { + const today = dayjs(); + const yesterday = dayjs().subtract(1, 'day'); + const startOfWeek = dayjs().startOf('week'); + const startOfMonth = dayjs().startOf('month'); + const startOfLastMonth = dayjs().subtract(1, 'month').startOf('month'); + const endOfLastMonth = dayjs().subtract(1, 'month').endOf('month'); + + const groupedData: GroupedDataItem[] = [ + { + data: [], + name: '浠婂ぉ', + key: 'today', + }, + { + data: [], + name: '鏄ㄥぉ', + key: 'yesterday', + }, + { + data: [], + name: '鏈懆', + key: 'thisWeek', + }, + { + data: [], + name: '杩欎釜鏈�', + key: 'thisMonth', + }, + { + data: [], + name: '涓婁釜鏈�', + key: 'lastMonth', + }, + { + data: [], + name: '鏇存棭', + key: 'earlier', + }, + ]; + + dataSource.forEach((item: any) => { + try { + const emailDate = dayjs(item.createTime); + if (emailDate.isSame(today, 'day')) { + groupedData[0].data.push(item); + } else if (emailDate.isSame(yesterday, 'day')) { + groupedData[1].data.push(item); + } else if (emailDate.isSameOrAfter(startOfWeek) && emailDate.isBefore(today, 'day')) { + groupedData[2].data.push(item); + } else if (emailDate.isSameOrAfter(startOfMonth) && emailDate.isBefore(today, 'day')) { + groupedData[3].data.push(item); + } else if ( + emailDate.isSameOrAfter(startOfLastMonth) && + emailDate.isSameOrBefore(endOfLastMonth) + ) { + groupedData[4].data.push(item); + } else { + groupedData[5].data.push(item); + } + } catch (error) { + console.error(`Error processing item date: ${item.date}`, error); + } + }); + // 灏嗙粨鏋滄寜涓枃鏄犲皠杩涜杩斿洖 + const result = <{ data: any; name: string; key: string }[]>[]; + groupedData.forEach((group: { data: any; name: string; key: string }) => { + if (group.data.length > 0) { + result.push(group); + } + }); + + return result; + } + + // 鍙抽敭鑿滃崟 + 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: '鍒犻櫎' }, + ], + ], + }, + }; + + function contextMenuClickEvent({ menu, row, column }) { + switch (menu.code) { + case 'copy': + if (row && column) { + } + break; + default: + } + } + const vxeTableRef = ref(); + function fnSelectAll(is) { + vxeTableRef.value.forEach((row) => { + row.setAllCheckboxRow(is); + }); + selectChangeEvent(); + } + + function selectChangeEvent() { + const isAll = getCheckboxRecords().length === dataSource.value.length; + const data = { + isAll, + records: getCheckboxRecords(), + }; + emit('updateSelectAll', data); + } + function getCheckboxRecords() { + const list = new Set(); + + vxeTableRef.value.forEach((row) => { + const records = row.getCheckboxRecords(); // 鍋囪璇ユ柟娉曡繑鍥炰竴涓暟缁� + if (Array.isArray(records)) { + // 纭繚 records 鏄暟缁� + records.forEach((record) => list.add(record)); // 灏嗚褰曟坊鍔犲埌 Set 涓� + } + }); + + return Array.from(list); // 灏� Set 杞崲鍥炴暟缁� + } + // 鎿嶄綔row + function fnProcessingTime(row) { + console.log(row); + } + function fnTagging(row) { + console.log(row); + } + import DrawerDetail from './drawerDetail.vue'; + // 璇︽儏鍐呭 + const openDrawerDetail = ref(false); + const rowMailId = ref(''); + const selectRow = ref([]); + const cellClickEvent = (event) => { + selectRow.value = []; + rowMailId.value = event.row.docCode; + selectRow.value.push({ docCode: event.row.docCode }); + openDrawerDetail.value = true; + }; + + // 鏇存柊绁栫埗缁勪欢鏁版嵁 + const getDataList = inject('getDataList'); + console.log(getDataList, '0000004'); + + import { updateReadApi, updateHandleAPi } from '@/api/email/userList'; + // 鏍囧織鏈/缁忚 + function fnRowUpdateRead(row) { + const data = { + status: !row.readFlag, + list: [row.docCode], + }; + pushReadApi(data); + } + function pushReadApi(params) { + updateReadApi(params).then((res) => { + if (res.code == 0) { + // + getDataList({}); + } + }); + } + const emit = defineEmits(['selectAll', 'updateSelectAll']); + defineExpose({ + fnSelectAll, + }); + + import TooltipAndDropdown from './TooltipAndDropdown .vue'; + import { formatToDateDay } from '@/utils/dateUtil'; +</script> +<style scoped lang="less"> + .display-flex { + display: flex; + align-items: center; + justify-content: space-between; + } + + .head { + display: flex; + justify-content: space-between; + width: 100%; + border-bottom: 1px solid rgb(5 5 5 / 6%); + + /* 澧炲姞閫夋嫨鍣ㄧ壒寮傛�� */ + & .left { + width: 20%; + + & .left-box { + display: flex; + align-items: center; + justify-content: space-flex-start; + width: 100%; + + & .icon { + margin-right: 15px; + font-size: 16px; + } + } + } + + & .right { + display: flex; + align-items: center; + } + } + + .left-bt { + display: flex; + align-items: center; + justify-content: center; + padding-left: 27px; + background: #fffbe6; + } + + .span-title { + width: 100%; + padding: 5px; + color: #000; + font-weight: 700; + text-align: left; + } + + .table { + height: 80vh; + } + + .my-menus { + background-color: #f8f8f9; + } + // 鍦嗙偣 + .dot { + display: inline-block; + width: 8px; + height: 8px; + margin-right: 10px; + border-radius: 50%; + background-color: #0a6aff; + } + + .dot-color { + background-color: #d9d9d9; + color: #d9d9d9; + } + + .title-dot { + color: #0a6aff; + } + + .title-dot-color { + color: #999; + } +</style> diff --git a/src/views/email/components/SelectUser.vue b/src/views/email/components/SelectUser.vue deleted file mode 100644 index 7d9dffb..0000000 --- a/src/views/email/components/SelectUser.vue +++ /dev/null @@ -1,261 +0,0 @@ -<!-- Props: - -modelValue: 鎺у埗鎶藉眽鐨勬墦寮�涓庡叧闂�� -title: 鎶藉眽鏍囬锛岄粯璁や负Basic Drawer銆� -placement: 鎶藉眽浣嶇疆锛岄粯璁や负right銆� -Emits: - -update:modelValue: 褰撳唴閮ㄧ姸鎬佹敼鍙樻椂锛屾洿鏂板閮ㄧ粦瀹氱殑modelValue銆� -璁$畻灞炴��: - -title 鍜� placement 鐢ㄤ簬鑾峰彇灞炴�у�兼垨榛樿鍊笺�� -鍐呴儴鐘舵��: - -drawerOpen 鐢ㄤ簬鎺у埗鎶藉眽鐨勭姸鎬併�� -鏂规硶: - -showDrawer 鐢ㄤ簬鏄剧ず鎶藉眽銆� --> - -<template> - <a-drawer - v-model:open="drawerOpen" - :title="title" - :placement="placement" - :width="1200" - :body-style="{ paddingBottom: '80px' }" - :footer-style="{ textAlign: 'right' }" - @after-open-change="afterOpenChange" - @close="afterOpenChange" - > - <a-form :model="form" :rules="rules" layout="vertical"> - <a-row :gutter="16"> - <a-col :span="12"> - <a-form-item label="鍏抽敭瀛�"> - <a-input v-model:value="form.keyword" placeholder="缂栧彿/鑱旂郴浜哄悕绉�" /> - </a-form-item> - </a-col> - </a-row> - </a-form> - <div class="table-content"> - <div class="left"> - <a-card :bordered="false" :title="`鑱旂郴浜猴紙${selectContactNum}锛塦" style="height: 500px"> - <template #extra> - <a-checkbox v-model:checked="selectAllCurrentPage" @change="fnSelectAllCurrentPage" - >鍏ㄩ�夊綋鍓嶉〉</a-checkbox - > - <a-checkbox v-model:checked="selectEmail" @change="fnSelectEmail" - >閫夋嫨鍓�30涓偖绠�</a-checkbox - ></template - > - <a-table - :showHeader="false" - :data-source="dataSource" - :row-selection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }" - :scroll="{ y: 300 }" - size="small" - :pagination="{ pageSize: 100 }" - rowKey="id" - > - <a-table-column key="name" title="name" data-index="name"> - <template #default="{ record }"> - <span>{{ record.name }}</span - ><a>{{ record.address }}</a> - </template> - </a-table-column> - </a-table> - </a-card> - </div> - <div class="right"> - <a-card :bordered="false" :title="`宸查�夎仈绯讳汉锛�${contactNum}/100锛塦" style="height: 500px"> - <template #extra><a @click="fnClearSelect">娓呯┖閫夐」</a></template> - <a-table - :showHeader="false" - :data-source="tempDataSource" - :pagination="false" - :scroll="{ y: 355 }" - size="small" - > - <a-table-column key="name" title="name" data-index="name"> - <template #default="{ record }"> - <span>{{ record.name }}</span - ><a>{{ record.address }}</a> - </template> - </a-table-column> - </a-table> - </a-card> - </div> - </div> - <template #extra> - <a-space> - <a-button @click="afterOpenChange">鍙栨秷</a-button> - <a-button type="primary" @click="fnSaveOpenChange">淇濆瓨</a-button> - </a-space> - </template> - </a-drawer> -</template> - -<script lang="ts" setup> - import { ref, watch, defineProps, defineEmits, computed, reactive } from 'vue'; - // 瀹氫箟缁勪欢鍚嶇О - defineOptions({ name: 'SelectUser' }); - - // 瀹氫箟灞炴�� - interface Props { - modelValue: boolean; - title?: string; - placement?: 'left' | 'right' | 'top' | 'bottom'; - idName?: string; - selectIds?: number[]; - } - const props = defineProps<Props>(); - - // 瀹氫箟浜嬩欢 - const emit = defineEmits(['update:modelValue', 'updateData']); - - // 鍐呴儴鐘舵�� - const drawerOpen = ref(props.modelValue); - - // 鐩戝惉灞炴�у彉鍖� - watch( - () => props.modelValue, - (newValue) => { - drawerOpen.value = newValue; - if (props.selectIds && props.selectIds.length > 0) { - state.selectedRowKeys = props.selectIds; - updateTempDataSource(props.selectIds); - } else { - state.selectedRowKeys = []; - updateTempDataSource([]); - } - }, - ); - - // 鏇存柊澶栭儴灞炴�� - watch(drawerOpen, (newValue) => { - emit('update:modelValue', newValue); - }); - import { getUserListApi } from '@/api/email/userList'; - - // 鏂规硶 - const afterOpenChange = (bool: boolean) => { - if (bool) { - fnGetUserList({ page: 1, pageSize: 30 }); - } - }; - const fnGetUserList = (params) => - getUserListApi(params).then((res) => { - if (res && res.items && Array.isArray(res.items)) { - dataSource.value = res.items; - selectContactNum.value = res.total; - } else { - console.error('Invalid response format:', res); - } - }); - // 璁$畻灞炴�� - const title = computed(() => props.title || 'Basic Drawer'); - const placement = computed(() => props.placement || 'right'); - const idName = computed(() => props.idName || 'recipients'); - - // // 鏄剧ず鎶藉眽鐨勬柟娉� - // const showDrawer = () => { - // drawerOpen.value = true; - // }; - - const form = ref({ - keyword: '', - }); - const rules = ref({ - keyword: [{ required: true, message: '鍏抽敭瀛椾笉鑳戒负绌�', trigger: 'blur' }], - }); - const selectAllCurrentPage = ref(false); - const selectEmail = ref(false); - const fnSelectAllCurrentPage = (e) => { - const temp = dataSource.value; - if (selectAllCurrentPage.value) { - state.selectedRowKeys = temp.map((item) => item.id); - } else { - state.selectedRowKeys = state.selectedRowKeys.filter( - (item) => !temp.some((t) => t.id === item), - ); - } - updateTempDataSource(state.selectedRowKeys); - }; - - const fnSelectEmail = (e) => { - const temp = dataSource.value.slice(0, 30); - if (selectEmail.value) { - state.selectedRowKeys = temp.map((item) => item.id); - } else { - state.selectedRowKeys = state.selectedRowKeys.filter( - (item) => !temp.some((t) => t.id === item), - ); - } - updateTempDataSource(state.selectedRowKeys); - }; - const updateTempDataSource = (selectedRowKeys: Key[]) => { - tempDataSource.value = dataSource.value.filter((item) => selectedRowKeys.includes(item.id)); - contactNum.value = selectedRowKeys.length; - }; - - const contactNum = ref(0); - const selectContactNum = ref(0); - - const dataSource = ref([]); - - type Key = string | number; - const state = reactive<{ - selectedRowKeys: Key[]; - loading: boolean; - }>({ - selectedRowKeys: [], // Check here to configure the default column - loading: false, - }); - const tempDataSource = ref([]); - - const fnClearSelect = () => { - state.selectedRowKeys = []; - tempDataSource.value = []; - contactNum.value = 0; - }; - const onSelectChange = (selectedRowKeys: Key[]) => { - console.log('selectedRowKeys changed: ', selectedRowKeys); - state.selectedRowKeys = selectedRowKeys; - tempDataSource.value = dataSource.value.filter((item) => selectedRowKeys.includes(item.id)); - contactNum.value = selectedRowKeys.length; - }; - const fnSaveOpenChange = () => { - console.log('selectedRowKeys changed: ', state.selectedRowKeys); - drawerOpen.value = false; - emit('update:modelValue', false); - const data = { - selectedRowKeys: state.selectedRowKeys, - idName: idName.value, - }; - emit('updateData', data); - }; -</script> - -<style scoped lang="less"> - /* 鏍峰紡鍙互鍦ㄨ繖閲屽畾涔� */ - - .table-content { - display: flex; - justify-content: space-between; - border-top: 1px solid #f0f0f0; - - .left { - width: 50%; - height: 100%; - border-right: 1px solid #f0f0f0; - } - - .right { - width: 50%; - height: 100%; - } - } - - ::v-deep(.ant-table) { - min-height: 355px !important; - } -</style> diff --git a/src/views/email/components/SelectUser/index.vue b/src/views/email/components/SelectUser/index.vue new file mode 100644 index 0000000..d8fe0a7 --- /dev/null +++ b/src/views/email/components/SelectUser/index.vue @@ -0,0 +1,367 @@ +<!-- Props: + +modelValue: 鎺у埗鎶藉眽鐨勬墦寮�涓庡叧闂�� +title: 鎶藉眽鏍囬锛岄粯璁や负Basic Drawer銆� +placement: 鎶藉眽浣嶇疆锛岄粯璁や负right銆� +Emits: + +update:modelValue: 褰撳唴閮ㄧ姸鎬佹敼鍙樻椂锛屾洿鏂板閮ㄧ粦瀹氱殑modelValue銆� +璁$畻灞炴��: + +title 鍜� placement 鐢ㄤ簬鑾峰彇灞炴�у�兼垨榛樿鍊笺�� +鍐呴儴鐘舵��: + +drawerOpen 鐢ㄤ簬鎺у埗鎶藉眽鐨勭姸鎬併�� +鏂规硶: + +showDrawer 鐢ㄤ簬鏄剧ず鎶藉眽銆� --> + +<template> + <a-drawer + v-model:open="drawerOpen" + :title="title" + :placement="placement" + :width="1200" + :body-style="{ paddingBottom: '80px' }" + :footer-style="{ textAlign: 'right' }" + @after-open-change="afterOpenChange" + @close="afterOpenChange" + > + <a-tabs v-model:activeKey="activeKey" @change="handleTabChange"> + <a-tab-pane :key="item.key" :tab="item.title" v-for="item in tabsList"> + <a-form :model="form" :rules="rules" layout="vertical"> + <a-row :gutter="16"> + <a-col :span="12"> + <a-form-item label="鍏抽敭瀛�"> + <a-input v-model:value="form.keyword" placeholder="缂栧彿/鑱旂郴浜哄悕绉�" /> + </a-form-item> + </a-col> + </a-row> + </a-form> + <div class="table-content"> + <div class="left"> + <a-card + :bordered="false" + :title="`鑱旂郴浜猴紙${selectContactNum}锛塦" + style="height: 500px" + > + <template #extra> + <a-checkbox v-model:checked="selectAllCurrentPage" @change="fnSelectAllCurrentPage" + >鍏ㄩ�夊綋鍓嶉〉</a-checkbox + > + <a-checkbox v-model:checked="selectEmail" @change="fnSelectEmail" + >閫夋嫨鍓�30涓偖绠�</a-checkbox + ></template + > + <a-table + :showHeader="false" + :data-source="dataSource" + :row-selection="{ + selectedRowKeys: state.selectedRowKeys, + onChange: onSelectChange, + }" + :scroll="{ y: 300 }" + size="small" + rowKey="userCode" + > + <!-- :pagination="{ pageSize: 100 }" --> + <a-table-column key="userName" title="name" data-index="name"> + <template #default="{ record }"> + <span>{{ record.userName }}</span + ><span>{{ `<${record.email}>` }}</span> + <span style="margin-left: 5px">{{ record.ccName }}</span> + </template> + </a-table-column> + </a-table> + </a-card> + </div> + <div class="right"> + <a-card + :bordered="false" + :title="`宸查�夎仈绯讳汉锛�${contactNum}/100锛塦" + style="height: 500px" + > + <template #extra><a @click="fnClearSelect">娓呯┖閫夐」</a></template> + <a-table + :showHeader="false" + :data-source="tempDataSource" + :pagination="false" + :scroll="{ y: 355 }" + size="small" + > + <a-table-column key="userName" title="name" data-index="name"> + <template #default="{ record }"> + <span>{{ record.userName }}</span + ><span>{{ `<${record.email}>` }}</span> + <!-- <span style="margin-left: 5px">{{ record.ccName }}</span> --> + </template> + </a-table-column> + </a-table> + </a-card> + </div> + </div> + </a-tab-pane> + </a-tabs> + + <template #extra> + <a-space> + <a-button @click="afterOpenChange">鍙栨秷</a-button> + <a-button type="primary" @click="fnSaveOpenChange">淇濆瓨</a-button> + </a-space> + </template> + </a-drawer> +</template> + +<script lang="ts" setup> + import { ref, watch, defineProps, defineEmits, computed, reactive } from 'vue'; + import { getUserInfoApi, contactListAPi, emailListAPi } from '@/api/email/userList'; + // 瀹氫箟缁勪欢鍚嶇О + defineOptions({ name: 'SelectUser' }); + + // 瀹氫箟灞炴�� + interface Props { + modelValue: boolean; + title?: string; + placement?: 'left' | 'right' | 'top' | 'bottom'; + idName?: string; + selectIds?: number[]; + } + const props = defineProps<Props>(); + + const activeKey = ref('3'); + const tabsList = ref([ + { + key: '1', + title: '瀹㈡埛鑱旂郴浜�', + }, + { + key: '2', + title: '绾跨储鑱旂郴浜�', + }, + { + key: '3', + title: '鍟嗘満鑱旂郴浜�', + }, + { + key: '4', + title: '浼佷笟鍚屼簨', + }, + { + key: '5', + title: '涓汉閫氳褰�', + }, + { + key: '6', + title: '浼佷笟閫氳褰�', + }, + ]); + + // 瀹氫箟浜嬩欢 + const emit = defineEmits(['update:modelValue', 'updateData', 'sudUserList']); + const gutUserApiList = ref([ + { key: '3', api: emailListAPi }, + { key: '2', api: contactListAPi }, + ]); + + const gutUserApi = ref<Record<string, any>>(emailListAPi); + function handleTabChange(key: string) { + gutUserApi.value = gutUserApiList.value.find((item) => item.key === key)?.api || emailListAPi; + fnGetUserList(); + } + + function fnGetUserList() { + gutUserApi.value({ key: '' }).then((res) => { + if (res.state === 0) { + dataSource.value = res.data; + if (res.data.data) { + dataSource.value = res.data.data; + } + selectContactNum.value = dataSource.value.length; + // emit('sudUserList', dataSource.value); + } + }); + } + // 鍐呴儴鐘舵�� + const drawerOpen = ref(props.modelValue); + + // 鐩戝惉灞炴�у彉鍖� + watch( + () => props.modelValue, + (newValue) => { + drawerOpen.value = newValue; + if (props.selectIds && props.selectIds.length > 0) { + state.selectedRowKeys = props.selectIds; + updateTempDataSource(props.selectIds); + } else { + state.selectedRowKeys = []; + updateTempDataSource([]); + } + }, + ); + + // 鏇存柊澶栭儴灞炴�� + watch(drawerOpen, (newValue) => { + emit('update:modelValue', newValue); + }); + // 鏂规硶 + const afterOpenChange = (bool: boolean) => { + if (bool) { + fnGetUserList(); + } + }; + interface User { + ccCode: string; + ccName: string; + userCode: string; + userName: string; + email: string; + } + // const fnGetUserList = (params) => + // getUserInfoApi(params).then((res) => { + // if (res && res.data && Array.isArray(res.data)) { + // dataSource.value = flattenAndDeduplicateData(res.data) || ([] as User[]); + // selectContactNum.value = dataSource.value.length; + // emit('sudUserList', dataSource.value); + // } else { + // console.error('Invalid response format:', res); + // } + // }); + + function flattenAndDeduplicateData(data) { + const result: Record<string, any>[] = []; + const userCodeSet = new Set(); // 鐢ㄤ簬璁板綍宸茬粡鍑虹幇杩囩殑 userCode + + data.forEach((department) => { + const { ccCode, ccName, subList } = department; + + if (subList && subList.length > 0) { + subList.forEach((user) => { + // 濡傛灉 userCode 涓嶅湪 Set 涓紝鎵嶆坊鍔犲埌缁撴灉涓� + if (!userCodeSet.has(user.userCode)) { + result.push({ + ccCode: user.ccCode || ccCode, // 浣跨敤瀛愰」鎴栫埗椤圭殑 ccCode + ccName: user.ccName || ccName, // 浣跨敤瀛愰」鎴栫埗椤圭殑 ccName + userCode: user.userCode, + userName: user.userName, + id: user.userCode, + email: user.email || '', // 纭繚 email 涓嶄负 null + }); + // 灏� userCode 鍔犲叆 Set锛岄伩鍏嶉噸澶� + userCodeSet.add(user.userCode); + } + }); + } + }); + + return result; + } + // 璁$畻灞炴�� + const title = computed(() => props.title || 'Basic Drawer'); + const placement = computed(() => props.placement || 'right'); + const idName = computed(() => props.idName || 'recipients'); + + // // 鏄剧ず鎶藉眽鐨勬柟娉� + // const showDrawer = () => { + // drawerOpen.value = true; + // }; + + const form = ref({ + keyword: '', + }); + const rules = ref({ + keyword: [{ required: true, message: '鍏抽敭瀛椾笉鑳戒负绌�', trigger: 'blur' }], + }); + const selectAllCurrentPage = ref(false); + const selectEmail = ref(false); + const fnSelectAllCurrentPage = (e) => { + const temp = dataSource.value; + if (selectAllCurrentPage.value) { + state.selectedRowKeys = temp.map((item) => item.userCode); + } else { + state.selectedRowKeys = state.selectedRowKeys.filter( + (item) => !temp.some((t) => t.userCode === item), + ); + } + updateTempDataSource(state.selectedRowKeys); + }; + + const fnSelectEmail = (e) => { + const temp = dataSource.value.slice(0, 30); + if (selectEmail.value) { + state.selectedRowKeys = temp.map((item) => item.userCode); + } else { + state.selectedRowKeys = state.selectedRowKeys.filter( + (item) => !temp.some((t) => t.userCode === item), + ); + } + updateTempDataSource(state.selectedRowKeys); + }; + const updateTempDataSource = (selectedRowKeys: Key[]) => { + tempDataSource.value = dataSource.value.filter((item) => selectedRowKeys.includes(item.userCode)); + contactNum.value = selectedRowKeys.length; + }; + + const contactNum = ref(0); + const selectContactNum = ref(0); + + const dataSource = ref<Record<string, any>>([]); + + type Key = string | number; + const state = reactive<{ + selectedRowKeys: Key[]; + loading: boolean; + }>({ + selectedRowKeys: [], // Check here to configure the default column + loading: false, + }); + const tempDataSource = ref([]); + + const fnClearSelect = () => { + state.selectedRowKeys = []; + tempDataSource.value = []; + contactNum.value = 0; + }; + const onSelectChange = (selectedRowKeys: Key[]) => { + + state.selectedRowKeys = dataSource.value.filter((item) => selectedRowKeys.includes(item.userCode));; + console.log('selectedRowKeys changed: ', state.selectedRowKeys); + + tempDataSource.value = dataSource.value.filter((item) => selectedRowKeys.includes(item.userCode)); + contactNum.value = selectedRowKeys.length; + }; + const fnSaveOpenChange = () => { + console.log('selectedRowKeys changed: ', state.selectedRowKeys); + drawerOpen.value = false; + emit('update:modelValue', false); + const data = { + selectedRowKeys: state.selectedRowKeys, + idName: idName.value, + }; + emit('updateData', data); + }; +</script> + +<style scoped lang="less"> + /* 鏍峰紡鍙互鍦ㄨ繖閲屽畾涔� */ + + .table-content { + display: flex; + justify-content: space-between; + border-top: 1px solid #f0f0f0; + + .left { + width: 50%; + height: 100%; + border-right: 1px solid #f0f0f0; + } + + .right { + width: 50%; + height: 100%; + } + } + + ::v-deep(.ant-table) { + min-height: 355px !important; + } +</style> diff --git a/src/views/email/components/emailDetail.vue b/src/views/email/components/emailDetail.vue new file mode 100644 index 0000000..2b41ec9 --- /dev/null +++ b/src/views/email/components/emailDetail.vue @@ -0,0 +1,11 @@ +<template> + <PageWrapper dense contentFullHeight fixedHeight> + 888 + </PageWrapper> +</template> + +<script lang="ts" setup> +import { PageWrapper } from '@/components/Page'; +</script> + +<style lang="less" scoped></style> diff --git a/src/views/email/index.vue b/src/views/email/index.vue index 2fd0db9..a3186f6 100644 --- a/src/views/email/index.vue +++ b/src/views/email/index.vue @@ -1,45 +1,167 @@ -锘�<template> - <PageWrapper :class="`${prefixCls}`" dense contentFullHeight fixedHeight> - - <Splitpanes class="default-theme" :push-other-panes="false" style="height: 100%"> - <Pane min-size="12" size="12"> - <LeftNav></LeftNav> - </Pane> - <Pane min-size="50" size="88" > - <ScrollContainer class="p-4"> - <Editor></Editor> - </ScrollContainer> - </Pane> - <!-- <Pane min-size="5" size="15">鍙�</Pane> --> - </Splitpanes> - </PageWrapper> +锘� +<template> + <div> + <a-layout class="default-theme" style="display: flex; height: 100%; background-color: #fff"> + <a-layout-sider width="250" style="border-inline-end: 1px solid rgb(5 5 5 / 6%)" :style="siderStyle" v-show="collapseStore.isOpen"> + <LeftNav></LeftNav> + </a-layout-sider> + <a-layout> + <a-layout-content :style="contentStyle"> + <RouterView> + <template #default="{ Component, route }"> + <transition + :name=" + getTransitionName({ + route, + openCache, + enableTransition: getEnableTransition, + cacheTabs: getCaches, + def: getBasicTransition, + }) + " + mode="out-in" + appear + > + <keep-alive v-if="openCache" :include="getCaches"> + <component :is="Component" :key="route.fullPath" /> + </keep-alive> + <component v-else :is="Component" :key="route.fullPath" /> + </transition> + </template> + </RouterView> + </a-layout-content> + </a-layout> + </a-layout> + <div @click="fuToggleContent(!collapseStore.isOpen)" class="toggle-btn" :class="collapseStore.isOpen ? 'iconOpen' : 'onIconOpen'"> + <LeftOutlined v-if="collapseStore.isOpen" /> + <RightOutlined v-else /> + </div> + <FrameLayout v-if="getCanEmbedIFramePage" /> + </div> </template> <script lang="ts" setup> -import {PageWrapper} from '@/components/Page'; -import {onMounted} from 'vue'; -import {useDesign} from "@/hooks/web/useDesign"; -import {Splitpanes, Pane} from 'splitpanes'; -import 'splitpanes/dist/splitpanes.css'; -import LeftNav from './components/LeftNav.vue'; -import Editor from './components/Editor.vue'; -import ScrollContainer from "@/components/Container/src/ScrollContainer.vue"; + import { PageWrapper } from '@/components/Page'; + import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; + import LeftNav from '@/views/email/components/LeftNav.vue'; + import { computed, unref, ref } from 'vue'; -onMounted(() => { - Logger.log('Hello, Logger!1'); -}); -const {prefixCls} = useDesign('email'); + import FrameLayout from '@/layouts/iframe/index.vue'; -</script> + import { useRootSetting } from '@/hooks/setting/useRootSetting'; -<style lang="less" scoped> -@prefix-cls: ~'@{namespace}-email'; -.@{prefix-cls} { - .splitpanes__pane { - display: flex; - justify-content: center; - align-items: center; - background-color: var(--component-background-color) + import { useTransitionSetting } from '@/hooks/setting/useTransitionSetting'; + import { useMultipleTabSetting } from '@/hooks/setting/useMultipleTabSetting'; + + import { useMultipleTabStore } from '@/store/modules/multipleTab'; + + defineOptions({ name: 'PageLayout' }); + import type { FunctionalComponent } from 'vue'; + import type { RouteLocation } from 'vue-router'; + + export interface DefaultContext { + Component: FunctionalComponent & { type: Recordable }; + route: RouteLocation; } -} + const siderStyle = { + lineHeight: '120px', + backgroundColor:'#fff', + }; + const contentStyle = { + lineHeight: '120px', + backgroundColor:'#fff', + +}; + function getTransitionName({ + route, + openCache, + cacheTabs, + enableTransition, + def, + }: Pick<DefaultContext, 'route'> & { + enableTransition: boolean; + openCache: boolean; + def: string; + cacheTabs: string[]; + }): string | undefined { + if (!enableTransition) { + return undefined; + } + + const isInCache = cacheTabs.includes(route.name as string); + const transitionName = 'fade-slide'; + let name: string | undefined = transitionName; + + if (openCache) { + name = isInCache && route.meta.loaded ? transitionName : undefined; + } + return name || (route.meta.transitionName as string) || def; + } + + const { getShowMultipleTab } = useMultipleTabSetting(); + const tabStore = useMultipleTabStore(); + + const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting(); + + const { getBasicTransition, getEnableTransition } = useTransitionSetting(); + + const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab)); + + const getCaches = computed((): string[] => { + if (!unref(getOpenKeepAlive)) { + return []; + } + return tabStore.getCachedTabList; + }); + import { useDesign } from '@/hooks/web/useDesign'; + + const { prefixCls } = useDesign('email'); + + + import { useCollapseStore } from '@/store/modules/useCollapseStore'; + const collapseStore = useCollapseStore(); + function fuToggleContent(is) { + collapseStore.toggle(is); + } +</script> +<style lang="less" scoped> + @prefix-cls: ~'@{namespace}-email'; + .@{prefix-cls} { + .splitpanes__pane { + display: flex; + align-items: center; + justify-content: center; + background-color: #fff; + } + } + + + .toggle-btn { + display: flex; + position: absolute; + z-index: 99; + top: 45%; + width: 20px; /* 鍘熷瀹藉害 */ + height: 54px; + transform: translateY(-50%); + border-left: none; + border-radius: 0 10px 10px 0; + background: #dfe1e5; + } + + .onOpen { + width: 86vw; + } + + .isOpen { + width: 100vw; + } + + .iconOpen { + left: 250px; + } + + .onIconOpen { + left: 0%; + } </style> diff --git a/src/views/email/outbox/index.vue b/src/views/email/outbox/index.vue new file mode 100644 index 0000000..e5386d2 --- /dev/null +++ b/src/views/email/outbox/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" > </PageIndex> + </a-spin> + </div> +</template> + +<script lang="ts" setup> + name: 'outbox'; + 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: 2,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/preview/index..vue b/src/views/email/preview/index..vue new file mode 100644 index 0000000..6913562 --- /dev/null +++ b/src/views/email/preview/index..vue @@ -0,0 +1,86 @@ +<template> + <a-card :title="data.subject"> + <div style="display: flex; align-items: center" class="p-4"> + <div class="left"> + <a-avatar :size="{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }"> + <template #icon> + <AntDesignOutlined /> + </template> + </a-avatar> + </div> + <div class="right"> + <div class="top"> + <div class="top-left"> + <div> + <span class="top-left-title">鍙戜欢浜猴細</span> + <span class="top-right-title">{{ data.sender }}</span> + </div> + <div> + <span class="top-left-title">鏀朵欢浜猴細</span> + <span class="top-right-title">{{ `${data.receiver}` }}</span> + </div> + <div> + <span class="top-left-title">鏃堕棿锛�</span> + <span class="top-right-title">{{ data.createTime }}</span> + </div> + </div> + </div> + </div> + </div> + <TinymcePw ref="TinymcePwRef" v-model="data.content"></TinymcePw> + </a-card> +</template> + +<script lang="ts" setup> + name: 'previewIndex'; + import { AntDesignOutlined } from '@ant-design/icons-vue'; + + import { ref, onMounted, computed, } from 'vue'; + import { TinymcePw } from '@/components/Tinymce'; + import { useRouter } from 'vue-router'; + import { getMailInfoApi } from '@/api/email/userList'; + import { nextTick } from 'vue'; + + const { currentRoute } = useRouter(); + const docCode = computed(() => { + return currentRoute.value.query.docCode; + }); + const data = ref<Record<string, any>>({}); + const TinymcePwRef = ref() +function fnGetData() { + getMailInfoApi({ docCode: docCode.value }) + .then((res) => { + console.log(res, 'res'); + nextTick(() => { + console.log(data.value, 'data'); + data.value = res.data; + if (!TinymcePwRef || TinymcePwRef.value === undefined) { + setTimeout(() => { + Object.assign(data.value, data.value.content); + }, 1000); + } + }); + }) + .catch((error) => { + console.error('鑾峰彇鏁版嵁澶辫触:', error); + // 鍙互鍦ㄨ繖閲屾坊鍔犲叾浠栭敊璇鐞嗛�昏緫锛屼緥濡傞�氱煡鐢ㄦ埛 + }); +} + onMounted(() => { + fnGetData(); + }); +</script> + +<style scoped lang="less"> + .right { + margin-left: 30px; + } + + .top-left-title { + color: #999; + } + + .top-right-title { + color: #000; + } +</style> diff --git a/types/axios.d.ts b/types/axios.d.ts index 384ec33..9adcee6 100644 --- a/types/axios.d.ts +++ b/types/axios.d.ts @@ -41,6 +41,10 @@ type: 'success' | 'error' | 'warning'; message: string; result: T; + msg: string; + data: T; + total: number; + state: number; } // multipart/form-data: upload file diff --git a/uno.config.ts b/uno.config.ts index 5e3512f..a947a87 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -1,5 +1,6 @@ import { defineConfig, presetTypography, presetUno } from 'unocss'; export default defineConfig({ + rules: [['my-d-f', { display: 'flex', 'align-items': 'center' }]], presets: [presetUno(), presetTypography()], }); -- Gitblit v1.8.0