- 在管理员页面新增修改用户信息表单,支持姓名、手机号、密码修改 - 实现验证码发送倒计时与发送状态管理 - 新增接口支持用户信息更新,包含密码和手机号校验 - 后端校验验证码有效性,编码密码后更新用户信息 - 修改用户信息后强制登出,确保安全性 - 优化登录状态判断,登出后跳转至登录页 - 取消部分日志打印,调整发送验证码缓存过期时间为5分钟
347 lines
10 KiB
Vue
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>
|