Browse Source

feat: 修改密码弹窗,工作台刷新,修改密码完成跳转页面(都缺接口)

lihui/feature-20250623144029-金币前端lihui
lihui 3 weeks ago
parent
commit
d32165a337
  1. 84
      src/components/PasswordSuccess.vue
  2. 303
      src/components/changePassword.vue
  3. 2
      src/router/index.js
  4. 31
      src/views/home.vue
  5. 61
      src/views/workspace/index.vue

84
src/components/PasswordSuccess.vue

@ -0,0 +1,84 @@
<template>
<div class="success-popup">
<div class="popup-content">
<div class="icon">
<el-icon :size="48" color="#67c23a">
<SuccessFilled />
</el-icon>
</div>
<div class="title">修改密码成功</div>
<div class="desc">系统将在 {{ countdown }} 秒后自动跳转至登录页</div>
<el-button type="primary" @click="immediateJump">立即跳转</el-button>
</div>
</div>
</template>
<script setup>
import {ref, onMounted} from 'vue';
import {ElIcon, ElButton} from 'element-plus';
import {SuccessFilled} from '@element-plus/icons-vue';
import {useRouter} from 'vue-router';
const countdown = ref(3);
const router = useRouter();
//
const startCountdown = () => {
const timer = setInterval(() => {
countdown.value--;
if (countdown.value === 0) {
clearInterval(timer);
//
// 使 window.location.href
// window.location.href = '/login';
router.replace ('/login');
// router.push('/login');
}
}, 1000);
};
const immediateJump = () => {
// window.location.href = '/login';
//
router.replace ('/login');
};
onMounted(() => {
startCountdown();
});
</script>
<style scoped>
.success-popup {
position: fixed;
top: 0;
left: 0;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
width: 100%;
height: 100%;
}
.popup-content {
background: #fff;
padding: 30px;
border-radius: 8px;
text-align: center;
}
.icon {
margin-bottom: 16px;
}
.title {
font-size: 18px;
font-weight: bold;
margin-bottom: 8px;
}
.desc {
margin-bottom: 24px;
color: #999;
}
</style>

303
src/components/changePassword.vue

