Initial import of frappe sunderer app
This commit is contained in:
Generated
-4
@@ -1087,7 +1087,6 @@
|
||||
"resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.14.tgz",
|
||||
"integrity": "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emotion/hash": "~0.8.0",
|
||||
"csstype": "~3.0.5"
|
||||
@@ -1110,7 +1109,6 @@
|
||||
"resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
@@ -1747,7 +1745,6 @@
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
@@ -1819,7 +1816,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz",
|
||||
"integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.26",
|
||||
"@vue/compiler-sfc": "3.5.26",
|
||||
|
||||
@@ -80,13 +80,13 @@ const menuOptions = [
|
||||
label: 'Dashboard',
|
||||
key: 'dashboard',
|
||||
icon: renderIcon(BookIcon),
|
||||
props: { onClick: () => router.push('/app/dashboard') }
|
||||
props: { onClick: () => router.push('/sundererapp/dashboard') }
|
||||
},
|
||||
{
|
||||
label: 'Users',
|
||||
key: 'users',
|
||||
icon: renderIcon(PersonIcon),
|
||||
props: { onClick: () => router.push('/app/users') }
|
||||
props: { onClick: () => router.push('/sundererapp/users') }
|
||||
},
|
||||
// 添加更多菜单项...
|
||||
]
|
||||
|
||||
@@ -1,166 +1,166 @@
|
||||
<template>
|
||||
<div class="password-setup-container">
|
||||
<div class="setup-card">
|
||||
<h2>设置密码</h2>
|
||||
|
||||
<form @submit.prevent="setupPassword">
|
||||
<div class="input-group">
|
||||
<label>新密码</label>
|
||||
<input
|
||||
v-model="newPassword"
|
||||
type="password"
|
||||
placeholder="请输入新密码"
|
||||
class="input-field"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label>确认密码</label>
|
||||
<input
|
||||
v-model="confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入密码"
|
||||
class="input-field"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="loading"
|
||||
class="btn-primary"
|
||||
>
|
||||
{{ loading ? '设置中...' : '设置密码' }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const newPassword = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const error = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const setupPassword = async () => {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
error.value = '两次输入的密码不一致'
|
||||
return
|
||||
}
|
||||
|
||||
if (newPassword.value.length < 6) {
|
||||
error.value = '密码长度至少为6位'
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const actualKey = route.query.key
|
||||
if (!actualKey) {
|
||||
error.value = '无效的密码重置链接'
|
||||
return
|
||||
}
|
||||
|
||||
const response = await fetch('http://172.25.162.172:8000/api/method/frappe.core.doctype.user.user.update_password', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
key: actualKey,
|
||||
new_password: newPassword.value
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.exc) {
|
||||
error.value = result.exc || '设置密码失败'
|
||||
} else {
|
||||
alert('密码设置成功!')
|
||||
router.push('/login')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = '网络错误,请稍后再试'
|
||||
console.error(err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.password-setup-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #00AFFF, #0077CC);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.setup-card {
|
||||
width: 400px;
|
||||
padding: 30px;
|
||||
background: rgba(0, 0, 0, 0.295);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 0 30px rgba(0, 175, 255, 0.4);
|
||||
border: 1px solid rgba(0, 175, 255, 0.3);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 94%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: rgb(255, 255, 255);
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 0 0 1px rgba(0, 175, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: linear-gradient(90deg, #4CAF50, #45a049);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff6b6b;
|
||||
margin: 10px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
<template>
|
||||
<div class="password-setup-container">
|
||||
<div class="setup-card">
|
||||
<h2>设置密码</h2>
|
||||
|
||||
<form @submit.prevent="setupPassword">
|
||||
<div class="input-group">
|
||||
<label>新密码</label>
|
||||
<input
|
||||
v-model="newPassword"
|
||||
type="password"
|
||||
placeholder="请输入新密码"
|
||||
class="input-field"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label>确认密码</label>
|
||||
<input
|
||||
v-model="confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入密码"
|
||||
class="input-field"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="loading"
|
||||
class="btn-primary"
|
||||
>
|
||||
{{ loading ? '设置中...' : '设置密码' }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const newPassword = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const error = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const setupPassword = async () => {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
error.value = '两次输入的密码不一致'
|
||||
return
|
||||
}
|
||||
|
||||
if (newPassword.value.length < 6) {
|
||||
error.value = '密码长度至少为6位'
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const actualKey = route.query.key
|
||||
if (!actualKey) {
|
||||
error.value = '无效的密码重置链接'
|
||||
return
|
||||
}
|
||||
|
||||
const response = await fetch('/api/method/frappe.core.doctype.user.user.update_password', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
key: actualKey,
|
||||
new_password: newPassword.value
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.exc) {
|
||||
error.value = result.exc || '设置密码失败'
|
||||
} else {
|
||||
alert('密码设置成功!')
|
||||
router.push('/login')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = '网络错误,请稍后再试'
|
||||
console.error(err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.password-setup-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #00AFFF, #0077CC);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.setup-card {
|
||||
width: 400px;
|
||||
padding: 30px;
|
||||
background: rgba(0, 0, 0, 0.295);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 0 30px rgba(0, 175, 255, 0.4);
|
||||
border: 1px solid rgba(0, 175, 255, 0.3);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 94%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: rgb(255, 255, 255);
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 0 0 1px rgba(0, 175, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: linear-gradient(90deg, #4CAF50, #45a049);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff6b6b;
|
||||
margin: 10px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,258 +1,258 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<!-- 背景几何图案 -->
|
||||
<div class="bg-pattern"></div>
|
||||
|
||||
<!-- 登录卡 -->
|
||||
<div class="login-card">
|
||||
<div class="logo">
|
||||
<span class="brand">决裂者终端</span>
|
||||
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleLogin">
|
||||
<input
|
||||
v-model="email"
|
||||
type="text"
|
||||
placeholder="请输入账号"
|
||||
required
|
||||
class="input-field"
|
||||
/>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
required
|
||||
class="input-field"
|
||||
/>
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
|
||||
<div class="auth-buttons">
|
||||
<button type="submit" :disabled="loginLoading" class="login-btn">
|
||||
{{ loginLoading ? '正在登录. . .' : '登录' }}
|
||||
</button>
|
||||
<button type="button" @click="goToRegister" :disabled="registerLoading" class="register-btn">
|
||||
{{ registerLoading ? '注册中...' : '注册' }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<p class="footer">© 2026 Sunderer games. </p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router' // ✅ 确保导入
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const error = ref('')
|
||||
const loginLoading = ref(false)
|
||||
const registerLoading = ref(false)
|
||||
|
||||
|
||||
async function handleLogin() {
|
||||
loginLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/method/login', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ usr: email.value, pwd: password.value })
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json()
|
||||
throw new Error(data.message || '账号或密码错误')
|
||||
}
|
||||
|
||||
// 跳转到 Frappe 后台
|
||||
window.location.href = 'http://172.25.162.172:8000/app'
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRegister() {
|
||||
|
||||
registerLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/method/frappe.core.doctype.user.user.sign_up', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email: email.value,
|
||||
full_name: email.value.split('@')[0], // 简单处理,可用其他字段
|
||||
redirect_to: '/app'
|
||||
})
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json()
|
||||
throw new Error(data.message || '注册失败')
|
||||
}
|
||||
|
||||
// 注册成功后,Frappe 会发送验证邮件
|
||||
alert('注册成功!请查收邮箱验证链接。')
|
||||
// 或跳转到提示页
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
} finally {
|
||||
registerLoading.value = false
|
||||
}
|
||||
}
|
||||
function goToRegister() {
|
||||
router.push('/register')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #00AFFF, #0077CC);
|
||||
}
|
||||
|
||||
/* 登录卡片保持原样 */
|
||||
.login-card {
|
||||
background: rgba(0, 0, 0, 0.295);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 20px;
|
||||
padding: 2.5rem;
|
||||
width: 360px;
|
||||
box-shadow: 0 0 30px rgba(0, 175, 255, 0.4);
|
||||
border: 1px solid rgba(0, 175, 255, 0.3);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center; /* 水平居中内容 */
|
||||
align-items: center; /* 垂直居中内容 */
|
||||
min-height: 300px; /* 防止高度塌陷 */
|
||||
}
|
||||
|
||||
/* 其他样式(logo、输入框、按钮等)保持不变... */
|
||||
.logo {
|
||||
font-family: 'Arial', sans-serif;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1.5rem;
|
||||
letter-spacing: 1px;
|
||||
color: white;
|
||||
text-shadow: 0 0 10px rgba(0, 175, 255, 0.8), 0 0 20px rgba(0, 175, 255, 0.6);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.brand {
|
||||
color: white;
|
||||
font-size: 2.2rem;
|
||||
text-shadow: 0 0 10px rgba(0, 175, 255, 0.8), 0 0 20px rgba(0, 175, 255, 0.6);
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #00FFFF;
|
||||
font-size: 1.8rem;
|
||||
margin: 0 0.5rem;
|
||||
text-shadow: 0 0 15px rgba(0, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 80%;
|
||||
padding: 0.8rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: rgb(255, 255, 255);
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 0 0 1px rgba(0, 175, 255, 0.3);
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
box-shadow: 0 0 0 2px rgba(0, 175, 255, 0.7), 0 0 10px rgba(0, 175, 255, 0.4);
|
||||
}
|
||||
.error {
|
||||
color: #ff6b6b;
|
||||
font-size: 0.85rem;
|
||||
margin: -0.5rem 0 1rem;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 70%;
|
||||
padding: 0.8rem;
|
||||
background: linear-gradient(90deg, #00AFFF, #0077CC);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 0 15px rgba(0, 175, 255, 0.5);
|
||||
}
|
||||
.register-btn {
|
||||
width: 70%;
|
||||
padding: 0.8rem;
|
||||
background: linear-gradient(90deg, #6bffce, #c74dff);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 0 15px rgba(255, 107, 107, 0.5);
|
||||
}
|
||||
.login-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 20px rgba(0, 175, 255, 0.7);
|
||||
}
|
||||
|
||||
.login-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.auth-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 1.5rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
.register-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 20px rgba(255, 107, 107, 0.7);
|
||||
}
|
||||
|
||||
.register-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<!-- 背景几何图案 -->
|
||||
<div class="bg-pattern"></div>
|
||||
|
||||
<!-- 登录卡 -->
|
||||
<div class="login-card">
|
||||
<div class="logo">
|
||||
<span class="brand">决裂者终端</span>
|
||||
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleLogin">
|
||||
<input
|
||||
v-model="email"
|
||||
type="text"
|
||||
placeholder="请输入账号"
|
||||
required
|
||||
class="input-field"
|
||||
/>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
required
|
||||
class="input-field"
|
||||
/>
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
|
||||
<div class="auth-buttons">
|
||||
<button type="submit" :disabled="loginLoading" class="login-btn">
|
||||
{{ loginLoading ? '正在登录. . .' : '登录' }}
|
||||
</button>
|
||||
<button type="button" @click="goToRegister" :disabled="registerLoading" class="register-btn">
|
||||
{{ registerLoading ? '注册中...' : '注册' }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<p class="footer">© 2026 Sunderer games. </p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router' // ✅ 确保导入
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const error = ref('')
|
||||
const loginLoading = ref(false)
|
||||
const registerLoading = ref(false)
|
||||
|
||||
|
||||
async function handleLogin() {
|
||||
loginLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/method/login', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ usr: email.value, pwd: password.value })
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json()
|
||||
throw new Error(data.message || '账号或密码错误')
|
||||
}
|
||||
|
||||
// 跳转到 Frappe 后台
|
||||
window.location.href = '/sundererapp'
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRegister() {
|
||||
|
||||
registerLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/method/frappe.core.doctype.user.user.sign_up', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email: email.value,
|
||||
full_name: email.value.split('@')[0], // 简单处理,可用其他字段
|
||||
redirect_to: '/app'
|
||||
})
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json()
|
||||
throw new Error(data.message || '注册失败')
|
||||
}
|
||||
|
||||
// 注册成功后,Frappe 会发送验证邮件
|
||||
alert('注册成功!请查收邮箱验证链接。')
|
||||
// 或跳转到提示页
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
} finally {
|
||||
registerLoading.value = false
|
||||
}
|
||||
}
|
||||
function goToRegister() {
|
||||
router.push('/register')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #00AFFF, #0077CC);
|
||||
}
|
||||
|
||||
/* 登录卡片保持原样 */
|
||||
.login-card {
|
||||
background: rgba(0, 0, 0, 0.295);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 20px;
|
||||
padding: 2.5rem;
|
||||
width: 360px;
|
||||
box-shadow: 0 0 30px rgba(0, 175, 255, 0.4);
|
||||
border: 1px solid rgba(0, 175, 255, 0.3);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center; /* 水平居中内容 */
|
||||
align-items: center; /* 垂直居中内容 */
|
||||
min-height: 300px; /* 防止高度塌陷 */
|
||||
}
|
||||
|
||||
/* 其他样式(logo、输入框、按钮等)保持不变... */
|
||||
.logo {
|
||||
font-family: 'Arial', sans-serif;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1.5rem;
|
||||
letter-spacing: 1px;
|
||||
color: white;
|
||||
text-shadow: 0 0 10px rgba(0, 175, 255, 0.8), 0 0 20px rgba(0, 175, 255, 0.6);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.brand {
|
||||
color: white;
|
||||
font-size: 2.2rem;
|
||||
text-shadow: 0 0 10px rgba(0, 175, 255, 0.8), 0 0 20px rgba(0, 175, 255, 0.6);
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #00FFFF;
|
||||
font-size: 1.8rem;
|
||||
margin: 0 0.5rem;
|
||||
text-shadow: 0 0 15px rgba(0, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 80%;
|
||||
padding: 0.8rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: rgb(255, 255, 255);
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 0 0 1px rgba(0, 175, 255, 0.3);
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
box-shadow: 0 0 0 2px rgba(0, 175, 255, 0.7), 0 0 10px rgba(0, 175, 255, 0.4);
|
||||
}
|
||||
.error {
|
||||
color: #ff6b6b;
|
||||
font-size: 0.85rem;
|
||||
margin: -0.5rem 0 1rem;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 70%;
|
||||
padding: 0.8rem;
|
||||
background: linear-gradient(90deg, #00AFFF, #0077CC);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 0 15px rgba(0, 175, 255, 0.5);
|
||||
}
|
||||
.register-btn {
|
||||
width: 70%;
|
||||
padding: 0.8rem;
|
||||
background: linear-gradient(90deg, #6bffce, #c74dff);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 0 15px rgba(255, 107, 107, 0.5);
|
||||
}
|
||||
.login-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 20px rgba(0, 175, 255, 0.7);
|
||||
}
|
||||
|
||||
.login-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.auth-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 1.5rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
.register-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 20px rgba(255, 107, 107, 0.7);
|
||||
}
|
||||
|
||||
.register-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
@@ -24,13 +24,13 @@ export default defineConfig({
|
||||
manifest: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: 'js/[name].[hash].js',
|
||||
chunkFileNames: 'js/[name].[hash].js',
|
||||
entryFileNames: 'js/index.js',
|
||||
chunkFileNames: 'js/[name].js',
|
||||
assetFileNames: (assetInfo) => {
|
||||
if (assetInfo.name.endsWith('.css')) {
|
||||
return 'css/[name].[hash].[ext]';
|
||||
return 'css/index.css';
|
||||
}
|
||||
return 'assets/[name].[hash].[ext]';
|
||||
return 'assets/[name][extname]';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,9 @@ export default defineConfig({
|
||||
format: 'umd', // 设置输出格式为 UMD
|
||||
name: 'SundererApp', // 指定全局变量名,构建后应用会挂载到 window.IncreaseAccApp
|
||||
// 可以保留 hash 以利用缓存,但 UMD 通常用于简单场景,可选
|
||||
entryFileNames: 'js/sundererapp.[hash].js', // 例如: increaseacc.js (或 [name].[hash].js)
|
||||
chunkFileNames: 'js/sundererapp.[hash].js',
|
||||
assetFileNames: 'js/sundererapp.[hash].[ext]', // 例如: increaseacc.css (或 [name].[hash].[ext])
|
||||
entryFileNames: 'js/sundererapp.js', // 例如: increaseacc.js (或 [name].[hash].js)
|
||||
chunkFileNames: 'js/[name].js',
|
||||
assetFileNames: 'assets/[name][extname]', // 例如: increaseacc.css (或 [name].[hash].[ext])
|
||||
// UMD 格式可能需要外部依赖,如果 Vue 是全局引入的,需要声明
|
||||
// globals: {
|
||||
// vue: 'Vue' // 假设 Vue 已通过 script 标签全局引入
|
||||
|
||||
Reference in New Issue
Block a user