diff --git a/frontend/src/features/chats/components/ChatAttachmentImage.vue b/frontend/src/features/chats/components/ChatAttachmentImage.vue deleted file mode 100644 index 63de32b..0000000 --- a/frontend/src/features/chats/components/ChatAttachmentImage.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - - - diff --git a/frontend/src/features/listings/views/ListingsView.vue b/frontend/src/features/listings/views/ListingsView.vue deleted file mode 100644 index 216df2c..0000000 --- a/frontend/src/features/listings/views/ListingsView.vue +++ /dev/null @@ -1,1247 +0,0 @@ - - - - - diff --git a/frontend/src/features/orders/composables/useOrderDetail.ts b/frontend/src/features/orders/composables/useOrderDetail.ts deleted file mode 100644 index 63d2985..0000000 --- a/frontend/src/features/orders/composables/useOrderDetail.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { computed, ref, onMounted } from 'vue' -import { useRoute } from 'vue-router' -import type { Order, HandoffRecord } from '../api/orders' -import { - fetchOrder, - fetchHandoffRecords, - cancelOrder, - startOrderPayment, - submitHandoff, - confirmReceive, -} from '../api/orders' -import { createDispute } from '@/features/disputes/api/disputes' -import { uploadFile } from '@/shared/api/files' -import { fetchOrderChat } from '@/features/chats/api/chats' -import { useSessionStore } from '@/stores/session' -import { usePaymentPolling } from './usePaymentPolling' -import { useSettlement } from './useSettlement' - -/** - * 订单详情 Composable - * 负责订单的核心流程:加载、取消、支付、交接、收货 - * 结算和支付轮询逻辑已拆分到独立 composables - */ -export function useOrderDetail() { - const route = useRoute() - const session = useSessionStore() - - // Loading states - const loading = ref(false) - const cancelling = ref(false) - const startingPayment = ref(false) - const handoffing = ref(false) - const confirming = ref(false) - const disputing = ref(false) - const uploadingEvidence = ref(false) - const openingChat = ref(false) - - // Data - const order = ref(null) - const handoffRecords = ref([]) - const handoffContent = ref('') - - // Dispute form - const disputeType = ref('cannot_login') - const disputeDescription = ref('') - const disputeEvidenceText = ref('') - - // 使用拆分的 composables - const paymentPolling = usePaymentPolling() - const settlement = useSettlement(order) - - // Computed - const isOwner = computed(() => order.value?.owner_id === session.userId) - const isRenter = computed(() => order.value?.renter_id === session.userId) - const orderAmountLabel = computed(() => (isOwner.value ? '我的租金' : '订单金额')) - - const canOpenDispute = computed(() => { - if (!order.value || (!isOwner.value && !isRenter.value)) return false - return ![ - 'completed', - 'cancelled', - 'closed', - 'disputing', - 'checkout_disputing', - 'abnormal', - ].includes(order.value.status) - }) - - const isCheckoutDisputeStage = computed(() => { - return ( - !!order.value && - ['pending_checkout_confirm', 'pending_checkout_accept'].includes(order.value.status) - ) - }) - - // Methods - async function loadOrder() { - loading.value = true - try { - order.value = await fetchOrder(String(route.params.id)) - handoffRecords.value = await fetchHandoffRecords(String(route.params.id)) - return order.value - } finally { - loading.value = false - } - } - - async function handleCancel() { - if (!order.value) return false - cancelling.value = true - try { - await cancelOrder(order.value.id) - await loadOrder() - return true - } finally { - cancelling.value = false - } - } - - async function handlePay() { - if (!order.value) return null - startingPayment.value = true - try { - const payment = await startOrderPayment(order.value.id) - // 开始支付轮询,成功后重新加载订单 - paymentPolling.startPaymentPolling(payment, loadOrder) - return payment - } finally { - startingPayment.value = false - } - } - - async function handleSubmitHandoff() { - if (!order.value || !handoffContent.value.trim()) return false - handoffing.value = true - try { - await submitHandoff(order.value.id, handoffContent.value.trim()) - handoffContent.value = '' - await loadOrder() - return true - } finally { - handoffing.value = false - } - } - - async function handleConfirmReceive() { - if (!order.value) return false - confirming.value = true - try { - await confirmReceive(order.value.id) - await loadOrder() - return true - } finally { - confirming.value = false - } - } - - async function handleCreateDispute() { - if (!order.value) return false - disputing.value = true - try { - await createDispute(order.value.id, { - type: disputeType.value, - description: disputeDescription.value.trim(), - evidence_urls: linesToList(disputeEvidenceText.value), - }) - await loadOrder() - return true - } finally { - disputing.value = false - } - } - - async function handleUploadEvidence(file: File) { - uploadingEvidence.value = true - try { - const result = await uploadFile(file, 'evidence') - return result.url - } finally { - uploadingEvidence.value = false - } - } - - async function handleOpenChat() { - if (!order.value) return null - openingChat.value = true - try { - const chat = await fetchOrderChat(order.value.id) - return chat - } finally { - openingChat.value = false - } - } - - function getOrderStep(status: string) { - const stepMap: Record = { - pending_payment: 0, - pending_handoff: 1, - renting: 2, - overdue: 2, - pending_checkout_confirm: 3, - pending_checkout_accept: 3, - completed: 5, - cancelled: 0, - closed: 4, - } - return stepMap[status] ?? 0 - } - - function linesToList(value: string) { - return value - .split('\n') - .map(line => line.trim()) - .filter(Boolean) - } - - onMounted(loadOrder) - - return { - // States - loading, - cancelling, - startingPayment, - handoffing, - confirming, - disputing, - uploadingEvidence, - openingChat, - - // Data - order, - handoffRecords, - handoffContent, - - // Dispute form - disputeType, - disputeDescription, - disputeEvidenceText, - - // Computed - isOwner, - isRenter, - orderAmountLabel, - canOpenDispute, - isCheckoutDisputeStage, - - // Methods - loadOrder, - handleCancel, - handlePay, - handleSubmitHandoff, - handleConfirmReceive, - handleCreateDispute, - handleUploadEvidence, - handleOpenChat, - getOrderStep, - - // 从拆分的 composables 导出 - ...paymentPolling, - ...settlement, - } -} diff --git a/frontend/src/features/orders/composables/usePaymentPolling.ts b/frontend/src/features/orders/composables/usePaymentPolling.ts deleted file mode 100644 index dbf6123..0000000 --- a/frontend/src/features/orders/composables/usePaymentPolling.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ref, onBeforeUnmount } from 'vue' -import { queryOrderPayment, type PaymentOrder } from '../api/orders' - -/** - * 支付轮询 Composable - * 负责轮询查询支付状态,直到支付完成 - */ -export function usePaymentPolling() { - const activePayment = ref(null) - const checkingPayment = ref(false) - let paymentPollingTimer: number | undefined - - /** - * 开始轮询支付状态 - */ - function startPaymentPolling(payment: PaymentOrder, onSuccess?: () => void) { - activePayment.value = payment - stopPaymentPolling() - paymentPollingTimer = window.setInterval(() => checkPaymentStatus(onSuccess), 2000) - } - - /** - * 停止轮询 - */ - function stopPaymentPolling() { - if (paymentPollingTimer !== undefined) { - clearInterval(paymentPollingTimer) - paymentPollingTimer = undefined - } - activePayment.value = null - } - - /** - * 检查支付状态 - */ - async function checkPaymentStatus(onSuccess?: () => void) { - if (!activePayment.value || checkingPayment.value) return false - - checkingPayment.value = true - try { - const updated = await queryOrderPayment(activePayment.value.order_id) - if (updated.paid) { - stopPaymentPolling() - onSuccess?.() - return true - } - } catch { - // 忽略轮询错误,继续下次轮询 - } finally { - checkingPayment.value = false - } - return false - } - - // 组件卸载时停止轮询 - onBeforeUnmount(stopPaymentPolling) - - return { - activePayment, - checkingPayment, - startPaymentPolling, - stopPaymentPolling, - checkPaymentStatus, - } -} diff --git a/frontend/src/features/orders/composables/useSettlement.ts b/frontend/src/features/orders/composables/useSettlement.ts deleted file mode 100644 index 5802e87..0000000 --- a/frontend/src/features/orders/composables/useSettlement.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { ref, type Ref } from 'vue' -import { submitCheckout, acceptCheckout, counterCheckout, confirmCheckout } from '../api/orders' -import type { Order } from '../api/orders' - -export interface CheckoutForm { - content: string - consumableAmountYuan: number - coin_consumed_m: number - otherAmountYuan: number - evidenceText: string -} - -export interface CounterForm { - consumableAmountYuan: number - coin_consumed_m: number - otherAmountYuan: number - depositDeductAmountYuan: number - reason: string - evidenceText: string -} - -/** - * 订单结算 Composable - * 负责处理订单结算流程:提交结算、接受结算、反驳结算、确认结算 - */ -export function useSettlement(order: Ref) { - const returning = ref(false) - const acceptingCheckout = ref(false) - const countering = ref(false) - const rejectingCheckout = ref(false) - const completing = ref(false) - - const checkoutForm = ref({ - content: '', - consumableAmountYuan: 0, - coin_consumed_m: 0, - otherAmountYuan: 0, - evidenceText: '', - }) - - const counterForm = ref({ - consumableAmountYuan: 0, - coin_consumed_m: 0, - otherAmountYuan: 0, - depositDeductAmountYuan: 0, - reason: '', - evidenceText: '', - }) - - const resourceUsage = ref>({}) - - /** - * 提交结算单 - */ - async function handleSubmitCheckout(onSuccess?: () => void) { - if (!order.value) return false - returning.value = true - try { - await submitCheckout(order.value.id, { - content: checkoutForm.value.content.trim(), - consumableAmountYuan: checkoutForm.value.consumableAmountYuan, - coin_consumed_m: checkoutForm.value.coin_consumed_m, - otherAmountYuan: checkoutForm.value.otherAmountYuan, - evidence_urls: linesToList(checkoutForm.value.evidenceText), - }) - onSuccess?.() - return true - } finally { - returning.value = false - } - } - - /** - * 接受对方的结算 - */ - async function handleAcceptCheckout(onSuccess?: () => void) { - if (!order.value) return false - acceptingCheckout.value = true - try { - await acceptCheckout(order.value.id) - onSuccess?.() - return true - } finally { - acceptingCheckout.value = false - } - } - - /** - * 反驳对方的结算 - */ - async function handleCounterCheckout(onSuccess?: () => void) { - if (!order.value) return false - countering.value = true - try { - const reasonText = counterForm.value.reason.trim() - await counterCheckout(order.value.id, { - content: reasonText, - consumableAmountYuan: counterForm.value.consumableAmountYuan, - coin_consumed_m: counterForm.value.coin_consumed_m, - otherAmountYuan: counterForm.value.otherAmountYuan, - depositDeductAmountYuan: counterForm.value.depositDeductAmountYuan, - reason: reasonText, - evidence_urls: linesToList(counterForm.value.evidenceText), - }) - onSuccess?.() - return true - } finally { - countering.value = false - } - } - - /** - * 拒绝对方的结算(简化版反驳) - */ - async function handleRejectCheckout(reason: string, onSuccess?: () => void) { - if (!order.value) return false - rejectingCheckout.value = true - try { - const reasonText = reason.trim() - await counterCheckout(order.value.id, { - content: reasonText, - consumableAmountYuan: 0, - coin_consumed_m: 0, - otherAmountYuan: 0, - depositDeductAmountYuan: 0, - reason: reasonText, - evidence_urls: [], - }) - onSuccess?.() - return true - } finally { - rejectingCheckout.value = false - } - } - - /** - * 确认最终结算 - */ - async function handleConfirmCheckout(onSuccess?: () => void) { - if (!order.value) return false - completing.value = true - try { - await confirmCheckout(order.value.id) - onSuccess?.() - return true - } finally { - completing.value = false - } - } - - function linesToList(value: string) { - return value - .split('\n') - .map(line => line.trim()) - .filter(Boolean) - } - - return { - // Loading states - returning, - acceptingCheckout, - countering, - rejectingCheckout, - completing, - - // Forms - checkoutForm, - counterForm, - resourceUsage, - - // Methods - handleSubmitCheckout, - handleAcceptCheckout, - handleCounterCheckout, - handleRejectCheckout, - handleConfirmCheckout, - } -} diff --git a/frontend/src/features/orders/index.ts b/frontend/src/features/orders/index.ts index 76cb1ab..6e8d803 100644 --- a/frontend/src/features/orders/index.ts +++ b/frontend/src/features/orders/index.ts @@ -1,9 +1,6 @@ // Orders 模块统一导出 export * from './api/orders' -export * from './composables/useOrderDetail' export * from './composables/useOrderSnapshot' -export * from './composables/usePaymentPolling' -export * from './composables/useSettlement' export { default as OrderCheckoutSummary } from './components/OrderCheckoutSummary.vue' export { default as OrderHandoffTimeline } from './components/OrderHandoffTimeline.vue' export { default as OrderResourceUsageEditor } from './components/OrderResourceUsageEditor.vue' diff --git a/frontend/src/features/wallet/composables/useWallet.ts b/frontend/src/features/wallet/composables/useWallet.ts deleted file mode 100644 index feea445..0000000 --- a/frontend/src/features/wallet/composables/useWallet.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ref, computed } from 'vue' -import { fetchWalletBalance, fetchWalletLedger } from '../api/wallet' -import type { WalletAccount, WalletLedger } from '../api/wallet' - -export function useWallet() { - const balance = ref(null) - const ledgers = ref([]) - const loading = ref(false) - const error = ref(null) - - const availableBalance = computed(() => balance.value?.available_balance_cent ?? 0) - const frozenBalance = computed(() => balance.value?.frozen_balance_cent ?? 0) - const totalBalance = computed(() => availableBalance.value + frozenBalance.value) - - async function loadBalance() { - loading.value = true - error.value = null - try { - balance.value = await fetchWalletBalance() - } catch (err) { - error.value = err instanceof Error ? err.message : '加载余额失败' - throw err - } finally { - loading.value = false - } - } - - async function loadLedger(page = 1, pageSize = 20) { - loading.value = true - error.value = null - try { - const result = await fetchWalletLedger(page, pageSize) - ledgers.value = result.items - return result - } catch (err) { - error.value = err instanceof Error ? err.message : '加载账单失败' - throw err - } finally { - loading.value = false - } - } - - return { - balance, - ledgers, - loading, - error, - availableBalance, - frozenBalance, - totalBalance, - loadBalance, - loadLedger, - } -} diff --git a/frontend/src/features/wallet/index.ts b/frontend/src/features/wallet/index.ts index aae0e64..9bd276c 100644 --- a/frontend/src/features/wallet/index.ts +++ b/frontend/src/features/wallet/index.ts @@ -1,4 +1,3 @@ // Wallet 模块统一导出 export * from './api/wallet' export * from './api/withdrawal' -export * from './composables/useWallet' diff --git a/frontend/src/shared/components/business/ChatAttachmentImage.vue b/frontend/src/shared/components/business/ChatAttachmentImage.vue deleted file mode 100644 index 63de32b..0000000 --- a/frontend/src/shared/components/business/ChatAttachmentImage.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - - - diff --git a/frontend/src/shared/components/layout/MobileBottomNav.vue b/frontend/src/shared/components/layout/MobileBottomNav.vue deleted file mode 100644 index 15a9225..0000000 --- a/frontend/src/shared/components/layout/MobileBottomNav.vue +++ /dev/null @@ -1,137 +0,0 @@ - - - - - diff --git a/frontend/src/shared/composables/index.ts b/frontend/src/shared/composables/index.ts index 2026db7..eac912e 100644 --- a/frontend/src/shared/composables/index.ts +++ b/frontend/src/shared/composables/index.ts @@ -1,4 +1,3 @@ // 通用 Composables -export { useMoney } from './useMoney' export { useSmsCountdown } from './useSmsCountdown' export { usePricingCalculator } from './usePricingCalculator' diff --git a/frontend/src/shared/composables/useMoney.ts b/frontend/src/shared/composables/useMoney.ts deleted file mode 100644 index 87ac217..0000000 --- a/frontend/src/shared/composables/useMoney.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { formatMoneyWithSymbol } from '@/shared/utils/money' - -export function useMoney() { - return (value: number | undefined | null) => formatMoneyWithSymbol(value) -} diff --git a/frontend/src/shared/styles/admin.css b/frontend/src/shared/styles/admin.css deleted file mode 100644 index e9e4443..0000000 --- a/frontend/src/shared/styles/admin.css +++ /dev/null @@ -1,1269 +0,0 @@ -/* ========== Admin Shell Layout ========== */ -.admin-shell { - display: grid; - min-height: 100vh; - grid-template-columns: 220px 1fr; - background: #f0f2f5; - transition: grid-template-columns 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -.admin-shell--collapsed { - grid-template-columns: 64px 1fr; -} - -/* ========== Sidebar ========== */ -.admin-sidebar { - display: flex; - flex-direction: column; - min-width: 0; - background: linear-gradient(180deg, #1a1f36 0%, #101428 100%); - padding: 0; - color: #a3aed0; - overflow: hidden; - position: sticky; - top: 0; - height: 100vh; -} - -.admin-sidebar-header { - display: flex; - align-items: center; - justify-content: space-between; - height: 60px; - padding: 0 16px; - border-bottom: 1px solid rgba(255, 255, 255, 0.06); -} - -.admin-brand { - display: flex; - align-items: center; - gap: 12px; - color: #ffffff; - font-weight: 700; - text-decoration: none; -} - -.brand-mark { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - border-radius: 8px; - background: linear-gradient(135deg, #4f7cff, #6c5ce7); - color: #ffffff; - font-size: 14px; - font-weight: 900; - flex-shrink: 0; -} - -.brand-text { - font-size: 15px; - font-weight: 800; - letter-spacing: 0.5px; - transition: - opacity 0.2s, - width 0.3s; - overflow: hidden; - white-space: nowrap; -} - -.admin-shell--collapsed .brand-text { - opacity: 0; - width: 0; -} - -.collapse-btn { - cursor: pointer; - color: #a3aed0; - font-size: 18px; - padding: 6px; - border-radius: 6px; - transition: all 0.2s; - flex-shrink: 0; -} - -.collapse-btn:hover { - color: #ffffff; - background: rgba(255, 255, 255, 0.08); -} - -/* ========== Navigation ========== */ -.admin-nav { - display: flex; - flex-direction: column; - gap: 2px; - padding: 16px 8px; - overflow-y: auto; - overflow-x: hidden; - scrollbar-width: thin; - scrollbar-color: rgba(255, 255, 255, 0.1) transparent; -} - -.admin-nav::-webkit-scrollbar { - width: 4px; -} - -.admin-nav::-webkit-scrollbar-track { - background: transparent; -} - -.admin-nav::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.1); - border-radius: 4px; -} - -.admin-nav::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.2); -} - -.admin-nav-link { - display: flex; - align-items: center; - gap: 10px; - height: 36px; - border-radius: 8px; - padding: 0 12px; - color: #a3aed0; - text-decoration: none; - font-size: 13px; - font-weight: 500; - transition: all 0.2s; - position: relative; -} - -.admin-nav-link:hover { - color: #ffffff; - background: rgba(255, 255, 255, 0.06); -} - -.admin-nav-link.router-link-active { - color: #ffffff; - background: linear-gradient(135deg, rgba(79, 124, 255, 0.2), rgba(108, 92, 231, 0.15)); - font-weight: 600; -} - -.admin-nav-link.router-link-active::before { - content: ''; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 3px; - height: 18px; - border-radius: 0 3px 3px 0; - background: linear-gradient(180deg, #4f7cff, #6c5ce7); -} - -.admin-nav-link .el-icon { - font-size: 15px; - flex-shrink: 0; -} - -.admin-shell--collapsed .admin-nav-link { - justify-content: center; - padding: 0; -} - -.admin-shell--collapsed .admin-nav-link::before { - left: 0; -} - -.nav-text { - transition: - opacity 0.2s, - width 0.3s; - overflow: hidden; - white-space: nowrap; -} - -.admin-shell--collapsed .nav-text { - opacity: 0; - width: 0; -} - -/* ========== Workspace ========== */ -.admin-workspace { - min-width: 0; - overflow: hidden; - display: flex; - flex-direction: column; -} - -.admin-topbar { - display: flex; - align-items: center; - justify-content: space-between; - min-height: 72px; - background: #ffffff; - padding: 0 32px; - border-bottom: 1px solid #e8ecf1; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); -} - -.admin-topbar > div:first-child { - display: flex; - align-items: baseline; - gap: 12px; -} - -.admin-topbar span { - color: #8f9bba; - font-size: 13px; - font-weight: 500; -} - -.admin-topbar strong { - color: #1b2559; - font-size: 15px; - font-weight: 700; -} - -.admin-topbar .el-button { - border-radius: 8px; - font-weight: 600; -} - -.admin-main { - flex: 1; - width: 100%; - min-width: 0; - padding: 28px clamp(20px, 3vw, 40px); - overflow-y: auto; - overflow-x: hidden; -} - -.admin-main .page { - max-width: none; -} - -/* ========== Login Page ========== */ -.admin-login-shell { - display: grid; - min-height: 100vh; - place-items: center; - background: linear-gradient(135deg, #0f0c29 0%, #1a1a3e 50%, #24243e 100%); - padding: 24px; - position: relative; - overflow: hidden; -} - -.admin-login-shell::before { - content: ''; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: radial-gradient(circle, rgba(79, 124, 255, 0.1) 0%, transparent 50%); - animation: loginPulse 15s ease-in-out infinite; -} - -@keyframes loginPulse { - 0%, - 100% { - transform: translate(0, 0); - } - 50% { - transform: translate(5%, 5%); - } -} - -.admin-login-panel { - width: min(420px, 100%); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 16px; - background: rgba(26, 31, 54, 0.85); - backdrop-filter: blur(20px); - padding: 36px; - box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); - position: relative; - z-index: 1; -} - -.admin-login-brand { - display: inline-flex; - align-items: center; - gap: 12px; - color: #ffffff; - font-weight: 700; -} - -.admin-login-header { - margin: 32px 0 28px; -} - -.admin-login-header h1 { - font-size: 28px; - color: #ffffff; - font-weight: 800; -} - -.admin-login-header p:last-child { - margin: 12px 0 0; - color: #a3aed0; - font-size: 14px; -} - -.admin-captcha-row { - display: grid; - grid-template-columns: 1fr 132px; - gap: 12px; - width: 100%; -} - -.captcha-image-button { - display: flex; - align-items: center; - justify-content: center; - height: 44px; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 10px; - background: rgba(255, 255, 255, 0.05); - padding: 0; - cursor: pointer; - overflow: hidden; - transition: all 0.2s; -} - -.captcha-image-button:hover { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.15); -} - -.captcha-image-button:disabled { - cursor: wait; - opacity: 0.6; -} - -.captcha-image-button img { - display: block; - width: 132px; - height: 44px; -} - -/* ========== Page Components ========== */ -.page-header { - max-width: 720px; -} - -.eyebrow { - margin: 0 0 8px; - color: #4f7cff; - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 1px; -} - -h1 { - margin: 0; - color: #1b2559; - font-size: 28px; - font-weight: 800; - line-height: 1.2; -} - -.page-header p:last-child { - margin: 12px 0 0; - color: #8f9bba; - line-height: 1.7; - font-size: 14px; -} - -.page-header-row { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 20px; - max-width: 100%; - margin-bottom: 28px; -} - -.toolbar-actions { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 10px; - justify-content: flex-end; -} - -/* ========== Metric Cards ========== */ -.metric-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 16px; - margin-bottom: 28px; -} - -.metric-card { - border: none; - border-radius: 14px; - background: #ffffff; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - transition: - transform 0.2s, - box-shadow 0.2s; -} - -.metric-card:hover { - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); -} - -.metric-card span { - display: block; - color: #8f9bba; - font-size: 13px; - font-weight: 500; -} - -.metric-card strong { - display: block; - margin-top: 10px; - color: #1b2559; - font-size: 24px; - font-weight: 800; -} - -.dashboard-metrics { - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); -} - -.admin-listings-page { - display: flex; - flex-direction: column; - min-height: calc(100vh - 128px); -} - -.listing-control-panel { - display: grid; - grid-template-columns: minmax(340px, 0.65fr) minmax(520px, 1fr) auto; - align-items: end; - gap: 14px; - margin-bottom: 14px; - border-radius: 14px; - background: #ffffff; - padding: 14px 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.listing-metric-strip { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.listing-metric-strip span { - display: inline-flex; - align-items: baseline; - gap: 6px; - min-height: 34px; - border-radius: 8px; - background: #f8f9fe; - padding: 8px 10px; - color: #8f9bba; - font-size: 12px; - font-weight: 600; -} - -.listing-metric-strip strong { - color: #1b2559; - font-size: 16px; - font-weight: 800; -} - -.listing-filter-bar { - display: grid; - grid-template-columns: minmax(180px, 1fr) minmax(160px, 0.8fr) minmax(170px, 0.8fr); - gap: 12px; -} - -.listing-filter-bar .el-form-item { - margin-bottom: 0; -} - -.listing-filter-bar .el-form-item__label { - color: #8f9bba; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.2px; -} - -.listing-action-strip { - display: flex; - align-items: center; - justify-content: flex-end; - gap: 8px; - padding-bottom: 1px; - white-space: nowrap; -} - -/* ========== Dashboard Panels ========== */ -.dashboard-panels { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 20px; - margin-bottom: 28px; -} - -.dashboard-panel { - max-width: none; - border: none; - border-radius: 14px; - background: #ffffff; - padding: 24px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.dashboard-panel h2 { - margin: 0 0 18px; - color: #1b2559; - font-size: 17px; - font-weight: 700; -} - -.pending-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - min-height: 48px; - border-top: 1px solid #f0f2f5; - color: #8f9bba; - text-decoration: none; - padding: 0 4px; - transition: background 0.2s; - border-radius: 8px; -} - -.pending-row:first-of-type { - border-top: 0; -} - -.pending-row:hover { - background: #f8f9fe; -} - -.pending-row strong { - color: #1b2559; - font-weight: 700; -} - -/* ========== Table Panel ========== */ -.table-panel { - margin-top: 0; - width: 100%; - overflow-x: hidden; - background: #ffffff; - border-radius: 14px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - padding: 4px; -} - -.table-panel .el-table { - --el-table-border-color: #f0f2f5; - --el-table-header-bg-color: #f8f9fe; - --el-table-header-text-color: #8f9bba; - --el-table-text-color: #1b2559; - font-size: 14px; -} - -.table-panel .el-table th { - font-weight: 600; - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.table-panel .el-table__inner-wrapper { - min-width: 760px; -} - -.admin-listings-table.el-table .el-table__inner-wrapper { - min-width: 0; -} - -.admin-listings-table.el-table .el-scrollbar__wrap { - overflow-x: hidden !important; -} - -.admin-listings-table.el-table .el-scrollbar__bar.is-horizontal { - display: none; -} - -.admin-listings-table.el-table { - font-size: 12px; -} - -.admin-listings-table.el-table th { - font-size: 11px; - letter-spacing: 0; - text-transform: none; -} - -.admin-listings-table.el-table .cell { - padding: 0 5px; - line-height: 1.35; - white-space: normal; - word-break: break-word; -} - -.admin-listings-table.el-table th .cell { - white-space: nowrap; - word-break: keep-all; - overflow-wrap: normal; - overflow: visible; -} - -.admin-listings-table .listing-code { - display: inline-block; - font-size: 11px; - line-height: 1; - white-space: nowrap; - word-break: keep-all; -} - -.admin-listings-table.el-table .el-table__cell { - padding: 8px 0; -} - -.admin-listings-table.el-table .el-button--small { - padding: 4px 8px; -} - -.table-subtext { - display: block; - margin-top: 4px; - color: #8f9bba; - font-size: 12px; -} - -.table-pagination { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - border: none; - background: #ffffff; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.table-pagination { - justify-content: flex-end; - margin-top: 12px; - border-radius: 12px; - padding: 12px 16px; - box-sizing: border-box; - width: 100%; - color: #66728f; - font-size: 12px; - font-weight: 600; - white-space: nowrap; -} - -.pagination-controls { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - white-space: nowrap; -} - -.pagination-summary, -.pagination-page, -.pagination-size-label { - flex-shrink: 0; - white-space: nowrap; -} - -.page-size-select { - width: 86px; - flex: 0 0 86px; -} - -.page-size-select .el-select__wrapper { - min-height: 30px; - padding: 4px 8px; -} - -.page-size-select .el-select__selected-item { - min-width: max-content; -} - -.admin-listings-page .table-pagination { - margin-top: auto; -} - -.pagination-controls .el-button--small { - min-width: 44px; - padding: 5px 8px; -} - -.pagination-controls .el-button + .el-button { - margin-left: 0; -} - -@media (max-width: 980px) { - .listing-control-panel { - grid-template-columns: 1fr; - align-items: stretch; - } - - .listing-filter-bar { - grid-template-columns: 1fr; - } - - .listing-action-strip { - justify-content: flex-start; - flex-wrap: wrap; - } -} - -/* ========== Filter Panel ========== */ -.filter-panel { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 16px; - margin-bottom: 24px; - border: none; - border-radius: 14px; - background: #ffffff; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.filter-panel .el-form-item { - margin-bottom: 0; -} - -.filter-panel .el-form-item__label { - color: #8f9bba; - font-size: 12px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* ========== Order Panel ========== */ -.order-panel { - max-width: min(100%, 560px); - border: none; - border-radius: 14px; - background: #ffffff; - padding: 24px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.order-panel p { - margin: 0 0 14px; - color: #8f9bba; -} - -.order-panel h2 { - margin: 0 0 18px; - color: #1b2559; - font-size: 18px; - font-weight: 700; -} - -/* ========== Status Panel ========== */ -.status-panel { - max-width: 520px; - border: none; - border-radius: 14px; - background: #ffffff; - padding: 24px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.status-panel span { - color: #8f9bba; - font-size: 13px; -} - -.status-panel strong { - display: block; - margin-top: 8px; - color: #4f7cff; - font-size: 24px; - font-weight: 800; -} - -.status-panel p { - margin: 10px 0 0; - color: #8f9bba; -} - -/* ========== Forms ========== */ -.listing-form, -.login-form { - max-width: 860px; - border: none; - border-radius: 14px; - background: #ffffff; - padding: 24px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.form-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - column-gap: 20px; -} - -.full-control { - width: 100%; -} - -/* ========== Dialogs ========== */ -.dialog-body { - display: grid; - gap: 14px; -} - -.dialog-body p { - margin: 0; - color: #8f9bba; - line-height: 1.6; -} - -/* ========== Pagination ========== */ -.pagination-wrap { - display: flex; - justify-content: center; - margin-top: 24px; - padding: 16px 0; -} - -/* ========== Publish Config Panel ========== */ -.publish-config-panel { - display: grid; - gap: 20px; - margin-bottom: 24px; - padding: 24px; - border: none; - border-radius: 14px; - background: #ffffff; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.publish-config-main { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; -} - -.panel-actions { - display: flex; - align-items: center; - gap: 10px; -} - -.publish-config-main h2 { - margin: 4px 0 8px; - color: #1b2559; - font-size: 20px; - font-weight: 700; -} - -.publish-config-main span { - color: #8f9bba; - font-size: 14px; -} - -.publish-stat-grid { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 12px; -} - -.home-stat-grid .publish-stat strong { - font-size: 18px; -} - -.publish-stat { - display: grid; - gap: 6px; - padding: 16px; - border-radius: 12px; - background: #f8f9fe; -} - -.publish-stat strong { - color: #4f7cff; - font-size: 22px; - line-height: 1; - font-weight: 800; -} - -.publish-stat span, -.config-value { - color: #8f9bba; - font-size: 13px; -} - -/* ========== Timeline ========== */ -.timeline-item { - border-top: 1px solid #f0f2f5; - padding: 16px 0; -} - -.timeline-item:first-of-type { - border-top: 0; -} - -.timeline-item strong { - display: block; - color: #1b2559; -} - -.timeline-item span { - color: #8f9bba; - font-size: 13px; -} - -.panel-action { - margin-top: 16px; -} - -/* ========== Upload Components ========== */ -.upload-line input { - width: 100%; -} - -.upload-stack { - display: grid; - gap: 12px; - width: 100%; -} - -.upload-line { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 12px; - align-items: center; - width: 100%; -} - -.evidence-list { - display: grid; - gap: 10px; -} - -.evidence-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 12px; - align-items: center; -} - -.evidence-row span { - overflow-wrap: anywhere; - color: #8f9bba; -} - -/* ========== Code Panel ========== */ -.code-panel { - max-width: none; - border: none; - border-radius: 14px; - background: #ffffff; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - overflow: hidden; -} - -.code-panel pre { - overflow-x: auto; - margin: 0; - background: #1a1f36; - color: #e2e8f0; - padding: 20px; - line-height: 1.7; - font-size: 13px; -} - -/* ========== Notification List ========== */ -.notification-list { - display: grid; - gap: 14px; -} - -.notification-item { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; - border: none; - border-radius: 14px; - background: #ffffff; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - transition: - transform 0.2s, - box-shadow 0.2s; -} - -.notification-item:hover { - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); -} - -.notification-item.unread { - border-left: 3px solid #4f7cff; -} - -.notification-item span { - color: #4f7cff; - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.notification-item h2 { - margin: 8px 0; - color: #1b2559; - font-size: 16px; - font-weight: 700; -} - -.notification-item p { - margin: 0 0 10px; - color: #8f9bba; - font-size: 14px; -} - -.notification-item small { - color: #a3aed0; - font-size: 12px; -} - -.notification-actions { - display: flex; - flex-wrap: wrap; - gap: 8px; - justify-content: flex-end; -} - -/* ========== Listing Grid ========== */ -.listing-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; -} - -.listing-card { - display: block; - min-height: 172px; - border: none; - border-radius: 14px; - background: #ffffff; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - text-decoration: none; - transition: - transform 0.2s, - box-shadow 0.2s; -} - -.listing-card:hover { - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); -} - -.listing-card span { - color: #4f7cff; - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.listing-card strong { - display: block; - margin-top: 12px; - color: #1b2559; - font-size: 18px; - font-weight: 700; -} - -.listing-card p { - color: #8f9bba; - line-height: 1.6; - font-size: 14px; -} - -.listing-price { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-top: 20px; -} - -.listing-price b { - color: #1b2559; - font-weight: 700; -} - -.listing-price em { - color: #8f9bba; - font-style: normal; - font-size: 13px; -} - -/* ========== Detail Grid ========== */ -.detail-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 16px; -} - -/* ========== Responsive ========== */ -@media (max-width: 1180px) { - .admin-shell { - grid-template-columns: 200px 1fr; - } - - .admin-shell--collapsed { - grid-template-columns: 64px 1fr; - } - - .admin-sidebar-header { - padding: 0 14px; - } - - .admin-nav { - padding: 10px 8px; - } - - .admin-nav-link { - padding: 0 10px; - } -} - -@media (max-width: 900px) { - .admin-shell { - grid-template-columns: 1fr; - } - - .admin-shell--collapsed { - grid-template-columns: 1fr; - } - - .admin-sidebar { - position: sticky; - top: 0; - z-index: 10; - flex-direction: row; - align-items: center; - height: auto; - min-height: 60px; - padding: 0 16px; - } - - .admin-sidebar-header { - min-height: auto; - padding: 0; - border-bottom: 0; - } - - .admin-nav { - grid-auto-flow: column; - grid-auto-columns: max-content; - gap: 8px; - padding: 0; - margin-left: auto; - overflow-x: auto; - } - - .admin-nav-link { - min-height: 40px; - white-space: nowrap; - font-size: 13px; - } - - .admin-nav-link::before { - display: none; - } - - .admin-shell--collapsed .admin-nav-link { - justify-content: flex-start; - padding: 0 12px; - } - - .admin-shell--collapsed .brand-text, - .admin-shell--collapsed .nav-text { - opacity: 1; - width: auto; - } - - .admin-topbar { - min-height: 64px; - padding: 0 20px; - } - - .admin-main { - padding: 20px 16px; - } - - .metric-grid, - .dashboard-metrics, - .dashboard-panels { - grid-template-columns: 1fr; - } - - .publish-stat-grid { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} - -@media (max-width: 520px) { - .admin-topbar { - align-items: flex-start; - flex-direction: column; - justify-content: center; - gap: 12px; - padding: 16px; - } - - .admin-topbar .el-button { - width: 100%; - } - - .page-header-row { - flex-direction: column; - gap: 16px; - } - - .toolbar-actions { - width: 100%; - justify-content: flex-start; - } - - .admin-captcha-row { - grid-template-columns: 1fr; - } - - .captcha-image-button, - .captcha-image-button img { - width: 100%; - } - - .publish-stat-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} - -@media (prefers-reduced-motion: reduce) { - .admin-shell, - .admin-nav-link, - .metric-card, - .notification-item, - .listing-card { - transition: none; - } -} diff --git a/frontend/src/shared/styles/base.css b/frontend/src/shared/styles/base.css deleted file mode 100644 index 1bbe58e..0000000 --- a/frontend/src/shared/styles/base.css +++ /dev/null @@ -1,49 +0,0 @@ -:root { - color: #1f2933; - background: #f6f8fb; - font-family: - Inter, - 'PingFang SC', - 'Microsoft YaHei', - system-ui, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - sans-serif; - font-synthesis: none; - text-rendering: optimizeLegibility; -} - -* { - box-sizing: border-box; -} - -html, -body { - margin: 0; - min-width: 320px; - min-height: 100vh; - max-width: 100%; - overflow-x: clip; -} - -#app { - max-width: 100%; - overflow-x: clip; -} - -a { - color: inherit; - text-decoration: none; -} - -/* ── Vant Toast 保护规则 ────────────────────────────────── - .van-popup 声明 background: var(--van-popup-background) = #fff (白色) - .van-toast 声明 background: var(--van-toast-background) = rgba(0,0,0,.7) (黑色) - 两个类同优先级,但 .van-popup 在 vant/lib/index.css 中后声明 → 覆盖黑色背景 - 导致 Toast 白底白字看不见。用双类选择器提升优先级强制使用黑色背景。 - ────────────────────────────────────────────────────────── */ -.van-popup.van-toast { - background: var(--van-toast-background) !important; - color: var(--van-toast-text-color) !important; -} diff --git a/frontend/src/shared/styles/home-filters.css b/frontend/src/shared/styles/home-filters.css deleted file mode 100644 index d1ba2f1..0000000 --- a/frontend/src/shared/styles/home-filters.css +++ /dev/null @@ -1,230 +0,0 @@ -/* 筛选器相关样式 */ -.horizontal-filter-card { - padding: 24px; - border: 1px solid #eef1f5; - border-radius: 16px; - background: #ffffff; - box-shadow: 0 4px 12px rgba(23, 35, 61, 0.03); -} - -.filter-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; -} - -.filter-title { - display: flex; - align-items: center; - gap: 12px; -} - -.filter-title strong { - font-size: 18px; - font-weight: 800; - color: #17233d; -} - -.filter-title span { - padding: 4px 10px; - border-radius: 6px; - background: #f1f5f9; - font-size: 13px; - font-weight: 700; - color: #64748b; -} - -.filter-chip-row { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.filter-chip { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 14px; - border: 2px solid #e2e8f0; - border-radius: 8px; - background: #ffffff; - font-size: 13px; - font-weight: 600; - color: #334155; - cursor: pointer; - transition: all 0.2s; -} - -.filter-chip:hover { - border-color: #ff6a00; - background: #fff7ed; -} - -.filter-chip.active { - border-color: #ff6a00; - background: #fff7ed; - color: #ff6a00; -} - -.filter-chip.wide { - min-width: 140px; -} - -/* 筛选器弹窗样式 */ -:global(.home-filter-popover) { - padding: 8px !important; - border-radius: 12px !important; -} - -.filter-menu, -.range-menu { - display: flex; - flex-direction: column; - gap: 4px; -} - -.filter-menu button, -.range-menu button { - padding: 10px 14px; - border: none; - border-radius: 8px; - background: transparent; - text-align: left; - font-size: 13px; - font-weight: 600; - color: #334155; - cursor: pointer; - transition: all 0.15s; -} - -.filter-menu button:hover, -.range-menu button:hover { - background: #f1f5f9; -} - -.filter-menu button.active, -.range-menu button.active { - background: #fff7ed; - color: #ff6a00; - font-weight: 700; -} - -.range-manual { - display: flex; - align-items: center; - gap: 8px; - padding: 10px 14px; - border-top: 1px solid #e2e8f0; - margin-top: 4px; -} - -.range-manual :deep(.el-input-number) { - flex: 1; -} - -.range-manual span { - color: #94a3b8; - font-weight: 700; -} - -/* 皮肤筛选器 */ -.skin-filter-menu { - display: flex; - flex-direction: column; - gap: 16px; - max-height: 400px; - overflow-y: auto; -} - -.skin-reset { - padding: 10px 14px; - border: none; - border-radius: 8px; - background: transparent; - text-align: left; - font-size: 13px; - font-weight: 600; - color: #334155; - cursor: pointer; - transition: all 0.15s; -} - -.skin-reset:hover { - background: #f1f5f9; -} - -.skin-reset.active { - background: #fff7ed; - color: #ff6a00; - font-weight: 700; -} - -.skin-filter-group { - display: flex; - flex-direction: column; - gap: 8px; -} - -.skin-filter-title { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 4px; -} - -.skin-filter-title strong { - font-size: 13px; - font-weight: 700; - color: #17233d; -} - -.skin-filter-title button { - padding: 4px 8px; - border: none; - border-radius: 6px; - background: transparent; - font-size: 11px; - font-weight: 600; - color: #64748b; - cursor: pointer; - transition: all 0.15s; -} - -.skin-filter-title button:hover { - background: #f1f5f9; -} - -.skin-filter-title button.active { - background: #fff7ed; - color: #ff6a00; -} - -.skin-filter-options { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 4px; -} - -.skin-filter-options button { - padding: 8px 12px; - border: none; - border-radius: 6px; - background: transparent; - text-align: left; - font-size: 12px; - font-weight: 600; - color: #334155; - cursor: pointer; - transition: all 0.15s; -} - -.skin-filter-options button:hover { - background: #f1f5f9; -} - -.skin-filter-options button.active { - background: #fff7ed; - color: #ff6a00; - font-weight: 700; -} diff --git a/frontend/src/shared/styles/pc.css b/frontend/src/shared/styles/pc.css deleted file mode 100644 index ac75685..0000000 --- a/frontend/src/shared/styles/pc.css +++ /dev/null @@ -1,1133 +0,0 @@ -.app-shell { - display: grid; - min-height: 100vh; - grid-template-columns: 248px 1fr; -} - -.sidebar { - border-right: 1px solid #e4e7ed; - background: #ffffff; - padding: 20px 16px; -} - -.brand { - display: flex; - align-items: center; - gap: 10px; - min-height: 44px; - font-weight: 700; -} - -.brand-mark { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - border-radius: 8px; - background: #0f766e; - color: #ffffff; -} - -.nav { - display: grid; - gap: 6px; - margin-top: 24px; -} - -.nav-link { - display: flex; - align-items: center; - gap: 10px; - min-height: 40px; - border-radius: 8px; - padding: 0 12px; - color: #52616f; -} - -.nav-link.router-link-active { - background: #e8f5f2; - color: #0f766e; - font-weight: 600; -} - -.main { - min-width: 0; - padding: 32px; -} - -.page { - width: 100%; - max-width: 1720px; -} - -.page-header { - max-width: 720px; -} - -.eyebrow { - margin: 0 0 8px; - color: #0f766e; - font-size: 13px; - font-weight: 700; - text-transform: uppercase; -} - -h1 { - margin: 0; - font-size: 32px; - line-height: 1.2; -} - -.page-header p:last-child { - margin: 12px 0 0; - color: #52616f; - line-height: 1.7; -} - -.metric-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 16px; - margin-top: 28px; -} - -.metric-card { - border: 1px solid #e4e7ed; - border-radius: 8px; - background: #ffffff; - padding: 18px; -} - -.metric-card span { - display: block; - color: #6b7785; - font-size: 13px; -} - -.metric-card strong { - display: block; - margin-top: 8px; - font-size: 20px; -} - -.dashboard-metrics { - grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); -} - -.dashboard-panels { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 16px; -} - -.dashboard-panel { - max-width: none; -} - -.pending-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - min-height: 40px; - border-top: 1px solid #e4e7ed; - color: #52616f; -} - -.pending-row:first-of-type { - border-top: 0; -} - -.pending-row strong { - color: #111827; -} - -.login-form { - max-width: 420px; - margin-top: 28px; - border: 1px solid #e4e7ed; - border-radius: 8px; - background: #ffffff; - padding: 20px; -} - -.code-row { - display: grid; - grid-template-columns: 1fr 96px; - gap: 10px; - width: 100%; -} - -.notice { - max-width: 520px; - margin-top: 24px; -} - -.status-panel { - max-width: 520px; - margin-top: 24px; - border: 1px solid #e4e7ed; - border-radius: 8px; - background: #ffffff; - padding: 18px; -} - -.status-panel span { - color: #6b7785; - font-size: 13px; -} - -.status-panel strong { - display: block; - margin-top: 6px; - color: #0f766e; - font-size: 22px; -} - -.status-panel p { - margin: 8px 0 0; - color: #52616f; -} - -.listing-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; - margin-top: 28px; -} - -.listing-card { - display: block; - min-height: 172px; - border: 1px solid #e4e7ed; - border-radius: 8px; - background: #ffffff; - padding: 18px; -} - -.listing-card span { - color: #0f766e; - font-size: 13px; - font-weight: 700; -} - -.listing-card strong { - display: block; - margin-top: 10px; - font-size: 20px; -} - -.listing-card p { - color: #52616f; - line-height: 1.6; -} - -.listing-price { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-top: 18px; -} - -.listing-price b { - color: #111827; -} - -.listing-price em { - color: #6b7785; - font-style: normal; -} - -.detail-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 16px; - margin-top: 28px; -} - -.page-header-row { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 20px; - max-width: 100%; -} - -.table-panel, -.listing-form { - margin-top: 28px; -} - -.table-panel { - width: 100%; - overflow-x: auto; -} - -.table-panel .el-table__inner-wrapper { - min-width: 760px; -} - -.listing-form { - max-width: 860px; - border: 1px solid #e4e7ed; - border-radius: 8px; - background: #ffffff; - padding: 20px; -} - -.form-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - column-gap: 16px; -} - -.order-panel { - max-width: min(100%, 560px); - margin-top: 28px; - border: 1px solid #e4e7ed; - border-radius: 8px; - background: #ffffff; - padding: 20px; -} - -.order-panel p { - margin: 0 0 12px; - color: #52616f; -} - -.order-panel h2 { - margin: 0 0 14px; - font-size: 18px; -} - -.timeline-item { - border-top: 1px solid #e4e7ed; - padding: 14px 0; -} - -.timeline-item:first-of-type { - border-top: 0; -} - -.timeline-item strong { - display: block; -} - -.timeline-item span { - color: #6b7785; - font-size: 13px; -} - -.panel-action { - margin-top: 14px; -} - -.upload-line input { - width: 100%; -} - -.upload-stack { - display: grid; - gap: 10px; - width: 100%; -} - -.upload-line { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 10px; - align-items: center; - width: 100%; -} - -.evidence-list { - display: grid; - gap: 8px; -} - -.evidence-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 10px; - align-items: center; -} - -.evidence-row span { - overflow-wrap: anywhere; - color: #52616f; -} - -.full-control { - width: 100%; -} - -.dialog-body { - display: grid; - gap: 10px; -} - -.dialog-body p { - margin: 0; - color: #52616f; - line-height: 1.6; -} - -.toolbar-actions { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 8px; - justify-content: flex-end; -} - -.filter-panel { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 12px 16px; - margin-top: 24px; - border: 1px solid #e4e7ed; - border-radius: 8px; - background: #ffffff; - padding: 16px; -} - -.filter-panel .el-form-item { - margin-bottom: 0; -} - -.table-subtext { - display: block; - margin-top: 4px; - color: #6b7785; - font-size: 12px; -} - -.code-panel { - max-width: none; -} - -.code-panel pre { - overflow-x: auto; - margin: 0; - border-radius: 6px; - background: #111827; - color: #dbe4ee; - padding: 14px; - line-height: 1.6; -} - -.notification-list { - display: grid; - gap: 12px; - margin-top: 28px; -} - -.notification-item { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; - border: 1px solid #e4e7ed; - border-radius: 8px; - background: #ffffff; - padding: 16px; -} - -.notification-item.unread { - border-color: #0f766e; -} - -.notification-item span { - color: #0f766e; - font-size: 13px; - font-weight: 700; -} - -.notification-item h2 { - margin: 6px 0; - font-size: 18px; -} - -.notification-item p { - margin: 0 0 8px; - color: #52616f; -} - -.notification-item small { - color: #6b7785; -} - -.notification-actions { - display: flex; - flex-wrap: wrap; - gap: 8px; - justify-content: flex-end; -} - -/* PC public marketplace */ -.pc-app-shell { - min-height: 100vh; - background: #f5f7fa; -} - -.pc-topbar { - position: sticky; - top: 0; - z-index: 20; - display: grid; - grid-template-columns: auto auto minmax(260px, 420px) auto; - align-items: center; - gap: 18px; - min-height: 66px; - border-bottom: 1px solid #e8edf3; - background: rgba(255, 255, 255, 0.94); - padding: 0 clamp(20px, 4vw, 56px); - backdrop-filter: blur(12px); -} - -.pc-brand, -.pc-nav, -.pc-user-actions, -.pc-search, -.pc-login-link, -.pc-post-link, -.pc-icon-link { - display: flex; - align-items: center; -} - -.pc-brand { - gap: 10px; - font-weight: 800; -} - -.pc-brand-mark { - display: inline-flex; - align-items: center; - justify-content: center; - width: 38px; - height: 38px; - border-radius: 12px; - background: #fff3e8; - color: #ff6a00; - font-size: 13px; - font-weight: 900; -} - -.pc-brand-text { - color: #111827; - font-size: 17px; -} - -.pc-nav { - gap: 6px; -} - -.pc-nav-link { - display: inline-flex; - align-items: center; - gap: 7px; - min-height: 40px; - border-radius: 999px; - padding: 0 14px; - color: #4b5563; - font-size: 14px; - font-weight: 600; -} - -.pc-nav-link.router-link-active, -.pc-nav-link:hover { - background: #fff3e8; - color: #ff6a00; -} - -.pc-search { - gap: 10px; - height: 40px; - border: 1px solid #edf1f6; - border-radius: 999px; - background: #f7f9fc; - padding: 0 16px; - color: #7b8798; -} - -.pc-search input { - width: 100%; - border: 0; - outline: 0; - background: transparent; - color: #111827; - font-size: 14px; -} - -.pc-user-actions { - justify-content: flex-end; - gap: 10px; -} - -.pc-icon-link, -.pc-login-link, -.pc-post-link { - justify-content: center; - min-height: 40px; - border-radius: 999px; - font-weight: 700; -} - -.pc-icon-link { - width: 40px; - border: 1px solid #e8edf3; - background: #ffffff; - color: #4b5563; -} - -.pc-login-link { - gap: 6px; - border: 1px solid #e8edf3; - background: #ffffff; - padding: 0 14px; - color: #111827; -} - -.pc-post-link { - gap: 6px; - background: #ff6a00; - padding: 0 16px; - color: #ffffff; - box-shadow: 0 10px 22px rgba(255, 106, 0, 0.18); -} - -.pc-post-link.large { - min-height: 44px; - padding: 0 22px; -} - -.pc-main { - width: min(1720px, calc(100% - 40px)); - margin: 0 auto; - padding: 18px 0 56px; -} - -.anti-fraud-strip { - display: flex; - align-items: center; - gap: 10px; - min-height: 44px; - border: 1px solid #fde68a; - border-radius: 14px; - background: linear-gradient(90deg, #fffbeb, #fef3c7); - padding: 10px 16px; - color: #92400e; - font-size: 14px; - font-weight: 600; -} - -.anti-fraud-strip.compact { - margin-bottom: 22px; -} - -.pc-hero { - display: grid; - grid-template-columns: minmax(0, 1fr) 340px; - gap: 24px; - margin-top: 18px; -} - -.pc-hero-content, -.pc-action-card, -.advanced-filter-card, -.resource-card, -.feature-card, -.pc-stat-card { - border: 1px solid rgba(229, 231, 235, 0.9); - background: rgba(255, 255, 255, 0.92); - box-shadow: 0 18px 44px rgba(17, 24, 39, 0.08); -} - -.pc-hero-content { - min-height: 360px; - border-radius: 28px; - background: - linear-gradient(135deg, rgba(17, 24, 39, 0.82), rgba(120, 53, 15, 0.72)), - radial-gradient(circle at 82% 20%, rgba(251, 191, 36, 0.9), transparent 24%), #111827; - padding: 48px; - color: #ffffff; - overflow: hidden; -} - -.pc-hero-content .eyebrow { - color: #fbbf24; -} - -.pc-hero h1 { - max-width: 620px; - font-size: clamp(40px, 5vw, 64px); - letter-spacing: -2px; -} - -.pc-hero-desc { - max-width: 560px; - margin: 18px 0 0; - color: rgba(255, 255, 255, 0.78); - font-size: 18px; - line-height: 1.8; -} - -.pc-hero-actions, -.hero-tags, -.sort-toolbar, -.resource-tags, -.resource-title-line { - display: flex; - align-items: center; -} - -.pc-hero-actions { - gap: 14px; - margin-top: 32px; -} - -.primary-cta, -.secondary-cta { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 46px; - border-radius: 999px; - padding: 0 22px; - font-weight: 800; -} - -.primary-cta { - gap: 8px; - background: #fbbf24; - color: #111827; -} - -.secondary-cta { - border: 1px solid rgba(255, 255, 255, 0.28); - color: #ffffff; -} - -.hero-tags { - flex-wrap: wrap; - gap: 10px; - margin-top: 34px; -} - -.hero-tags span { - border: 1px solid rgba(255, 255, 255, 0.22); - border-radius: 999px; - background: rgba(255, 255, 255, 0.1); - padding: 8px 12px; - color: #fde68a; - font-size: 13px; -} - -.pc-action-card { - border-radius: 24px; - padding: 22px; -} - -.pc-action-card h2 { - margin: 0 0 18px; - font-size: 20px; -} - -.action-row { - display: grid; - grid-template-columns: 42px 1fr; - gap: 12px; - align-items: center; - border: 1px solid #edf0f4; - border-radius: 16px; - background: #ffffff; - padding: 14px; -} - -.action-row + .action-row { - margin-top: 12px; -} - -.action-row .el-icon { - width: 42px; - height: 42px; - border-radius: 14px; - background: #fff7ed; - color: #ea580c; - font-size: 20px; -} - -.action-row strong, -.action-row span { - display: block; -} - -.action-row strong { - color: #111827; -} - -.action-row span { - margin-top: 4px; - color: #6b7280; - font-size: 13px; - line-height: 1.5; -} - -.action-row.highlight { - border-color: #fbbf24; - background: #fffbeb; -} - -.pc-stat-grid, -.pc-feature-row { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; - margin-top: 20px; -} - -.pc-stat-card, -.feature-card { - border-radius: 20px; - padding: 20px; -} - -.pc-stat-card span, -.feature-card span { - color: #6b7280; - font-size: 13px; -} - -.pc-stat-card strong, -.feature-card strong { - display: block; - margin-top: 8px; - color: #111827; - font-size: 24px; -} - -.pc-stat-card p { - margin: 8px 0 0; - color: #6b7280; -} - -.feature-card .el-icon { - color: #f59e0b; - font-size: 24px; -} - -.feature-card strong { - font-size: 18px; -} - -.listings-header { - align-items: center; - margin-bottom: 18px; -} - -.advanced-filter-card { - border-radius: 22px; - padding: 20px; -} - -.filter-title, -.sort-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; -} - -.filter-title strong { - color: #111827; - font-size: 18px; -} - -.filter-title span { - color: #6b7280; -} - -.pc-filter-grid { - display: grid; - grid-template-columns: 1.25fr repeat(4, minmax(0, 1fr)); - gap: 14px; - margin-top: 16px; -} - -.pc-filter-grid .el-form-item { - margin-bottom: 0; -} - -.sort-toolbar { - margin-top: 16px; -} - -.pc-resource-list { - display: grid; - gap: 14px; - margin-top: 18px; -} - -.resource-card { - display: grid; - grid-template-columns: 150px minmax(0, 1fr) 150px; - gap: 18px; - align-items: center; - border-radius: 22px; - padding: 14px; - transition: - transform 0.18s ease, - box-shadow 0.18s ease; -} - -.resource-card:hover { - transform: translateY(-2px); - box-shadow: 0 22px 50px rgba(17, 24, 39, 0.12); -} - -.resource-cover { - display: grid; - place-items: center; - height: 108px; - border-radius: 18px; - background: linear-gradient(135deg, #111827, #f59e0b); - color: #ffffff; - font-weight: 900; - overflow: hidden; -} - -.resource-cover img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.resource-title-line { - justify-content: space-between; - gap: 12px; -} - -.resource-title-line strong { - color: #111827; - font-size: 19px; -} - -.resource-title-line em { - border-radius: 999px; - background: #dcfce7; - padding: 4px 10px; - color: #166534; - font-size: 12px; - font-style: normal; - font-weight: 700; -} - -.resource-main p { - display: -webkit-box; - margin: 10px 0 0; - overflow: hidden; - color: #6b7280; - line-height: 1.6; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; -} - -.resource-tags { - flex-wrap: wrap; - gap: 8px; - margin-top: 12px; -} - -.resource-tags span { - border-radius: 999px; - background: #f3f4f6; - padding: 5px 10px; - color: #4b5563; - font-size: 12px; -} - -.resource-price-box { - border-left: 1px solid #edf0f4; - padding-left: 18px; - text-align: right; -} - -.resource-price-box span, -.resource-price-box em, -.resource-price-box small { - display: block; - color: #6b7280; - font-style: normal; -} - -.resource-price-box strong { - display: block; - margin: 4px 0; - color: #ef4444; - font-size: 28px; -} - -.pc-detail-layout { - display: grid; - grid-template-columns: minmax(0, 1fr) 360px; - gap: 22px; -} - -.pc-detail-main, -.pc-order-card { - border: 1px solid rgba(229, 231, 235, 0.9); - border-radius: 24px; - background: #ffffff; - box-shadow: 0 18px 44px rgba(17, 24, 39, 0.08); -} - -.pc-detail-main { - overflow: hidden; -} - -.detail-cover { - display: grid; - place-items: center; - height: 300px; - background: linear-gradient(135deg, #111827, #92400e 54%, #f59e0b); - color: #ffffff; - font-size: 28px; - font-weight: 900; - letter-spacing: 1px; -} - -.detail-cover img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.detail-title { - padding: 28px; -} - -.pc-detail .detail-grid { - grid-template-columns: repeat(4, minmax(0, 1fr)); - margin: 0; - padding: 0 28px 28px; -} - -.pc-order-card { - position: sticky; - top: 96px; - align-self: start; - margin-top: 0; - padding: 24px; -} - -.pc-order-card h2 { - margin: 0 0 8px; - font-size: 22px; -} - -.order-safe-text { - margin: 0 0 18px; - color: #6b7280; - line-height: 1.7; -} - -.order-total-box { - border-radius: 18px; - background: #fffbeb; - margin-bottom: 16px; - padding: 16px; -} - -.order-total-box span, -.order-total-box em { - display: block; - color: #92400e; - font-style: normal; -} - -.order-total-box strong { - display: block; - margin: 4px 0; - color: #ef4444; - font-size: 30px; -} - -@media (max-width: 1180px) { - .pc-topbar { - grid-template-columns: auto 1fr auto; - } - - .pc-search { - display: none; - } - - .pc-hero, - .pc-filter-grid, - .pc-detail-layout, - .pc-detail .detail-grid { - grid-template-columns: 1fr; - } - - .pc-order-card { - position: static; - } - - .resource-card { - grid-template-columns: 120px minmax(0, 1fr); - } - - .resource-price-box { - grid-column: 2; - border-left: 0; - border-top: 1px solid #edf0f4; - padding: 12px 0 0; - text-align: left; - } -} - -@media (max-width: 760px) { - .app-shell { - grid-template-columns: 1fr; - } - - .sidebar { - position: sticky; - top: 0; - z-index: 10; - border-right: 0; - border-bottom: 1px solid #e4e7ed; - } - - .nav { - grid-auto-flow: column; - grid-auto-columns: max-content; - overflow-x: auto; - } - - .main { - padding: 24px 16px; - } - - .metric-grid { - grid-template-columns: 1fr; - } - - .listing-grid, - .detail-grid, - .dashboard-metrics, - .dashboard-panels, - .form-grid { - grid-template-columns: 1fr; - } - - .page-header-row { - display: grid; - } - - .toolbar-actions { - justify-content: flex-start; - } -} diff --git a/frontend/src/shared/styles/performance.css b/frontend/src/shared/styles/performance.css deleted file mode 100644 index dc42702..0000000 --- a/frontend/src/shared/styles/performance.css +++ /dev/null @@ -1,36 +0,0 @@ -/* 性能优化相关样式 */ - -.lazy-image { - background: #f1f5f9; - min-height: 180px; -} - -.component-loading, -.component-error { - display: flex; - align-items: center; - justify-content: center; - min-height: 200px; - color: #94a3b8; - font-size: 14px; -} - -.component-error { - color: #ef4444; -} - -/* 骨架屏动画 */ -@keyframes skeleton-loading { - 0% { - background-position: -200px 0; - } - 100% { - background-position: calc(200px + 100%) 0; - } -} - -.skeleton { - background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); - background-size: 200px 100%; - animation: skeleton-loading 1.5s ease-in-out infinite; -} diff --git a/frontend/src/styles/performance.css b/frontend/src/styles/performance.css deleted file mode 100644 index dc42702..0000000 --- a/frontend/src/styles/performance.css +++ /dev/null @@ -1,36 +0,0 @@ -/* 性能优化相关样式 */ - -.lazy-image { - background: #f1f5f9; - min-height: 180px; -} - -.component-loading, -.component-error { - display: flex; - align-items: center; - justify-content: center; - min-height: 200px; - color: #94a3b8; - font-size: 14px; -} - -.component-error { - color: #ef4444; -} - -/* 骨架屏动画 */ -@keyframes skeleton-loading { - 0% { - background-position: -200px 0; - } - 100% { - background-position: calc(200px + 100%) 0; - } -} - -.skeleton { - background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); - background-size: 200px 100%; - animation: skeleton-loading 1.5s ease-in-out infinite; -}