初始化

This commit is contained in:
2026-03-03 09:53:54 +08:00
commit 3f349a35a4
437 changed files with 65639 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<!-- 工作台页面 -->
<template>
<div>
<template v-if="userInfo.dashboard === 'statistics'">
<CardList></CardList>
<ElRow :gutter="20">
<ElCol :sm="24" :md="12" :lg="10">
<ActiveUser />
</ElCol>
<ElCol :sm="24" :md="12" :lg="14">
<SalesOverview />
</ElCol>
</ElRow>
</template>
<template v-if="userInfo.dashboard === 'work'">
<ElRow :gutter="20">
<ElCol :sm="24" :md="24" :lg="12">
<NewUser />
</ElCol>
<ElCol :sm="24" :md="12" :lg="6">
<Dynamic />
</ElCol>
<ElCol :sm="24" :md="12" :lg="6">
<TodoList />
</ElCol>
</ElRow>
</template>
<AboutProject />
</div>
</template>
<script setup lang="ts">
import CardList from './modules/card-list.vue'
import ActiveUser from './modules/active-user.vue'
import SalesOverview from './modules/sales-overview.vue'
import AboutProject from './modules/about-project.vue'
import NewUser from './modules/new-user.vue'
import Dynamic from './modules/dynamic-stats.vue'
import TodoList from './modules/todo-list.vue'
import { useCommon } from '@/hooks/core/useCommon'
import { useUserStore } from '@/store/modules/user'
defineOptions({ name: 'Console' })
const userStore = useUserStore()
const userInfo = userStore.getUserInfo
const { scrollToTop } = useCommon()
scrollToTop()
</script>

View File

@@ -0,0 +1,43 @@
<template>
<div class="art-card p-5 flex-b mb-5 max-sm:mb-4">
<div>
<h2 class="text-2xl font-medium">关于项目</h2>
<p class="text-g-700 mt-1">{{ systemName }} 是一款兼具设计美学与高效开发的后台系统</p>
<p class="text-g-700 mt-1">使用了 webman + Vue3 + Element Plus 高性能高颜值技术栈</p>
<div class="flex flex-wrap gap-3.5 max-w-150 mt-9">
<div
class="w-60 flex-cb h-12.5 px-3.5 border border-g-300 c-p rounded-lg text-sm bg-g-100 duration-300 hover:-translate-y-1 max-sm:w-full"
v-for="link in linkList"
:key="link.label"
@click="goPage(link.url)"
>
<span class="text-g-700">{{ link.label }}</span>
<ArtSvgIcon icon="ri:arrow-right-s-line" class="text-lg text-g-600" />
</div>
</div>
</div>
<img class="w-75 max-md:!hidden" src="@imgs/draw/draw1.png" alt="draw1" />
</div>
</template>
<script setup lang="ts">
import AppConfig from '@/config'
const systemName = AppConfig.systemInfo.name
const linkList = [
{ label: '项目官网', url: 'https://saithink.top/' },
{ label: '文档', url: 'https://saithink.top/documents/' },
{ label: 'Github', url: 'https://github.com/saithink/saiadmin' },
{ label: '插件市场', url: 'https://saas.saithink.top/' }
]
/**
* 在新标签页中打开指定 URL
* @param url 要打开的网页地址
*/
const goPage = (url: string): void => {
window.open(url, '_blank', 'noopener,noreferrer')
}
</script>

View File

@@ -0,0 +1,38 @@
<template>
<div class="art-card h-105 p-4 box-border mb-5 max-sm:mb-4">
<div class="art-card-header">
<div class="title">
<h4>月度登录汇总</h4>
</div>
</div>
<ArtBarChart
class="box-border p-2"
barWidth="50%"
height="calc(100% - 40px)"
:showAxisLine="false"
:data="yData"
:xAxisData="xData"
/>
</div>
</template>
<script setup lang="ts">
import { fetchLoginBarChart } from '@/api/dashboard'
/**
* 登录数据
*/
const yData = ref<number[]>([])
/**
* 时间数据
*/
const xData = ref<string[]>([])
onMounted(async () => {
fetchLoginBarChart().then((data) => {
yData.value = data.login_count
xData.value = data.login_month
})
})
</script>

View File

