Files
en-edu/enlish-vue/src/pages/admid/admid.vue
lbw 49963bb49c feat(user): 添加用户信息修改功能及对应验证码校验
- 在管理员页面新增修改用户信息表单,支持姓名、手机号、密码修改
- 实现验证码发送倒计时与发送状态管理
- 新增接口支持用户信息更新,包含密码和手机号校验
- 后端校验验证码有效性,编码密码后更新用户信息
- 修改用户信息后强制登出,确保安全性
- 优化登录状态判断,登出后跳转至登录页
- 取消部分日志打印,调整发送验证码缓存过期时间为5分钟
2026-01-05 12:07:15 +08:00

347 lines
10 KiB
Vue

<template>
<div class="common-layout">
<el-container>
<el-header>
<Header></Header>
</el-header>
<el-container>
<el-main class="p-2">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="panel-shell p-6">
<div class="flex items-center mb-4">
<el-input v-model="query.name" placeholder="姓名" clearable style="max-width:220px" />
<el-button type="primary" class="ml-2" @click="fetchList">查询</el-button>
<el-button class="ml-2" @click="resetSearch">重置</el-button>
<el-button type="primary" class="ml-2" @click="openCreate">新增用户</el-button>
</div>
<el-table :data="list" v-loading="loading" border stripe>
<el-table-column prop="name" label="姓名" />
<el-table-column prop="phone" label="手机号" />
<el-table-column prop="roleName" label="角色" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background :current-page="page" :page-size="pageSize" :total="totalCount"
layout="prev, pager, next, sizes, total" @current-change="onPageChange"
@size-change="onSizeChange" />
</div>
</div>
<div class="panel-shell p-6">
<div class="flex items-center justify-between mb-4">
<span class="text-lg font-semibold">生成邀请码</span>
</div>
<el-form :model="inviteForm" :rules="inviteRules" ref="inviteFormRef" label-width="120px">
<el-form-item label="使用次数限制" prop="limit">
<el-input-number v-model="inviteForm.limit" :min="1" :max="9999" />
</el-form-item>
<el-form-item label="有效期(天)" prop="expire">
<el-input-number v-model="inviteForm.expire" :min="1" :max="3650" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="inviteLoading"
@click="submitInvite">生成邀请码</el-button>
<el-button class="ml-2" @click="resetInvite">重置</el-button>
</el-form-item>
</el-form>
<div v-if="inviteCode" class="mt-2">
<el-alert type="success" :closable="false" :title="`邀请码:${inviteCode}`" show-icon />
</div>
</div>
<div class="panel-shell p-6">
<div class="flex items-center justify-between mb-4">
<span class="text-lg font-semibold">修改用户信息</span>
</div>
<el-form :model="pwForm" :rules="pwRules" ref="pwFormRef" label-width="120px">
<el-form-item label="姓名" prop="name">
<el-input v-model="pwForm.name" />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="pwForm.newPassword" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="pwForm.confirmPassword" type="password" show-password />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="pwForm.phone" />
</el-form-item>
<el-form-item label="验证码" prop="code">
<el-input v-model="pwForm.code">
<template #append>
<el-button :disabled="codeCountdown > 0 || codeSending || !pwForm.phone"
@click="sendCode">
{{ codeCountdown > 0 ? `${codeCountdown}s` : '获取验证码' }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="pwLoading" @click="submitPw">修改用户信息</el-button>
<el-button class="ml-2" @click="resetPw">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
<el-dialog v-model="createVisible" title="新增用户" width="420px">
<el-form :model="createForm" :rules="rules" ref="createFormRef" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="createForm.name" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="createForm.phone" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="createForm.password" type="password" show-password />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createVisible = false">取消</el-button>
<el-button type="primary" :loading="createLoading" @click="submitCreate">提交</el-button>
</template>
</el-dialog>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script setup>
import Header from '@/layouts/components/Header.vue'
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { getUserList, createUser, createInvitationCode } from '@/api/admin'
import { updateUserInfo, getVerificationCode } from '@/api/user'
import { showMessage } from '@/composables/util.js'
import Sidebar from '@/layouts/components/Sidebar.vue'
const loading = ref(false)
const list = ref([])
const page = ref(1)
const pageSize = ref(10)
const totalCount = ref(0)
const query = reactive({ name: '' })
async function fetchList() {
try {
loading.value = true
const r = await getUserList({ page: page.value, pageSize: pageSize.value, name: query.name })
const d = r?.data
if (d?.success) {
list.value = d?.data || []
page.value = d?.pageNo || page.value
totalCount.value = d?.totalCount || 0
pageSize.value = d?.pageSize || pageSize.value
} else {
list.value = []
totalCount.value = 0
}
} finally {
loading.value = false
}
}
function resetSearch() {
query.name = ''
page.value = 1
fetchList()
}
function onPageChange(p) {
page.value = p
fetchList()
}
function onSizeChange(s) {
pageSize.value = s
page.value = 1
fetchList()
}
const createVisible = ref(false)
const createForm = reactive({ name: '', phone: '', password: '' })
const createFormRef = ref()
const createLoading = ref(false)
const rules = {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
}
function openCreate() {
createVisible.value = true
}
function submitCreate() {
createFormRef.value?.validate(async (valid) => {
if (!valid) return
createLoading.value = true
try {
const r = await createUser({ name: createForm.name, phone: createForm.phone, password: createForm.password })
const d = r?.data
if (d?.success) {
showMessage('新增成功', 'success')
createVisible.value = false
createForm.name = ''
createForm.phone = ''
createForm.password = ''
fetchList()
} else {
showMessage(d?.message || '新增失败', 'error')
}
} finally {
createLoading.value = false
}
})
}
onMounted(() => {
fetchList()
})
const inviteForm = reactive({ limit: 10, expire: 3 })
const inviteFormRef = ref()
const inviteLoading = ref(false)
const inviteCode = ref('')
const inviteRules = {
limit: [{ required: true, message: '请输入使用次数限制', trigger: 'change' }],
expire: [{ required: true, message: '请输入有效期', trigger: 'change' }],
}
function resetInvite() {
inviteForm.limit = 10
inviteForm.expire = 3
inviteCode.value = ''
}
function submitInvite() {
inviteFormRef.value?.validate(async (valid) => {
if (!valid) return
inviteLoading.value = true
try {
const r = await createInvitationCode({ limit: inviteForm.limit, expire: inviteForm.expire })
const d = r?.data
if (d?.success) {
inviteCode.value = d?.data?.invitationCode || ''
if (inviteCode.value) {
showMessage('邀请码生成成功', 'success')
} else {
showMessage('生成成功,但未返回邀请码', 'warning')
}
} else {
showMessage(d?.message || '邀请码生成失败', 'error')
}
} finally {
inviteLoading.value = false
}
})
}
const pwForm = reactive({ name: '', phone: '', newPassword: '', confirmPassword: '', code: '' })
const pwFormRef = ref()
const pwLoading = ref(false)
const pwRules = {
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
newPassword: [],
confirmPassword: [
{
validator: (rule, value, callback) => {
if (pwForm.newPassword) {
if (!value) {
callback(new Error('请确认密码'))
return
}
if (value !== pwForm.newPassword) {
callback(new Error('两次输入的密码不一致'))
return
}
}
callback()
},
trigger: 'blur'
}
],
code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
}
function resetPw() {
if (codeTimer) {
clearInterval(codeTimer)
codeTimer = null
}
codeCountdown.value = 0
codeSending.value = false
pwForm.name = ''
pwForm.phone = ''
pwForm.newPassword = ''
pwForm.confirmPassword = ''
pwForm.code = ''
}
const codeCountdown = ref(0)
const codeSending = ref(false)
let codeTimer = null
async function sendCode() {
if (!pwForm.phone) {
showMessage('请输入手机号', 'warning')
return
}
if (codeSending.value || codeCountdown.value > 0) return
codeSending.value = true
try {
const r = await getVerificationCode({ phone: pwForm.phone })
const d = r?.data
if (d?.success) {
showMessage('验证码已发送', 'success')
codeCountdown.value = 60
codeTimer = setInterval(() => {
if (codeCountdown.value > 0) {
codeCountdown.value -= 1
} else {
clearInterval(codeTimer)
codeTimer = null
}
}, 1000)
} else {
showMessage(d?.message || '发送验证码失败', 'error')
}
} finally {
codeSending.value = false
}
}
onUnmounted(() => {
if (codeTimer) {
clearInterval(codeTimer)
codeTimer = null
}
})
function submitPw() {
pwFormRef.value?.validate(async (valid) => {
if (!valid) return
pwLoading.value = true
try {
const r = await updateUserInfo({
newPassword: pwForm.newPassword || '',
name: pwForm.name || '',
phone: pwForm.phone,
code: pwForm.code
})
const d = r?.data
if (d?.success) {
showMessage('用户信息修改成功', 'success')
resetPw()
} else {
showMessage(d?.message || '用户信息修改失败', 'error')
}
} finally {
pwLoading.value = false
}
})
}
</script>