This commit is contained in:
oneao committed 2025-12-16 17:21:56 +08:00
1 parent 1da52dc4f4
commit 276644f473
223 files changed
+9609 -262

No files matched your search

@@ -1,14 +1,29 @@
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
import { reactive, ref } from 'vue'
import { computed, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { loginApi } from '@/request/api/auth'
import { loginByEmailCodeApi, loginByPasswordApi } from '@/request/api/auth'
import { authCodeSendApi } from '@/request/api/auth/code'
import { useUserStore } from '@/store/user'
import { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
import { validateEmail } from '@/utils/validate'
const userStore = useUserStore()
const router = useRouter()
const loginOptions = [
{
label: '密码登录',
value: '0',
},
{
label: '验证码登录',
value: '1',
},
]
const loginMode = ref('0')
const carouselList = [
{
title: '值得信赖',
@@ -37,59 +52,160 @@ 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<{
loginCredential: string
email: string
password: string
code: string
rememberMe: boolean
}>({
loginCredential: '',
email: '',
password: '',
code: '',
rememberMe: true,
})
const formRef = ref<FormInstance>()
const formRules = {
loginCredential: [
email: [
{ required: true, message: '邮箱号不能为空', trigger: 'blur' },
{ type: 'email', required: true, message: '邮箱格式错误', trigger: 'blur' },
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
if (loginMode.value === '0') {
if (!value) {
callback(new Error('密码不能为空'))
return
}
}
callback() // 校验通过
},
trigger: 'blur',
},
],
code: [
{
validator: (rule: any, value: any, callback: any) => {
if (loginMode.value === '1') {
if (!value) {
callback(new Error('验证码不能为空'))
return
}
}
callback() // 校验通过
},
trigger: 'blur',
},
],
}
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
await formEl.validate((valid) => {
if (valid) {
submitLoading.value = true
loginApi({
loginCredential: form.loginCredential,
password: form.password,
}).then((res) => {
if (res.isSuccess) {
userStore.setUserInfo({
id: res.data.id,
account: res.data.account,
email: res.data.email,
name: res.data.name,
token: res.data.token,
avatar: res.data.avatar || '/no-avatar.png',
})
ElMessage.success('登录成功')
router.push('/')
}
else {
ElMessage.error(res.message || '登录失败,请稍后再试!')
}
}).catch((err) => {
console.log(err)
}).finally(() => {
submitLoading.value = false
})
await formEl.validate(async (valid) => {
if (!valid)
return
submitLoading.value = true
try {
let res
if (loginMode.value === '0') {
// 密码登录
res = await loginByPasswordApi({
email: form.email,
password: form.password,
})
}
else {
// 验证码登录
res = await loginByEmailCodeApi({
email: form.email,
code: form.code,
})
}
if (res.isSuccess) {
ElMessage.success('登录成功')
userStore.setUserInfo({
user: {
id: res.data.userId,
name: res.data.userName,
position: res.data.userPosition,
role: res.data.userRole,
},
company: {
id: res.data.companyId,
logo: res.data.companyLogo,
name: res.data.companyName,
},
token: res.data.token,
})
router.push('/')
}
else {
ElMessage.error(res.message || '登录失败,请稍后再试')
}
}
catch (err) {
console.log(err)
ElMessage.error('登录失败,请稍后再试')
}
finally {
submitLoading.value = false
}
})
}
@@ -130,8 +246,19 @@ function navigateTo(path: string) {
<h4 class="title">
登录
</h4>
<div style="padding: 8px 0px;">
<el-segmented
v-model="loginMode" :options="loginOptions" size="large"
/>
</div>
<p class="title-extra">
输入你的 邮箱 和 密码 登录账户.
<template v-if="loginMode === '0'">
输入你的 邮箱 和 密码 登录系统
</template>
<template v-else-if="loginMode === '1'">
输入你的 邮箱 和 验证码 登录系统
</template>
</p>
<el-form
ref="formRef"
@@ -141,11 +268,14 @@ function navigateTo(path: string) {
label-position="top"
hide-required-asterisk
>
<el-form-item label="邮箱" prop="loginCredential">
<el-input v-model="form.loginCredential" placeholder="请输入邮箱" size="large" />
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" size="large" />
</el-form-item>
<el-form-item prop="password">
<el-form-item
v-if="loginMode === '0'"
prop="password"
>
<template #label>
<span class="form-label-flex">
<span>密码</span>
@@ -155,11 +285,47 @@ function navigateTo(path: string) {
<el-input
v-model="form.password"
placeholder="请输入密码"
show-password
size="large"
@keyup.enter="submitForm(formRef)"
/>
</el-form-item>
<el-form-item
v-else
prop="code"
>
<template #label>
<span class="form-label-flex">
<span>密码</span>
<span class="forgot" @click="navigateTo('/auth/forget')">忘记密码?</span>
</span>
</template>
<el-input
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>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.rememberMe" label="记住我" class="remeber" />
</el-form-item>
@@ -257,6 +423,20 @@ function navigateTo(path: string) {
width: 100%;
margin-bottom: 12px;
}
.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;
}
}
}
.remeber {
:deep(.el-checkbox__inner) {
@@ -267,6 +447,7 @@ function navigateTo(path: string) {
font-size: 16px;
}
}
.title {
font-size: 28px;
font-weight: 600;
@@ -5,6 +5,7 @@ import { ElMessage } from 'element-plus'
import { computed, onUnmounted, reactive, ref } from 'vue'
import { authCodeSendApi, authCodeVerifyApi } from '@/request/api/auth/code'
import { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
import { validateEmail, validatePhone } from '@/utils/validate'
const emit = defineEmits<{
(e: 'prev'): void
@@ -12,6 +13,7 @@ const emit = defineEmits<{
}>()
const submitLoading = ref(false)
const isEmailCodeValid = ref(true)
const formRef = ref<FormInstance>()
const form = reactive<RegisterAdminForm>({
@@ -53,11 +55,25 @@ const formRules = {
trigger: ['blur', 'change'],
},
],
phone: [{ validator: phoneValidator, trigger: 'blur' }],
}
// 校验邮箱
const emailRegex = /^\w+(?:[-.]\w+)*@\w+(?:[-.]\w+)*\.\w+(?:[-.]\w+)*$/
const isEmailValid = computed(() => emailRegex.test(form.email.trim()))
function phoneValidator(rule: any, value: any, callback: any) {
// 未填写:直接通过
if (!value) {
callback()
return
}
if (!validatePhone(value)) {
callback(new Error('手机号格式不正确'))
}
else {
callback()
}
}
const isEmailValid = computed(() => validateEmail(form.email))
// --- 发送验证码状态 & 倒计时 ---
const sending = ref(false)
@@ -118,6 +134,11 @@ async function handleSubmit(formEl: FormInstance | undefined) {
await formEl.validate((valid) => {
if (valid) {
if (isEmailCodeValid.value) {
emit('submit', form)
return
}
submitLoading.value = true
authCodeVerifyApi({
channel: AuthCodeChannel.EMAIL,
@@ -127,6 +148,8 @@ async function handleSubmit(formEl: FormInstance | undefined) {
}).then((res) => {
if (res.isSuccess) {
emit('submit', form)
isEmailCodeValid.value = true
}
else {
ElMessage.error(res.message || '验证码无效或已过期,请重新获取')
@@ -134,6 +157,8 @@ async function handleSubmit(formEl: FormInstance | undefined) {
}).catch((err) => {
console.log(err)
ElMessage.error('邮箱验证码校验失败,请稍后再试')
isEmailCodeValid.value = false
}).finally(() => {
submitLoading.value = false
})
@@ -207,12 +232,12 @@ function handlePrev() {
</el-col>
<el-col :span="12">
<el-form-item label="登录密码" prop="password">
<el-input v-model="form.password" placeholder="请输入登录密码" />
<el-input v-model="form.password" placeholder="请输入登录密码" show-password />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" placeholder="请输入确认密码" />
<el-input v-model="form.confirmPassword" placeholder="请输入确认密码" show-password />
</el-form-item>
</el-col>
<el-col :span="24">
@@ -1,7 +1,10 @@
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import type { RegisterCompanyForm } from '@/types/auth'
import { reactive, ref } from 'vue'
import { reactive, ref, watch } from 'vue'
import AreaSelect from '@/components/AreaSelect.vue'
import IndustrySelect from '@/components/IndustrySelect.vue'
import { validateEmail, validatePhone } from '@/utils/validate'
const emit = defineEmits<{
(e: 'submit', value: RegisterCompanyForm): void
@@ -19,6 +22,7 @@ const formRules = {
{ required: true, message: '公司地址不能为空', trigger: 'blur' },
],
email: [{ validator: emailValidator, trigger: 'blur' }],
phone: [{ validator: phoneValidator, trigger: 'blur' }],
}
function emailValidator(rule: any, value: any, callback: any) {
@@ -28,27 +32,42 @@ function emailValidator(rule: any, value: any, callback: any) {
return
}
// 填了就校验格式
const emailReg
= /^[\w.%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i
if (!emailReg.test(value)) {
if (!validateEmail(value)) {
callback(new Error('邮箱格式不正确'))
}
else {
callback()
}
}
function phoneValidator(rule: any, value: any, callback: any) {
// 未填写:直接通过
if (!value) {
callback()
return
}
if (!validatePhone(value)) {
callback(new Error('手机号格式不正确'))
}
else {
callback()
}
}
const form = reactive<RegisterCompanyForm>({
name: '',
industry: '',
address: '',
industry: [],
address: [],
phone: '',
email: '',
profile: '',
})
watch(() => form.industry, (newVal) => {
console.log(newVal)
})
watch(() => form.address, (newVal) => {
console.log(newVal)
})
async function handleSubmit(formEl: FormInstance | undefined) {
if (!formEl)
return
@@ -83,12 +102,15 @@ async function handleSubmit(formEl: FormInstance | undefined) {
</el-col>
<el-col :span="12">
<el-form-item label="所属行业" prop="industry">
<el-input v-model="form.industry" placeholder="请输入所属行业" />
<IndustrySelect v-model="form.industry" placeholder="请输入所属行业" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司地址" prop="address">
<el-input v-model="form.address" placeholder="请输入公司地址" />
<AreaSelect
v-model="form.address"
placeholder="请输入公司地址"
/>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -1,9 +1,11 @@
<script setup lang="ts">
import type { PropType } from 'vue'
import type { RegisterAdminForm, RegisterCompanyForm } from '@/types/auth'
import { ElMessage } from 'element-plus'
import { ref } from 'vue'
import { registerApi } from '@/request/api/auth'
defineProps({
const props = defineProps({
company: {
type: Object as PropType<RegisterCompanyForm>,
required: true,
@@ -18,11 +20,48 @@ const emit = defineEmits<{
(e: 'prev'): void
(e: 'submit'): void
}>()
const showPassword = ref(false)
const submitLoading = ref(false)
const isAgree = ref(false)
function handleSubmit() {
emit('submit')
if (!isAgree.value) {
return
}
submitLoading.value = true
registerApi({
company: {
name: props.company.name,
industry: props.company.industry.join(','),
address: props.company.address.join(','),
phone: props.company.phone,
email: props.company.email,
profile: props.company.profile,
},
admin: {
name: props.admin.name,
phone: props.admin.phone,
email: props.admin.email,
password: props.admin.password,
position: props.admin.position,
},
}).then((res) => {
if (res.isSuccess) {
ElMessage.success('注册成功')
emit('submit')
}
else {
ElMessage.error(res.message || '注册失败,请稍后再试')
}
}).catch((err) => {
console.log(err)
ElMessage.error('注册失败,请稍后再试')
}).finally(() => {
submitLoading.value = false
})
}
</script>
@@ -33,24 +72,51 @@ function handleSubmit() {
公司信息
</h2>
<el-row :gutter="16" style="padding: 12px 0px;">
<el-row :gutter="16" class="info-group">
<el-col :span="8">
公司全称:-
<div class="info-item">
<span class="info-label">公司全称</span>
<span class="info-value">{{ company.name || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
所属行业:-
<div class="info-item">
<span class="info-label">所属行业</span>
<span class="info-value">
{{ company.industry?.join(',') || '-' }}
</span>
</div>
</el-col>
<el-col :span="8">
公司地址:-
<div class="info-item">
<span class="info-label">公司地址</span>
<span class="info-value">
{{ company.address?.join('/') || '-' }}
</span>
</div>
</el-col>
<el-col :span="8">
联系电话:-
<div class="info-item">
<span class="info-label">联系电话</span>
<span class="info-value">{{ company.phone || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
公司邮箱:-
<div class="info-item">
<span class="info-label">公司邮箱</span>
<span class="info-value">{{ company.email || '-' }}</span>
</div>
</el-col>
<el-col :span="24">
公司简介:-
<div class="info-item">
<span class="info-label">公司简介</span>
<span class="info-value">{{ company.profile || '-' }}</span>
</div>
</el-col>
</el-row>
@@ -58,21 +124,45 @@ function handleSubmit() {
管理员信息
</h2>
<el-row :gutter="16" style="padding: 12px 0px;">
<el-row :gutter="16" class="info-group">
<el-col :span="8">
管理员名称:-
<div class="info-item">
<span class="info-label">管理员名称</span>
<span class="info-value">{{ admin.name || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
管理员电话:-
<div class="info-item">
<span class="info-label">管理员电话</span>
<span class="info-value">{{ admin.phone || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
管理员邮箱:-
<div class="info-item">
<span class="info-label">管理员邮箱</span>
<span class="info-value">{{ admin.email || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
登录密码:-
<div class="info-item">
<span class="info-label">登录密码</span>
<span class="info-value">{{ showPassword ? admin.password : '******' }}</span>
<span class="password" @click="showPassword = !showPassword">
<i-solar:eye-broken v-if="showPassword" />
<i-solar:eye-closed-broken v-else />
</span>
</div>
</el-col>
<el-col :span="8">
所属职务:-
<div class="info-item">
<span class="info-label">所属职务</span>
<span class="info-value">{{ admin.position || '-' }}</span>
</div>
</el-col>
</el-row>
</div>
@@ -96,7 +186,7 @@ function handleSubmit() {
>
上一步
</el-button>
<el-button type="primary" :disabled="!isAgree" @click="handleSubmit()">
<el-button type="primary" :disabled="!isAgree" :loading="submitLoading" @click="handleSubmit()">
确认提交
</el-button>
</div>
@@ -113,6 +203,24 @@ function handleSubmit() {
flex-direction: column;
height: 100%;
.info-item {
display: flex;
font-size: 14px;
padding: 6px 0px;
}
.info-label {
color: #71717a;
white-space: nowrap;
}
.info-value {
color: #111827;
font-weight: 500;
word-break: break-all;
margin-left: 8px;
}
:deep(.el-col) {
word-break: break-all;
padding: 3px 0px;
@@ -125,7 +233,7 @@ function handleSubmit() {
.section-title {
font-size: 17px;
font-weight: 600;
padding-bottom: 6px;
padding: 16px 0px;
border-bottom: 1px solid #e4e4e7;
}
.section-subtitle {
@@ -134,6 +242,12 @@ function handleSubmit() {
margin-bottom: 10px;
}
.password {
margin-left: 6px;
cursor: pointer;
color: var(--el-color-primary);
}
.terms {
display: flex;
font-size: 14px;
@@ -1,7 +1,6 @@
<script lang="ts" setup>
import type { RegisterAdminForm, RegisterCompanyForm } from '@/types/auth'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import AdminForm from './components/AdminForm.vue'
import CompanyForm from './components/CompanyForm.vue'
@@ -52,8 +51,8 @@ const adminForm = ref<RegisterAdminForm>({
})
const companyForm = ref<RegisterCompanyForm>({
name: '',
industry: '',
address: '',
industry: [],
address: [],
phone: '',
email: '',
profile: '',
@@ -71,6 +70,16 @@ function handlePrevStep() {
function navigateTo(path: string) {
router.push(path)
}
function handleCompanySubmit(data: RegisterCompanyForm) {
companyForm.value = data
handleNextStep()
}
function handleAdminSubmit(data: RegisterAdminForm) {
adminForm.value = data
handleNextStep()
}
</script>
<template>
@@ -100,7 +109,7 @@ function navigateTo(path: string) {
<div class="auth-container">
<div class="form-container">
<div>
<img src="/logo.png" class="logo">
<img src="/logo.png" class="logo" @click="navigateTo('/')">
<el-steps finish-status="success" :active="activeStep" align-center>
<el-step title="公司信息" />
<el-step title="管理员信息" />
@@ -108,8 +117,8 @@ function navigateTo(path: string) {
</el-steps>
</div>
<CompanyForm v-show="activeStep === 0" @submit="handleNextStep" />
<AdminForm v-show="activeStep === 1" @submit="handleNextStep" @prev="handlePrevStep()" />
<CompanyForm v-show="activeStep === 0" @submit="handleCompanySubmit" />
<AdminForm v-show="activeStep === 1" @submit="handleAdminSubmit" @prev="handlePrevStep()" />
<ConfirmForm v-show="activeStep === 2" :admin="adminForm" :company="companyForm" @prev="handlePrevStep()" @submit="handleNextStep()" />
<ResultForm v-show="activeStep === 3" />
@@ -30,10 +30,11 @@ const tableData = [
企业名录
</div>
<el-table :data="tableData" border stripe style="width: 100%">
<!-- <el-table :data="tableData" style="width: 100%" :show-header="false">
<el-table-column prop="date" label="地区" width="180" />
<el-table-column prop="name" label="公司" />
</el-table>
</el-table> -->
<el-empty description="暂无企业名录" style="background-color: #fff;" />
</div>
<div class="right">
@@ -42,10 +43,12 @@ const tableData = [
热门推荐
</div>
<div class="hot-card-list">
<div v-for="i in 10" :key="i" class="hot-card-item">
<el-empty description="暂无热门推荐" />
<!-- <div v-for="i in 10" :key="i" class="hot-card-item">
<span class="number">{{ i }}</span>
<span class="name">xxxxxxxxxx公司公司</span>
</div>
</div> -->
</div>
</div>
</div>