feat: UI use wangeditor for send mail (#157)

This commit is contained in:
Dream Hunter
2024-04-23 12:13:45 +08:00
committed by GitHub
parent 1be94a81e0
commit 58dcdc65f8
5 changed files with 3821 additions and 2738 deletions
+1
View File
@@ -29,3 +29,4 @@ coverage
.env.* .env.*
*-dist/ *-dist/
components.d.ts
+2
View File
@@ -14,6 +14,8 @@
"dependencies": { "dependencies": {
"@vicons/material": "^0.12.0", "@vicons/material": "^0.12.0",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.8", "axios": "^1.6.8",
"mail-parser-wasm": "^0.1.6", "mail-parser-wasm": "^0.1.6",
"naive-ui": "^2.38.1", "naive-ui": "^2.38.1",
+3744 -2725
View File
File diff suppressed because it is too large Load Diff
+65 -11
View File
@@ -1,6 +1,9 @@
<script setup> <script setup>
import '@wangeditor/editor/dist/css/style.css'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { DomEditor } from '@wangeditor/editor'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { onMounted, ref } from 'vue' import { onMounted, onBeforeUnmount, ref, shallowRef } from 'vue'
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import { useGlobalState } from '../../store' import { useGlobalState } from '../../store'
@@ -9,13 +12,14 @@ import router from '../../router'
const message = useMessage() const message = useMessage()
const isPreview = ref(false) const isPreview = ref(false)
const editorRef = shallowRef()
const mailModel = useStorage('mailModelCache', { const mailModel = useStorage('mailModelCache', {
fromName: "", fromName: "",
toName: "", toName: "",
toMail: "", toMail: "",
subject: "", subject: "",
isHtml: false, contentType: 'text',
content: "", content: "",
}) })
@@ -30,7 +34,6 @@ const { t } = useI18n({
toName: 'Recipient Name and Address, leave Name blank to use email address', toName: 'Recipient Name and Address, leave Name blank to use email address',
subject: 'Subject', subject: 'Subject',
options: 'Options', options: 'Options',
isHtml: 'Enable HTML',
edit: 'Edit', edit: 'Edit',
preview: 'Preview', preview: 'Preview',
content: 'Content', content: 'Content',
@@ -38,6 +41,10 @@ const { t } = useI18n({
requestAccess: 'Request Access', requestAccess: 'Request Access',
requestAccessTip: 'You need to request access to send mail, if have request, please contact admin.', requestAccessTip: 'You need to request access to send mail, if have request, please contact admin.',
send_balance: 'Send Mail Balance Left', send_balance: 'Send Mail Balance Left',
text: 'Text',
html: 'HTML',
'rich text': 'Rich Text',
tooLarge: 'Too large file, please upload file less than 1MB.',
}, },
zh: { zh: {
successSend: '请查看您的发件箱, 如果失败, 请检查您的余额或稍后重试。', successSend: '请查看您的发件箱, 如果失败, 请检查您的余额或稍后重试。',
@@ -45,7 +52,6 @@ const { t } = useI18n({
toName: '收件人名称和地址,名称不填写则使用邮箱地址', toName: '收件人名称和地址,名称不填写则使用邮箱地址',
subject: '主题', subject: '主题',
options: '选项', options: '选项',
isHtml: '启用HTML',
edit: '编辑', edit: '编辑',
preview: '预览', preview: '预览',
content: '内容', content: '内容',
@@ -53,10 +59,20 @@ const { t } = useI18n({
requestAccess: '申请权限', requestAccess: '申请权限',
requestAccessTip: '您需要申请权限才能发送邮件, 如果已经申请过, 请联系管理员提升额度。', requestAccessTip: '您需要申请权限才能发送邮件, 如果已经申请过, 请联系管理员提升额度。',
send_balance: '剩余发送邮件额度', send_balance: '剩余发送邮件额度',
text: '文本',
html: 'HTML',
'rich text': '富文本',
tooLarge: '文件过大, 请上传小于1MB的文件。',
} }
} }
}); });
const contentTypes = [
{ label: t('text'), value: 'text' },
{ label: t('html'), value: 'html' },
{ label: t('rich text'), value: 'rich' },
]
const send = async () => { const send = async () => {
try { try {
await api.fetch(`/api/send_mail`, await api.fetch(`/api/send_mail`,
@@ -68,7 +84,7 @@ const send = async () => {
to_name: mailModel.value.toName, to_name: mailModel.value.toName,
to_mail: mailModel.value.toMail, to_mail: mailModel.value.toMail,
subject: mailModel.value.subject, subject: mailModel.value.subject,
is_html: mailModel.value.isHtml, is_html: mailModel.value.contentType != 'text',
content: mailModel.value.content, content: mailModel.value.content,
}) })
}) })
@@ -77,7 +93,7 @@ const send = async () => {
toName: "", toName: "",
toMail: "", toMail: "",
subject: "", subject: "",
isHtml: false, contentType: 'text',
content: "", content: "",
} }
} catch (error) { } catch (error) {
@@ -103,6 +119,33 @@ const requestAccess = async () => {
} }
} }
const toolbarConfig = {
excludeKeys: ["uploadVideo"]
}
const editorConfig = {
MENU_CONF: {
'uploadImage': {
async customUpload() {
message.error(t('tooLarge'))
},
maxFileSize: 1 * 1024 * 1024,
base64LimitSize: 1 * 1024 * 1024,
}
}
}
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor) => {
editorRef.value = editor;
console.log(editor.getAllMenuKeys())
}
onMounted(async () => { onMounted(async () => {
await api.getSettings(); await api.getSettings();
}) })
@@ -143,18 +186,29 @@ onMounted(async () => {
<n-input v-model:value="mailModel.subject" /> <n-input v-model:value="mailModel.subject" />
</n-form-item> </n-form-item>
<n-form-item :label="t('options')" label-placement="top"> <n-form-item :label="t('options')" label-placement="top">
<n-checkbox v-model:checked="mailModel.isHtml"> <n-radio-group v-model:value="mailModel.contentType">
{{ t('isHtml') }} <n-radio-button v-for="option in contentTypes" :key="option.value" :value="option.value"
</n-checkbox> :label="option.label" />
<n-button v-if="mailModel.isHtml" @click="isPreview = !isPreview"> </n-radio-group>
<n-button v-if="mailModel.contentType != 'text'" @click="isPreview = !isPreview"
style="margin-left: 10px;">
{{ isPreview ? t('edit') : t('preview') }} {{ isPreview ? t('edit') : t('preview') }}
</n-button> </n-button>
</n-form-item> </n-form-item>
<n-form-item :label="t('content')" label-placement="top"> <n-form-item :label="t('content')" label-placement="top">
<div v-if="isPreview" v-html="mailModel.content" /> <n-card v-if="isPreview">
<div v-html="mailModel.content" />
</n-card>
<div v-else-if="mailModel.contentType == 'rich'" style="border: 1px solid #ccc">
<Toolbar style="border-bottom: 1px solid #ccc" :defaultConfig="toolbarConfig"
:editor="editorRef" mode="default" />
<Editor style="height: 500px; overflow-y: hidden;" v-model="mailModel.content"
:defaultConfig="editorConfig" mode="default" @onCreated="handleCreated" />
</div>
<n-input v-else type="textarea" v-model:value="mailModel.content" :autosize="{ <n-input v-else type="textarea" v-model:value="mailModel.content" :autosize="{
minRows: 3 minRows: 3
}" /> }" />
</n-form-item> </n-form-item>
</n-form> </n-form>
</div> </div>
+9 -2
View File
@@ -3,7 +3,6 @@ import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa' import { VitePWA } from 'vite-plugin-pwa'
import { splitVendorChunkPlugin } from 'vite';
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
@@ -14,12 +13,20 @@ import topLevelAwait from "vite-plugin-top-level-await";
export default defineConfig({ export default defineConfig({
build: { build: {
outDir: './dist', outDir: './dist',
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('wangeditor')) {
return 'vendor-wangeditor';
}
}
}
}
}, },
plugins: [ plugins: [
vue(), vue(),
wasm(), wasm(),
topLevelAwait(), topLevelAwait(),
splitVendorChunkPlugin(),
AutoImport({ AutoImport({
imports: [ imports: [
'vue', 'vue',