@@ -0,0 +1,93 @@
<template>
<ElRow :gutter="20" class="flex">
<ElCol :sm="12" :md="6" :lg="6">
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
<span class="text-g-700 text-sm">用户统计</span>
<ArtCountTo class="text-[26px] font-medium mt-2" :target="statData.user" :duration="1300" />
<div class="flex-c mt-1">
<span class="text-xs text-g-600">较上周</span>
<span class="ml-1 text-xs font-semibold text-success">+10%</span>
</div>
<div
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
>
<ArtSvgIcon icon="ri:group-line" class="text-xl text-theme" />
</div>
</div>
</ElCol>
<ElCol :sm="12" :md="6" :lg="6">
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
<span class="text-g-700 text-sm">附件统计</span>
<ArtCountTo
class="text-[26px] font-medium mt-2"
:target="statData.attach"
:duration="1300"
/>
<div class="flex-c mt-1">
<span class="text-xs text-g-600">较上周</span>
<span class="ml-1 text-xs font-semibold text-success">+10%</span>
</div>
<div
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
>
<ArtSvgIcon icon="ri:attachment-line" class="text-xl text-theme" />
</div>
</div>
</ElCol>
<ElCol :sm="12" :md="6" :lg="6">
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
<span class="text-g-700 text-sm">登录统计</span>
<ArtCountTo
class="text-[26px] font-medium mt-2"
:target="statData.login"
:duration="1300"
/>
<div class="flex-c mt-1">
<span class="text-xs text-g-600">较上周</span>
<span class="ml-1 text-xs font-semibold text-success">+12%</span>
</div>
<div
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
>
<ArtSvgIcon icon="ri:fire-line" class="text-xl text-theme" />
</div>
</div>
</ElCol>
<ElCol :sm="12" :md="6" :lg="6">
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
<span class="text-g-700 text-sm">操作统计</span>
<ArtCountTo
class="text-[26px] font-medium mt-2"
:target="statData.operate"
:duration="1300"
/>
<div class="flex-c mt-1">
<span class="text-xs text-g-600">较上周</span>
<span class="ml-1 text-xs font-semibold text-danger">-5%</span>
</div>
<div
class="absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"
>
<ArtSvgIcon icon="ri:pie-chart-line" class="text-xl text-theme" />
</div>
</div>
</ElCol>
</ElRow>
</template>
<script setup lang="ts">
import { fetchStatistics } from '@/api/dashboard'
const statData = ref({
user: 0,
attach: 0,
login: 0,
operate: 0
})
onMounted(() => {
fetchStatistics().then((data) => {
statData.value = data
})
})
</script>

View File

@@ -0,0 +1,79 @@
<template>
<div class="art-card h-128 p-5 mb-5 max-sm:mb-4">
<div class="art-card-header">
<div class="title">
<h4>动态</h4>
<p>新增<span class="text-success">+6</span></p>
</div>
</div>
<div class="h-9/10 mt-2 overflow-hidden">
<ElScrollbar>
<div
class="h-17.5 leading-17.5 border-b border-g-300 text-sm overflow-hidden last:border-b-0"
v-for="(item, index) in list"
:key="index"
>
<span class="text-g-800 font-medium">{{ item.username }}</span>
<span class="mx-2 text-g-600">{{ item.type }}</span>
<span class="text-theme">{{ item.target }}</span>
</div>
</ElScrollbar>
</div>
</div>
</template>
<script setup lang="ts">
interface DynamicItem {
username: string
type: string
target: string
}
/**
* 用户动态列表
* 记录用户的关注、发文、提问、兑换等各类活动
*/
const list = reactive<DynamicItem[]>([
{
username: '中小鱼',
type: '关注了',
target: '誶誶淰'
},
{
username: '何小荷',
type: '发表文章',
target: 'Vue3 + Typescript + Vite 项目实战笔记'
},
{
username: '中小鱼',
type: '关注了',
target: '誶誶淰'
},
{
username: '何小荷',
type: '发表文章',
target: 'Vue3 + Typescript + Vite 项目实战笔记'
},
{
username: '誶誶淰',
type: '提出问题',
target: '主题可以配置吗'
},
{
username: '发呆草',
type: '兑换了物品',
target: '《奇特的一生》'
},
{
username: '甜筒',
type: '关闭了问题',
target: '发呆草'
},
{
username: '冷月呆呆',
type: '兑换了物品',
target: '《高效人士的七个习惯》'
}
])
</script>

View File

