huangyinfeng
4 天以前 db42d08c39ae6129e2b95cd24c0d57c6769282e5
邮件右键菜单
2个文件已删除
8个文件已添加
16个文件已修改
2173 ■■■■ 已修改文件
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-lock.yaml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/email/userList.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/MyUpload/hooks/useUploadHook.ts 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/MyUpload/index.vue 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Tinymce/src/Editor.vue 109 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Tinymce/src/ImgUpload.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes/modules/email.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings/projectSetting.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/Edit/index.vue 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/Utils/folder.vue 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/Utils/mailboxManagement.vue 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/ContextMenu/CustomTimePicker.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/ContextMenu/index.vue 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/LeftMenu/EmailMenuItem.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/LeftMenu/MyMenu.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/LeftMenu/index.vue 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/LeftNav.vue 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/ListPage/TooltipAndDropdown .vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/ListPage/drawerDetail.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/ListPage/list.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/components/ListPage/table.vue 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/folder/index.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/email/label/index.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json
@@ -86,6 +86,7 @@
    "echarts": "^5.5.1",
    "exceljs": "^4.4.0",
    "html2canvas": "^1.4.1",
    "jsencrypt": "^3.3.2",
    "lodash-es": "^4.17.21",
    "mockjs": "^1.1.0",
    "nprogress": "^0.2.0",
pnpm-lock.yaml
@@ -59,6 +59,9 @@
      html2canvas:
        specifier: ^1.4.1
        version: 1.4.1
      jsencrypt:
        specifier: ^3.3.2
        version: 3.3.2
      lodash-es:
        specifier: ^4.17.21
        version: 4.17.21
@@ -5200,6 +5203,9 @@
    peerDependenciesMeta:
      canvas:
        optional: true
  jsencrypt@3.3.2:
    resolution: {integrity: sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==}
  jsesc@2.5.2:
    resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
@@ -13689,6 +13695,8 @@
      - supports-color
      - utf-8-validate
  jsencrypt@3.3.2: {}
  jsesc@2.5.2: {}
  json-buffer@3.0.1: {}
src/App.vue
@@ -26,7 +26,7 @@
    // 先检查proxy是否存在,再进行操作
    if (proxy && proxy.$cookies) {
      proxy.$cookies.remove('JSESSIONID');
      proxy.$cookies.set('JSESSIONID', '741E9E2AAD7578915B16287A5ECAE1DF.jvm_59_9010', '1d');
      proxy.$cookies.set('JSESSIONID', '071339196A579D1A2D374739F29D7521.jvm_59_9010', '1d');
    } else {
      console.error('proxy对象未初始化或不包含$cookies属性');
    }
src/api/email/userList.ts
@@ -49,6 +49,7 @@
  DELETE_BLACKLIST = '/crm/mail/blacklist/deleteBlackList.do',
  GET_BLACKLIST = '/crm/mail/blacklist/getBlackList.do',
  GET_EMAIL_MODULE_BELOW = '/crm/mail/getEmailModuleBelow.do',
  ADD_LIAS_EMAIL = '/crm/mail/account/addAliasEmail.do',
}
// 获取邮件路由列表
export const getEmailModuleApi = () => defHttp.get({ url: Api.GET_EMAIL_MODULE });
@@ -73,7 +74,8 @@
// 修改邮箱配置
export const updateAccountApi = (params) => defHttp.post<{}>({ url: Api.UPDATE_ACCOUNT, params });
// 删除邮箱配置
export const deleteAccountApi = (params) => defHttp.post<{}>({ url: Api.DELETE_ACCOUNT, params });
export const deleteAccountApi = (query) =>
  defHttp.post<{}>({ url: Api.DELETE_ACCOUNT + '?accountId=' + query });
// 获取邮箱列表
export const getAccountListApi = () => defHttp.get<{}>({ url: Api.GET_ACCOUNT_LIST });
@@ -158,7 +160,7 @@
  });
// 设置完成时间
export const updateHandleAPi = (params) =>
  defHttp.get({
  defHttp.post({
    url: Api.UPDATE_HANDLE,
    params,
  });
@@ -252,7 +254,6 @@
    params,
  });
// 新增黑名单
export const addBlackListApi = (params) =>
  defHttp.post({
@@ -281,9 +282,15 @@
    params,
  });
// 文件夹结构
export const getEmailModuleBelowApi = () =>
  defHttp.get({
    url: Api.GET_EMAIL_MODULE_BELOW,
  });
  export const getEmailModuleBelowApi = () =>
    defHttp.get({
      url: Api.GET_EMAIL_MODULE_BELOW,
    });
//邮箱别名
export const addLiasEmailApi = (params) =>
  defHttp.get({
    url: Api.ADD_LIAS_EMAIL,
    params,
  });
src/components/MyUpload/hooks/useUploadHook.ts
New file
@@ -0,0 +1,56 @@
import { ref } from 'vue';
import JSEncrypt from 'jsencrypt'; // 导入 JSEncrypt 库
export const useUploadHook = (baseUrl: string) => {
  const computedUploadUrl = ref(baseUrl);
  // RSA 加密用的公钥
  const publicKey = `-----BEGIN PUBLIC KEY-----
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRupiYcKVGGUtDBDoR1t/1zm3ZtZgnte39iTJW6hlqjdY0UagKjpNiIv7J6XjtgfX7SgsR4AWnivqQHAICIvdPKfGZZzIs62OQ19MqrDTMoB/LvK5teNWhClv23WMUfRbP+EHgprT6hTw8U5apw1IB6i/y57NkLav792wiYBYRU4X45NoTaT+aiTSLFEflbfm94EXnhSS3vFkBmrZGy5BRNI8gmzafroslGx2Hk90CqlNdeKYxgZQ6xtvj+u33yrszWvPT6F9fsJT8aMjtvH050iYKRVct+x6Q7VRJgCI4MgvAexnTKdxW54YzvXCuO5bDiy5la7CgerWkTAq9dzXwIDAQAB
  -----END PUBLIC KEY-----`;
  // 固定的域名和ID(将来可从 Vuex 中获取)
  const domain = 'https://img.onbus.cn';
  const dbid = 82; // 这里可以从 Vuex 获取
  const formid = 0; // 这里也可以从 Vuex 获取
  const uuid = ref(undefined); // 这里也可以从 Vuex 获取
  // 使用 RSA 加密数据
  const encryptData = (data: Record<string, any>) => {
    const encryptor = new JSEncrypt();
    encryptor.setPublicKey(publicKey);
    const encryptedData: Record<string, string> = {};
    for (const key in data) {
      if (data.hasOwnProperty(key)) {
        const encryptedValue = encryptor.encrypt(data[key]);
        if (encryptedValue) {
          encryptedData[key] = encodeURIComponent(encryptedValue); // URL 编码
        }
      }
    }
    return encryptedData;
  };
  // 获取上传数据并加密
  const getUploadData = (uuid) => {
    const data = {
      dbid: dbid.toString(),
      username: 'huang',
      usercode: 'z9000137',
    };
    const newData = encryptData(data);
    return { ...newData, type: 3, fieldid: '333', uuid: uuid };
  };
  // 生成文件 URL
  const generateFileUrl = (response: any) => {
    const unid = response.uuid.split(';')[0] + '@P@' + response.uuid.split(';')[1];
    const fileExt = response.fileType;
    return `${domain}/uploads/attachment/${dbid}/${formid}/${unid}.${fileExt}`;
  };
  return { computedUploadUrl, getUploadData, generateFileUrl };
};
src/components/MyUpload/index.vue
New file
@@ -0,0 +1,205 @@
<template>
  <div>
    <Upload
      v-if="type == 'png'"
      name="file"
      multiple
      @change="handleUploadChange"
      :action="computedUploadUrl"
      :showUploadList="false"
      :accept="accept"
      :beforeUpload="beforeUpload"
    >
      <a-button type="text" size="small" v-bind="{ ...getButtonProps }">
        <PictureOutlined /> {{ title }}
      </a-button>
    </Upload>
    <a-upload
      v-if="type == 'file'"
      v-model:file-list="fileListTemp"
      :action="computedUploadUrl"
      list-type="picture"
      class="upload-list-inline"
      :before-upload="beforeUpload"
      @change="handleFileUploadChange"
    >
      <a-button type="text" size="small" style="margin: 0 auto">
        <UploadOutlined />
        附件
      </a-button>
      <template #iconRender>
        <PaperClipOutlined />
      </template>
      <template #itemRender="{ file, fileList, actions }">
        <a-space class="ant-upload-list-picture-card">
          <span style="display: flex; flex-wrap: wrap">
            <PaperClipOutlined style="margin-right: 4px" />
            <span v-if="!file.editor" :style="file.status === 'error' ? 'color: red' : ''">
              {{ file.name }}
            </span>
            <span v-else>
              <a-input size="small" v-model:value="file.tempName"></a-input>
            </span>
          </span>
          <span v-if="!file.editor">
            <a href="javascript:;" @click="fnPreview(file)">预览</a>
            <a href="javascript:;" @click="fnRename(file)">重命名</a>
            <a href="javascript:;" @click="actions.remove">删除</a>
          </span>
          <span v-else>
            <a href="javascript:;" @click="fnSaveRename(file)">保存</a>
            <a href="javascript:;" @click="fnOffRename(file)">取消</a>
          </span>
        </a-space>
      </template>
    </a-upload>
    <div id="previewContainer"></div>
  </div>
