代码分离,功能优化 (#204)

* feat: 代码分离,功能优化

1. 代码迁移到 ts.
2. 分离 axios 部分代码.
3. 增加 pinia 支持,全局状态代码迁移到相对应的 store.
4. 代码格式优化, 用 === 代替 ==.
5. 代码声明更改,用 const 代替 var 声明.
6. Header 使用 Router 导航.
7. v-for 增加 key.

* fix[fe]: 移除过时的 prop 引用

* fix[fe]: 移除过时的 prop 引用

* fix[fe]: 修复 logo 上面有横线的问题

* fix[fe]: 修复 logo 上面有横线的问题

---------

Co-authored-by: zhe28 <huangze28@foxmail.com>
This commit is contained in:
HuangZhe 2024-09-23 10:24:49 +08:00 committed by GitHub
parent dbb671df67
commit ad0167f6fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1640 additions and 1638 deletions

View File

@ -15,6 +15,7 @@
"element-plus": "^2.3.6",
"pinia": "^2.0.36",
"vue": "^3.3.2",
"vue-icons-plus": "^0.1.6",
"vue-router": "^4.2.0"
},
"devDependencies": {
@ -23,6 +24,7 @@
"eslint-plugin-vue": "^9.11.0",
"unplugin-auto-import": "^0.16.4",
"unplugin-vue-components": "^0.25.0",
"vite": "^4.3.5"
"vite": "^4.3.5",
"vue-tsc": "^2.1.6"
}
}

View File

@ -1,31 +1,31 @@
<script setup>
import { RouterView } from 'vue-router'
import {RouterView, useRoute} from 'vue-router'
import HomeHeader from '@/components/HomeHeader.vue'
import HomeAside from '@/components/HomeAside.vue';
import { watch, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {ref, watch} from 'vue'
const route = useRoute()
const pageName = ref(route.name)
watch(
() => route.fullPath,
(n, o) => {
pageName.value = route.name
}
() => route.fullPath,
() => {
pageName.value = route.name
}
)
</script>
<template>
<div id="main">
<HomeHeader />
<HomeHeader/>
<div id="content">
<div id="aside" v-if="pageName != 'login' && pageName != 'setup'">
<HomeAside />
<div id="aside" v-if="pageName !== 'login' && pageName !== 'setup'">
<HomeAside/>
</div>
<div id="body">
<RouterView />
<RouterView/>
</div>
</div>
</div>

View File

@ -1,98 +1,123 @@
<template>
<div id="main">
<el-tree :expand-on-click-node="false" :data="data" :props="defaultProps" :defaultExpandAll="true" :class="node">
<template #default="{ node, data }">
<div>
<span v-if="data.id != -1"> {{ data.label }}</span>
<el-input v-if="data.id == -1" v-model="data.label" @blur="onInputBlur(data)"></el-input>
<el-button v-if="data.id != 0" @click="del(node, data)" size="small" type="danger" circle> -
</el-button>
<el-button v-if="data.id != 0" @click="add(data)" size="small" type="primary" circle> + </el-button>
</div>
</template>
</el-tree>
<div id="main">
<el-tree
:expand-on-click-node="false"
:data="data"
:defaultExpandAll="true"
>
<template #default="{ node, data }">
<div>
<span v-if="data.id !== -1"> {{ data.label }}</span>
<el-input
v-if="data.id === -1"
v-model="data.label"
@blur="onInputBlur(data)"
></el-input>
<el-button
v-if="data.id !== 0"
@click="del(node, data)"
size="small"
type="danger"
circle
>
-
</el-button>
<el-button
v-if="data.id !== 0"
@click="add(data)"
size="small"
type="primary"
circle
>
+
</el-button>
</div>
</template>
</el-tree>
<el-button @click="addRoot">{{ lang.add_group }}</el-button>
</div>
<el-button @click="addRoot">{{ lang.add_group }}</el-button>
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance } from 'vue'
import lang from '../i18n/i18n';
import { reactive } from "vue";
import lang from "../i18n/i18n";
import { http } from "@/utils/axios";
import { ElMessage } from "element-plus";
const data = reactive([])
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const data = reactive([]);
$http.get('/api/group').then(res => {
data.push(...res.data)
})
http.get("/api/group").then((res) => {
data.push(...res.data);
});
const del = function (node, data) {
if (data.id != -1) {
this.$axios.post("/api/group/del", { "id": data.id }).then(res => {
if (res.errorNo != 0) {
ElMessage({
message: res.errorMsg,
type: 'error',
})
} else {
const pc = node.parent.childNodes
for (let i = 0; i < pc.length; i++) {
if (pc[i].id == node.id) {
pc.splice(i, 1)
return
}
}
}
})
} else {
const pc = node.parent.childNodes
if (data.id !== -1) {
this.$axios.post("/api/group/del", { id: data.id }).then((res) => {
if (res.errorNo !== 0) {
ElMessage({
message: res.errorMsg,
type: "error",
});
} else {
const pc = node.parent.childNodes;
for (let i = 0; i < pc.length; i++) {
if (pc[i].id == node.id) {
pc.splice(i, 1)
return
}
if (pc[i].id === node.id) {
pc.splice(i, 1);
return;
}
}
}
});
} else {
const pc = node.parent.childNodes;
for (let i = 0; i < pc.length; i++) {
if (pc[i].id === node.id) {
pc.splice(i, 1);
return;
}
}
}
}
};
const add = function (item) {
if (item.children == null) {
item.children = []
}
item.children.push({
"children": [],
"label": "",
"id": "-1",
"parent_id": item.id
})
}
if (item.children == null) {
item.children = [];
}
item.children.push({
children: [],
label: "",
id: "-1",
parent_id: item.id,
});
};
const addRoot = function () {
data.push({
"children": [],
"label": "",
"id": "-1",
"parent_id": 0
})
}
data.push({
children: [],
label: "",
id: "-1",
parent_id: 0,
});
};
const onInputBlur = function (item) {
if (item.label != "") {
this.$axios.post("/api/group/add", { "name": item.label, "parent_id": item.parent_id }).then(res => {
if (res.errorNo != 0) {
ElMessage({
message: res.errorMsg,
type: 'error',
})
} else {
this.$axios.get('/api/group').then(res => {
data.splice(0, data.length)
data.push(...res.data)
})
}
})
}
}
</script>
if (item.label !== "") {
http
.post("/api/group/add", { name: item.label, parent_id: item.parent_id })
.then((res) => {
if (res.errorNo !== 0) {
ElMessage({
message: res.errorMsg,
type: "error",
});
} else {
this.$axios.get("/api/group").then((res) => {
data.splice(0, data.length);
data.push(...res.data);
});
}
});
}
};
</script>

View File

@ -1,58 +1,49 @@
<template>
<div id="main">
<input id="search" :placeholder="lang.search">
<el-tree :data="data" :props="defaultProps" :defaultExpandAll="true" @node-click="handleNodeClick" :class="node" />
<input id="search" :placeholder="lang.search" />
<el-tree
:data="data"
:defaultExpandAll="true"
@node-click="handleNodeClick"
/>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { reactive, ref } from 'vue'
import useGroupStore from '../stores/group'
import lang from '../i18n/i18n';
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const groupStore = useGroupStore()
const router = useRouter()
const data = ref([])
$http.get('/api/group').then(res => {
data.value = res.data
})
import { useRouter } from "vue-router";
import { ref } from "vue";
import useGroupStore from "../stores/group";
import lang from "../i18n/i18n";
import { http } from "@/utils/axios";
const groupStore = useGroupStore();
const router = useRouter();
const data = ref([]);
http.get("/api/group").then((res) => {
if (res.data) data.value = res.data;
});
const handleNodeClick = function (data) {
if (data.tag != null) {
groupStore.name = data.label
groupStore.tag = data.tag
groupStore.name = data.label;
groupStore.tag = data.tag;
router.push({
name: "list",
})
});
}
}
};
</script>
<style scoped>
#main {
width: 243px;
background-color: #F1F1F1;
background-color: #f1f1f1;
height: 100%;
}
#search {
background-color: #D6E7F7;
background-color: #d6e7f7;
width: 100%;
height: 40px;
padding-left: 10px;
@ -62,10 +53,10 @@ const handleNodeClick = function (data) {
}
.el-tree {
background-color: #F1F1F1;
background-color: #f1f1f1;
}
.add_group{
.add_group {
font-size: 14px;
text-align: left;
padding-left: 15px;

View File

@ -1,76 +1,78 @@
<template>
<div id="header_main">
<div id="logo">
<span style="padding-left: 20px;">PMail</span>
</div>
<div id="settings" @click="settings" v-if="$isLogin">
<el-icon style="font-size: 25px;">
<Setting style="color:#FFFFFF" />
</el-icon>
</div>
<el-drawer v-model="openSettings" size="80%" :title="lang.settings">
<el-tabs tab-position="left">
<el-tab-pane :label="lang.security">
<SecuritySettings />
</el-tab-pane>
<el-tab-pane :label="lang.group_settings">
<GroupSettings />
</el-tab-pane>
<el-tab-pane :label="lang.rule_setting">
<RuleSettings />
</el-tab-pane>
<el-tab-pane v-if="$userInfos.is_admin" :label="lang.user_management">
<UserManagement />
</el-tab-pane>
<el-tab-pane :label="lang.plugin_settings">
<PluginSettings />
</el-tab-pane>
</el-tabs>
</el-drawer>
<div id="header_main">
<div id="logo">
<router-link to="/" style="text-underline: none">
<el-text :line-clamp="1" size="large"><h1>Pmail</h1></el-text>
</router-link>
</div>
<div id="settings" @click="settings" v-if="isLogin">
<el-icon style="font-size: 25px;">
<TbSettings style="color:#FFFFFF"/>
</el-icon>
</div>
<el-drawer v-model="openSettings" size="80%" :title="lang.settings">
<el-tabs tab-position="left">
<el-tab-pane :label="lang.security">
<SecuritySettings/>
</el-tab-pane>
<el-tab-pane :label="lang.group_settings">
<GroupSettings/>
</el-tab-pane>
<el-tab-pane :label="lang.rule_setting">
<RuleSettings/>
</el-tab-pane>
<el-tab-pane v-if="userInfos.is_admin" :label="lang.user_management">
<UserManagement/>
</el-tab-pane>
<el-tab-pane :label="lang.plugin_settings">
<PluginSettings/>
</el-tab-pane>
</el-tabs>
</el-drawer>
</div>
</template>
<script setup>
import { Setting } from '@element-plus/icons-vue';
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import {TbSettings} from "vue-icons-plus/tb";
import {ref} from 'vue'
import {ElMessage} from 'element-plus'
import SecuritySettings from '@/components/SecuritySettings.vue'
import lang from '../i18n/i18n';
import GroupSettings from './GroupSettings.vue';
import RuleSettings from './RuleSettings.vue';
import UserManagement from './UserManagement.vue';
import { getCurrentInstance } from 'vue'
import PluginSettings from './PluginSettings.vue';
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const $isLogin = app.appContext.config.globalProperties.$isLogin
const $userInfos = app.appContext.config.globalProperties.$userInfos
import {http} from "@/utils/axios";
import {useGlobalStatusStore} from "@/stores/useGlobalStatusStore";
const globalStatus = useGlobalStatusStore();
const isLogin = globalStatus.isLogin;
const userInfos = globalStatus.userInfos;
const openSettings = ref(false)
const settings = function () {
if (Object.keys($userInfos.value).length == 0) {
$http.post("/api/user/info", {}).then(res => {
if (res.errorNo == 0) {
$userInfos.value = res.data
openSettings.value = true;
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
})
}else{
if (Object.keys(userInfos).length === 0) {
http.post("/api/user/info", {}).then(res => {
if (res.errorNo === 0) {
userInfos.value = res.data
openSettings.value = true;
}
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
})
} else {
openSettings.value = true;
}
}
@ -79,32 +81,38 @@ const settings = function () {
<style scoped>
#header_main {
height: 50px;
background-color: #000;
display: flex;
padding: 0;
height: 50px;
background-color: #000;
display: flex;
padding: 0;
}
#logo {
height: 3rem;
line-height: 3rem;
font-size: 2.3rem;
flex-grow: 1;
width: 200px;
color: #FFF;
text-align: left;
height: 3rem;
line-height: 3rem;
font-size: 2.3rem;
flex-grow: 1;
width: 200px;
color: #FFF;
text-align: left;
}
#logo h1 {
padding-left: 20px;
color: white;
}
#search {
height: 3rem;
width: 100%;
height: 3rem;
width: 100%;
}
#settings {
display: flex;
justify-content: center;
align-items: center;
padding-right: 20px;
display: flex;
justify-content: center;
align-items: center;
padding-right: 20px;
}
</style>

View File