@ -0,0 +1,303 @@
<template>
<div class="form-container">
<el-form
ref="passwdFormRef"
:model="passwd"
:rules="rules"
label-width="100px"
class="password-form"
>
<h3 class="form-title">修改密码</h3>
<!-- 原密码 -->
<el-form-item prop="oldVal" label="原密码">
<el-input
v-model.trim="passwd.oldVal"
type="password"
placeholder="请输入原密码"
show-password
/>
</el-form-item>
<!-- 新密码 -->
<el-form-item prop="newVal" label="新密码">
<el-input
v-model.trim="passwd.newVal"
type="password"
placeholder="请输入新密码"
show-password
/>
</el-form-item>
<!-- 重复密码 -->
<el-form-item prop="repeatNewVal" label="重复密码">
<el-input
v-model.trim="passwd.repeatNewVal"
type="password"
placeholder="请再次输入新密码"
show-password
/>
</el-form-item>
<!-- 密码规则提示 -->
<div class="rule-tips">
<div :class="isLengthValid ? 'tip pass' : 'tip neutral'">
<el-icon>
<component :is="isLengthValid ? SuccessFilled : SuccessFilled"/>
</el-icon>
密码由8-16位数字字母或符号组成
</div>
<div :class="isComplexValid ? 'tip pass' : 'tip neutral'">
<el-icon>
<component :is="isComplexValid ? SuccessFilled : SuccessFilled"/>
</el-icon>
至少含2种以上字符
</div>
<div v-if="errorMsg" class="tip fail">
<el-icon>
<CircleCloseFilled/>
</el-icon>
{{ errorMsg }}
</div>
</div>
<!-- 按钮 -->
<div class="button-group">
<el-button
type="primary"
@click="onSubmit"
style="width: 300px"
:loading="loading"
:disabled="!isLengthValid || !isComplexValid"
>
{{ loading ? '修改中...' : '确定' }}
</el-button>
</div>
</el-form>
</div>
</template>
<script setup>
import {ref, reactive, computed, watch} from 'vue'
import {CircleCloseFilled, SuccessFilled} from '@element-plus/icons-vue'
import {ElMessage} from "element-plus";
import API from '@/util/http'
import PasswordSuccess from './PasswordSuccess.vue';
import router from "@/router/index.js";
//
const emit = defineEmits(['confirm'])
const passwdFormRef = ref(null)
const passwd = reactive({
oldVal: '',
newVal: '',
repeatNewVal: ''
})
const errorMsg = ref('')
//
const isLengthValid = computed(() => passwd.newVal.length >= 8 && passwd.newVal.length <= 16)
const isComplexValid = computed(() => {
const rules = [/\d/, /[a-z]/, /[A-Z]/, /[^a-zA-Z0-9]/]
return rules.filter((r) => r.test(passwd.newVal)).length >= 2
})
watch(() => passwd.newVal, (val) => {
if (val && val === passwd.oldVal) {
errorMsg.value = '新密码不能与旧密码一致'
} else {
errorMsg.value = ''
}
})
//
const loading = ref(false)
//
const rules = reactive({
oldVal: [{required: true, message: '请输入原密码', trigger: 'blur'}],
newVal: [
{required: true, message: '新密码不能为空', trigger: 'blur'},
{
validator: (rule, value, callback) => {
if (value === passwd.oldVal) {
callback(new Error('新密码不能与旧密码一致'))
} else if (value.length < 8 || value.length > 16) {
callback(new Error('长度应在 8 到 16 个字符'))
} else {
const types = [/\d/, /[a-z]/, /[A-Z]/, /[^a-zA-Z0-9]/]
const matchCount = types.filter((r) => r.test(value)).length
if (matchCount < 2) {
callback(new Error('密码至少包含两种类型(数字、字母或符号)'))
} else {
callback()
}
}
},
trigger: 'blur'
}
],
repeatNewVal: [
{required: true, message: '请再次输入新密码', trigger: 'blur'},
{
validator: (rule, value, callback) => {
if (value !== passwd.newVal) {
callback(new Error('两次输入密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
})
// //
// const changePassword = async function () {
// try {
// const params = {
// oldVal: passwd.oldVal,
// newVal: passwd.newVal
// }
//
// const result = await API({url: '/user/changePassword', data: params})
// console.log('@@@@@@@@@@@:', result)
// if(result.status === 200){
// //
//
// ElMessage.success('')
// //
// resetFields()
// router.replace ('/PasswordSuccess');
// }else if(result === 400){
// //todo
// }
//
// } catch (error) {
// console.log('re:', result)
// console.error('', error)
// ElMessage.error('')
// // finally
// throw error
// }
// }
//
const changePassword = async function () {
try {
// API
const result = {
status: 200,
data: {success: true, message: '密码修改成功'}
};
console.log('修改密码结果:', result);
//
if (result.status === 200 ) {
//
ElMessage.success(result.data.message || '修改密码成功');
//
resetFields();
router.replace ('/PasswordSuccess');
return result;
} else {
// API
const errorMsg = result.data.message || '密码修改失败';
ElMessage.error(errorMsg);
throw new Error(errorMsg);
}
} catch (error) {
// API
console.error('修改密码失败:', error);
ElMessage.error(error.message || '操作失败,请重试');
throw error;
}
};
//
const resetFields = () => {
passwdFormRef.value.resetFields()
errorMsg.value = ''
}
//
const onSubmit = () => {
//
if (loading.value) return
passwdFormRef.value.validate(async (valid) => {
if (valid) {
loading.value = true //
try {
await changePassword() //
emit('confirm')
} finally {
loading.value = false //
}
} else {
console.log('表单校验失败')
}
})
}
</script>
<style scoped>
.form-container {
justify-content: center;
align-items: center;
}
.password-form {
border-radius: 8px;
}
.form-title {
text-align: left;
margin-bottom: 24px;
font-size: 20px;
font-weight: bold;
}
.button-group {
display: flex;
justify-content: center;
margin-top: 24px;
}
.rule-tips {
margin-left: 100px;
margin-bottom: 12px;
}
.tip {
font-size: 14px;
margin: 4px 0;
display: flex;
align-items: center;
}
.pass {
color: #67c23a;
}
.neutral {
color: #999;
}
.fail {
color: #f56c6c;
}
.rule-tips .el-icon {
margin-right: 6px;
}
</style>

2
src/router/index.js

@ -71,6 +71,8 @@ const router = createRouter({
{ path: '/noPermission', name: "noPermission", component: () => import("../views/noPermissionPage.vue") } { path: '/noPermission', name: "noPermission", component: () => import("../views/noPermissionPage.vue") }
] ]
}, },
// 跳转页面
{ path: '/PasswordSuccess', name: "PasswordSuccess.vue", component: () => import("../components/PasswordSuccess.vue") },
] ]
}); });

