fix: 🧩 开关控制封装和邮件发送优化

This commit is contained in:
Bunny 2024-12-19 11:19:09 +08:00
parent 00fe865e15
commit a1eb4acacd
14 changed files with 290 additions and 57 deletions

View File

@ -264,7 +264,9 @@ function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
v.component = IFrame;
} else {
// 对后端传component组件路径和不传做兼容如果后端传component组件路径那么path可以随便写如果不传component组件路径会跟path保持一致
const index = v?.component ? modulesRoutesKeys.findIndex(ev => ev.includes(v.component as any)) : modulesRoutesKeys.findIndex(ev => ev.includes(v.path));
const index = v?.component
? modulesRoutesKeys.findIndex(ev => ev.includes(v.component as any))
: modulesRoutesKeys.findIndex(ev => ev.includes(v.path));
v.component = modulesRoutes[modulesRoutesKeys[index]];
}
if (v?.children && v.children.length !== 0) {
@ -316,6 +318,7 @@ function hasAuth(value: string | Array<string>): boolean {
if (metaAuths.includes('*::*::*') || metaAuths.includes('*::*') || metaAuths.includes('*')) {
return true;
}
return isString(value) ? metaAuths.includes(value) : isIncludeAllChildren(value, metaAuths);
}

View File

@ -7,16 +7,26 @@ import { useAdminUserStore } from '@/store/system/adminUser';
import ResetPasswordDialog from '@/components/Table/ResetPasswords.vue';
import { removeToken } from '@/utils/auth';
import { useRouter } from 'vue-router';
import { switchLoadMapControl, usePublicHooks } from '@/views/hooks';
import { messageBox } from '@/utils/message';
const router = useRouter();
//
const { switchStyle } = usePublicHooks();
//
const switchLoadMap = ref({});
// Ref
const ruleFormByRestPasswordRef = ref();
const router = useRouter();
const adminUserStore = useAdminUserStore();
//
const restPasswordForm = reactive({
password: '',
});
//
const reminderStatus = reactive({
week: false, //
mouth: false, //
});
/** 重置密码 */
const onResetPassword = () => {
@ -46,46 +56,129 @@ const onResetPassword = () => {
});
};
const list = ref([
{
title: $t('account_password'),
illustrate: $t('rest_password_tip'),
button: $t('modify'),
callback: onResetPassword,
},
// {
// title: '',
// illustrate: '158****6789',
// button: '',
// },
// {
// title: '',
// illustrate: '',
// button: '',
// },
// {
// title: '',
// illustrate: 'pure***@163.com',
// button: '',
// },
]);
/** 更新用户周账单提醒是否开启 */
const updateUserReportStatusByWeek = async (index: number) => {
// loading
switchLoadMapControl(switchLoadMap, index, true);
//
const confirm = await messageBox({
title: $t('confirm_update_status'),
showMessage: false,
confirmMessage: undefined,
cancelMessage: $t('cancel'),
});
//
if (!confirm) {
reminderStatus.week = !reminderStatus.week;
switchLoadMapControl(switchLoadMap, index, false);
return;
}
//
// const data = { userId: row.id, status: row.status };
// const result = await adminUserStore.updateUserStatusByAdmin(data);
// if (!result) {
// row.status = !row.status;
// switchLoadMap.value[index] = Object.assign({}, switchLoadMap.value[index], {
// loading: false,
// });
// return;
// }
// await onSearch();
switchLoadMapControl(switchLoadMap, index, false);
};
/** 更新用户周账单提醒是否开启 */
const updateUserReportStatusByMouth = async (index: number) => {
// loading
switchLoadMapControl(switchLoadMap, index, true);
//
const confirm = await messageBox({
title: $t('confirm_update_status'),
showMessage: false,
confirmMessage: undefined,
cancelMessage: $t('cancel'),
});
//
if (!confirm) {
reminderStatus.week = !reminderStatus.week;
switchLoadMapControl(switchLoadMap, index, false);
return;
}
//
// const data = { userId: row.id, status: row.status };
// const result = await adminUserStore.updateUserStatusByAdmin(data);
// if (!result) {
// row.status = !row.status;
// switchLoadMap.value[index] = Object.assign({}, switchLoadMap.value[index], {
// loading: false,
// });
// return;
// }
// await onSearch();
switchLoadMapControl(switchLoadMap, index, false);
};
</script>
<template>
<div :class="['min-w-[180px]', deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]']">
<h3 class="my-8">{{ $t('account_management') }}</h3>
<div v-for="(item, index) in list" :key="index">
<div class="flex items-center">
<ul>
<!-- 重置密码 -->
<li class="flex items-center">
<div class="flex-1">
<p>{{ item.title }}</p>
<el-text class="mx-1" type="info">{{ item.illustrate }}</el-text>
<p>{{ $t('account_password') }}</p>
<el-text class="mx-1" type="info">{{ $t('rest_password_tip') }}</el-text>
</div>
<el-button text type="primary" @click="item.callback">
{{ item.button }}
</el-button>
</div>
<el-button text type="primary" @click="onResetPassword"> {{ $t('modify') }}</el-button>
</li>
<el-divider />
</div>
<!-- 是否开启周账单提醒 -->
<li class="flex items-center">
<div class="flex-1">
<p>周账单提醒</p>
<el-text class="mx-1" type="info">是否开启周账单提醒(需要保证有邮箱)</el-text>
</div>
<el-switch
v-model="reminderStatus.week"
:active-text="$t('enable')"
:active-value="false"
:inactive-text="$t('disable')"
:inactive-value="true"
:loading="switchLoadMap[0]?.loading"
:style="switchStyle"
inline-prompt
@click="updateUserReportStatusByWeek(0)"
/>
</li>
<el-divider />
<!-- 是否开启月账单提醒 -->
<li class="flex items-center">
<div class="flex-1">
<p>月账单提醒</p>
<el-text class="mx-1" type="info">是否开启月账单提醒(需要保证有邮箱)</el-text>
</div>
<el-switch
v-model="reminderStatus.mouth"
:active-text="$t('enable')"
:active-value="false"
:inactive-text="$t('disable')"
:inactive-value="true"
:loading="switchLoadMap[1]?.loading"
:style="switchStyle"
inline-prompt
@click="updateUserReportStatusByMouth(1)"
/>
</li>
<el-divider />
</ul>
</div>
</template>

