| | |
| | | // 先检查proxy是否存在,再进行操作 |
| | | if (proxy && proxy.$cookies) { |
| | | proxy.$cookies.remove('JSESSIONID'); |
| | | proxy.$cookies.set('JSESSIONID', 'F0442747E3B63FB7C854454C925C1BD6.jvm_59_9010', '1d'); |
| | | proxy.$cookies.set('JSESSIONID', '6D5DE448FED8F63528B386BF5E78D3EE.jvm_59_9010', '1d'); |
| | | } else { |
| | | console.error('proxy对象未初始化或不包含$cookies属性'); |
| | | } |
| | |
| | | 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', |
| | | |
| | | |
| | | } |
| | |
| | | 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, |
| | | }) |
| | | |
| | |
| | | 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); |
| | | |
| | |
| | | <div :class="prefixCls" :style="{ width: containerWidth }"> |
| | | <textarea |
| | | :id="tinymceId" |
| | | ref="elRef" |
| | | 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'; |
| | |
| | | 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'; |
| | |
| | | height: { |
| | | type: [Number, String] as PropType<string | number>, |
| | | required: false, |
| | | default: 200, |
| | | default: 400, |
| | | }, |
| | | width: { |
| | | type: [Number, String] as PropType<string | number>, |
| | |
| | | fontsize: { |
| | | type: String, |
| | | }, |
| | | isElse: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | isText: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | isImg: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | }); |
| | | console.log('Editor', props); |
| | | |
| | | const emit = defineEmits(['change', 'update:modelValue', 'inited', 'init-error']); |
| | | |
| | | const attrs = useAttrs(); |
| | | const editorRef = ref<Editor | null>(null); |
| | | const tinymceId = ref<string>(buildShortUUID('tiny-vue')); |
| | | const elRef = ref<HTMLElement | 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'); |
| | | |
| | |
| | | fontsize_formats: '10px 11px 12px 14px 16px 18px 24px 36px 48px 48px 56px 72px', |
| | | image_advtab: true, |
| | | importcss_append: true, // 允许样式生效 |
| | | toolbar:[], |
| | | toolbar_sticky: true, // 粘性工具栏(或停靠工具栏),在向下滚动网页直到不再可见编辑器时,将工具栏和菜单停靠在屏幕顶部 |
| | | toolbar_mode: 'floating', //默认wrap不收缩工具栏,取值为floating或sliding时,将第一行放不下的工具栏按钮缩进抽屉(3个点的图标)里,scrolling则采用移动端的横线滚动方式。 |
| | | // style_formats_autohide: 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', |
| | |
| | | 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)); |
| | | }, |
| | | // 只读模式 |
| | | readonly: true, |
| | | }; |
| | | }); |
| | | |
| | | 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( |
| | |
| | | } |
| | | |
| | | function initEditor() { |
| | | const el = unref(elRef); |
| | | const el = unref(elPwRef); |
| | | if (el) { |
| | | el.style.visibility = ''; |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | 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 ( |
| | | editor && |
| | |
| | | val !== editor.getContent({ format: attrs.outputFormat }) |
| | | ) { |
| | | editor.setContent(val); |
| | | }else{ |
| | | editor.setContent(''); |
| | | console.log('-5-5-5555555555'); |
| | | |
| | | } |
| | | } |
| | | |
| | | |
| | | function bindModelHandlers(editor: any) { |
| | | const modelEvents = attrs.modelEvents ? attrs.modelEvents : null; |
| | | const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents; |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (val, prevVal) => { |
| | | 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'; |
| | |
| | | } |
| | | } |
| | | |
| | | .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; |
| | |
| | | currentActiveMenu: '/email/index', |
| | | }, |
| | | }, |
| | | { |
| | | path: 'list/:id', |
| | | name: 'EmailPage4', |
| | | component: () => import('@/views/email/UnreadEmail/user.vue'), |
| | | meta: { |
| | | title: '1111', |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: 'index', |
| | | name: 'Index', |
| | | component: () => import('@/views/email/UnreadEmail/index.vue'), |
| | | |
| | | meta: { |
| | | title: '邮件', |
| | | currentActiveMenu: '/email/index', |
| | |
| | | throw new Error(t('sys.api.apiRequestFailed')); |
| | | } |
| | | // 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式 |
| | | const { code, result } = data; |
| | | const { msg: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; |
| | | |
| | |
| | | import { useMessage } from '@/hooks/web/useMessage'; |
| | | import { Tinymce } from '@/components/Tinymce'; |
| | | import { PlusCircleOutlined } from '@ant-design/icons-vue'; |
| | | import SelectUser from '../components/SelectUser.vue'; |
| | | import SelectUser from '../components/SelectUser/index.vue'; |
| | | import { Form } from 'ant-design-vue'; |
| | | const modelRef = reactive({ |
| | | sender: '', |
| | |
| | | <template> |
| | | <div> |
| | | <Page :pageList="pageList"></Page> |
| | | <PageIndex :pageList="pageList"></PageIndex> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | name: 'Inbox'; |
| | | import { ref, onMounted, computed } from 'vue'; |
| | | import Page from '@/views/email/components/ListPage/list.vue'; |
| | | import { ref, onMounted, computed, provide } from 'vue'; |
| | | import PageIndex from '@/views/email/components/ListPage/list.vue'; |
| | | import { useRoute } from 'vue-router'; |
| | | |
| | | const route = useRoute(); |
| | |
| | | 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 } from '@/api/email/userList'; |
| | | import { receiveApi, getMailListApi } from '@/api/email/userList'; |
| | | const pageList = ref([]); |
| | | function getDataList() { |
| | | receiveApi({ mail: routerId.value }).then((res) => {}); |
| | | getMailListApi({ mail: routerId.value }).then((res) => { |
| | | if (res.code == 0) { |
| | | pageList.value = res.data; |
| | | } |
| | | }); |
| | | } |
| | | provide('getDataList',getDataList); |
| | | onMounted(() => { |
| | | getDataList(); |
| | | }); |
| | | const pageList = ref([ |
| | | { |
| | | title: '收件箱', |
| | | path: '/email/index', |
| | | icon: 'ion:mail-outline', |
| | | }, |
| | | ]); |
| | | </script> |
| | | <style scoped lang="less"></style> |
| | |
| | | <template> |
| | | <div class="p-2" style="margin-left: 20px"> |
| | | <div class="title" style="font-size: 20px">常规</div> |
| | | <h2 class="title" style="font-size: 20px">常规</h2> |
| | | |
| | | <a-form style="width: 60%" :labelCol="{ span: 6 }"> |
| | | <div> |
| | | <div class="title">账号</div> |
| | | <h2 class="title">账号</h2> |
| | | <a-form-item label="默认邮箱"> |
| | | <a-select> |
| | | <a-select-option v-for="(item, index) in emailList" :key="index" :value="item.email"> |
| | |
| | | </div> |
| | | |
| | | <div> |
| | | <div class="title">签名</div> |
| | | <h2 class="title">签名</h2> |
| | | <a-form-item label="个性签名"> |
| | | <div style="display: flex"> |
| | | <a-select @change="fnSignatureChange" v-model:value="signature"> |
| | |
| | | <PlusCircleOutlined style="margin-left: 20px" @click="showModal('add')" /> |
| | | </div> |
| | | </a-form-item> |
| | | <a-form-item :labelCol="{ span: 6 }"> |
| | | <a-form-item :labelCol="{ span: 6 }" label="内容"> |
| | | <div style="height: 200px; overflow: hidden; border-bottom: 1px solid #d9d9d9"> |
| | | <Tinymce v-model="signContent"></Tinymce> |
| | | <TinymcePw v-model="signContent"></TinymcePw> |
| | | </div> |
| | | <div style="margin-top: 20px; text-align: right"> |
| | | <a-button type="primary" size="small">编辑</a-button> |
| | | <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> |
| | |
| | | <a-input v-model:value="form.signName" placeholder="请输入名称" /> |
| | | </a-form-item> |
| | | <a-form-item label="内容"> |
| | | <TinymceAdd v-model="form.signContent" :isElse="false" :isText="false"></TinymceAdd> |
| | | <Tinymce v-model="form.signContent" :isElse="false" :isText="false"></Tinymce> |
| | | </a-form-item> |
| | | </a-form> |
| | | </a-modal> |
| | |
| | | |
| | | <script lang="ts" setup> |
| | | name: 'convention'; |
| | | import { onMounted, ref } from 'vue'; |
| | | import { nextTick, onMounted, ref } from 'vue'; |
| | | import { getAccountListApi } from '@/api/email/userList'; |
| | | import { PlusCircleOutlined } from '@ant-design/icons-vue'; |
| | | import Tinymce from '@/components/Tinymce/src/index.vue'; |
| | | import TinymceAdd from '@/components/Tinymce/src/Editor.vue'; |
| | | import { addSignatureApi, getSignatureApi } from '@/api/email/userList'; |
| | | import { TinymcePw, Tinymce } from '@/components/Tinymce'; |
| | | import { addSignatureApi, getSignatureApi, updateSignatureApi } from '@/api/email/userList'; |
| | | |
| | | interface EmailItem { |
| | | email: string; |
| | |
| | | }); |
| | | 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; |
| | | form.value = { |
| | | signName: '', |
| | | signContent: '', |
| | | }; |
| | | 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('<p>ddd</p>'); |
| | | const signContent = ref(); |
| | | function fnSignatureChange(e) { |
| | | const matchedItem = signatureList.value.find((item) => item.signId === e); |
| | | if (matchedItem) { |
| | |
| | | } else { |
| | | signContent.value = ''; |
| | | } |
| | | console.log(signContent.value, '05050050505'); |
| | | } |
| | | const signature = ref(); |
| | | const handleOk = (e: MouseEvent) => { |
| | | console.log(form.value, '----4'); |
| | | |
| | | formRef.value |
| | | .validate() |
| | | .then(() => { |
| | | const data = form.value; |
| | | // const api = signType.value !== 'add' ? updateAccountApi : addSignatureApi; |
| | | const api = addSignatureApi; |
| | | const api = signType.value == 'update' ? updateSignatureApi : addSignatureApi; |
| | | // const api = addSignatureApi; |
| | | api(data).then((res) => { |
| | | if (res.code === 0) { |
| | | createMessage.success(res.msg); |
| | |
| | | console.log(e); |
| | | }); |
| | | }; |
| | | const form = ref<Record<string, any>>({}); |
| | | const title = '新建'; |
| | | </script> |
| | | <style scoped lang="less"> |
| | | .title { |
| | |
| | | <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="快速文本">Content of Tab Pane 3</a-tab-pane> |
| | | <a-tab-pane key="3" tab="文件夹">Content of Tab Pane 3</a-tab-pane> |
| | | <a-tab-pane key="3" tab="标签">Content of Tab Pane 3</a-tab-pane> |
| | | <a-tab-pane key="3" tab="黑名单">Content of Tab Pane 3</a-tab-pane> |
| | | </a-tabs> |
| | | <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="文件夹">Content of Tab Pane 3</a-tab-pane> |
| | | <a-tab-pane key="5" tab="标签">Content of Tab Pane 3</a-tab-pane> |
| | | <a-tab-pane key="6" tab="黑名单">Content of Tab Pane 3</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' |
| | | |
| | | const activeKey = ref('1'); |
| | | import MailboxManagement from './mailboxManagement.vue'; |
| | | import Convention from './convention.vue'; |
| | | import QuickText from './quickText.vue'; |
| | | </script> |
| | | <style scoped lang="less"></style> |
| | |
| | | ref="xTable" |
| | | id="key" |
| | | style="margin: 10px 0" |
| | | :data="data" |
| | | :data="demo.tableData" |
| | | min-height="40px" |
| | | row-key |
| | | keep-source |
| | |
| | | :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 /> --> |
| | |
| | | :confirmLoading="loading" |
| | | @ok="fnHandleOk" |
| | | > |
| | | <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 name="password" label="邮箱密码"> |
| | | <a-input type="password" v-model:value="formData.password" /> |
| | | </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> |
| | | </a-form-item> |
| | | <!-- <a-form-item v-if="formData.receiveProtocol === 'exchange'" :name="['user', 'email']" label="Exchange服务器" :rules="[{ type: 'email' }]"> |
| | | <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协议支持问题,网易邮箱推荐使用IMAP协议。 |
| | | 如需调整协议类型,请联系小满客服</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-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 |
| | | <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 |
| | | > |
| | | </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-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、了解常见几类邮箱的具体绑定方法</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、了解常见几类邮箱的具体绑定方法</a |
| | | ></div |
| | | ></div |
| | | > |
| | | </a-modal> |
| | |
| | | <a-form-item label="邮箱密码"> |
| | | <div class="form-item"> |
| | | <div class="left" :class="checkStatus ? 'isColor' : 'isRed'"> ********</div> |
| | | <div class="right" |
| | | > |
| | | <div class="right"> |
| | | <a-spin v-if="!isCheck" :indicator="indicator" /> |
| | | <CheckCircleOutlined v-else-if="checkStatus" class="isColor" /> |
| | | <CloseCircleOutlined v-else class="isRed" /> |
| | |
| | | <a-tooltip placement="right"> |
| | | <template #title> |
| | | <span |
| | | >开启后,对于新绑定的邮箱,全量同步文件夹及文件夹内的邮件。对于已经绑定的邮箱,全量同步文件夹及文件夹新收取的邮件,历史邮件不移动</span |
| | | >开启后,对于新绑定的邮箱,全量同步文件夹及文件夹内的邮件。<br />对于已经绑定的邮箱,全量同步文件夹及文件夹新收取的邮件,历史邮件不移动</span |
| | | > |
| | | </template> |
| | | <QuestionCircleOutlined style="margin-right: 5px" /> |
| | |
| | | CheckCircleOutlined, |
| | | CloseCircleOutlined, |
| | | } from '@ant-design/icons-vue'; |
| | | import { ref } from 'vue'; |
| | | import { ref, reactive, onUnmounted } from 'vue'; |
| | | import { |
| | | addAccountApi, |
| | | getAccountApi, |
| | |
| | | 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); |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const data = ref([]); |
| | | let initTime: any; |
| | | nextTick(() => { |
| | | // 加载完成之后在绑定拖动事件 |
| | | initTime = setTimeout(() => { |
| | | rowDrop(); |
| | | }, 500); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | clearTimeout(initTime); |
| | | if (sortable) { |
| | | sortable.destroy(); |
| | | } |
| | | }); |
| | | |
| | | function fnMailList() { |
| | | getAccountListApi() |
| | | .then((res) => { |
| | | if (res.code == 0) { |
| | | data.value = res.data; |
| | | demo.tableData = res.data; |
| | | } |
| | | console.log(res); |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | data.value = []; |
| | | demo.tableData = []; |
| | | }); |
| | | } |
| | | fnMailList(); |
| | |
| | | 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(); |
| | |
| | | createMessage.success(res.msg); |
| | | } |
| | | loading.value = false; |
| | | fnMailList(); |
| | | }) |
| | | .catch((err) => { |
| | | loading.value = false; |
| | |
| | | } |
| | | const title = ref('添加'); |
| | | const typeAccount = ref(1); |
| | | function openAccount(type, row) { |
| | | function openAccount(type, email) { |
| | | formData.value = { ...defaultFormData }; |
| | | try { |
| | | if (type == 'add') { |
| | |
| | | title.value = '修改'; |
| | | typeAccount.value = 2; |
| | | isShow.value = true; |
| | | getAccountApi({ mail: '1244041895@qq.com' }).then((res) => { |
| | | getAccountApi({ mail: email }).then((res) => { |
| | | formData.value = res.data; |
| | | }); |
| | | } |
New file |
| | |
| | | <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> |
| | |
| | | import { useMessage } from '@/hooks/web/useMessage'; |
| | | import { Tinymce } from '@/components/Tinymce'; |
| | | import { PlusCircleOutlined } from '@ant-design/icons-vue'; |
| | | import SelectUser from './SelectUser.vue'; |
| | | import SelectUser from './SelectUser/SelectUser.vue'; |
| | | import { Form } from 'ant-design-vue'; |
| | | const modelRef = reactive({ |
| | | sender: '', |
| | |
| | | title: '提示', |
| | | content: '邮件主题为空,确定要发送吗?', |
| | | onOk() { |
| | | pushSendingMail() |
| | | pushSendingMail(); |
| | | }, |
| | | onCancel() {}, |
| | | }); |
| | | }else{pushSendingMail()} |
| | | |
| | | } else { |
| | | pushSendingMail(); |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | createMessage.error('表单验证失败'); |
| | | }); |
| | | } |
| | | |
| | | function pushSendingMail(){ |
| | | const data = fnBuildingCommitData(); |
| | | sendingMailApi(data).then((res) => { |
| | | |
| | | }); |
| | | function pushSendingMail() { |
| | | const data = fnBuildingCommitData(); |
| | | sendingMailApi(data).then((res) => {}); |
| | | } |
| | | function fnPreview() {} |
| | | |
| | |
| | | value: string; |
| | | email: string; |
| | | title: string; |
| | | name: string |
| | | name: string; |
| | | } |
| | | |
| | | const userParticulars = computed<Recipient | null>(() => { |
| | |
| | | <script lang="ts" setup> |
| | | import { ref, onMounted } from 'vue'; |
| | | import { PageWrapper } from '@/components/Page'; |
| | | import { Menu } from 'ant-design-vue'; |
| | | import { MailOutlined, UserOutlined } from '@ant-design/icons-vue'; |
| | | import { getEmailModuleApi } from '@/api/email/userList'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | const selectedKeys = ref<string[]>(['Index']); |
| | | const openKeys = ref<string[]>(['index']); |
| | | const openKeys = ref<string[]>(['Inbox']); |
| | | const items = ref([]); // 定义 items 类型 |
| | | |
| | | const fnGetEmailModule = async () => { |
| | |
| | | }); |
| | | const routesConfig = { |
| | | InboxPage1: '/email/index', |
| | | page: '/email/Inbox/list', |
| | | receiver: '/email/Inbox/list', |
| | | }; |
| | | // 点击事件处理 |
| | | const router = useRouter(); |
| | |
| | | router.push(routesConfig[e.key]); |
| | | matched = true; |
| | | return; // 跳出当前循环 |
| | | case 'page': |
| | | case 'receiver': |
| | | router.push(`${routesConfig[e.key]}?${e.title}`); |
| | | matched = true; |
| | | return; // 跳出当前循环 |
| | |
| | | CopyOutlined, |
| | | UserOutlined |
| | | } from '@ant-design/icons-vue'; |
| | | import pageHeadLeft from '../pageHeadLeft.vue'; |
| | | import pageHeadLeft from './pageHeadLeft.vue'; |
| | | import Tinymce from '@/components/Tinymce/src/index.vue'; |
| | | |
| | | // 定义属性 |
| | |
| | | ></a-checkbox> |
| | | <!--更新 --> |
| | | <SyncOutlined class="icon" v-show="!checked" /> |
| | | <pageHeadLeft :checked="checked"></pageHeadLeft> |
| | | <pageHeadLeft :checked="checked" :selectAllRow="selectAllRow" |
| | | :parentTableList='newList' |
| | | ></pageHeadLeft> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | <Table |
| | | ref="tableRef" |
| | | :page="pageCurrent" |
| | | :pageList="newList" |
| | | @selectAll="fnSelectAll" |
| | | @updateSelectAll="updateSelectAll" |
| | | /> |
| | |
| | | FilterOutlined, |
| | | PushpinOutlined, |
| | | } from '@ant-design/icons-vue'; |
| | | import pageHeadLeft from '../pageHeadLeft.vue'; |
| | | import pageHeadLeft from './pageHeadLeft.vue'; |
| | | import { PageWrapper } from '@/components/Page'; |
| | | |
| | | import { ref, watch, defineProps, defineEmits, computed, reactive, onMounted } from 'vue'; |
| | | |
| | | // 定义属性 |
| | | interface Props { |
| | | modelValue: boolean; |
| | | title?: string; |
| | | placement?: 'left' | 'right' | 'top' | 'bottom'; |
| | | idName?: string; |
| | | pageList?: []; |
| | | pageList: []; |
| | | } |
| | | const props = defineProps<Props>(); |
| | | const newList = ref([]); |
| | | const selectAllRow = ref([]); |
| | | watch( |
| | | () => props.pageList, |
| | | (newValue) => { |
| | | newList.value = newValue; |
| | | }, |
| | | ); |
| | | |
| | | const checked = ref(false); |
| | | const pageCurrent = ref(1); |
| | |
| | | checked.value = e.target.checked; |
| | | } |
| | | function updateSelectAll(data) { |
| | | selectAllRow.value = data.records; |
| | | if (!data.isAll) { |
| | | state.indeterminate = true; |
| | | state.checkAll = false; |
| | |
| | | const activeKey = ref('1'); |
| | | const checked3 = ref(false); |
| | | import Table from './table.vue'; |
| | | import { router } from '@/router'; |
| | | onMounted(() => { |
| | | console.log('tableRef:', tableRef.value[0]); |
| | | }); |
| | | function fnSelectAll() { |
| | | console.log('44444444444'); |
| | | } |
| | | |
| | | </script> |
| | | <style scoped lang="less"> |
| | | .head { |
New file |
| | |
| | | <template> |
| | | <div class="left-box"> |
| | | <!-- 分发 --> |
| | | <a-tooltip placement="bottom"> |
| | | <template #title> |
| | | <span>分发</span> |
| | | </template> |
| | | <ExportOutlined v-show="checked" class="icon" /> |
| | | </a-tooltip> |
| | | <!-- 时间 --> |
| | | <ClockCircleOutlined class="icon" v-show="checked" /> |
| | | <!-- 删除 --> |
| | | <a-tooltip placement="bottom"> |
| | | <template #title> |
| | | <span>删除</span> |
| | | </template> |
| | | <DeleteOutlined v-show="checked" class="icon" /> |
| | | </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 { |
| | | 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 } from '@/api/email/userList'; |
| | | |
| | | const getDataList = inject('getDataList'); |
| | | function fnSelectAllRead(is) { |
| | | const data = { |
| | | status: is, |
| | | list: getReadId(), |
| | | }; |
| | | pushUpdateReadApi(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 |
| | | } |
| | | </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> |
| | |
| | | @cell-click="cellClickEvent" |
| | | @checkbox-change="selectChangeEvent" |
| | | > |
| | | <vxe-column type="checkbox" width="60"></vxe-column> |
| | | <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"> |
| | | <a-avatar size="small" style="margin-right: 8px" :src="row.avatar" /> |
| | | <div |
| | | 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 |
| | |
| | | " |
| | | > |
| | | <a-avatar size="small" style="margin-right: 8px" :src="row.avatar" /> |
| | | <span style="color: #000; font-weight: 700">{{ row.email }}</span> |
| | | <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></div |
| | | > |
| | | </template> |
| | | <div> |
| | | <span style="font-weight: 700">{{ row.sender }}</span |
| | | ><span style="padding: 0 8px; color: #999">|</span> |
| | | <span style="color: #999; font-weight: 500">{{ row.email }}</span> |
| | | <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> |
| | |
| | | </vxe-column> |
| | | <vxe-column show-overflow field="subject" title="表题" data-index="subject" min-width="250"> |
| | | <template #default="{ row }"> |
| | | <span style="color: #3081fe; font-weight: 500">{{ row.subject }}</span> - |
| | | <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> |
| | |
| | | DownOutlined, |
| | | } from '@ant-design/icons-vue'; |
| | | |
| | | import { ref, watch, defineProps, defineEmits, computed, defineExpose } from 'vue'; |
| | | import { ref, watch, defineProps, defineEmits, computed, defineExpose, inject } from 'vue'; |
| | | |
| | | // 定义属性 |
| | | interface Props { |
| | | page: number; |
| | | pageList?: []; |
| | | pageList: []; |
| | | } |
| | | // const props = defineProps<Props>(); |
| | | const props = defineProps<Props>(); |
| | | |
| | | const dataSource = [ |
| | | { |
| | | sender: '百旺金税云', |
| | | email: 'gdbwjf.dzfp3@gdfapiao.com', |
| | | subject: '您收到一张【佛山火炬创新创业园有限公司】开具的发票【发票号码: ...', |
| | | date: '2024-09-08 20:19', |
| | | }, |
| | | { |
| | | sender: '百旺金税云', |
| | | email: 'gdbwjf.dzfp3@gdfapiao.com', |
| | | subject: '您收到一张【佛山火炬创新创业园有限公司】开具的发票【发票号码: ...', |
| | | date: '2024-07-09 20:19', |
| | | }, |
| | | { |
| | | sender: '百旺金税云', |
| | | email: 'gdbwjf.dzfp3@gdfapiao.com', |
| | | subject: '您收到一张【佛山火炬创新创业园有限公司】开具的发票【发票号码: ...', |
| | | date: '2024-09-06 20:19', |
| | | }, |
| | | { |
| | | sender: '百旺金税云', |
| | | email: 'gdbwjf.dzfp3@gdfapiao.com', |
| | | subject: '您收到一张【佛山火炬创新创业园有限公司】开具的发票【发票号码: ...', |
| | | date: '2024-09-07 20:19', |
| | | }, |
| | | { |
| | | sender: '百旺金税云', |
| | | email: 'gdbwjf.dzfp3@gdfapiao.com', |
| | | subject: '您收到一张【佛山火炬创新创业园有限公司】开具的发票【发票号码: ...', |
| | | date: '2024-09-07 20:19', |
| | | }, |
| | | { |
| | | sender: '百旺金税云', |
| | | email: 'gdbwjf.dzfp3@gdfapiao.com', |
| | | subject: '您收到一张【佛山火炬创新创业园有限公司】开具的发票【发票号码: ...', |
| | | date: '2024-09-05 20:19', |
| | | }, |
| | | { |
| | | sender: '百旺金税云', |
| | | email: 'gdbwjf.dzfp3@gdfapiao.com', |
| | | subject: '您收到一张【佛山火炬创新创业园有限公司】开具的发票【发票号码: ...', |
| | | date: '2024-09-05 20:19', |
| | | }, |
| | | { |
| | | sender: '微信支付小助手', |
| | | email: 'weixinpay@tencent.com', |
| | | subject: '自动提现失败通知 - 微信商户平台', |
| | | date: '2024-09-05 11:46', |
| | | }, |
| | | { |
| | | sender: '网易外贸通', |
| | | email: 'mianfei@qiye.163.com', |
| | | subject: '开发外贸客户很难?这些方法开发精准客户效率直接提升3倍', |
| | | date: '2024-09-04 10:13', |
| | | }, |
| | | { |
| | | sender: 'Apple Developer', |
| | | email: 'developer@insideapple.app', |
| | | subject: '开发者你好:2024年9月准备好迎接 Apple Intelligence...', |
| | | date: '2024-09-04 01:13', |
| | | }, |
| | | { |
| | | sender: '微信支付小助手', |
| | | email: 'weixinpay@tencent.com', |
| | | subject: '自动提现失败通知 - 微信商户平台', |
| | | date: '2024-09-03 11:45', |
| | | }, |
| | | { |
| | | sender: 'notice', |
| | | email: 'notice@qiye.163.com', |
| | | subject: '【反垃圾通知】您的邮箱共收到1封异常邮件', |
| | | date: '2024-09-03 10:02', |
| | | }, |
| | | { |
| | | sender: '三星电子', |
| | | email: 'memberclub.club@samsung.com', |
| | | subject: '【三星电子】秋日畅享,以旧换“星”,等你来!', |
| | | date: '2024-09-02 18:15', |
| | | }, |
| | | { |
| | | sender: '极光', |
| | | email: 'support@amail.jpush.io', |
| | | subject: '极光统计运营周报 - 运营周报 ERP 2024.08.26-2024.09.01...', |
| | | date: '2024-09-02 12:00', |
| | | }, |
| | | { |
| | | sender: '微信支付小助手', |
| | | email: 'weixinpay@tencent.com', |
| | | subject: '自动提现失败通知 - 微信商户平台', |
| | | date: '2024-09-02 12:00', |
| | | }, |
| | | { |
| | | sender: 'Salesforce Dreamforce', |
| | | email: 'apacemarketing@salesforce.com', |
| | | subject: 'Session lineup is live. Explore now on Salesforce+', |
| | | date: '2024-09-02 11:01', |
| | | }, |
| | | { |
| | | sender: '网易外贸通', |
| | | email: 'mianfei@qiye.163.com', |
| | | subject: '更懂外贸的AI管家来了,帮您节省30%的人工成本', |
| | | date: '2024-09-02 09:55', |
| | | }, |
| | | ]; |
| | | 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'; |
| | |
| | | |
| | | return result; |
| | | } |
| | | const groupedEmails: GroupedDataItem[] = groupEmailsByDate(dataSource); |
| | | console.log(groupedEmails); |
| | | |
| | | // 右键菜单 |
| | | const tableMenu = { |
| | |
| | | vxeTableRef.value.forEach((row) => { |
| | | row.setAllCheckboxRow(is); |
| | | }); |
| | | selectChangeEvent() |
| | | } |
| | | |
| | | function selectChangeEvent() { |
| | | const isAll = getCheckboxRecords().length === dataSource.length; |
| | | const data = { |
| | | isAll, |
| | | records: getCheckboxRecords(), |
| | | } |
| | | emit('updateSelectAll',data,) |
| | | function selectChangeEvent() { |
| | | const isAll = getCheckboxRecords().length === dataSource.value.length; |
| | | const data = { |
| | | isAll, |
| | | records: getCheckboxRecords(), |
| | | }; |
| | | emit('updateSelectAll', data); |
| | | } |
| | | function getCheckboxRecords() { |
| | | const list = new Set(); |
| | |
| | | // collapseStore.toggle(); |
| | | }; |
| | | |
| | | const emit = defineEmits(['selectAll',"updateSelectAll"]); |
| | | // 更新祖父组件数据 |
| | | const getDataList = inject('getDataList'); |
| | | console.log(getDataList, '0000004'); |
| | | |
| | | // function sendUpdate() { |
| | | // getDataList({ a: '33' }); |
| | | // } |
| | | |
| | | import { updateReadApi } 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({ a: '33' }); |
| | | } |
| | | }); |
| | | } |
| | | const emit = defineEmits(['selectAll', 'updateSelectAll']); |
| | | defineExpose({ |
| | | fnSelectAll, |
| | | }); |
| | |
| | | .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> |
New file |
| | |
| | | <!-- 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"> |
| | | <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="id" |
| | | > |
| | | <!-- :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 } 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']); |
| | | |
| | | // 内部状态 |
| | | 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; |
| | | } 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.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<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[]) => { |
| | | 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> |
| | |
| | | result: T; |
| | | msg: string; |
| | | data: T; |
| | | total: number; |
| | | state: number; |
| | | } |
| | | |
| | | // multipart/form-data: upload file |