31
src/views/home.vue

@ -1,16 +1,14 @@
<script setup> <script setup>
// //
import { ref, onMounted } from 'vue'
import {onMounted, ref} from 'vue'
import {useRouter} from 'vue-router' import {useRouter} from 'vue-router'
import ElementPlus from 'element-plus'
import { VscGlobe } from 'vue-icons-plus/vsc'
import {ElMessage} from 'element-plus' import {ElMessage} from 'element-plus'
import axios from 'axios'
import API from '@/util/http' import API from '@/util/http'
import dmmn from '../assets/link.png' import dmmn from '../assets/link.png'
import { useRoute } from 'vue-router'
import moment from 'moment' import moment from 'moment'
import { ca } from 'element-plus/es/locales.mjs'
import ChangePassword from '@/components/changePassword.vue'
const router = useRouter() const router = useRouter()
const imgrule1 = dmmn const imgrule1 = dmmn
@ -143,12 +141,17 @@ const changeDataByArea = (item) => {
// //
const exportListVisible = ref(false) const exportListVisible = ref(false)
//
const showPasswordDialog = ref(false)
// //
const openExportList = () => { const openExportList = () => {
getExportList() getExportList()
exportListVisible.value = true exportListVisible.value = true
} }
//
const openChangePassword = () => {
showPasswordDialog.value = true
}
</script> </script>
<template> <template>
@ -255,12 +258,14 @@ const openExportList = () => {
<el-badge @click="openExportList" class="item" > <!-- 用的el-badgeBadge 徽章 之后可以加数字 --> <el-badge @click="openExportList" class="item" > <!-- 用的el-badgeBadge 徽章 之后可以加数字 -->
<el-button>查看导出列表</el-button> <el-button>查看导出列表</el-button>
</el-badge> </el-badge>
<el-sub-menu index="1" class="admin"> <el-sub-menu index="1" class="admin">
<template #title> <template #title>
<el-image :src="imgrule1" alt="错误" style="width: 50px; height: 50px" /> <el-image :src="imgrule1" alt="错误" style="width: 50px; height: 50px" />
<span style="margin-left: 10px">{{ adminData.name }}</span> <span style="margin-left: 10px">{{ adminData.name }}</span>
</template> </template>
<el-menu-item @click="message()">查看个人信息</el-menu-item> <el-menu-item @click="message()">查看个人信息</el-menu-item>
<el-menu-item @click="openChangePassword">修改密码</el-menu-item>
<el-menu-item @click="logout">退出登录</el-menu-item> <el-menu-item @click="logout">退出登录</el-menu-item>
<el-menu-item index="1-3" @click="openExportList">查看下载列表</el-menu-item> <el-menu-item index="1-3" @click="openExportList">查看下载列表</el-menu-item>
@ -274,6 +279,7 @@ const openExportList = () => {
</el-main> </el-main>
</el-container> </el-container>
</el-container> </el-container>
<!-- 查看个人信息 --> <!-- 查看个人信息 -->
<el-dialog v-model="messageVisible" title="查看个人信息" width="500px"> <el-dialog v-model="messageVisible" title="查看个人信息" width="500px">
<el-form :model="adminData"> <el-form :model="adminData">
@ -329,6 +335,17 @@ const openExportList = () => {
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<!-- 自定义密码修改弹窗组件 -->
<el-dialog
v-model="showPasswordDialog"
:center="true"
width="470px"
>
<ChangePassword @confirm="showPasswordDialog = false" />
</el-dialog>
</div> </div>
</template> </template>

61
src/views/workspace/index.vue

@ -1,7 +1,24 @@
<template> <template>
<el-col :span="4">
<el-row>
<!-- 数据总览卡片 -->
<el-col :span="4" style="padding-right: 10px;"> <!-- 适当留白避免拥挤 -->
<el-card class="center-card margin-bottom">数据总览</el-card> <el-card class="center-card margin-bottom">数据总览</el-card>
</el-col> </el-col>
<!-- 刷新按钮 -->
<el-col :span="18" style="display: flex; align-items: center; font-size: 18px">
最后更新时间2025年7月10日17:31:09
<el-button text @click="refreshWorkspace()">
<el-icon style="vertical-align: middle; font-size: 18px" :class="{ 'is-loading': isLoading } ">
<Refresh/>
</el-icon>
</el-button>
</el-col>
<!-- 剩余栅格空间可选用于占满一行 -->
<el-col :span="18"></el-col>
</el-row>
<el-row :gutter="10"> <el-row :gutter="10">
<!-- 第一个卡片 --> <!-- 第一个卡片 -->
<el-col :span="6"> <el-col :span="6">
@ -9,8 +26,10 @@
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<div class="card-title">当前金币余量</div> <div class="card-title">当前金币余量</div>
<div>{{ currentGold / 100 }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;较前一日 {{
dailyChange / 100 }}
<div>{{ currentGold / 100 }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;较前一日
{{
dailyChange / 100
}}
<template v-if="dailyChange > 0"> <template v-if="dailyChange > 0">
<el-icon style="color:red"> <el-icon style="color:red">
<ArrowUpBold/> <ArrowUpBold/>
@ -34,7 +53,9 @@
<div class="margin-bottom">免费金币{{ currentFree / 100 }}</div> <div class="margin-bottom">免费金币{{ currentFree / 100 }}</div>
<div class="margin-bottom">[六月到期|{{ currentFreeJune / 100 }}]&nbsp;&nbsp;&nbsp;&nbsp;[12月到期|{{ <div class="margin-bottom">[六月到期|{{ currentFreeJune / 100 }}]&nbsp;&nbsp;&nbsp;&nbsp;[12月到期|{{
currentFreeDecember / currentFreeDecember /
100 }}]</div>
100
}}]
</div>
<div>任务金币{{ currentTask / 100 }}</div> <div>任务金币{{ currentTask / 100 }}</div>
</div> </div>
</el-card> </el-card>
@ -57,14 +78,14 @@
<!-- 第三个卡片 --> <!-- 第三个卡片 -->
<el-col :span="6"> <el-col :span="6">
<el-card class="card-item"> <el-card class="card-item">
<div class="card-title">全年累计消金币数</div>
<div class="card-title">全年累计消金币数</div>
<div class="card-title">{{ yearlyReduce / 100 }}</div> <div class="card-title">{{ yearlyReduce / 100 }}</div>
<div class="center-card">消费{{ yearlyConsume / 100 }}</div> <div class="center-card">消费{{ yearlyConsume / 100 }}</div>
<div class="center-card">退款{{ yearlyRefund / 100 }}</div> <div class="center-card">退款{{ yearlyRefund / 100 }}</div>
<template #footer> <template #footer>
<div></div> <div></div>
<div class="margin-bottom center-card">昨日新增消耗{{ dailyReduce / 100 }}</div>
<div class="margin-bottom center-card">昨日新增消费{{ dailyConsume / 100 }}</div> <div class="margin-bottom center-card">昨日新增消费{{ dailyConsume / 100 }}</div>
<div class="margin-bottom center-card">昨日新增消耗{{ dailyReduce / 100 }}</div>
<div class="margin-bottom center-card">昨日新增退款{{ dailyRefund / 100 }}</div> <div class="margin-bottom center-card">昨日新增退款{{ dailyRefund / 100 }}</div>
</template> </template>
</el-card> </el-card>
@ -130,11 +151,14 @@
<el-col :span="24"> <el-col :span="24">
<el-row> <el-row>
<div style="margin-top:5px">合计&nbsp;&nbsp;&nbsp;&nbsp; <div style="margin-top:5px">合计&nbsp;&nbsp;&nbsp;&nbsp;
永久金币 {{ activeTab === 'recharge' ? sumRechargePermanent / 100 : sumConsumePermanent / 100
永久金币 {{
activeTab === 'recharge' ? sumRechargePermanent / 100 : sumConsumePermanent / 100
}}&nbsp;&nbsp;&nbsp;&nbsp; }}&nbsp;&nbsp;&nbsp;&nbsp;
免费金币 {{ activeTab === 'recharge' ? sumRechargeFree / 100 : sumConsumeFree / 100
免费金币 {{
activeTab === 'recharge' ? sumRechargeFree / 100 : sumConsumeFree / 100
}}&nbsp;&nbsp;&nbsp;&nbsp; }}&nbsp;&nbsp;&nbsp;&nbsp;
任务金币 {{ activeTab === 'recharge' ? sumRechargeTask / 100 : sumConsumeTask / 100
任务金币 {{
activeTab === 'recharge' ? sumRechargeTask / 100 : sumConsumeTask / 100
}}&nbsp;&nbsp;&nbsp;&nbsp; }}&nbsp;&nbsp;&nbsp;&nbsp;
</div> </div>
<div> <div>
@ -198,8 +222,9 @@ import API from '@/util/http'
import {ElMessage} from 'element-plus' import {ElMessage} from 'element-plus'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import utc from 'dayjs-plugin-utc' import utc from 'dayjs-plugin-utc'
dayjs.extend(utc) dayjs.extend(utc)
import { ArrowUpBold, ArrowDownBold, SemiSelect } from '@element-plus/icons-vue'
import {ArrowUpBold, ArrowDownBold, SemiSelect, Refresh} from '@element-plus/icons-vue'
// //
const markets = ref([]) const markets = ref([])
// //
@ -242,6 +267,7 @@ const sumDaily = ref(0)
const rechargeNum = ref(0) const rechargeNum = ref(0)
const firstRecharge = ref(0) const firstRecharge = ref(0)
const length = ref(0) const length = ref(0)
const isLoading = ref(false)
const formatDate = function (date) { const formatDate = function (date) {
const year = date.getFullYear(); const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
@ -671,6 +697,21 @@ const getCardData = async () => {
} }
} }
const refreshWorkspace = async () => {
isLoading.value = true
try {
const result = await API({url: '/workbench/updateCard', data: {}})
if (result.code === 200) {
ElMessage.success('数据更新成功')
} else {
ElMessage.error('数据更新失败')
}
} catch (error) {
console.error('刷新工作台失败:', error)
} finally {
isLoading.value = false
}
}
onMounted(async () => { onMounted(async () => {

Loading…
Cancel
Save