</template>
<script lang="ts" setup>
  import { computed, ref, watch } from 'vue';
  import { PictureOutlined, UploadOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
  import { Upload } from 'ant-design-vue';
  import { useGlobSetting } from '@/hooks/setting';
  import { useUploadHook } from './hooks/useUploadHook';
  defineOptions({ name: 'TinymceImageUpload' });
  const props = defineProps({
    fullscreen: {
      type: Boolean,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    accept: {
      type: String,
      default: '.jpg,.jpeg,.gif,.png,.webp',
    },
    title: {
      type: String,
      default: '图片',
    },
    type: {
      type: String,
      default: 'png',
    },
  });
  const emit = defineEmits(['uploading', 'done', 'error', 'fileListChange']);
  let uploading = false;
  const { uploadUrl: baseUrl } = useGlobSetting();
  const { computedUploadUrl, getUploadData, generateFileUrl } = useUploadHook(baseUrl);
  const fileListTemp = ref([]); // 用于附件的文件列表
  const getButtonProps = computed(() => ({
    disabled: props.disabled,
  }));
  const uuid = ref('');
  // beforeUpload 拦截器,用于动态修改 URL
  const beforeUpload = () => {
    const encryptedParams = getUploadData(uuid.value);
    const queryString = new URLSearchParams(encryptedParams).toString();
    computedUploadUrl.value = `${baseUrl}?${queryString}`;
  };
  // 处理上传变化
  const handleUploadChange = (info: Record<string, any>) => {
    const file = info.file;
    const status = file?.status;
    if (status === 'uploading') {
      if (!uploading) {
        emit('uploading', file.name);
        uploading = true;
      }
    } else if (status === 'done') {
      const newUrl = generateFileUrl(file.response); // 生成文件 URL
      debugger;
      if (!uuid.value) {
        uuid.value = file.response.uuid.split(';')[0];
      }
      emit('done', file.name, newUrl);
      uploading = false;
    } else if (status === 'error') {
      emit('error');
      uploading = false;
    }
  };
  function handleFileUploadChange(info: Record<string, any>) {
    const file = info.file;
    const status = file?.status;
    if (status === 'done') {
      if (!uuid.value) {
        uuid.value = file.response.uuid.split(';')[0];
      }
      uploading = false;
    }
  }
  // 重命名功能
  const fnRename = (file) => {
    file.editor = true;
  };
  // 保存重命名
  const fnSaveRename = (file) => {
    file.editor = false;
    file.name = file.tempName;
  };
  // 取消重命名
  const fnOffRename = (file) => {
    file.editor = false;
  };
  // 预览
  const fnPreview = (file) => {
    if (!file || !file.response) {
      console.error('Invalid file or response');
      return;
    }
    // 生成安全的文件 URL
    const safeUrl = generateFileUrl(file.response);
    // 获取文件类型(通过文件扩展名或 MIME 类型)
    const fileExt = file.response.fileType || file.name.split('.').pop().toLowerCase(); // 获取文件扩展名
    const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExt); // 判断是否为图片
    if (isImage) {
      // 直接打开图片
      window.open(safeUrl, '_blank');
    } else {
      // 非图片类型使用 Office Online Viewer 或其他工具预览
      const iframeSrc = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(safeUrl)}`;
      window.open(iframeSrc, '_blank');
    }
  };
  watch(
    () => fileListTemp.value,
    (newValue) => {
      emit('fileListChange', fileListTemp.value);
    },
  );
</script>
<style lang="less" scoped>
  @prefix-cls: ~'@{namespace}-tinymce-img-upload';
  .@{prefix-cls} {
    z-index: 20;
    top: 4px;
    right: 10px;
    &.fullscreen {
      position: fixed;
      z-index: 10000;
    }
  }
</style>
src/components/Tinymce/src/Editor.vue
@@ -8,42 +8,20 @@
    ></textarea>
    <slot v-else></slot>
    <div class="p-2 tox-statusbar">
      <a-upload
        v-if="isElse"
        v-model:file-list="fileListTemp"
        action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
        list-type="picture"
        class="upload-list-inline"
      >
        <a-button type="text" size="small" style="margin: 0 auto">
          <upload-outlined></upload-outlined>
          附件
        </a-button>
        <template #iconRender><PaperClipOutlined /></template>
        <template #itemRender="{ file, fileList, actions }">
          <a-space class="ant-upload-list-picture-card">
            <span style="display: flex; flex-wrap: wrap">
              <PaperClipOutlined style="margin-right: 4px" />
              <span v-if="!file.editor" :style="file.status === 'error' ? 'color: red' : ''">{{
                file.name
              }}</span>
              <span v-else>
                <a-input size="small" v-model:value="file.tempName"></a-input>
              </span>
            </span>
            <span v-if="!file.editor">
              <a href="javascript:;" @click="actions.preview">预览</a>
              <a href="javascript:;" @click="fnRename(file, fileList)">重命名</a>
              <a href="javascript:;" @click="actions.remove">删除</a>
            </span>
            <span v-else>
              <a href="javascript:;" @click="fnSaveRename(file, fileList)">保存</a>
              <a href="javascript:;" @click="fnOffRename(file, fileList)">取消</a>
            </span>
          </a-space>
        </template>
      </a-upload>
      <div :class="fileListTemp.length > 0 ? 'my-upload-list' : ''">
      <ImgUpload
        ref="fileRef"
        :fullscreen="fullscreen"
        :fileListTemp="fileListTemp"
        @uploading="handleImageUploading"
        @done="handleDone"
        @fileListChange="fileListChange"
        v-if="isImg"
        v-show="editorRef"
        :title="'附件'"
        :type="'file'"
        :disabled="disabled"
      />
      <div class="my-upload-list">
        <div style="display: flex">
          <ImgUpload
            :fullscreen="fullscreen"
@@ -52,6 +30,7 @@
            v-if="isImg"
            v-show="editorRef"
            :title="'图片'"
            :type="'png'"
            :disabled="disabled"
            :accept="'.jpg,.jpeg,.gif,.png,.webp'"
          />
@@ -68,6 +47,8 @@
<script lang="ts" setup>
  import type { Editor, RawEditorSettings } from 'tinymce';
  import { PaperClipOutlined, UploadOutlined, SnippetsOutlined } from '@ant-design/icons-vue';
  import { useGlobSetting } from '@/hooks/setting';
  import tinymce from 'tinymce/tinymce';
  import 'tinymce/themes/silver';
  import 'tinymce/icons/default/icons';
@@ -144,7 +125,7 @@
    PropType,
    useAttrs,
  } from 'vue';
  import ImgUpload from './ImgUpload.vue';
  import ImgUpload from '@/components/MyUpload/index.vue';
  import {
    plugins as defaultPlugins,
    toolbar as defaultToolbar,
@@ -157,6 +138,8 @@
  import { isNumber } from '@/utils/is';
  import { useLocale } from '@/locales/useLocale';
  import { useAppStore } from '@/store/modules/app';
  const { uploadUrl } = useGlobSetting();
  // console.log(uploadUrl,'uploadUrl');
  defineOptions({ name: 'Tinymce', inheritAttrs: false });
@@ -370,6 +353,7 @@
      editor.setContent(val);
    }
  }
  const fileRef = ref();
  function bindModelHandlers(editor: any) {
    const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;
@@ -432,54 +416,9 @@
  }
  // 附件
  const fileListTemp = ref<UploadProps['fileList']>([
    // {
    //   uid: '-1',
    //   name: 'xxx.png',
    //   tempName: 'xxx',
    //   status: 'done',
    //   url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    //   thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    //   editor: false,
    // },
    // {
    //   uid: '-2',
    //   name: 'yyy.png',
    //   tempName: 'yyy',
    //   status: 'done',
    //   url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    //   thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    //   editor: false,
    // },
  ]);
  function fnRename(file, fileList) {
    console.log(file, fileList);
    fileListTemp.value = fileList.map((item) => {
      // item.tempName = item.name.split('.').slice(0,-1)
      if (file.uid == item.uid) {
        item.editor = true;
      } else {
        item.editor = false;
      }
      return item;
    });
  }
  function fnSaveRename(file, fileList) {
    fileListTemp.value = fileList.map((item) => {
      if (file.uid == item.uid) {
        item.name = item.tempName;
        item.editor = false;
      }
      return item;
    });
  }
  function fnOffRename(file, fileList) {
    fileListTemp.value = fileList.map((item) => {
      if (file.uid == item.uid) {
        item.editor = false;
      }
      return item;
    });
  const fileListTemp = ref([]);
  function fileListChange(data) {
    fileListTemp.value = data;
  }
</script>
<style lang="less" scope>
src/components/Tinymce/src/ImgUpload.vue
File was deleted
src/router/routes/modules/email.ts
@@ -108,7 +108,8 @@
          meta: {
            title: '全部发件',
          },
        }]
        },
      ],
    },
    // {
    //   path: 'MassMailbox',
@@ -120,6 +121,26 @@
    //     currentActiveMenu: '/email/index',
    //   },
    // },
    {
      path: 'folder',
      name: 'Folder',
      component: () => import('@/views/email/folder/index.vue'),
      meta: {
        title: '文件夹',
        hideTab: true,
        currentActiveMenu: '/email/index',
      },
    },
    {
      path: 'label',
      name: 'Label',
      component: () => import('@/views/email/label/index.vue'),
      meta: {
        title: '标签',
        hideTab: true,
        currentActiveMenu: '/email/index',
      },
    },
  ],
};
src/settings/projectSetting.ts
@@ -235,7 +235,7 @@
  // Use error-handler-plugin
  // 是否使用全局错误捕获
  useErrorHandle: true,
  useErrorHandle: false,
  // Whether to open back to top
  // 是否开启回到顶部
src/views/email/Edit/index.vue
@@ -393,9 +393,47 @@
  };
  function fnChangeContent(e) {
    modelRef.content = e.content;
    modelRef.attachmentList = e.attachmentList;
  if (!isValidEvent(e)) {
    console.error('Invalid event:', e);
    return;
  }
  console.log('Event:', e, '----------------');
  modelRef.content = e.content;
  modelRef.attachmentList = mergeArrayWithPrefix(fnBuildAttachmentList(e.fileUNID));
  console.log('Updated modelRef:', modelRef);
}
function isValidEvent(e) {
  return e && typeof e === 'object' && typeof e.content === 'string';
}
function fnBuildAttachmentList(data) {
  if (!Array.isArray(data)) {
    console.error('Invalid argument: data must be an array');
    return [];
  }
  return data.reduce((acc, item) => {
    const uuid = item?.response?.uuid;
    if (uuid) {
      acc.push(uuid);
    } else {
      console.warn('Invalid item:', item);
    }
    return acc;
  }, []);
}
function mergeArrayWithPrefix(arr) {
  if (!arr || arr.length === 0) return '';
  const [prefix] = arr[0].split(';');
  const suffixes = arr.map(item => item.split(';')[1]).filter(Boolean).join(';');
  return `${prefix};${suffixes}`;
}
  function fnBuildingCommitData() {
    return {
@@ -405,7 +443,7 @@
      bcc: modelRef.bccRecipients,
      subject: modelRef.subject,
      content: modelRef.content,
      attachmentList: '',
      attachmentList: modelRef.attachmentList,
      docCode: docCode.value,
    };
  }
@@ -441,7 +479,7 @@
        loading.value = false;
        if (res.code === 0) {
          createMessage.success(res.msg);
          router.push('/email/list');
          router.push('/email/index');
        }
      })
      .catch((error) => {
src/views/email/Utils/folder.vue
@@ -114,31 +114,32 @@
  function fnGetList() {
    getFolderApi({}).then((res) => {
      console.log(res);
      demo.tableData = convertToTableData(res.data);
      console.log(demo.tableData, '3333333333333');
    });
  }
  function convertToTableData(data, parentId = null) {
    let tableData = [];
    try {
      let tableData = [];
    data.forEach((item) => {
      let tableItem = {
        folderId: item.folderId,
        parentRowId: parentId,
        rowId: item.rowId,
        folderName: item.folderName,
        treeControl: item.treeControl,
      };
      data.forEach((item) => {
        let tableItem = {
          folderId: item.folderId,
          parentRowId: parentId,
          rowId: item.rowId,
          folderName: item.folderName,
          treeControl: item.treeControl,
        };
      if (item.list && item.list.length > 0) {
        let children = convertToTableData(item.list, item.rowId);
        tableData = tableData.concat(children);
      }
        if (item.list && item.list.length > 0) {
          let children = convertToTableData(item.list, item.rowId);
          tableData = tableData.concat(children);
        }
      tableData.push(tableItem);
    });
    return tableData;
        tableData.push(tableItem);
      });
      return tableData;
    } catch (error) { return [];}
  }
  const inputRefs = ref<{ [key: number]: HTMLElement | null }>({});
@@ -146,7 +147,7 @@
    const $table = xTable.value;
    const rid = Date.now();
    const record = {
      folderName: `新数据${rid}`,
      folderName: `新文件夹`,
      id: rid,
    };
    $table.insert(record).then(({ row }) => $table.setEditRow(row));
@@ -158,9 +159,9 @@
  const { createMessage } = useMessage();
  function fnInputHandle(row) {
    console.log(row, '----333');
    if (row.folderName == '') {
      editRowEvent(row)
      editRowEvent(row);
      fnGetList();
      return createMessage.error('请输入文件夹名称');
    }
    const data =
@@ -184,6 +185,7 @@
        fnGetList();
      } else {
        createMessage.error(res.msg);
        fnGetList();
      }
    });
  }
@@ -191,15 +193,16 @@
    const $table = xTable.value;
    const rid = Date.now();
    const record = {
      folderName: `新数据${rid}`,
      folderName: `新子文件夹`,
      id: rid,
      parentRowId: row.rowId, // 需要指定父节点,自动插入该节点中
    };
    console.log(record, '99999993');
    const { row: newRow } = await $table.insert(record);
    await $table.setTreeExpand(row, true); // 将父节点展开
    await $table.setEditRow(newRow); // 插入子节点
    setTimeout(() => {
      inputRefs.value[rid].focus();
    }, 300);
  }
  function fnDelete(row) {
    deleteFolderApi({ folderId: row.folderId })
@@ -216,7 +219,6 @@
  function editRowEvent(row) {
    const $table = xTable.value;
    console.log(row, '---30494');
    row.opType = 'edit';
    $table.setEditRow(row);
  }
src/views/email/Utils/mailboxManagement.vue
@@ -20,6 +20,7 @@
      :filter-config="{ showIcon: false }"
      :row-config="{ isHover: true }"
      :column-config="{ resizable: true }"
      :edit-config="{ trigger: 'click', mode: 'cell' }"
    >
      <vxe-column width="60">
        <template #default>
@@ -34,15 +35,29 @@
          <span style="margin-left: 10px; color: #3081fe; font-weight: 500">{{ row.email }}</span>
        </template>
      </vxe-column>
      <vxe-column show-overflow field="companyName" title="显示名称" min-width="250">
      <vxe-column
        show-overflow
        field="aliasEmail"
        title="显示名称"
        min-width="250"
        :edit-render="{}"
      >
        <template #default="{ row }">
          <span style="color: #999">{{ row.companyName }}</span>
          <span style="color: #999">{{ row.aliasEmail }}</span>
        </template>
        <template #edit="{ row }">
          <vxe-input
            ref="inputRef"
            v-model="row.aliasEmail"
            type="text"
            @blur="fnInputHandle(row)"
          ></vxe-input>
        </template>
      </vxe-column>
      <vxe-column show-overflow field="status" title="邮箱状态" min-width="250">
        <template #default="{ row }">
          <div v-if="!isCheckAll">
            <a-tag color="orange" v-if="row.status === '正常'">正常</a-tag>
            <a-tag color="success" v-if="row.mailStatus === '正常'">正常</a-tag>
            <a-tag color="red" v-else>异常</a-tag>
          </div>
          <div v-else>
@@ -98,7 +113,8 @@
            </template>
            <a-input :disabled="typeAccount === 2" v-model:value="formData.email" />
          </a-form-item>
          <a-form-item v-if="isCustom == 'custom'" name="password" label="邮箱密码">
          <!-- v-if="isCustom == 'custom'"   -->
          <a-form-item name="password" label="邮箱密码">
            <a-input-password
              type="password"
              v-model:value="formData.password"
@@ -397,6 +413,7 @@
    deleteAccountApi,
    getAccountListApi,
    isEmailValidApi,
    addLiasEmailApi,
  } from '@/api/email/userList';
  const loading = ref(false);
  import Sortable from 'sortablejs';
@@ -454,11 +471,11 @@
    aliasEmail: '',
    biSyncFlag: false,
    proxyFlag: true,
    receiveProtocol: 'imap',
    receiveProtocol: 'imaps',
    receiveSSL: false,
    receivePort: '',
    receiveHost: '',
    smtpSSL: false,
    smtpSSL: true,
    smtpPort: '',
    smtpHost: '',
    invalid: '',
@@ -510,7 +527,12 @@
  const open = ref(false);
  const fnHandleOk = () => {
    formRef.value.validate().then(() => {
      const data = formData.value;
      const data = !isShow
        ? formData.value
        : {
            email: formData.value.email,
            password: formData.value.password,
          };
      if (isShow.value == true) {
        data.mailType = isCustom.value === 'onCustom' ? 1 : 2;
      }
@@ -523,11 +545,12 @@
          }
          loading.value = false;
          fnMailList();
          open.value = false;
        })
        .catch((err) => {
          loading.value = false;
          open.value = false;
        });
      open.value = false;
    });
  };
@@ -586,7 +609,7 @@
  }
  function fnHandleDetailOk() {
    openDrawerDetail.value = false;
    deleteAccountApi({ accountId: accountId.value })
    deleteAccountApi(accountId.value )
      .then((res) => {
        if (res.code === 0) {
          createMessage.success(res.msg);
@@ -626,7 +649,7 @@
    isEmailValidApi(email).then((res) => {
      if (res.code == 0) {
        isCheck.value = true;
        checkStatus.value = res.data.status;
        checkStatus.value = res.data.code == 0 ? true : false;
      }
    });
  }
@@ -664,6 +687,10 @@
      isCheckAll.value = false;
    }, 3000);
  }
  function fnInputHandle(row) {
    addLiasEmailApi({ aliasEmail: row.aliasEmail, accountId: row.accountId }).then((res) => {});
  }
</script>
<style scoped lang="less">
  .bullet {
src/views/email/components/ContextMenu/CustomTimePicker.vue
New file
@@ -0,0 +1,49 @@
<template>
  <a-form :model="form" ref="formRef">
    <a-form-item
      label="日期"
      name="date"
      :rules="[{ required: true, message: '请选择日期' }]"
    >
      <a-date-picker
        format="YYYY/MM/DD"
        :disabledDate="disabledDate"
        v-model:value="form.date"
      />
    </a-form-item>
    <a-form-item
      label="时间"
      name="time"
      :rules="[{ required: true, message: '请选择时间' }]"
    >
      <a-time-picker v-model:value="form.time" />
    </a-form-item>
    <a-form-item>
      <a-button @click="$emit('cancel')">取消</a-button>
      <a-button
        style="margin-left: 10px"
        type="primary"
        @click="$emit('submit')"
      >确定</a-button>
    </a-form-item>
  </a-form>
</template>
<script setup>
import { defineProps, defineEmits, reactive } from 'vue';
const props = defineProps();
const emit = defineEmits();
const form = reactive({
  date: '',
  time: '',
});
const disabledDate = (currentDate) => {
  return currentDate && currentDate < dayjs().startOf('day');
};
</script>
<style scoped>
/* 添加自定义样式 */
</style>
src/views/email/components/ContextMenu/index.vue
New file
@@ -0,0 +1,327 @@
<template>
  <div :style="style" class="custom-dropdown">
    <!-- <template #overlay> -->
    <a-menu>
      <a-menu-item key="reply" @click="handleMenuAction('reply')">回复</a-menu-item>
      <a-menu-item key="replyAll" @click="handleMenuAction('replyAll')">回复全部</a-menu-item>
      <a-menu-item key="replyWithAttachment" @click="handleMenuAction('replyWithAttachment')">
        带附件回复
      </a-menu-item>
      <a-menu-item key="replyAllWithAttachment" @click="handleMenuAction('replyAllWithAttachment')">
        带附件回复全部
      </a-menu-item>
      <a-menu-item key="forward" @click="handleMenuAction('forward')">转发</a-menu-item>
      <a-menu-item key="forwardAsAttachment" @click="handleMenuAction('forwardAsAttachment')">
        作为附件转发
      </a-menu-item>
      <a-menu-item key="reEdit" @click="handleMenuAction('reEdit')">分发</a-menu-item>
      <a-menu-item key="addNote" @click="handleMenuAction('addNote')">设置备注</a-menu-item>
      <a-divider style="margin: 5px" />
      <!-- <a-menu-item key="toProcess" @click="handleMenuAction('toProcess')">
         <div class="my-display">
          <a-dropdown>
            <span>待处理 <FieldTimeOutlined /></span>
            <template #overlay>
              <a-card title="选择稍后处理时间:" style="width: 250px" size="small">
                <div
                  class="date p-1"
                  v-for="item in dateList"
                  :key="item.key"
                  @click="fnSelectDate(item)"
                >
                  <div class="date-left">{{ item.name }}</div>
                  <div class="date-right">
                    <span v-if="item.key !== 'today'">{{ item.dayOfWeek }}</span>
                    <span style="margin-left: 5px">{{ item.time }}</span>
                  </div>
                </div>
                <a-divider style="margin: 5px 0" />
                <div class="date p-1">
                  <a-popover
                    :trigger="trigger"
                    title="自定义时间"
                    v-model:open="customTimeDropdownOpen"
                    @confirm="onSubmitCustomTime"
                  >
                    <template #content>
                      <CustomTimePicker
                        :form="form"
                        @cancel="cancelCustomTime"
                        @submit="submitCustomTime"
                      />
                    </template>
                    <div class="date-left" @click="toggleCustomTime">自定义时间</div>
                  </a-popover>
                </div>
              </a-card>
            </template>
          </a-dropdown>
        </div>
      </a-menu-item> -->
      <a-menu-item key="markAs" @click="handleMenuAction('markAs')">{{
        `标记${row.readFlag ? '未' : '已'}读`
      }}</a-menu-item>
      <a-menu-item key="markAs" @click="handleMenuAction('markAs')">标记为</a-menu-item>
      <a-divider style="margin: 5px" />
      <a-menu-item key="createRule" @click="handleMenuAction('createRule')"
        >新建收发件规则</a-menu-item
      >
      <a-menu-item key="moveTo" @click="handleMenuAction('moveTo')">移动到</a-menu-item>
      <a-divider style="margin: 5px" />
      <a-menu-item key="exportEmail" @click="handleMenuAction('exportEmail')">导出邮件</a-menu-item>
      <a-menu-item key="trackCustomer" @click="handleMenuAction('trackCustomer')"
        >建为客户跟进</a-menu-item
      >
      <a-menu-item key="createSchedule" @click="handleMenuAction('createSchedule')"
        >新建日程</a-menu-item
      >
      <a-divider style="margin: 5px" />
      <a-menu-item key="delete" @click="handleMenuAction('delete')">标为垃圾邮件</a-menu-item>
      <a-menu-item key="delete" @click="handleMenuAction('delete')">删除</a-menu-item>
    </a-menu>
    <!-- </template> -->
  </div>
</template>
<script setup>
  import { defineProps, defineEmits, computed, ref, reactive, inject } from 'vue';
  import CustomTimePicker from './CustomTimePicker.vue';
  const props = defineProps({
    style: Object,
    selectedCell: Object,
  });
  const row = computed(() => props.selectedCell.row);
  const emit = defineEmits(['close-menu']);
  import { useRouter } from 'vue-router';
  const router = useRouter();
  import { updateReadApi, updateHandleAPi, deleteEmailAPi } from '@/api/email/userList';
  const getDataList = inject('getDataList');
  const handleMenuAction = (action) => {
    const res = props.selectedCell.row;
    // 根据不同的action执行对应的操作
    switch (action) {
      case 'reply':
        router.push({ path: '/email/edit', query: { docCode: res.docCode, type: 'reply' } });
        break;
      case 'replyAll':
        router.push({ path: '/email/edit', query: { docCode: res.docCode, type: 'reply' } });
        break;
      case 'replyWithAttachment':
        router.push({ path: '/email/edit', query: { docCode: res.docCode, type: 'reply' } });
        break;
      case 'replyAllWithAttachment':
        router.push({ path: '/email/edit', query: { docCode: res.docCode, type: 'reply' } });
        break;
      case 'forward':
        console.log('Forward action triggered');
        break;
      case 'addNote':
        console.log('Add Note action triggered');
        break;
      case 'forwardAsAttachment':
        console.log('Forward as Attachment action triggered');
        break;
      case 'reEdit':
        console.log('Re-edit action triggered');
        break;
      case 'toProcess':
        console.log('Add to Process action triggered');
        break;
      case 'markAs':
        const data = {
          status: !res.readFlag,
          list: [res.docCode],
        };
        updateReadApi(data).then((res) => {
          if (res.code == 0) {
            getDataList({});
          }
        });
        console.log('Mark as action triggered');
        break;
      case 'createRule':
        console.log('Create rule action triggered');
        break;
      case 'moveTo':
        console.log('Move to action triggered');
        break;
      case 'exportEmail':
        console.log('Export Email action triggered');
        break;
      case 'trackCustomer':
        console.log('Track Customer action triggered');
        break;
      case 'createSchedule':
        console.log('Create Schedule action triggered');
        break;
      case 'delete':
        deleteEmailAPi([res.docCode]).then((res) => {
          if (res.code == 0) {
            getDataList({});
          }
        });
        break;
      default:
        console.warn('Unknown action:', action);
    }
    // 操作完成后关闭菜单
    emit('close-menu');
  };
  // 待处理
  import { FieldTimeOutlined, PushpinOutlined } from '@ant-design/icons-vue';
  function processDateList() {
    const dateList = [
      { name: '今天稍晚', key: 'today' },
      { name: '明天', key: 'tomorrow' },
      { name: '本周稍晚', key: 'thisWeek' },
      { name: '本周末', key: 'thisWeekend' },
      { name: '下周', key: 'nextWeek' },
    ];
    const now = new Date();
    const dateArray = dateList.map((item) => {
      let date = new Date(); // 初始化当前日期
      let dayOfWeekString = ''; // 初始化星期几
      let timeString = ''; // 初始化时间
      switch (item.key) {
        case 'today':
          // 今天稍晚是今天的 16:00
          date.setHours(16, 0, 0, 0);
          break;
        case 'tomorrow':
          // 明天 08:00
          date.setDate(now.getDate() + 1);
          date.setHours(8, 0, 0, 0);
          break;
        case 'thisWeek':
          // 本周稍晚:周五 08:00
          const dayOfWeek = now.getDay();
          const daysUntilFriday = (5 - dayOfWeek + 7) % 7; // 计算到周五的天数
          date.setDate(now.getDate() + daysUntilFriday);
          date.setHours(8, 0, 0, 0);
          break;
        case 'thisWeekend':
          // 本周末:周日 08:00
          const daysUntilSunday = (7 - now.getDay()) % 7; // 计算到周日的天数
          date.setDate(now.getDate() + daysUntilSunday);
          date.setHours(8, 0, 0, 0);
          break;
        case 'nextWeek':
          // 下周一 08:00
          const daysUntilNextMonday = ((1 - now.getDay() + 7) % 7) + 7; // 计算到下周一的天数
          date.setDate(now.getDate() + daysUntilNextMonday);
          date.setHours(8, 0, 0, 0);
          break;
        default:
          break;
      }
      // 获取星期几的字符串表示
      dayOfWeekString = date.toLocaleDateString('zh-CN', { weekday: 'long' });
      // 获取仅时间部分
      timeString = date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
      // 返回日期、星期几和时间信息
      return {
        name: item.name,
        key: item.key,
        date: date,
        dayOfWeek: dayOfWeekString,
        time: timeString, // 保存仅时间部分
      };
    });
    return dateArray;
  }
  const dateList = processDateList();
  const customTimeDropdownOpen = ref(false);
  function onSubmitCustomTime() {
    customTimeDropdownOpen.value = true;
  }
  const disabledDate = (currentDate) => {
    return currentDate && currentDate < dayjs().startOf('day');
  };
  const cancelCustomTime = () => {
    customTimeDropdownOpen.value = false;
    dropdownOpen.value = true;
  };
  const submitCustomTime = () => {
    formRef.value.validate().then((valid) => {
      if (valid) {
        customTimeDropdownOpen.value = false;
        const date = form.date ? dayjs(form.date).format('YYYY-MM-DD') : '';
        const time = form.time ? dayjs(form.time).format('HH:mm') : '';
        const data = {
          handleTime: date + ' ' + time,
          docCode: props.docCodeS,
        };
        pushUpdateHandle(data);
      } else {
        return false;
      }
    });
  };
  const toggleCustomTime = () => {
    customTimeDropdownOpen.value = !customTimeDropdownOpen.value;
    dropdownOpen.value = false;
  };
  // 表单数据
  const form = reactive({
    date: '',
    time: '',
  });
  import { useMessage } from '@/hooks/web/useMessage';
  import { formatToDateDay } from '@/utils/dateUtil';
  function fnSelectDate(item) {
    dropdownOpen.value = false;
    const date = formatToDateDay(new Date(item.date));
    const data = {
      handleTime: date,
      docCode: props.docCodeS,
    };
    pushUpdateHandle(data);
  }
  function pushUpdateHandle(data) {
    updateHandleAPi(data)
      .then((res) => {
        if (res.code == 0) {
          createMessage.success(res.msg);
          // getDataList({});
        }
      })
      .catch((err) => {});
  }
</script>
<style scoped>
  .custom-dropdown {
    position: absolute;
    z-index: 9900;
    width: 250px;
    padding: 10px;
    border: 1px solid #0505050f;
    background-color: #fff;
  }
  .custom-dropdown :deep(.ant-menu-vertical) {
    border-inline-end: 0;
  }
  .custom-dropdown :deep(.ant-menu-item) {
    height: 32px;
    line-height: 32px;
  }
</style>
src/views/email/components/LeftMenu/EmailMenuItem.vue
New file
@@ -0,0 +1,44 @@
<template>
  <div>
    <a-sub-menu v-if="item.children.length > 0" :key="item.key">
      <template #title>
        <div class="my-display">
          <span>{{ item.title }}</span>
        </div>
      </template>
      <a-menu-item
        v-for="child in item.children"
        :key="child.key"
        @click="$emit('click', child)"
        style="display: flex; justify-content: space-between; padding-left: 28px"
      >
        <div class="my-display">
          <span>{{ child.title }}</span>
          <span v-if="child.total > 0">{{ child.total }}</span>
        </div>
      </a-menu-item>
    </a-sub-menu>
    <a-menu-item v-else :key="item.key" @click.stop="$emit('click', item)">
      <div class="my-display">
        <span>{{ item.title }}</span>
        <span v-if="item.total > 0" class="my-left">{{ item.total }}</span>
      </div>
    </a-menu-item>
  </div>
</template>
<script setup>
  defineProps(['item']);
</script>
<style scoped>
  .my-display {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .my-left {
    margin-left: 5px;
  }
</style>
src/views/email/components/LeftMenu/MyMenu.vue
New file
@@ -0,0 +1,121 @@
<template>
  <div>
    <a-sub-menu :key="item.key" :open="isOpen">
      <template #title>
        <div>
          <div v-if="!isSearch" class="my-display">
            <span>{{ item.title }}</span>
            <div class="handle-icon">
              <SearchOutlined v-if="item.key == 'moduleBelowA'" @click.stop="handleSearch(item)" />
              <MoreOutlined />
            </div>
          </div>
          <a-select
            v-if="isSearch"
            ref="inputRef"
            v-model:value="value"
            style="width: 100%"
            show-search
            placeholder="请输入文件夹关键字搜索"
            :options="options"
            :field-names="{ label: 'title', value: 'key' }"
            @change="handleChange"
            @blur="blurSearch"
          ></a-select>
        </div>
      </template>
      <template v-for="child in item.children" :key="child.key">
        <template v-if="!child.children || child.children.length === 0">
          <a-menu-item :key="child.key" @click="$emit('click', child)">
            <span>{{ child.title }}</span>
            <span v-if="child.total > 0" class="my-left">{{ child.total }}</span>
          </a-menu-item>
        </template>
        <template v-else>
          <!-- 递归调用自身 -->
          <MyMenu :item="child" @click="$emit('click', child)" />
        </template>
      </template>
    </a-sub-menu>
  </div>
</template>
<script lang="ts" setup>
  import { defineProps, defineEmits, ref, computed } from 'vue';
  import { SearchOutlined, MoreOutlined } from '@ant-design/icons-vue';
  interface MenuItem {
    title: string;
    key: string;
    total: number;
    children?: MenuItem[];
  }
  const props = defineProps<{ item: MenuItem }>();
  const emit = defineEmits();
  const options = ref([]);
  const isSearch = ref(false);
  const inputRef = ref();
  const value = ref();
  // 计算属性,判断当前菜单项是否应展开
  const isOpen = computed(() => {
    return true; // 默认为展开状态
  });
  function handleSearch(item: MenuItem) {
    console.log('handleSearch', item);
    options.value = flattenMenuItems(item.children);
    console.log('options', options.value);
    isSearch.value = true;
    setTimeout(() => {
      inputRef.value.focus();
    }, 100);
    emit('handleSearch', item);
  }
  function blurSearch() {
    isSearch.value = false;
  }
  const handleChange = (value: string) => {
    console.log(`selected ${value}`, isSearch.value);
    isSearch.value = false;
    emit('updateSelectedKeys', value);
  };
  const flattenMenuItems = (items: MenuItem[]): MenuItem[] => {
    let result: MenuItem[] = [];
    const recursiveFlatten = (items: MenuItem[]) => {
      for (const item of items) {
        result.push({ ...item, children: undefined }); // 不保留 children
        if (item.children && item.children.length > 0) {
          recursiveFlatten(item.children);
        }
      }
    };
    recursiveFlatten(items);
    return result;
  };
</script>
<style scoped>
  .my-display {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .my-left {
    margin-left: 5px;
  }
  .handle-icon {
    display: none;
    font-size: 16px;
  }
  .my-display:hover .handle-icon {
    display: block;
  }
</style>
src/views/email/components/LeftMenu/index.vue
@@ -27,22 +27,34 @@
          v-model:open-keys="openKeys"
          v-model:selected-keys="selectedKeys"
          mode="inline"
          :popupClassName="popupClassName"
        >
          <template v-for="item in menuItems" :key="item.key">
            <render-menu-item :item="item" @click="handleClick" />
            <EmailMenuItem :item="item" @click="handleClick" />
          </template>
        </a-menu>
        <a-divider />
        <a-menu
          id="email-left-nav2"
          v-model:open-keys="openKeys"
          v-model:selected-keys="selectedKeys"
          v-model:open-keys="openKeys2"
          v-model:selected-keys="selectedKeys2"
          mode="inline"
          :popupClassName="popupClassName"
        >
          <template v-for="item in menuItems2" :key="item.key">
            <render-menu-item :item="item" @click="handleClick" />
            <template v-if="!item.children">
              <a-menu-item :key="item.key" @click="$emit('click', item)">
                <div class="my-display">
                  <span>{{ item.title }}</span>
                  <span v-if="item.total > 0" class="my-left">{{ item.total }}</span>
                </div>
              </a-menu-item>
            </template>
            <template v-else>
              <MyMenu
                :item="item"
                @click.stop="handleClickMyMenu(item)"
                @updateSelectedKeys="updateSelectedKeys"
              />
            </template>
          </template>
        </a-menu>
      </div>
@@ -51,112 +63,178 @@
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue';
import { PageWrapper } from '@/components/Page';
import { MailOutlined, UserOutlined } from '@ant-design/icons-vue';
import { getEmailModuleApi, getEmailModuleBelowApi } from '@/api/email/userList';
import { useRouter } from 'vue-router';
  import { ref, onMounted } from 'vue';
  import { PageWrapper } from '@/components/Page';
  import { DingdingOutlined, MailOutlined, UserOutlined } from '@ant-design/icons-vue';
  import { getEmailModuleApi, getEmailModuleBelowApi } from '@/api/email/userList';
  import { useRouter } from 'vue-router';
  import MyMenu from './MyMenu.vue';
  import EmailMenuItem from './EmailMenuItem.vue';
interface MenuItem {
  key: string;
  title: string;
  total?: number;
  children?: MenuItem[];
}
  const selectedKeys = ref<string[]>(['Index']);
  const openKeys = ref<string[]>(['Inbox']);
  const openKeys2 = ref(['0049LM']);
  const selectedKeys2 = ref([]);
const selectedKeys = ref<string[]>(['Index']);
const openKeys = ref<string[]>(['Inbox']);
const items = ref<MenuItem[]>([]);
const items2 = ref<MenuItem[]>([]);
  const menuItems = ref([]);
  const menuItems2 = ref([]);
const fetchEmailModules = async () => {
  try {
    const [res, res2] = await Promise.all([getEmailModuleApi(), getEmailModuleBelowApi()]);
    items.value = convertRoutesToMenuItems(res.data);
    items2.value = convertRoutesToMenuItems2(res2.data);
  } catch (error) {
    console.error('获取邮箱模块失败:', error);
  const fetchEmailModules = async () => {
    try {
      const [res, res2] = await Promise.all([getEmailModuleApi(), getEmailModuleBelowApi()]);
      menuItems.value = convertRoutesToMenuItems(res.data);
      menuItems2.value = convertRoutesToMenuItems2(res2.data);
      console.log('menuItems:', menuItems2.value);
    } catch (error) {
      console.error('获取邮箱模块失败:', error);
    }
  };
  const convertRoutesToMenuItems = (routes: any[]) => {
    return routes
      .map((route) => ({
        key: route.key,
        title: route.mailName,
        total: route.total,
        children: route.children ? convertRoutesToMenuItems(route.children) : undefined,
      }))
      .filter(Boolean);
  };
  const convertRoutesToMenuItems2 = (routes: any[], path) => {
    return routes
      .map((route) => ({
        key: route.key,
        title: route.name,
        total: route.number,
        path: path ? path : route.key,
        children: route.list
          ? convertRoutesToMenuItems2(route.list, path ? path : route.key)
          : undefined,
      }))
      .filter(Boolean);
  };
  onMounted(fetchEmailModules);
  const routesConfig = {
    InboxPage1: '/email/index',
    receiver: '/email/Inbox/list',
    sender: '/email/outbox/list',
    IndexPage1: '/email/outbox',
    moduleBelowA: '/email/index',
    moduleBelowB: '/email/index',
    moduleBelowC: '/email/index',
  };
  const router = useRouter();
  // const handleClick = (item: any) => {
  //   selectedKeys2.value = [];
  //   debugger
  //   const routePath =
  //     routesConfig[item.key] || router.getRoutes().find((r) => r.name === item.key)?.path;
  //   if (routePath) {
  //     router.push(routePath);
  //   } else {
  //     console.warn(`Unknown key: ${item.key}`);
  //   }
  // };
  const handleClick = (e: any) => {
    selectedKeys2.value = [];
    let matched = false;
    const route = router.getRoutes() || [];
    route.forEach((item) => {
      if (item.name === e.key) {
        router.push(item.path);
        matched = true;
        return; // 跳出当前循环
      }
      if (!matched) {
        switch (e.key) {
          case 'InboxPage1':
            router.push(routesConfig[e.key]);
            matched = true;
            return; // 跳出当前循环
          case 'receiver':
            router.push(`${routesConfig[e.key]}?${e.title}`);
            matched = true;
            return; // 跳出当前循环
          case 'sender':
            router.push(`${routesConfig[e.key]}?${e.title}`);
            matched = true;
            return; // 跳出当前循环
          case 'IndexPage1':
            router.push(`${routesConfig[e.key]}`);
            matched = true;
            return; // 跳出当前循环
          default:
            // 处理默认情况,例如记录日志或抛出警告
            console.warn(`Unknown key: ${e.key}`);
        }
      }
    });
  };
  const updateSelectedKeys = (keys) => {
    openKeys2.value = findParentKeys(menuItems2.value[0], keys);
    selectedKeys2.value = [keys];
  };
  function findParentKeys(data, targetKey, path = []) {
    path.push(data.key);
    if (data.key === targetKey) {
      return path;
    }
    for (const item of data.children || []) {
      const result = findParentKeys(item, targetKey, path.slice());
      if (result) {
        return result;
      }
    }
    return null;
  }
};
const convertRoutesToMenuItems = (routes: any[]): MenuItem[] => {
  return routes
    .map(route => ({
      key: route.key,
      title: route.mailName,
      total: route.total,
      children: route.children ? convertRoutesToMenuItems(route.children) : undefined,
    }))
    .filter(Boolean) as MenuItem[];
};
const convertRoutesToMenuItems2 = (routes: any[]): MenuItem[] => {
  return routes
    .map(route => ({
      key: route.key,
      title: route.name,
      total: route.number,
      children: route.list ? convertRoutesToMenuItems2(route.list) : undefined,
    }))
    .filter(Boolean) as MenuItem[];
};
onMounted(fetchEmailModules);
const routesConfig = {
  InboxPage1: '/email/index',
  receiver: '/email/Inbox/list',
  sender: '/email/outbox/list',
  IndexPage1: '/email/outbox',
};
const router = useRouter();
const handleClick = (item: MenuItem) => {
  const routePath = routesConfig[item.key] || router.getRoutes().find(r => r.name === item.key)?.path;
  if (routePath) {
    router.push(routePath);
  } else {
    console.warn(`Unknown key: ${item.key}`);
  }
};
const popupClassName = {
  display: 'flex',
  'align-items': 'center',
  'justify-content': 'space-between',
};
  const handleClickMyMenu = (item: any) => {
    selectedKeys.value = [];
    // console.log('handleClickMyMenu:', item);
    router.push({ path: '/email/folder' });
    console.log('updateSelectedKeys111111112333:', openKeys2.value);
  };
</script>
<style lang="less" scoped>
.header-container {
  height: 15vh;
  padding: 20px 40px;
  text-align: center;
}
  .header-container {
    height: 15vh;
    padding: 20px 40px;
    text-align: center;
  }
.button-group {
  display: flex;
  justify-content: space-around;
}
  .button-group {
    display: flex;
    justify-content: space-around;
  }
.write-button {
  width: 100%;
  margin-top: 10px;
  padding: 0 30px;
}
  .write-button {
    width: 100%;
    margin-top: 10px;
    padding: 0 30px;
  }
.menu-container {
  height: 70vh;
  overflow: auto;
}
  .menu-container {
    height: 75vh;
    overflow: auto;
  }
.my-display {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
  .my-display {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
.my-left {
  margin-left: 5px;
}
  .my-left {
    margin-left: 5px;
  }
</style>
src/views/email/components/LeftNav.vue
File was deleted
src/views/email/components/ListPage/TooltipAndDropdown .vue
@@ -43,7 +43,7 @@
          <a-divider style="margin: 5px 0" />
          <div class="date p-1">
            <a-popover
              trigger="click"
              :trigger="trigger"
              title="自定义时间"
              v-model:open="customTimeDropdownOpen"
              @confirm="onSubmitCustomTime"
@@ -167,6 +167,10 @@
    initialTooltipOpen: Boolean, // Tooltip 初始打开状态
    row: Object, // 当前行对象
    docCodeS: Array,
    trigger: {
      type: String,
      default: 'click',
    },
  });
  const emit = defineEmits(['updateHandleTime', 'completeAction', 'customTimeSubmit', 'tagRow']);
@@ -183,6 +187,7 @@
      handleTime: date,
      docCode: props.docCodeS,
    };
    pushUpdateHandle(data);
  }
  function pushUpdateHandle(data) {
@@ -226,27 +231,26 @@
  const onComplete = () => {
    const data = {
      handleTime: '',
      docCode: props.docCodeS,
    };
    pushUpdateHandle(data);
  };
  const formRef = ref();
  const submitCustomTime = () => {
      formRef.value.validate().then((valid) => {
        if (valid) {
          customTimeDropdownOpen.value = false;
          const date = form.date ? dayjs(form.date).format('YYYY-MM-DD') : '';
          const time = form.time ? dayjs(form.time).format('HH:mm') : '';
          const data = {
            handleTime: date + ' ' + time,
            docCode: props.docCodeS,
          };
          pushUpdateHandle(data);
        } else {
          return false;
        }
      });
    formRef.value.validate().then((valid) => {
      if (valid) {
        customTimeDropdownOpen.value = false;
        const date = form.date ? dayjs(form.date).format('YYYY-MM-DD') : '';
        const time = form.time ? dayjs(form.time).format('HH:mm') : '';
        const data = {
          handleTime: date + ' ' + time,
          docCode: props.docCodeS,
        };
        pushUpdateHandle(data);
      } else {
        return false;
      }
    });
  };
  const cancelCustomTime = () => {
src/views/email/components/ListPage/drawerDetail.vue
@@ -53,9 +53,19 @@
          <span style="margin-right: 10px; font-size: 16px">
            <PushpinOutlined />
          </span>
          <a-tag
            v-if="tableRowData.attachmentPath?.length > 0"
            style="margin-right: 10px; font-size: 12px"
          >
            <PaperClipOutlined style="margin-right: 4px; color: #ffac00" />{{
              tableRowData.attachmentPath?.length
            }}
          </a-tag>
        </div>
        <div class="right">
          <div class="tate">{{ formatToDateDay(tableRowData.receiveTime || tableRowData.updateTime) }}</div>
          <div class="tate">{{
            formatToDateDay(tableRowData.receiveTime || tableRowData.updateTime)
          }}</div>
          <div>
            <a-dropdown-button>
              <div v-if="!isDrafts">
@@ -142,6 +152,28 @@
              <span>暂未查询到该客户的当地时间</span>
              <!-- <span>2024-06-08 22:22</span> -->
            </div>
            <div v-if="tableRowData.attachmentPath?.length>0" class="p-2 f-z-14" style="margin-top: 10px">
              <div style="display: flex; align-items: center">
                <span style="margin-right: 10px">{{
                  `附件(${tableRowData.attachmentPath?.length})`
                }}</span>
                <a type="link" @click="fnAllDownload">全部下载</a>
              </div>
              <div class="my-d-f" style="width: 100%; margin-top: 10px">
                <div
                  class="file-item my-d-f"
                  v-for="item in tableRowData.attachmentPath"
                  :key="item"
                >
                  <div class="icon"><FileExcelOutlined /></div>
                  <div class="name" @click="fnPreview(item)">{{ item.name }}</div>
                  <div class="size"> {{ item.size + 'k' }} </div>
                  <div class="download" @click="fnDownload(item)"><CloudDownloadOutlined /></div>
                  <!-- <div class="delete">删除</div> -->
                </div>
              </div>
            </div>
            <div class="ct">
              <div v-if="tableRowData.content">
                <TinymcePw ref="TinymcePwRef" v-model="tableRowData.content" />
@@ -177,6 +209,9 @@
    CopyOutlined,
    UserOutlined,
    RollbackOutlined,
    FileExcelOutlined,
    CloudDownloadOutlined,
    PaperClipOutlined,
  } from '@ant-design/icons-vue';
  import pageHeadLeft from './pageHeadLeft.vue';
  import UserTips from '@/views/email/components/userTips/index.vue';
@@ -322,6 +357,40 @@
  function replyEmail(row, type) {
    router.push({ path: '/email/edit', query: { docCode: row.docCode, type: type } });
  }
  const fnPreview = (item) => {
    if (!item) {
      console.error('Invalid file or response');
      return;
    }
    // // 获取文件类型(通过文件扩展名或 MIME 类型)
    const fileExt = item.fileType; // 获取文件扩展名
    const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExt); // 判断是否为图片
    if (isImage) {
      // 直接打开图片
      window.open(item.url, '_blank');
    } else {
      const iframeSrc = `https://view.officeapps.live.com/op/view.aspx?src=${item.url}`;
      window.open(iframeSrc, '_blank');
    }
  };
  const fnDownload = (item) => {
    const link = document.createElement('a');
    link.href = item.url;
    link.download = item.name; // 提取文件名
    document.body.appendChild(link); // 将链接添加到 DOM
    link.click(); // 模拟点击下载
    document.body.removeChild(link); // 下载后移除链接
  };
  const fnAllDownload = () => {
    const urls = tableRowData.value.attachmentPath.map((item) => item.url);
    const url = urls.join(',');
    fnDownload(url);
  };
