@@ -1,12 +1,14 @@
< script setup lang = "ts" >
import { computed , ref } from 'vue'
import { Calendar , ChevronRight , CreditCard , Crown , Mail , User } from 'lucide-vue-next'
import { useAuthStore } from '@/stores/auth.ts'
import { useRouter } from 'vue-router'
import { computed , ref } from 'vue'
import { Calendar , CreditCard , Crown } from 'lucide-vue-next'
import { useAuthStore } from '@/stores/auth.ts'
import { useRouter } from 'vue-router'
import BaseInput from '@/components/base/BaseInput.vue'
import BasePage from "@/components/BasePage.vue" ;
import { APP _NAME , GITHUB } from "@/config/env.ts" ;
import { APP _NAME , GITHUB } from "@/config/env.ts" ;
import BaseButton from "@/components/BaseButton.vue" ;
import { PASSWORD _CONFIG } from "@/config/auth.ts" ;
import { setPassword } from "@/apis/user.ts" ;
const authStore = useAuthStore ( )
const router = useRouter ( )
@@ -33,11 +35,6 @@ const isEditingUsername = ref(false)
const isEditingEmail = ref ( false )
const showPasswordSection = ref ( false )
// Handlers
const handleLogin = ( ) => {
router . push ( '/login' )
}
const handleLogout = ( ) => {
authStore . logout ( )
router . push ( '/login' )
@@ -82,227 +79,214 @@ const contactSupport = () => {
const leaveTrustpilotReview = ( ) => {
window . open ( GITHUB + '/issues' , '_blank' )
}
async function changePassword ( e ) {
let res = await setPassword ( e . target . value )
//todo
}
< / script >
< template >
< BasePage >
<!-- Unauthenticated View -- >
< div v-if = "! isLoggedIn" class="w-full max-w-md " >
< div class = "bg-white rounded-2xl shadow-lg p-8 text-center" >
< div class = "mb-8 " >
< div class = "w-20 h-20 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4 ">
< User class = "w-10 h-10 text-blue-600" / >
< / div >
< h1 class = "text-2xl font-bold text-gray-900 mb-2" > 欢迎使用 < / h1 >
< p class = "text-gray-600" > 请登录以管理您的账户 < / p >
< div v-if = "isLoggedIn" class="center h-screen " >
< div class = "card shadow-lg text-center flex-col gap-6 w-100 " >
< div class = "w-20 h-20 bg-blue-100 rounded-full center mx-auto " >
< IconFluentPerson20Regular class = "text-3xl text-blue-600 "/ >
< / div >
< button
@click ="handleLogin"
class = "w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-6 rounded-xl transition-colors duration-200 mb-4 "
< h1 class = "text-2xl font-bold" > 欢迎使用 < / h1 >
< p class = "" > 请登录以管理您的账户 < / p >
< BaseButton
@click ="router.push('/login') "
size = "large"
class = "w-full mt-4"
>
登录
< / b utton>
< / BaseB utton>
< p class = "text-sm text-gray-500" >
还没有账户 ?
< a href = "#" class = "text-blue-600 hover:text-blue-700 font-medium "> 立即注册 < / a >
< router-link to = "/login" class = "line "> 立即注册 < / router-link >
< / p >
< / div >
< / div >
<!-- Authenticated View -- >
< div v-else class = "w-full max-w-4xl " >
< div class = "grid grid-cols-1 lg:grid-cols-3 gap-6" >
<!-- Main Account Settings -- >
< div class = "lg:col-span-2" >
< div class = "card" >
<!-- Header -- >
< div class = "px-6 border-b border-gray-200 " >
< h1 class = "text-xl font-bold text-gray-9 00" > 帐户 < / h1 >
< / div >
<!-- Username Section -- >
< div class = "px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors" >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 用户名 < / div >
< div class = "flex items-center gap-3" >
< User class = "w-4 h-4 text-gray-500" / >
< BaseInput
v-if = "isEditingUsername "
v-model = "username"
type = "text"
size = "normal"
@blur ="saveUsername"
@keyup.enter ="saveUsername"
class = "flex-1 max-w-xs"
autofocus
/ >
< span v-else class = "text-gray-900" > { { username } } < / span >
< / div >
< / div >
< IconFluentTextEditStyle20Regular
@click ="isEditingUsername ? saveUsername() : editUsername()"
class = "text-xl" / >
< / div >
< div class = "border-t border-gray-200" > < / div >
<!-- Email Section -- >
< div class = "px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors" >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 电子邮箱 < / div >
< div class = "flex items-center gap-3" >
< Mail class = "w-4 h-4 text-gray-500" / >
< BaseInput
v-if = "isEditingEmail"
v-model = "email"
type = "email"
size = "normal"
@blur ="saveEmail"
@keyup.enter ="saveEmail"
class = "flex-1 max-w-xs"
autofocus
/ >
< span v-else class = "text-gray-900" > { { email } } < / span >
< / div >
< / div >
< IconFluentTextEditStyle20Regular
@click ="isEditingEmail ? saveEmail() : editEmail()"
class = "text-xl" / >
< / div >
< div class = "border-t border-gray-200" > < / div >
<!-- Password Section -- >
< div class = "px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors" >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 设置密码 < / div >
< div class = "text-sm text-gray-500" > 在此输入密码 < / div >
< / div >
< IconFluentChevronLeft28Filled @click ="showPasswordSection = !showPasswordSection"
class = "transition-transform"
:class = "['rotate-270','rotate-180'][showPasswordSection?0:1]" / >
< / div >
< div class = "border-t border-gray-200" > < / div >
<!-- Notification Toggle -- >
< div class = "px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors" >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 同意接收优惠信息 < / div >
< div class = "text-sm text-gray-500" > 第一时间掌握 Lingvist 的各种优惠及最新消息 。 < / div >
< / div >
< button
@click ="toggleNotifications"
class = "relative inline-flex h-6 w-11 items-center rounded-full transition-colors"
: class = "receiveNotifications ? 'bg-blue-600' : 'bg-gray-200'"
>
< span
class = "inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
: class = "receiveNotifications ? 'translate-x-6' : 'translate-x-1'"
/ >
< / button >
< / div >
< div class = "border-t border-gray-200" > < / div >
<!-- Contact Support -- >
< div class = "px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors cursor-pointer"
@click ="contactSupport" >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 联系 { { APP _NAME } } 客服 < / div >
< / div >
< ChevronRight class = "w-5 h-5 text-gray-400" / >
< / div >
< div class = "border-t border-gray-200" > < / div >
<!-- Trustpilot Review -- >
< div class = "px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors cursor-pointer"
@click ="leaveTrustpilotReview" >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 在 { { APP _NAME } } 上留下评论 < / div >
< / div >
< ChevronRight class = "w-5 h-5 text-gray-400" / >
< / div >
<!-- Logout Button -- >
< div class = "px-6 py-6 border-t border-gray-200" >
< button
@click ="handleLogout"
class = "w-full bg-gray-800 hover:bg-gray-900 text-white font-semibold py-3 px-6 rounded-xl transition-colors duration-200"
>
登出
< / button >
< / div >
<!-- Footer Links -- >
< div class = "px-6 py-4 border-t border-gray-200 text-center" >
< div class = "text-sm text-gray-500" >
< a href = "/user-agreement.html" class = "text-gray-500 hover:text-gray-700 underline" > 用户协议 < / a >
、
< a href = "/privacy-policy.html" class = "text-gray-500 hover:text-gray-700 underline" > 隐私政策 < / a >
< / div >
< div v-else class = "w-full flex items-start gap-4 " >
<!-- Main Account Settings -- >
< div class = "card flex-1 flex flex-col gap-2 px-8" >
< h1 class = "text-xl font-bold" > 帐户 < / h1 >
<!-- Username Section -- >
< div class = "flex items-center justify-between " >
< div class = "flex-1 " >
< div class = "text-sm font-medium text-gray-7 00 mb-1 " > 用户名 < / div >
< div class = "flex items-center gap-2" >
< IconFluentPerson20Regular class = "text-base text-gray-500" / >
< BaseInput
v-if = "isEditingUsername"
v-model = "username"
type = "text"
size = "normal"
@blur ="saveUsername"
@keyup.enter ="saveUsername"
class = "flex-1 max-w-xs "
autofocus
/ >
< span v-else class = "text-gray-900" > { { username } } < / span >
< / div >
< / div >
< IconFluentTextEditStyle20Regular
@click ="isEditingUsername ? saveUsername() : editUsername()"
class = "text-xl" / >
< / div >
< div class = "line" > < / div >
<!-- Email Section -- >
< div class = "flex items-center justify-between " >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 电子邮箱 < / div >
< div class = "flex items-center gap-2" >
< IconFluentMail20Regular class = "text-base text-gray-500" / >
< BaseInput
v-if = "isEditingEmail"
v-model = "email"
type = "email"
size = "normal"
@blur ="saveEmail"
@keyup.enter ="saveEmail"
class = "flex-1 max-w-xs"
autofocus
/ >
< span v-else class = "text-gray-900" > { { email } } < / span >
< / div >
< / div >
< IconFluentTextEditStyle20Regular
@click ="isEditingEmail ? saveEmail() : editEmail()"
class = "text-xl" / >
< / div >
<!-- Subscription Information -- >
< div class = "lg:col-span-1" >
< div class = "card" >
< div class = "flex items-center gap-3 mb-4" >
< Crown class = "w-6 h-6 text-yellow-500" / >
< h2 class = "text-lg font-bold text-gray-900" > 订阅信息 < / h2 >
< div class = "line" > < / div >
<!-- Password Section -- >
< div class = "flex items-center justify-between cp"
@click ="showPasswordSection = !showPasswordSection"
>
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 设置密码 < / div >
< div class = "text-xs text-gray-500" > 在此输入密码 < / div >
< / div >
< IconFluentChevronLeft28Filled
class = "transition-transform"
:class = "['rotate-270','rotate-180'][showPasswordSection?0:1]" / >
< / div >
< div v-if = "showPasswordSection" >
< BaseInput placeholder = "新密码"
type = "password"
autofocus
:min = "PASSWORD_CONFIG.minLength"
:max = "PASSWORD_CONFIG.maxLength" / >
< div class = "text-align-end mt-4" >
< BaseButton type = "info" @click ="showPasswordSection = !showPasswordSection" > 取消 < / BaseButton >
< BaseButton @click ="changePassword" > 保存 < / BaseButton >
< / div >
< / div >
< div class = "line" > < / div >
<!-- Contact Support -- >
< div class = "flex py-2 items-center justify-between cp"
@click ="contactSupport" >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 联系 { { APP _NAME } } 客服 < / div >
< / div >
< IconFluentChevronLeft28Filled class = "rotate-180" / >
< / div >
< div class = "line" > < / div >
<!-- Trustpilot Review -- >
< div class = "flex py-2 items-center justify-between cp"
@click ="leaveTrustpilotReview" >
< div class = "flex-1" >
< div class = "text-sm font-medium text-gray-700 mb-1" > 在 { { APP _NAME } } 上留下评论 < / div >
< / div >
< IconFluentChevronLeft28Filled class = "rotate-180" / >
< / div >
< div class = "line" > < / div >
<!-- Logout Button -- >
< div class = "center w-full" >
< BaseButton
@click ="handleLogout"
size = "large"
class = "w-[80%]"
>
登出
< / BaseButton >
< / div >
< div class = "text-xs text-center" >
< a href = "/user-agreement.html" target = "_blank" class = "text-gray-500 hover:text-gray-700" > 用户协议 < / a >
、
< a href = "/privacy-policy.html" target = "_blank" class = "text-gray-500 hover:text-gray-700" > 隐私政策 < / a >
< / div >
< / div >
<!-- Subscription Information -- >
< div class = "card w-80" >
< div class = "flex items-center gap-3 mb-4" >
< Crown class = "w-6 h-6 text-yellow-500" / >
< h2 class = "text-lg font-bold text-gray-900" > 订阅信息 < / h2 >
< / div >
< div class = "space-y-4" >
< div >
< div class = "text-sm text-gray-500 mb-1" > 当前计划 < / div >
< div class = "text-lg font-semibold text-gray-900" > { { subscriptionData . plan } } < / div >
< / div >
< div >
< div class = "text-sm text-gray-500 mb-1" > 状态 < / div >
< div class = "flex items-center gap-2" >
< div class = "w-2 h-2 bg-green-500 rounded-full" > < / div >
< span class = "text-sm font-medium text-green-700" > { {
subscriptionData . status === 'active' ? '活跃' : '已过期'
} } < / span >
< / div >
< / div >
< div class = "space-y-4" >
< div >
< div class = "text-sm text-gray-500 mb-1" > 当前计划 < / div >
< div class = "text-lg font-semibold text-gray-9 00" > { { subscriptionData . plan } } < / div >
< / div >
< div >
<div class = "text-sm text-gray-500 mb-1" > 到期时间 < / div >
< div class = "flex items-center gap-2" >
< Calendar class = "w-4 h-4 text-gray-4 00" / >
< span class = "text-sm font-medium text-gray-900" > { { subscriptionData . expiresAt } } < / span >
< / div >
< / div >
< div >
< div class = "text-sm text-gray-500 mb-1" > 状态 < / div >
< div class = "flex items-center gap-2" >
< div class = "w-2 h-2 bg-green-500 rounded-full" > < / div >
< span class = "text-sm font-medium text-green-700" > { {
subscriptionData . statu s === 'active' ? '活跃' : '已过期'
} } < / span >
< / div >
< / div >
< div >
< div class = "text-sm text-gray-500 mb-1" > 到期时间 < / div >
< div class = "flex items-center gap-2" >
< Calendar class = "w-4 h-4 text-gray-400" / >
< span class = "text-sm font-medium text-gray-900" > { { subscriptionData . expiresAt } } < / span >
< / div >
< / div >
< div >
< div class = "text-sm text-gray-500 mb-1" > 自动续费 < / div >
< div class = "flex items-center gap-2" >
< div class = "w-2 h-2" : class = "subscriptionData.autoRenew ? 'bg-blue-500' : 'bg-gray-400'"
rounded -full > < / div >
< span class = "text-sm font-medium"
: class = "subscriptionData.autoRenew ? 'text-blue-700' : 'text-gray-600'" >
< div >
< div class = "text-sm text-gray-500 mb-1" > 自动续费 < / div >
< div class = "flex items-center gap-2" >
< div class = "w-2 h-2" : class = "subscriptionData.autoRenew ? 'bg-blue-500' : 'bg-gray-400'"
rounded -full > < / div >
< span clas s="text-sm font-medium"
: class = "subscriptionData.autoRenew ? 'text-blue-700' : 'text-gray-600'" >
{ { subscriptionData . autoRenew ? '已开启' : '已关闭' } }
< / span >
< / div >
< / div >
< div >
< div class = "text-sm text-gray-500 mb-1" > 付款方式 < / div >
< div class = "flex items-center gap-2" >
< CreditCard class = "w-4 h-4 text-gray-400" / >
< span class = "text-sm font-medium text-gray-900" > { { subscriptionData . paymentMethod } } < / span >
< / div >
< / div >
< div class = "pt-4 border-t border-gray-200" >
< BaseButton class = "w-full" > 管理订阅 < / BaseButton >
< / div >
< / div >
< / div >
< div >
< div class = "text-sm text-gray-500 mb-1" > 付款方式 < / div >
< div class = "flex items-center gap-2" >
< CreditCard class = "w-4 h-4 text-gray-400" / >
< span class = "text-sm font-medium text-gray-900" > { { subscriptionData . paymentMethod } } < / span >
< / div >
< / div >
< div class = "pt-4 border-t border-gray-200" >
< BaseButton class = "w-full" > 管理订阅 < / BaseButton >
< / div >
< / div >
< / div >
< / div >