@@ -0,0 +1,169 @@
<template>
<div class="art-card p-5 h-128 overflow-hidden mb-5 max-sm:mb-4">
<div class="art-card-header">
<div class="title">
<h4>新用户</h4>
<p>这个月增长<span class="text-success">+20%</span></p>
</div>
<ElRadioGroup v-model="radio2">
<ElRadioButton value="本月" label="本月"></ElRadioButton>
<ElRadioButton value="上月" label="上月"></ElRadioButton>
<ElRadioButton value="今年" label="今年"></ElRadioButton>
</ElRadioGroup>
</div>
<ArtTable
class="w-full"
:data="tableData"
style="width: 100%"
size="large"
:border="false"
:stripe="false"
:header-cell-style="{ background: 'transparent' }"
>
<template #default>
<ElTableColumn label="头像" prop="avatar" width="150px">
<template #default="scope">
<div style="display: flex; align-items: center">
<img class="size-9 rounded-lg" :src="scope.row.avatar" alt="avatar" />
<span class="ml-2">{{ scope.row.username }}</span>
</div>
</template>
</ElTableColumn>
<ElTableColumn label="地区" prop="province" />
<ElTableColumn label="性别" prop="avatar">
<template #default="scope">
<div style="display: flex; align-items: center">
<span style="margin-left: 10px">{{ scope.row.sex === 1 ? '男' : '女' }}</span>
</div>
</template>
</ElTableColumn>
<ElTableColumn label="进度" width="240">
<template #default="scope">
<ElProgress
:percentage="scope.row.pro"
:color="scope.row.color"
:stroke-width="4"
:aria-label="`${scope.row.username}的完成进度: ${scope.row.pro}%`"
/>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
</template>
<script setup lang="ts">
import avatar1 from '@/assets/images/avatar/avatar1.webp'
import avatar2 from '@/assets/images/avatar/avatar2.webp'
import avatar3 from '@/assets/images/avatar/avatar3.webp'
import avatar4 from '@/assets/images/avatar/avatar4.webp'
import avatar5 from '@/assets/images/avatar/avatar5.webp'
import avatar6 from '@/assets/images/avatar/avatar6.webp'
interface UserTableItem {
username: string
province: string
sex: 0 | 1
age: number
percentage: number
pro: number
color: string
avatar: string
}
const ANIMATION_DELAY = 100
const radio2 = ref('本月')
/**
* 新用户表格数据
* 包含用户基本信息和完成进度
*/
const tableData = reactive<UserTableItem[]>([
{
username: '中小鱼',
province: '北京',
sex: 0,
age: 22,
percentage: 60,
pro: 0,
color: 'var(--art-primary)',
avatar: avatar1
},
{
username: '何小荷',
province: '深圳',
sex: 1,
age: 21,
percentage: 20,
pro: 0,
color: 'var(--art-secondary)',
avatar: avatar2
},
{
username: '誶誶淰',
province: '上海',
sex: 1,
age: 23,
percentage: 60,
pro: 0,
color: 'var(--art-warning)',
avatar: avatar3
},
{
username: '发呆草',
province: '长沙',
sex: 0,
age: 28,
percentage: 50,
pro: 0,
color: 'var(--art-info)',
avatar: avatar4
},
{
username: '甜筒',
province: '浙江',
sex: 1,
age: 26,
percentage: 70,
pro: 0,
color: 'var(--art-error)',
avatar: avatar5
},
{
username: '冷月呆呆',
province: '湖北',
sex: 1,
age: 25,
percentage: 90,
pro: 0,
color: 'var(--art-success)',
avatar: avatar6
}
])
/**
* 添加进度条动画效果
* 延迟后将进度值从 0 更新到目标百分比,触发动画
*/
const addAnimation = (): void => {
setTimeout(() => {
tableData.forEach((item) => {
item.pro = item.percentage
})
}, ANIMATION_DELAY)
}
onMounted(() => {
addAnimation()
})
</script>
<style lang="scss" scoped>
.art-card {
:deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
color: var(--el-color-primary) !important;
background: transparent !important;
}
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div class="art-card h-105 p-5 mb-5 max-sm:mb-4">
<div class="art-card-header">
<div class="title">
<h4>近期登录统计</h4>
</div>
</div>
<ArtLineChart
height="calc(100% - 40px)"
:data="yData"
:xAxisData="xData"
:showAreaColor="true"
:showAxisLine="false"
/>
</div>
</template>
<script setup lang="ts">
import { fetchLoginChart } from '@/api/dashboard'
/**
* 登录数据
*/
const yData = ref<number[]>([])
/**
* 时间数据
*/
const xData = ref<string[]>([])
onMounted(async () => {
fetchLoginChart().then((data) => {
yData.value = data.login_count
xData.value = data.login_date
})
})
</script>

