diff --git a/frontend/src/features/admin/components/GeneralConfigDialog.vue b/frontend/src/features/admin/components/GeneralConfigDialog.vue index 9fb9d8b..c771934 100644 --- a/frontend/src/features/admin/components/GeneralConfigDialog.vue +++ b/frontend/src/features/admin/components/GeneralConfigDialog.vue @@ -3,7 +3,10 @@ import { ElMessage } from 'element-plus' import { computed, ref, watch } from 'vue' import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs' -import { getSystemConfigSelectOptions, type SystemConfigOption } from '@/utils/systemConfigOptions' +import { + getSystemConfigSelectOptions, + type SystemConfigOption, +} from '@/shared/utils/systemConfigOptions' const props = defineProps<{ modelValue: boolean diff --git a/frontend/src/features/admin/components/HomeAnnouncementsDialog.vue b/frontend/src/features/admin/components/HomeAnnouncementsDialog.vue index 6e8799b..db4a072 100644 --- a/frontend/src/features/admin/components/HomeAnnouncementsDialog.vue +++ b/frontend/src/features/admin/components/HomeAnnouncementsDialog.vue @@ -4,7 +4,7 @@ import { ref, watch } from 'vue' import { defaultHomeAnnouncements, mergeHomeConfig } from '@/features/listings/api/homeConfig' import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs' -import { safeParseJSON } from '@/utils/json' +import { safeParseJSON } from '@/shared/utils/json' const props = defineProps<{ modelValue: boolean diff --git a/frontend/src/features/admin/components/HomeBannersDialog.vue b/frontend/src/features/admin/components/HomeBannersDialog.vue index 8ee4767..7edef41 100644 --- a/frontend/src/features/admin/components/HomeBannersDialog.vue +++ b/frontend/src/features/admin/components/HomeBannersDialog.vue @@ -9,7 +9,7 @@ import { type HomeBannerSlide, } from '@/features/listings/api/homeConfig' import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs' -import { safeParseJSON } from '@/utils/json' +import { safeParseJSON } from '@/shared/utils/json' const props = defineProps<{ modelValue: boolean diff --git a/frontend/src/features/admin/components/ListingPublishAgreementsDialog.vue b/frontend/src/features/admin/components/ListingPublishAgreementsDialog.vue index 1832674..891d07f 100644 --- a/frontend/src/features/admin/components/ListingPublishAgreementsDialog.vue +++ b/frontend/src/features/admin/components/ListingPublishAgreementsDialog.vue @@ -4,7 +4,7 @@ import { ref, watch } from 'vue' import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs' import type { ListingPublishAgreements } from '@/features/listings/api/listingOptions' -import { safeParseJSON } from '@/utils/json' +import { safeParseJSON } from '@/shared/utils/json' const defaultListingPublishAgreements: ListingPublishAgreements = { virtual_asset_sale: { diff --git a/frontend/src/features/admin/components/OrderAgreementsDialog.vue b/frontend/src/features/admin/components/OrderAgreementsDialog.vue index 10fc4d1..59b774b 100644 --- a/frontend/src/features/admin/components/OrderAgreementsDialog.vue +++ b/frontend/src/features/admin/components/OrderAgreementsDialog.vue @@ -4,7 +4,7 @@ import { ref, watch } from 'vue' import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs' import type { OrderAgreements } from '@/features/orders/api/orders' -import { safeParseJSON } from '@/utils/json' +import { safeParseJSON } from '@/shared/utils/json' const defaultOrderAgreements: OrderAgreements = { virtual_asset_purchase: { diff --git a/frontend/src/features/admin/components/PaymentConfigDialog.vue b/frontend/src/features/admin/components/PaymentConfigDialog.vue index d685f83..a200d70 100644 --- a/frontend/src/features/admin/components/PaymentConfigDialog.vue +++ b/frontend/src/features/admin/components/PaymentConfigDialog.vue @@ -10,7 +10,7 @@ import { type CreatePaymentConfigRequest, type UpdatePaymentConfigRequest, } from '@/features/admin/api/paymentConfig' -import { readError } from '@/utils/error' +import { readError } from '@/shared/utils/error' const props = defineProps<{ modelValue: boolean diff --git a/frontend/src/features/admin/components/PostRentalNoticeDialog.vue b/frontend/src/features/admin/components/PostRentalNoticeDialog.vue index 1e40c47..44e1463 100644 --- a/frontend/src/features/admin/components/PostRentalNoticeDialog.vue +++ b/frontend/src/features/admin/components/PostRentalNoticeDialog.vue @@ -3,7 +3,7 @@ import { ElMessage } from 'element-plus' import { ref, watch } from 'vue' import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs' -import { safeParseJSON } from '@/utils/json' +import { safeParseJSON } from '@/shared/utils/json' interface PostRentalNotice { title: string diff --git a/frontend/src/features/admin/components/PublishOptionsDialog.vue b/frontend/src/features/admin/components/PublishOptionsDialog.vue index 0530223..08b6905 100644 --- a/frontend/src/features/admin/components/PublishOptionsDialog.vue +++ b/frontend/src/features/admin/components/PublishOptionsDialog.vue @@ -8,7 +8,7 @@ import { type ListingPublishOptions, } from '@/features/listings/api/listingOptions' import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs' -import { safeParseJSON } from '@/utils/json' +import { safeParseJSON } from '@/shared/utils/json' const props = defineProps<{ modelValue: boolean diff --git a/frontend/src/features/admin/components/SalePriceDialog.vue b/frontend/src/features/admin/components/SalePriceDialog.vue index c932831..501336d 100644 --- a/frontend/src/features/admin/components/SalePriceDialog.vue +++ b/frontend/src/features/admin/components/SalePriceDialog.vue @@ -8,7 +8,7 @@ import { type PublishSalePriceConfig, } from '@/features/listings/api/listingOptions' import { updateSystemConfig, type SystemConfig } from '@/features/admin/api/systemConfigs' -import { safeParseJSON } from '@/utils/json' +import { safeParseJSON } from '@/shared/utils/json' const props = defineProps<{ modelValue: boolean diff --git a/frontend/src/features/admin/views/AdminAnnouncementsView.vue b/frontend/src/features/admin/views/AdminAnnouncementsView.vue index 7740db9..78f2c84 100644 --- a/frontend/src/features/admin/views/AdminAnnouncementsView.vue +++ b/frontend/src/features/admin/views/AdminAnnouncementsView.vue @@ -23,7 +23,7 @@ import { type UpdateAnnouncementRequest, } from '@/features/admin/api/adminAnnouncements' import type { Announcement } from '@/features/announcement' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' import AnnouncementDialog from '../components/AnnouncementDialog.vue' import AdminTablePagination from '../components/AdminTablePagination.vue' diff --git a/frontend/src/features/admin/views/AdminAuditLogsView.vue b/frontend/src/features/admin/views/AdminAuditLogsView.vue index b285787..686feee 100644 --- a/frontend/src/features/admin/views/AdminAuditLogsView.vue +++ b/frontend/src/features/admin/views/AdminAuditLogsView.vue @@ -3,7 +3,7 @@ import { Search } from '@element-plus/icons-vue' import { computed, onMounted, reactive, ref } from 'vue' import { fetchAdminAuditLogs, type AdminAuditLog } from '@/features/admin/api/adminAudit' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' import AdminTablePagination from '../components/AdminTablePagination.vue' const loading = ref(false) diff --git a/frontend/src/features/admin/views/AdminChatsView.vue b/frontend/src/features/admin/views/AdminChatsView.vue index 81c2d63..7b2a1bb 100644 --- a/frontend/src/features/admin/views/AdminChatsView.vue +++ b/frontend/src/features/admin/views/AdminChatsView.vue @@ -19,7 +19,7 @@ import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue' import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE' import { useDesktopNotification } from '@/features/chats/composables/useDesktopNotification' import NotificationSettings from '@/features/chats/components/NotificationSettings.vue' -import { formatDateMinute } from '@/utils/time' +import { formatDateMinute } from '@/shared/utils/time' import TransferDialog from '../components/TransferDialog.vue' import QuickReplyDialog from '../components/QuickReplyDialog.vue' diff --git a/frontend/src/features/admin/views/AdminDashboardView.vue b/frontend/src/features/admin/views/AdminDashboardView.vue index 54e8d8b..a316e68 100644 --- a/frontend/src/features/admin/views/AdminDashboardView.vue +++ b/frontend/src/features/admin/views/AdminDashboardView.vue @@ -18,8 +18,8 @@ import { import { fetchAdminDashboard, type AdminDashboard } from '@/features/admin/api/adminDashboard' import { useAdminTable } from '@/features/admin/composables/useAdminTable' import { formatCentWithSymbol } from '@/shared/utils/money' -import { disputeStatusLabel, orderStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' +import { disputeStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' const { loading, diff --git a/frontend/src/features/admin/views/AdminDisputesView.vue b/frontend/src/features/admin/views/AdminDisputesView.vue index 1450bde..2036e0f 100644 --- a/frontend/src/features/admin/views/AdminDisputesView.vue +++ b/frontend/src/features/admin/views/AdminDisputesView.vue @@ -4,9 +4,9 @@ import { computed, onMounted, ref } from 'vue' import { arbitrateDispute, fetchAdminDisputes, type Dispute } from '@/features/disputes' import { fetchAdminFileBlob } from '@/shared/api/files' -import { disputeStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' -import { formatListingNo } from '@/utils/listingDisplay' +import { disputeStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' +import { formatListingNo } from '@/shared/utils/listingDisplay' import { yuanToCent } from '@/shared/utils/money' import AdminTablePagination from '../components/AdminTablePagination.vue' diff --git a/frontend/src/features/admin/views/AdminFinanceDashboardView.vue b/frontend/src/features/admin/views/AdminFinanceDashboardView.vue index e5fe5df..df8fb10 100644 --- a/frontend/src/features/admin/views/AdminFinanceDashboardView.vue +++ b/frontend/src/features/admin/views/AdminFinanceDashboardView.vue @@ -8,7 +8,7 @@ import { type FinanceDailyItem, } from '@/features/admin/api/adminFinance' import { formatCentWithSymbol } from '@/shared/utils/money' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' const loading = ref(false) const dashboard = ref(null) diff --git a/frontend/src/features/admin/views/AdminFinanceDetailsView.vue b/frontend/src/features/admin/views/AdminFinanceDetailsView.vue index a869cce..d880890 100644 --- a/frontend/src/features/admin/views/AdminFinanceDetailsView.vue +++ b/frontend/src/features/admin/views/AdminFinanceDetailsView.vue @@ -4,8 +4,8 @@ import { onMounted, reactive, ref } from 'vue' import { fetchFinanceDetails, type FinanceDetail } from '@/features/admin/api/adminFinance' import { formatCentWithSymbol } from '@/shared/utils/money' -import { orderStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' +import { orderStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' import AdminTablePagination from '../components/AdminTablePagination.vue' const loading = ref(false) diff --git a/frontend/src/features/admin/views/AdminListingDetailView.vue b/frontend/src/features/admin/views/AdminListingDetailView.vue index cd26358..faa3c47 100644 --- a/frontend/src/features/admin/views/AdminListingDetailView.vue +++ b/frontend/src/features/admin/views/AdminListingDetailView.vue @@ -12,9 +12,9 @@ import { fetchAdminListing, type Listing, } from '@/features/listings' -import { listingReviewStatusLabel, listingStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' -import { formatListingCode } from '@/utils/listingDisplay' +import { listingReviewStatusLabel, listingStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' +import { formatListingCode } from '@/shared/utils/listingDisplay' const route = useRoute() const loading = ref(false) diff --git a/frontend/src/features/admin/views/AdminListingReviewView.vue b/frontend/src/features/admin/views/AdminListingReviewView.vue index eb788a8..2172e51 100644 --- a/frontend/src/features/admin/views/AdminListingReviewView.vue +++ b/frontend/src/features/admin/views/AdminListingReviewView.vue @@ -23,8 +23,8 @@ import { getSkinNames, readAssetNumber, readAssetString, -} from '@/utils/listingDisplay' -import { formatDateTime } from '@/utils/time' +} from '@/shared/utils/listingDisplay' +import { formatDateTime } from '@/shared/utils/time' type RiskLevel = 'danger' | 'warning' | 'info' diff --git a/frontend/src/features/admin/views/AdminListingsView.vue b/frontend/src/features/admin/views/AdminListingsView.vue index 27a5429..3a16dd5 100644 --- a/frontend/src/features/admin/views/AdminListingsView.vue +++ b/frontend/src/features/admin/views/AdminListingsView.vue @@ -21,7 +21,7 @@ import { getDailyLoss, getResourceQuantity, getSkinGroup, -} from '@/utils/listingDisplay' +} from '@/shared/utils/listingDisplay' const filters = reactive({ owner_id: '', diff --git a/frontend/src/features/admin/views/AdminMgrUsersView.vue b/frontend/src/features/admin/views/AdminMgrUsersView.vue index f93c47d..1affbc8 100644 --- a/frontend/src/features/admin/views/AdminMgrUsersView.vue +++ b/frontend/src/features/admin/views/AdminMgrUsersView.vue @@ -9,7 +9,7 @@ import { type AdminMgrUser, } from '@/features/admin/api/adminMgr' import { useAdminPaginatedTable } from '@/features/admin/composables/useAdminPaginatedTable' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' import AdminUserDialog from '../components/AdminUserDialog.vue' import AssignRolesDialog from '../components/AssignRolesDialog.vue' diff --git a/frontend/src/features/admin/views/AdminOrderDetailView.vue b/frontend/src/features/admin/views/AdminOrderDetailView.vue index 22c9119..44fe751 100644 --- a/frontend/src/features/admin/views/AdminOrderDetailView.vue +++ b/frontend/src/features/admin/views/AdminOrderDetailView.vue @@ -16,9 +16,9 @@ import { } from '@/features/orders' import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments' import { centToYuan, formatCentWithSymbol, formatMoney } from '@/shared/utils/money' -import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' -import { formatListingNo } from '@/utils/listingDisplay' +import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' +import { formatListingNo } from '@/shared/utils/listingDisplay' const route = useRoute() const loading = ref(false) diff --git a/frontend/src/features/admin/views/AdminOrdersView.vue b/frontend/src/features/admin/views/AdminOrdersView.vue index 5cb312c..9c29929 100644 --- a/frontend/src/features/admin/views/AdminOrdersView.vue +++ b/frontend/src/features/admin/views/AdminOrdersView.vue @@ -2,10 +2,14 @@ import { computed, onMounted, ref } from 'vue' import { fetchAdminOrders, type Order } from '@/features/orders' -import { handoffStatusLabel, orderStatusLabel, settlementStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' +import { + handoffStatusLabel, + orderStatusLabel, + settlementStatusLabel, +} from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' import { centToYuan, formatMoney } from '@/shared/utils/money' -import { formatListingNo } from '@/utils/listingDisplay' +import { formatListingNo } from '@/shared/utils/listingDisplay' import AdminTablePagination from '../components/AdminTablePagination.vue' const status = ref('') diff --git a/frontend/src/features/admin/views/AdminPaymentConfigsView.vue b/frontend/src/features/admin/views/AdminPaymentConfigsView.vue index ccb96aa..15ea046 100644 --- a/frontend/src/features/admin/views/AdminPaymentConfigsView.vue +++ b/frontend/src/features/admin/views/AdminPaymentConfigsView.vue @@ -10,9 +10,9 @@ import { importPaymentConfigBackup, type PaymentConfig, } from '@/features/admin/api/paymentConfig' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' import { formatCent } from '@/shared/utils/money' -import { readError } from '@/utils/error' +import { readError } from '@/shared/utils/error' import PaymentConfigDialog from '../components/PaymentConfigDialog.vue' const loading = ref(false) diff --git a/frontend/src/features/admin/views/AdminPaymentsView.vue b/frontend/src/features/admin/views/AdminPaymentsView.vue index 883c611..3377099 100644 --- a/frontend/src/features/admin/views/AdminPaymentsView.vue +++ b/frontend/src/features/admin/views/AdminPaymentsView.vue @@ -4,7 +4,7 @@ import { computed, onMounted, reactive, ref } from 'vue' import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments' import { formatCentWithSymbol } from '@/shared/utils/money' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' import AdminTablePagination from '../components/AdminTablePagination.vue' const loading = ref(false) diff --git a/frontend/src/features/admin/views/AdminSystemConfigsView.vue b/frontend/src/features/admin/views/AdminSystemConfigsView.vue index d18bd9a..f6d67ef 100644 --- a/frontend/src/features/admin/views/AdminSystemConfigsView.vue +++ b/frontend/src/features/admin/views/AdminSystemConfigsView.vue @@ -18,9 +18,9 @@ import { } from '@/features/listings/api/homeConfig' import type { OrderAgreements } from '@/features/orders/api/orders' import { fetchSystemConfigs, type SystemConfig } from '@/features/admin/api/systemConfigs' -import { formatDateTime } from '@/utils/time' -import { safeParseJSON } from '@/utils/json' -import { formatSystemConfigSelectValue } from '@/utils/systemConfigOptions' +import { formatDateTime } from '@/shared/utils/time' +import { safeParseJSON } from '@/shared/utils/json' +import { formatSystemConfigSelectValue } from '@/shared/utils/systemConfigOptions' // 导入重构后的 Dialog 子组件 import PublishOptionsDialog from '../components/PublishOptionsDialog.vue' diff --git a/frontend/src/features/admin/views/AdminUsersView.vue b/frontend/src/features/admin/views/AdminUsersView.vue index 2ab2177..a34f56d 100644 --- a/frontend/src/features/admin/views/AdminUsersView.vue +++ b/frontend/src/features/admin/views/AdminUsersView.vue @@ -11,8 +11,8 @@ import { } from '@/features/admin/api/adminUsers' import { centToYuan, formatCent, yuanToCent } from '@/shared/utils/money' import { useAdminPaginatedTable } from '@/features/admin/composables/useAdminPaginatedTable' -import { userStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' +import { userStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' import AdminTablePagination from '../components/AdminTablePagination.vue' const submitting = ref(false) diff --git a/frontend/src/features/admin/views/AdminWalletLedgerView.vue b/frontend/src/features/admin/views/AdminWalletLedgerView.vue index 5c3b68f..29d5a4a 100644 --- a/frontend/src/features/admin/views/AdminWalletLedgerView.vue +++ b/frontend/src/features/admin/views/AdminWalletLedgerView.vue @@ -4,8 +4,8 @@ import { computed, onMounted, reactive, ref } from 'vue' import { fetchAdminWalletLedger, type AdminWalletLedger } from '@/features/admin/api/adminWallet' import { formatCentWithSymbol } from '@/shared/utils/money' -import { balanceTypeLabel, ledgerDirectionLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' +import { balanceTypeLabel, ledgerDirectionLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' import AdminTablePagination from '../components/AdminTablePagination.vue' const loading = ref(false) diff --git a/frontend/src/features/announcement/views/AnnouncementDetailView.vue b/frontend/src/features/announcement/views/AnnouncementDetailView.vue index d3e685a..ed799fc 100644 --- a/frontend/src/features/announcement/views/AnnouncementDetailView.vue +++ b/frontend/src/features/announcement/views/AnnouncementDetailView.vue @@ -11,7 +11,7 @@ import { Warning, } from '@element-plus/icons-vue' import { fetchAnnouncementDetail, type Announcement } from '@/features/announcement' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' import { marked } from 'marked' const route = useRoute() diff --git a/frontend/src/features/announcement/views/AnnouncementsView.vue b/frontend/src/features/announcement/views/AnnouncementsView.vue index 5fc0457..c768a75 100644 --- a/frontend/src/features/announcement/views/AnnouncementsView.vue +++ b/frontend/src/features/announcement/views/AnnouncementsView.vue @@ -10,7 +10,7 @@ import { Warning, } from '@element-plus/icons-vue' import { fetchAnnouncements, type Announcement } from '@/features/announcement' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' const router = useRouter() const loading = ref(false) diff --git a/frontend/src/features/announcement/views/MobileAnnouncementDetailView.vue b/frontend/src/features/announcement/views/MobileAnnouncementDetailView.vue index 7a1568f..c97083b 100644 --- a/frontend/src/features/announcement/views/MobileAnnouncementDetailView.vue +++ b/frontend/src/features/announcement/views/MobileAnnouncementDetailView.vue @@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router' import { showToast } from 'vant' import { marked } from 'marked' import { fetchAnnouncementDetail, type Announcement } from '@/features/announcement' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' const route = useRoute() const router = useRouter() diff --git a/frontend/src/features/announcement/views/MobileAnnouncementsView.vue b/frontend/src/features/announcement/views/MobileAnnouncementsView.vue index 26e8e13..e726968 100644 --- a/frontend/src/features/announcement/views/MobileAnnouncementsView.vue +++ b/frontend/src/features/announcement/views/MobileAnnouncementsView.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import { showToast } from 'vant' import MobileBottomNav from '@/components/MobileBottomNav.vue' import { fetchAnnouncements, type Announcement } from '@/features/announcement' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' const router = useRouter() const loading = ref(false) diff --git a/frontend/src/features/auth/views/MobileProfileView.vue b/frontend/src/features/auth/views/MobileProfileView.vue index b030f60..54a6395 100644 --- a/frontend/src/features/auth/views/MobileProfileView.vue +++ b/frontend/src/features/auth/views/MobileProfileView.vue @@ -11,7 +11,7 @@ import { type WalletLedger, } from '@/features/wallet/api/wallet' import { fetchPostRentalNotice, type PostRentalNotice } from '@/features/orders/api/orders' -import { formatDateMinute } from '@/utils/time' +import { formatDateMinute } from '@/shared/utils/time' import { uploadFile } from '@/shared/api/files' import { formatCent } from '@/shared/utils/money' diff --git a/frontend/src/features/auth/views/MobileRealnameView.vue b/frontend/src/features/auth/views/MobileRealnameView.vue index 54a611a..189efe9 100644 --- a/frontend/src/features/auth/views/MobileRealnameView.vue +++ b/frontend/src/features/auth/views/MobileRealnameView.vue @@ -3,9 +3,9 @@ import { onMounted, reactive, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import { showToast, showDialog } from 'vant' import { getRealnameStatus, startRealname, type RealnameStatus } from '@/features/auth/api/realname' -import { realnameStatusLabel } from '@/utils/statusLabels' +import { realnameStatusLabel } from '@/shared/utils/statusLabels' import { useSessionStore } from '@/stores/session' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' const session = useSessionStore() const router = useRouter() diff --git a/frontend/src/features/auth/views/NotificationsView.vue b/frontend/src/features/auth/views/NotificationsView.vue index c77b4c5..b7f6591 100644 --- a/frontend/src/features/auth/views/NotificationsView.vue +++ b/frontend/src/features/auth/views/NotificationsView.vue @@ -7,7 +7,7 @@ import { markNotificationRead, type NotificationItem, } from '@/features/auth/api/notifications' -import { formatDateTime } from '@/utils/time' +import { formatDateTime } from '@/shared/utils/time' const loading = ref(false) const notifications = ref([]) diff --git a/frontend/src/features/auth/views/ProfileView.vue b/frontend/src/features/auth/views/ProfileView.vue index 3fcd0bd..5f633c4 100644 --- a/frontend/src/features/auth/views/ProfileView.vue +++ b/frontend/src/features/auth/views/ProfileView.vue @@ -23,7 +23,7 @@ import { useRouter } from 'vue-router' import { uploadFile } from '@/shared/api/files' import { useSessionStore } from '@/stores/session' -import { realnameStatusLabel } from '@/utils/statusLabels' +import { realnameStatusLabel } from '@/shared/utils/statusLabels' const session = useSessionStore() const router = useRouter() diff --git a/frontend/src/features/auth/views/RealnameView.vue b/frontend/src/features/auth/views/RealnameView.vue index ee776dd..f146c63 100644 --- a/frontend/src/features/auth/views/RealnameView.vue +++ b/frontend/src/features/auth/views/RealnameView.vue @@ -15,8 +15,8 @@ import { useRoute, useRouter } from 'vue-router' import { getRealnameStatus, startRealname, type RealnameStatus } from '@/features/auth/api/realname' import { useSessionStore } from '@/stores/session' -import { realnameStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' +import { realnameStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' const session = useSessionStore() const route = useRoute() diff --git a/frontend/src/features/chats/composables/useChatSSE.ts b/frontend/src/features/chats/composables/useChatSSE.ts index 0765c47..0f67518 100644 --- a/frontend/src/features/chats/composables/useChatSSE.ts +++ b/frontend/src/features/chats/composables/useChatSSE.ts @@ -1,6 +1,6 @@ import { onBeforeUnmount, ref, type Ref } from 'vue' import { refreshAccessToken } from '@/shared/api/client' -import { getAccessToken, type AuthScope } from '@/utils/authStorage' +import { getAccessToken, type AuthScope } from '@/shared/utils/authStorage' export interface SSEMessage { id: number diff --git a/frontend/src/features/chats/views/ChatView.vue b/frontend/src/features/chats/views/ChatView.vue index 7352bfa..03f7638 100644 --- a/frontend/src/features/chats/views/ChatView.vue +++ b/frontend/src/features/chats/views/ChatView.vue @@ -15,7 +15,7 @@ import { uploadFile } from '@/shared/api/files' import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue' import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE' import { useDesktopNotification } from '@/features/chats/composables/useDesktopNotification' -import { formatDateMinute } from '@/utils/time' +import { formatDateMinute } from '@/shared/utils/time' const currentUserId = Number(localStorage.getItem('user_id') || 0) diff --git a/frontend/src/features/chats/views/MessagesView.vue b/frontend/src/features/chats/views/MessagesView.vue index 4c7c3f7..3a6474c 100644 --- a/frontend/src/features/chats/views/MessagesView.vue +++ b/frontend/src/features/chats/views/MessagesView.vue @@ -7,7 +7,7 @@ import { fetchChats, type ChatConversation } from '@/features/chats/api/chats' import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE' import { useDesktopNotification } from '@/features/chats/composables/useDesktopNotification' import NotificationSettings from '@/features/chats/components/NotificationSettings.vue' -import { formatDateMinute } from '@/utils/time' +import { formatDateMinute } from '@/shared/utils/time' const router = useRouter() const currentUserId = Number(localStorage.getItem('user_id') || 0) diff --git a/frontend/src/features/chats/views/MobileChatView.vue b/frontend/src/features/chats/views/MobileChatView.vue index ae4cb20..692fd57 100644 --- a/frontend/src/features/chats/views/MobileChatView.vue +++ b/frontend/src/features/chats/views/MobileChatView.vue @@ -14,7 +14,7 @@ import { uploadFile } from '@/shared/api/files' import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue' import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE' import { useDesktopNotification } from '@/features/chats/composables/useDesktopNotification' -import { formatDateMinute } from '@/utils/time' +import { formatDateMinute } from '@/shared/utils/time' const currentUserId = Number(localStorage.getItem('user_id') || 0) diff --git a/frontend/src/features/chats/views/MobileMessagesView.vue b/frontend/src/features/chats/views/MobileMessagesView.vue index f03d5f1..6953709 100644 --- a/frontend/src/features/chats/views/MobileMessagesView.vue +++ b/frontend/src/features/chats/views/MobileMessagesView.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import { showToast } from 'vant' import MobileBottomNav from '@/components/MobileBottomNav.vue' import { fetchChats, type ChatConversation } from '@/features/chats/api/chats' -import { formatDateMinute } from '@/utils/time' +import { formatDateMinute } from '@/shared/utils/time' const router = useRouter() const loading = ref(false) diff --git a/frontend/src/features/listings/components/ListingCard.vue b/frontend/src/features/listings/components/ListingCard.vue index cbde804..fabc972 100644 --- a/frontend/src/features/listings/components/ListingCard.vue +++ b/frontend/src/features/listings/components/ListingCard.vue @@ -21,7 +21,7 @@ import { readAssetString, getRatioValue, type ListingDisplayResource, -} from '@/utils/listingDisplay' +} from '@/shared/utils/listingDisplay' interface Props { listing: Listing diff --git a/frontend/src/features/listings/views/ListingDetailView.vue b/frontend/src/features/listings/views/ListingDetailView.vue index a750be3..a6bcfcb 100644 --- a/frontend/src/features/listings/views/ListingDetailView.vue +++ b/frontend/src/features/listings/views/ListingDetailView.vue @@ -31,7 +31,7 @@ import { getLoginMethod, getServerRegion, readAssetString, -} from '@/utils/listingDisplay' +} from '@/shared/utils/listingDisplay' const route = useRoute() const router = useRouter() diff --git a/frontend/src/features/listings/views/ListingsView.vue b/frontend/src/features/listings/views/ListingsView.vue index 8edaf88..216df2c 100644 --- a/frontend/src/features/listings/views/ListingsView.vue +++ b/frontend/src/features/listings/views/ListingsView.vue @@ -28,7 +28,7 @@ import { hasGiftResources, readAssetNumber, readAssetString, -} from '@/utils/listingDisplay' +} from '@/shared/utils/listingDisplay' /* ------------------------------------------------------------------ */ /* 数据加载 */ diff --git a/frontend/src/features/listings/views/MobileHomeView.vue b/frontend/src/features/listings/views/MobileHomeView.vue index b5004c3..216c3ff 100644 --- a/frontend/src/features/listings/views/MobileHomeView.vue +++ b/frontend/src/features/listings/views/MobileHomeView.vue @@ -34,7 +34,7 @@ import { assetRegions, hasAcceleratedSaleRatio, hasGiftResources, -} from '@/utils/listingDisplay' +} from '@/shared/utils/listingDisplay' import { useSessionStore } from '@/stores/session' const router = useRouter() diff --git a/frontend/src/features/listings/views/MobileListingDetailView.vue b/frontend/src/features/listings/views/MobileListingDetailView.vue index 84abb04..744b587 100644 --- a/frontend/src/features/listings/views/MobileListingDetailView.vue +++ b/frontend/src/features/listings/views/MobileListingDetailView.vue @@ -29,7 +29,7 @@ import { getLoginMethod, getServerRegion, readAssetString, -} from '@/utils/listingDisplay' +} from '@/shared/utils/listingDisplay' const route = useRoute() const router = useRouter() diff --git a/frontend/src/features/orders/views/MobileOrderDetailView.vue b/frontend/src/features/orders/views/MobileOrderDetailView.vue index 5f1d6f0..a45f91f 100644 --- a/frontend/src/features/orders/views/MobileOrderDetailView.vue +++ b/frontend/src/features/orders/views/MobileOrderDetailView.vue @@ -23,9 +23,9 @@ import { type PaymentOrder, } from '@/features/orders/api/orders' import { useSessionStore } from '@/stores/session' -import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' -import { formatListingNo } from '@/utils/listingDisplay' +import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' +import { formatListingNo } from '@/shared/utils/listingDisplay' const route = useRoute() const router = useRouter() diff --git a/frontend/src/features/orders/views/MobileOrdersView.vue b/frontend/src/features/orders/views/MobileOrdersView.vue index aa9ba24..50a1a35 100644 --- a/frontend/src/features/orders/views/MobileOrdersView.vue +++ b/frontend/src/features/orders/views/MobileOrdersView.vue @@ -12,8 +12,8 @@ import { } from '@/features/orders/api/orders' import { centToYuan, formatMoney } from '@/shared/utils/money' import { useSessionStore } from '@/stores/session' -import { formatDateMinute } from '@/utils/time' -import { formatListingNo } from '@/utils/listingDisplay' +import { formatDateMinute } from '@/shared/utils/time' +import { formatListingNo } from '@/shared/utils/listingDisplay' const router = useRouter() const route = useRoute() diff --git a/frontend/src/features/orders/views/OrderDetailView.vue b/frontend/src/features/orders/views/OrderDetailView.vue index fa92593..7bde27a 100644 --- a/frontend/src/features/orders/views/OrderDetailView.vue +++ b/frontend/src/features/orders/views/OrderDetailView.vue @@ -26,9 +26,9 @@ import { type PaymentOrder, } from '@/features/orders' import { useSessionStore } from '@/stores/session' -import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' -import { formatListingNo } from '@/utils/listingDisplay' +import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' +import { formatListingNo } from '@/shared/utils/listingDisplay' const route = useRoute() const router = useRouter() diff --git a/frontend/src/features/orders/views/OrdersView.vue b/frontend/src/features/orders/views/OrdersView.vue index 62baf14..45e94e0 100644 --- a/frontend/src/features/orders/views/OrdersView.vue +++ b/frontend/src/features/orders/views/OrdersView.vue @@ -7,9 +7,9 @@ import { CopyDocument, Search } from '@element-plus/icons-vue' import { fetchOrders, type Order } from '@/features/orders' import { centToYuan, formatMoney } from '@/shared/utils/money' import { useSessionStore } from '@/stores/session' -import { orderStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' -import { formatListingNo } from '@/utils/listingDisplay' +import { orderStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' +import { formatListingNo } from '@/shared/utils/listingDisplay' const loading = ref(false) const orders = ref([]) diff --git a/frontend/src/features/seller/composables/usePublishForm.ts b/frontend/src/features/seller/composables/usePublishForm.ts index 6121f9c..a9e0f30 100644 --- a/frontend/src/features/seller/composables/usePublishForm.ts +++ b/frontend/src/features/seller/composables/usePublishForm.ts @@ -27,7 +27,12 @@ import { } from '@/features/seller/composables/usePublishDraft' import type { PublishForm } from '@/types/publish' import { yuanToCent } from '@/shared/utils/money' -import { commonOnlineTimes, dailyLossOptions, formatNumber, roundRatio } from '@/utils/pricing' +import { + commonOnlineTimes, + dailyLossOptions, + formatNumber, + roundRatio, +} from '@/shared/utils/pricing' const draftSaveDelay = 400 const multiScreenshotLimit = 3 diff --git a/frontend/src/features/seller/views/SellerHandoffsView.vue b/frontend/src/features/seller/views/SellerHandoffsView.vue index b9e97bd..3b5f67a 100644 --- a/frontend/src/features/seller/views/SellerHandoffsView.vue +++ b/frontend/src/features/seller/views/SellerHandoffsView.vue @@ -3,10 +3,10 @@ import { computed, onMounted, ref } from 'vue' import { fetchOrders, type Order } from '@/features/orders' import { useSessionStore } from '@/stores/session' -import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' +import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' import { centToYuan, formatMoney } from '@/shared/utils/money' -import { formatListingNo } from '@/utils/listingDisplay' +import { formatListingNo } from '@/shared/utils/listingDisplay' const session = useSessionStore() const loading = ref(false) diff --git a/frontend/src/features/seller/views/SellerListingsView.vue b/frontend/src/features/seller/views/SellerListingsView.vue index 4e61ac0..c23687f 100644 --- a/frontend/src/features/seller/views/SellerListingsView.vue +++ b/frontend/src/features/seller/views/SellerListingsView.vue @@ -10,8 +10,8 @@ import { type Listing, } from '@/features/listings' import { formatCent, formatMoneyWithSymbol } from '@/shared/utils/money' -import { listingReviewStatusLabel, listingStatusLabel } from '@/utils/statusLabels' -import { formatListingCode, getListingSellerPrice } from '@/utils/listingDisplay' +import { listingReviewStatusLabel, listingStatusLabel } from '@/shared/utils/statusLabels' +import { formatListingCode, getListingSellerPrice } from '@/shared/utils/listingDisplay' const loading = ref(false) const submittingID = ref(null) diff --git a/frontend/src/features/wallet/views/WalletView.vue b/frontend/src/features/wallet/views/WalletView.vue index 4661b34..30ca7b2 100644 --- a/frontend/src/features/wallet/views/WalletView.vue +++ b/frontend/src/features/wallet/views/WalletView.vue @@ -23,8 +23,12 @@ import { type WalletLedger, } from '@/features/wallet' import type { PaymentOrder } from '@/features/orders' -import { balanceTypeLabel, ledgerDirectionLabel, walletStatusLabel } from '@/utils/statusLabels' -import { formatDateTime } from '@/utils/time' +import { + balanceTypeLabel, + ledgerDirectionLabel, + walletStatusLabel, +} from '@/shared/utils/statusLabels' +import { formatDateTime } from '@/shared/utils/time' import { formatCentWithSymbol, formatMoney } from '@/shared/utils/money' const router = useRouter() diff --git a/frontend/src/shared/api/client.ts b/frontend/src/shared/api/client.ts index 6cfe703..4da7051 100644 --- a/frontend/src/shared/api/client.ts +++ b/frontend/src/shared/api/client.ts @@ -93,7 +93,7 @@ import { getRefreshToken, setAuthTokens, type AuthScope, -} from '@/utils/authStorage' +} from '@/shared/utils/authStorage' import type { ApiResponse } from '@/shared/types/types' export const apiClient = axios.create({ diff --git a/frontend/src/shared/composables/usePricingCalculator.ts b/frontend/src/shared/composables/usePricingCalculator.ts index 95b0166..a81f1df 100644 --- a/frontend/src/shared/composables/usePricingCalculator.ts +++ b/frontend/src/shared/composables/usePricingCalculator.ts @@ -21,7 +21,7 @@ import { readFinalSaleRatio, roundMoney, roundRatio, -} from '@/utils/pricing' +} from '@/shared/utils/pricing' export function usePricingCalculator(options: { publishOptions: Ref diff --git a/frontend/src/utils/error.ts b/frontend/src/shared/utils/error.ts similarity index 76% rename from frontend/src/utils/error.ts rename to frontend/src/shared/utils/error.ts index b3b824b..ce33ab1 100644 --- a/frontend/src/utils/error.ts +++ b/frontend/src/shared/utils/error.ts @@ -1,8 +1,5 @@ /** - * 从错误对象中提取错误消息 - * @param error 错误对象 - * @param fallback 默认错误消息 - * @returns 错误消息 + * 从错误对象中提取错误消息。 */ export function readError(error: unknown, fallback: string): string { if (typeof error === 'object' && error && 'response' in error) { diff --git a/frontend/src/shared/utils/index.ts b/frontend/src/shared/utils/index.ts index 2e36176..cae13e4 100644 --- a/frontend/src/shared/utils/index.ts +++ b/frontend/src/shared/utils/index.ts @@ -1,5 +1,6 @@ // 通用工具函数 export * from './authStorage' +export * from './error' export * from './imageUpload' export * from './json' export * from './listingDisplay' diff --git a/frontend/src/shared/utils/listingDisplay.ts b/frontend/src/shared/utils/listingDisplay.ts index c6f106b..36eb24e 100644 --- a/frontend/src/shared/utils/listingDisplay.ts +++ b/frontend/src/shared/utils/listingDisplay.ts @@ -306,7 +306,7 @@ function roundMoney(value: number) { function formatRatioNumber(value: number) { const rounded = roundMoney(value) - return rounded.toFixed(1) + return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(2) } function formatCompactNumber(value: number) { diff --git a/frontend/src/stores/adminSession.ts b/frontend/src/stores/adminSession.ts index d722eb8..43ca537 100644 --- a/frontend/src/stores/adminSession.ts +++ b/frontend/src/stores/adminSession.ts @@ -11,7 +11,7 @@ import { getAccessToken, getRefreshToken, setAuthTokens, -} from '@/utils/authStorage' +} from '@/shared/utils/authStorage' export const useAdminSessionStore = defineStore('adminSession', { state: () => ({ diff --git a/frontend/src/stores/session.ts b/frontend/src/stores/session.ts index bbaa0ce..08bf0ca 100644 --- a/frontend/src/stores/session.ts +++ b/frontend/src/stores/session.ts @@ -6,7 +6,7 @@ import { getAccessToken, getRefreshToken, setAuthTokens, -} from '@/utils/authStorage' +} from '@/shared/utils/authStorage' export const useSessionStore = defineStore('session', { state: () => ({ diff --git a/frontend/src/utils/authStorage.ts b/frontend/src/utils/authStorage.ts deleted file mode 100644 index f63a0d0..0000000 --- a/frontend/src/utils/authStorage.ts +++ /dev/null @@ -1,54 +0,0 @@ -export type AuthScope = 'user' | 'admin' - -export interface AuthTokenPair { - access_token: string - refresh_token: string -} - -const userKeys = { - accessToken: 'access_token', - refreshToken: 'refresh_token', - profile: ['user_id', 'phone', 'nickname', 'avatar_url', 'realname_status'], -} - -const adminKeys = { - accessToken: 'admin_access_token', - refreshToken: 'admin_refresh_token', - profile: ['admin_id', 'admin_username'], -} - -function keysFor(scope: AuthScope) { - return scope === 'admin' ? adminKeys : userKeys -} - -export function getAccessToken(scope: AuthScope) { - return localStorage.getItem(keysFor(scope).accessToken) || '' -} - -export function getRefreshToken(scope: AuthScope) { - return localStorage.getItem(keysFor(scope).refreshToken) || '' -} - -export function setAuthTokens(scope: AuthScope, tokens: AuthTokenPair) { - const keys = keysFor(scope) - localStorage.setItem(keys.accessToken, tokens.access_token) - localStorage.setItem(keys.refreshToken, tokens.refresh_token) - notifyAuthStorageChanged(scope) -} - -export function clearAuthStorage(scope: AuthScope) { - const keys = keysFor(scope) - localStorage.removeItem(keys.accessToken) - localStorage.removeItem(keys.refreshToken) - keys.profile.forEach(key => localStorage.removeItem(key)) - notifyAuthStorageChanged(scope) -} - -export function getLoginPath(scope: AuthScope, currentPath: string) { - if (scope === 'admin') return '/admin/login' - return currentPath.startsWith('/m') ? '/m/login' : '/login' -} - -export function notifyAuthStorageChanged(scope: AuthScope) { - window.dispatchEvent(new CustomEvent('auth-storage-changed', { detail: { scope } })) -} diff --git a/frontend/src/utils/imageUpload.ts b/frontend/src/utils/imageUpload.ts deleted file mode 100644 index 3dcf008..0000000 --- a/frontend/src/utils/imageUpload.ts +++ /dev/null @@ -1,84 +0,0 @@ -const IMAGE_UPLOAD_MAX_SIDE: Record = { - avatar: 512, - chat: 1280, - 'home-banner': 1920, - listing: 1920, - dispute: 1920, - handoff: 1920, - realname: 1920, -} - -const IMAGE_UPLOAD_QUALITY: Record = { - avatar: 0.82, - chat: 0.8, - 'home-banner': 0.84, - listing: 0.84, - dispute: 0.86, - handoff: 0.86, - realname: 0.86, -} - -export async function optimizeImageForUpload(file: File, scene: string) { - if (!file.type.startsWith('image/')) return file - if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) return file - if (typeof document === 'undefined') return file - - try { - const image = await loadImage(file) - const maxSide = IMAGE_UPLOAD_MAX_SIDE[scene] || 1600 - const quality = IMAGE_UPLOAD_QUALITY[scene] || 0.82 - const { width, height } = fitSize(image.naturalWidth, image.naturalHeight, maxSide) - const canvas = document.createElement('canvas') - canvas.width = width - canvas.height = height - const context = canvas.getContext('2d') - if (!context) return file - context.fillStyle = '#ffffff' - context.fillRect(0, 0, width, height) - context.drawImage(image, 0, 0, width, height) - const blob = await canvasToBlob(canvas, 'image/webp', quality) - if (!blob || blob.size >= file.size) return file - return new File([blob], replaceFileExt(file.name, 'webp'), { - type: 'image/webp', - lastModified: Date.now(), - }) - } catch { - return file - } -} - -function loadImage(file: File) { - return new Promise((resolve, reject) => { - const url = URL.createObjectURL(file) - const image = new Image() - image.onload = () => { - URL.revokeObjectURL(url) - resolve(image) - } - image.onerror = () => { - URL.revokeObjectURL(url) - reject(new Error('图片读取失败')) - } - image.src = url - }) -} - -function fitSize(width: number, height: number, maxSide: number) { - if (width <= 0 || height <= 0) return { width: 1, height: 1 } - const scale = Math.min(1, maxSide / Math.max(width, height)) - return { - width: Math.max(1, Math.round(width * scale)), - height: Math.max(1, Math.round(height * scale)), - } -} - -function canvasToBlob(canvas: HTMLCanvasElement, type: string, quality: number) { - return new Promise(resolve => { - canvas.toBlob(resolve, type, quality) - }) -} - -function replaceFileExt(filename: string, ext: string) { - const base = filename.replace(/\.[^.]+$/, '') - return `${base || 'image'}.${ext}` -} diff --git a/frontend/src/utils/json.ts b/frontend/src/utils/json.ts deleted file mode 100644 index 5be683f..0000000 --- a/frontend/src/utils/json.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function safeParseJSON(raw: string, fallback: T): T { - if (!raw || !raw.trim()) return fallback - try { - return JSON.parse(raw) as T - } catch { - return fallback - } -} diff --git a/frontend/src/utils/listingDisplay.ts b/frontend/src/utils/listingDisplay.ts deleted file mode 100644 index 36eb24e..0000000 --- a/frontend/src/utils/listingDisplay.ts +++ /dev/null @@ -1,315 +0,0 @@ -import type { Listing } from '@/features/listings/api/listings' -import { centToYuan } from '@/shared/utils/money' - -export interface ListingDisplayChip { - label: string - value: string -} - -export interface ListingDisplayResource { - key: string - label: string - price: string - quantity: number - mode: string - amount: number -} - -export function getCoinWan(item: Listing) { - return Math.round(Number(item.haf_coin_amount || 0) / 10000) -} - -export function getCoinM(item: Listing) { - return getCoinWan(item) / 100 -} - -export function formatListingCode(item: Listing) { - return formatListingNo(item.listing_no, item.id) -} - -export function formatListingNo(listingNo?: string, listingID?: number | string) { - if (listingNo) return listingNo - if (listingID !== undefined && listingID !== null && listingID !== '') { - return `SP${String(listingID).padStart(6, '0')}` - } - return '-' -} - -export function formatHafCoinM(amountWan: number) { - const amountM = amountWan / 100 - const rounded = Math.round(amountM * 10) / 10 - return `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)}M` -} - -export function getListingDisplayPrice(item: Listing) { - return centToYuan(item.price_cent) -} - -export function getListingRentPrice(item: Listing) { - const buyerCoinBasePrice = readPriceBreakdownNumber(item, 'buyer_coin_base_price') - if (buyerCoinBasePrice > 0) return roundMoney(buyerCoinBasePrice) - return Math.max(0, roundMoney(getListingDisplayPrice(item) - getListingConsumablePrice(item))) -} - -export function getListingConsumablePrice(item: Listing) { - const consumablePrice = readPriceBreakdownNumber(item, 'consumable_price') - if (consumablePrice > 0) return roundMoney(consumablePrice) - return getListingResources(item).reduce((sum, resource) => sum + resource.amount, 0) -} - -export function getListingSellerPrice(item: Listing) { - const priceBreakdown = item.asset_summary?.price_breakdown - if (typeof priceBreakdown === 'object' && priceBreakdown !== null) { - const price = readUnknownNumber((priceBreakdown as Record).seller_total_price) - if (price > 0) return price - } - return getListingDisplayPrice(item) -} - -export function getRatioValue(item: Listing) { - const ratio = readAssetNumber(item, 'publish_ratio') - if (ratio > 0) return ratio - const price = getListingDisplayPrice(item) - if (price <= 0) return 0 - return getCoinWan(item) / price -} - -export function formatRatio(item: Listing) { - const ratio = getRatioValue(item) - return ratio > 0 ? `1:${formatRatioNumber(ratio)}` : '--' -} - -export function getValuePerYuanText(item: Listing) { - return formatRatio(item) -} - -export function getLoginMethod(item: Listing) { - return item.login_platform.trim() -} - -export function getServerRegion(item: Listing) { - return item.server_region.trim() -} - -export function getListingTitle(item: Listing) { - const parts = [ - `纯币${formatHafCoinM(getCoinWan(item))}`, - formatInsuranceSlotText(readAssetString(item, 'season_insurance')), - formatLevelShort(readAssetString(item, 'stamina_level'), '体'), - formatLevelShort(readAssetString(item, 'load_level'), '负'), - formatResourceShort(item, 'armor6', '六甲'), - formatResourceShort(item, 'helmet6', '六头'), - ...getSkinNames(item).slice(0, 4), - ].filter(Boolean) - return parts.join('/') -} - -export function getListingSubtitle(item: Listing) { - return getValuePerYuanText(item) -} - -export function getListingChips(item: Listing): ListingDisplayChip[] { - const totalAsset = readAssetNumber(item, 'total_asset_wan') - const chips: ListingDisplayChip[] = [ - { label: '哈夫币', value: formatHafCoinM(getCoinWan(item)) }, - { label: '保险格数', value: readAssetString(item, 'season_insurance') }, - { label: '体力', value: readAssetString(item, 'stamina_level') }, - { label: '负重', value: readAssetString(item, 'load_level') }, - { label: '段位', value: item.rank_level }, - ] - const awmAmmo = getResourceQuantity(item, 'awmAmmo') - if (awmAmmo > 0) { - chips.push({ label: 'AWM', value: `${awmAmmo}发` }) - } - if (totalAsset > 0) { - chips.push({ label: '总资产', value: formatHafCoinM(totalAsset) }) - } - const online = getOnlineTimeText(item) - if (online) { - chips.push({ label: '方便上号', value: online }) - } - return chips.filter(chip => chip.value) -} - -export function getListingResources(item: Listing): ListingDisplayResource[] { - const resources = item.asset_summary?.resources - if (!Array.isArray(resources)) return [] - return resources - .map(resource => { - if (typeof resource !== 'object' || resource === null) return null - const row = resource as Record - return { - key: typeof row.key === 'string' ? row.key : '', - label: typeof row.label === 'string' ? row.label : '', - price: typeof row.price === 'string' ? row.price : '', - quantity: readUnknownNumber(row.quantity), - mode: typeof row.mode === 'string' ? row.mode : '', - amount: - row.mode === '收费' - ? roundMoney( - readUnknownNumber(row.quantity) * - readUnitPrice(typeof row.price === 'string' ? row.price : '') - ) - : 0, - } - }) - .filter((resource): resource is ListingDisplayResource => { - return Boolean(resource?.key && resource.label && resource.quantity > 0) - }) -} - -export function getResourceQuantity(item: Listing, resourceKey: string) { - return getListingResources(item).find(resource => resource.key === resourceKey)?.quantity || 0 -} - -export function hasGiftResources(item: Listing) { - return getListingResources(item).some(resource => resource.mode === '赠送') -} - -export function hasAcceleratedSaleRatio(item: Listing) { - if (item.is_accelerated_sale) return true - - const priceBreakdown = item.asset_summary?.price_breakdown - if (typeof priceBreakdown !== 'object' || priceBreakdown === null) return false - - const row = priceBreakdown as Record - const referenceRatio = readUnknownNumber(row.seller_reference_ratio) - const sellerRatio = readUnknownNumber(row.seller_ratio) - const acceleratedRatio = readUnknownNumber(row.accelerated_sale_ratio) - - if (referenceRatio <= 0) return false - return sellerRatio > referenceRatio || acceleratedRatio > referenceRatio -} - -export function getSkinGroup(item: Listing, groupKey: string) { - const skinGroups = item.asset_summary?.skin_groups - if ( - typeof skinGroups !== 'object' || - skinGroups === null || - !Array.isArray((skinGroups as Record)[groupKey]) - ) { - return [] - } - return ((skinGroups as Record)[groupKey] as unknown[]).filter( - (skin): skin is string => typeof skin === 'string' - ) -} - -export function getSkinNames(item: Listing) { - const skinGroups = item.asset_summary?.skin_groups - if (typeof skinGroups !== 'object' || skinGroups === null) return [] - return Object.values(skinGroups as Record) - .flatMap(group => (Array.isArray(group) ? group : [])) - .filter((skin): skin is string => typeof skin === 'string') -} - -export function assetRegions(item: Listing) { - const regions = item.asset_summary?.common_regions - return Array.isArray(regions) - ? regions.filter((region): region is string => typeof region === 'string') - : [] -} - -export function getOnlineTimeText(item: Listing) { - const onlineTime = item.asset_summary?.online_time - if (typeof onlineTime !== 'object' || onlineTime === null) return '' - const start = (onlineTime as Record).start - const end = (onlineTime as Record).end - if (typeof start !== 'string' || typeof end !== 'string' || !start || !end) return '' - if (start === '全天' || end === '全天' || (start === '00:00' && end === '23:59')) return '全天' - return `${start.replace(':00', '')}-${end.replace(':00', '')}点` -} - -export function getDailyLoss(item: Listing) { - const dailyLossM = getDailyLossM(item) - return dailyLossM > 0 ? `${formatCompactNumber(dailyLossM)}M` : '' -} - -export function getDailyLossM(item: Listing) { - const configuredLoss = readAssetNumber(item, 'daily_loss_m') - if (configuredLoss > 0) return configuredLoss - return 0 -} - -export function getEstimatedRentalDays(item: Listing) { - const coinM = getCoinM(item) - const dailyLossM = getDailyLossM(item) - if (coinM <= 0 || dailyLossM <= 0) return 0 - return coinM / dailyLossM -} - -export function formatEstimatedRentalDuration(item: Listing) { - const days = getEstimatedRentalDays(item) - if (days <= 0) return '--' - return `${Math.max(1, Math.round(days))}天` -} - -export function readAssetString(item: Listing, key: string) { - const value = item.asset_summary?.[key] - return typeof value === 'string' ? value : '' -} - -export function readAssetNumber(item: Listing, key: string) { - return readUnknownNumber(item.asset_summary?.[key]) -} - -function readUnknownNumber(value: unknown) { - if (typeof value === 'number') return value - if (typeof value === 'string' && value.trim()) { - const parsed = Number(value) - return Number.isFinite(parsed) ? parsed : 0 - } - return 0 -} - -function readPriceBreakdownNumber(item: Listing, key: string) { - const priceBreakdown = item.asset_summary?.price_breakdown - if (typeof priceBreakdown !== 'object' || priceBreakdown === null) return 0 - return readUnknownNumber((priceBreakdown as Record)[key]) -} - -function readUnitPrice(priceText: string) { - const normalized = priceText.replace(/,/g, ',').trim() - const fractionMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元\s*\/\s*(\d+(?:\.\d+)?)/) - if (fractionMatch) { - const amount = Number(fractionMatch[1]) - const count = Number(fractionMatch[2]) - return count > 0 ? amount / count : 0 - } - const singleMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元/) - return singleMatch ? Number(singleMatch[1]) : 0 -} - -function formatInsuranceSlotText(value: string) { - const parts = value.split('*').map(item => Number(item)) - const rows = parts[0] || 0 - const cols = parts[1] || 0 - if (!Number.isFinite(rows) || !Number.isFinite(cols) || rows <= 0 || cols <= 0) { - return value - } - return `${rows * cols}格` -} - -function formatLevelShort(value: string, suffix: string) { - const level = value.match(/\d+/)?.[0] - return level ? `${level}${suffix}` : value -} - -function formatResourceShort(item: Listing, key: string, label: string) { - const quantity = getResourceQuantity(item, key) - return quantity > 0 ? `${quantity}${label}` : '' -} - -function roundMoney(value: number) { - return Math.round(value * 100) / 100 -} - -function formatRatioNumber(value: number) { - const rounded = roundMoney(value) - return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(2) -} - -function formatCompactNumber(value: number) { - const rounded = Math.round(value * 10) / 10 - return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(1) -} diff --git a/frontend/src/utils/pricing.ts b/frontend/src/utils/pricing.ts deleted file mode 100644 index f618a49..0000000 --- a/frontend/src/utils/pricing.ts +++ /dev/null @@ -1,327 +0,0 @@ -import type { - ChargeMode, - ListingPublishOptions, - PublishDepositRecommendConfig, - PublishOptionGroup, - PublishQuantityItem, - PublishRatioConfig, - PublishSalePriceConfig, -} from '@/features/listings/api/listingOptions' -import type { DepositBreakdownItem, PublishForm, PublishPlatformPricing } from '@/types/publish' - -export const dailyLossOptions = [10, 20, 30, 40, 50] -export const commonOnlineTimes = [ - '00:00', - '08:00', - '10:00', - '12:00', - '14:00', - '18:00', - '20:00', - '22:00', - '23:59', -] - -export function roundMoney(value: number) { - return Math.round(value * 10) / 10 -} - -export function roundRatio(value: number) { - return Math.round(value * 10) / 10 -} - -export function formatNumber(value: number) { - return (Math.round(value * 10) / 10).toFixed(1) -} - -export function readUnitPrice(priceText: string) { - const normalized = priceText.replace(/,/g, ',').trim() - const fractionMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元\s*\/\s*(\d+(?:\.\d+)?)/) - if (fractionMatch) { - const amount = Number(fractionMatch[1]) - const count = Number(fractionMatch[2]) - return count > 0 ? amount / count : 0 - } - const singleMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元/) - return singleMatch ? Number(singleMatch[1]) : 0 -} - -export function calculateDailyLossRatioAdjustment(dailyLossMAmount: number) { - return Math.min(Math.max(Math.floor((dailyLossMAmount - 10) / 10), 0), 4) -} - -export function isGridCardQuantityItem(item: { key: string; label: string }) { - return item.key === 'gridCard9' || item.label.includes('9格体验卡') -} - -export function isQuantityItemDisabledForInsurance( - item: { key: string; label: string }, - seasonInsurance: string -) { - return seasonInsurance === '3*3' && isGridCardQuantityItem(item) -} - -export function calculateConsumablePrice(options: { - quantityItems: PublishQuantityItem[] - quantityValues: Record - quantityModes: Record - seasonInsurance: string -}) { - const total = options.quantityItems.reduce((sum, item) => { - const quantity = Number(options.quantityValues[item.key] || 0) - const mode = options.quantityModes[item.key] || '收费' - if (isQuantityItemDisabledForInsurance(item, options.seasonInsurance)) return sum - if (quantity <= 0 || mode !== '收费') return sum - return sum + quantity * readUnitPrice(item.price) - }, 0) - return roundMoney(total) -} - -export function calculateRecommendedDeposit(options: { - depositRecommendConfig: PublishDepositRecommendConfig - skinGroups: PublishOptionGroup[] - selectedSkins: string[] -}) { - const baseAmount = Number(options.depositRecommendConfig.base_amount || 0) - const skinAmount = options.depositRecommendConfig.skin_group_rules.reduce((sum, rule) => { - const group = options.skinGroups.find(item => item.key === rule.group_key) - if (!group) return sum - const selectedCount = group.options.filter(skin => options.selectedSkins.includes(skin)).length - return sum + selectedCount * Number(rule.amount_per_item || 0) - }, 0) - return roundMoney(baseAmount + skinAmount) -} - -export function buildDepositBreakdownItems(options: { - depositRecommendConfig: PublishDepositRecommendConfig - skinGroups: PublishOptionGroup[] - selectedSkins: string[] -}): DepositBreakdownItem[] { - const items: DepositBreakdownItem[] = [ - { - label: '基础押金', - amount: Number(options.depositRecommendConfig.base_amount || 0), - count: 1, - }, - ] - for (const rule of options.depositRecommendConfig.skin_group_rules) { - const group = options.skinGroups.find(item => item.key === rule.group_key) - if (!group) continue - const count = group.options.filter(skin => options.selectedSkins.includes(skin)).length - if (count <= 0) continue - items.push({ - label: rule.label, - amount: Number(rule.amount_per_item || 0) * count, - count, - }) - } - return items -} - -export function calculateSellerReferenceRatio(options: { - coinMAmount: number - form: PublishForm - ratioConfig: PublishRatioConfig - skinGroups: PublishOptionGroup[] - selectedSkins: string[] - levelOptions: string[] - dailyLossRatioAdjustment: number -}) { - const { coinMAmount, form, ratioConfig } = options - if (coinMAmount <= 0 || !form.season_insurance || !form.stamina_level || !form.load_level) - return 0 - const baseRatio = getInsuranceBaseRatio(ratioConfig, form.season_insurance) - if (baseRatio <= 0) return 0 - return ( - baseRatio + - calculateConfigPenalty(ratioConfig, options) + - getCoinCorrection(ratioConfig, coinMAmount) + - options.dailyLossRatioAdjustment - ) -} - -export function readFinalSaleRatio( - defaultRatio: number, - acceleratedSaleRatio: number | '', - maxAcceleratedSaleRatio: number -) { - if (defaultRatio <= 0) return 0 - if (!hasAcceleratedSaleRatioInput(acceleratedSaleRatio)) return defaultRatio - const ratio = Number(acceleratedSaleRatio) - if (!Number.isFinite(ratio) || ratio <= 0) return defaultRatio - return roundRatio(Math.min(Math.max(ratio, defaultRatio), maxAcceleratedSaleRatio)) -} - -export function hasAcceleratedSaleRatioInput(value: number | '') { - return value !== '' && value !== null -} - -export function calculatePlatformPricing(options: { - coinMAmount: number - coinWanAmount: number - sellerRatio: number - sellerCoinBasePrice: number - sellerTotalPrice: number - consumablePrice: number - salePriceConfig: PublishSalePriceConfig -}): PublishPlatformPricing { - if (options.sellerRatio <= 0 || options.sellerCoinBasePrice <= 0) return emptyPlatformPricing() - const fixedRule = findSaleFixedMarkupRule(options.salePriceConfig, options.coinMAmount) - if (fixedRule) { - return buildPlatformPricing( - roundMoney(options.sellerCoinBasePrice + Number(fixedRule.markup_amount || 0)), - 'fixed_markup', - options - ) - } - const ratioRule = findSaleRatioAdjustmentRule(options.salePriceConfig, options.coinMAmount) - const ratioSubtract = ratioRule ? Number(ratioRule.ratio_subtract || 0) : 0 - const buyerRatio = options.sellerRatio - ratioSubtract - if (buyerRatio > 0 && ratioRule) { - return buildPlatformPricing( - roundMoney(options.coinWanAmount / buyerRatio), - 'ratio_subtract', - options - ) - } - return buildPlatformPricing(options.sellerCoinBasePrice, 'none', options) -} - -export function emptyPlatformPricing(): PublishPlatformPricing { - return { - buyerCoinBasePrice: 0, - buyerTotalPrice: 0, - buyerRatio: 0, - platformMarkupAmount: 0, - ruleType: 'none', - } -} - -function buildPlatformPricing( - buyerCoinBasePrice: number, - ruleType: string, - options: { - coinWanAmount: number - sellerTotalPrice: number - consumablePrice: number - } -): PublishPlatformPricing { - const buyerTotalPrice = roundMoney(buyerCoinBasePrice + options.consumablePrice) - return { - buyerCoinBasePrice, - buyerTotalPrice, - buyerRatio: calculateEffectiveRatio(options.coinWanAmount, buyerCoinBasePrice), - platformMarkupAmount: roundMoney(buyerTotalPrice - options.sellerTotalPrice), - ruleType, - } -} - -function findSaleFixedMarkupRule(config: PublishSalePriceConfig, coinMAmount: number) { - return [...config.fixed_markup_rules] - .sort((a, b) => a.min_m - b.min_m) - .find((item, index, rules) => - isCoinInSaleRange(item, index, rules, coinMAmount, { includeLastMax: true }) - ) -} - -function findSaleRatioAdjustmentRule(config: PublishSalePriceConfig, coinMAmount: number) { - return [...config.ratio_adjustment_rules] - .sort((a, b) => a.min_m - b.min_m) - .find((item, index, rules) => - isCoinInSaleRange(item, index, rules, coinMAmount, { excludeFirstMin: true }) - ) -} - -function isCoinInSaleRange( - item: { min_m: number; max_m: number }, - index: number, - rules: Array<{ min_m: number; max_m: number }>, - coinMAmount: number, - options: { includeLastMax?: boolean; excludeFirstMin?: boolean } = {} -) { - const maxM = Number(item.max_m || 0) - const minM = Number(item.min_m || 0) - const minMatched = - options.excludeFirstMin && index === 0 ? coinMAmount > minM : coinMAmount >= minM - const isLastRule = index === rules.length - 1 - const maxMatched = - maxM <= 0 || coinMAmount < maxM || (options.includeLastMax && isLastRule && coinMAmount <= maxM) - return minMatched && maxMatched -} - -function calculateEffectiveRatio(coinWanAmount: number, price: number) { - if (price <= 0) return 0 - return roundRatio(coinWanAmount / price) -} - -function getInsuranceBaseRatio( - config: Pick, - insurance: string -) { - return config.insurance_base_ratios.find(item => item.insurance === insurance)?.ratio || 0 -} - -function calculateConfigPenalty( - config: Pick, - options: { - form: PublishForm - skinGroups: PublishOptionGroup[] - selectedSkins: string[] - levelOptions: string[] - } -) { - return config.config_items.reduce((sum, item) => { - return isRatioConfigItemMatched(item, options) ? sum : sum + Number(item.missing_penalty || 0) - }, 0) -} - -function isRatioConfigItemMatched( - item: { kind: string; group_key?: string }, - options: { - form: PublishForm - skinGroups: PublishOptionGroup[] - selectedSkins: string[] - levelOptions: string[] - } -) { - if (item.kind === 'skin_group') return hasSelectedSkinGroup(item.group_key || '', options) - if (item.kind === 'max_stamina') - return isMaxLevel(options.form.stamina_level, options.levelOptions) - if (item.kind === 'max_load') return isMaxLevel(options.form.load_level, options.levelOptions) - return false -} - -function hasSelectedSkinGroup( - groupKey: string, - options: { - skinGroups: PublishOptionGroup[] - selectedSkins: string[] - } -) { - const group = options.skinGroups.find(item => item.key === groupKey) - if (!group) return false - return group.options.some(skin => options.selectedSkins.includes(skin)) -} - -function isMaxLevel(value: string, levelOptions: string[]) { - const currentLevel = readLevelNumber(value) - const maxLevel = Math.max(...levelOptions.map(readLevelNumber).filter(Boolean)) - if (currentLevel > 0 && maxLevel > 0) return currentLevel >= maxLevel - return value === levelOptions[levelOptions.length - 1] -} - -function readLevelNumber(value: string) { - const match = value.match(/\d+/) - return match ? Number(match[0]) : 0 -} - -function getCoinCorrection( - config: Pick, - coinM: number -) { - return ( - [...config.coin_corrections] - .sort((a, b) => b.threshold_m - a.threshold_m) - .find(item => coinM > item.threshold_m)?.correction || 0 - ) -} diff --git a/frontend/src/utils/statusLabels.ts b/frontend/src/utils/statusLabels.ts deleted file mode 100644 index adfa8d9..0000000 --- a/frontend/src/utils/statusLabels.ts +++ /dev/null @@ -1,175 +0,0 @@ -import type { - BalanceType, - DisputeStatus, - HandoffStatus, - LedgerDirection, - ListingReviewStatus, - ListingStatus, - OrderStatus, - RealnameStatusValue, - RiskStatus, - SettlementStatus, - UserStatus, - WalletStatus, -} from '@/types/status' - -const listingStatusMap: Record = { - draft: '草稿', - published: '已上架', - rented: '租用中', - offline: '已下架', - abnormal: '异常', -} - -const listingReviewStatusMap: Record = { - none: '未提交', - pending: '待审核', - approved: '已通过', - rejected: '已拒绝', -} - -const orderStatusMap: Record = { - pending_confirm: '待确认', - pending_payment: '待支付', - pending_handoff: '待交接', - renting: '使用中', - overdue: '已逾期', - pending_return_confirm: '待结账确认', - pending_checkout_confirm: '待号主确认结账', - pending_checkout_accept: '待租客确认修正', - checkout_disputing: '结账争议中', - completed: '已完成', - cancelled: '已取消', - closed: '已关闭', - disputing: '申诉中', - abnormal: '异常', -} - -const handoffStatusMap: Record = { - pending_owner: '待号主交接', - pending_renter_confirm: '待租客确认', - received: '已确认收号', - pending_owner_return_confirm: '待号主确认结账', - pending_owner_checkout: '待号主确认结账', - pending_renter_checkout: '待租客确认修正', - checkout_disputed: '结账争议中', - returned: '已归还', - cancelled: '已取消', - owner_timeout: '号主交接超时', - renter_confirm_timeout: '租客确认超时', - return_overdue: '归还逾期', - owner_return_confirm_timeout: '号主确认结账超时', - owner_checkout_confirm_timeout: '号主确认结账超时', - admin_closed: '客服关闭', - admin_abnormal: '客服标记异常', - arbitrated: '已仲裁', -} - -const settlementStatusMap: Record = { - unsettled: '未结算', - pending: '待结算', - frozen: '冻结中', - settled: '已结算', - refunded: '已退款', - cancelled: '已取消', - closed: '已关闭', - disputed: '争议中', - arbitrated: '已仲裁', -} - -const realnameStatusMap: Partial> = { - unverified: '未认证', - pending: '认证中', - verified: '已认证', - rejected: '认证失败', -} - -const userStatusMap: Record = { - active: '正常', - frozen: '已冻结', - disabled: '已禁用', -} - -const riskStatusMap: Record = { - normal: '正常', - watch: '观察', - restricted: '受限', - blocked: '已拦截', -} - -const disputeStatusMap: Record = { - open: '待处理', - processing: '处理中', - resolved: '已处理', - closed: '已关闭', -} - -const walletStatusMap: Record = { - active: '正常', - frozen: '已冻结', - disabled: '已禁用', -} - -const ledgerDirectionMap: Record = { - in: '收入', - out: '支出', - freeze: '冻结', - unfreeze: '解冻', -} - -const balanceTypeMap: Record = { - available: '可用余额', - frozen: '冻结余额', -} - -function readLabel(map: Record, value: string) { - return map[value] || value || '-' -} - -export function listingStatusLabel(status: string) { - return readLabel(listingStatusMap, status) -} - -export function listingReviewStatusLabel(status: string) { - return readLabel(listingReviewStatusMap, status) -} - -export function orderStatusLabel(status: string) { - return readLabel(orderStatusMap, status) -} - -export function handoffStatusLabel(status: string) { - return readLabel(handoffStatusMap, status) -} - -export function settlementStatusLabel(status: string) { - return readLabel(settlementStatusMap, status) -} - -export function realnameStatusLabel(status: string) { - return readLabel(realnameStatusMap, status) -} - -export function userStatusLabel(status: string) { - return readLabel(userStatusMap, status) -} - -export function riskStatusLabel(status: string) { - return readLabel(riskStatusMap, status) -} - -export function disputeStatusLabel(status: string) { - return readLabel(disputeStatusMap, status) -} - -export function walletStatusLabel(status: string) { - return readLabel(walletStatusMap, status) -} - -export function ledgerDirectionLabel(direction: string) { - return readLabel(ledgerDirectionMap, direction) -} - -export function balanceTypeLabel(type: string) { - return readLabel(balanceTypeMap, type) -} diff --git a/frontend/src/utils/systemConfigOptions.ts b/frontend/src/utils/systemConfigOptions.ts deleted file mode 100644 index 4ebd6cb..0000000 --- a/frontend/src/utils/systemConfigOptions.ts +++ /dev/null @@ -1,65 +0,0 @@ -export interface SystemConfigOption { - label: string - value: string -} - -const shortTimeoutOptions: SystemConfigOption[] = [ - { label: '不限时', value: '0' }, - { label: '5 分钟', value: '5' }, - { label: '10 分钟', value: '10' }, - { label: '15 分钟', value: '15' }, - { label: '30 分钟', value: '30' }, - { label: '45 分钟', value: '45' }, - { label: '60 分钟', value: '60' }, - { label: '90 分钟', value: '90' }, - { label: '120 分钟', value: '120' }, -] - -const longTimeoutOptions: SystemConfigOption[] = [ - { label: '不限时', value: '0' }, - { label: '30 分钟', value: '30' }, - { label: '60 分钟', value: '60' }, - { label: '120 分钟', value: '120' }, - { label: '180 分钟', value: '180' }, - { label: '240 分钟', value: '240' }, - { label: '360 分钟', value: '360' }, - { label: '12 小时', value: '720' }, - { label: '24 小时', value: '1440' }, - { label: '48 小时', value: '2880' }, -] - -const booleanOptions: SystemConfigOption[] = [ - { label: '开启', value: 'true' }, - { label: '关闭', value: 'false' }, -] - -export const systemConfigSelectOptions: Record = { - 'handoff.owner_submit_timeout_minutes': shortTimeoutOptions, - 'handoff.renter_confirm_timeout_minutes': shortTimeoutOptions, - 'handoff.owner_return_confirm_timeout_minutes': longTimeoutOptions, - 'order.pending_payment_timeout_minutes': shortTimeoutOptions, - 'order.return_overdue_grace_minutes': [ - { label: '无宽限', value: '0' }, - { label: '5 分钟', value: '5' }, - { label: '10 分钟', value: '10' }, - { label: '15 分钟', value: '15' }, - { label: '30 分钟', value: '30' }, - { label: '60 分钟', value: '60' }, - { label: '120 分钟', value: '120' }, - ], - 'listing.review_required': booleanOptions, - 'chat.default_support_admin_id': [ - { label: '管理员 ID 1', value: '1' }, - { label: '管理员 ID 2', value: '2' }, - { label: '管理员 ID 3', value: '3' }, - ], -} - -export function getSystemConfigSelectOptions(key: string) { - return systemConfigSelectOptions[key] || null -} - -export function formatSystemConfigSelectValue(key: string, value: string) { - const option = getSystemConfigSelectOptions(key)?.find(item => item.value === value) - return option?.label || null -} diff --git a/frontend/src/utils/time.ts b/frontend/src/utils/time.ts deleted file mode 100644 index cc0bc4f..0000000 --- a/frontend/src/utils/time.ts +++ /dev/null @@ -1,22 +0,0 @@ -type DateInput = string | number | Date | null | undefined - -export function formatDateTime(value: DateInput, fallback = '-') { - if (!value) return fallback - const date = value instanceof Date ? value : new Date(value) - if (Number.isNaN(date.getTime())) { - return typeof value === 'string' && value.trim() ? value.replace('T', ' ') : fallback - } - return ( - [date.getFullYear(), pad(date.getMonth() + 1), pad(date.getDate())].join('-') + - ` ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` - ) -} - -export function formatDateMinute(value: DateInput, fallback = '-') { - const formatted = formatDateTime(value, fallback) - return formatted === fallback ? fallback : formatted.slice(0, 16) -} - -function pad(value: number) { - return String(value).padStart(2, '0') -}