[Snow Team] frontend-mobile: auto-commit work
This commit is contained in:
+175
-69
@@ -1,104 +1,210 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
import { isMobileHost, toMobilePath } from "./mobileHost";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{ path: '/admin', redirect: '/admin/dashboard' },
|
||||
{ path: '/', name: 'home', component: () => import('@/views/public/HomeView.vue') },
|
||||
{ path: '/login', name: 'login', component: () => import('@/views/account/LoginView.vue') },
|
||||
{ path: '/listings', name: 'listings', component: () => import('@/views/public/ListingsView.vue') },
|
||||
{ path: '/listings/:id', name: 'listing-detail', component: () => import('@/views/public/ListingDetailView.vue') },
|
||||
{ path: '/orders/create', name: 'order-create', component: () => import('@/views/account/OrderCreateView.vue') },
|
||||
{ path: '/orders', name: 'orders', component: () => import('@/views/account/OrdersView.vue') },
|
||||
{ path: '/orders/:id', name: 'order-detail', component: () => import('@/views/account/OrderDetailView.vue') },
|
||||
{ path: '/wallet', name: 'wallet', component: () => import('@/views/account/WalletView.vue') },
|
||||
{ path: '/notifications', name: 'notifications', component: () => import('@/views/account/NotificationsView.vue') },
|
||||
{ path: '/realname', name: 'realname', component: () => import('@/views/account/RealnameView.vue') },
|
||||
{ path: '/seller/listings', name: 'seller-listings', component: () => import('@/views/seller/SellerListingsView.vue') },
|
||||
{ path: '/seller/listings/create', name: 'seller-listing-create', component: () => import('@/views/seller/SellerListingCreateView.vue') },
|
||||
{ path: '/seller/handoffs', name: 'seller-handoffs', component: () => import('@/views/seller/SellerHandoffsView.vue') },
|
||||
{ path: '/seller/earnings', name: 'seller-earnings', component: () => import('@/views/seller/SellerEarningsView.vue') },
|
||||
{ path: '/admin/login', name: 'admin-login', component: () => import('@/views/admin/AdminLoginView.vue'), meta: { layout: 'blank' } },
|
||||
{ path: "/admin", redirect: "/admin/dashboard" },
|
||||
{
|
||||
path: '/admin/dashboard',
|
||||
name: 'admin-dashboard',
|
||||
component: () => import('@/views/admin/AdminDashboardView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/m",
|
||||
name: "mobile-home",
|
||||
component: () => import("@/views/mobile/MobileHomeView.vue"),
|
||||
meta: { layout: "blank" },
|
||||
},
|
||||
{
|
||||
path: '/admin/users',
|
||||
name: 'admin-users',
|
||||
component: () => import('@/views/admin/AdminUsersView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/m/listings",
|
||||
name: "mobile-listings",
|
||||
component: () => import("@/views/mobile/MobileHomeView.vue"),
|
||||
meta: { layout: "blank" },
|
||||
},
|
||||
{
|
||||
path: '/admin/orders',
|
||||
name: 'admin-orders',
|
||||
component: () => import('@/views/admin/AdminOrdersView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/m/listings/:id",
|
||||
name: "mobile-listing-detail",
|
||||
component: () => import("@/views/public/ListingDetailView.vue"),
|
||||
meta: { layout: "blank" },
|
||||
},
|
||||
{
|
||||
path: '/admin/orders/:id',
|
||||
name: 'admin-order-detail',
|
||||
component: () => import('@/views/admin/AdminOrderDetailView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/m/login",
|
||||
name: "mobile-login",
|
||||
component: () => import("@/views/account/LoginView.vue"),
|
||||
meta: { layout: "blank" },
|
||||
},
|
||||
{
|
||||
path: '/admin/listings',
|
||||
name: 'admin-listings',
|
||||
component: () => import('@/views/admin/AdminListingsView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/m/orders",
|
||||
name: "mobile-orders",
|
||||
component: () => import("@/views/account/OrdersView.vue"),
|
||||
meta: { layout: "blank" },
|
||||
},
|
||||
{
|
||||
path: '/admin/listings/:id',
|
||||
name: 'admin-listing-detail',
|
||||
component: () => import('@/views/admin/AdminListingDetailView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/m/seller/listings/create",
|
||||
name: "mobile-seller-listing-create",
|
||||
component: () => import("@/views/seller/SellerListingCreateView.vue"),
|
||||
meta: { layout: "blank" },
|
||||
},
|
||||
{
|
||||
path: '/admin/listings/review',
|
||||
name: 'admin-listing-review',
|
||||
component: () => import('@/views/admin/AdminListingReviewView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: () => import("@/views/public/HomeView.vue"),
|
||||
},
|
||||
{
|
||||
path: '/admin/disputes',
|
||||
name: 'admin-disputes',
|
||||
component: () => import('@/views/admin/AdminDisputesView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/login",
|
||||
name: "login",
|
||||
component: () => import("@/views/account/LoginView.vue"),
|
||||
},
|
||||
{
|
||||
path: '/admin/wallet-ledger',
|
||||
name: 'admin-wallet-ledger',
|
||||
component: () => import('@/views/admin/AdminWalletLedgerView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/listings",
|
||||
name: "listings",
|
||||
component: () => import("@/views/public/ListingsView.vue"),
|
||||
},
|
||||
{
|
||||
path: '/admin/system-configs',
|
||||
name: 'admin-system-configs',
|
||||
component: () => import('@/views/admin/AdminSystemConfigsView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/listings/:id",
|
||||
name: "listing-detail",
|
||||
component: () => import("@/views/public/ListingDetailView.vue"),
|
||||
},
|
||||
{
|
||||
path: '/admin/audit-logs',
|
||||
name: 'admin-audit-logs',
|
||||
component: () => import('@/views/admin/AdminAuditLogsView.vue'),
|
||||
meta: { layout: 'admin', requiresAdmin: true },
|
||||
path: "/orders/create",
|
||||
name: "order-create",
|
||||
component: () => import("@/views/account/OrderCreateView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/orders",
|
||||
name: "orders",
|
||||
component: () => import("@/views/account/OrdersView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/orders/:id",
|
||||
name: "order-detail",
|
||||
component: () => import("@/views/account/OrderDetailView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/wallet",
|
||||
name: "wallet",
|
||||
component: () => import("@/views/account/WalletView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/notifications",
|
||||
name: "notifications",
|
||||
component: () => import("@/views/account/NotificationsView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/realname",
|
||||
name: "realname",
|
||||
component: () => import("@/views/account/RealnameView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/listings",
|
||||
name: "seller-listings",
|
||||
component: () => import("@/views/seller/SellerListingsView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/listings/create",
|
||||
name: "seller-listing-create",
|
||||
component: () => import("@/views/seller/SellerListingCreateView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/handoffs",
|
||||
name: "seller-handoffs",
|
||||
component: () => import("@/views/seller/SellerHandoffsView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/earnings",
|
||||
name: "seller-earnings",
|
||||
component: () => import("@/views/seller/SellerEarningsView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/admin/login",
|
||||
name: "admin-login",
|
||||
component: () => import("@/views/admin/AdminLoginView.vue"),
|
||||
meta: { layout: "blank" },
|
||||
},
|
||||
{
|
||||
path: "/admin/dashboard",
|
||||
name: "admin-dashboard",
|
||||
component: () => import("@/views/admin/AdminDashboardView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/users",
|
||||
name: "admin-users",
|
||||
component: () => import("@/views/admin/AdminUsersView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/orders",
|
||||
name: "admin-orders",
|
||||
component: () => import("@/views/admin/AdminOrdersView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/orders/:id",
|
||||
name: "admin-order-detail",
|
||||
component: () => import("@/views/admin/AdminOrderDetailView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/listings",
|
||||
name: "admin-listings",
|
||||
component: () => import("@/views/admin/AdminListingsView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/listings/:id",
|
||||
name: "admin-listing-detail",
|
||||
component: () => import("@/views/admin/AdminListingDetailView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/listings/review",
|
||||
name: "admin-listing-review",
|
||||
component: () => import("@/views/admin/AdminListingReviewView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/disputes",
|
||||
name: "admin-disputes",
|
||||
component: () => import("@/views/admin/AdminDisputesView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/wallet-ledger",
|
||||
name: "admin-wallet-ledger",
|
||||
component: () => import("@/views/admin/AdminWalletLedgerView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/system-configs",
|
||||
name: "admin-system-configs",
|
||||
component: () => import("@/views/admin/AdminSystemConfigsView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "/admin/audit-logs",
|
||||
name: "admin-audit-logs",
|
||||
component: () => import("@/views/admin/AdminAuditLogsView.vue"),
|
||||
meta: { layout: "admin", requiresAdmin: true },
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
router.beforeEach((to) => {
|
||||
if (to.path === '/admin/login' && localStorage.getItem('admin_access_token')) {
|
||||
return '/admin/dashboard'
|
||||
if (isMobileHost() && to.path !== toMobilePath(to.path)) {
|
||||
return { path: toMobilePath(to.path), query: to.query, hash: to.hash };
|
||||
}
|
||||
|
||||
if (
|
||||
to.path === "/admin/login" &&
|
||||
localStorage.getItem("admin_access_token")
|
||||
) {
|
||||
return "/admin/dashboard";
|
||||
}
|
||||
if (to.meta.requiresAdmin) {
|
||||
const token = localStorage.getItem('admin_access_token')
|
||||
const token = localStorage.getItem("admin_access_token");
|
||||
if (!token) {
|
||||
return '/admin/login'
|
||||
return "/admin/login";
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true;
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export function isMobileHost(hostname = window.location.hostname) {
|
||||
return hostname === 'm.localhost' || hostname.startsWith('m.')
|
||||
}
|
||||
|
||||
export function toMobilePath(path: string) {
|
||||
if (path === '/') {
|
||||
return '/m'
|
||||
}
|
||||
if (path.startsWith('/m') || path.startsWith('/admin')) {
|
||||
return path
|
||||
}
|
||||
return `/m${path}`
|
||||
}
|
||||
@@ -0,0 +1,567 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
import { fetchListings, type Listing } from '@/api/listings'
|
||||
|
||||
const loading = ref(false)
|
||||
const loadFailed = ref(false)
|
||||
const listings = ref<Listing[]>([])
|
||||
const filterOpen = ref(false)
|
||||
const activeSort = ref('推荐')
|
||||
|
||||
const sortOptions = ['推荐', '低价', '高哈夫币', '免押优先']
|
||||
const quickFilters = ['三角洲行动', '安卓/扫码', '高段位', '可包夜']
|
||||
|
||||
const fallbackListings: Listing[] = [
|
||||
{
|
||||
id: 9001,
|
||||
account_id: 0,
|
||||
owner_id: 0,
|
||||
title: '烽火地带 · 80W 哈夫币安全号',
|
||||
description: '支持扫码上号,号主在线交接,适合快速体验高资产账号。',
|
||||
game_name: '三角洲行动',
|
||||
server_region: '微信区',
|
||||
login_platform: '安卓扫码',
|
||||
rank_level: '机密行动 王牌',
|
||||
haf_coin_amount: 800000,
|
||||
screenshot_urls: [],
|
||||
price_hourly: 6.8,
|
||||
price_daily: 58,
|
||||
price_weekly: 288,
|
||||
deposit_amount: 200,
|
||||
min_rent_hours: 2,
|
||||
max_rent_hours: 168,
|
||||
status: 'published',
|
||||
review_status: 'approved',
|
||||
review_reason: '',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
},
|
||||
{
|
||||
id: 9002,
|
||||
account_id: 0,
|
||||
owner_id: 0,
|
||||
title: '航天基地 · 高配皮肤成品号',
|
||||
description: '热门枪皮齐全,低押金,新手友好。',
|
||||
game_name: '三角洲行动',
|
||||
server_region: 'QQ区',
|
||||
login_platform: 'iOS扫码',
|
||||
rank_level: '钻石',
|
||||
haf_coin_amount: 520000,
|
||||
screenshot_urls: [],
|
||||
price_hourly: 4.5,
|
||||
price_daily: 39,
|
||||
price_weekly: 198,
|
||||
deposit_amount: 120,
|
||||
min_rent_hours: 1,
|
||||
max_rent_hours: 72,
|
||||
status: 'published',
|
||||
review_status: 'approved',
|
||||
review_reason: '',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
},
|
||||
{
|
||||
id: 9003,
|
||||
account_id: 0,
|
||||
owner_id: 0,
|
||||
title: '零号大坝 · 搬砖专用稳定号',
|
||||
description: '可长租,支持包周,交接说明完整。',
|
||||
game_name: '三角洲行动',
|
||||
server_region: '微信区',
|
||||
login_platform: '安卓/模拟器',
|
||||
rank_level: '铂金',
|
||||
haf_coin_amount: 360000,
|
||||
screenshot_urls: [],
|
||||
price_hourly: 3.9,
|
||||
price_daily: 32,
|
||||
price_weekly: 168,
|
||||
deposit_amount: 80,
|
||||
min_rent_hours: 3,
|
||||
max_rent_hours: 168,
|
||||
status: 'published',
|
||||
review_status: 'approved',
|
||||
review_reason: '',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
},
|
||||
]
|
||||
|
||||
const displayListings = computed(() => (listings.value.length > 0 ? listings.value : fallbackListings))
|
||||
|
||||
onMounted(loadListings)
|
||||
|
||||
async function loadListings() {
|
||||
loading.value = true
|
||||
loadFailed.value = false
|
||||
try {
|
||||
listings.value = await fetchListings()
|
||||
} catch {
|
||||
loadFailed.value = true
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function formatCoin(amount: number) {
|
||||
if (amount >= 10000) {
|
||||
return `${Math.round(amount / 10000)}W`
|
||||
}
|
||||
return `${amount}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="mobile-shell">
|
||||
<section class="mobile-hero">
|
||||
<div class="mobile-topbar">
|
||||
<div class="mobile-brand">
|
||||
<span class="mobile-logo">锤</span>
|
||||
<div>
|
||||
<strong>大锤商行</strong>
|
||||
<small>哈夫币租号</small>
|
||||
</div>
|
||||
</div>
|
||||
<button class="mobile-service" type="button">客服</button>
|
||||
</div>
|
||||
|
||||
<div class="fraud-alert">
|
||||
<span>防骗提示</span>
|
||||
平台担保交易,拒绝私下转账/共享验证码,交接全程留痕。
|
||||
</div>
|
||||
|
||||
<label class="mobile-search">
|
||||
<span>🔍</span>
|
||||
<input type="search" placeholder="搜索区服 / 段位 / 哈夫币数量" />
|
||||
</label>
|
||||
|
||||
<div class="mobile-banner">
|
||||
<div>
|
||||
<p>三角洲行动账号专区</p>
|
||||
<h1>高哈夫币 · 安全交接 · 随租随玩</h1>
|
||||
<span>新人领券最高减 20 元</span>
|
||||
</div>
|
||||
<div class="banner-badge">HOT</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mobile-content">
|
||||
<button class="filter-entry" type="button" @click="filterOpen = !filterOpen">
|
||||
<span>高级筛选</span>
|
||||
<strong>{{ filterOpen ? '收起' : '展开' }}</strong>
|
||||
</button>
|
||||
|
||||
<div v-if="filterOpen" class="filter-panel">
|
||||
<button v-for="item in quickFilters" :key="item" type="button">{{ item }}</button>
|
||||
</div>
|
||||
|
||||
<div class="sort-pills" aria-label="排序方式">
|
||||
<button
|
||||
v-for="item in sortOptions"
|
||||
:key="item"
|
||||
:class="{ active: activeSort === item }"
|
||||
type="button"
|
||||
@click="activeSort = item"
|
||||
>
|
||||
{{ item }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="loadFailed" class="mobile-tip">接口暂不可用,当前展示移动端示例数据。</p>
|
||||
<p v-else-if="loading" class="mobile-tip">正在加载优质账号...</p>
|
||||
|
||||
<div class="mobile-list">
|
||||
<RouterLink v-for="item in displayListings" :key="item.id" class="mobile-card" :to="`/listings/${item.id}`">
|
||||
<div class="card-cover">
|
||||
<span>{{ formatCoin(item.haf_coin_amount) }}</span>
|
||||
<small>哈夫币</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-title-row">
|
||||
<h2>{{ item.title }}</h2>
|
||||
<em>可租</em>
|
||||
</div>
|
||||
<p>{{ item.server_region }} · {{ item.login_platform }} · {{ item.rank_level || '未填写段位' }}</p>
|
||||
<div class="card-tags">
|
||||
<span>担保</span>
|
||||
<span>秒接单</span>
|
||||
<span>{{ item.min_rent_hours }}小时起租</span>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<strong>¥{{ item.price_hourly }}<small>/小时</small></strong>
|
||||
<span>押金 ¥{{ item.deposit_amount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<nav class="mobile-tabbar" aria-label="底部导航">
|
||||
<RouterLink to="/" class="tab-item active">首页</RouterLink>
|
||||
<RouterLink to="/listings" class="tab-item">租号</RouterLink>
|
||||
<RouterLink to="/seller/listings/create" class="publish-tab">发布</RouterLink>
|
||||
<RouterLink to="/orders" class="tab-item">订单</RouterLink>
|
||||
<RouterLink to="/login" class="tab-item">我的</RouterLink>
|
||||
</nav>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mobile-shell {
|
||||
min-height: 100vh;
|
||||
background: #eef5ff;
|
||||
color: #17233d;
|
||||
padding-bottom: 92px;
|
||||
}
|
||||
|
||||
.mobile-hero {
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 28px 28px;
|
||||
background: linear-gradient(180deg, #1479ff 0%, #0b66e4 58%, #eef5ff 58%);
|
||||
padding: 14px 14px 0;
|
||||
}
|
||||
|
||||
.mobile-topbar,
|
||||
.mobile-brand,
|
||||
.card-title-row,
|
||||
.card-footer,
|
||||
.mobile-tabbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mobile-topbar {
|
||||
justify-content: space-between;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.mobile-brand {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mobile-logo {
|
||||
display: grid;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
place-items: center;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
color: #1479ff;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.mobile-brand strong,
|
||||
.mobile-brand small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile-brand small {
|
||||
margin-top: 2px;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mobile-service {
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
color: #ffffff;
|
||||
padding: 7px 12px;
|
||||
}
|
||||
|
||||
.fraud-alert {
|
||||
margin-top: 14px;
|
||||
border-radius: 12px;
|
||||
background: #ffe27a;
|
||||
color: #5c3b00;
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.fraud-alert span {
|
||||
margin-right: 6px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.mobile-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
padding: 11px 14px;
|
||||
box-shadow: 0 10px 24px rgba(0, 54, 135, 0.12);
|
||||
}
|
||||
|
||||
.mobile-search input {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
color: #17233d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mobile-banner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
margin-top: 14px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(135deg, #ffffff, #dcecff);
|
||||
padding: 18px;
|
||||
box-shadow: 0 14px 34px rgba(20, 121, 255, 0.18);
|
||||
}
|
||||
|
||||
.mobile-banner p,
|
||||
.mobile-banner h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mobile-banner p {
|
||||
color: #1479ff;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.mobile-banner h1 {
|
||||
margin-top: 6px;
|
||||
font-size: 22px;
|
||||
line-height: 1.22;
|
||||
}
|
||||
|
||||
.mobile-banner span {
|
||||
display: inline-flex;
|
||||
margin-top: 12px;
|
||||
border-radius: 999px;
|
||||
background: #ffefb5;
|
||||
color: #7a4b00;
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.banner-badge {
|
||||
align-self: flex-start;
|
||||
border-radius: 12px;
|
||||
background: #ff6a00;
|
||||
color: #ffffff;
|
||||
padding: 8px 9px;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
transform: rotate(8deg);
|
||||
}
|
||||
|
||||
.mobile-content {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.filter-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
background: #ffffff;
|
||||
color: #17233d;
|
||||
padding: 14px;
|
||||
font-size: 15px;
|
||||
box-shadow: 0 8px 22px rgba(23, 35, 61, 0.06);
|
||||
}
|
||||
|
||||
.filter-entry strong {
|
||||
color: #1479ff;
|
||||
}
|
||||
|
||||
.filter-panel,
|
||||
.sort-pills {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
padding: 10px 0 2px;
|
||||
}
|
||||
|
||||
.filter-panel button,
|
||||
.sort-pills button {
|
||||
flex: 0 0 auto;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
color: #4d5b73;
|
||||
padding: 9px 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sort-pills button.active {
|
||||
background: #1479ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.mobile-tip {
|
||||
margin: 12px 2px;
|
||||
color: #6b7a90;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mobile-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.mobile-card {
|
||||
display: grid;
|
||||
grid-template-columns: 92px 1fr;
|
||||
gap: 12px;
|
||||
border-radius: 18px;
|
||||
background: #ffffff;
|
||||
padding: 12px;
|
||||
box-shadow: 0 10px 26px rgba(23, 35, 61, 0.07);
|
||||
}
|
||||
|
||||
.card-cover {
|
||||
display: grid;
|
||||
min-height: 104px;
|
||||
place-content: center;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(145deg, #126cff, #63b3ff);
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-cover span {
|
||||
font-size: 24px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.card-cover small {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-title-row {
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card-title-row h2 {
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-title-row em {
|
||||
flex: 0 0 auto;
|
||||
border-radius: 999px;
|
||||
background: #e9f8ef;
|
||||
color: #0f9f57;
|
||||
padding: 3px 7px;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.card-body p {
|
||||
overflow: hidden;
|
||||
margin: 7px 0 0;
|
||||
color: #6b7a90;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.card-tags span {
|
||||
border-radius: 6px;
|
||||
background: #f0f6ff;
|
||||
color: #1479ff;
|
||||
padding: 3px 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
justify-content: space-between;
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
.card-footer strong {
|
||||
color: #ff5f00;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.card-footer small,
|
||||
.card-footer span {
|
||||
color: #8a96a8;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mobile-tabbar {
|
||||
position: fixed;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
left: 12px;
|
||||
z-index: 20;
|
||||
justify-content: space-around;
|
||||
height: 60px;
|
||||
border-radius: 22px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 16px 36px rgba(23, 35, 61, 0.16);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
color: #708097;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #1479ff;
|
||||
}
|
||||
|
||||
.publish-tab {
|
||||
display: grid;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
place-items: center;
|
||||
border: 5px solid #eef5ff;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(180deg, #ffb21a, #ff6a00);
|
||||
color: #ffffff;
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
transform: translateY(-18px);
|
||||
box-shadow: 0 10px 24px rgba(255, 106, 0, 0.35);
|
||||
}
|
||||
|
||||
@media (min-width: 520px) {
|
||||
.mobile-shell {
|
||||
max-width: 430px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 0 0 1px rgba(23, 35, 61, 0.08);
|
||||
}
|
||||
|
||||
.mobile-tabbar {
|
||||
right: calc((100vw - 430px) / 2 + 12px);
|
||||
left: calc((100vw - 430px) / 2 + 12px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user