View File

@@ -0,0 +1,71 @@
<template>
<div class="art-card h-128 p-5 mb-5 max-sm:mb-4">
<div class="art-card-header">
<div class="title">
<h4>代办事项</h4>
<p>待处理<span class="text-danger">3</span></p>
</div>
</div>
<div class="h-[calc(100%-40px)] overflow-auto">
<ElScrollbar>
<div
class="flex-cb h-17.5 border-b border-g-300 text-sm last:border-b-0"
v-for="(item, index) in list"
:key="index"
>
<div>
<p class="text-sm">{{ item.username }}</p>
<p class="text-g-500 mt-1">{{ item.date }}</p>
</div>
<ElCheckbox v-model="item.complate" />
</div>
</ElScrollbar>
</div>
</div>
</template>
<script setup lang="ts">
interface TodoItem {
username: string
date: string
complate: boolean
}
/**
* 待办事项列表
* 记录每日工作任务及完成状态
*/
const list = reactive<TodoItem[]>([
{
username: '查看今天工作内容',
date: '上午 09:30',
complate: true
},
{
username: '回复邮件',
date: '上午 10:30',
complate: true
},
{
username: '工作汇报整理',
date: '上午 11:00',
complate: true
},
{
username: '产品需求会议',
date: '下午 02:00',
complate: false
},
{
username: '整理会议内容',
date: '下午 03:30',
complate: false
},
{
username: '明天工作计划',
date: '下午 06:30',
complate: false
}
])
</script>

View File