View File

@ -5,6 +5,7 @@ import { rules } from '@/views/configuration/emailTemplate/utils/columns';
import { FormProps } from '@/views/configuration/emailTemplate/utils/types';
import { $t } from '@/plugins/i18n';
import { useEmailTemplateStore } from '@/store/configuration/emailTemplate';
import { usePublicHooks } from '@/views/hooks';
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
@ -14,6 +15,8 @@ const props = withDefaults(defineProps<FormProps>(), {
emailUser: undefined,
//
subject: undefined,
//
isDefault: false,
//
body: undefined,
//
@ -21,6 +24,8 @@ const props = withDefaults(defineProps<FormProps>(), {
}),
});
//
const { switchStyle } = usePublicHooks();
const formRef = ref<FormInstance>();
const form = ref(props.formInline);
const emailTemplateStore = useEmailTemplateStore();
@ -51,6 +56,19 @@ defineExpose({ formRef });
<el-input v-model="form.subject" autocomplete="off" type="text" />
</el-form-item>
<!-- 用户状态 -->
<el-form-item :label="$t('isDefault')" prop="isDefault">
<el-switch
v-model="form.isDefault"
:active-text="$t('enable')"
:active-value="true"
:inactive-text="$t('disable')"
:inactive-value="false"
:style="switchStyle"
inline-prompt
/>
</el-form-item>
<!-- 配置邮件发送体 -->
<el-form-item :label="$t('emailTemplate_body')" prop="body">
<el-input v-model="form.body" :autosize="{ minRows: 2, maxRows: 26 }" autocomplete="off" type="textarea" />

View File

