支持配置后台页面入口
This commit is contained in:
@@ -19,6 +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 { adminPath } from '@/shared/utils/adminPath'
|
||||
import { formatDateMinute } from '@/shared/utils/time'
|
||||
import TransferDialog from '../components/TransferDialog.vue'
|
||||
import QuickReplyDialog from '../components/QuickReplyDialog.vue'
|
||||
@@ -407,7 +408,7 @@ function getSupportName(item: ChatConversation) {
|
||||
</div>
|
||||
<div class="head-actions">
|
||||
<el-button size="small" @click="transferVisible = true">转接</el-button>
|
||||
<RouterLink v-if="active.order_id" :to="`/admin/orders/${active.order_id}`">
|
||||
<RouterLink v-if="active.order_id" :to="adminPath(`orders/${active.order_id}`)">
|
||||
<el-button size="small">查看订单</el-button>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { fetchAdminDashboard, type AdminDashboard } from '@/features/admin/api/adminDashboard'
|
||||
import { useAdminTable } from '@/features/admin/composables/useAdminTable'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { disputeStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
|
||||
@@ -121,7 +122,7 @@ const {
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="pending-list">
|
||||
<RouterLink class="pending-row" to="/admin/disputes">
|
||||
<RouterLink class="pending-row" :to="adminPath('disputes')">
|
||||
<div class="pending-info">
|
||||
<span class="pending-label">待仲裁申诉</span>
|
||||
<span class="pending-desc">需要处理的用户争议</span>
|
||||
@@ -130,7 +131,7 @@ const {
|
||||
dashboard.pending.disputes
|
||||
}}</strong>
|
||||
</RouterLink>
|
||||
<RouterLink class="pending-row" to="/admin/listings/review">
|
||||
<RouterLink class="pending-row" :to="adminPath('listings/review')">
|
||||
<div class="pending-info">
|
||||
<span class="pending-label">待审核商品</span>
|
||||
<span class="pending-desc">新提交的商品审核</span>
|
||||
@@ -168,27 +169,27 @@ const {
|
||||
</h2>
|
||||
</div>
|
||||
<div class="quick-links">
|
||||
<RouterLink class="quick-link" to="/admin/users">
|
||||
<RouterLink class="quick-link" :to="adminPath('users')">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>用户管理</span>
|
||||
</RouterLink>
|
||||
<RouterLink class="quick-link" to="/admin/orders">
|
||||
<RouterLink class="quick-link" :to="adminPath('orders')">
|
||||
<el-icon><Tickets /></el-icon>
|
||||
<span>订单管理</span>
|
||||
</RouterLink>
|
||||
<RouterLink class="quick-link" to="/admin/disputes">
|
||||
<RouterLink class="quick-link" :to="adminPath('disputes')">
|
||||
<el-icon><ScaleToOriginal /></el-icon>
|
||||
<span>仲裁中心</span>
|
||||
</RouterLink>
|
||||
<RouterLink class="quick-link" to="/admin/listings">
|
||||
<RouterLink class="quick-link" :to="adminPath('listings')">
|
||||
<el-icon><Shop /></el-icon>
|
||||
<span>商品管理</span>
|
||||
</RouterLink>
|
||||
<RouterLink class="quick-link" to="/admin/chats">
|
||||
<RouterLink class="quick-link" :to="adminPath('chats')">
|
||||
<el-icon><ChatDotRound /></el-icon>
|
||||
<span>客服群聊</span>
|
||||
</RouterLink>
|
||||
<RouterLink class="quick-link" to="/admin/system-configs">
|
||||
<RouterLink class="quick-link" :to="adminPath('system-configs')">
|
||||
<el-icon><Operation /></el-icon>
|
||||
<span>系统配置</span>
|
||||
</RouterLink>
|
||||
@@ -259,7 +260,7 @@ const {
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default>
|
||||
<RouterLink to="/admin/disputes">
|
||||
<RouterLink :to="adminPath('disputes')">
|
||||
<el-button size="small" type="primary" link>处理</el-button>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Search } from '@element-plus/icons-vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { fetchFinanceDetails, type FinanceDetail } from '@/features/admin/api/adminFinance'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import { orderStatusLabel } from '@/shared/utils/statusLabels'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
@@ -197,7 +198,7 @@ function formatInputDate(date: Date) {
|
||||
<div v-if="details.length > 0" class="finance-grid-body">
|
||||
<div v-for="row in details" :key="row.order_id" class="finance-grid-row">
|
||||
<div class="order-cell">
|
||||
<RouterLink class="order-no" :to="`/admin/orders/${row.order_id}`">
|
||||
<RouterLink class="order-no" :to="adminPath(`orders/${row.order_id}`)">
|
||||
{{ row.order_no }}
|
||||
</RouterLink>
|
||||
<span class="cell-muted">结算 {{ shortDateTime(row.settled_at) }}</span>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { fetchAdminFileBlob } from '@/shared/api/files'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import {
|
||||
adminMarkListingAbnormal,
|
||||
@@ -117,7 +118,7 @@ async function openScreenshot(url: string) {
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button :icon="CopyDocument" @click="copyListingCode">复制编号</el-button>
|
||||
<RouterLink to="/admin/listings">
|
||||
<RouterLink :to="adminPath('listings')">
|
||||
<el-button>返回列表</el-button>
|
||||
</RouterLink>
|
||||
<el-button type="warning" :disabled="!canOperate" @click="openAction('offline')"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
|
||||
import { fetchAdminFileBlob } from '@/shared/api/files'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { centToYuan, formatCent, formatMoneyWithSymbol, yuanToCent } from '@/shared/utils/money'
|
||||
import {
|
||||
adjustListingReviewPrice,
|
||||
@@ -667,7 +668,7 @@ async function openScreenshot(url: string) {
|
||||
<h2>{{ selectedListing.title }}</h2>
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<RouterLink :to="`/admin/listings/${selectedListing.id}`">
|
||||
<RouterLink :to="adminPath(`listings/${selectedListing.id}`)">
|
||||
<el-button>详情页</el-button>
|
||||
</RouterLink>
|
||||
<el-button
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type Listing,
|
||||
} from '@/features/listings'
|
||||
import { useAdminTable } from '@/features/admin/composables/useAdminTable'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import {
|
||||
assetRegions,
|
||||
@@ -558,7 +559,7 @@ function formatQuantity(value: number) {
|
||||
</el-table-column>
|
||||
<el-table-column label="详情" width="60">
|
||||
<template #default="{ row }">
|
||||
<RouterLink :to="`/admin/listings/${row.id}`">
|
||||
<RouterLink :to="adminPath(`listings/${row.id}`)">
|
||||
<el-button size="small">详情</el-button>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { fetchAdminCaptcha, type AdminCaptcha } from '@/features/admin'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
import { ADMIN_DASHBOARD_PATH, isAdminPath } from '@/shared/utils/adminPath'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -45,7 +46,7 @@ async function handleLogin() {
|
||||
)
|
||||
ElMessage.success('后台登录成功')
|
||||
const redirect = typeof route.query.redirect === 'string' ? route.query.redirect : ''
|
||||
await router.push(redirect.startsWith('/admin') ? redirect : '/admin/dashboard')
|
||||
await router.push(isAdminPath(redirect) ? redirect : ADMIN_DASHBOARD_PATH)
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '后台登录失败'))
|
||||
await loadCaptcha()
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type RefundStatus,
|
||||
} from '@/features/orders'
|
||||
import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { centToYuan, formatCentWithSymbol, formatMoney } from '@/shared/utils/money'
|
||||
import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
@@ -195,7 +196,7 @@ function formatHandoffRecordType(type: string) {
|
||||
</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<RouterLink to="/admin/orders">
|
||||
<RouterLink :to="adminPath('orders')">
|
||||
<el-button>返回列表</el-button>
|
||||
</RouterLink>
|
||||
<el-button type="warning" :disabled="!canOperate" @click="openAction('abnormal')"
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { centToYuan, formatMoney } from '@/shared/utils/money'
|
||||
import { formatListingNo } from '@/shared/utils/listingDisplay'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
||||
|
||||
const status = ref('')
|
||||
@@ -109,7 +110,7 @@ function money(value: unknown) {
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<RouterLink :to="`/admin/orders/${row.id}`">
|
||||
<RouterLink :to="adminPath(`orders/${row.id}`)">
|
||||
<el-button size="small">详情</el-button>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Document, Search } from '@element-plus/icons-vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
||||
@@ -196,7 +197,7 @@ function jsonText(value: unknown) {
|
||||
<el-table-column prop="payment_no" label="支付单号" min-width="220" />
|
||||
<el-table-column label="订单" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<RouterLink v-if="row.order_id" :to="`/admin/orders/${row.order_id}`">{{
|
||||
<RouterLink v-if="row.order_id" :to="adminPath(`orders/${row.order_id}`)">{{
|
||||
row.order_no || row.order_id
|
||||
}}</RouterLink>
|
||||
<span v-else>-</span>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Search } from '@element-plus/icons-vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { fetchAdminWalletLedger, type AdminWalletLedger } from '@/features/admin/api/adminWallet'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import { balanceTypeLabel, ledgerDirectionLabel } from '@/shared/utils/statusLabels'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
@@ -136,7 +137,7 @@ function directionLabel(direction: string) {
|
||||
</el-table-column>
|
||||
<el-table-column label="订单" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<RouterLink v-if="row.order_id" :to="`/admin/orders/${row.order_id}`">{{
|
||||
<RouterLink v-if="row.order_id" :to="adminPath(`orders/${row.order_id}`)">{{
|
||||
row.order_no || row.order_id
|
||||
}}</RouterLink>
|
||||
<span v-else>-</span>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { ChatEvent } from './useChatSSE'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { debugError } from '@/shared/utils/debug'
|
||||
|
||||
export type NotificationPermission = 'default' | 'granted' | 'denied'
|
||||
@@ -109,7 +110,7 @@ export function useDesktopNotification(scope: 'user' | 'admin') {
|
||||
window.focus()
|
||||
const path =
|
||||
scope === 'admin'
|
||||
? `/admin/chats/${event.conversation_id}`
|
||||
? adminPath(`chats/${event.conversation_id}`)
|
||||
: `/messages/${event.conversation_id}`
|
||||
router.push(path)
|
||||
notification.close()
|
||||
|
||||
@@ -32,6 +32,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { changeAdminPassword, logoutAdmin, updateSupportStatus } from '@/features/admin'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
import { adminPath, ADMIN_DASHBOARD_PATH, ADMIN_LOGIN_PATH } from '@/shared/utils/adminPath'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -66,7 +67,7 @@ const allNavGroups: NavGroup[] = [
|
||||
index: 'workspace',
|
||||
icon: House,
|
||||
children: [
|
||||
{ label: '仪表盘', to: '/admin/dashboard', icon: DataLine, permission: 'dashboard:view' },
|
||||
{ label: '仪表盘', to: ADMIN_DASHBOARD_PATH, icon: DataLine, permission: 'dashboard:view' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -74,12 +75,12 @@ const allNavGroups: NavGroup[] = [
|
||||
index: 'business',
|
||||
icon: Briefcase,
|
||||
children: [
|
||||
{ label: '用户管理', to: '/admin/users', icon: User, permission: 'user:view' },
|
||||
{ label: '订单管理', to: '/admin/orders', icon: Tickets, permission: 'order:view' },
|
||||
{ label: '商品管理', to: '/admin/listings', icon: Shop, permission: 'listing:view' },
|
||||
{ label: '用户管理', to: adminPath('users'), icon: User, permission: 'user:view' },
|
||||
{ label: '订单管理', to: adminPath('orders'), icon: Tickets, permission: 'order:view' },
|
||||
{ label: '商品管理', to: adminPath('listings'), icon: Shop, permission: 'listing:view' },
|
||||
{
|
||||
label: '商品审核',
|
||||
to: '/admin/listings/review',
|
||||
to: adminPath('listings/review'),
|
||||
icon: DocumentChecked,
|
||||
permission: 'listing:approve',
|
||||
},
|
||||
@@ -92,11 +93,11 @@ const allNavGroups: NavGroup[] = [
|
||||
children: [
|
||||
{
|
||||
label: '仲裁中心',
|
||||
to: '/admin/disputes',
|
||||
to: adminPath('disputes'),
|
||||
icon: ScaleToOriginal,
|
||||
permission: 'dispute:view',
|
||||
},
|
||||
{ label: '客服群聊', to: '/admin/chats', icon: ChatDotRound, permission: 'chat:view' },
|
||||
{ label: '客服群聊', to: adminPath('chats'), icon: ChatDotRound, permission: 'chat:view' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -106,22 +107,22 @@ const allNavGroups: NavGroup[] = [
|
||||
children: [
|
||||
{
|
||||
label: '财务仪表盘',
|
||||
to: '/admin/finance/dashboard',
|
||||
to: adminPath('finance/dashboard'),
|
||||
icon: DataLine,
|
||||
permission: 'wallet:view',
|
||||
},
|
||||
{
|
||||
label: '财务明细',
|
||||
to: '/admin/finance/details',
|
||||
to: adminPath('finance/details'),
|
||||
icon: Document,
|
||||
permission: 'wallet:view',
|
||||
},
|
||||
{ label: '资金流水', to: '/admin/wallet-ledger', icon: Wallet, permission: 'wallet:view' },
|
||||
{ label: '支付流水', to: '/admin/payments', icon: CreditCard, permission: 'wallet:view' },
|
||||
{ label: '提现审核', to: '/admin/withdrawals', icon: Money, permission: 'withdrawal:list' },
|
||||
{ label: '资金流水', to: adminPath('wallet-ledger'), icon: Wallet, permission: 'wallet:view' },
|
||||
{ label: '支付流水', to: adminPath('payments'), icon: CreditCard, permission: 'wallet:view' },
|
||||
{ label: '提现审核', to: adminPath('withdrawals'), icon: Money, permission: 'withdrawal:list' },
|
||||
{
|
||||
label: '支付配置',
|
||||
to: '/admin/payment-configs',
|
||||
to: adminPath('payment-configs'),
|
||||
icon: CreditCard,
|
||||
permission: 'payment_config:list',
|
||||
},
|
||||
@@ -134,13 +135,13 @@ const allNavGroups: NavGroup[] = [
|
||||
children: [
|
||||
{
|
||||
label: '公告管理',
|
||||
to: '/admin/announcements',
|
||||
to: adminPath('announcements'),
|
||||
icon: Bell,
|
||||
permission: 'announcement:view',
|
||||
},
|
||||
{
|
||||
label: '系统配置',
|
||||
to: '/admin/system-configs',
|
||||
to: adminPath('system-configs'),
|
||||
icon: Operation,
|
||||
permission: 'system_config:view',
|
||||
},
|
||||
@@ -151,14 +152,14 @@ const allNavGroups: NavGroup[] = [
|
||||
index: 'system-auth',
|
||||
icon: Lock,
|
||||
children: [
|
||||
{ label: '审计日志', to: '/admin/audit-logs', icon: Document, permission: 'audit_log:view' },
|
||||
{ label: '审计日志', to: adminPath('audit-logs'), icon: Document, permission: 'audit_log:view' },
|
||||
{
|
||||
label: '管理员管理',
|
||||
to: '/admin/admin-users',
|
||||
to: adminPath('admin-users'),
|
||||
icon: UserFilled,
|
||||
permission: 'admin_user:manage',
|
||||
},
|
||||
{ label: '角色管理', to: '/admin/roles', icon: Setting, permission: 'role:manage' },
|
||||
{ label: '角色管理', to: adminPath('roles'), icon: Setting, permission: 'role:manage' },
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -236,7 +237,7 @@ async function handleLogout() {
|
||||
}
|
||||
adminSession.logout()
|
||||
ElMessage.success('已退出后台')
|
||||
await router.push('/admin/login')
|
||||
await router.push(ADMIN_LOGIN_PATH)
|
||||
}
|
||||
|
||||
function passwordStrongEnough(value: string) {
|
||||
@@ -264,7 +265,7 @@ async function handleForcedPasswordChange() {
|
||||
})
|
||||
ElMessage.success('密码已修改,请重新登录')
|
||||
adminSession.logout()
|
||||
await router.replace('/admin/login')
|
||||
await router.replace(ADMIN_LOGIN_PATH)
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '密码修改失败'))
|
||||
} finally {
|
||||
@@ -277,7 +278,7 @@ async function handleForcedPasswordChange() {
|
||||
<div class="admin-shell" :class="{ 'admin-shell--collapsed': isCollapsed }">
|
||||
<aside class="admin-sidebar">
|
||||
<div class="admin-sidebar-header">
|
||||
<RouterLink class="admin-brand" to="/admin/dashboard">
|
||||
<RouterLink class="admin-brand" :to="ADMIN_DASHBOARD_PATH">
|
||||
<span class="brand-mark">H</span>
|
||||
<span class="brand-text" v-show="!isCollapsed">哈夫币后台</span>
|
||||
</RouterLink>
|
||||
@@ -318,7 +319,7 @@ async function handleForcedPasswordChange() {
|
||||
<header class="admin-topbar">
|
||||
<div class="topbar-left">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item :to="{ path: '/admin/dashboard' }">首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item :to="{ path: ADMIN_DASHBOARD_PATH }">首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-if="route.meta.title">{{ route.meta.title }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
|
||||
@@ -1,131 +1,133 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
import { adminPath, ADMIN_DASHBOARD_PATH } from '@/shared/utils/adminPath'
|
||||
|
||||
const adminMeta = { layout: 'admin', requiresAdmin: true }
|
||||
|
||||
export const adminRoutes: RouteRecordRaw[] = [
|
||||
{ path: '/admin', redirect: '/admin/dashboard' },
|
||||
{ path: adminPath(), redirect: ADMIN_DASHBOARD_PATH },
|
||||
{
|
||||
path: '/admin/login',
|
||||
path: adminPath('login'),
|
||||
name: 'admin-login',
|
||||
component: () => import('@/features/admin/views/AdminLoginView.vue'),
|
||||
meta: { layout: 'blank' },
|
||||
},
|
||||
{
|
||||
path: '/admin/dashboard',
|
||||
path: adminPath('dashboard'),
|
||||
name: 'admin-dashboard',
|
||||
component: () => import('@/features/admin/views/AdminDashboardView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/users',
|
||||
path: adminPath('users'),
|
||||
name: 'admin-users',
|
||||
component: () => import('@/features/admin/views/AdminUsersView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/orders',
|
||||
path: adminPath('orders'),
|
||||
name: 'admin-orders',
|
||||
component: () => import('@/features/admin/views/AdminOrdersView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/orders/:id',
|
||||
path: adminPath('orders/:id'),
|
||||
name: 'admin-order-detail',
|
||||
component: () => import('@/features/admin/views/AdminOrderDetailView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/listings',
|
||||
path: adminPath('listings'),
|
||||
name: 'admin-listings',
|
||||
component: () => import('@/features/admin/views/AdminListingsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/listings/:id',
|
||||
path: adminPath('listings/:id'),
|
||||
name: 'admin-listing-detail',
|
||||
component: () => import('@/features/admin/views/AdminListingDetailView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/listings/review',
|
||||
path: adminPath('listings/review'),
|
||||
name: 'admin-listing-review',
|
||||
component: () => import('@/features/admin/views/AdminListingReviewView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/disputes',
|
||||
path: adminPath('disputes'),
|
||||
name: 'admin-disputes',
|
||||
component: () => import('@/features/admin/views/AdminDisputesView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/chats',
|
||||
path: adminPath('chats'),
|
||||
name: 'admin-chats',
|
||||
component: () => import('@/features/admin/views/AdminChatsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/finance/dashboard',
|
||||
path: adminPath('finance/dashboard'),
|
||||
name: 'admin-finance-dashboard',
|
||||
component: () => import('@/features/admin/views/AdminFinanceDashboardView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/finance/details',
|
||||
path: adminPath('finance/details'),
|
||||
name: 'admin-finance-details',
|
||||
component: () => import('@/features/admin/views/AdminFinanceDetailsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/wallet-ledger',
|
||||
path: adminPath('wallet-ledger'),
|
||||
name: 'admin-wallet-ledger',
|
||||
component: () => import('@/features/admin/views/AdminWalletLedgerView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/payments',
|
||||
path: adminPath('payments'),
|
||||
name: 'admin-payments',
|
||||
component: () => import('@/features/admin/views/AdminPaymentsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/withdrawals',
|
||||
path: adminPath('withdrawals'),
|
||||
name: 'admin-withdrawals',
|
||||
component: () => import('@/features/admin/views/AdminWithdrawalsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/system-configs',
|
||||
path: adminPath('system-configs'),
|
||||
name: 'admin-system-configs',
|
||||
component: () => import('@/features/admin/views/AdminSystemConfigsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/payment-configs',
|
||||
path: adminPath('payment-configs'),
|
||||
name: 'admin-payment-configs',
|
||||
component: () => import('@/features/admin/views/AdminPaymentConfigsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/audit-logs',
|
||||
path: adminPath('audit-logs'),
|
||||
name: 'admin-audit-logs',
|
||||
component: () => import('@/features/admin/views/AdminAuditLogsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/admin-users',
|
||||
path: adminPath('admin-users'),
|
||||
name: 'admin-admin-users',
|
||||
component: () => import('@/features/admin/views/AdminMgrUsersView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/roles',
|
||||
path: adminPath('roles'),
|
||||
name: 'admin-roles',
|
||||
component: () => import('@/features/admin/views/AdminRolesView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/announcements',
|
||||
path: adminPath('announcements'),
|
||||
name: 'admin-announcements',
|
||||
component: () => import('@/features/admin/views/AdminAnnouncementsView.vue'),
|
||||
meta: adminMeta,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import { getAccessToken, getLoginPath } from '@/shared/utils/authStorage'
|
||||
import { ADMIN_DASHBOARD_PATH, ADMIN_LOGIN_PATH } from '@/shared/utils/adminPath'
|
||||
import { accountRoutes } from './accountRoutes'
|
||||
import { adminRoutes } from './adminRoutes'
|
||||
import { isMobileHost, isMobileDevice, getMobilePath, getPcPath } from './mobileHost'
|
||||
@@ -77,11 +78,11 @@ router.beforeEach(async to => {
|
||||
|
||||
const adminSession = useAdminSessionStore()
|
||||
adminSession.syncFromStorage()
|
||||
if (to.path === '/admin/login') {
|
||||
if (to.path === ADMIN_LOGIN_PATH) {
|
||||
if (!adminSession.hasSessionHint) return true
|
||||
try {
|
||||
await adminSession.loadMe()
|
||||
return '/admin/dashboard'
|
||||
return ADMIN_DASHBOARD_PATH
|
||||
} catch {
|
||||
adminSession.logout()
|
||||
return true
|
||||
@@ -98,7 +99,7 @@ router.beforeEach(async to => {
|
||||
}
|
||||
} catch {
|
||||
adminSession.logout()
|
||||
return { path: '/admin/login', query: { redirect: to.fullPath } }
|
||||
return { path: ADMIN_LOGIN_PATH, query: { redirect: to.fullPath } }
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isAdminPath } from '@/shared/utils/adminPath'
|
||||
|
||||
export function isMobileHost(hostname = window.location.hostname) {
|
||||
return hostname === 'm.localhost' || hostname.startsWith('m.')
|
||||
}
|
||||
@@ -14,7 +16,7 @@ export function getMobilePath(path: string): string {
|
||||
if (path === '/' || path === '') {
|
||||
return '/m'
|
||||
}
|
||||
if (path === '/m' || path.startsWith('/m/') || path.startsWith('/admin')) {
|
||||
if (path === '/m' || path.startsWith('/m/') || isAdminPath(path)) {
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -44,7 +46,7 @@ export function getMobilePath(path: string): string {
|
||||
}
|
||||
|
||||
export function getPcPath(path: string): string {
|
||||
if ((path !== '/m' && !path.startsWith('/m/')) || path.startsWith('/admin')) {
|
||||
if ((path !== '/m' && !path.startsWith('/m/')) || isAdminPath(path)) {
|
||||
return path
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const DEFAULT_ADMIN_BASE_PATH = '/admin'
|
||||
|
||||
function normalizeAdminBasePath(value: string | undefined) {
|
||||
const raw = (value || DEFAULT_ADMIN_BASE_PATH).trim()
|
||||
if (!raw || raw === '/') return DEFAULT_ADMIN_BASE_PATH
|
||||
const withLeadingSlash = raw.startsWith('/') ? raw : `/${raw}`
|
||||
return withLeadingSlash.replace(/\/+$/, '') || DEFAULT_ADMIN_BASE_PATH
|
||||
}
|
||||
|
||||
export const ADMIN_BASE_PATH = normalizeAdminBasePath(import.meta.env.VITE_ADMIN_BASE_PATH)
|
||||
|
||||
export function adminPath(path = '') {
|
||||
const suffix = path.trim().replace(/^\/+/, '')
|
||||
if (!suffix) return ADMIN_BASE_PATH
|
||||
return `${ADMIN_BASE_PATH}/${suffix}`
|
||||
}
|
||||
|
||||
export const ADMIN_LOGIN_PATH = adminPath('login')
|
||||
export const ADMIN_DASHBOARD_PATH = adminPath('dashboard')
|
||||
|
||||
export function isAdminPath(path: string) {
|
||||
return path === ADMIN_BASE_PATH || path.startsWith(`${ADMIN_BASE_PATH}/`)
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ADMIN_LOGIN_PATH } from '@/shared/utils/adminPath'
|
||||
|
||||
export type AuthScope = 'user' | 'admin'
|
||||
|
||||
export interface AuthTokenPair {
|
||||
@@ -53,7 +55,7 @@ export function clearAuthStorage(scope: AuthScope) {
|
||||
}
|
||||
|
||||
export function getLoginPath(scope: AuthScope, currentPath: string) {
|
||||
if (scope === 'admin') return '/admin/login'
|
||||
if (scope === 'admin') return ADMIN_LOGIN_PATH
|
||||
return currentPath.startsWith('/m') ? '/m/login' : '/login'
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user