</script>
<style scoped lang="less">
@@ -445,4 +514,45 @@
  .f-z-14 {
    font-size: 12px;
  }
  .file-item {
    width: 240px;
    margin-right: 10px;
    padding: 10px;
    border-radius: 4px;
    background-color: #f0f0f0;
    & .icon {
      width: 20px;
      margin-right: 5px;
    }
    & .name {
      flex: 1;
      margin-right: 5px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    & .download {
      display: none;
      width: 20px;
    }
  }
  .file-item:hover .name {
    transition: 0.3s;
    color: #0960bd;
  }
  .file-item:hover .size {
    display: none;
    transition: 0.3s;
  }
  .file-item:hover .download {
    display: block;
    transition: 0.3s;
  }
</style>
src/views/email/components/ListPage/list.vue
@@ -23,10 +23,11 @@
        </div>
        <div class="right p-3"
          >共<span style="padding: 0 5px">{{page.total}}</span>封
          >共<span style="padding: 0 5px">{{ page.total }}</span
          >封
          <a-pagination
            v-model:current="pageCurrent"
             v-model:page-size='page.limit'
            v-model:page-size="page.limit"
            simple
            :total="page.total"
            style="margin-left: 10px"
@@ -61,27 +62,39 @@
          </a-switch>
        </div>
      </div>
      <div v-if="checked" style="height: 30px;" class="left-bt p-3">
      <div v-if="checked" style="height: 30px" class="left-bt p-3">
        已选择此页面上所有 20 封邮件 , 选择全部 335 封邮件
      </div>
      <div class="p-4" style="height: 90%; overflow: hidden">
        <a-tabs v-model:activeKey="activeKey">
          <a-tab-pane
            v-for="item in tabsList"
            :key="item.key"
            :tab="`${item.label}${item.num ? '(' + item.num + ')' : ''}`"
            style="height: 200px"
          >
            <Table
              ref="tableRef"
              :page="pageCurrent"
              :pageList="newList"
              :isDrafts="isDrafts"
              @selectAll="fnSelectAll"
              @updateSelectAll="updateSelectAll"
            />
          </a-tab-pane>
        </a-tabs>
      <div class="p-4" style="height: 90%; overflow: auto">
        <div v-if="isTabs">
          <a-tabs v-model:activeKey="activeKey">
            <a-tab-pane
              v-for="item in tabsList"
              :key="item.key"
              :tab="`${item.label}${item.num ? '(' + item.num + ')' : ''}`"
              style="height: 200px"
            >
              <Table
                ref="tableRef"
                :page="pageCurrent"
                :pageList="newList"
                :isDrafts="isDrafts"
                @selectAll="fnSelectAll"
                @updateSelectAll="updateSelectAll"
              />
            </a-tab-pane>
          </a-tabs>
        </div>
        <div v-else>
          <Table
            ref="tableRef"
            :page="pageCurrent"
            :pageList="newList"
            :isDrafts="isDrafts"
            @selectAll="fnSelectAll"
            @updateSelectAll="updateSelectAll"
          />
        </div>
      </div>
    </div>
  </PageWrapper>
@@ -98,7 +111,7 @@
  import pageHeadLeft from './pageHeadLeft.vue';
  import { PageWrapper } from '@/components/Page';
  import { ref, watch, defineProps, defineEmits, computed, reactive, onMounted,inject } from 'vue';
  import { ref, watch, defineProps, defineEmits, computed, reactive, onMounted, inject } from 'vue';
  // 定义属性
  interface Props {
@@ -106,8 +119,30 @@
    pageData?: any;
    mailType?: number;
    isDrafts?: boolean;
    isTabs?: boolean;
  }
  const props = defineProps<Props>();
  const props = defineProps({
    isTabs: {
      type: Boolean,
      default: true,
    },
    pageList: {
      type: Array,
      default: () => [],
    },
    pageData: {
      type: Object,
      default: () => {},
    },
    mailType: {
      type: Number,
      default: 0,
    },
    isDrafts: {
      type: Boolean,
      default: false,
    },
  });
  const newList = ref([]);
  const selectAllRow = ref([]);
  watch(
@@ -117,7 +152,7 @@
    },
  );