@ -55,7 +55,12 @@ onMounted(() => {
<Auth :value="auth.search">
<el-form ref="formRef" :inline="true" :model="emailTemplateStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item :label="$t('emailTemplate_templateName')" prop="templateName">
<el-input v-model="emailTemplateStore.form.templateName" :placeholder="`${$t('input')}${$t('emailTemplate_templateName')}`" class="!w-[180px]" clearable />
<el-input
v-model="emailTemplateStore.form.templateName"
:placeholder="`${$t('input')}${$t('emailTemplate_templateName')}`"
class="!w-[180px]"
clearable
/>
</el-form-item>
<el-form-item :label="$t('emailTemplate_subject')" prop="subject">
<el-input v-model="emailTemplateStore.form.subject" :placeholder="`${$t('input')}${$t('emailTemplate_subject')}`" class="!w-[180px]" clearable />
@ -67,7 +72,9 @@ onMounted(() => {
<el-input v-model="emailTemplateStore.form.type" :placeholder="`${$t('input')}${$t('emailTemplate_type')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="emailTemplateStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="emailTemplateStore.loading" type="primary" @click="onSearch">
{{ $t('search') }}
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
@ -126,7 +133,15 @@ onMounted(() => {
<el-button :icon="useRenderIcon(View)" :size="size" class="reset-margin" link type="primary" @click="viewTemplate(row.body)">
{{ $t('view') }}
</el-button>
<el-button v-if="hasAuth(auth.update)" :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)">
<el-button
v-if="hasAuth(auth.update)"
:icon="useRenderIcon(EditPen)"
:size="size"
class="reset-margin"
link
type="primary"
@click="onUpdate(row)"
>
{{ $t('modify') }}
</el-button>
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')} ${row.templateName}?`" @confirm="onDelete(row)">

View File

@ -1,5 +1,6 @@
import { reactive } from 'vue';
import { $t } from '@/plugins/i18n';
import { ElTag } from 'element-plus';
// 表格列
export const columns: TableColumnList = [
@ -14,7 +15,21 @@ export const columns: TableColumnList = [
// 邮件内容
{ label: $t('emailTemplate_body'), prop: 'body' },
// 邮件类型
{ label: $t('emailTemplate_type'), prop: 'type' },
{ label: $t('emailTemplate_type'), prop: 'summary' },
// 是否默认
{
label: $t('isDefault'),
prop: 'isDefault',
formatter({ isDefault }) {
return isDefault ? (
<ElTag type={'success'}>{$t('default')}</ElTag>
) : (
<ElTag size={'large'} type={'danger'}>
{$t('no_default')}
</ElTag>
);
},
},
{ label: $t('table.updateTime'), prop: 'updateTime', sortable: true, width: 160 },
{ label: $t('table.createTime'), prop: 'createTime', sortable: true, width: 160 },
{ label: $t('table.createUser'), prop: 'createUser', slot: 'createUser', width: 130 },

View File

@ -29,6 +29,7 @@ export function onAdd() {
templateName: undefined,
emailUser: undefined,
subject: undefined,
isDefault: undefined,
body: undefined,
type: undefined,
},
@ -64,6 +65,7 @@ export function onUpdate(row: any) {
templateName: row.templateName,
emailUser: row.emailUser,
subject: row.subject,
isDefault: row.isDefault,
body: row.body,
type: row.type,
},

View File

@ -6,6 +6,8 @@ export interface FormItemProps {
emailUser: string;
// 主题
subject: string;
// 是否默认
isDefault: boolean;
// 邮件内容
body: string;
// 邮件类型

View File

@ -61,7 +61,15 @@ defineExpose({ formRef });
<!-- 是否为默认 -->
<el-form-item :label="$t('emailUsers_isDefault')" prop="isDefault">
<el-switch v-model="form.isDefault" :active-text="$t('default')" :active-value="true" :inactive-text="$t('no_default')" :inactive-value="false" :style="switchStyle" inline-prompt />
<el-switch
v-model="form.isDefault"
:active-text="$t('default')"
:active-value="true"
:inactive-text="$t('no_default')"
:inactive-value="false"
:style="switchStyle"
inline-prompt
/>
</el-form-item>
</el-form>
</template>

View File

@ -63,6 +63,7 @@ defineExpose({ formRef });
<el-date-picker
v-model="form.transactionDate"
:placeholder="$t('select') + $t('transactionDate')"
class="!w-[100%]"
format="YYYY/MM/DD HH:mm:ss"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"

View File

@ -1,5 +1,5 @@
// 抽离可公用的工具函数等用于系统管理页面逻辑
import { computed } from 'vue';
import { computed, type UnwrapRef } from 'vue';
import { useDark } from '@pureadmin/utils';
export function usePublicHooks() {
@ -36,3 +36,15 @@ export function usePublicHooks() {
tagStyle,
};
}
/**
*
* @param switchLoadMap
* @param index
* @param loading
*/
export const switchLoadMapControl = (switchLoadMap: Ref<UnwrapRef<any>>, index: number, loading: boolean) => {
switchLoadMap.value[index] = Object.assign({}, switchLoadMap.value[index], {
loading,
});
};

View File

@ -92,7 +92,15 @@ defineExpose({ formRef });
<re-col :sm="24" :value="12" :xs="24">
<el-form-item :label="$t('adminUser_dept')" prop="deptId">
<el-cascader v-model="form.deptId" :options="deptList" :placeholder="$t('select') + $t('adminUser_dept')" :props="deptSelector" class="w-full" clearable filterable>
<el-cascader
v-model="form.deptId"
:options="deptList"
:placeholder="$t('select') + $t('adminUser_dept')"
:props="deptSelector"
class="w-full"
clearable
filterable
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
@ -104,7 +112,15 @@ defineExpose({ formRef });
<!-- 用户状态 -->
<re-col :sm="24" :value="12" :xs="24">
<el-form-item :label="$t('adminUser_status')" prop="status">
<el-switch v-model="form.status" :active-text="$t('enable')" :active-value="false" :inactive-text="$t('disable')" :inactive-value="true" :style="switchStyle" inline-prompt />
<el-switch
v-model="form.status"
:active-text="$t('enable')"
:active-value="false"
:inactive-text="$t('disable')"
:inactive-value="true"
:style="switchStyle"
inline-prompt
/>
</el-form-item>
</re-col>

View File

@ -88,7 +88,13 @@ onMounted(() => {
<template>
<div :class="['flex', 'justify-between', deviceDetection() && 'flex-wrap']">
<tree ref="treeRef" :class="['mr-2', deviceDetection() ? 'w-full' : 'min-w-[200px]']" :treeData="deptList" :treeLoading="deptStore.loading" @tree-select="onTreeSelect" />
<tree
ref="treeRef"
:class="['mr-2', deviceDetection() ? 'w-full' : 'min-w-[200px]']"
:treeData="deptList"
:treeLoading="deptStore.loading"
@tree-select="onTreeSelect"
/>
<div :class="[deviceDetection() ? ['w-full', 'mt-2'] : 'w-[calc(100%-200px)]']">
<Auth :value="auth.search">
<el-form ref="formRef" :inline="true" :model="adminUserStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
@ -126,13 +132,21 @@ onMounted(() => {
<!-- 查询状态 -->
<el-form-item :label="$t('adminUser_status')" prop="status">
<el-select v-model="adminUserStore.form.status" :placeholder="`${$t('input')}${$t('adminUser_status')}`" class="!w-[180px]" clearable filterable>
<el-select
v-model="adminUserStore.form.status"
:placeholder="`${$t('input')}${$t('adminUser_status')}`"
class="!w-[180px]"
clearable
filterable
>
<el-option v-for="(item, index) in userStatus" :key="index" :label="item.label" :navigationBar="false" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="adminUserStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="adminUserStore.loading" type="primary" @click="onSearch">
{{ $t('search') }}
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
@ -173,7 +187,13 @@ onMounted(() => {
>
<!-- 显示头像 -->
<template #avatar="{ row }">
<el-image :preview-src-list="Array.of(row.avatar || UserAvatar)" :src="row.avatar || UserAvatar" class="w-[24px] h-[24px] rounded-full align-middle" fit="cover" preview-teleported>
<el-image
:preview-src-list="Array.of(row.avatar || UserAvatar)"
:src="row.avatar || UserAvatar"
class="w-[24px] h-[24px] rounded-full align-middle"
fit="cover"
preview-teleported
>
<template #error>
<div class="image-slot">
<img :src="UserAvatar" alt="" />
@ -218,7 +238,17 @@ onMounted(() => {
<template #operation="{ row }">
<!-- 修改 -->
<el-button v-if="hasAuth(auth.update)" :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
<el-button
v-if="hasAuth(auth.update)"
:icon="useRenderIcon(EditPen)"
:size="size"
class="reset-margin"
link
type="primary"
@click="onUpdate(row)"
>
{{ $t('modify') }}
</el-button>
<!-- 删除 -->
<el-popconfirm v-if="hasAuth(auth.deleted)" :title="`${$t('delete')} ${row.username}?`" @confirm="onDelete(row)">
@ -236,19 +266,27 @@ onMounted(() => {
<el-dropdown-menu>
<!-- 上传头像 -->
<el-dropdown-item v-if="hasAuth(auth.uploadAvatarByAdmin)">
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Upload)" :size="size" link type="primary" @click="onUploadAvatar(row)"> {{ $t('upload_avatar') }} </el-button>
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Upload)" :size="size" link type="primary" @click="onUploadAvatar(row)">
{{ $t('upload_avatar') }}
</el-button>
</el-dropdown-item>
<!-- 重置密码 -->
<el-dropdown-item v-if="hasAuth(auth.updateUserPasswordByAdmin)">
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Password)" :size="size" link type="primary" @click="onResetPassword(row)"> {{ $t('reset_passwords') }} </el-button>
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Password)" :size="size" link type="primary" @click="onResetPassword(row)">
{{ $t('reset_passwords') }}
</el-button>
</el-dropdown-item>
<!-- 分配角色 -->
<el-dropdown-item v-if="hasAuth(auth.updateUserPasswordByAdmin)">
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Role)" :size="size" link type="primary" @click="onAssignRolesToUser(row)"> {{ $t('assign_roles') }} </el-button>
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Role)" :size="size" link type="primary" @click="onAssignRolesToUser(row)">
{{ $t('assign_roles') }}
</el-button>
</el-dropdown-item>
<!-- 强制下线 -->
<el-dropdown-item v-if="hasAuth(auth.forcedOffline)">
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Airplane)" :size="size" link type="primary" @click="onForcedOffline(row)"> {{ $t('forced_offline') }} </el-button>
<el-button :class="tableSelectButtonClass" :icon="useRenderIcon(Airplane)" :size="size" link type="primary" @click="onForcedOffline(row)">
{{ $t('forced_offline') }}
</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</template>

View File

@ -14,7 +14,14 @@ import { currentMouth, currentYear, shortcutsAllMouth } from '@/enums/dateEnums'
const { isDark } = useDark();
//
const shortcuts = [{ text: $t('thisMonth'), value: currentMouth }, { text: $t('thisYear'), value: currentYear }, ...shortcutsAllMouth()];
const shortcuts = [
{ text: $t('thisMonth'), value: currentMouth },
{
text: $t('thisYear'),
value: currentYear,
},
...shortcutsAllMouth(),
];
onMounted(() => {
onSearch();
@ -69,13 +76,16 @@ onMounted(() => {
<el-card class="line-card" shadow="never">
<div class="flex justify-between">
<span class="text-md font-medium"> {{ $t('percentageOfExpenditure') }} </span>
<div :style="{ backgroundColor: isDark ? 'transparent' : '#fff5f4' }" class="w-8 h-8 flex justify-center items-center rounded-md">
<IconifyIconOnline color="#F56C6C" icon="tabler:percentage-66" width="18" />
<div
:style="{ backgroundColor: isDark ? 'transparent' : 'rgba(227,225,239,0.67)' }"
class="w-8 h-8 flex justify-center items-center rounded-md"
>
<IconifyIconOnline color="#7872E8" icon="tabler:percentage-66" width="18" />
</div>
</div>
<div class="flex justify-between items-start mt-3">
<div class="w-1/2">
<span class="font-medium text-purple-500" style="font-size: 1.6em">{{ `${expendPercent} %` }}</span>
<span class="font-medium text-violet-500" style="font-size: 1.6em">{{ `${expendPercent} %` }}</span>
</div>
<ChartRound :data="expendPercent" class="!w-1/2" />
</div>

View File

@ -103,7 +103,7 @@ function homeCardFun(income, expend) {
const profitValue = (income - expend).toFixed(2);
data = {
icon: 'hugeicons:profit',
bgColor: '#fff5f4',
bgColor: '#eef4ff',
color: '#409EFF',
duration: 2200,
name: $t('amountOfProfit'),