@@ -0,0 +1,395 @@
<!-- 个人中心页面 -->
<template>
<div class="w-full h-full p-0 bg-transparent border-none shadow-none">
<div class="relative flex-b mt-2.5 max-md:block max-md:mt-1">
<div class="w-112 mr-5 max-md:w-full max-md:mr-0">
<div class="art-card-sm relative p-9 pb-6 overflow-hidden text-center">
<img
class="absolute top-0 left-0 w-full h-50 object-cover"
src="@imgs/user/user-bg.jpg"
/>
<SaImageUpload
class="w-20 h-20 mt-30 mx-auto"
:width="80"
:height="80"
:showTips="false"
v-model="avatar"
@change="handleAvatarChange"
round
/>
<h2 class="mt-5 text-xl font-normal">{{ userInfo.username }}</h2>
<div class="w-75 mx-auto mt-2.5 text-left">
<div class="mt-2.5">
<ArtSvgIcon icon="ri:user-line" class="text-g-700" />
<span class="ml-2 text-sm">{{ userInfo.realname }}</span>
</div>
<div class="mt-2.5">
<ArtSvgIcon
:icon="userInfo.gender === '1' ? 'ri:men-line' : 'ri:women-line'"
class="text-g-700"
/>
<span class="ml-2 text-sm">{{ userInfo.gender === '1' ? '男' : '女' }}</span>
</div>
<div class="mt-2.5">
<ArtSvgIcon icon="ri:mail-line" class="text-g-700" />
<span class="ml-2 text-sm">{{ userInfo.email }}</span>
</div>
<div class="mt-2.5">
<ArtSvgIcon icon="ri:phone-line" class="text-g-700" />
<span class="ml-2 text-sm">{{ userInfo.phone }}</span>
</div>
<div class="mt-2.5">
<ArtSvgIcon icon="ri:dribbble-fill" class="text-g-700" />
<span class="ml-2 text-sm">{{ userInfo.department?.name }}</span>
</div>
</div>
</div>
<div class="art-card-sm py-5 h-128 my-5">
<div class="art-card-header border-b border-g-300">
<h1 class="p-4 text-xl font-normal">日志信息</h1>
<ElRadioGroup v-model="logType">
<ElRadioButton :value="1" label="登录日志"></ElRadioButton>
<ElRadioButton :value="2" label="操作日志"></ElRadioButton>
</ElRadioGroup>
</div>
<div class="mt-7.5">
<el-timeline class="pl-5 mt-3" v-if="logType === 1 && loginLogList.length > 0">
<el-timeline-item
v-for="(item, idx) in loginLogList"
:key="idx"
:timestamp="`您于 ${item.login_time} 登录系统`"
placement="top"
>
<div class="py-2 text-xs">
<span>地理位置{{ item.ip_location || '未知' }}</span>
<span class="ml-2">操作系统{{ item.os }}</span>
</div>
</el-timeline-item>
</el-timeline>
<el-timeline class="pl-5 mt-3" v-if="logType === 2 && operationLogList.length > 0">
<el-timeline-item
v-for="(item, idx) in operationLogList"
:key="idx"
:timestamp="`您于 ${item.create_time} 执行了 ${item.service_name}`"
placement="top"
>
<div class="py-2 text-xs">
<span>地理位置{{ item.ip_location || '未知' }}</span>
<span class="ml-2">路由{{ item.router }}</span>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
<div class="flex-1 overflow-hidden max-md:w-full max-md:mt-3.5">
<div class="art-card-sm">
<h1 class="p-4 text-xl font-normal border-b border-g-300">基本设置</h1>
<ElForm
:model="form"
class="box-border p-5 [&>.el-row_.el-form-item]:w-[calc(50%-10px)] [&>.el-row_.el-input]:w-full [&>.el-row_.el-select]:w-full"
ref="ruleFormRef"
:rules="rules"
label-width="86px"
label-position="top"
>
<ElRow>
<ElFormItem label="姓名" prop="realname">
<ElInput v-model="form.realname" :disabled="!isEdit" />
</ElFormItem>
<ElFormItem label="性别" prop="gender" class="ml-5">
<SaSelect
v-model="form.gender"
placeholder="请选择性别"
dict="gender"
valueType="string"
:disabled="!isEdit"
/>
</ElFormItem>
</ElRow>
<ElRow>
<ElFormItem label="邮箱" prop="email">
<ElInput v-model="form.email" :disabled="!isEdit" />
</ElFormItem>
<ElFormItem label="手机" prop="phone" class="ml-5">
<ElInput v-model="form.phone" :disabled="!isEdit" />
</ElFormItem>
</ElRow>
<ElFormItem label="个人介绍" prop="signed" class="h-32">
<ElInput type="textarea" :rows="4" v-model="form.signed" :disabled="!isEdit" />
</ElFormItem>
<div class="flex-c justify-end [&_.el-button]:!w-27.5">
<ElButton type="primary" class="w-22.5" v-ripple @click="edit">
{{ isEdit ? '保存' : '编辑' }}
</ElButton>
</div>
</ElForm>
</div>
<div class="art-card-sm h-128 my-5">
<h1 class="p-4 text-xl font-normal border-b border-g-300">更改密码</h1>
<ElForm
:model="pwdForm"
:rules="pwdRules"
ref="pwdFormRef"
class="box-border p-5"
label-width="86px"
label-position="top"
>
<ElFormItem label="当前密码" prop="oldPassword">
<ElInput
v-model="pwdForm.oldPassword"
type="password"
:disabled="!isEditPwd"
show-password
/>
</ElFormItem>
<ElFormItem label="新密码" prop="newPassword">
<ElInput
v-model="pwdForm.newPassword"
type="password"
:disabled="!isEditPwd"
show-password
@input="checkSafe"
/>
</ElFormItem>
<ElFormItem label="密码安全度" prop="passwordSafePercent">
<ElProgress
:percentage="passwordSafePercent"
:show-text="false"
class="w-full"
status="success"
:stroke-width="12"
/>
</ElFormItem>
<ElFormItem label="确认新密码" prop="confirmPassword">
<ElInput
v-model="pwdForm.confirmPassword"
type="password"
:disabled="!isEditPwd"
show-password
/>
</ElFormItem>
<div class="flex-c justify-end [&_.el-button]:!w-27.5">
<ElButton type="primary" class="w-22.5" v-ripple @click="editPwd">
{{ isEditPwd ? '保存' : '编辑' }}
</ElButton>
</div>
</ElForm>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/modules/user'
import { fetchGetLogin, fetchGetOperate, updateUserInfo, modifyPassword } from '@/api/auth'
import type { FormInstance, FormRules } from 'element-plus'
defineOptions({ name: 'UserCenter' })
const userStore = useUserStore()
const userInfo = computed(() => userStore.getUserInfo)
const isEdit = ref(false)
const isEditPwd = ref(false)
const date = ref('')
/**
* 用户信息表单
*/
const form = toReactive(userStore.info)
const ruleFormRef = ref<FormInstance>()
const pwdFormRef = ref<FormInstance>()
const passwordSafePercent = ref(0)
const avatar = ref('')
/**
* 密码修改表单
*/
const pwdForm = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: ''
})
/**
* 表单验证规则
*/
const rules = reactive<FormRules>({
realname: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
phone: [{ required: true, message: '请输入手机号码', trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'blur' }]
})
const pwdRules = reactive<FormRules>({
oldPassword: [{ required: true, message: '请输入当前密码', trigger: 'blur' }],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
],
confirmPassword: [{ required: true, message: '请确认新密码', trigger: 'blur' }]
})
const loginLogList = ref<Api.Common.ApiData[]>([])
const operationLogList = ref<Api.Common.ApiData[]>([])
const logType = ref(1) // 1: 登录日志, 2: 操作日志
// 监听radio切换
watch(logType, (newVal) => {
if (newVal === 1 && loginLogList.value.length === 0) {
loadLogin()
} else if (newVal === 2 && operationLogList.value.length === 0) {
loadOperate()
}
})
const loadLogin = async () => {
try {
const data = await fetchGetLogin({
page: 1,
limit: 5,
orderType: 'desc'
})
loginLogList.value = data.data || []
} catch (error) {
console.error('加载登录日志失败:', error)
}
}
const loadOperate = async () => {
try {
const data = await fetchGetOperate({
page: 1,
limit: 5,
orderType: 'desc'
})
operationLogList.value = data.data || []
} catch (error) {
console.error('加载操作日志失败:', error)
}
}
onMounted(() => {
avatar.value = userInfo.value.avatar || ''
getDate()
loadLogin()
loadOperate()
})
/**
* 根据当前时间获取问候语
*/
const getDate = () => {
const h = new Date().getHours()
if (h >= 6 && h < 9) date.value = '早上好'
else if (h >= 9 && h < 11) date.value = '上午好'
else if (h >= 11 && h < 13) date.value = '中午好'
else if (h >= 13 && h < 18) date.value = '下午好'
else if (h >= 18 && h < 24) date.value = '晚上好'
else date.value = '很晚了,早点睡'
}
const handleAvatarChange = async (val: string | string[]) => {
if (!val) {
return
}
try {
await updateUserInfo({
id: form.id,
avatar: val
})
userStore.setAvatar(val as string)
ElMessage.success('修改头像成功')
} catch (error) {
console.log('表单验证失败:', error)
}
}
/**
* 切换用户信息编辑状态
*/
const edit = async () => {
if (isEdit.value) {
try {
await ruleFormRef.value?.validate()
await updateUserInfo(form)
ElMessage.success('修改成功')
isEdit.value = !isEdit.value
} catch (error) {
console.log('表单验证失败:', error)
}
} else {
isEdit.value = !isEdit.value
}
}
/**
* 切换密码编辑状态
*/
const editPwd = async () => {
if (isEditPwd.value) {
try {
await pwdFormRef.value?.validate()
if (pwdForm.newPassword !== pwdForm.confirmPassword) {
ElMessage.error('确认密码与新密码不一致')
return
}
await modifyPassword(pwdForm)
ElMessage.success('修改成功')
Object.assign(pwdForm, { oldPassword: '', newPassword: '', confirmPassword: '' })
isEditPwd.value = !isEditPwd.value
} catch (error) {
console.log('表单验证失败:', error)
}
} else {
isEditPwd.value = !isEditPwd.value
}
}
/**
* 检查密码安全程度
* @param password 密码
*/
const checkSafe = (password: string) => {
if (password.length < 1) {
passwordSafePercent.value = 0
return
}
if (!(password.length >= 6)) {
passwordSafePercent.value = 0
return
}
passwordSafePercent.value = 10
if (/\d/.test(password)) {
passwordSafePercent.value += 10
}
if (/[a-z]/.test(password)) {
passwordSafePercent.value += 10
}
if (/[A-Z]/.test(password)) {
passwordSafePercent.value += 30
}
if (/[`~!@#$%^&*()_+<>?:"{},./;'[\]]/.test(password)) {
passwordSafePercent.value += 40
}
}
</script>