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:
2026-05-19 23:09:11 +08:00
parent 39724e6e73
commit d7b055f88b
212 changed files with 4337 additions and 51 deletions
@@ -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>