const page = computed(() => props.pageData);
  const page = computed(() => props.pageData);
  const checked = computed(() => selectAllRow.value.length > 0);
  const pageCurrent = ref(1);
  const tableRef = ref();
@@ -189,9 +224,8 @@
  });
  const getDataList = inject('getDataList');
  function handlePageChange(page, pageSize){
    getDataList(page)
  function handlePageChange(page, pageSize) {
    getDataList(page);
  }
</script>
<style scoped lang="less">
@@ -212,7 +246,7 @@
        align-items: center;
        justify-content: space-flex-start;
        width: 100%;
        height: 100%;;
        height: 100%;
        & .icon {
          margin-right: 15px;
src/views/email/components/ListPage/table.vue
@@ -11,10 +11,10 @@
          size="small"
          min-height="40px"
          :row-config="{ isCurrent: true, isHover: true }"
          :menu-config="tableMenu"
          @menu-click="contextMenuClickEvent"
          :menu-config="{ enabled: true }"
          @cell-click="cellClickEvent"
          @checkbox-change="selectChangeEvent"
          @cell-menu="onCellContextMenu"
        >
          <vxe-column type="checkbox" width="30"></vxe-column>
          <vxe-column field="sender" title="发件人" data-index="sender" min-width="300px">
@@ -78,7 +78,7 @@
                      <a-button type="link" size="small">往来邮件</a-button></div
                    >
                  </template>
                  <div class="title-dot" :class="fnIsItHighlighted(row) ? 'title-dot-color' : ''">
                  <div :class="fnIsItHighlighted(row) ? 'title-dot-color' : ''">
                    <span style="font-weight: 700">{{ row.senderName }}</span
                    ><span style="padding: 0 8px">|</span>
                    <span style="font-weight: 500">{{ row.sender }}</span>
@@ -87,6 +87,26 @@
              </div>
            </template>
          </vxe-column>
          <vxe-column
            show-overflow
            field="icon"
            title="表题"
            data-index="icon"
            width="100"
            align="right"
          >
            <template #default="{ row }">
              <a-tooltip placement="bottom">
                <template #title>
                  <span>有附件</span>
                </template>
                <span v-show="row.attachmentList?.length > 0">
                  <PaperClipOutlined />
                </span>
              </a-tooltip>
            </template>
          </vxe-column>
          <vxe-column
            show-overflow
            field="subject"
@@ -110,7 +130,7 @@
              <span style="display: flex; justify-content: space-around">
                <span>{{
                  row.mailType !== 0
                    ? formatToDateDay(row.receiveTime)
                    ? formatToDateDay(row.receiveTime || row.createTime)
                    : formatToDateDay(row.createTime)
                }}</span>
@@ -126,7 +146,13 @@
              </span>
            </template>
          </vxe-column>
        </vxe-table> </div
        </vxe-table>
        <ContextMenu
          v-if="showMenu"
          :style="menuStyle"
          :selected-cell="selectedCell"
          @close-menu="showMenu = false"
        /> </div
    ></div>
    <div v-else style="display: flex; align-items: center; justify-content: center; height: 70vh">
@@ -151,6 +177,7 @@
    PushpinOutlined,
    CopyOutlined,
    DownOutlined,
    PaperClipOutlined,
  } from '@ant-design/icons-vue';
  import { ref, watch, defineProps, defineEmits, computed, defineExpose, inject } from 'vue';
@@ -268,57 +295,63 @@
  }
  // 右键菜单
  const tableMenu = {
    className: 'my-menus',
    body: {
      options: [
        [
          {
            code: 'reply',
            name: '回复',
          },
          { code: 'replyAll', name: '回复全部' },
          { code: 'replyWithAttachment', name: '带附件回复' },
          { code: 'replyAllWithAttachment', name: '带附件回复全部' },
          { code: 'forward', name: '转发' },
          { code: 'forwardAsAttachment', name: '作为附件转发' },
          { code: 'distribute', name: '分发' },
          { code: 'setRemark', name: '设置备注' },
        ],
        [
          { code: 'toHandle', name: '待处理' },
          { code: 'markAsUnread', name: '标为未读' },
          { code: 'labelAs', name: '标注为' },
        ],
        [
          { code: 'newRule', name: '新建收发件规则' },
          { code: 'moveTo', name: '移动到' },
        ],
        [
          { code: 'exportEmail', name: '导出邮件' },
          { code: 'createFollowUp', name: '建为客户跟进' },
          { code: 'createSchedule', name: '新建日程' },
        ],
        [
          { code: 'markAsSpam', name: '标为垃圾邮件' },
          { code: 'delete', name: '删除' },
        ],
      ],
    },
  import ContextMenu from '@/views/email/components/ContextMenu/index.vue';
  const showMenu = ref(false);
  const menuStyle = ref({});
  const selectedCell = ref(null);
  const onCellContextMenu = ({
    type,
    row,
    rowIndex,
    $rowIndex,
    column,
    columnIndex,
    $columnIndex,
    $event,
  }) => {
    $event.preventDefault(); // 阻止默认的浏览器右键菜单?
    selectedCell.value = { row, column, columnIndex }; // 保存当前选中的单元格数据
    // 计算菜单初始位置
    let menuX = $event.clientX;
    let menuY = $event.clientY;
    // 获取菜单的宽度和高度
    const menuWidth = 200; // 假设菜单宽度为200px,可以根据实际情况调整
    const menuHeight = 800; // 假设菜单高度为150px,可以根据实际情况调整
    // 获取窗口宽度和高度
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    // 检查菜单是否超出窗口的右边界,如果是,则向左调整
    if (menuX + menuWidth > windowWidth) {
      menuX = windowWidth - menuWidth;
    }
    // 检查菜单是否超出窗口的下边界,如果是,则向上调整
    if (menuY + menuHeight > windowHeight) {
      menuY = windowHeight - menuHeight;
    }
    menuStyle.value = {
      position: 'fixed',
      top: `${menuY}px`,
      left: `${menuX}px`,
    };
    showMenu.value = true;
  };
  function contextMenuClickEvent({ menu, row, column }) {
    switch (menu.code) {
      case 'copy':
        if (row && column) {
        }
        break;
      default:
    }
  }
  // 监听全局点击事件,点击页面其他地方关闭菜单
  document.addEventListener('click', () => {
    showMenu.value = false;
  });
  const vxeTableRef = ref();
  function fnIsItHighlighted(row) {
    return row.readFlag && props.isDrafts;
    if (props.isDrafts) {
      return row.readFlag && props.isDrafts;
    } else {
      return row.readFlag;
    }
  }
  function fnSelectAll(is) {
    try {
@@ -462,9 +495,11 @@
  .span-title {
    width: 100%;
    height: 30px;
    padding: 5px;
    color: #000;
    font-weight: 700;
    line-height: 30px;
    text-align: left;
  }
src/views/email/folder/index.vue
New file
@@ -0,0 +1,61 @@
<template>
  <div>
    <a-spin :spinning="loading" class="p-1" style="height: 100%;">
      <PageIndex :pageList="pageList" :mailType="1" :pageData="pageData" :isTabs="false"> </PageIndex>
    </a-spin>
  </div>
</template>
<script lang="ts" setup>
  name: 'Inbox';
  import { ref, onMounted, computed, provide } from 'vue';
  import PageIndex from '@/views/email/components/ListPage/list.vue';
  import { useRoute } from 'vue-router';
  const route = useRoute();
  // 获取当前完整 URL
  const routerId = computed(() => {
    try {
      const url = window.location.href;
      if (!url) {
        throw new Error('Invalid URL');
      }
      const basePart = url.split('?')[1];
      console.log(basePart, 'basePart');
      return basePart;
    } catch (error) {
      console.error('Error processing URL:', error);
      // 返回默认值或空字符串
      return '';
    }
  });
  import { getMailListApi } from '@/api/email/userList';
  const pageList = ref([]);
  const loading = ref(false);
  const pageData = ref({
    page: 1,
    limit: 20,
    total: 0,
  });
  function getDataList(page:1) {
    loading.value = true;
    getMailListApi({ mail: routerId.value, mailType: 1,page })
      .then((res) => {
        loading.value = false;
        if (res.code == 0) {
          pageList.value = res.data.list;
          pageData.value.total = res.data.total;
        }
      })
      .catch(() => {
        loading.value = false;
      });
  }
  provide('getDataList', getDataList);
  onMounted(() => {
    getDataList();
  });
</script>
<style scoped lang="less"></style>
src/views/email/label/index.vue
New file
@@ -0,0 +1,61 @@
<template>
  <div>
    <a-spin :spinning="loading" class="p-1" style="height: 100%;">
      <PageIndex :pageList="pageList" :mailType="1" :pageData="pageData" :isTabs="false"> </PageIndex>
    </a-spin>
  </div>
</template>
<script lang="ts" setup>
  name: 'Inbox';
  import { ref, onMounted, computed, provide } from 'vue';
  import PageIndex from '@/views/email/components/ListPage/list.vue';
  import { useRoute } from 'vue-router';
  const route = useRoute();
  // 获取当前完整 URL
  const routerId = computed(() => {
    try {
      const url = window.location.href;
      if (!url) {
        throw new Error('Invalid URL');
      }
      const basePart = url.split('?')[1];
      console.log(basePart, 'basePart');
      return basePart;
    } catch (error) {
      console.error('Error processing URL:', error);
      // 返回默认值或空字符串
      return '';
    }
  });
  import { getMailListApi } from '@/api/email/userList';
  const pageList = ref([]);
  const loading = ref(false);
  const pageData = ref({
    page: 1,
    limit: 20,
    total: 0,
  });
  function getDataList(page:1) {
    loading.value = true;
    getMailListApi({ mail: routerId.value, mailType: 1,page })
      .then((res) => {
        loading.value = false;
        if (res.code == 0) {
          pageList.value = res.data.list;
          pageData.value.total = res.data.total;
        }
      })
      .catch(() => {
        loading.value = false;
      });
  }
  provide('getDataList', getDataList);
  onMounted(() => {
    getDataList();
  });
</script>
<style scoped lang="less"></style>
vite.config.ts
@@ -31,7 +31,7 @@
          // secure: false
        },
        '/upload': {
          target: 'http://localhost:3300/upload',
          target: 'http://yingchen.onbus.cn:9010/attachment/uploadAttachmentV2.do',
          changeOrigin: true,
          ws: true,
          rewrite: (path) => path.replace(new RegExp(`^/upload`), ''),