173 lines
6.3 KiB
Vue
173 lines
6.3 KiB
Vue
|
|
<script setup lang="ts">
|
|||
|
|
import { ref } from 'vue';
|
|||
|
|
import { Rocket, Plus } from 'lucide-vue-next';
|
|||
|
|
import { useRouter } from 'vue-router';
|
|||
|
|
import { useAuthStore } from '@/stores/auth';
|
|||
|
|
import { ElMessage } from 'element-plus';
|
|||
|
|
import { uploadFileToMinIO } from '@/api';
|
|||
|
|
|
|||
|
|
const router = useRouter();
|
|||
|
|
const authStore = useAuthStore();
|
|||
|
|
const errorMsg = ref('');
|
|||
|
|
const isLoading = ref(false);
|
|||
|
|
|
|||
|
|
const form = ref({
|
|||
|
|
username: '', phone: '', email: '',
|
|||
|
|
nickname: '', password: '', confirmPassword: '',
|
|||
|
|
avatar_url: ''
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const handleAvatarUpload = async (options: any) => {
|
|||
|
|
try {
|
|||
|
|
const url = await uploadFileToMinIO(options.file, 'avatars');
|
|||
|
|
form.value.avatar_url = url;
|
|||
|
|
ElMessage.success('头像上传成功');
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error('头像上传失败,请重试');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleRegister = async () => {
|
|||
|
|
if (!form.value.username.trim()) { errorMsg.value = '用户名不能为空'; return; }
|
|||
|
|
if (form.value.password.length < 6) { errorMsg.value = '密码长度至少为 6 位'; return; }
|
|||
|
|
if (form.value.password !== form.value.confirmPassword) { errorMsg.value = '两次输入的密码不一致'; return; }
|
|||
|
|
if (form.value.phone && !/^1[3-9]\d{9}$/.test(form.value.phone)) { errorMsg.value = '请输入有效的11位手机号'; return; }
|
|||
|
|
if (form.value.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.email)) { errorMsg.value = '请输入有效的邮箱地址'; return; }
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
isLoading.value = true;
|
|||
|
|
errorMsg.value = '';
|
|||
|
|
await authStore.register({
|
|||
|
|
username: form.value.username,
|
|||
|
|
phone: form.value.phone || undefined,
|
|||
|
|
email: form.value.email || undefined,
|
|||
|
|
password: form.value.password,
|
|||
|
|
nickname: form.value.nickname || form.value.username,
|
|||
|
|
avatar_url: form.value.avatar_url || undefined
|
|||
|
|
});
|
|||
|
|
await authStore.login({ username: form.value.username, password: form.value.password });
|
|||
|
|
router.push('/user/certification/apply');
|
|||
|
|
} catch (e: any) {
|
|||
|
|
if (e.response?.data) {
|
|||
|
|
const data = e.response.data;
|
|||
|
|
if (typeof data === 'object') {
|
|||
|
|
const errors = Object.entries(data).map(([key, val]) => {
|
|||
|
|
const fn: any = { username: '用户名', phone: '手机号', email: '邮箱', password: '密码', nickname: '昵称' }[key] || key;
|
|||
|
|
return `${fn}: ${Array.isArray(val) ? val[0] : val}`;
|
|||
|
|
});
|
|||
|
|
errorMsg.value = errors.join('; ');
|
|||
|
|
} else {
|
|||
|
|
errorMsg.value = data.detail || '注册失败';
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
errorMsg.value = '网络错误,请检查后端服务是否运行';
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
isLoading.value = false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<div class="min-h-screen bg-gray-50 flex items-center justify-center px-4 py-12">
|
|||
|
|
<div class="max-w-lg w-full">
|
|||
|
|
<el-card shadow="always" class="!rounded-2xl !p-2">
|
|||
|
|
<div class="text-center mb-6 pt-4">
|
|||
|
|
<div class="mx-auto h-14 w-14 bg-gray-900 rounded-2xl flex items-center justify-center mb-4">
|
|||
|
|
<Rocket class="w-7 h-7 text-blue-400" />
|
|||
|
|
</div>
|
|||
|
|
<h2 class="text-2xl font-bold text-gray-800">加入 <span class="text-blue-600">CorpScale</span></h2>
|
|||
|
|
<p class="text-sm text-gray-400 mt-1">开启您的一人公司智能之旅</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<el-form @submit.prevent="handleRegister" label-position="top">
|
|||
|
|
<el-alert v-if="errorMsg" type="error" :title="errorMsg" :closable="false" class="mb-4" />
|
|||
|
|
|
|||
|
|
<!-- Avatar Upload -->
|
|||
|
|
<div class="flex justify-center mb-6">
|
|||
|
|
<el-upload
|
|||
|
|
action=""
|
|||
|
|
class="avatar-uploader"
|
|||
|
|
:show-file-list="false"
|
|||
|
|
:http-request="handleAvatarUpload"
|
|||
|
|
accept="image/*"
|
|||
|
|
>
|
|||
|
|
<img v-if="form.avatar_url" :src="form.avatar_url" class="w-full h-full object-cover" />
|
|||
|
|
<div v-else class="text-gray-400 flex flex-col items-center">
|
|||
|
|
<Plus class="w-6 h-6 mb-1" />
|
|||
|
|
<span class="text-[10px]">上传头像</span>
|
|||
|
|
</div>
|
|||
|
|
</el-upload>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<el-form-item label="用户名" required>
|
|||
|
|
<el-input v-model="form.username" placeholder="请输入用户名" size="large" />
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-row :gutter="12">
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="手机号(可选)">
|
|||
|
|
<el-input v-model="form.phone" placeholder="11位手机号" size="large" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="邮箱(可选)">
|
|||
|
|
<el-input v-model="form.email" type="email" placeholder="email@example.com" size="large" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-form-item label="显示昵称">
|
|||
|
|
<el-input v-model="form.nickname" placeholder="显示昵称(不填则使用用户名)" size="large" />
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-row :gutter="12">
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="设置密码" required>
|
|||
|
|
<el-input v-model="form.password" type="password" placeholder="至少6位" size="large" show-password />
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="确认密码" required>
|
|||
|
|
<el-input v-model="form.confirmPassword" type="password" placeholder="再次输入" size="large" show-password />
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-button type="primary" native-type="submit" :loading="isLoading" class="w-full" size="large">
|
|||
|
|
{{ isLoading ? '正在处理...' : '立即注册账户' }}
|
|||
|
|
</el-button>
|
|||
|
|
|
|||
|
|
<el-divider>
|
|||
|
|
<span class="text-xs text-gray-400">已有账户?</span>
|
|||
|
|
</el-divider>
|
|||
|
|
|
|||
|
|
<el-button class="w-full" @click="router.push('/login')">去登录</el-button>
|
|||
|
|
</el-form>
|
|||
|
|
</el-card>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.avatar-uploader {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
.avatar-uploader :deep(.el-upload) {
|
|||
|
|
width: 96px;
|
|||
|
|
height: 96px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
border: 2px dashed #e5e7eb;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
overflow: hidden;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: border-color 0.3s;
|
|||
|
|
}
|
|||
|
|
.avatar-uploader :deep(.el-upload:hover) {
|
|||
|
|
border-color: #60a5fa;
|
|||
|
|
}
|
|||
|
|
</style>
|