diff --git a/frontend/src/layouts/AppLayout.vue b/frontend/src/layouts/AppLayout.vue index 9e14372..dd4da62 100644 --- a/frontend/src/layouts/AppLayout.vue +++ b/frontend/src/layouts/AppLayout.vue @@ -1,14 +1,21 @@ @@ -44,14 +59,79 @@ const navItems = [ - - - - - - 登录/注册 - - + + + + + + + + 登录/注册 + + + + + + + + + + + + + + {{ session.avatarText }} + + {{ session.displayName }} + + + + + + + 我的订单 + + + + 钱包 + + + + 发布账号 + + + + + 退出登录 + + + + + + + + 发布账号 @@ -63,3 +143,310 @@ const navItems = [ + + diff --git a/frontend/src/stores/session.ts b/frontend/src/stores/session.ts index e551ec6..14b5071 100644 --- a/frontend/src/stores/session.ts +++ b/frontend/src/stores/session.ts @@ -1,67 +1,80 @@ -import { defineStore } from 'pinia' +import { defineStore } from "pinia"; -import { fetchMe, loginWithSms, updateMe, type AuthUser } from '@/api/auth' +import { fetchMe, loginWithSms, updateMe, type AuthUser } from "@/api/auth"; -export const useSessionStore = defineStore('session', { +export const useSessionStore = defineStore("session", { state: () => ({ - token: localStorage.getItem('access_token') || '', - refreshToken: localStorage.getItem('refresh_token') || '', - userId: Number(localStorage.getItem('user_id') || 0), - phone: localStorage.getItem('phone') || '', - nickname: localStorage.getItem('nickname') || '', - avatarUrl: localStorage.getItem('avatar_url') || '', - realnameStatus: localStorage.getItem('realname_status') || 'unknown', + token: localStorage.getItem("access_token") || "", + refreshToken: localStorage.getItem("refresh_token") || "", + userId: Number(localStorage.getItem("user_id") || 0), + phone: localStorage.getItem("phone") || "", + nickname: localStorage.getItem("nickname") || "", + avatarUrl: localStorage.getItem("avatar_url") || "", + realnameStatus: localStorage.getItem("realname_status") || "unknown", }), + getters: { + isLoggedIn: (state) => Boolean(state.token), + displayName: (state) => + state.nickname || + (state.phone + ? `用户${state.phone.slice(-6)}` + : `用户${state.userId || 0}`), + avatarText: (state) => (state.nickname || state.phone || "U").slice(0, 1), + }, actions: { async login(phone: string, code: string) { - const result = await loginWithSms(phone, code) - this.applySession(result.user, result.tokens.access_token, result.tokens.refresh_token) - return result + const result = await loginWithSms(phone, code); + this.applySession( + result.user, + result.tokens.access_token, + result.tokens.refresh_token + ); + return result; }, async loadMe() { - const user = await fetchMe() - this.applyUser(user) - return user + const user = await fetchMe(); + this.applyUser(user); + return user; }, async updateProfile(payload: { nickname: string; avatar_url: string }) { - const user = await updateMe(payload) - this.applyUser(user) - return user + const user = await updateMe(payload); + this.applyUser(user); + return user; }, logout() { - this.token = '' - this.refreshToken = '' - this.userId = 0 - this.phone = '' - this.realnameStatus = 'unknown' - localStorage.removeItem('access_token') - localStorage.removeItem('refresh_token') - localStorage.removeItem('user_id') - localStorage.removeItem('phone') - localStorage.removeItem('nickname') - localStorage.removeItem('avatar_url') - localStorage.removeItem('realname_status') + this.token = ""; + this.refreshToken = ""; + this.userId = 0; + this.phone = ""; + this.realnameStatus = "unknown"; + localStorage.removeItem("access_token"); + localStorage.removeItem("refresh_token"); + localStorage.removeItem("user_id"); + localStorage.removeItem("phone"); + localStorage.removeItem("nickname"); + localStorage.removeItem("avatar_url"); + localStorage.removeItem("realname_status"); }, applySession(user: AuthUser, accessToken: string, refreshToken: string) { - this.token = accessToken - this.refreshToken = refreshToken - this.userId = user.id - localStorage.setItem('access_token', accessToken) - localStorage.setItem('refresh_token', refreshToken) - localStorage.setItem('user_id', String(user.id)) - this.applyUser(user) + this.token = accessToken; + this.refreshToken = refreshToken; + this.userId = user.id; + localStorage.setItem("access_token", accessToken); + localStorage.setItem("refresh_token", refreshToken); + localStorage.setItem("user_id", String(user.id)); + this.applyUser(user); }, applyUser(user: AuthUser) { - this.userId = user.id - localStorage.setItem('user_id', String(user.id)) - this.phone = user.phone - localStorage.setItem('phone', user.phone) - this.nickname = user.nickname - localStorage.setItem('nickname', user.nickname) - this.avatarUrl = user.avatar_url - localStorage.setItem('avatar_url', user.avatar_url) - this.realnameStatus = user.realname_status - localStorage.setItem('realname_status', user.realname_status) + this.userId = user.id; + localStorage.setItem("user_id", String(user.id)); + this.phone = user.phone; + localStorage.setItem("phone", user.phone); + this.nickname = user.nickname; + localStorage.setItem("nickname", user.nickname); + this.avatarUrl = user.avatar_url; + localStorage.setItem("avatar_url", user.avatar_url); + this.realnameStatus = user.realname_status; + localStorage.setItem("realname_status", user.realname_status); }, }, -}) +}); diff --git a/frontend/src/views/public/ListingsView.vue b/frontend/src/views/public/ListingsView.vue index e2f36d0..e554b87 100644 --- a/frontend/src/views/public/ListingsView.vue +++ b/frontend/src/views/public/ListingsView.vue @@ -1,181 +1,694 @@ - - + + + + 防骗提示:只在平台内沟通、下单和确认归还,谨防冒充客服与低价私单。{{ text }} - - - Account Market - 租号大厅 - 参考大锤商行 PC 信息密度,支持高级筛选、排序和卡片式资源浏览。 - - 账号发布 + + + 租号大厅 + + {{ filteredListings.length }} 个可租账号 + - - - 高级筛选 - 共 {{ filteredListings.length }} 个可租账号 - - - - + + + + - - - - - - - - + + + + + {{ activeSortLabel }} + + + + + + + + + + 筛选 + {{ activeFilterCount }} + + + + + + + + {{ option.label }} + - - - - - - - - - - - - - 综合推荐 - 价格最低 - 哈夫币最多 - 押金最低 - - 重置 + + - + - - + + + + + + + + + + + + 正在加载优质账号... + + 接口暂不可用,请稍后刷新。 + + + 没有符合条件的账号 + + 重置条件 + + + + + - HFB + 图 + + 有赠送 + 特惠 + - - {{ item.title }} - {{ item.status === "published" ? "可租" : listingStatusLabel(item.status) }} + {{ getListingTitle(item) }} + {{ getListingSubtitle(item) }} + + 押金秒退 + {{ getServerRegion(item) }} + {{ + getLoginMethod(item) + }} - - {{ - item.description || "号主暂未填写说明,可下单后按平台流程交接。" - }} - - - {{ item.server_region }} - {{ item.login_platform }} - {{ item.rank_level || "未填写段位" }} - 哈夫币 {{ item.haf_coin_amount }} + + {{ chip.label }}:{{ chip.value }} - 价格 - ¥{{ listingPrice(item).toFixed(2) }} - 押金 ¥{{ item.deposit_amount }} + ¥{{ getListingDisplayPrice(item) }} + 押金¥{{ item.deposit_amount }} + + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index bfd16fb..f824ba8 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -26,7 +26,8 @@ export default defineConfig({ '221329.cc.cd', "d4aa-77-93-89-84.ngrok-free.app", "frp-bus.com", - "www.u499731.nyat.app" + "www.u499731.nyat.app", + "fn.221329.xyz" ], }, });
Account Market
参考大锤商行 PC 信息密度,支持高级筛选、排序和卡片式资源浏览。
没有符合条件的账号
{{ getListingSubtitle(item) }}
- {{ - item.description || "号主暂未填写说明,可下单后按平台流程交接。" - }} -