This commit is contained in:
oneao committed 2025-12-18 22:39:59 +08:00
1 parent 9a35a56a97
commit 1fea531918
16 files changed
+1026 -184

No files matched your search

@@ -3,6 +3,7 @@ import type { FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
import { computed, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import EmailCodeInput from '@/components/EmailCodeInput.vue'
import { loginByEmailCodeApi, loginByPasswordApi } from '@/request/api/auth'
import { authCodeSendApi } from '@/request/api/auth/code'
import { useUserStore } from '@/store/user'
@@ -52,22 +53,6 @@ const carouselList = [
},
]
// --- 发送验证码状态 & 倒计时 ---
const sending = ref(false)
const countdown = ref(0)
let timer: number | undefined
function startCountdown(seconds = 60) {
countdown.value = seconds
timer = window.setInterval(() => {
countdown.value -= 1
if (countdown.value <= 0) {
clearInterval(timer)
timer = undefined
}
}, 1000)
}
const form = reactive<{
email: string
password: string
@@ -116,39 +101,8 @@ const formRules = {
],
}
const isEmailValid = computed(() => validateEmail(form.email))
const submitLoading = ref(false)
function handleSendEmailCode() {
if (sending.value || countdown.value > 0 || !isEmailValid.value)
return
sending.value = true
authCodeSendApi({
channel: AuthCodeChannel.EMAIL,
purpose: AuthCodePurpose.LOGIN,
target: form.email,
}).then((res) => {
if (res.isSuccess) {
ElMessage.success('发送成功')
startCountdown()
}
else {
sending.value = false
ElMessage.error(res.message || '发送失败,请检查邮箱是否正确或稍后再试!')
}
}).catch((err) => {
console.log(err)
ElMessage.error('发送失败,请稍后再试')
sending.value = false
}).finally(() => {
sending.value = false
})
}
async function submitForm(formEl: FormInstance | undefined) {
if (!formEl)
return
@@ -301,30 +255,14 @@ function navigateTo(path: string) {
<span class="forgot" @click="navigateTo('/auth/forget')">忘记密码?</span>
</span>
</template>
<el-input
<EmailCodeInput
v-model="form.code"
placeholder="请输入邮箱验证码"
class="email-code"
size="large"
:class="(sending || countdown > 0 || !isEmailValid) ? 'disabled' : ''"
>
<template #append>
<el-button
:disabled="sending || countdown > 0 || !isEmailValid"
@click="handleSendEmailCode"
>
<template v-if="sending">
发送中...
</template>
<template v-else-if="countdown > 0">
{{ countdown }}s
</template>
<template v-else>
发送验证码
</template>
</el-button>
</template>
</el-input>
:email="form.email"
:channel="AuthCodeChannel.EMAIL"
:purpose="AuthCodePurpose.LOGIN"
placeholder="请输入邮箱验证码"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.rememberMe" label="记住我" class="remeber" />
@@ -2,10 +2,12 @@
import type { FormInstance } from 'element-plus'
import type { RegisterAdminForm } from '@/types/auth'
import { ElMessage } from 'element-plus'
import { computed, onUnmounted, reactive, ref } from 'vue'
import { authCodeSendApi, authCodeVerifyApi } from '@/request/api/auth/code'
import { reactive, ref } from 'vue'
import EmailCodeInput from '@/components/EmailCodeInput.vue'
import { authCodeVerifyApi } from '@/request/api/auth/code'
import { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
import { validateEmail, validatePhone } from '@/utils/validate'
import { validatePhone } from '@/utils/validate'
const emit = defineEmits<{
(e: 'prev'): void
@@ -73,61 +75,6 @@ function phoneValidator(rule: any, value: any, callback: any) {
}
}
const isEmailValid = computed(() => validateEmail(form.email))
// --- 发送验证码状态 & 倒计时 ---
const sending = ref(false)
const countdown = ref(0)
let timer: number | undefined
function startCountdown(seconds = 60) {
countdown.value = seconds
timer = window.setInterval(() => {
countdown.value -= 1
if (countdown.value <= 0) {
clearInterval(timer)
timer = undefined
}
}, 1000)
}
function handleSendEmailCode() {
if (sending.value || countdown.value > 0 || !isEmailValid.value)
return
sending.value = true
authCodeSendApi({
channel: AuthCodeChannel.EMAIL,
purpose: AuthCodePurpose.REGISTER,
target: form.email,
}).then((res) => {
if (res.isSuccess) {
ElMessage.success('发送成功')
startCountdown()
}
else {
sending.value = false
ElMessage.error(res.message || '发送失败,请检查邮箱是否正确或稍后再试!')
}
}).catch((err) => {
console.log(err)
ElMessage.error('发送失败,请稍后再试')
sending.value = false
}).finally(() => {
sending.value = false
})
}
// 清理
onUnmounted(() => {
if (timer) {
clearInterval(timer)
timer = undefined
}
})
async function handleSubmit(formEl: FormInstance | undefined) {
if (!formEl)
return
@@ -206,28 +153,13 @@ function handlePrev() {
</el-col>
<el-col :span="12">
<el-form-item label="邮箱验证码" prop="emailCode">
<el-input
v-model="form.emailCode" placeholder="请输入邮箱验证码"
class="email-code"
:class="(sending || countdown > 0 || !isEmailValid) ? 'disabled' : ''"
>
<template #append>
<el-button
:disabled="sending || countdown > 0 || !isEmailValid"
@click="handleSendEmailCode"
>
<template v-if="sending">
发送中...
</template>
<template v-else-if="countdown > 0">
{{ countdown }}s
</template>
<template v-else>
发送验证码
</template>
</el-button>
</template>
</el-input>
<EmailCodeInput
v-model="form.emailCode"
:email="form.email"
:channel="AuthCodeChannel.EMAIL"
:purpose="AuthCodePurpose.REGISTER"
placeholder="请输入邮箱验证码"
/>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -274,21 +206,6 @@ function handlePrev() {
overflow-x: hidden;
}
.email-code {
:deep(.el-input-group__append) {
background-color: #409eff;
color: #fff;
cursor: pointer;
}
&.disabled {
:deep(.el-input-group__append) {
background-color: #f5f7fa;
color: #c0c4cc;
}
}
}
.section-title {
font-size: 17px;
font-weight: 600;
@@ -95,12 +95,12 @@ async function handleSubmit(formEl: FormInstance | undefined) {
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属行业" prop="industry">
<el-form-item label="所属行业" prop="industries">
<IndustrySelect v-model="form.industries" placeholder="请输入所属行业" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司地址" prop="address">
<el-form-item label="公司地址" prop="areas">
<AreaSelect
v-model="form.areas"
placeholder="请输入公司地址"
@@ -0,0 +1,274 @@
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import type { PropType } from 'vue'
import type { CompanyUser, CompanyUserForm } from '@/types/company/user'
import { ElMessage } from 'element-plus'
import { computed, ref, watch } from 'vue'
import EmailCodeInput from '@/components/EmailCodeInput.vue'
import { authCodeVerifyApi } from '@/request/api/auth/code'
import { addCompanyUserApi } from '@/request/api/company/user'
import { useUserStore } from '@/store/user'
import { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
import { validatePhone } from '@/utils/validate'
const props = defineProps({
user: {
type: Object as PropType<CompanyUser | null>,
default: null,
},
})
const userStore = useUserStore()
const roleOptions = [
{
value: 0,
label: '系统管理员',
},
{
value: 1,
label: '普通成员',
},
]
const originEmail = ref('')
const form = ref<CompanyUserForm>({
id: 0,
role: 1,
name: '',
position: '',
email: '',
phone: '',
canuse: '1',
password: '',
emailCode: '',
})
watch(
() => props.user,
(user) => {
if (!user)
return
form.value = { ...user, password: '', emailCode: '' }
originEmail.value = user.email
},
{ immediate: true },
)
const isUpdate = computed(() => !!props.user && !!props.user.id)
const isEmailChanged = computed(() => {
return form.value.email !== originEmail.value
})
const formRef = ref<FormInstance>()
const formRules = {
name: [
{ required: true, message: '名称不能为空', trigger: 'blur' },
],
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] },
],
emailCode: [
{
validator: (rule: any, value: any, callback: any) => {
if (!isEmailChanged.value) {
callback()
return
}
if (!value) {
callback(new Error('邮箱验证码不能为空'))
return
}
callback()
},
trigger: ['blur', 'change'],
},
],
password: [
{
validator: (rule: any, value: any, callback: any) => {
// 更新时允许不填
if (isUpdate.value && !value) {
callback()
return
}
if (!value) {
callback(new Error('密码不能为空'))
}
else {
callback()
}
},
trigger: 'blur',
},
],
role: [
{ required: true, message: '角色不能为空', trigger: 'blur' },
],
canuse: [
{ required: true, message: '启用不能为空', trigger: 'blur' },
],
phone: [{ validator: phoneValidator, trigger: 'blur' }],
}
const submitLoading = ref(false)
function phoneValidator(rule: any, value: any, callback: any) {
// 未填写:直接通过
if (!value) {
callback()
return
}
if (!validatePhone(value)) {
callback(new Error('手机号格式不正确'))
}
else {
callback()
}
}
async function handleSubmit(formEl: FormInstance | undefined) {
if (!formEl)
return
await formEl.validate((valid) => {
if (valid) {
if (isEmailChanged.value) {
submitLoading.value = true
authCodeVerifyApi({
channel: AuthCodeChannel.EMAIL,
purpose: isUpdate.value ? AuthCodePurpose.UPDATE_EMAIL : AuthCodePurpose.REGISTER,
target: form.value.email,
code: form.value.emailCode,
}).then((res) => {
if (res.isSuccess) {
addCompanyUserApi({
companyId: userStore.userInfo.company.id,
email: form.value.email,
password: form.value.password,
name: form.value.name,
position: form.value.position,
phone: form.value.phone,
canuse: form.value.canuse,
}).then((res) => {
if (res.isSuccess) {
ElMessage.success('保存成功')
}
else {
ElMessage.error(res.message || '新增失败,请稍后再试')
}
}).catch((err) => {
console.log(err)
ElMessage.error('新增失败,请稍后再试')
}).finally(() => {
submitLoading.value = false
})
}
else {
submitLoading.value = false
ElMessage.error(res.message || '验证码无效或已过期,请重新获取')
}
}).catch((err) => {
submitLoading.value = false
console.log(err)
ElMessage.error('邮箱验证码校验失败,请稍后再试')
})
}
}
else {
console.log('===验证失败===')
}
})
}
</script>
<template>
<el-drawer v-bind="$attrs" title="成员编辑" resizable>
<el-alert
title="提示"
style="margin-bottom: 24px;"
:closable="false"
>
<div>
<p>• 修改邮箱需要进行邮箱验证码验证</p>
<p>• 出于安全考虑,密码仅支持重置修改,无法查看原密码</p>
<p>• 角色无法更改</p>
</div>
</el-alert>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="auto"
label-position="right"
>
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="职位" prop="position">
<el-input v-model="form.position" />
</el-form-item>
<el-form-item label="角色" prop="role">
<el-select v-model="form.role" disabled>
<el-option
v-for="item in roleOptions "
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="邮箱号" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item
v-if="isEmailChanged"
label="邮箱验证码"
prop="emailCode"
>
<EmailCodeInput
v-model="form.emailCode"
:email="form.email"
:channel=" AuthCodeChannel.EMAIL"
:purpose="isUpdate ? AuthCodePurpose.UPDATE_EMAIL : AuthCodePurpose.REGISTER"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" show-password />
</el-form-item>
<el-form-item label="启用" prop="canuse">
<el-switch v-model="form.canuse" active-value="1" inactive-value="0" />
</el-form-item>
</el-form>
<template #footer>
<div style="flex: auto">
<el-button>
取消
</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit(formRef)">
确认
</el-button>
</div>
</template>
</el-drawer>
</template>
<style lang="scss" scoped>
//
</style>
@@ -5,6 +5,7 @@ import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { queryCompanyUserApi } from '@/request/api/company/user'
import { useUserStore } from '@/store/user'
import EditDrawer from './components/EditDrawer.vue'
const userStore = useUserStore()
const activeCollapseNames = ref(['1'])
@@ -43,6 +44,9 @@ const canuseOptions = [
]
const tableData = ref<CompanyUser[]>([])
const showEdit = ref(false)
const editUser = ref<CompanyUser | null>(null)
onMounted(() => {
getCompanyUser()
})
@@ -65,6 +69,11 @@ function getCompanyUser() {
ElMessage.error('查询失败,请稍后再试')
})
}
function handleShowEditDrawer(user: CompanyUser | null) {
editUser.value = user
showEdit.value = true
}
</script>
<template>
@@ -121,12 +130,8 @@ function getCompanyUser() {
</el-collapse>
<div class="table-container">
<div class="table-header">
<div class="table-title">
成员列表
</div>
<div class="table-actions">
<el-button type="primary" plain :icon="Plus">
<el-button type="primary" plain :icon="Plus" @click="handleShowEditDrawer(null)">
新增
</el-button>
<el-button type="danger" plain :icon="Delete">
@@ -140,7 +145,19 @@ function getCompanyUser() {
<div class="table-body-wrapper">
<el-table :data="tableData" border stripe>
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="name" label="名称" align="center" />
<el-table-column prop="name" label="名称" align="center">
<template #default="scope">
<template v-if="scope.row.role === 1">
{{ scope.row.name }}
</template>
<div v-else>
<div>
{{ scope.row.name }}
</div>
<el-tag>超级管理员</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="position" label="职位" align="center" />
<el-table-column prop="email" label="邮箱" align="center" />
<el-table-column prop="phone" label="电话" align="center" />
@@ -180,6 +197,8 @@ function getCompanyUser() {
/>
</div>
</div>
<EditDrawer v-model="showEdit" :user="editUser" />
</div>
</template>
@@ -207,7 +226,6 @@ function getCompanyUser() {
.table-container {
flex: 1;
margin-top: 16px;
display: flex;
flex-direction: column;
min-height: 0;
@@ -227,10 +245,6 @@ function getCompanyUser() {
align-items: center;
justify-content: space-between;
height: 56px;
.table-title {
font-size: 18px;
font-weight: 500;
}
}
:deep(.el-table__header) {
.el-table__cell {