新增邮件发送和管理员功能
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import {useSettingStore} from "@/store/setting.js";
|
||||
|
||||
export function cvtR2Url(key) {
|
||||
const settingStore = useSettingStore();
|
||||
return 'https://' + settingStore.settings.r2Domain + '/' + key
|
||||
return settingStore.settings.r2Domain + '/' + key
|
||||
}
|
||||
43
mail-vue/src/utils/day.js
Normal file
43
mail-vue/src/utils/day.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
|
||||
export function fromNow(date) {
|
||||
const d = dayjs.utc(date).tz('Asia/Shanghai');
|
||||
const now = dayjs();
|
||||
const diffSeconds = now.diff(d, 'second');
|
||||
const diffMinutes = now.diff(d, 'minute');
|
||||
const diffHours = now.diff(d, 'hour');
|
||||
const isToday = now.isSame(d, 'day');
|
||||
|
||||
if (isToday) {
|
||||
if (diffSeconds < 60) return `几秒前`;
|
||||
if (diffMinutes < 60) return `${diffMinutes}分钟前`;
|
||||
if (diffHours >= 1 && diffHours < 2) return '1小时前';
|
||||
return d.format('HH:mm');
|
||||
}
|
||||
else if (now.subtract(1, 'day').isSame(d, 'day')) {
|
||||
return `昨天 ${d.format('HH:mm')}`;
|
||||
}
|
||||
else if (now.subtract(2, 'day').isSame(d, 'day')) {
|
||||
return `前天 ${d.format('HH:mm')}`;
|
||||
}
|
||||
return d.year() === now.year()
|
||||
? d.format('M月D日')
|
||||
: d.format('YYYY/M/D');
|
||||
}
|
||||
|
||||
|
||||
export function formatDetailDate(time) {
|
||||
return dayjs.utc(time).format('YYYY年M月D日 ddd AH:mm')
|
||||
}
|
||||
|
||||
export function tzDayjs(time) {
|
||||
return dayjs.utc(time).tz('Asia/Shanghai')
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import Compressor from "compressorjs";
|
||||
|
||||
export function getExtName(fileName) {
|
||||
const index = fileName.lastIndexOf('.')
|
||||
return index !== -1 ? fileName.slice(index + 1).toLowerCase() : ''
|
||||
@@ -10,4 +12,38 @@ export function formatBytes(bytes) {
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
const size = (bytes / Math.pow(k, i)).toFixed(2);
|
||||
return `${size} ${units[i]}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function fileToBase64(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
const base64 = reader.result.split(',')[1];
|
||||
resolve(base64);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
});
|
||||
}
|
||||
|
||||
export function base64Size(base64String) {
|
||||
const padding = (base64String.match(/=*$/) || [''])[0].length;
|
||||
const base64Length = base64String.length;
|
||||
return (base64Length * 3) / 4 - padding;
|
||||
}
|
||||
|
||||
export function compressImage(file, quality = 0.6) {
|
||||
return new Promise((resolve, reject) => {
|
||||
new Compressor(file, {
|
||||
quality,
|
||||
mimeType: 'image/jpeg',
|
||||
convertSize: 1024 * 1024,
|
||||
success(result) {
|
||||
resolve(result);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
16
mail-vue/src/utils/icon-utils.js
Normal file
16
mail-vue/src/utils/icon-utils.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import {getExtName} from "@/utils/file-utils.js";
|
||||
|
||||
export function getIconByName(filename) {
|
||||
const extName = getExtName(filename)
|
||||
if (['zip', 'rar', '7z', 'tar', 'tgz'].includes(extName)) return 'octicon:file-zip-24';
|
||||
if (['png', 'jpg', 'jpeg','gif','webp'].includes(extName)) return 'mingcute:pic-line';
|
||||
if (['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv'].includes(extName)) return 'fluent:video-clip-24-regular';
|
||||
if (['txt', 'doc', 'docx', 'md','ini','conf'].includes(extName)) return 'hugeicons:google-doc'
|
||||
if (['xls', 'csv', 'xlsx'].includes(extName)) return 'codicon:table';
|
||||
if (['mp3', 'wav', 'aac', 'ogg', 'flac', 'm4a'].includes(extName)) return 'mynaui:music';
|
||||
if (['.ppt', 'pptx', 'pps', 'potx', 'pot'].includes(extName)) return 'lsicon:file-ppt-filled'
|
||||
if (extName === 'pdf') return 'hugeicons:pdf-02';
|
||||
if (extName === 'apk') return 'proicons:android';
|
||||
if (extName === 'exe') return 'bi:filetype-exe';
|
||||
return "hugeicons:attachment-01"
|
||||
}
|
||||
71
mail-vue/src/utils/init.js
Normal file
71
mail-vue/src/utils/init.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useUserStore } from "@/store/user.js";
|
||||
import { useSettingStore } from "@/store/setting.js";
|
||||
import { useAccountStore } from "@/store/account.js";
|
||||
import { loginUserInfo } from "@/request/my.js";
|
||||
import { permsToRouter } from "@/utils/perm.js";
|
||||
import router from "@/router";
|
||||
import { settingQuery } from "@/request/setting.js";
|
||||
import {cvtR2Url} from "@/utils/convert.js";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
export async function init() {
|
||||
document.title = '-'
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
const userStore = useUserStore();
|
||||
const accountStore = useAccountStore();
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
let setting = null;
|
||||
|
||||
if (token) {
|
||||
const userPromise = loginUserInfo().catch(e => {
|
||||
console.error(e);
|
||||
return null;
|
||||
});
|
||||
|
||||
const [s, user] = await Promise.all([settingQuery(), userPromise]);
|
||||
setting = s;
|
||||
settingStore.settings = setting;
|
||||
settingStore.domainList = setting.domainList;
|
||||
document.title = setting.title;
|
||||
|
||||
if (user) {
|
||||
accountStore.currentAccountId = user.accountId;
|
||||
userStore.user = user;
|
||||
|
||||
const routers = permsToRouter(user.permKeys);
|
||||
routers.forEach(routerData => {
|
||||
router.addRoute('layout', routerData);
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
setting = await settingQuery();
|
||||
settingStore.settings = setting;
|
||||
settingStore.domainList = setting.domainList;
|
||||
document.title = setting.title;
|
||||
}
|
||||
|
||||
const loading = document.getElementById('loading-first');
|
||||
|
||||
if (!setting.background) {
|
||||
loading.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const img = new Image();
|
||||
img.src = cvtR2Url(setting.background);
|
||||
img.onload = () => {
|
||||
loading.remove();
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
|
||||
console.warn('背景图片加载失败:', img.src);
|
||||
loading.remove();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
60
mail-vue/src/utils/perm.js
Normal file
60
mail-vue/src/utils/perm.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import {useUserStore} from "@/store/user.js";
|
||||
|
||||
export default function hasPerm(permKey) {
|
||||
const {permKeys} = useUserStore().user;
|
||||
return permKeys.includes('*') || permKeys.includes(permKey);
|
||||
}
|
||||
|
||||
|
||||
export function permsToRouter(permKeys) {
|
||||
const routerList = []
|
||||
Object.keys(routers).forEach(perm => {
|
||||
if (permKeys.includes(perm) || permKeys.includes('*')) {
|
||||
routerList.push(routers[perm])
|
||||
}
|
||||
})
|
||||
return routerList;
|
||||
}
|
||||
|
||||
const routers = {
|
||||
'user:query': {
|
||||
path: '/sys/user',
|
||||
name: 'user',
|
||||
component: () => import('@/views/user/index.vue'),
|
||||
meta: {
|
||||
title: '用户列表',
|
||||
name: 'user',
|
||||
menu: true
|
||||
}
|
||||
},
|
||||
'role:query': {
|
||||
path: '/sys/role',
|
||||
name: 'role',
|
||||
component: () => import('@/views/role/index.vue'),
|
||||
meta: {
|
||||
title: '权限控制',
|
||||
name: 'role',
|
||||
menu: true
|
||||
}
|
||||
},
|
||||
'setting:query': {
|
||||
path: '/sys/setting',
|
||||
name: 'sys-setting',
|
||||
component: () => import('@/views/sys-setting/index.vue'),
|
||||
meta: {
|
||||
title: '系统设置',
|
||||
name: 'sys-setting',
|
||||
menu: true
|
||||
}
|
||||
},
|
||||
'sys-email:query': {
|
||||
path: '/sys/email',
|
||||
name: 'sys-email',
|
||||
component: () => import('@/views/sys-email/index.vue'),
|
||||
meta: {
|
||||
title: '邮箱列表',
|
||||
name: 'sys-email',
|
||||
menu: true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user