优化订单详情页面 ui

This commit is contained in:
yml
2026-06-17 22:58:18 +08:00
parent bd6e15179e
commit f8156aec44
@@ -7,6 +7,7 @@ import { useRoute } from 'vue-router'
import { fetchAdminUsers, type AdminUserItem } from '@/features/admin/api/adminUsers'
import { fetchAdminFileBlob } from '@/shared/api/files'
import AuthImage from '@/shared/components/business/AuthImage.vue'
import { adminPath } from '@/shared/utils/adminPath'
import { formatCentWithSymbol } from '@/shared/utils/money'
import {
@@ -23,7 +24,21 @@ import {
userStatusLabel,
} from '@/shared/utils/statusLabels'
import { formatDateTime } from '@/shared/utils/time'
import { formatListingCode } from '@/shared/utils/listingDisplay'
import {
assetRegions,
formatEstimatedRentalDuration,
formatHafCoinM,
getCoinWan,
getDailyLoss,
getListingConsumablePrice,
getListingResources,
getOnlineTimeText,
getResourceQuantity,
getSkinNames,
formatListingCode,
readAssetNumber,
readAssetString,
} from '@/shared/utils/listingDisplay'
const route = useRoute()
const loading = ref(false)
@@ -51,6 +66,77 @@ const canTransfer = computed(() => !!listing.value && listing.value.status !== '
const selectedUserAvailable = computed(
() => selectedUser.value?.status === 'active' && selectedUser.value.realname_status === 'verified'
)
const assetRows = computed(() => {
if (!listing.value) return []
return [
{ label: '商品编号', value: formatListingCode(listing.value) },
{ label: '账号 ID', value: listing.value.account_id },
{ label: '游戏', value: listing.value.game_name || '-' },
{ label: '区服', value: listing.value.server_region || '-' },
{ label: '平台', value: listing.value.login_platform || '-' },
{ label: '段位', value: listing.value.rank_level || '-' },
{ label: '烽火等级', value: assetNumberText(listing.value, 'fire_level') },
{ label: '保险格数', value: assetText(listing.value, 'season_insurance') },
{
label: '体力/负重',
value: `${assetText(listing.value, 'stamina_level')} / ${assetText(listing.value, 'load_level')}`,
},
{ label: '绝密 KD', value: assetNumberText(listing.value, 'secret_kd') },
{ label: '常用地区', value: regionText(listing.value) },
{ label: '在线时间', value: getOnlineTimeText(listing.value) || '-' },
]
})
const ownerRows = computed(() => {
if (!listing.value) return []
return [
{
label: '号主',
value: listing.value.owner_phone || listing.value.owner_nickname || listing.value.owner_id,
},
{ label: '号主 ID', value: listing.value.owner_id },
{ label: '价格', value: listingPrice(listing.value) },
{ label: '押金', value: moneyCent(listing.value.deposit_amount_cent) },
{ label: '哈夫币', value: formatHafCoinM(getCoinWan(listing.value)) },
{ label: '每日损耗', value: getDailyLoss(listing.value) || '-' },
{ label: '预计可租', value: formatEstimatedRentalDuration(listing.value) },
{
label: '额外消耗',
value: moneyCent(Math.round(getListingConsumablePrice(listing.value) * 100)),
},
]
})
const resourceCards = computed(() => {
if (!listing.value) return []
const core = [
{ key: 'awmAmmo', label: 'AWM 子弹', quantity: getResourceQuantity(listing.value, 'awmAmmo') },
{ key: 'helmet6', label: '六级头', quantity: getResourceQuantity(listing.value, 'helmet6') },
{ key: 'armor6', label: '六级甲', quantity: getResourceQuantity(listing.value, 'armor6') },
].filter(item => item.quantity > 0)
const extra = getListingResources(listing.value)
.filter(item => !core.some(coreItem => coreItem.key === item.key))
.slice(0, 6)
.map(item => ({ key: item.key, label: item.label, quantity: item.quantity }))
return [...core, ...extra]
})
const skinNames = computed(() => (listing.value ? getSkinNames(listing.value).slice(0, 16) : []))
const hiddenSkinCount = computed(() => {
if (!listing.value) return 0
return Math.max(0, getSkinNames(listing.value).length - skinNames.value.length)
})
const statusTone = computed(() => {
if (!listing.value) return 'info'
if (listing.value.status === 'published') return 'success'
if (['offline', 'abnormal'].includes(listing.value.status)) return 'danger'
if (listing.value.status === 'rented') return 'warning'
return 'info'
})
const reviewTone = computed(() => {
if (!listing.value) return 'info'
if (listing.value.review_status === 'approved') return 'success'
if (listing.value.review_status === 'rejected') return 'danger'
if (listing.value.review_status === 'pending') return 'warning'
return 'info'
})
onMounted(loadListing)
@@ -158,18 +244,42 @@ function listingPrice(row: Listing) {
return moneyCent(row.price_cent)
}
function assetText(row: Listing, key: string) {
const value = readAssetString(row, key)
if (value) return value
const numberValue = readAssetNumber(row, key)
return numberValue > 0 ? String(numberValue) : '-'
}
function assetNumberText(row: Listing, key: string) {
const value = readAssetNumber(row, key)
if (value <= 0) return '-'
const rounded = Math.round(value * 10) / 10
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)
}
function regionText(row: Listing) {
const regions = assetRegions(row)
return regions.length ? regions.join('、') : '-'
}
function extractObjectKey(url: string) {
try {
const parsed = new URL(url, window.location.origin)
return parsed.searchParams.get('key') || url
return parsed.searchParams.get('key') || ''
} catch {
return url
return ''
}
}
async function openScreenshot(url: string) {
try {
const blob = await fetchAdminFileBlob(extractObjectKey(url))
const key = extractObjectKey(url)
if (!key) {
window.open(url, '_blank')
return
}
const blob = await fetchAdminFileBlob(key)
window.open(URL.createObjectURL(blob), '_blank')
} catch {
window.open(url, '_blank')
@@ -179,18 +289,30 @@ async function openScreenshot(url: string) {
<template>
<section class="page" v-loading="loading">
<div v-if="listing" class="page-header-row">
<div class="page-header">
<p class="eyebrow">商品编号{{ formatListingCode(listing) }}</p>
<h1>商品详情</h1>
<p>{{ listing.title }} · {{ listing.server_region }} / {{ listing.login_platform }}</p>
<div v-if="listing" class="listing-detail-header">
<div class="listing-title-block">
<div class="listing-code-line">
<span>商品编号</span>
<strong>{{ formatListingCode(listing) }}</strong>
</div>
<h1>{{ listing.title || '商品详情' }}</h1>
<div class="listing-subtitle">
<span>{{ listing.server_region || '-' }}</span>
<span>{{ listing.rank_level || '-' }}</span>
<span>{{ listing.login_platform || '-' }}</span>
<span>账号 {{ listing.account_id }}</span>
</div>
</div>
<div class="toolbar-actions">
<el-button :icon="CopyDocument" @click="copyListingCode">复制编号</el-button>
<RouterLink :to="adminPath('listings')">
<el-button>返回列表</el-button>
</RouterLink>
<el-button type="primary" plain :disabled="!canTransfer" @click="openTransfer"
<el-button
class="transfer-owner-trigger"
type="success"
:disabled="!canTransfer"
@click="openTransfer"
>转移所有权</el-button
>
<el-button type="warning" :disabled="!canOperate" @click="openAction('offline')"
@@ -202,64 +324,137 @@ async function openScreenshot(url: string) {
</div>
</div>
<div v-if="listing" class="metric-grid dashboard-metrics">
<div class="metric-card">
<div v-if="listing" class="listing-summary-bar">
<div>
<span>商品状态</span>
<strong>{{ listingStatusLabel(listing.status) }}</strong>
<el-tag :type="statusTone" effect="light">{{ listingStatusLabel(listing.status) }}</el-tag>
</div>
<div class="metric-card">
<div>
<span>审核状态</span>
<strong>{{ listingReviewStatusLabel(listing.review_status) }}</strong>
<el-tag :type="reviewTone" effect="light">{{
listingReviewStatusLabel(listing.review_status)
}}</el-tag>
</div>
<div class="metric-card">
<span>价格</span>
<div>
<span>租金</span>
<strong>{{ listingPrice(listing) }}</strong>
</div>
<div class="metric-card">
<div>
<span>押金</span>
<strong>{{ moneyCent(listing.deposit_amount_cent) }}</strong>
</div>
</div>
<div v-if="listing" class="dashboard-panels">
<div class="order-panel dashboard-panel">
<h2>账号信息</h2>
<p>商品编号{{ formatListingCode(listing) }}</p>
<p>账号 ID{{ listing.account_id }}</p>
<p>游戏{{ listing.game_name }}</p>
<p>区服{{ listing.server_region }}</p>
<p>平台{{ listing.login_platform }}</p>
<p>段位{{ listing.rank_level || '-' }}</p>
<p>哈夫币{{ listing.haf_coin_amount }}</p>
<p>资产截图{{ listing.screenshot_urls?.length || 0 }} </p>
<div>
<span>哈夫币</span>
<strong>{{ formatHafCoinM(getCoinWan(listing)) }}</strong>
</div>
<div class="order-panel dashboard-panel">
<h2>号主与价格</h2>
<p>号主{{ listing.owner_phone || listing.owner_nickname || listing.owner_id }}</p>
<p>号主 ID{{ listing.owner_id }}</p>
<p>价格{{ listingPrice(listing) }}</p>
<p>押金{{ moneyCent(listing.deposit_amount_cent) }}</p>
<div>
<span>预计可租</span>
<strong>{{ formatEstimatedRentalDuration(listing) }}</strong>
</div>
</div>
<div v-if="listing" class="order-panel dashboard-panel">
<h2>说明与审核原因</h2>
<p>{{ listing.description || '暂无商品说明' }}</p>
<p>审核/后台原因{{ listing.review_reason || '-' }}</p>
<p>上架时间{{ formatDateTime(listing.published_at) }}</p>
<p>更新时间{{ formatDateTime(listing.updated_at) }}</p>
</div>
<div v-if="listing" class="order-panel dashboard-panel">
<h2>资产截图</h2>
<div v-if="listing.screenshot_urls?.length" class="evidence-list">
<div v-for="url in listing.screenshot_urls" :key="url" class="evidence-row">
<span>{{ url }}</span>
<el-button size="small" @click="openScreenshot(url)">打开</el-button>
<div v-if="listing" class="listing-workspace">
<section class="detail-panel primary-panel">
<div class="panel-heading">
<h2>账号属性</h2>
<span>{{ listing.game_name || 'delta_force' }}</span>
</div>
</div>
<p v-else>暂无截图</p>
<dl class="compact-detail-grid">
<div v-for="row in assetRows" :key="row.label">
<dt>{{ row.label }}</dt>
<dd>{{ row.value }}</dd>
</div>
</dl>
</section>
<section class="detail-panel side-panel">
<div class="panel-heading">
<h2>号主与价格</h2>
<span>{{ listing.owner_phone || listing.owner_nickname || listing.owner_id }}</span>
</div>
<dl class="compact-detail-grid single">
<div v-for="row in ownerRows" :key="row.label">
<dt>{{ row.label }}</dt>
<dd>{{ row.value }}</dd>
</div>
</dl>
</section>
<section class="detail-panel primary-panel">
<div class="panel-heading">
<h2>库存资产</h2>
<span
>{{ resourceCards.length }} 项资源 ·
{{ skinNames.length + hiddenSkinCount }} 个皮肤</span
>
</div>
<div v-if="resourceCards.length" class="resource-grid">
<div v-for="item in resourceCards" :key="item.key" class="resource-item">
<span>{{ item.label }}</span>
<strong>{{ item.quantity }}</strong>
</div>
</div>
<p v-else class="empty-line">暂无库存资源</p>
<div class="skin-chip-list">
<el-tag v-for="skin in skinNames" :key="skin" size="small" effect="plain">{{
skin
}}</el-tag>
<el-tag v-if="hiddenSkinCount" size="small" effect="plain">+{{ hiddenSkinCount }}</el-tag>
<span v-if="!skinNames.length" class="empty-line">暂无皮肤数据</span>
</div>
</section>
<section class="detail-panel side-panel">
<div class="panel-heading">
<h2>说明与时间</h2>
<span>{{ formatDateTime(listing.updated_at) }}</span>
</div>
<dl class="compact-detail-grid single">
<div>
<dt>商品说明</dt>
<dd>{{ listing.description || '暂无商品说明' }}</dd>
</div>
<div>
<dt>审核/后台原因</dt>
<dd>{{ listing.review_reason || '-' }}</dd>
</div>
<div>
<dt>上架时间</dt>
<dd>{{ formatDateTime(listing.published_at) }}</dd>
</div>
<div>
<dt>更新时间</dt>
<dd>{{ formatDateTime(listing.updated_at) }}</dd>
</div>
</dl>
</section>
<section class="detail-panel screenshots-panel">
<div class="panel-heading">
<h2>资产截图</h2>
<span>{{ listing.screenshot_urls?.length || 0 }} </span>
</div>
<div v-if="listing.screenshot_urls?.length" class="screenshot-grid">
<button
v-for="(url, index) in listing.screenshot_urls"
:key="url"
class="screenshot-thumb"
type="button"
@click="openScreenshot(url)"
>
<AuthImage
:source="url"
admin
:alt="`资产截图 ${index + 1}`"
image-class="screenshot-image"
fallback-class="screenshot-image"
fit="cover"
/>
<span>查看 {{ index + 1 }}</span>
</button>
</div>
<p v-else class="empty-line">暂无截图</p>
</section>
</div>
<el-dialog
@@ -285,25 +480,47 @@ async function openScreenshot(url: string) {
</template>
</el-dialog>
<el-dialog v-model="transferVisible" title="转移发布所有权" width="640px">
<el-dialog
v-model="transferVisible"
title="转移发布所有权"
width="680px"
class="transfer-owner-dialog"
>
<div v-if="listing" class="dialog-body transfer-dialog">
<div class="transfer-current">
<span>当前号主</span>
<strong>{{ listing.owner_phone || listing.owner_nickname || listing.owner_id }}</strong>
<small>ID{{ listing.owner_id }}</small>
<div class="transfer-route">
<div class="transfer-owner-card">
<span>当前号主</span>
<strong>{{ listing.owner_phone || listing.owner_nickname || listing.owner_id }}</strong>
<small>ID{{ listing.owner_id }}</small>
</div>
<div class="transfer-arrow"></div>
<div class="transfer-owner-card target" :class="{ empty: !selectedUser }">
<span>目标号主</span>
<strong v-if="selectedUser">{{ selectedUser.nickname || selectedUser.phone }}</strong>
<strong v-else>请选择用户</strong>
<small v-if="selectedUser"
>ID{{ selectedUser.id }} · {{ selectedUser.phone || '-' }}</small
>
<small v-else>仅支持正常且已实名用户</small>
</div>
</div>
<el-input
v-model="userKeyword"
clearable
placeholder="搜索手机号 / 昵称 / 用户ID"
@keyup.enter="searchUsers"
@clear="searchUsers"
>
<template #append>
<el-button :icon="Search" :loading="userSearching" @click="searchUsers">查询</el-button>
</template>
</el-input>
<div class="transfer-section">
<label>选择目标用户</label>
<el-input
v-model="userKeyword"
clearable
placeholder="输入手机号 / 昵称 / 用户ID 查询"
@keyup.enter="searchUsers"
@clear="searchUsers"
>
<template #append>
<el-button :icon="Search" :loading="userSearching" @click="searchUsers"
>查询</el-button
>
</template>
</el-input>
</div>
<div class="transfer-user-list">
<button
@@ -314,14 +531,13 @@ async function openScreenshot(url: string) {
type="button"
@click="selectUser(user)"
>
<span>
<span class="transfer-user-main">
<strong>{{ user.nickname || user.phone || `用户${user.id}` }}</strong>
<small>{{ user.phone || '-' }}</small>
<small>{{ user.phone || '-' }} · ID {{ user.id }}</small>
</span>
<span class="transfer-user-meta">
<el-tag size="small" type="success">{{ userStatusLabel(user.status) }}</el-tag>
<el-tag size="small">{{ realnameStatusLabel(user.realname_status) }}</el-tag>
<small>ID{{ user.id }}</small>
</span>
</button>
<el-empty
@@ -331,12 +547,15 @@ async function openScreenshot(url: string) {
/>
</div>
<el-input
v-model="transferReason"
type="textarea"
:rows="3"
placeholder="填写转移原因,会写入审计日志"
/>
<div class="transfer-section">
<label>转移原因</label>
<el-input
v-model="transferReason"
type="textarea"
:rows="3"
placeholder="填写转移原因,会写入审计日志"
/>
</div>
</div>
<template #footer>
<el-button @click="transferVisible = false">取消</el-button>
@@ -354,36 +573,345 @@ async function openScreenshot(url: string) {
</template>
<style scoped>
.transfer-dialog {
.listing-detail-header {
display: grid;
gap: 16px;
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
gap: 20px;
margin-bottom: 18px;
}
.transfer-current {
.listing-title-block {
min-width: 0;
}
.listing-code-line {
display: inline-flex;
align-items: center;
gap: 8px;
color: #64748b;
font-size: 13px;
font-weight: 700;
}
.listing-code-line strong {
color: #2563eb;
letter-spacing: 0;
}
.listing-title-block h1 {
margin: 8px 0 10px;
color: #0f172a;
font-size: 30px;
line-height: 1.15;
}
.listing-subtitle {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.listing-subtitle span {
padding: 4px 9px;
border: 1px solid #dbe3ef;
border-radius: 8px;
background: #fff;
color: #64748b;
font-size: 13px;
}
.listing-summary-bar {
display: grid;
grid-template-columns: repeat(6, minmax(120px, 1fr));
gap: 1px;
overflow: hidden;
margin-bottom: 16px;
border: 1px solid #e5e7eb;
border-radius: 10px;
background: #e5e7eb;
}
.listing-summary-bar > div {
display: grid;
min-height: 74px;
align-content: center;
gap: 8px;
padding: 12px 14px;
background: #fff;
}
.listing-summary-bar span {
color: #64748b;
font-size: 13px;
font-weight: 700;
}
.listing-summary-bar strong {
color: #0f172a;
font-size: 21px;
line-height: 1.1;
}
.listing-workspace {
display: grid;
grid-template-columns: minmax(0, 1.3fr) minmax(360px, 0.7fr);
gap: 16px;
align-items: start;
}
.detail-panel {
min-width: 0;
border: 1px solid #e5e7eb;
border-radius: 10px;
background: #fff;
padding: 16px;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
}
.screenshots-panel {
grid-column: 1 / -1;
}
.panel-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
padding-bottom: 10px;
border-bottom: 1px solid #eef2f7;
}
.panel-heading h2 {
margin: 0;
color: #0f172a;
font-size: 16px;
font-weight: 800;
}
.panel-heading span {
overflow: hidden;
color: #94a3b8;
font-size: 13px;
text-overflow: ellipsis;
white-space: nowrap;
}
.compact-detail-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px;
overflow: hidden;
margin: 0;
border: 1px solid #eef2f7;
border-radius: 8px;
background: #eef2f7;
}
.compact-detail-grid.single {
grid-template-columns: 1fr;
}
.compact-detail-grid div {
display: grid;
grid-template-columns: 88px minmax(0, 1fr);
gap: 10px;
padding: 12px 14px;
align-items: start;
min-height: 38px;
padding: 10px 12px;
background: #fff;
}
.compact-detail-grid dt {
color: #64748b;
font-size: 13px;
font-weight: 700;
}
.compact-detail-grid dd {
min-width: 0;
margin: 0;
overflow-wrap: anywhere;
color: #0f172a;
font-size: 13px;
font-weight: 700;
}
.resource-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 10px;
}
.resource-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 12px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #f8fafc;
}
.resource-item span {
color: #64748b;
font-size: 13px;
font-weight: 700;
}
.resource-item strong {
color: #0f172a;
font-size: 18px;
}
.skin-chip-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.screenshot-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
gap: 12px;
}
.screenshot-thumb {
display: grid;
gap: 8px;
padding: 8px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fff;
color: #64748b;
cursor: pointer;
text-align: left;
}
.screenshot-thumb:hover {
border-color: #93c5fd;
background: #eff6ff;
}
.screenshot-thumb img {
width: 100%;
aspect-ratio: 4 / 3;
border-radius: 6px;
background: #f1f5f9;
object-fit: cover;
}
.screenshot-image {
width: 100%;
aspect-ratio: 4 / 3;
border-radius: 6px;
}
.screenshot-thumb span {
color: #334155;
font-size: 13px;
font-weight: 700;
}
.empty-line {
margin: 0;
color: #94a3b8;
font-size: 13px;
}
.transfer-owner-trigger {
min-width: 120px;
font-weight: 700;
}
.transfer-dialog {
display: grid;
gap: 18px;
}
.transfer-route {
display: grid;
grid-template-columns: minmax(0, 1fr) 32px minmax(0, 1fr);
align-items: stretch;
gap: 12px;
padding: 14px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #f8fafc;
}
.transfer-owner-card {
display: flex;
min-width: 0;
flex-direction: column;
justify-content: center;
gap: 6px;
padding: 12px 14px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fff;
color: #64748b;
}
.transfer-current strong {
color: #0f172a;
.transfer-owner-card.target {
border-color: #86efac;
background: #f0fdf4;
}
.transfer-current small {
margin-left: auto;
.transfer-owner-card.target.empty {
border-color: #bfdbfe;
background: #eff6ff;
}
.transfer-owner-card span {
font-size: 13px;
}
.transfer-owner-card strong {
overflow: hidden;
color: #0f172a;
font-size: 18px;
line-height: 1.25;
text-overflow: ellipsis;
white-space: nowrap;
}
.transfer-owner-card small {
overflow: hidden;
color: #94a3b8;
text-overflow: ellipsis;
white-space: nowrap;
}
.transfer-arrow {
display: grid;
place-items: center;
color: #2563eb;
font-size: 22px;
font-weight: 800;
}
.transfer-section {
display: grid;
gap: 8px;
}
.transfer-section label {
color: #334155;
font-size: 14px;
font-weight: 700;
}
.transfer-user-list {
min-height: 120px;
max-height: 260px;
min-height: 138px;
max-height: 280px;
overflow: auto;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fff;
}
.transfer-user {
@@ -392,7 +920,7 @@ async function openScreenshot(url: string) {
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 14px;
padding: 13px 14px;
border: 0;
border-bottom: 1px solid #eef2f7;
background: #fff;
@@ -410,15 +938,29 @@ async function openScreenshot(url: string) {
background: #eff6ff;
}
.transfer-user.is-selected {
box-shadow: inset 3px 0 0 #2563eb;
}
.transfer-user-main {
min-width: 0;
}
.transfer-user strong {
display: block;
overflow: hidden;
color: #0f172a;
text-overflow: ellipsis;
white-space: nowrap;
}
.transfer-user small {
display: block;
margin-top: 4px;
overflow: hidden;
color: #94a3b8;
text-overflow: ellipsis;
white-space: nowrap;
}
.transfer-user-meta {
@@ -427,4 +969,32 @@ async function openScreenshot(url: string) {
align-items: center;
gap: 8px;
}
@media (max-width: 760px) {
.listing-detail-header,
.listing-workspace {
grid-template-columns: 1fr;
}
.listing-summary-bar {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.compact-detail-grid {
grid-template-columns: 1fr;
}
.transfer-route {
grid-template-columns: 1fr;
}
.transfer-arrow {
transform: rotate(90deg);
}
.transfer-user {
align-items: flex-start;
flex-direction: column;
}
}
</style>