This commit is contained in:
oneao committed 2025-12-15 17:31:19 +08:00
1 parent 52112bd277
commit 1da52dc4f4
66 files changed
+3294 -721

No files matched your search

@@ -10,3 +10,4 @@ export function registerApi(params: registerApiParams) {
export function forgetApi(params: forgetApiParams) {
return httpPostJson<null>(`/auth/forget`, params)
}
@@ -0,0 +1,10 @@
import type { AuthCodeSendParams, AuthCodeSendResult, AuthCodeVerifyParams, AuthCodeVerifyResult } from '@/types/auth/code'
import { httpPostJson } from '@/request/utils'
export function authCodeSendApi(params: AuthCodeSendParams) {
return httpPostJson<AuthCodeSendResult>(`/auth/code/send`, params)
}
export function authCodeVerifyApi(params: AuthCodeVerifyParams) {
return httpPostJson<AuthCodeVerifyResult>(`/auth/code/verify`, params)
}
@@ -2,5 +2,5 @@ import type { IndustryResult } from '@/types/company/index'
import { httpGet } from '../utils'
export function getPropertyListApi() {
return httpGet<IndustryResult>(`/webProperty/list`)
return httpGet<IndustryResult>(`/base/industry/list`)
}
@@ -124,13 +124,4 @@ function isDownloadResponse(headers: any): boolean {
return /application\/(?:octet-stream|force-download|pdf|vnd.ms-excel|msexcel|OCTET-STREAM)/.test(contentType || '')
}
function parseJsonOrReturnString(input: any) {
try {
return JSON.parse(input)
}
catch {
return input
}
}
export default axiosInstance
@@ -0,0 +1,31 @@
export enum AuthCodeChannel {
EMAIL = 'EMAIL',
SMS = 'SMS',
}
export enum AuthCodePurpose {
REGISTER = 'REGISTER',
RESET_PASSWORD = 'RESET_PASSWORD',
LOGIN = 'LOGIN',
}
export interface AuthCodeSendParams {
purpose: AuthCodePurpose
channel: AuthCodeChannel
target: string
}
export interface AuthCodeSendResult {
}
export interface AuthCodeVerifyParams {
purpose: AuthCodePurpose
channel: AuthCodeChannel
target: string
code: string
}
export interface AuthCodeVerifyResult {
}
@@ -10,6 +10,7 @@ export interface Industry {
label: string
value: string
}
export type IndustryResult = Industry[]
export interface CompanyQuery {
@@ -1,331 +0,0 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue'
const activeStep = ref(0)
const companyForm = reactive({
name: '',
industry: '',
address: '',
})
const form = reactive({
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
})
function handleCompanySubmit() {
activeStep.value += 1
}
function handlePrevStep() {
if (activeStep.value > 0) {
activeStep.value -= 1
}
}
</script>
<template>
<div class="auth-wrapper">
<div class="auth-container">
<div class="form-container">
<div class="logo-area">
<img src="/logo.png">
</div>
<el-steps finish-status="success" :active="activeStep" align-center>
<el-step title="公司信息" />
<el-step title="管理员信息" />
<el-step title="确认信息" />
</el-steps>
<div v-if="activeStep === 0" class="section-area">
<h2 class="section-title">
公司基本信息
</h2>
<el-form :model="form" label-width="auto" label-position="top">
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="公司全称">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属行业">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司地址">
<el-input v-model="form.name" placeholder="请输入邮箱" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司邮箱">
<el-input v-model="form.name" placeholder="请输入邮箱" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="公司简介">
<el-input v-model="form.name" type="textarea" placeholder="请输入姓名" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="button-group">
<div />
<el-button type="primary" @click="handleCompanySubmit()">
下一步
</el-button>
</div>
</div>
<div v-else-if="activeStep === 1" class="section-area">
<h2 class="section-title">
超级管理员信息
</h2>
<p class="section-subtitle">
超级管理员将拥有系统的最高权限,请谨慎设置
</p>
<el-form :model="form" label-width="auto" label-position="top">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="管理员姓名">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手机号">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="管理员邮箱">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱验证码">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="登录密码">
<el-input v-model="form.name" placeholder="请输入邮箱" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="确认密码">
<el-input v-model="form.name" placeholder="请输入邮箱" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="担任职务">
<el-input v-model="form.name" placeholder="请输入邮箱" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="button-group">
<el-button @click="handlePrevStep()">
上一步
</el-button>
<el-button type="primary">
下一步
</el-button>
</div>
</div>
</div>
<div class="info-sidebar">
<div class="info-header">
<h1 class="page-title">
link56 注册说明
</h1>
<p class="page-description">
请填写公司信息并创建超级管理员账户以完成系统初始化设置
</p>
</div>
<ul class="info-list">
<li class="info-item">
<div class="info-item-header">
<i-ri:building-fill />
<div class="info-title">
公司信息
</div>
</div>
<div class="info-content">
请填写公司的法定全称、地址和联系方式,确保信息真实有效,这将用于系统备案和官方文件。
</div>
</li>
<li class="info-item">
<div class="info-item-header">
<i-fa7-solid:user-shield />
<div class="info-title">
超级管理员
</div>
</div>
<div class="info-content">
超级管理员拥有系统的最高权限,可以创建和管理其他用户,分配角色和权限,请妥善保管账户信息。
</div>
</li>
<li class="info-item">
<div class="info-item-header">
<i-material-symbols:lock-open-right-sharp />
<div class="info-title">
账户安全
</div>
</div>
<div class="info-content">
请设置强密码(至少8位,包含字母和数字),建议定期更换密码,不要将账户信息透露给他人。
</div>
</li>
<li class="info-item">
<div class="info-item-header">
<i-ic:twotone-headphones />
<div class="info-title">
技术支持
</div>
</div>
<div class="info-content">
如果在注册过程中遇到问题,请联系我们的技术支持团队:<br>
电话:400-123-4567<br>
邮箱:support@example.com
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.auth-wrapper {
background-color: #f2f4f6;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
width: 100%;
.auth-container {
width: 1200px;
border: 1px solid #ddd;
background-color: #fff;
display: flex;
.form-container {
flex: 1;
padding: 28px 40px;
border-right: 1px solid #e4e4e7;
.logo-area {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 28px;
img {
width: 220px;
height: 68px;
cursor: pointer;
}
}
.section-area {
margin-top: 16px;
overflow-y: auto;
overflow-x: hidden;
.section-title {
font-size: 17px;
font-weight: 600;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #e4e4e7;
}
.section-subtitle {
color: #71717a;
font-size: 13px;
margin-bottom: 10px;
}
.button-group {
display: flex;
justify-content: space-between;
margin-top: 29px;
}
}
}
.info-sidebar {
width: 350px;
padding: 28px;
display: flex;
flex-direction: column;
background-color: #fafafa;
.info-header {
margin-bottom: 24px;
}
.page-title {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.022em;
margin-bottom: 6px;
color: #09090b;
}
.page-description {
font-size: 14px;
line-height: 1.55;
color: #71717a;
}
.info-list {
list-style: none;
flex-grow: 1;
.info-item {
padding: 16px 0;
border-bottom: 1px solid #e4e4e7;
}
.info-item:last-child {
border-bottom: none;
}
.info-item-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 9px;
}
.info-title {
font-size: 14px;
font-weight: 600;
color: #09090b;
}
.info-content {
color: #71717a;
font-size: 13px;
line-height: 1.5;
}
}
}
}
}
</style>
@@ -1,13 +1,18 @@
<script setup lang="ts">
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 { AuthCodeChannel, AuthCodePurpose } from '@/types/auth/code'
const emit = defineEmits<{
(e: 'prev'): void
(e: 'submit', value: RegisterAdminForm): void
}>()
const submitLoading = ref(false)
const formRef = ref<FormInstance>()
const form = reactive<RegisterAdminForm>({
name: '',
@@ -19,6 +24,7 @@ const form = reactive<RegisterAdminForm>({
confirmPassword: '',
position: '',
})
const formRules = {
name: [
{ required: true, message: '管理员名称不能为空', trigger: 'blur' },
@@ -65,14 +71,37 @@ function startCountdown(seconds = 60) {
if (countdown.value <= 0) {
clearInterval(timer)
timer = undefined
sending.value = false
}
}, 1000)
}
function handleSendEmailCode() {
if (sending.value || countdown.value > 0 || !isEmailValid.value)
return
sending.value = true
startCountdown()
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
})
}
// 清理
@@ -89,7 +118,25 @@ async function handleSubmit(formEl: FormInstance | undefined) {
await formEl.validate((valid) => {
if (valid) {
emit('submit', form)
submitLoading.value = true
authCodeVerifyApi({
channel: AuthCodeChannel.EMAIL,
purpose: AuthCodePurpose.REGISTER,
target: form.email,
code: form.emailCode,
}).then((res) => {
if (res.isSuccess) {
emit('submit', form)
}
else {
ElMessage.error(res.message || '验证码无效或已过期,请重新获取')
}
}).catch((err) => {
console.log(err)
ElMessage.error('邮箱验证码校验失败,请稍后再试')
}).finally(() => {
submitLoading.value = false
})
}
})
}
@@ -137,11 +184,22 @@ function handlePrev() {
<el-input
v-model="form.emailCode" placeholder="请输入邮箱验证码"
class="email-code"
:class="(!isEmailValid || sending) ? 'disabled' : ''"
:class="(sending || countdown > 0 || !isEmailValid) ? 'disabled' : ''"
>
<template #append>
<el-button :disabled="!isEmailValid || sending" @click="handleSendEmailCode()">
{{ sending ? `${countdown}s` : "发送验证码" }}
<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>
@@ -170,7 +228,7 @@ function handlePrev() {
<el-button @click="handlePrev()">
上一步
</el-button>
<el-button type="primary" @click="handleSubmit(formRef)">
<el-button type="primary" :loading="submitLoading" @click="handleSubmit(formRef)">
下一步
</el-button>
</div>
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { RegisterAdminForm, RegisterCompanyForm } from '@/types/auth'
import { reactive, ref } from 'vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import AdminForm from './components/AdminForm.vue'
@@ -194,7 +194,7 @@ function navigateTo(path: string) {
overflow: hidden;
.auth-bg {
width: 50%;
width: 40%;
height: 100%;
position: relative;
@@ -236,6 +236,7 @@ function navigateTo(path: string) {
background-color: #fff;
display: flex;
height: 100%;
width: 60%;
.form-container {
display: flex;
@@ -243,7 +244,7 @@ function navigateTo(path: string) {
flex: 1;
padding: 36px;
border-right: 1px solid #e4e4e7;
.logo {
height: 70px;
width: 160px;
@@ -1,369 +0,0 @@
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { computed, onUnmounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const carouselList = [
{
title: '值得信赖',
company: '恒星科技有限公司',
content: '我们已经连续三年使用该平台,数据处理稳定高效,团队协作体验非常顺畅。',
},
{
title: '安全 · 高效',
company: '博越信息技术',
content: '平台的权限管理做得很好,操作简单清晰,极大提升了内部管理效率。',
},
{
title: '稳定可靠',
company: '远航供应链集团',
content: '系统运行非常稳定,几乎没有出现过宕机情况,客服响应速度也很快。',
},
{
title: '智能化体验',
company: '蓝海数据服务中心',
content: '智能报表和自动化功能非常实用,减少了大量人工整理数据的时间。',
},
{
title: '灵活易用',
company: '中联创投有限公司',
content: 'UI设计简洁友好,新员工上手很快,各项业务流程也能灵活配置。',
},
]
const form = reactive<{
email: string
code: string
name: string
account: string
password: string
confirmPassword: string
}>({
email: '',
code: '',
name: '',
account: '',
password: '',
confirmPassword: '',
})
const formRef = ref<FormInstance>()
const formRules = {
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] },
],
code: [
{ required: true, message: '验证码不能为空', trigger: 'blur' },
],
name: [
{ required: true, message: '名称不能为空', trigger: 'blur' },
{ min: 2, max: 20, message: '名称长度需在 2 到 20 个字符之间', trigger: 'blur' },
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度需在 6 到 20 个字符之间', trigger: 'blur' },
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
if (value !== form.password) {
callback(new Error('两次输入的密码不一致'))
}
else {
callback()
}
},
trigger: ['blur', 'change'],
},
],
}
const submitLoading = ref(false)
// --- 邮箱校验(简单正则) ---
const emailRegex = /^\w+(?:[-.]\w+)*@\w+(?:[-.]\w+)*\.\w+(?:[-.]\w+)*$/
const isEmailValid = computed(() => emailRegex.test(form.email.trim()))
// --- 发送验证码状态 & 倒计时 ---
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
sending.value = false
}
}, 1000)
}
// 清理
onUnmounted(() => {
if (timer) {
clearInterval(timer)
timer = undefined
}
})
function handleSendEmailCode() {
sending.value = true
startCountdown()
}
async function submitForm(formEl: FormInstance | undefined) {
if (!formEl)
return
await formEl.validate((valid) => {
if (valid) {
submitLoading.value = true
}
})
}
function navigateTo(path: string) {
router.push(path)
}
</script>
<template>
<div class="auth-wrapper">
<div class="auth-bg">
<img src="/auth-bg.jpg">
<div class="bg-content">
<el-carousel
height="150px"
indicator-position="none"
>
<el-carousel-item v-for="(item, index) in carouselList" :key="index">
<div class="carousel-item">
<h2 class="title">
{{ item.title }}
</h2>
<span class="content">{{ item.content }}</span>
<span class="company"> - {{ item.company }}</span>
</div>
</el-carousel-item>
</el-carousel>
</div>
</div>
<div class="login-form">
<img src="/logo.png" class="logo" @click="navigateTo('/')">
<div class="main">
<h4 class="title">
注册
</h4>
<p class="title-extra">
请填写必要的注册信息,完成账户创建。
</p>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
hide-required-asterisk
label-width="auto"
label-position="top"
>
<el-form-item
label="邮箱"
prop="email"
>
<el-input
v-model="form.email"
placeholder="请输入邮箱"
size="large"
/>
</el-form-item>
<el-form-item
label="邮箱验证码"
prop="code"
>
<el-input
v-model="form.account" placeholder="请输入邮箱验证码" size="large"
class="email-code"
:class="(!isEmailValid || sending) ? 'disabled' : ''"
>
<template #append>
<el-button :disabled="!isEmailValid || sending" @click="handleSendEmailCode()">
{{ sending ? `${countdown}s` : "发送验证码" }}
</el-button>
</template>
</el-input>
</el-form-item>
<!-- <el-form-item label="账号">
<el-input v-model="form.account" placeholder="请输入账号" size="large" />
</el-form-item> -->
<el-form-item
label="公司名称"
prop="name"
>
<el-input v-model="form.name" placeholder="请输入公司名称" size="large" />
</el-form-item>
<el-form-item
label="密码"
prop="password"
>
<el-input v-model="form.password" placeholder="请输入密码" size="large" />
</el-form-item>
<el-form-item
label="确认密码"
prop="confirmPassword"
>
<el-input v-model="form.confirmPassword" placeholder="请输入确认密码" size="large" />
</el-form-item>
<el-form-item>
<el-button type="primary" class="form-btn" @click="submitForm(formRef)">
注册
</el-button>
</el-form-item>
</el-form>
</div>
<div class="footer">
没有账号?前去
<span class="back" @click="navigateTo('/auth/login')">登录</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.email-code {
:deep(.el-input-group__append) {
background-color: #eceffd;
color: #4041d1;
cursor: pointer;
}
&.disabled {
:deep(.el-input-group__append) {
background-color: #f5f7fa;
color: #c0c4cc;
}
}
}
.auth-wrapper {
display: flex;
height: 100vh;
overflow: hidden;
.auth-bg {
width: 70%;
height: 100%;
position: relative;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.bg-content {
position: absolute;
left: 50%;
bottom: 10%;
transform: translateX(-50%);
text-align: center;
color: #fff;
width: 600px;
.carousel-item {
display: flex;
flex-direction: column;
.title {
font-size: 28px;
line-height: 28px;
margin-bottom: 16px;
}
.content {
margin-bottom: 8px;
}
.company {
font-size: 14px;
}
}
}
}
.form-label-flex {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
.forgot {
color: #0f6fec;
cursor: pointer;
}
}
.login-form {
overflow-y: auto;
flex: 1;
display: flex;
flex-direction: column;
padding: 36px;
.logo {
height: 70px;
width: 160px;
cursor: pointer;
margin-bottom: 24px;
}
.main {
flex: 1;
display: flex;
justify-content: center;
flex-direction: column;
:deep(.el-form-item__label) {
font-size: 16px;
width: 100%;
margin-bottom: 12px;
}
.title {
font-size: 28px;
font-weight: 600;
margin-bottom: 10px;
}
.title-extra {
font-size: 16px;
color: #666;
margin-bottom: 36px;
}
.form-btn {
width: 100%;
height: 36px;
font-size: 16px;
margin-top: 16px;
}
}
.footer {
font-size: 18px;
color: #999;
height: 48px;
text-align: center;
line-height: 48px;
.back {
color: #0f6fec;
cursor: pointer;
}
}
}
}
</style>