fix(admin): resolve site base at runtime instead of build time
This commit is contained in:
1 parent
cb8e711f33
commit
02d7f901a8
26 files changed
+117
-73
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import i18n from '@/i18n'
|
||||
import type { ApiResponse } from './types'
|
||||
import { notifyError } from '@/utils/notify'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
export type { ApiResponse }
|
||||
|
||||
@@ -19,11 +20,10 @@ const createNotifiedError = (message: string): NotifiedError => {
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''
|
||||
const API_PREFIX = '/api/v1'
|
||||
const ADMIN_PATH = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const redirectToLogin = () => {
|
||||
localStorage.removeItem('admin_token')
|
||||
window.location.href = `${ADMIN_PATH}/login`
|
||||
window.location.href = adminUrl('/login')
|
||||
}
|
||||
|
||||
const isLoginEndpoint = (url: string) => /\/admin\/login\b/.test(url)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useAdminAuthStore } from '@/stores/auth'
|
||||
import { ADMIN_BASE } from '@/utils/adminBase'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -377,10 +378,10 @@ const routes = [
|
||||
},
|
||||
]
|
||||
|
||||
// base 由 utils/adminBase 统一解析:fullstack 模式下读后端注入的 <base href>,
|
||||
// 否则退回构建期的 import.meta.env.BASE_URL。原生 <a href> 走同一份前缀(adminUrl)。
|
||||
const router = createRouter({
|
||||
history: createWebHistory(
|
||||
document.querySelector('base')?.getAttribute('href') ?? import.meta.env.BASE_URL,
|
||||
),
|
||||
history: createWebHistory(ADMIN_BASE || '/'),
|
||||
routes,
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* admin 站点的挂载前缀,运行时解析。
|
||||
*
|
||||
* fullstack 单二进制模式下 admin 可以挂在任意自定义路径(config.yml 的 `web.admin_path`),
|
||||
* 该前缀在构建期是未知的:Vite 只往 index.html 注入 `<base href="__DJ_ADMIN_BASE__/">`
|
||||
* 占位符,由后端启动时替换成实际值(见 internal/web/handler.go)。
|
||||
*
|
||||
* 所以凡是绕过 vue-router 自行拼接的 URL —— 原生 `<a href>`、`window.location` 跳转 ——
|
||||
* 都必须走这里运行时读取,不能依赖构建期的 `import.meta.env`。
|
||||
*
|
||||
* 反过来,`<router-link :to>` 和 `router.push()` 不要用本模块:vue-router 的 history
|
||||
* 已经带了同一个 base,再拼一层会得到 `/admin/admin/...` 的双重前缀。
|
||||
*/
|
||||
|
||||
// 必须与后端 internal/web/handler.go 的 adminBasePlaceholder 保持一致
|
||||
const BASE_PLACEHOLDER = '__DJ_ADMIN_BASE__'
|
||||
|
||||
function resolveAdminBase(): string {
|
||||
const fromTag =
|
||||
typeof document !== 'undefined'
|
||||
? document.querySelector('base')?.getAttribute('href')
|
||||
: null
|
||||
const raw = fromTag || import.meta.env.BASE_URL || '/'
|
||||
|
||||
// 占位符没被后端替换(例如直接 vite preview 查看 fullstack 产物)时退回根路径,
|
||||
// 避免生成 /__DJ_ADMIN_BASE__/users/1 这种无效链接。
|
||||
if (raw.includes(BASE_PLACEHOLDER)) return ''
|
||||
|
||||
const trimmed = raw.replace(/\/+$/, '')
|
||||
// './' 是 fullstack 构建的 Vite base,语义上等于「挂在当前根」,同样按空前缀处理。
|
||||
return trimmed === '.' ? '' : trimmed
|
||||
}
|
||||
|
||||
/** admin 的挂载前缀,不带尾斜杠。例如 ''、'/admin'、'/dj-mgmt-7x9k2'。 */
|
||||
export const ADMIN_BASE = resolveAdminBase()
|
||||
|
||||
/**
|
||||
* 把 admin 内部路由路径拼成可直接用于 `<a href>` / `window.location` 的完整路径。
|
||||
*
|
||||
* @example adminUrl('/users/1') // admin 挂在 /admin 时得到 '/admin/users/1'
|
||||
*/
|
||||
export function adminUrl(path: string): string {
|
||||
return `${ADMIN_BASE}${path.startsWith('/') ? path : `/${path}`}`
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { useListRefresh, type ListFetchOptions } from '@/composables/useListRefr
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import ComplianceGuardWrapper from '@/components/ComplianceGuardWrapper.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
@@ -31,7 +32,6 @@ const pagination = ref({
|
||||
total: 0,
|
||||
total_page: 1,
|
||||
})
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
@@ -41,7 +41,7 @@ const filters = reactive({
|
||||
})
|
||||
|
||||
const normalizeFilterValue = (value: string) => (value === '__all__' ? '' : value)
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const resolveAffiliateUserID = (item: AdminAffiliateCommission) => Number(item?.affiliate_profile?.user_id || item?.affiliate_profile?.user?.id || 0)
|
||||
|
||||
const fetchRows = async (page = 1, options: ListFetchOptions = {}) => {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { confirmAction } from '@/utils/confirm'
|
||||
import { notifyError, notifySuccess } from '@/utils/notify'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
@@ -38,8 +39,7 @@ const filters = reactive({
|
||||
})
|
||||
|
||||
const normalizeFilterValue = (value: string) => (value === '__all__' ? '' : value)
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
|
||||
const parseNumber = (value: unknown, fallback = 0) => {
|
||||
const parsed = Number(value)
|
||||
|
||||
@@ -21,6 +21,7 @@ import { confirmAction } from '@/utils/confirm'
|
||||
import { notifyError, notifySuccess } from '@/utils/notify'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import ComplianceGuardWrapper from '@/components/ComplianceGuardWrapper.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
@@ -33,7 +34,6 @@ const pagination = ref({
|
||||
total: 0,
|
||||
total_page: 1,
|
||||
})
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
@@ -42,7 +42,7 @@ const filters = reactive({
|
||||
})
|
||||
|
||||
const normalizeFilterValue = (value: string) => (value === '__all__' ? '' : value)
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const resolveAffiliateUserID = (item: AdminAffiliateWithdraw) => Number(item?.affiliate_profile?.user_id || item?.affiliate_profile?.user?.id || 0)
|
||||
|
||||
const fetchRows = async (page = 1, options: ListFetchOptions = {}) => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { confirmAction } from '@/utils/confirm'
|
||||
import { notifyError, notifySuccess } from '@/utils/notify'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
@@ -30,8 +31,7 @@ const jumpPage = ref('')
|
||||
|
||||
const filterStatus = ref('__all__')
|
||||
const searchQuery = ref('')
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
|
||||
const statusOptions = [
|
||||
{ value: '__all__', key: 'apiCredentials.filters.allStatus' },
|
||||
|
||||
@@ -11,9 +11,9 @@ import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { getLocalizedText } from '@/utils/format'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const productKeyword = ref('')
|
||||
const productOptions = ref<AdminProduct[]>([])
|
||||
@@ -150,7 +150,7 @@ const confirmExportMessage = computed(() => {
|
||||
: t('admin.cardSecretExports.confirmUsed', { count: exportCount.value })
|
||||
})
|
||||
|
||||
const productLink = (productId: number) => `${adminPath}/products?product_id=${productId}`
|
||||
const productLink = (productId: number) => adminUrl(`/products?product_id=${productId}`)
|
||||
|
||||
const resetMessages = () => {
|
||||
successMessage.value = ''
|
||||
|
||||
@@ -11,9 +11,9 @@ import { Input } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { getLocalizedText } from '@/utils/format'
|
||||
import CardSecretBatchCreateModal from './components/CardSecretBatchCreateModal.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const productKeyword = ref('')
|
||||
const productOptions = ref<AdminProduct[]>([])
|
||||
@@ -220,7 +220,7 @@ const handleImportSuccess = async () => {
|
||||
await loadProductInfo()
|
||||
}
|
||||
|
||||
const productLink = (productId: number) => `${adminPath}/products?product_id=${productId}`
|
||||
const productLink = (productId: number) => adminUrl(`/products?product_id=${productId}`)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadProductOptions()
|
||||
|
||||
@@ -19,10 +19,10 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { formatDate, getLocalizedText } from '@/utils/format'
|
||||
import { confirmAction } from '@/utils/confirm'
|
||||
import CardSecretEditModal from './components/CardSecretEditModal.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { refreshing, refreshList } = useListRefresh()
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const pageSizeOptions = [10, 20, 50, 100, 200]
|
||||
|
||||
const productKeyword = ref('')
|
||||
@@ -212,8 +212,8 @@ const resolveSecretBatchLabel = (secret: AdminCardSecret) => {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const productLink = (productId: number) => `${adminPath}/products?product_id=${productId}`
|
||||
const orderLink = (orderId: number) => `${adminPath}/orders?order_id=${orderId}`
|
||||
const productLink = (productId: number) => adminUrl(`/products?product_id=${productId}`)
|
||||
const orderLink = (orderId: number) => adminUrl(`/orders?order_id=${orderId}`)
|
||||
|
||||
const clearBatchActionMessages = () => {
|
||||
batchActionError.value = ''
|
||||
|
||||
@@ -15,6 +15,7 @@ import ListPagination from '@/components/ListPagination.vue'
|
||||
import { useListRefresh, type ListFetchOptions } from '@/composables/useListRefresh'
|
||||
import { formatDate, getLocalizedText, toRFC3339 } from '@/utils/format'
|
||||
import { formatSkuDisplayLabel } from '@/utils/sku'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const loading = ref(true)
|
||||
const { refreshing, refreshList } = useListRefresh()
|
||||
@@ -25,7 +26,6 @@ const pagination = ref({
|
||||
total: 0,
|
||||
total_page: 1,
|
||||
})
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const route = useRoute()
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
@@ -47,9 +47,9 @@ const selectedRefundId = ref<number | null>(null)
|
||||
|
||||
const orderLink = (orderId?: number) => {
|
||||
if (!orderId) return ''
|
||||
return `${adminPath}/orders?order_id=${orderId}`
|
||||
return adminUrl(`/orders?order_id=${orderId}`)
|
||||
}
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
|
||||
const refundTypeLabel = (item: AdminOrderRefund | null) => {
|
||||
if (!item) return '-'
|
||||
|
||||
@@ -23,6 +23,7 @@ import { formatDate, formatMoney, getLocalizedText, toRFC3339 } from '@/utils/fo
|
||||
import { formatSkuDisplayLabel } from '@/utils/sku'
|
||||
import OrderDetailDialog from './components/OrderDetailDialog.vue'
|
||||
import OrderFulfillmentModal from './components/OrderFulfillmentModal.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const loading = ref(true)
|
||||
const { refreshing, refreshList } = useListRefresh()
|
||||
@@ -57,8 +58,7 @@ const fulfillmentParentId = ref<number | null>(null)
|
||||
const maxRefundDays = ref(30)
|
||||
const route = useRoute()
|
||||
const { t, locale } = useI18n()
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
|
||||
const itemSkuLabel = (item: AdminOrderItem & Record<string, unknown>) =>
|
||||
formatSkuDisplayLabel((item as any)?.sku_snapshot, locale.value)
|
||||
|
||||
@@ -20,6 +20,7 @@ import { paymentStatusClass, paymentStatusLabel } from '@/utils/status'
|
||||
import ComplianceGuardWrapper from '@/components/ComplianceGuardWrapper.vue'
|
||||
import { copyText } from '@/utils/clipboard'
|
||||
import { formatDate, toRFC3339 } from '@/utils/format'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const loading = ref(true)
|
||||
const { refreshing, refreshList } = useListRefresh()
|
||||
@@ -30,7 +31,6 @@ const pagination = ref({
|
||||
total: 0,
|
||||
total_page: 1,
|
||||
})
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const filters = reactive({
|
||||
userId: '',
|
||||
orderId: '',
|
||||
@@ -95,9 +95,9 @@ const changePageSize = (size: number) => {
|
||||
fetchPayments(1)
|
||||
}
|
||||
|
||||
const orderLink = (orderId: number) => `${adminPath}/orders?order_id=${orderId}`
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const channelLink = (channelId: number) => `${adminPath}/payment-channels?channel_id=${channelId}`
|
||||
const orderLink = (orderId: number) => adminUrl(`/orders?order_id=${orderId}`)
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const channelLink = (channelId: number) => adminUrl(`/payment-channels?channel_id=${channelId}`)
|
||||
|
||||
const handleExport = async () => {
|
||||
exportError.value = ''
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useListRefresh, type ListFetchOptions } from '@/composables/useListRefr
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import ComplianceGuardWrapper from '@/components/ComplianceGuardWrapper.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
@@ -33,7 +34,6 @@ const pagination = ref({
|
||||
total: 0,
|
||||
total_page: 1,
|
||||
})
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
@@ -51,7 +51,7 @@ const initFiltersFromQuery = () => {
|
||||
|
||||
const normalizeFilterValue = (value: string) => (value === '__all__' ? '' : value)
|
||||
const pageSizeOptions = [10, 20, 50, 100]
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const resolveResellerUserID = (item: AdminResellerBalanceAccount) => Number(item?.profile?.user_id || item?.profile?.user?.id || 0)
|
||||
|
||||
const fetchRows = async (page = 1, options: ListFetchOptions = {}) => {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { confirmAction } from '@/utils/confirm'
|
||||
import { notifyError, notifySuccess } from '@/utils/notify'
|
||||
import { formatDate, toRFC3339 } from '@/utils/format'
|
||||
import { getResellerDomainActionState } from '@/utils/resellerManagement'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
@@ -51,10 +52,9 @@ const filters = reactive({
|
||||
createdTo: '',
|
||||
})
|
||||
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const pageSizeOptions = [10, 20, 50, 100]
|
||||
const normalizeFilterValue = (value: string) => (value === '__all__' ? '' : value)
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const resolveResellerUserID = (item: AdminResellerDomain) => Number(item.profile?.user_id || item.profile?.user?.id || 0)
|
||||
|
||||
const fetchRows = async (page = 1, options: ListFetchOptions = {}) => {
|
||||
|
||||
@@ -27,6 +27,7 @@ import { useListRefresh, type ListFetchOptions } from '@/composables/useListRefr
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { formatDate, toRFC3339 } from '@/utils/format'
|
||||
import ComplianceGuardWrapper from '@/components/ComplianceGuardWrapper.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
@@ -39,7 +40,6 @@ const pagination = ref({
|
||||
total: 0,
|
||||
total_page: 1,
|
||||
})
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
@@ -62,8 +62,8 @@ const initFiltersFromQuery = () => {
|
||||
|
||||
const normalizeFilterValue = (value: string) => (value === '__all__' ? '' : value)
|
||||
const pageSizeOptions = [10, 20, 50, 100]
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const orderLink = (orderNo: string) => `${adminPath}/orders?order_no=${encodeURIComponent(orderNo)}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const orderLink = (orderNo: string) => adminUrl(`/orders?order_no=${encodeURIComponent(orderNo)}`)
|
||||
|
||||
const fetchRows = async (page = 1, options: ListFetchOptions = {}) => {
|
||||
if (!options.preserveRows) loading.value = true
|
||||
|
||||
@@ -29,11 +29,11 @@ import { formatDate, formatMoney, getLocalizedText } from '@/utils/format'
|
||||
import { getImageUrl } from '@/utils/image'
|
||||
import { notifyError, notifySuccess } from '@/utils/notify'
|
||||
import { getResellerProfileStatusKey } from '@/utils/resellerManagement'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
@@ -210,11 +210,11 @@ const verificationLabel = (status?: string) => {
|
||||
return status || '-'
|
||||
}
|
||||
|
||||
const userDetailLink = computed(() => (profile.value?.user_id ? `${adminPath}/users/${profile.value.user_id}` : ''))
|
||||
const siteConfigLink = computed(() => `${adminPath}/resellers/site-configs?reseller_id=${profileId.value}`)
|
||||
const productSettingsLink = computed(() => `${adminPath}/resellers/product-settings?reseller_id=${profileId.value}`)
|
||||
const ledgerLink = computed(() => `${adminPath}/resellers/ledger-entries?reseller_id=${profileId.value}`)
|
||||
const withdrawLink = computed(() => `${adminPath}/resellers/withdraws?reseller_id=${profileId.value}`)
|
||||
const userDetailLink = computed(() => (profile.value?.user_id ? adminUrl(`/users/${profile.value.user_id}`) : ''))
|
||||
const siteConfigLink = computed(() => adminUrl(`/resellers/site-configs?reseller_id=${profileId.value}`))
|
||||
const productSettingsLink = computed(() => adminUrl(`/resellers/product-settings?reseller_id=${profileId.value}`))
|
||||
const ledgerLink = computed(() => adminUrl(`/resellers/ledger-entries?reseller_id=${profileId.value}`))
|
||||
const withdrawLink = computed(() => adminUrl(`/resellers/withdraws?reseller_id=${profileId.value}`))
|
||||
|
||||
onMounted(fetchDetail)
|
||||
</script>
|
||||
|
||||
@@ -27,6 +27,7 @@ import { confirmAction } from '@/utils/confirm'
|
||||
import { notifyError, notifySuccess } from '@/utils/notify'
|
||||
import { formatDate, toRFC3339 } from '@/utils/format'
|
||||
import { getResellerProfileActionState, getResellerProfileStatusKey } from '@/utils/resellerManagement'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
@@ -54,11 +55,10 @@ const filters = reactive({
|
||||
createdTo: '',
|
||||
})
|
||||
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const pageSizeOptions = [10, 20, 50, 100]
|
||||
const normalizeFilterValue = (value: string) => (value === '__all__' ? '' : value)
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const profileDetailLink = (profileId: number) => `${adminPath}/resellers/profiles/${profileId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const profileDetailLink = (profileId: number) => adminUrl(`/resellers/profiles/${profileId}`)
|
||||
|
||||
const approveForm = reactive({
|
||||
defaultMarkup: '0.00',
|
||||
|
||||
@@ -25,6 +25,7 @@ import { confirmAction } from '@/utils/confirm'
|
||||
import { notifyError, notifySuccess } from '@/utils/notify'
|
||||
import { formatDate, toRFC3339 } from '@/utils/format'
|
||||
import ComplianceGuardWrapper from '@/components/ComplianceGuardWrapper.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
@@ -40,7 +41,6 @@ const pagination = ref({
|
||||
total: 0,
|
||||
total_page: 1,
|
||||
})
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
@@ -57,7 +57,7 @@ const rejectForm = reactive({
|
||||
const queryString = (value: unknown) => (Array.isArray(value) ? value[0] : value)
|
||||
const normalizeFilterValue = (value: string) => (value === '__all__' ? '' : value)
|
||||
const pageSizeOptions = [10, 20, 50, 100]
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const resolveResellerUserID = (item: AdminResellerWithdraw) => Number(item?.profile?.user_id || item?.profile?.user?.id || 0)
|
||||
|
||||
const fetchRows = async (page = 1, options: ListFetchOptions = {}) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { confirmAction } from '@/utils/confirm'
|
||||
import Setup2FAModal from './components/Setup2FAModal.vue'
|
||||
import RecoveryCodesModal from './components/RecoveryCodesModal.vue'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(false)
|
||||
@@ -66,8 +67,7 @@ const submitDisable = async () => {
|
||||
disableForm.recoveryCode = ''
|
||||
disableForm.useRecovery = false
|
||||
localStorage.removeItem('admin_token')
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
window.location.href = `${adminPath}/login`
|
||||
window.location.href = adminUrl(`/login`)
|
||||
} catch (err: any) {
|
||||
notifyErrorIfNeeded(err, t('admin.twofa.disable.failed'))
|
||||
}
|
||||
@@ -121,8 +121,7 @@ const changePassword = async () => {
|
||||
})
|
||||
notifySuccess(t('admin.settings.alerts.passwordSuccess'))
|
||||
localStorage.removeItem('admin_token')
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
window.location.href = `${adminPath}/login`
|
||||
window.location.href = adminUrl(`/login`)
|
||||
} catch (err: any) {
|
||||
notifyErrorIfNeeded(err, t('admin.settings.alerts.passwordFailed'))
|
||||
} finally {
|
||||
|
||||
@@ -27,7 +27,6 @@ import { formatDate, formatMoney, getLocalizedText } from '@/utils/format'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const userId = computed(() => Number(route.params.id))
|
||||
|
||||
interface UserDetailCouponUsage {
|
||||
@@ -284,10 +283,11 @@ const changeTab = (tab: 'orders' | 'payments' | 'coupons' | 'wallet') => {
|
||||
}
|
||||
}
|
||||
|
||||
const orderLink = (orderId: number) => `${adminPath}/orders?order_id=${orderId}`
|
||||
const paymentDetailLink = (paymentId: number) => `${adminPath}/payments?payment_id=${paymentId}`
|
||||
const orderListLink = computed(() => `${adminPath}/orders?user_id=${userId.value}`)
|
||||
const paymentListLink = computed(() => `${adminPath}/payments?user_id=${userId.value}`)
|
||||
// 本页链接全部通过 router-link 的 :to 消费,base 前缀由 vue-router 自己带上,这里不能再拼
|
||||
const orderLink = (orderId: number) => `/orders?order_id=${orderId}`
|
||||
const paymentDetailLink = (paymentId: number) => `/payments?payment_id=${paymentId}`
|
||||
const orderListLink = computed(() => `/orders?user_id=${userId.value}`)
|
||||
const paymentListLink = computed(() => `/payments?user_id=${userId.value}`)
|
||||
|
||||
const statusClass = (status?: string) => userStatusClass(status)
|
||||
const statusLabel = (status?: string) => userStatusLabel(t, status)
|
||||
@@ -470,7 +470,7 @@ watch(
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<router-link :to="`${adminPath}/users`" class="text-muted-foreground hover:text-foreground transition-colors">
|
||||
<router-link to="/users" class="text-muted-foreground hover:text-foreground transition-colors">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
|
||||
@@ -13,12 +13,12 @@ import TableSkeleton from '@/components/TableSkeleton.vue'
|
||||
import ListPagination from '@/components/ListPagination.vue'
|
||||
import { useListRefresh, type ListFetchOptions } from '@/composables/useListRefresh'
|
||||
import { formatDate, toRFC3339 } from '@/utils/format'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
const { refreshing, refreshList } = useListRefresh()
|
||||
const logs = ref<AdminUserLoginLog[]>([])
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
@@ -95,7 +95,7 @@ const changePageSize = (size: number) => {
|
||||
fetchLogs(1)
|
||||
}
|
||||
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
|
||||
const statusLabel = (status: string) => t(`admin.userLoginLogs.status.${status || 'failed'}`)
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ const loading = ref(true)
|
||||
const { refreshing, refreshList } = useListRefresh()
|
||||
const users = ref<AdminUser[]>([])
|
||||
const selectedIds = ref<number[]>([])
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
@@ -167,7 +166,8 @@ const resetFilters = () => {
|
||||
fetchUsers(1)
|
||||
}
|
||||
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
// router-link 的 :to 走 vue-router,base 前缀由 router 自己带上,这里不能再拼
|
||||
const userDetailLink = (userId: number) => `/users/${userId}`
|
||||
|
||||
const allSelected = computed(() => {
|
||||
if (users.value.length === 0) return false
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { paymentStatusClass, paymentStatusLabel } from '@/utils/status'
|
||||
import { formatDate, toRFC3339 } from '@/utils/format'
|
||||
import ComplianceGuardWrapper from '@/components/ComplianceGuardWrapper.vue'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
@@ -25,7 +26,6 @@ const pagination = ref({
|
||||
total: 0,
|
||||
total_page: 1,
|
||||
})
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const filters = reactive({
|
||||
rechargeNo: '',
|
||||
@@ -92,9 +92,9 @@ const changePageSize = (size: number) => {
|
||||
fetchRecharges(1)
|
||||
}
|
||||
|
||||
const userLink = (userID: number) => `${adminPath}/users/${userID}`
|
||||
const paymentLink = (paymentID: number) => `${adminPath}/payments?payment_id=${paymentID}`
|
||||
const channelLink = (channelID: number) => `${adminPath}/payment-channels?channel_id=${channelID}`
|
||||
const userLink = (userID: number) => adminUrl(`/users/${userID}`)
|
||||
const paymentLink = (paymentID: number) => adminUrl(`/payments?payment_id=${paymentID}`)
|
||||
const channelLink = (channelID: number) => adminUrl(`/payment-channels?channel_id=${channelID}`)
|
||||
|
||||
const statusClass = (status?: string) => paymentStatusClass(status)
|
||||
const statusLabel = (status?: string) => paymentStatusLabel(t, status)
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from '@/utils/fulfillment'
|
||||
import { formatDate, formatMoney, getLocalizedText, hasPositiveAmount } from '@/utils/format'
|
||||
import { resolveSkuCodeFromSnapshot, resolveSkuSpecFromSnapshot } from '@/utils/sku'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -59,13 +60,12 @@ const manualRefundForm = reactive({
|
||||
reason: '',
|
||||
})
|
||||
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const userDetailLink = (userId: number) => `${adminPath}/users/${userId}`
|
||||
const productLink = (productId: number) => `${adminPath}/products?product_id=${productId}`
|
||||
const couponCodeLink = (code: string) => `${adminPath}/coupons?code=${encodeURIComponent(code)}`
|
||||
const promotionLink = (promotionId: number) => `${adminPath}/promotions?id=${promotionId}`
|
||||
const paymentLink = (paymentId: number) => `${adminPath}/payments?payment_id=${paymentId}`
|
||||
const userDetailLink = (userId: number) => adminUrl(`/users/${userId}`)
|
||||
const productLink = (productId: number) => adminUrl(`/products?product_id=${productId}`)
|
||||
const couponCodeLink = (code: string) => adminUrl(`/coupons?code=${encodeURIComponent(code)}`)
|
||||
const promotionLink = (promotionId: number) => adminUrl(`/promotions?id=${promotionId}`)
|
||||
const paymentLink = (paymentId: number) => adminUrl(`/payments?payment_id=${paymentId}`)
|
||||
|
||||
const statusLabel = (status: string) => orderStatusLabel(t, status)
|
||||
const statusClass = (status: string) => orderStatusClass(status)
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Dialog, DialogScrollContent, DialogHeader, DialogTitle } from '@/compon
|
||||
import { copyText } from '@/utils/clipboard'
|
||||
import { formatDate, getLocalizedText } from '@/utils/format'
|
||||
import { formatSkuDisplayLabel } from '@/utils/sku'
|
||||
import { adminUrl } from '@/utils/adminBase'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -24,7 +25,6 @@ const { t, locale } = useI18n()
|
||||
|
||||
const entrySkuLabel = (entry: Record<string, unknown>) =>
|
||||
formatSkuDisplayLabel(entry?.sku_snapshot, locale.value)
|
||||
const adminPath = import.meta.env.VITE_ADMIN_PATH || ''
|
||||
|
||||
const detailLoading = ref(false)
|
||||
const detailError = ref('')
|
||||
@@ -38,7 +38,7 @@ const resetDetail = () => {
|
||||
|
||||
const resolveOrderDetailLink = (refund: AdminOrderRefund | null) => {
|
||||
if (!refund || !refund.order_id) return ''
|
||||
return `${adminPath}/orders?order_id=${refund.order_id}`
|
||||
return adminUrl(`/orders?order_id=${refund.order_id}`)
|
||||
}
|
||||
|
||||
const refundTypeLabel = (item: AdminOrderRefund | null) => {
|
||||
|
||||
Reference in new issue
Block a user