v0.5.4: 全面修复 — template literal URL, Cookie验证, 用户默认is_active, 默认账号路由, 空间信息, 密钥清理, promoForm修复
修复:
- quark-share.ts/storage.ts: 9处template literal ${}缺失导致fetch URL写死
- user/routes.ts: testCloudConnectionWithCookie缺await + 按cloudType分发驱动
- credential.service.ts: INSERT缺?参数 (9values/10cols)
- user/routes.ts: 用户新增网盘默认is_active=0
- admin.routes.ts: 新增PUT /admin/cloud-configs/:id/primary路由
- database.ts: is_primary列迁移
- UserDashboard.vue: 保存时传递storage_used/storage_total
- SystemConfig.vue: promoForm const重赋值bug
- config/index.ts: 移除泄露的默认密钥token
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="user-login-page">
|
||||
<div class="login-card">
|
||||
<h1 class="login-title">CloudSearch 用户中心</h1>
|
||||
<p class="login-subtitle">{{ isLogin ? '登录您的账号' : '注册新账号' }}</p>
|
||||
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="0" @keyup.enter="handleSubmit">
|
||||
<el-form-item prop="platform">
|
||||
<el-select v-model="form.platform" placeholder="选择推广平台" size="large" style="width: 100%">
|
||||
<el-option
|
||||
v-for="p in platforms"
|
||||
:key="p.name"
|
||||
:label="p.name"
|
||||
:value="p.name"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="手机号码" size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="form.password" type="password" placeholder="密码" size="large" show-password />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!isLogin" prop="confirmPassword">
|
||||
<el-input v-model="form.confirmPassword" type="password" placeholder="确认密码" size="large" show-password />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="large" :loading="loading" style="width: 100%" @click="handleSubmit">
|
||||
{{ isLogin ? '登 录' : '注 册' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="login-switch">
|
||||
{{ isLogin ? '还没有账号?' : '已有账号?' }}
|
||||
<el-button link type="primary" @click="isLogin = !isLogin">
|
||||
{{ isLogin ? '立即注册' : '去登录' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码区域 -->
|
||||
<div v-if="platforms.length > 0" class="qr-section">
|
||||
<div class="qr-title">{{ qrTitle }}</div>
|
||||
<div class="qr-grid">
|
||||
<div v-for="p in platforms" :key="p.name" class="qr-item">
|
||||
<div class="qr-label">加入【{{ p.name }}】团队</div>
|
||||
<div class="qr-img-wrap">
|
||||
<img
|
||||
:src="'https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=' + encodeURIComponent(p.join_url)"
|
||||
:alt="'加入' + p.name"
|
||||
class="qr-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
|
||||
const router = useRouter()
|
||||
const isLogin = ref(true)
|
||||
const loading = ref(false)
|
||||
const formRef = ref()
|
||||
|
||||
interface Platform {
|
||||
id: number
|
||||
name: string
|
||||
join_url: string
|
||||
sort_order: number
|
||||
}
|
||||
const platforms = ref<Platform[]>([])
|
||||
const qrTitle = ref('扫码加入推广团队')
|
||||
|
||||
const form = reactive({
|
||||
platform: '',
|
||||
phone: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
|
||||
const validatePhone = (_rule: any, value: string, callback: any) => {
|
||||
if (!value) callback(new Error('请输入手机号码'))
|
||||
else if (!/^1[3-9]\d{9}$/.test(value)) callback(new Error('请输入有效的手机号码'))
|
||||
else callback()
|
||||
}
|
||||
|
||||
const rules = {
|
||||
platform: [{ required: true, message: '请选择推广平台', trigger: 'change' }],
|
||||
phone: [{ validator: validatePhone, trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, message: '密码至少6位', trigger: 'blur' }],
|
||||
confirmPassword: [{
|
||||
validator: (_rule: any, value: string, callback: any) => {
|
||||
if (!value) callback(new Error('请确认密码'))
|
||||
else if (value !== form.password) callback(new Error('两次密码不一致'))
|
||||
else callback()
|
||||
}, trigger: 'blur'
|
||||
}],
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const { data } = await axios.get('/api/promotion-platforms')
|
||||
platforms.value = data.platforms
|
||||
if (data.title) qrTitle.value = data.title
|
||||
} catch { /* */ }
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
if (isLogin.value) {
|
||||
const account = form.platform + '-' + form.phone.trim()
|
||||
const { data } = await axios.post('/api/user/login', {
|
||||
account,
|
||||
password: form.password,
|
||||
})
|
||||
localStorage.setItem('user_token', data.token)
|
||||
localStorage.setItem('user_account', account)
|
||||
ElMessage.success('登录成功')
|
||||
router.push('/user/dashboard')
|
||||
} else {
|
||||
const { data } = await axios.post('/api/user/register', {
|
||||
platform: form.platform,
|
||||
phone: form.phone.trim(),
|
||||
password: form.password,
|
||||
})
|
||||
ElMessage.success(data.message || '注册成功,请登录')
|
||||
isLogin.value = true
|
||||
form.password = ''
|
||||
form.confirmPassword = ''
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.response?.data?.error || '操作失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-login-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f0f2f5;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
.login-card {
|
||||
width: 420px;
|
||||
padding: 40px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
.login-title { text-align: center; font-size: 24px; margin: 0 0 8px; color: #303133; }
|
||||
.login-subtitle { text-align: center; font-size: 14px; color: #909399; margin: 0 0 32px; }
|
||||
.login-switch { text-align: center; font-size: 14px; color: #909399; margin-top: 16px; }
|
||||
|
||||
.qr-section {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
}
|
||||
.qr-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.qr-grid {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.qr-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.qr-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
.qr-img-wrap {
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.08);
|
||||
}
|
||||
.qr-image {
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user