@ -1,35 +1,35 @@
<template>
<div id="main">
<el-tabs>
<el-tab-pane v-for="(src, name) in pluginList" :label="name">
<iframe :src="src"></iframe>
</el-tab-pane>
</el-tabs>
</div>
<div id="main">
<el-tabs>
<el-tab-pane v-for="(src, name) in pluginList" :key="src" :label="name">
<iframe :src="src"></iframe>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
import {reactive} from 'vue'
import {http} from "@/utils/axios";
const pluginList = reactive({})
$http.get('/api/plugin/list').then(res => {
if (res.data != null && res.data.length > 0) {
for (let i = 0; i < res.data.length; i++) {
let name = res.data[i];
pluginList[name] = "/api/plugin/settings/"+ name +"/index.html";
}
http.get('/api/plugin/list').then(res => {
if (res.data != null && res.data.length > 0) {
for (let i = 0; i < res.data.length; i++) {
let name = res.data[i];
pluginList[name] = "/api/plugin/settings/" + name + "/index.html";
}
}
})
</script>
<style scoped>
iframe{
width: 100%;
border: 0;
iframe {
width: 100%;
border: 0;
}
</style>

View File

@ -1,115 +1,105 @@
<template>
<el-table :data="data" :show-header="true">
<el-table-column prop="id" label="id" />
<el-table-column prop="name" :label="lang.rule_name" />
<el-table-column prop="action" :label="lang.rule_do">
<template #default="scope">
{{ ActionName[scope.row.action] }}
<el-table :data="data" :show-header="true">
<el-table-column prop="id" label="id"/>
<el-table-column prop="name" :label="lang.rule_name"/>
<el-table-column prop="action" :label="lang.rule_do">
<template #default="scope">
{{ ActionName[scope.row.action] }}
</template>
</el-table-column>
<el-table-column prop="params" :label="lang.rule_params"/>
<el-table-column prop="sort" :label="lang.rule_priority"/>
<el-table-column>
<template #default="scope">
<div style="display: flex; align-items: center">
<el-button size="small" type="primary" :icon="Edit" circle @click="editRule(scope.row)"/>
<el-popconfirm confirm-button-text="Yes" cancel-button-text="No, Thanks" :icon="InfoFilled"
@confirm="delRule(scope.row.id)" icon-color="#626AEF" :title="lang.del_rule_confirm">
<template #reference>
<el-button size="small" type="danger" :icon="Delete" circle/>
</template>
</el-table-column>
<el-table-column prop="params" :label="lang.rule_params" />
<el-table-column prop="sort" :label="lang.rule_priority" />
<el-table-column>
<template #default="scope">
<div style="display: flex; align-items: center">
<el-button size="small" type="primary" :icon="Edit" circle @click="editRule(scope.row)" />
<el-popconfirm confirm-button-text="Yes" cancel-button-text="No, Thanks" :icon="InfoFilled"
@confirm="delRule(scope.row.id)" icon-color="#626AEF" :title="lang.del_rule_confirm">
<template #reference>
<el-button size="small" type="danger" :icon="Delete" circle />
</template>
</el-popconfirm>
</div>
</template>
</el-table-column>
</el-table>
<div>
<el-button @click="dialogVisible = true">{{ lang.new_rule }}</el-button>
</div>
<el-dialog v-model="dialogVisible" :title="lang.new_rule" width="60%">
<div style="text-align: left; padding-left: 20px;">
<el-form v-model="addRuleForm" :inline="true" label-position="top">
<el-form-item style="width: 400px;" :label="lang.rule_name">
<el-input v-model="addRuleForm.name" />
</el-form-item>
<el-form-item :label="lang.rule_priority">
<el-input v-model="addRuleForm.sort" type="number" oninput="value=value.replace(/[^\-\d]/g, '')" />
</el-form-item>
<el-divider />
<div style="width: 100%;">{{ lang.rule_desc }}</div>
<div style="width: 100%;">
<div v-for="(rule, index) in addRuleForm.rules">
<el-select v-model="rule.field" placeholder="Select">
<el-option key="From" :label="lang.from" value="From" />
<el-option key="Subject" :label="lang.subject" value="Subject" />
<el-option key="To" :label="lang.to" value="To" />
<el-option key="Cc" :label="lang.cc" value="Cc" />
<el-option key="Content" :label="lang.content" value="Content" />
</el-select>
<el-select v-model="rule.type" placeholder="Select">
<el-option key="equal" :label="lang.equal" value="equal" />
<el-option key="contains" :label="lang.contains" value="contains" />
<el-option key="regex" :label="lang.regex" value="regex" />
</el-select>
<el-input v-model="rule.rule" style="width: 350px;" />
<el-button size="small" type="danger" :icon="Delete" @click="removeRuleLine(index)" circle />
</div>
</div>
<div style="padding-top: 7px;">
<el-button size="small" type="primary" :icon="Plus" circle @click="addRule()" />
</div>
<el-divider />
<div style="width: 100%;">{{ lang.rule_do }}</div>
<el-form-item>
<el-select v-model="addRuleForm.action" placeholder="Select" @change="ruleTypeChange()">
<el-option key="mark_read" :label="lang.mark_read" :value="READ" />
<el-option key="move" :label="lang.move" :value="MOVE" />
<el-option key="delete" :label="lang.delete" :value="DELETE" />
<el-option key="forward" :label="lang.forward" :value="FORWARD" />
</el-select>
<el-select v-if="addRuleForm.action == 4" v-model="addRuleForm.params" @click="reflushGroupInfos">
<el-option v-for="gp in groupData.list" :key="gp.id" :label="gp.name" :value="gp.id" />
</el-select>
<el-input v-if="addRuleForm.action == 2" v-model="addRuleForm.params" style="width: 250px;"
placeholder="Forward Email Address" />
</el-form-item>
</el-form>
</el-popconfirm>
</div>
<template #footer>
</template>
</el-table-column>
</el-table>
<div>
<el-button @click="dialogVisible = true">{{ lang.new_rule }}</el-button>
</div>
<el-dialog v-model="dialogVisible" :title="lang.new_rule" width="60%">
<div style="text-align: left; padding-left: 20px;">
<el-form v-model="addRuleForm" :inline="true" label-position="top">
<el-form-item style="width: 400px;" :label="lang.rule_name">
<el-input v-model="addRuleForm.name"/>
</el-form-item>
<el-form-item :label="lang.rule_priority">
<el-input v-model="addRuleForm.sort" type="number" oninput="value=value.replace(/[^\-\d]/g, '')"/>
</el-form-item>
<el-divider/>
<div style="width: 100%;">{{ lang.rule_desc }}</div>
<div style="width: 100%;">
<div v-for="(rule, index) in addRuleForm.rules" :key="index">
<el-select v-model="rule.field" placeholder="Select">
<el-option key="From" :label="lang.from" value="From"/>
<el-option key="Subject" :label="lang.subject" value="Subject"/>
<el-option key="To" :label="lang.to" value="To"/>
<el-option key="Cc" :label="lang.cc" value="Cc"/>
<el-option key="Content" :label="lang.content" value="Content"/>
</el-select>
<el-select v-model="rule.type" placeholder="Select">
<el-option key="equal" :label="lang.equal" value="equal"/>
<el-option key="contains" :label="lang.contains" value="contains"/>
<el-option key="regex" :label="lang.regex" value="regex"/>
</el-select>
<el-input v-model="rule.rule" style="width: 350px;"/>
<el-button size="small" type="danger" :icon="Delete" @click="removeRuleLine(index)" circle/>
</div>
</div>
<div style="padding-top: 7px;">
<el-button size="small" type="primary" :icon="Plus" circle @click="addRule()"/>
</div>
<el-divider/>
<div style="width: 100%;">{{ lang.rule_do }}</div>
<el-form-item>
<el-select v-model="addRuleForm.action" placeholder="Select" @change="ruleTypeChange()">
<el-option key="mark_read" :label="lang.mark_read" :value="READ"/>
<el-option key="move" :label="lang.move" :value="MOVE"/>
<el-option key="delete" :label="lang.delete" :value="DELETE"/>
<el-option key="forward" :label="lang.forward" :value="FORWARD"/>
</el-select>
<el-select v-if="addRuleForm.action === 4" v-model="addRuleForm.params" @click="reflushGroupInfos">
<el-option v-for="gp in groupData.list" :key="gp.id" :label="gp.name" :value="gp.id"/>
</el-select>
<el-input v-if="addRuleForm.action === 2" v-model="addRuleForm.params" style="width: 250px;"
placeholder="Forward Email Address"/>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="submitRule()">
{{ lang.submit }}
</el-button>
</span>
</template>
</el-dialog>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive } from 'vue';
import {reactive, ref} from 'vue';
import lang from '../i18n/i18n';
import {
Plus,
Delete,
Edit,
InfoFilled
} from '@element-plus/icons-vue'
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
import {Delete, Edit, InfoFilled, Plus} from '@element-plus/icons-vue'
import {http} from "@/utils/axios";
import {ElNotification} from "element-plus";
const data = ref([])
const dialogVisible = ref(false)
@ -119,127 +109,126 @@ const DELETE = 3
const MOVE = 4
const ActionName = {
1: lang.mark_read,
2: lang.forward,
3: lang.delete,
4: lang.move
1: lang.mark_read,
2: lang.forward,
3: lang.delete,
4: lang.move
}
const init = function () {
$http.post("/api/rule/get").then((res) => {
data.value = res.data
})
http.post("/api/rule/get").then((res) => {
data.value = res.data
})
}
init()
const groupData = reactive({
list: []
list: []
})
const reflushGroupInfos = function () {
$http.get('/api/group/list').then(res => {
if (res.data != null) {
groupData.list = res.data
for (let i = 0; i < groupData.list.length; i++) {
groupData.list[i].id += ""
}
}
http.get('/api/group/list').then(res => {
if (res.data != null) {
groupData.list = res.data
for (let i = 0; i < groupData.list.length; i++) {
groupData.list[i].id += ""
}
}
})
})
}
reflushGroupInfos()
const addRuleForm = reactive({
"id": 0,
"name": "",
"sort": 0,
"rules": [
{
"field": "",
"type": "",
"rule": ""
}
],
"action": "",
"params": ""
"id": 0,
"name": "",
"sort": 0,
"rules": [
{
"field": "",
"type": "",
"rule": ""
}
],
"action": "",
"params": ""
})
const delRule = function (id) {
$http.post("/api/rule/del", { "id": id }).then((res) => {
ElNotification({
title: res.errorNo == 0 ? lang.succ : lang.fail,
message: res.data,
type: res.errorNo == 0 ? 'success' : 'error',
})
init()
http.post("/api/rule/del", {"id": id}).then((res) => {
ElNotification({
title: res.errorNo === 0 ? lang.succ : lang.fail,
message: res.data,
type: res.errorNo === 0 ? 'success' : 'error',
})
init()
})
}
const editRule = function (ruleInfo) {
addRuleForm.id = ruleInfo.id
addRuleForm.name = ruleInfo.name
addRuleForm.rules = ruleInfo.rules
addRuleForm.action = ruleInfo.action
addRuleForm.params = ruleInfo.params
addRuleForm.sort = ruleInfo.sort
dialogVisible.value = true
addRuleForm.id = ruleInfo.id
addRuleForm.name = ruleInfo.name
addRuleForm.rules = ruleInfo.rules
addRuleForm.action = ruleInfo.action
addRuleForm.params = ruleInfo.params
addRuleForm.sort = ruleInfo.sort
dialogVisible.value = true
}
const removeRuleLine = function (index) {
addRuleForm.rules.splice(index, 1);
addRuleForm.rules.splice(index, 1);
}
const addRule = function () {
addRuleForm.rules.push(
{
"field": "",
"type": "",
"rule": ""
}
)
addRuleForm.rules.push(
{
"field": "",
"type": "",
"rule": ""
}
)
}
const submitRule = function () {
let api = "/api/rule/add"
if (addRuleForm.id > 0) {
api = "/api/rule/update"
}
let api = "/api/rule/add"
if (addRuleForm.id > 0) {
api = "/api/rule/update"
}
addRuleForm.sort = parseInt(addRuleForm.sort)
addRuleForm.sort = parseInt(addRuleForm.sort)
$http.post(api, addRuleForm).then((res) => {
if (res.errorNo != 0) {
ElNotification({
title: lang.fail,
message: res.data,
type: 'error',
})
} else {
init()
dialogVisible.value = false
http.post(api, addRuleForm).then((res) => {
if (res.errorNo !== 0) {
ElNotification({
title: lang.fail,
message: res.data,
type: 'error',
})
} else {
init()
dialogVisible.value = false
addRuleForm.id = 0
addRuleForm.name = ""
addRuleForm.sort = 0
addRuleForm.rules = [
{
"field": "",
"type": "",
"rule": ""
}
]
addRuleForm.action = ""
addRuleForm.params = ""
addRuleForm.id = 0
addRuleForm.name = ""
addRuleForm.sort = 0
addRuleForm.rules = [
{
"field": "",
"type": "",
"rule": ""
}
})
]
addRuleForm.action = ""
addRuleForm.params = ""
}
})
}
const ruleTypeChange = function () {
addRuleForm.params = ''
addRuleForm.params = ''
}
</script>

View File

@ -1,78 +1,75 @@
<template>
<el-form :model="ruleForm" :rules="rules" status-icon>
<el-form :model="ruleForm" :rules="rules" status-icon>
<el-divider content-position="left">{{lang.modify_pwd}}</el-divider>
<el-divider content-position="left">{{ lang.modify_pwd }}</el-divider>
<el-form-item :label="lang.modify_pwd" prop="new_pwd">
<el-input type="password" v-model="ruleForm.new_pwd" />
</el-form-item>
<el-form-item :label="lang.modify_pwd" prop="new_pwd">
<el-input type="password" v-model="ruleForm.new_pwd"/>
</el-form-item>
<el-form-item :label="lang.enter_again" prop="new_pwd2">
<el-input type="password" v-model="ruleForm.new_pwd2" />
</el-form-item>
<el-form-item :label="lang.enter_again" prop="new_pwd2">
<el-input type="password" v-model="ruleForm.new_pwd2"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">
{{ lang.submit }}
</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">
{{ lang.submit }}
</el-button>
</el-form-item>
<el-divider content-position="left">{{lang.logout}}</el-divider>
<el-form-item>
<el-button type="primary" @click="logout">
{{ lang.logout }}
</el-button>
</el-form-item>
</el-form>
<el-divider content-position="left">{{ lang.logout }}</el-divider>
<el-form-item>
<el-button type="primary" @click="logout">
{{ lang.logout }}
</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { ElNotification } from 'element-plus'
import {reactive} from 'vue'
import {ElNotification} from 'element-plus'
import lang from '../i18n/i18n';
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
import {http} from "@/utils/axios";
const ruleForm = reactive({
new_pwd: "",
new_pwd2: ""
new_pwd: "",
new_pwd2: ""
})
const rules = reactive({
new_pwd: [{ required: true, message: lang.err_required_pwd, trigger: 'blur' },],
new_pwd2: [{ required: true, message: lang.err_required_pwd, trigger: 'blur' },],
new_pwd: [{required: true, message: lang.err_required_pwd, trigger: 'blur'},],
new_pwd2: [{required: true, message: lang.err_required_pwd, trigger: 'blur'},],
})
const logout = function(){
$http.post("/api/logout", { }).then(res => {
location.reload();
})
const logout = function () {
http.post("/api/logout", {}).then(() => {
location.reload();
})
}
const submit = function () {
if (ruleForm.new_pwd == ""){
return
}
if (ruleForm.new_pwd === "") {
return
}
if (ruleForm.new_pwd != ruleForm.new_pwd2) {
ElNotification({
title: 'Error',
message: lang.err_pwd_diff,
type: 'error',
})
return
}
$http.post("/api/settings/modify_password", { password: ruleForm.new_pwd }).then(res => {
ElNotification({
title: res.errorNo == 0 ? lang.succ : lang.fail,
message: res.data,
type: res.errorNo == 0 ? 'success' : 'error',
})
if (ruleForm.new_pwd !== ruleForm.new_pwd2) {
ElNotification({
title: 'Error',
message: lang.err_pwd_diff,
type: 'error',
})
return
}
http.post("/api/settings/modify_password", {password: ruleForm.new_pwd}).then(res => {
ElNotification({
title: res.errorNo === 0 ? lang.succ : lang.fail,
message: res.data,
type: res.errorNo === 0 ? 'success' : 'error',
})
})
}

View File

@ -1,12 +1,12 @@
<template>
<div id="main">
<el-table :data="userList" style="width: 100%">
<el-table-column label="ID" prop="ID" />
<el-table-column :label="lang.account" prop="Account" />
<el-table-column :label="lang.user_name" prop="Name" />
<el-table-column label="ID" prop="ID"/>
<el-table-column :label="lang.account" prop="Account"/>
<el-table-column :label="lang.user_name" prop="Name"/>
<el-table-column :label="lang.disabled" prop="Disabled">
<template #default="scope">
<span>{{ scope.row.Disabled == 1 ? lang.disabled : lang.enabled }}</span>
<span>{{ scope.row.Disabled === 1 ? lang.disabled : lang.enabled }}</span>
</template>
</el-table-column>
<el-table-column align="right">
@ -24,30 +24,29 @@
</el-table>
<div id="paginationBox">
<el-pagination v-model:current-page="currentPage" small background layout="prev, pager, next"
:page-count="totalPage" class="mt-4" @current-change="reflushList" />
:page-count="totalPage" class="mt-4" @current-change="reflushList"/>
</div>
<el-dialog v-model="userInfoDialog" :title="title" width="500">
<el-form>
<el-form-item label-width="100px" :label="lang.account">
<el-input :disabled="editModel == 'edit'" v-model="editUserInfo.account" autocomplete="off" />
<el-input :disabled="editModel === 'edit'" v-model="editUserInfo.account" autocomplete="off"/>
</el-form-item>
<el-form-item label-width="100px" :label="lang.user_name">
<el-input v-model="editUserInfo.name" autocomplete="off" />
<el-input v-model="editUserInfo.name" autocomplete="off"/>
</el-form-item>
<el-form-item label-width="100px" :label="lang.password">
<el-input :placeholder="lang.resetPwd" v-model="editUserInfo.password" autocomplete="off" />
<el-input :placeholder="lang.resetPwd" v-model="editUserInfo.password" autocomplete="off"/>
</el-form-item>
<div style="display: flex;">
<div
style="display: inline-flex;justify-content: flex-end;align-items: flex-start;flex: 0 0 auto;font-size: var(--el-form-label-font-size); height: 32px;line-height: 32px;padding: 0 12px 0 60px;box-sizing: border-box; ">
style="display: inline-flex;justify-content: flex-end;align-items: flex-start;flex: 0 0 auto;font-size: var(--el-form-label-font-size); height: 32px;line-height: 32px;padding: 0 12px 0 60px;box-sizing: border-box; ">
<el-switch v-model="editUserInfo.disabled" class="ml-2" :active-text="lang.disabled"
:inactive-text="lang.enabled" />
:inactive-text="lang.enabled"/>
</div>
@ -70,11 +69,12 @@
<script setup>
import { reactive, ref, getCurrentInstance } from 'vue'
import {reactive, ref} from 'vue'
import lang from '../i18n/i18n';
import {http} from "@/utils/axios";
import {ElNotification} from "element-plus";
const userList = reactive([])
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const currentPage = ref(1)
const totalPage = ref(1)
const userInfoDialog = ref(false)
@ -88,7 +88,7 @@ const editUserInfo = reactive({
const title = ref(lang.editUser)
const reflushList = function () {
$http.post('/api/user/list', { "current_page": currentPage.value, "page_size": 10 }).then(res => {
http.post('/api/user/list', {"current_page": currentPage.value, "page_size": 10}).then(res => {
userList.length = 0
totalPage.value = res.data.total_page
@ -100,14 +100,14 @@ const reflushList = function () {
const handleEdit = function (idx, row) {
editUserInfo.account = row.Account
editUserInfo.name = row.Name
editUserInfo.disabled = row.Disabled == 1
editUserInfo.disabled = row.Disabled === 1
editUserInfo.password = ""
editModel.value = "edit"
title.value = lang.editUser
userInfoDialog.value = true
}
const createUser = function(){
const createUser = function () {
editUserInfo.account = ""
editUserInfo.name = ""
editUserInfo.disabled = false
@ -119,50 +119,48 @@ const createUser = function(){
const submit = function () {
if (editModel.value == 'edit') {
if (editModel.value === 'edit') {
let newData = {
"account": editUserInfo.account,
"username": editUserInfo.name,
"disabled": editUserInfo.disabled ? 1 : 0
}
if (editUserInfo.password != "") {
if (editUserInfo.password !== "") {
newData["password"] = editUserInfo.password
}
$http.post('/api/user/edit', newData).then(res => {
http.post('/api/user/edit', newData).then(res => {
ElNotification({
title: res.errorNo == 0 ? lang.succ : lang.fail,
message: res.errorNo == 0 ? "" : res.data,
type: res.errorNo == 0 ? 'success' : 'error',
title: res.errorNo === 0 ? lang.succ : lang.fail,
message: res.errorNo === 0 ? "" : res.data,
type: res.errorNo === 0 ? 'success' : 'error',
})
if (res.errorNo == 0) {
if (res.errorNo === 0) {
reflushList()
userInfoDialog.value = false
}
})
}else{
} else {
let newData = {
"account": editUserInfo.account,
"username": editUserInfo.name,
"disabled": editUserInfo.disabled ? 1 : 0,
"password":editUserInfo.password
"password": editUserInfo.password
}
$http.post('/api/user/create', newData).then(res => {
http.post('/api/user/create', newData).then(res => {
ElNotification({
title: res.errorNo == 0 ? lang.succ : lang.fail,
message: res.errorNo == 0 ? "" : res.data,
type: res.errorNo == 0 ? 'success' : 'error',
title: res.errorNo === 0 ? lang.succ : lang.fail,
message: res.errorNo === 0 ? "" : res.data,
type: res.errorNo === 0 ? 'success' : 'error',
})
if (res.errorNo == 0) {
if (res.errorNo === 0) {
reflushList()
userInfoDialog.value = false
}
})
}
}

View File

@ -1,4 +1,4 @@
var lang = {
let lang = {
"logout": "Logout",
"resetPwd": "Reset the account password",
"disabled": "Disabled",
@ -71,11 +71,11 @@ var lang = {
"ssl_auto": "Automatically configure SSL certificates (recommended)",
"wait_desc": "Please Wait.",
"dns_challenge_wait": "DNS propagation and cache refreshes take a long time, and a wait of 10-30 minutes is possible here.",
"ssl_challenge_type":"Challenge Type",
"ssl_auto_http":"Http Request",
"ssl_auto_dns":"DNS Records",
"ssl_challenge_type": "Challenge Type",
"ssl_auto_http": "Http Request",
"ssl_auto_dns": "DNS Records",
"challenge_typ_desc": "If PMail uses port 80 directly, it is recommended that you use the HTTP challenge method.",
"oomain_service_provider":"Domain Name Service Provider",
"oomain_service_provider": "Domain Name Service Provider",
"ssl_manuallyf": "Manually configure an SSL certificate",
"ssl_key_path": "ssl key file path",
"ssl_crt_path": "ssl crt file path",
@ -86,32 +86,31 @@ var lang = {
"del_btn": "Delete",
"move_btn": "Move",
"read_btn": "Readed",
"dangerous":"The content of this email is not secure!",
"rule_setting":"Rules",
"new_rule":"New mail receiving rules",
"rule_name":"Rule Name",
"rule_priority":"Rule Priority(Larger values are executed first)",
"rule_desc":"When a new message arrives that meets all these conditions:",
"rule_do":"Do the following:",
"from":"From Email Address",
"subject":"Email Subject",
"content":"Email Content",
"equal":"Equal",
"regex":"Regex Match",
"contains":"Contains",
"mark_read":"Mark Read",
"delete":"Delete",
"forward":"Forward",
"move":"Move to group",
"del_rule_confirm":"Are you sure to delete this?",
"rule_params":"Executed params",
"dangerous": "The content of this email is not secure!",
"rule_setting": "Rules",
"new_rule": "New mail receiving rules",
"rule_name": "Rule Name",
"rule_priority": "Rule Priority(Larger values are executed first)",
"rule_desc": "When a new message arrives that meets all these conditions:",
"rule_do": "Do the following:",
"from": "From Email Address",
"subject": "Email Subject",
"content": "Email Content",
"equal": "Equal",
"regex": "Regex Match",
"contains": "Contains",
"mark_read": "Mark Read",
"delete": "Delete",
"forward": "Forward",
"move": "Move to group",
"del_rule_confirm": "Are you sure to delete this?",
"rule_params": "Executed params",
"autoSSLWarn": "PMail is not currently running on port 80. If you want PMail to manage SSL certificates automatically, please forward the /.well-known/* route to PMail. See https://github.com/Jinnrry/PMail/issues/94 for details.",
"err_db_dsn_empty": "Database path cannot be empty!",
};
var zhCN = {
const zhCN = {
"logout": "注销",
"resetPwd": "重置账号密码",
"disabled": "禁用",
@ -182,10 +181,10 @@ var zhCN = {
"web_domain": "Web域名地址",
"dns_desc": "请将以下信息添加到DNS记录中",
"ssl_auto": "自动配置SSL证书(推荐)",
"oomain_service_provider":"域名服务商",
"ssl_auto_http":"HTTP请求",
"ssl_auto_dns":"DNS记录",
"ssl_challenge_type":"验证方式",
"oomain_service_provider": "域名服务商",
"ssl_auto_http": "HTTP请求",
"ssl_auto_dns": "DNS记录",
"ssl_challenge_type": "验证方式",
"ssl_manuallyf": "手动配置SSL证书",
"challenge_typ_desc": "如果PMail直接使用80端口建议使用HTTP验证方式。",
"wait_desc": "请稍等",
@ -199,38 +198,29 @@ var zhCN = {
"del_btn": "删除",
"move_btn": "移动",
"read_btn": "已读",
"dangerous":"该邮件内容不安全!",
"rule_setting":"规则",
"new_rule":"新建收信规则",
"rule_name":"规则名称",
"rule_priority":"优先级(值越大越先执行)",
"rule_desc":"以下规则全部满足时:",
"rule_do":"执行操作:",
"from":"发件人地址",
"subject":"邮件主题",
"content":"邮件内容",
"equal":"等于",
"regex":"正则匹配",
"contains":"包含",
"mark_read":"标记已读",
"delete":"删除",
"forward":"转发",
"move":"移动分组",
"del_rule_confirm":"确定要删除吗?",
"rule_params":"执行参数",
"dangerous": "该邮件内容不安全!",
"rule_setting": "规则",
"new_rule": "新建收信规则",
"rule_name": "规则名称",
"rule_priority": "优先级(值越大越先执行)",
"rule_desc": "以下规则全部满足时:",
"rule_do": "执行操作:",
"from": "发件人地址",
"subject": "邮件主题",
"content": "邮件内容",
"equal": "等于",
"regex": "正则匹配",
"contains": "包含",
"mark_read": "标记已读",
"delete": "删除",
"forward": "转发",
"move": "移动分组",
"del_rule_confirm": "确定要删除吗?",
"rule_params": "执行参数",
"autoSSLWarn": "PMail当前未使用80端口启动如果想要PMail自动管理SSL证书请将/.well-known/*路由转发到PMail。 详见https://github.com/Jinnrry/PMail/issues/94",
"err_db_dsn_empty": "数据库路径不能为空!",
}
switch (navigator.language) {
case "zh":
lang = zhCN
break
case "zh-CN":
lang = zhCN
break
default:
break
}
if (navigator.language === "zh-CN" || navigator.language === "zh") lang = zhCN
export default lang;

View File

@ -3,119 +3,10 @@ import 'element-plus/dist/index.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import {ref} from 'vue'
import App from './App.vue'
import router from './router'
import {router} from './router'
const app = createApp(App)
app.config.globalProperties.$isLogin = ref(true)
app.config.globalProperties.$userInfos = ref({})
app.use(createPinia())
app.use(router)
import axios from 'axios'
import lang from './i18n/i18n';
//创建axios的一个实例
var $http = axios.create({
baseURL: import.meta.env.VITE_APP_URL, //接口统一域名
timeout: 60000, //设置超时
headers: {
'Content-Type': 'application/json;charset=UTF-8;',
'Lang': lang.lang
}
})
//请求拦截器
$http.interceptors.request.use((config) => {
//若请求方式为post则将data参数转为JSON字符串
if (config.method === 'POST') {
config.data = JSON.stringify(config.data);
}
return config;
}, (error) =>
// 对请求错误做些什么
Promise.reject(error));
//响应拦截器
$http.interceptors.response.use((response) => {
//响应成功
if (response.data.errorNo == 403) {
app.config.globalProperties.$isLogin.value = false
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
}
//响应成功
if (response.data.errorNo == 402) {
router.replace({
path: '/setup',
query: {
redirect: router.currentRoute.fullPath
}
})
}
return response.data;
}, (error) => {
//响应错误
if (error.response && error.response.status) {
const status = error.response.status
let message = ""
switch (status) {
case 400:
message = '请求错误';
break;
case 401:
message = '请求错误';
break;
case 403:
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
break;
case 404:
message = '请求地址出错';
break;
case 408:
message = '请求超时';
break;
case 500:
message = '服务器内部错误!';
break;
case 501:
message = '服务未实现!';
break;
case 502:
message = '网关错误!';
break;
case 503:
message = '服务不可用!';
break;
case 504:
message = '网关超时!';
break;
case 505:
message = 'HTTP版本不受支持';
break;
default:
message = '请求失败'
}
return Promise.reject(error);
}
return Promise.reject(error);
});
// 注册到全局
app.config.globalProperties.$http = $http
app.use(createPinia())
app.mount('#app')

View File

@ -46,4 +46,4 @@ const router = createRouter({
export default router
export {router};

View File

@ -1,4 +1,4 @@
import { ref, computed } from 'vue'
import { ref } from 'vue'
import { defineStore } from 'pinia'
import lang from '../i18n/i18n';

View File

@ -0,0 +1,15 @@
import {defineStore} from "pinia";
const useGlobalStatusStore = defineStore('useGlobalStatusStore', {
state() {
return {
isLogin: true,
userInfos:{}
}
},
getters: {},
actions: {}
})
export {useGlobalStatusStore};

103
fe/src/utils/axios.js Normal file
View File

@ -0,0 +1,103 @@
import axios from 'axios'
import lang from '../i18n/i18n';
import {useGlobalStatusStore} from "@/stores/useGlobalStatusStore";
import {router} from "@/router";
//创建axios的一个实例
const http = axios.create({
baseURL: import.meta.env.VITE_APP_URL, //接口统一域名
timeout: 60000, //设置超时
headers: {
'Content-Type': 'application/json;charset=UTF-8;',
'Lang': lang.lang
}
});
//请求拦截器
http.interceptors.request.use((config) => {
//若请求方式为post则将data参数转为JSON字符串
if (config.method === 'POST') {
config.data = JSON.stringify(config.data);
}
return config;
}, (error) =>
// 对请求错误做些什么
Promise.reject(error));
//响应拦截器
http.interceptors.response.use(async (response) => {
const globalStatus = useGlobalStatusStore();
//响应成功
if (response.data.errorNo === 403) {
globalStatus.isLogin = false
await router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
});
}
//响应成功
if (response.data.errorNo === 402) {
await router.replace({
path: '/setup',
query: {
redirect: router.currentRoute.fullPath
}
});
}
return response.data;
}, async (error) => {
//响应错误
if (error.response && error.response.status) {
let message = ""
switch (error.response.status) {
case 400:
message = '请求错误';
break;
case 401:
message = '请求错误';
break;
case 403:
await router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
});
break;
case 404:
message = '请求地址出错';
break;
case 408:
message = '请求超时';
break;
case 500:
message = '服务器内部错误!';
break;
case 501:
message = '服务未实现!';
break;
case 502:
message = '网关错误!';
break;
case 503:
message = '服务不可用!';
break;
case 504:
message = '网关超时!';
break;
case 505:
message = 'HTTP版本不受支持';
break;
default:
// eslint-disable-next-line no-unused-vars
message = '请求失败';
}
return Promise.reject(error);
}
return Promise.reject(error);
});
export {http};

View File

@ -1,247 +1,243 @@
<template>
<div id="main">
<el-form label-width="100px" :rules="rules" ref="ruleFormRef" :model="ruleForm" status-icon>
<div id="main">
<el-form label-width="100px" :rules="rules" ref="ruleFormRef" :model="ruleForm" status-icon>
<el-form-item :label="lang.sender" prop="sender">
<el-popover trigger="click" :width="600">
<template #reference>
<div
style="border: 1px solid #dcdfe6; border-radius:3px;height: 30px; line-height: 30px; padding: 0 5px 0 5px;">
<span style="font-size: 16px; font-weight: bolder;">{{ ruleForm.nickName }}</span>
<span> &lt;{{ ruleForm.sender }}@{{ ruleForm.pickDomain }}&gt;</span>
</div>
</template>
<template #default>
<div style="display: flex; flex-direction:column;">
<div style=" margin-bottom: 10px;">
<el-form-item :label="lang.sender" prop="sender">
<el-input style="max-width: 200px" :disabled="!$userInfos.is_admin"
v-model="ruleForm.sender" :placeholder="lang.sender_desc" />
<div>@</div>
<el-select v-model="ruleForm.pickDomain">
<el-option :value="item" v-for="item in ruleForm.domains">{{ item }}</el-option>
</el-select>
</el-form-item>
</div>
<div>
<el-form-item :label="lang.nick_name" >
<el-input style="max-width: 300px" v-model="ruleForm.nickName" />
</el-form-item>
</div>
</div>
</template>
</el-popover>
</el-form-item>
<el-form-item :label="lang.to" prop="receivers">
<el-select v-model="ruleForm.receivers" style="width: 100%;" multiple filterable allow-create
:reserve-keyword="false" :placeholder="lang.to_desc"></el-select>
</el-form-item>
<el-form-item :label="lang.cc" prop="cc">
<el-select v-model="ruleForm.cc" style="width: 100%;" multiple filterable allow-create
:reserve-keyword="false" :placeholder="lang.cc_desc"></el-select>
</el-form-item>
<el-form-item :label="lang.title" prop="subject">
<el-input v-model="ruleForm.subject" :placeholder="lang.title"></el-input>
</el-form-item>
<div id="editor">
<div style="border: 1px solid #ccc">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig"
:mode="mode" />
<Editor style="height: 300px;" v-model="valueHtml" :defaultConfig="editorConfig" :mode="mode"
@onCreated="handleCreated" />
</div>
<el-form-item :label="lang.sender" prop="sender">
<el-popover trigger="click" :width="600">
<template #reference>
<div
style="border: 1px solid #dcdfe6; border-radius:3px;height: 30px; line-height: 30px; padding: 0 5px 0 5px;">
<span style="font-size: 16px; font-weight: bolder;">{{ ruleForm.nickName }}</span>
<span> &lt;{{ ruleForm.sender }}@{{ ruleForm.pickDomain }}&gt;</span>
</div>
</template>
<template #default>
<div style="display: flex; flex-direction:column;">
<div style=" margin-bottom: 10px;">
<el-form-item :label="lang.sender" prop="sender">
<el-input style="max-width: 200px" :disabled="!userInfos.is_admin"
v-model="ruleForm.sender" :placeholder="lang.sender_desc"/>
<div>@</div>
<el-select v-model="ruleForm.pickDomain">
<el-option :value="item" v-for="item in ruleForm.domains" :key="item">{{ item }}</el-option>
</el-select>
</el-form-item>
</div>
<div>
<el-form-item :label="lang.nick_name">
<el-input style="max-width: 300px" v-model="ruleForm.nickName"/>
</el-form-item>
</div>
<div id="fileList">
<ol>
<li v-for="(item, index) in fileList">{{ item.name }} <el-icon @click="delFile(index)">
<Close />
</el-icon> </li>
</ol>
</div>
</template>
</el-popover>
<div id="sendButton">
<el-button type="primary" @click="send(ruleFormRef)">{{ lang.send }}</el-button>
<!-- <el-button>定时发送</el-button> -->
<div style="margin-left: 15px">
<el-button @click="upload">{{ lang.add_att }}</el-button>
<input v-show="false" ref="fileRef" type="file" @change="fileChange">
</div>
</div>
</el-form-item>
<el-form-item :label="lang.to" prop="receivers">
<el-select v-model="ruleForm.receivers" style="width: 100%;" multiple filterable allow-create
:reserve-keyword="false" :placeholder="lang.to_desc"></el-select>
</el-form-item>
</el-form>
</div>
<el-form-item :label="lang.cc" prop="cc">
<el-select v-model="ruleForm.cc" style="width: 100%;" multiple filterable allow-create
:reserve-keyword="false" :placeholder="lang.cc_desc"></el-select>
</el-form-item>
<el-form-item :label="lang.title" prop="subject">
<el-input v-model="ruleForm.subject" :placeholder="lang.title"></el-input>
</el-form-item>
<div id="editor">
<div style="border: 1px solid #ccc">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig"
:mode="mode"/>
<Editor style="height: 300px;" v-model="valueHtml" :defaultConfig="editorConfig" :mode="mode"
@onCreated="handleCreated"/>
</div>
</div>
<div id="fileList">
<ol>
<li v-for="(item, index) in fileList" :key="item">{{ item.name }}
<el-icon @click="delFile(index)">
<Close/>
</el-icon>
</li>
</ol>
</div>
<div id="sendButton">
<el-button type="primary" @click="send(ruleFormRef)">{{ lang.send }}</el-button>
<!-- <el-button>定时发送</el-button> -->
<div style="margin-left: 15px">
<el-button @click="upload">{{ lang.add_att }}</el-button>
<input v-show="false" ref="fileRef" type="file" @change="fileChange">
</div>
</div>
</el-form>
</div>
</template>
<style scoped>
#main {
text-align: left;
padding-right: 20px;
text-align: left;
padding-right: 20px;
}
#editor {
padding-left: 25px;
padding-left: 25px;
}
#sendButton {
padding-left: 25px;
padding-top: 5px;
display: flex;
padding-left: 25px;
padding-top: 5px;
display: flex;
}
</style>
<script setup>
import '@wangeditor/editor/dist/css/style.css' // css
import { ElMessage } from 'element-plus'
import { onBeforeUnmount, ref, shallowRef, reactive, onMounted } from 'vue'
import { Close } from '@element-plus/icons-vue';
import {ElMessage} from 'element-plus'
import {onBeforeUnmount, reactive, ref, shallowRef} from 'vue'
import {Close} from '@element-plus/icons-vue';
import lang from '../i18n/i18n';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { i18nChangeLanguage } from '@wangeditor/editor'
import { useRouter } from 'vue-router';
const router = useRouter();
import {Editor, Toolbar} from '@wangeditor/editor-for-vue'
import {i18nChangeLanguage} from '@wangeditor/editor'
import {useRouter} from 'vue-router';
import {http} from "@/utils/axios";
import useGroupStore from '../stores/group'
import {useGlobalStatusStore} from "@/stores/useGlobalStatusStore";
const router = useRouter();
const groupStore = useGroupStore()
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const $isLogin = app.appContext.config.globalProperties.$isLogin
const $userInfos = app.appContext.config.globalProperties.$userInfos
if (lang.lang == "zhCn") {
i18nChangeLanguage('zh-CN')
const globalStatus = useGlobalStatusStore();
const userInfos = globalStatus.userInfos
if (lang.lang === "zhCn") {
i18nChangeLanguage('zh-CN')
} else {
i18nChangeLanguage('en')
i18nChangeLanguage('en')
}
// HTML
const valueHtml = ref('<p>hello</p>')
const toolbarConfig = {}
const editorConfig = {
MENU_CONF: {},
placeholder: ''
MENU_CONF: {},
placeholder: ''
}
editorConfig.MENU_CONF['uploadImage'] = {
base64LimitSize: 100 * 1024 * 1024 * 1024, // 100Gbase64
base64LimitSize: 100 * 1024 * 1024 * 1024, // 100Gbase64
}
const mode = ref()
const fileRef = ref();
const pickFile = ref();
const ruleFormRef = ref()
const ruleForm = reactive({
nickName: '',
sender: '',
receivers: '',
cc: '',
subject: '',
domains: [],
pickDomain: ""
nickName: '',
sender: '',
receivers: '',
cc: '',
subject: '',
domains: [],
pickDomain: ""
})
const fileList = reactive([]);
const init = function () {
if (Object.keys($userInfos.value).length == 0) {
$http.post("/api/user/info", {}).then(res => {
if (res.errorNo == 0) {
$userInfos.value = res.data
ruleForm.sender = res.data.account
ruleForm.domains = res.data.domains
ruleForm.pickDomain = res.data.domains[0]
ruleForm.nickName = res.data.name
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
if (Object.keys(userInfos.value).length === 0) {
http.post("/api/user/info", {}).then(res => {
if (res.errorNo === 0) {
userInfos.value = res.data
ruleForm.sender = res.data.account
ruleForm.domains = res.data.domains
ruleForm.pickDomain = res.data.domains[0]
ruleForm.nickName = res.data.name
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
} else {
ruleForm.sender = $userInfos.value.account
ruleForm.domains = $userInfos.value.domains
ruleForm.pickDomain = $userInfos.value.domains[0]
ruleForm.nickName = $userInfos.value.name
}
}
})
} else {
ruleForm.sender = userInfos.value.account
ruleForm.domains = userInfos.value.domains
ruleForm.pickDomain = userInfos.value.domains[0]
ruleForm.nickName = userInfos.value.name
}
}
init()
const validateSender = function (rule, value, callback) {
if (typeof ruleForm.sender === "undefined" || ruleForm.sender === null || ruleForm.sender.trim() === "") {
callback(new Error(lang.err_sender_must))
} else if (ruleForm.sender.includes("@")) {
callback(new Error(lang.only_prefix))
} else {
callback()
}
if (typeof ruleForm.sender === "undefined" || ruleForm.sender === null || ruleForm.sender.trim() === "") {
callback(new Error(lang.err_sender_must))
} else if (ruleForm.sender.includes("@")) {
callback(new Error(lang.only_prefix))
} else {
callback()
}
}
const checkEmail = function (str) {
var re = /.+@.+\..+/
if (re.test(str)) {
return true
} else {
return false
}
const re = /.+@.+\..+/;
return re.test(str);
}
const validateReceivers = function (rule, value, callback) {
for (let index = 0; index < ruleForm.receivers.length; index++) {
let element = ruleForm.receivers[index];
if (!checkEmail(element)) {
callback(new Error(lang.err_email_format))
return
}
for (let index = 0; index < ruleForm.receivers.length; index++) {
let element = ruleForm.receivers[index];
if (!checkEmail(element)) {
callback(new Error(lang.err_email_format))
return
}
callback()
}
callback()
}
const validateCc = function (rule, value, callback) {
for (let index = 0; index < ruleForm.cc.length; index++) {
let element = ruleForm.cc[index];
if (!checkEmail(element)) {
callback(new Error(err_email_format))
return
}
for (let index = 0; index < ruleForm.cc.length; index++) {
let element = ruleForm.cc[index];
if (!checkEmail(element)) {
callback(new Error(lang.err_email_format))
return
}
callback()
}
callback()
}
const rules = reactive({
sender: [
{ validator: validateSender, trigger: 'change' }
],
receivers: [
{ validator: validateReceivers, trigger: 'change' }
],
cc: [
{ validator: validateCc, trigger: 'change' }
],
subject: [
{ required: true, message: lang.err_title_must, trigger: 'change' },
],
sender: [
{validator: validateSender, trigger: 'change'}
],
receivers: [
{validator: validateReceivers, trigger: 'change'}
],
cc: [
{validator: validateCc, trigger: 'change'}
],
subject: [
{required: true, message: lang.err_title_must, trigger: 'change'},
],
})
@ -249,97 +245,93 @@ const rules = reactive({
const editorRef = shallowRef()
//
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor) => {
editorRef.value = editor // editor
editorRef.value = editor // editor
}
const send = function (formEl) {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
let objectTos = []
for (let index = 0; index < ruleForm.receivers.length; index++) {
let element = ruleForm.receivers[index];
objectTos.push({
name: "",
email: element
})
}
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
let objectTos = []
for (let index = 0; index < ruleForm.receivers.length; index++) {
let element = ruleForm.receivers[index];
objectTos.push({
name: "",
email: element
})
}
let objectCcs = []
for (let index = 0; index < ruleForm.cc.length; index++) {
let element = ruleForm.cc[index];
objectCcs.push({
name: "",
email: element
})
}
let objectCcs = []
for (let index = 0; index < ruleForm.cc.length; index++) {
let element = ruleForm.cc[index];
objectCcs.push({
name: "",
email: element
})
}
let text = editorRef.value.getText()
let text = editorRef.value.getText()
$http.post("/api/email/send", {
from: { name: ruleForm.nickName, email: ruleForm.sender + "@" + ruleForm.pickDomain },
to: objectTos,
cc: objectCcs,
subject: ruleForm.subject,
text: text,
html: valueHtml.value,
attrs: fileList
}).then(res => {
if (res.errorNo === 0) {
ElMessage({
message: lang.succ_send,
type: 'success',
})
groupStore.name = lang.outbox
groupStore.tag = '{"type":1,"status":-1}'
router.replace({
name: 'list',
})
} else {
ElMessage.error(res.data)
}
})
http.post("/api/email/send", {
from: {name: ruleForm.nickName, email: ruleForm.sender + "@" + ruleForm.pickDomain},
to: objectTos,
cc: objectCcs,
subject: ruleForm.subject,
text: text,
html: valueHtml.value,
attrs: fileList
}).then(res => {
if (res.errorNo === 0) {
ElMessage({
message: lang.succ_send,
type: 'success',
})
groupStore.name = lang.outbox
groupStore.tag = '{"type":1,"status":-1}'
router.replace({
name: 'list',
})
} else {
return false
ElMessage.error(res.data)
}
})
})
} else {
return false
}
})
}
const upload = function () {
fileRef.value.dispatchEvent(new MouseEvent('click'))
fileRef.value.dispatchEvent(new MouseEvent('click'))
}
const fileChange = function (e) {
let files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
for (let i = 0; i < files.length; i++) {
const reader = new FileReader();
reader.onload = function fileReadCompleted() {
fileList.push({
name: files[i].name,
data: this.result
})
};
reader.readAsDataURL(files[i]);
let files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
for (let i = 0; i < files.length; i++) {
const reader = new FileReader();
reader.onload = function fileReadCompleted() {
fileList.push({
name: files[i].name,
data: this.result
})
};
reader.readAsDataURL(files[i]);
}
}
}
const delFile = function (index) {
fileList.splice(index, 1);
fileList.splice(index, 1);
}
</script>

View File

@ -1,117 +1,118 @@
<template>
<div id="main">
<div id="title">{{ detailData.subject }}</div>
<el-divider />
<div id="main">
<div id="title">{{ detailData.subject }}</div>
<el-divider/>
<div>
<div>{{ lang.to }}
<el-tooltip v-for="to in tos" class="box-item" effect="dark" :content="to.EmailAddress" placement="top">
<el-tag size="small" type="info">{{to.Name != '' ? to.Name : to.EmailAddress }}</el-tag>
</el-tooltip>
</div>
<div>
<div>{{ lang.to }}
<el-tooltip v-for="to in tos" :key.prop="to" class="box-item" effect="dark" :content="to.EmailAddress" placement="top">
<el-tag size="small" type="info">{{ to.Name !== '' ? to.Name : to.EmailAddress }}</el-tag>
</el-tooltip>
</div>
<div v-if="showCC">{{ lang.cc }}
<el-tooltip v-for="item in ccs" class="box-item" effect="dark" :content="item.EmailAddress" placement="top">
<el-tag size="small" type="info">{{item.Name != '' ? item.Name : item.EmailAddress }}</el-tag>
</el-tooltip>
</div>
<div v-if="showCC">{{ lang.cc }}
<el-tooltip v-for="item in ccs" :key="item" class="box-item" effect="dark" :content="item.EmailAddress" placement="top">
<el-tag size="small" type="info">{{ item.Name !== '' ? item.Name : item.EmailAddress }}</el-tag>
</el-tooltip>
</div>
<div>{{ lang.sender }}
<el-tooltip class="box-item" effect="dark" :content="detailData.from_address" placement="top">
<el-tag size="small" type="info">{{detailData.from_name != '' ? detailData.from_name : detailData.from_address }}</el-tag>
</el-tooltip>
</div>
<div>{{ lang.sender }}
<el-tooltip class="box-item" effect="dark" :content="detailData.from_address" placement="top">
<el-tag size="small" type="info">
{{ detailData.from_name !== '' ? detailData.from_name : detailData.from_address }}
</el-tag>
</el-tooltip>
</div>
<div>{{ lang.date }}
{{ detailData.send_date }}
</div>
</div>
<el-divider />
<div class="content" id="text" v-if="detailData.html == ''">
{{ detailData.text }}
</div>
<div class="content" id="html" v-else v-html="detailData.html">
</div>
<div v-if="detailData.attachments.length > 0" style="">
<el-divider />
{{ lang.attachment }}
<a class="att" v-for="item in detailData.attachments"
:href="'/attachments/download/' + detailData.id + '/' + item.Index"> <el-icon>
<Document />
</el-icon> {{ item.Filename }} </a>
</div>
<div>{{ lang.date }}
{{ detailData.send_date }}
</div>
</div>
<el-divider/>
<div class="content" id="text" v-if="detailData.html === ''">
{{ detailData.text }}
</div>
<div class="content" id="html" v-else v-html="detailData.html">
</div>
<div v-if="detailData.attachments.length > 0" style="">
<el-divider/>
{{ lang.attachment }}
<a class="att" v-for="item in detailData.attachments" :key="item"
:href="'/attachments/download/' + detailData.id + '/' + item.Index">
<el-icon>
<Document/>
</el-icon>
{{ item.Filename }} </a>
</div>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { useRoute } from 'vue-router'
import { Document } from '@element-plus/icons-vue';
import {ref} from 'vue'
import {useRoute} from 'vue-router'
import {Document} from '@element-plus/icons-vue';
import lang from '../i18n/i18n';
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
import {http} from "@/utils/axios";
const route = useRoute()
const detailData = ref({
attachments:[]
attachments: []
})
const tos = ref()
const ccs = ref()
const showCC = ref(false)
$http.post("/api/email/detail", { id: parseInt(route.params.id) }).then(res => {
detailData.value = res.data
if (res.data.to != "" && res.data.to != null) {
tos.value = JSON.parse(res.data.to)
}
if (res.data.cc != "" && res.data.cc != null) {
ccs.value = JSON.parse(res.data.cc)
http.post("/api/email/detail", {id: parseInt(route.params.id)}).then(res => {
detailData.value = res.data
if (res.data.to !== "" && res.data.to != null) {
tos.value = JSON.parse(res.data.to)
}
if (res.data.cc !== "" && res.data.cc != null) {
ccs.value = JSON.parse(res.data.cc)
}
}
if (ccs.value != null && ccs.value != undefined){
showCC.value = ccs.value.length > 0
}else{
showCC.value = false
}
if (ccs.value != null) {
showCC.value = ccs.value.length > 0
} else {
showCC.value = false
}
})
</script>
<style scoped>
#main {
display: flex;
padding-left: 20px;
padding-right: 80px;
text-align: left;
display: flex;
padding-left: 20px;
padding-right: 80px;
text-align: left;
}
#title {
font-size: 40px;
text-align: left;
font-size: 40px;
text-align: left;
}
#userItem {}
#userItem {
}
.content {
/* background-color: aliceblue; */
/* background-color: aliceblue; */
}
a,a:link,a:visited,a:hover,a:active{
text-decoration: none;
color:inherit;
a, a:link, a:visited, a:hover, a:active {
text-decoration: none;
color: inherit;
}
.att{
display:block;
.att {
display: block;
}
</style>

View File

@ -1,269 +1,262 @@
<template>
<div style="height: 100%">
<div id="operation">
<div id="action">
<RouterLink to="/editer">+{{ lang.compose }}</RouterLink>
</div>
</div>
<div id="title">{{ groupStore.name }}</div>
<div id="action">
<el-button @click="del" size="small">{{ lang.del_btn }}</el-button>
<el-button @click="markRead" size="small">{{ lang.read_btn }}</el-button>
<el-dropdown style="margin-left: 12px;">
<el-button size="small">
{{ lang.move_btn }}
<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="move(group.id)" v-for="group in groupList">{{ group.name
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div id="table">
<el-table ref="taskTableDataRef" @selection-change="selectionLineChange" :data="data" :show-header="true"
<div style="height: 100%">
<div id="operation">
<div id="action">
<RouterLink to="/editer">+{{ lang.compose }}</RouterLink>
</div>
</div>
<div id="title">{{ groupStore.name }}</div>
<div id="action">
<el-button @click="del" size="small">{{ lang.del_btn }}</el-button>
<el-button @click="markRead" size="small">{{ lang.read_btn }}</el-button>
<el-dropdown style="margin-left: 12px;">
<el-button size="small">
{{ lang.move_btn }}
<el-icon class="el-icon--right">
<EpArrowDownBold />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="move(group.id)" v-for="group in groupList" :key="group.id">{{
group.name
}}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div id="table">
<el-table ref="taskTableDataRef" :data="data" :show-header="true"
:border="false" @row-click="rowClick" :row-style="rowStyle">
<el-table-column type="selection" width="30" />
<el-table-column prop="is_read" label="" width="50">
<template #default="scope">
<div>
<el-table-column type="selection" width="30"/>
<el-table-column prop="is_read" label="" width="50">
<template #default="scope">
<div>
<span v-if="!scope.row.is_read">
{{ lang.new }}
</span>
<span style="font-weight: 900;color: #FF0000;" v-if="scope.row.dangerous">
<span style="font-weight: 900;color: #FF0000;" v-if="scope.row.dangerous">
<el-tooltip effect="dark" :content="lang.dangerous" placement="top-start">
!
</el-tooltip>
</span>
<span style="font-weight: 900;color: #FF0000;" v-if="scope.row.error != ''">
<span style="font-weight: 900;color: #FF0000;" v-if="scope.row.error !== ''">
<el-tooltip effect="dark" :content="scope.row.error" placement="top-start">
!
</el-tooltip>
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="title" :label="lang.sender" width="150">
<template #default="scope">
<el-tooltip class="box-item" effect="dark" :content="scope.row.sender.EmailAddress" placement="top">
<el-tag size="small" type="info">{{scope.row.sender.Name != '' ? scope.row.sender.Name : scope.row.sender.EmailAddress }}</el-tag>
</el-tooltip>
</template>
</el-table-column>
</div>
</template>
</el-table-column>
<el-table-column prop="title" :label="lang.sender" width="150">
<template #default="scope">
<el-tooltip class="box-item" effect="dark" :content="scope.row.sender.EmailAddress" placement="top">
<el-tag size="small" type="info">
{{ scope.row.sender.Name !== '' ? scope.row.sender.Name : scope.row.sender.EmailAddress }}
</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="title" :label="lang.to" width="150">
<template #default="scope">
<el-tooltip v-for="toInfo in scope.row.to" class="box-item" effect="dark" :content="toInfo.EmailAddress" placement="top">
<el-tag size="small" type="info">{{toInfo.Name != '' ? toInfo.Name : toInfo.EmailAddress }}</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="title" :label="lang.to" width="150">
<template #default="scope">
<el-tooltip v-for="toInfo in scope.row.to" :key="toInfo" class="box-item" effect="dark" :content="toInfo.EmailAddress"
placement="top">
<el-tag size="small" type="info">{{ toInfo.Name !== '' ? toInfo.Name : toInfo.EmailAddress }}</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="desc" :label="lang.title">
<template #default="scope">
<div v-if="scope.row.is_read">{{ scope.row.title }}</div>
<div v-else style="font-weight:bolder;">{{ scope.row.title }}</div>
<el-table-column prop="desc" :label="lang.title">
<template #default="scope">
<div v-if="scope.row.is_read">{{ scope.row.title }}</div>
<div v-else style="font-weight:bolder;">{{ scope.row.title }}</div>
<div style="font-size: 12px;height: 24px;">{{ scope.row.desc }}</div>
<div style="font-size: 12px;height: 24px;">{{ scope.row.desc }}</div>
</template>
</el-table-column>
<el-table-column prop="datetime" :label="lang.date" width="180">
<template #default="scope">
<span v-if="scope.row.is_read">{{ scope.row.datetime }}</span>
<span v-else style="font-weight:bolder;">{{ scope.row.datetime }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div id="pagination">
<el-pagination background layout="prev, pager, next" :page-count="totalPage" @current-change="pageChange" />
</div>
</template>
</el-table-column>
<el-table-column prop="datetime" :label="lang.date" width="180">
<template #default="scope">
<span v-if="scope.row.is_read">{{ scope.row.datetime }}</span>
<span v-else style="font-weight:bolder;">{{ scope.row.datetime }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div id="pagination">
<el-pagination background layout="prev, pager, next" :page-count="totalPage" @current-change="pageChange"/>
</div>
</div>
</template>
<script setup>
import { ArrowDown } from '@element-plus/icons-vue'
import { RouterLink } from 'vue-router'
import { reactive, ref, watch } from 'vue'
import {EpArrowDownBold} from "vue-icons-plus/ep";
import {RouterLink, useRouter} from 'vue-router'
import {ref, watch} from 'vue'
import useGroupStore from '../stores/group'
import lang from '../i18n/i18n';
import { useRouter } from 'vue-router';
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
import {http} from "@/utils/axios";
import {ElMessage, ElMessageBox} from "element-plus";
const router = useRouter();
const groupStore = useGroupStore()
const groupList = ref([])
const taskTableDataRef = ref(null)
let tag = groupStore.tag;
if (tag == "") {
tag = '{"type":0,"status":-1}'
if (tag === "") {
tag = '{"type":0,"status":-1}'
}
watch(groupStore, async (newV, oldV) => {
tag = newV.tag;
if (tag == "") {
tag = '{"type":0,"status":-1}'
}
data.value = []
$http.post("/api/email/list", { tag: tag, page_size: 10 }).then(res => {
data.value = res.data.list
totalPage.value = res.data.total_page
})
watch(groupStore, async (newV) => {
tag = newV.tag;
if (tag === "") {
tag = '{"type":0,"status":-1}'
}
data.value = []
http.post("/api/email/list", {tag: tag, page_size: 10}).then(res => {
data.value = res.data.list
totalPage.value = res.data.total_page
})
})
const data = ref([])
const totalPage = ref(0)
const updateList = function () {
$http.post("/api/email/list", { tag: tag, page_size: 10 }).then(res => {
data.value = res.data.list
totalPage.value = res.data.total_page
})
http.post("/api/email/list", {tag: tag, page_size: 10}).then(res => {
data.value = res.data.list
totalPage.value = res.data.total_page
})
}
const updateGroupList = function () {
$http.post("/api/group/list").then(res => {
groupList.value = res.data
})
http.post("/api/group/list").then(res => {
groupList.value = res.data
})
}
updateList()
updateGroupList()
const rowClick = function (row, column, event) {
router.push("/detail/" + row.id)
const rowClick = function (row) {
router.push("/detail/" + row.id)
}
const markRead = function () {
let rows = taskTableDataRef.value?.getSelectionRows()
let ids = []
rows.forEach(element => {
ids.push(element.id)
});
let rows = taskTableDataRef.value?.getSelectionRows()
let ids = []
rows.forEach(element => {
ids.push(element.id)
});
$http.post("/api/email/read", { "ids": ids }).then(res => {
if (res.errorNo == 0) {
updateList()
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
})
http.post("/api/email/read", {"ids": ids}).then(res => {
if (res.errorNo === 0) {
updateList()
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
})
}
const move = function (group_id) {
let rows = taskTableDataRef.value?.getSelectionRows()
let ids = []
rows.forEach(element => {
ids.push(element.id)
});
let rows = taskTableDataRef.value?.getSelectionRows()
let ids = []
rows.forEach(element => {
ids.push(element.id)
});
ElMessageBox.confirm(
lang.move_email_confirm,
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
$http.post("/api/email/move", { "group_id": group_id, "ids": ids }).then(res => {
if (res.errorNo == 0) {
updateList()
ElMessage({
type: 'success',
message: 'Move completed',
})
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
ElMessageBox.confirm(
lang.move_email_confirm,
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
http.post("/api/email/move", {"group_id": group_id, "ids": ids}).then(res => {
if (res.errorNo === 0) {
updateList()
ElMessage({
type: 'success',
message: 'Move completed',
})
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
})
}
})
}
const del = function () {
let rows = taskTableDataRef.value?.getSelectionRows()
let ids = []
rows.forEach(element => {
ids.push(element.id)
});
let rows = taskTableDataRef.value?.getSelectionRows()
let ids = []
rows.forEach(element => {
ids.push(element.id)
});
let groupTag = JSON.parse(tag)
let groupTag = JSON.parse(tag)
ElMessageBox.confirm(
lang.del_email_confirm,
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
$http.post("/api/email/del", { "ids": ids ,"forcedDel":groupTag.status == 3 }).then(res => {
if (res.errorNo == 0) {
updateList()
ElMessage({
type: 'success',
message: 'Delete completed',
})
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
ElMessageBox.confirm(
lang.del_email_confirm,
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
http.post("/api/email/del", {"ids": ids, "forcedDel": groupTag.status === 3}).then(res => {
if (res.errorNo === 0) {
updateList()
ElMessage({
type: 'success',
message: 'Delete completed',
})
} else {
ElMessage({
type: 'error',
message: res.errorMsg,
})
}
})
})
}
const rowStyle = function ({ row, rowIndwx }) {
return { 'cursor': 'pointer' }
const rowStyle = function () {
return {'cursor': 'pointer'}
}
const pageChange = function (p) {
$http.post("/api/email/list", { tag: tag, page_size: 10, current_page: p }).then(res => {
data.value = res.data.list
})
http.post("/api/email/list", {tag: tag, page_size: 10, current_page: p}).then(res => {
data.value = res.data.list
})
}
</script>
@ -271,41 +264,41 @@ const pageChange = function (p) {
<style scoped>
#action {
display: flex;
flex-direction: row;
display: flex;
flex-direction: row;
}
#action a,
a:visited {
color: #000000;
text-decoration: none;
color: #000000;
text-decoration: none;
}
#operation {
display: flex;
height: 40px;
background-color: rgb(236, 244, 251);
display: flex;
height: 40px;
background-color: rgb(236, 244, 251);
}
#title {
margin-top: 10px;
font-size: 23px;
text-align: left;
padding-left: 20px;
margin-top: 10px;
font-size: 23px;
text-align: left;
padding-left: 20px;
}
#table {
text-align: left;
width: 100%;
padding-left: 20px;
text-align: left;
width: 100%;
padding-left: 20px;
}
#pagination {
padding-top: 30px;
display: flex;
justify-content: center;
/* 水平居中 */
width: 100%;
padding-top: 30px;
display: flex;
justify-content: center;
/* 水平居中 */
width: 100%;
}
</style>

View File

@ -1,54 +1,56 @@
<template>
<div id="main">
<div id="form">
<el-form :model="form" label-width="120px" @keyup.enter.native="onSubmit">
<el-form-item :label="lang.account">
<el-input v-model="form.account" placeholder="User Name" />
</el-form-item>
<el-form-item :label="lang.password">
<el-input v-model="form.password" placeholder="Password" type="password" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">{{ lang.login }}</el-button>
</el-form-item>
</el-form>
<div id="main">
<div id="form">
<el-form :model="form" label-width="120px" @keyup.enter="onSubmit">
<el-form-item :label="lang.account">
<el-input v-model="form.account" placeholder="User Name"/>
</el-form-item>
<el-form-item :label="lang.password">
<el-input v-model="form.password" placeholder="Password" type="password"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">{{ lang.login }}</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { reactive } from 'vue'
import { ElMessage } from 'element-plus'
import router from "@/router"; //
import {reactive} from 'vue'
import {ElMessage} from 'element-plus'
import {router} from "@/router"; //
import lang from '../i18n/i18n';
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const $isLogin = app.appContext.config.globalProperties.$isLogin
const $userInfos = app.appContext.config.globalProperties.$userInfos
import {http} from "@/utils/axios";
import {useGlobalStatusStore} from "@/stores/useGlobalStatusStore";
const globalStatus = useGlobalStatusStore();
// eslint-disable-next-line no-unused-vars
let isLogin = globalStatus.isLogin
const userInfos = globalStatus.userInfos
const form = reactive({
account: '',
password: '',
account: '',
password: '',
})
const onSubmit = () => {
$http.post("/api/login", form).then(res => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
$isLogin.value = true
$userInfos.value = res.data
router.replace({
path: '/',
query: {
redirect: router.currentRoute.fullPath
}
})
http.post("/api/login", form).then(res => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
isLogin = true
userInfos.value = res.data
router.replace({
path: '/',
query: {
redirect: router.currentRoute.fullPath
}
})
})
}
})
}
</script>
@ -56,13 +58,13 @@ const onSubmit = () => {
<style scoped>
#main {
width: 100%;
height: 100%;
background-color: #f1f1f1;
display: flex;
justify-content: center;
/* 水平居中 */
align-items: center;
/* 垂直居中 */
width: 100%;
height: 100%;
background-color: #f1f1f1;
display: flex;
justify-content: center;
/* 水平居中 */
align-items: center;
/* 垂直居中 */
}
</style>

View File

@ -1,269 +1,264 @@
<template>
<div id="main">
<el-steps :active="active" align-center finish-status="success" id="status">
<el-step :title="lang.welcome" />
<el-step :title="lang.setDatabase" />
<el-step :title="lang.setAdminPassword" />
<el-step :title="lang.SetDomail" />
<el-step :title="lang.setDNS" />
<el-step :title="lang.setSSL" />
</el-steps>
<div id="main">
<el-steps :active="active" align-center finish-status="success" id="status">
<el-step :title="lang.welcome"/>
<el-step :title="lang.setDatabase"/>
<el-step :title="lang.setAdminPassword"/>
<el-step :title="lang.SetDomail"/>
<el-step :title="lang.setDNS"/>
<el-step :title="lang.setSSL"/>
</el-steps>
<div v-if="active == 0" class="ctn">
<div class="desc">
<h2>{{ lang.tks_pmail }}</h2>
<div style="margin-top: 10px;">{{ lang.guid_desc }}</div>
</div>
</div>
<div v-if="active === 0" class="ctn">
<div class="desc">
<h2>{{ lang.tks_pmail }}</h2>
<div style="margin-top: 10px;">{{ lang.guid_desc }}</div>
</div>
</div>
<div v-if="active === 1" class="ctn">
<div class="desc">
<h2>{{ lang.select_db }}</h2>
<div style="margin-top: 10px;">{{ lang.db_desc }}</div>
</div>
<div class="form" style="width: 400px;">
<el-form label-width="120px">
<el-form-item :label="lang.type">
<el-select :placeholder="lang.db_select_ph" v-model="dbSettings.type"
@change="dbSettings.dsn = ''">
<el-option label="MySQL" value="mysql"/>
<el-option label="SQLite3" value="sqlite"/>
<el-option label="PostgreSQL" value="postgres"/>
</el-select>
</el-form-item>
<el-form-item :label="lang.mysql_dsn" v-if="dbSettings.type === 'mysql'">
<el-input :rows="2" type="textarea" v-model="dbSettings.dsn"
placeholder="root:12345@tcp(127.0.0.1:3306)/pmail?parseTime=True&loc=Local"></el-input>
</el-form-item>
<el-form-item :label="lang.pg_dsn" v-if="dbSettings.type === 'postgres'">
<el-input :rows="2" type="textarea" v-model="dbSettings.dsn"
placeholder="postgres://postgres:12345@127.0.0.1:5432/pmail?sslmode=disable"></el-input>
</el-form-item>
<el-form-item :label="lang.sqlite_db_path" v-if="dbSettings.type === 'sqlite'">
<el-input v-model="dbSettings.dsn" placeholder="./config/pmail.db"></el-input>
</el-form-item>
</el-form>
</div>
</div>
<div v-if="active === 2" class="ctn">
<div class="desc">
<h2>{{ lang.setAdminPassword }}</h2>
<!-- <div style="margin-top: 10px;">{{ lang.domain_desc }}</div> -->
</div>
<div class="form" style="width: 400px;">
<el-form label-width="120px">
<el-form-item :label="lang.admin_account">
<el-input v-bind:disabled="adminSettings.hadSeted" placeholder="admin"
v-model="adminSettings.account"></el-input>
</el-form-item>
<el-form-item :label="lang.password">
<el-input type="password" v-bind:disabled="adminSettings.hadSeted" placeholder=""
v-model="adminSettings.password"></el-input>
</el-form-item>
<el-form-item :label="lang.enter_again">
<el-input type="password" v-bind:disabled="adminSettings.hadSeted" placeholder=""
v-model="adminSettings.password2"></el-input>
</el-form-item>
</el-form>
</div>
</div>
<div v-if="active == 1" class="ctn">
<div class="desc">
<h2>{{ lang.select_db }}</h2>
<div style="margin-top: 10px;">{{ lang.db_desc }}</div>
</div>
<div class="form" style="width: 400px;">
<el-form label-width="120px">
<el-form-item :label="lang.type">
<el-select :placeholder="lang.db_select_ph" v-model="dbSettings.type"
@change="dbSettings.dsn = ''">
<el-option label="MySQL" value="mysql" />
<el-option label="SQLite3" value="sqlite" />
<el-option label="PostgreSQL" value="postgres" />
</el-select>
</el-form-item>
<div v-if="active === 3" class="ctn">
<div class="desc">
<h2>{{ lang.SetDomail }}</h2>
<!-- <div style="margin-top: 10px;">{{ lang.domain_desc }}</div> -->
</div>
<div class="form" style="width: 400px;">
<el-form label-width="120px">
<el-form-item :label="lang.mysql_dsn" v-if="dbSettings.type == 'mysql'">
<el-input :rows="2" type="textarea" v-model="dbSettings.dsn"
placeholder="root:12345@tcp(127.0.0.1:3306)/pmail?parseTime=True&loc=Local"></el-input>
</el-form-item>
<el-form-item :label="lang.smtp_domain">
<el-input placeholder="domaim.com" v-model="domainSettings.smtp_domain">
<template #prepend>smtp.</template>
</el-input>
</el-form-item>
<el-form-item :label="lang.pg_dsn" v-if="dbSettings.type == 'postgres'">
<el-input :rows="2" type="textarea" v-model="dbSettings.dsn"
placeholder="postgres://postgres:12345@127.0.0.1:5432/pmail?sslmode=disable"></el-input>
</el-form-item>
<el-form-item :label="lang.web_domain">
<el-input placeholder="pmail.domain.com" v-model="domainSettings.web_domain"></el-input>
</el-form-item>
<el-form-item :label="lang.sqlite_db_path" v-if="dbSettings.type == 'sqlite'">
<el-input v-model="dbSettings.dsn" placeholder="./config/pmail.db"></el-input>
</el-form-item>
</el-form>
</div>
</div>
<el-form-item :label="lang.multi_domain_setting">
<span>{{ lang.multi_domain_setting_desc }}
<el-button @click="addDomain" size="small"
type="success" :icon="Plus"
circle>
</el-button>
</span>
<el-input :placeholder="'domain' + i + '.com'" v-for="(item, i) in domainSettings.multi_domain "
v-model="domainSettings.multi_domain[i]" :key="item"></el-input>
</el-form-item>
<div v-if="active == 2" class="ctn">
<div class="desc">
<h2>{{ lang.setAdminPassword }}</h2>
<!-- <div style="margin-top: 10px;">{{ lang.domain_desc }}</div> -->
</div>
<div class="form" style="width: 400px;">
<el-form label-width="120px">
</el-form>
</div>
</div>
<el-form-item :label="lang.admin_account">
<el-input v-bind:disabled="adminSettings.hadSeted" placeholder="admin"
v-model="adminSettings.account"></el-input>
</el-form-item>
<div v-if="active === 4" class="ctn_s">
<el-form-item :label="lang.password">
<el-input type="password" v-bind:disabled="adminSettings.hadSeted" placeholder=""
v-model="adminSettings.password"></el-input>
</el-form-item>
<div class="desc">
<h2>{{ lang.setDNS }}</h2>
<div style="margin-top: 10px;">{{ lang.dns_desc }}</div>
</div>
<div class="form" width="600px" v-for="(info,domain) in dnsInfos" :key="info">
<h3>{{ domain }}</h3>
<el-table :data="info" border style="width: 100%">
<el-table-column prop="host" label="HOSTNAME" width="110px">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-tooltip :content="lang.dns_root_desc" placement="top"
v-if="scope.row.host === '' || scope.row.host === '@' ">
{{ scope.row.host }}
</el-tooltip>
<span v-else>{{ scope.row.host }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="type" label="TYPE" width="110px"/>
<el-table-column prop="value" label="VALUE">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-tooltip :content="scope.row.tips" placement="top" v-if="scope.row.tips !== ''">
{{ scope.row.value }}
</el-tooltip>
<span v-else>{{ scope.row.value }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="ttl" label="TTL" width="110px"/>
</el-table>
</div>
</div>
<el-form-item :label="lang.enter_again">
<el-input type="password" v-bind:disabled="adminSettings.hadSeted" placeholder=""
v-model="adminSettings.password2"></el-input>
</el-form-item>
</el-form>
</div>
</div>
<el-alert :closable="false" title="Warning!" type="error" center
v-if="active === 5 && sslSettings.type === 0 && port !== 80" :description="lang.autoSSLWarn"/>
<div v-if="active === 5" class="ctn">
<div class="desc">
<h2>{{ lang.setSSL }}</h2>
<div style="margin-top: 10px;">{{ lang.setSSL }}</div>
</div>
<div class="form" width="600px">
<el-form label-width="120px">
<el-form-item :label="lang.type">
<el-select :placeholder="lang.ssl_auto" v-model="sslSettings.type" :disabled="dnsChecking">
<el-option :label="lang.ssl_auto" value="0"/>
<el-option :label="lang.ssl_manuallyf" value="1"/>
</el-select>
</el-form-item>
<el-form-item :label="lang.ssl_challenge_type" v-if="sslSettings.type === '0'">
<el-select :placeholder="lang.ssl_auto_http" v-model="sslSettings.challenge"
:disabled="dnsChecking">
<el-option :label="lang.ssl_auto_http" value="http"/>
<el-option :label="lang.ssl_auto_dns" value="dns"/>
</el-select>
<el-tooltip class="box-item" effect="dark" :content="lang.challenge_typ_desc"
placement="top-start">
<span style="margin-left: 6px; font-size:18px; font-weight: bolder;">?</span>
</el-tooltip>
</el-form-item>
<div v-if="active == 3" class="ctn">
<div class="desc">
<h2>{{ lang.SetDomail }}</h2>
<!-- <div style="margin-top: 10px;">{{ lang.domain_desc }}</div> -->
</div>
<div class="form" style="width: 400px;">
<el-form label-width="120px">
<el-form-item :label="lang.ssl_key_path" v-if="sslSettings.type === '1'">
<el-input placeholder="./config/ssl/private.key" v-model="sslSettings.key_path"></el-input>
</el-form-item>
<el-form-item :label="lang.smtp_domain">
<el-input placeholder="domaim.com" v-model="domainSettings.smtp_domain">
<template #prepend>smtp.</template>
</el-input>
</el-form-item>
<el-form-item :label="lang.web_domain">
<el-input placeholder="pmail.domain.com" v-model="domainSettings.web_domain"></el-input>
</el-form-item>
<el-form-item :label="lang.multi_domain_setting">
<span>{{ lang.multi_domain_setting_desc }} <el-button @click="addDomain" size="small"
type="success" :icon="Plus" circle></el-button></span>
<el-input :placeholder="'domain' + i + '.com'" v-for="(item, i) in domainSettings.multi_domain"
v-model="domainSettings.multi_domain[i]"></el-input>
</el-form-item>
<el-form-item :label="lang.ssl_crt_path" v-if="sslSettings.type === '1'">
<el-input placeholder="./config/ssl/public.crt" v-model="sslSettings.crt_path"></el-input>
</el-form-item>
</el-form>
</el-form>
</div>
</div>
<div v-if="active == 4" class="ctn_s">
<div class="desc">
<h2>{{ lang.setDNS }}</h2>
<div style="margin-top: 10px;">{{ lang.dns_desc }}</div>
</div>
<div class="form" width="600px" v-for="(info,domain) in dnsInfos">
<h3>{{ domain }}</h3>
<el-table :data="info" border style="width: 100%">
<el-table-column prop="host" label="HOSTNAME" width="110px" >
<template #default="scope">
<div style="display: flex; align-items: center">
<el-tooltip :content="lang.dns_root_desc" placement="top" v-if="scope.row.host == '' || scope.row.host == '@' ">
{{ scope.row.host }}
</el-tooltip>
<span v-else>{{ scope.row.host }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="type" label="TYPE" width="110px" />
<el-table-column prop="value" label="VALUE">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-tooltip :content="scope.row.tips" placement="top" v-if="scope.row.tips != ''">
{{ scope.row.value }}
</el-tooltip>
<span v-else>{{ scope.row.value }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="ttl" label="TTL" width="110px" />
</el-table>
</div>
</div>
<el-alert :closable="false" title="Warning!" type="error" center
v-if="active == 5 && sslSettings.type == 0 && port != 80" :description="lang.autoSSLWarn" />
<div v-if="active == 5" class="ctn">
<div class="desc">
<h2>{{ lang.setSSL }}</h2>
<div style="margin-top: 10px;">{{ lang.setSSL }}</div>
</div>
<div class="form" width="600px">
<el-form label-width="120px">
<el-form-item :label="lang.type">
<el-select :placeholder="lang.ssl_auto" v-model="sslSettings.type" :disabled="dnsChecking">
<el-option :label="lang.ssl_auto" value="0" />
<el-option :label="lang.ssl_manuallyf" value="1" />
</el-select>
</el-form-item>
<el-form-item :label="lang.ssl_challenge_type" v-if="sslSettings.type == '0'">
<el-select :placeholder="lang.ssl_auto_http" v-model="sslSettings.challenge"
:disabled="dnsChecking">
<el-option :label="lang.ssl_auto_http" value="http" />
<el-option :label="lang.ssl_auto_dns" value="dns" />
</el-select>
<el-tooltip class="box-item" effect="dark" :content="lang.challenge_typ_desc"
placement="top-start">
<span style="margin-left: 6px; font-size:18px; font-weight: bolder;">?</span>
</el-tooltip>
</el-form-item>
<el-form-item :label="lang.ssl_key_path" v-if="sslSettings.type == '1'">
<el-input placeholder="./config/ssl/private.key" v-model="sslSettings.key_path"></el-input>
</el-form-item>
<el-form-item :label="lang.ssl_crt_path" v-if="sslSettings.type == '1'">
<el-input placeholder="./config/ssl/public.crt" v-model="sslSettings.crt_path"></el-input>
</el-form-item>
</el-form>
</div>
</div>
<div v-if="dnsChecking">
<label>{{ lang.dns_desc }}</label>
<el-table :data="sslSettings.paramsList" border v-loading="sslSettings.paramsList.length == 0">
<el-table-column prop="host" label="HOSTNAME" width="110px" />
<el-table-column prop="type" label="TYPE" width="110px" />
<el-table-column prop="value" label="VALUE">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-tooltip :content="scope.row.tips" placement="top" v-if="scope.row.tips != ''">
{{ scope.row.value }}
</el-tooltip>
<span v-else>{{ scope.row.value }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="ttl" label="TTL" width="110px" />
</el-table>
</div>
<el-button :element-loading-text="waitDesc" v-loading.fullscreen.lock="fullscreenLoading" id="next"
style="margin-top: 12px" @click="next">{{
lang.next }}</el-button>
</div>
</div>
<div v-if="dnsChecking">
<label>{{ lang.dns_desc }}</label>
<el-table :data="sslSettings.paramsList" border v-loading="sslSettings.paramsList.length === 0">
<el-table-column prop="host" label="HOSTNAME" width="110px"/>
<el-table-column prop="type" label="TYPE" width="110px"/>
<el-table-column prop="value" label="VALUE">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-tooltip :content="scope.row.tips" placement="top" v-if="scope.row.tips !== ''">
{{ scope.row.value }}
</el-tooltip>
<span v-else>{{ scope.row.value }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="ttl" label="TTL" width="110px"/>
</el-table>
</div>
<el-button :element-loading-text="waitDesc" v-loading.fullscreen.lock="fullscreenLoading" id="next"
style="margin-top: 12px" @click="next">{{
lang.next
}}
</el-button>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import {reactive, ref} from 'vue'
import {ElMessage} from 'element-plus'
import lang from '../i18n/i18n';
import axios from 'axios'
import { getCurrentInstance } from 'vue'
import {
Plus
} from '@element-plus/icons-vue'
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const waitDesc = ref(lang.wait_desc)
import {Plus} from '@element-plus/icons-vue'
import {http} from "@/utils/axios";
const waitDesc = ref(lang.wait_desc);
const adminSettings = reactive({
"account": "admin",
"password": "",
"password2": "",
"hadSeted": false
"account": "admin",
"password": "",
"password2": "",
"hadSeted": false
})
const dbSettings = reactive({
"type": "sqlite",
"dsn": "./config/pmail.db",
"lable": ""
"type": "sqlite",
"dsn": "./config/pmail.db",
"lable": ""
})
const domainSettings = reactive({
"web_domain": "",
"smtp_domain": "",
"multi_domain": []
"web_domain": "",
"smtp_domain": "",
"multi_domain": []
})
const sslSettings = reactive({
"type": "0",
"challenge": "http",
"key_path": "./config/ssl/private.key",
"crt_path": "./config/ssl/public.crt",
"paramsList": [],
"type": "0",
"challenge": "http",
"key_path": "./config/ssl/private.key",
"crt_path": "./config/ssl/public.crt",
"paramsList": [],
})
@ -277,236 +272,244 @@ const port = ref(80)
const addDomain = () => {
domainSettings.multi_domain.push([])
domainSettings.multi_domain.push([])
}
const setPassword = () => {
if (adminSettings.hadSeted) {
if (adminSettings.hadSeted) {
active.value++;
getDomainConfig();
return;
}
if (adminSettings.password !== adminSettings.password2) {
ElMessage.error(lang.err_pwd_diff)
} else {
http.post("/api/setup", {
"action": "set",
"step": "password",
"account": adminSettings.account,
"password": adminSettings.password
}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
active.value++;
getDomainConfig();
return;
}
if (adminSettings.password != adminSettings.password2) {
ElMessage.error(lang.err_pwd_diff)
} else {
$http.post("/api/setup", { "action": "set", "step": "password", "account": adminSettings.account, "password": adminSettings.password }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
active.value++;
getDomainConfig();
}
})
}
}
})
}
}
const getPassword = () => {
$http.post("/api/setup", { "action": "get", "step": "password" }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
adminSettings.hadSeted = res.data != ""
if (adminSettings.hadSeted) {
adminSettings.account = res.data
adminSettings.password = "*******"
adminSettings.password2 = "*******"
}
http.post("/api/setup", {"action": "get", "step": "password"}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
adminSettings.hadSeted = res.data !== ""
if (adminSettings.hadSeted) {
adminSettings.account = res.data
adminSettings.password = "*******"
adminSettings.password2 = "*******"
}
}
})
}
})
}
const getDbConfig = () => {
$http.post("/api/setup", { "action": "get", "step": "database" }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
dbSettings.type = res.data.db_type;
dbSettings.dsn = res.data.db_dsn;
}
})
http.post("/api/setup", {"action": "get", "step": "database"}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
dbSettings.type = res.data.db_type;
dbSettings.dsn = res.data.db_dsn;
}
})
}
const getDomainConfig = () => {
$http.post("/api/setup", { "action": "get", "step": "domain" }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
domainSettings.web_domain = res.data.web_domain;
domainSettings.smtp_domain = res.data.smtp_domain;
domainSettings.multi_domain = res.data.domains;
}
})
http.post("/api/setup", {"action": "get", "step": "domain"}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
domainSettings.web_domain = res.data.web_domain;
domainSettings.smtp_domain = res.data.smtp_domain;
domainSettings.multi_domain = res.data.domains;
}
})
}
const setDbConfig = () => {
// sqlite使
if (dbSettings.type === "sqlite" && !dbSettings.dsn) dbSettings.dsn = "./config/pmail.db";
else if (!dbSettings.dsn) ElMessage({
title: "Error",
message: lang.err_db_dsn_empty,
type: "error",
});
$http.post("/api/setup", { "action": "set", "step": "database", "db_type": dbSettings.type, "db_dsn": dbSettings.dsn }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
active.value++;
getPassword();
}
})
// sqlite使
if (dbSettings.type === "sqlite" && !dbSettings.dsn) dbSettings.dsn = "./config/pmail.db";
else if (!dbSettings.dsn) ElMessage({
title: "Error",
message: lang.err_db_dsn_empty,
type: "error",
});
http.post("/api/setup", {
"action": "set",
"step": "database",
"db_type": dbSettings.type,
"db_dsn": dbSettings.dsn
}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
active.value++;
getPassword();
}
})
}
const getDNSConfig = () => {
$http.post("/api/setup", { "action": "get", "step": "dns" }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
dnsInfos.value = res.data
}
})
http.post("/api/setup", {"action": "get", "step": "dns"}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
dnsInfos.value = res.data
}
})
}
const getSSLConfig = () => {
$http.post("/api/setup", { "action": "get", "step": "ssl" }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
sslSettings.type = res.data.type
if (sslSettings.type == "2") {
sslSettings.type = "0"
sslSettings.challenge = "dns"
}
http.post("/api/setup", {"action": "get", "step": "ssl"}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
sslSettings.type = res.data.type
if (sslSettings.type === "2") {
sslSettings.type = "0"
sslSettings.challenge = "dns"
}
port.value = res.data.port
}
})
port.value = res.data.port
}
})
}
const setSSLConfig = () => {
fullscreenLoading.value = true;
fullscreenLoading.value = true;
let sslType = sslSettings.type;
if (sslType == "0" && sslSettings.challenge == "dns") {
sslType = "2"
let sslType = sslSettings.type;
if (sslType === "0" && sslSettings.challenge === "dns") {
sslType = "2"
}
http.post("/api/setup", {
"action": "set",
"step": "ssl",
"ssl_type": sslType,
"key_path": sslSettings.key_path,
"crt_path": sslSettings.crt_path
}).then((res) => {
if (res.errorNo !== 0) {
fullscreenLoading.value = false;
ElMessage.error(res.errorMsg)
} else {
if (sslType === 2) {
fullscreenLoading.value = false;
dnsChecking.value = true;
getSSLDNSParams();
}
checkStatus();
}
$http.post("/api/setup", {
"action": "set",
"step": "ssl",
"ssl_type": sslType,
"key_path": sslSettings.key_path,
"crt_path": sslSettings.crt_path
}).then((res) => {
if (res.errorNo != 0) {
fullscreenLoading.value = false;
ElMessage.error(res.errorMsg)
} else {
if (sslType == 2) {
fullscreenLoading.value = false;
dnsChecking.value = true;
getSSLDNSParams();
}
checkStatus();
}
})
})
}
const checkStatus = () => {
axios.post("/api/ping", {}).then((res) => {
if (res.data.errorNo != 0) {
setTimeout(function () {
checkStatus()
}, 1000);
} else {
if(sslSettings.type == 1){
window.location.href = "http://" + domainSettings.web_domain;
}else{
window.location.href = "https://" + domainSettings.web_domain;
}
}
}).catch((error) => {
setTimeout(function () {
checkStatus()
}, 1000);
})
axios.post("/api/ping", {}).then((res) => {
if (res.data.errorNo !== 0) {
setTimeout(function () {
checkStatus()
}, 1000);
} else {
if (sslSettings.type === 1) {
window.location.href = "http://" + domainSettings.web_domain;
} else {
window.location.href = "https://" + domainSettings.web_domain;
}
}
}).catch(() => {
setTimeout(function () {
checkStatus()
}, 1000);
})
}
const setDomainConfig = () => {
$http.post("/api/setup", {
"action": "set",
"step": "domain",
"web_domain": domainSettings.web_domain,
"smtp_domain": domainSettings.smtp_domain,
"multi_domain": domainSettings.multi_domain.join(",")
}).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
active.value++;
getDNSConfig();
}
})
http.post("/api/setup", {
"action": "set",
"step": "domain",
"web_domain": domainSettings.web_domain,
"smtp_domain": domainSettings.smtp_domain,
"multi_domain": domainSettings.multi_domain.join(",")
}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
active.value++;
getDNSConfig();
}
})
}
const getSSLDNSParams = () => {
$http.post("/api/setup", { "action": "getParams", "step": "ssl" }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
sslSettings.paramsList = res.data
console.log(sslSettings.paramsList)
}
})
if (sslSettings.paramsList.length == 0) {
setTimeout(function () {
getSSLDNSParams()
}, 1000);
http.post("/api/setup", {"action": "getParams", "step": "ssl"}).then((res) => {
if (res.errorNo !== 0) {
ElMessage.error(res.errorMsg)
} else {
sslSettings.paramsList = res.data
console.log(sslSettings.paramsList)
}
})
if (sslSettings.paramsList.length === 0) {
setTimeout(function () {
getSSLDNSParams()
}, 1000);
}
}
const next = () => {
switch (active.value) {
case 0:
active.value++
getDbConfig();
break
case 1:
setDbConfig();
break;
case 2:
setPassword();
break;
case 3:
setDomainConfig();
break;
case 4:
getSSLConfig();
active.value++
break
case 5:
if (dnsChecking.value) {
fullscreenLoading.value = true;
waitDesc.value = lang.dns_challenge_wait;
} else {
setSSLConfig();
}
break
}
switch (active.value) {
case 0:
active.value++
getDbConfig();
break
case 1:
setDbConfig();
break;
case 2:
setPassword();
break;
case 3:
setDomainConfig();
break;
case 4:
getSSLConfig();
active.value++
break
case 5:
if (dnsChecking.value) {
fullscreenLoading.value = true;
waitDesc.value = lang.dns_challenge_wait;
} else {
setSSLConfig();
}
break
}
}
</script>
@ -514,30 +517,32 @@ const next = () => {
<style scoped>
#main {
width: 100%;
height: 100%;
background-color: #f1f1f1;
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 100%;
background-color: #f1f1f1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.desc {
padding-right: 20px;
padding-right: 20px;
}
#status {}
#status {
}
.ctn {
display: flex;
justify-content: center;
display: flex;
justify-content: center;
}
.ctn_s {
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
}
#next {}
#next {
}
</style>