新增中英文切换

This commit is contained in:
eoao
2025-07-22 22:02:23 +08:00
parent 249edb9e19
commit bbcff4908d
64 changed files with 1951 additions and 1298 deletions

View File

@@ -6,15 +6,15 @@
<el-input
v-model="params.email"
class="search-input"
placeholder="输入邮箱名搜索"
:placeholder="$t('searchByEmail')"
>
</el-input>
</div>
<el-select v-model="params.status" placeholder="Select" class="status-select">
<el-option :key="-1" label="全部" :value="-1"/>
<el-option :key="0" label="正常" :value="0"/>
<el-option :key="1" label="封禁" :value="1"/>
<el-option :key="-2" label="删除" :value="-2"/>
<el-option :key="-1" :label="$t('all')" :value="-1"/>
<el-option :key="0" :label="$t('active')" :value="0"/>
<el-option :key="1" :label="$t('banned')" :value="1"/>
<el-option :key="-2" :label="$t('deleted')" :value="-2"/>
</el-select>
<Icon class="icon" icon="iconoir:search" @click="search" width="20" height="20"/>
<Icon class="icon" @click="changeTimeSort" icon="material-symbols-light:timer-arrow-down-outline"
@@ -41,93 +41,93 @@
<el-table-column :width="expandWidth" type="expand">
<template #default="props">
<div class="details">
<div v-if="!sendNumShow"><span class="details-item-title">发件数量:</span>{{ props.row.sendEmailCount }}
<div v-if="!sendNumShow"><span class="details-item-title">{{$t('tabSent')}}:</span>{{ props.row.sendEmailCount }}
</div>
<div v-if="!accountNumShow"><span class="details-item-title">邮箱数量:</span>{{
<div v-if="!accountNumShow"><span class="details-item-title">{{$t('tabMailboxes')}}:</span>{{
props.row.accountCount
}}
</div>
<div v-if="!createTimeShow"><span class="details-item-title">注册时间:</span>{{
tzDayjs(props.row.createTime).format('YYYY-MM-DD HH:mm:ss')
<div v-if="!createTimeShow"><span class="details-item-title">{{$t('tabRegisteredAt')}}:</span>{{
tzDayjs(props.row.createTime).format('YYYY-MM-DD HH:mm')
}}
</div>
<div v-if="!typeShow"><span class="details-item-title">身份类型:</span> {{ toRoleName(props.row.type) }}
<div v-if="!typeShow"><span class="details-item-title">{{$t('perm')}}:</span> {{ toRoleName(props.row.type) }}
</div>
<div v-if="!statusShow">
<span class="details-item-title">状态:</span>
<el-tag disable-transitions v-if="props.row.isDel === 1" type="info">删除</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 0" type="primary">正常</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 1" type="danger">封禁</el-tag>
<span class="details-item-title">{{$t('tabStatus')}}:</span>
<el-tag disable-transitions v-if="props.row.isDel === 1" type="info">{{$t('deleted')}}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 0" type="primary">{{$t('active')}}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 1" type="danger">{{$t('banned')}}</el-tag>
</div>
<div><span class="details-item-title">注册IP:</span>{{ props.row.createIp || '未知' }}</div>
<div><span class="details-item-title">近期IP:</span>{{ props.row.activeIp || '未知' }}</div>
<div><span class="details-item-title">近期活动:</span>{{
props.row.activeTime ? tzDayjs(props.row.activeTime).format('YYYY-MM-DD') : '未知'
<div><span class="details-item-title">{{$t('registrationIp')}}:</span>{{ props.row.createIp || $t('unknown') }}</div>
<div><span class="details-item-title">{{$t('recentIP')}}:</span>{{ props.row.activeIp || $t('unknown') }}</div>
<div><span class="details-item-title">{{$t('recentActivity')}}:</span>{{
props.row.activeTime ? tzDayjs(props.row.activeTime).format('YYYY-MM-DD') : $t('unknown')
}}
</div>
<div><span class="details-item-title">登录设备:</span>{{ props.row.device || '未知' }}</div>
<div><span class="details-item-title">登录系统:</span>{{ props.row.os || '未知' }}</div>
<div><span class="details-item-title">登录浏览器:</span>{{ props.row.browser || '未知' }}</div>
<div><span class="details-item-title">{{$t('loginDevice')}}:</span>{{ props.row.device || $t('unknown') }}</div>
<div><span class="details-item-title">{{$t('loginSystem')}}:</span>{{ props.row.os || $t('unknown') }}</div>
<div><span class="details-item-title">{{$t('browserLogin')}}:</span>{{ props.row.browser || $t('unknown') }}</div>
<div>
<span class="details-item-title">发件次数:</span>
<span class="details-item-title">{{$t('sendEmail')}}:</span>
<span>{{ formatSendCount(props.row) }}</span>
<el-tag style="margin-left: 10px" v-if="props.row.sendAction.hasPerm" >
{{ formatSendType(props.row) }}
</el-tag>
<el-button size="small" style="margin-left: 10px"
v-if="props.row.sendAction.hasPerm && props.row.sendAction.sendCount"
@click="resetSendCount(props.row)" type="primary">重置
@click="resetSendCount(props.row)" type="primary">{{$t('reset')}}
</el-button>
</div>
</div>
</template>
</el-table-column>
<el-table-column show-overflow-tooltip :tooltip-formatter="tableRowFormatter" label="用户邮箱" :min-width="emailWidth">
<el-table-column show-overflow-tooltip :tooltip-formatter="tableRowFormatter" :label="$t('tabEmailAddress')" :min-width="emailWidth">
<template #default="props">
<div class="email-row">{{ props.row.email }}</div>
</template>
</el-table-column>
<el-table-column :formatter="formatterReceive" label-class-name="receive" column-key="receive"
:filtered-value="filteredValue" :filters="filters" :width="receiveWidth" label="收件数量"
:filtered-value="filteredValue" :filters="filters" :width="receiveWidth" :label="$t('tabReceived')"
prop="receiveEmailCount"/>
<el-table-column :formatter="formatterSend" label-class-name="send" column-key="send"
:filtered-value="filteredValue" :filters="filters" v-if="sendNumShow" label="发件数量"
:filtered-value="filteredValue" :filters="filters" v-if="sendNumShow" :label="$t('tabSent')"
prop="sendEmailCount"/>
<el-table-column :formatter="formatterAccount" label-class-name="account" column-key="account"
:filtered-value="filteredValue" :filters="filters" v-if="accountNumShow" label="邮箱数量"
:filtered-value="filteredValue" :filters="filters" v-if="accountNumShow" :label="$t('tabMailboxes')"
prop="accountCount"/>
<el-table-column v-if="createTimeShow" label="注册时间" min-width="160" prop="createTime">
<el-table-column v-if="createTimeShow" :label="$t('tabRegisteredAt')" min-width="160" prop="createTime">
<template #default="props">
{{ tzDayjs(props.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
{{ tzDayjs(props.row.createTime).format('YYYY-MM-DD HH:mm') }}
</template>
</el-table-column>
<el-table-column v-if="statusShow" min-width="60px" label="状态" prop="status">
<el-table-column v-if="statusShow" min-width="60px" :label="$t('tabStatus')" prop="status">
<template #default="props">
<el-tag disable-transitions v-if="props.row.isDel === 1" type="info">删除</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 0" type="primary">正常</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 1" type="danger">封禁</el-tag>
<el-tag disable-transitions v-if="props.row.isDel === 1" type="info">{{$t('deleted')}}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 0" type="primary">{{$t('active')}}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 1" type="danger">{{$t('banned')}}</el-tag>
</template>
</el-table-column>
<el-table-column v-if="typeShow" label="身份类型" min-width="140" prop="type">
<el-table-column v-if="typeShow" :label="$t('tabRole')" min-width="140" prop="type">
<template #default="props">
<div class="type">
{{ toRoleName(props.row.type) }}
</div>
</template>
</el-table-column>
<el-table-column label="设置" :width="settingWidth">
<el-table-column :label="$t('tabSetting')" :width="settingWidth">
<template #default="props">
<el-dropdown trigger="click">
<el-button size="small" type="primary">操作</el-button>
<el-button size="small" type="primary">{{$t('action')}}</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openSetPwd(props.row)">改密</el-dropdown-item>
<el-dropdown-item @click="openSetType(props.row)">权限</el-dropdown-item>
<el-dropdown-item @click="openSetPwd(props.row)">{{$t('chgPwd')}}</el-dropdown-item>
<el-dropdown-item @click="openSetType(props.row)">{{$t('perm')}}</el-dropdown-item>
<el-dropdown-item v-if="props.row.isDel !== 1" @click="setStatus(props.row)">
{{ setStatusName(props.row) }}
</el-dropdown-item>
<el-dropdown-item v-else @click="restore(props.row)">恢复</el-dropdown-item>
<el-dropdown-item @click="delUser(props.row)">删除</el-dropdown-item>
<el-dropdown-item v-else @click="restore(props.row)">{{$t('restore')}}</el-dropdown-item>
<el-dropdown-item @click="delUser(props.row)">{{$t('delete')}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -163,35 +163,35 @@
</div>
</div>
</el-scrollbar>
<el-dialog class="dialog" v-model="setPwdShow" title="修改密码" @closed="resetUserForm">
<el-dialog class="dialog" v-model="setPwdShow" :title="$t('changePassword')" @closed="resetUserForm">
<div class="dialog-box">
<el-input v-model="userForm.password" type="password" placeholder="密码" autocomplete="off">
<el-input v-model="userForm.password" type="password" :placeholder="$t('newPassword')" autocomplete="off">
</el-input>
<el-button class="btn" type="primary" :loading="settingLoading" @click="updatePwd"
>保存
>{{$t('save')}}
</el-button>
</div>
</el-dialog>
<el-dialog class="dialog" v-model="setTypeShow" title="设置权限" @closed="resetUserForm">
<el-dialog class="dialog" v-model="setTypeShow" :title="$t('changePerm')" @closed="resetUserForm">
<div class="dialog-box">
<el-input disabled model-value="超级管理员" v-if="userForm.type === 0" />
<el-input disabled :model-value="$t('admin')" v-if="userForm.type === 0" />
<el-select v-else v-model="userForm.type" placeholder="Select" >
<el-option v-for="item in roleList" :label="item.name" :value="item.roleId" :key="item.roleId"/>
</el-select>
<el-button :disabled="userForm.type === 0" class="btn" :loading="settingLoading" type="primary" @click="setType"
>保存
>{{$t('save')}}
</el-button>
</div>
</el-dialog>
<el-dialog v-model="showAdd" title="添加用户">
<el-dialog v-model="showAdd" :title="$t('addUser')">
<div class="container">
<el-input v-model="addForm.email" type="text" placeholder="邮箱" autocomplete="off">
<el-input v-model="addForm.email" type="text" :placeholder="$t('emailAccount')" autocomplete="off">
<template #append>
<div @click.stop="openSelect">
<el-select
ref="mySelect"
v-model="addForm.suffix"
placeholder="请选择"
:placeholder="$t('select')"
class="select"
>
<el-option
@@ -208,12 +208,12 @@
</div>
</template>
</el-input>
<el-input type="password" v-model="addForm.password" placeholder="密码"/>
<el-select v-model="addForm.type" placeholder="身份类型">
<el-input type="password" v-model="addForm.password" :placeholder="$t('password')"/>
<el-select v-model="addForm.type" :placeholder="$t('perm')">
<el-option v-for="item in roleList" :label="item.name" :value="item.roleId" :key="item.roleId"/>
</el-select>
<el-button class="btn" type="primary" @click="submit" :loading="addLoading"
>添加
>{{$t('add')}}
</el-button>
</div>
</el-dialog>
@@ -239,16 +239,18 @@ import {useSettingStore} from "@/store/setting.js";
import {isEmail} from "@/utils/verify-utils.js";
import {useRoleStore} from "@/store/role.js";
import {useUserStore} from "@/store/user.js";
import { useI18n } from 'vue-i18n';
defineOptions({
name: 'user'
})
const { t, locale } = useI18n();
const roleStore = useRoleStore()
const userStore = useUserStore()
const settingStore = useSettingStore()
const filteredValue = ['normal', 'del']
const filters = [{text: '正常', value: 'normal'}, {text: '删除', value: 'del'}]
const filters = [{text: t('active'), value: 'normal'}, {text: t('deleted'), value: 'del'}]
const preserveExpanded = ref(false)
const emailWidth = ref(230)
const expandWidth = ref(40)
@@ -402,9 +404,9 @@ function formatterReceive(e) {
}
function setStatusName(user) {
if (user.isDel === 1) return '恢复'
if (user.status === 0) return '禁用'
if (user.status === 1) return '启用'
if (user.isDel === 1) return t('restore')
if (user.status === 0) return t('btnBan')
if (user.status === 1) return t('enable')
}
const tableRowFormatter = (data) => {
@@ -435,7 +437,7 @@ function submit() {
if (!addForm.email) {
ElMessage({
message: "邮箱不能为空",
message: t('emptyEmailMsg'),
type: "error",
plain: true
})
@@ -444,7 +446,7 @@ function submit() {
if (!isEmail(addForm.email + addForm.suffix)) {
ElMessage({
message: "非法邮箱",
message: t('notEmailMsg'),
type: "error",
plain: true
})
@@ -453,7 +455,7 @@ function submit() {
if (!addForm.password) {
ElMessage({
message: "密码不能为空",
message: t('emptyPwdMsg'),
type: "error",
plain: true
})
@@ -462,7 +464,7 @@ function submit() {
if (addForm.password.length < 6) {
ElMessage({
message: "密码至少六位",
message: t('pwdLengthMsg'),
type: "error",
plain: true
})
@@ -471,7 +473,7 @@ function submit() {
if (!addForm.type) {
ElMessage({
message: "身份类型不能为空",
message: t('emptyRole'),
type: "error",
plain: true
})
@@ -485,7 +487,7 @@ function submit() {
addLoading.value = false
showAdd.value = false
ElMessage({
message: "添加成功",
message: t('addSuccessMsg'),
type: "success",
plain: true
})
@@ -498,27 +500,29 @@ function submit() {
function formatSendType(user) {
if (user.sendAction.sendType === 'day') return '每天'
if (user.sendAction.sendType === 'count') return '总数'
if (user.sendAction.sendType === 'day') return t('daily')
if (user.sendAction.sendType === 'count') return t('total')
}
function formatSendCount(user) {
if (!user.sendAction.hasPerm) {
return '无权限'
return t('noPerm')
}
if (!user.sendAction.sendCount) {
return '无限制';
return t('noLimit');
}
return user.sendCount + '/' + user.sendAction.sendCount + '次'
let count = user.sendCount + '/' + user.sendAction.sendCount
return count
}
function toRoleName(type) {
if (type === 0) {
return '超级管理员'
return t('admin')
}
const index = roleList.findIndex(role => role.roleId === type)
@@ -530,14 +534,14 @@ function toRoleName(type) {
function resetSendCount(user) {
ElMessageBox.confirm(`确认重置${user.email}发件次数吗?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
ElMessageBox.confirm(t('reSendConfirm',{msg: user.email}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
userRestSendCount(user.userId).then(() => {
ElMessage({
message: "重置成功",
message: t('reSuccessMsg'),
type: "success",
plain: true
})
@@ -547,14 +551,14 @@ function resetSendCount(user) {
}
function delUser(user) {
ElMessageBox.confirm(`确认删除${user.email}吗?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
ElMessageBox.confirm(t('delConfirm',{msg: user.email}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
userDelete(user.userId).then(() => {
ElMessage({
message: "删除成功",
message: t('delSuccessMsg'),
type: "success",
plain: true
})
@@ -568,16 +572,16 @@ function restore(user) {
const type = ref(0)
ElMessageBox.confirm( null, {
confirmButtonText: '确定',
cancelButtonText: '取消',
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
message: () => h('div', [
h('div', { class: 'mb-2' }, `确认要恢复 ${user.email}`),
h('div', { class: 'mb-2' }, t('restoreConfirm', {msg: user.email})),
h(ElRadioGroup, {
modelValue: type.value,
'onUpdate:modelValue': (val) => (type.value = val),
}, [
h(ElRadio, { label: 'option1', value: 0 }, '普通恢复'),
h(ElRadio, { label: 'option2', value: 1 }, '包括已删除的数据'),
h(ElRadio, { label: 'option1', value: 0 }, t('normalRestore')),
h(ElRadio, { label: 'option2', value: 1 }, t('allRestore')),
])
]),
type: 'warning'
@@ -585,7 +589,7 @@ function restore(user) {
userRestore(user.userId,type.value).then(() => {
user.isDel = 0
ElMessage({
message: "恢复成功",
message: t('restoreSuccessMsg'),
type: "success",
plain: true
})
@@ -596,9 +600,9 @@ function restore(user) {
function setStatus(user) {
if (user.status === 0) {
ElMessageBox.confirm(`确认禁用 ${user.email} 吗?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
ElMessageBox.confirm(t('banRestore',{ msg: user.email }), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
httpSetStatus(user)
@@ -613,7 +617,7 @@ function httpSetStatus(user) {
userSetStatus({status: status, userId: user.userId}).then(() => {
user.status = status
ElMessage({
message: "设置成功",
message: t('changSuccessMsg'),
type: "success",
plain: true
})
@@ -626,7 +630,7 @@ function setType() {
chooseUser.type = userForm.type
setTypeShow.value = false
ElMessage({
message: "设置成功",
message: t('changSuccessMsg'),
type: "success",
plain: true
})
@@ -652,7 +656,7 @@ function updatePwd() {
if (!userForm.password) {
ElMessage({
message: '密码不能为空',
message: t('emptyPwdMsg'),
type: 'error',
plain: true,
})
@@ -661,7 +665,7 @@ function updatePwd() {
if (userForm.password.length < 6) {
ElMessage({
message: '密码最少六位',
message: t('pwdLengthMsg'),
type: 'error',
plain: true,
})
@@ -672,7 +676,7 @@ function updatePwd() {
userSetPwd({password: userForm.password, userId: userForm.userId}).then(() => {
setPwdShow.value = false
ElMessage({
message: "设置成功",
message: t('changSuccessMsg'),
type: "success",
plain: true
})
@@ -754,7 +758,7 @@ function adjustWidth() {
sendNumShow.value = width > 685
typeShow.value = width > 767
emailWidth.value = width > 480 ? 230 : null
settingWidth.value = width < 480 ? 75 : null
settingWidth.value = width < 480 ? (locale.value === 'en' ? 85 : 75) : null
expandWidth.value = width < 480 ? 25 : 40
pagerCount.value = width < 768 ? 7 : 11
receiveWidth.value = width < 480 ? 90 : null
@@ -894,7 +898,7 @@ function adjustWidth() {
}
.status-select {
width: 80px;
width: 95px;
:deep(.el-select__wrapper) {
min-height: 28px;