实现外部商品调价
This commit is contained in:
@@ -1,22 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import { readError } from '@/shared/utils/error'
|
||||
import { CopyDocument, Search } from '@element-plus/icons-vue'
|
||||
import { CopyDocument, EditPen, Search } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { fetchAdminUsers, type AdminUserItem } from '@/features/admin/api/adminUsers'
|
||||
import { fetchAdminFileBlob } from '@/shared/api/files'
|
||||
import AuthImage from '@/shared/components/business/AuthImage.vue'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { formatCentWithSymbol, formatMoneyWithSymbol, roundMoney } from '@/shared/utils/money'
|
||||
import {
|
||||
centToYuan,
|
||||
formatCentWithSymbol,
|
||||
formatMoneyWithSymbol,
|
||||
roundMoney,
|
||||
yuanToCent,
|
||||
} from '@/shared/utils/money'
|
||||
import {
|
||||
adjustExternalListing,
|
||||
adminMarkListingAbnormal,
|
||||
adminOfflineListing,
|
||||
adminTransferListingOwner,
|
||||
emptyListingSalePriceConfig,
|
||||
fetchAdminListing,
|
||||
fetchListingSalePriceConfig,
|
||||
type AdminExternalListingAdjustMode,
|
||||
type PublishSalePriceConfig,
|
||||
type Listing,
|
||||
} from '@/features/listings'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
import {
|
||||
listingReviewStatusLabel,
|
||||
listingStatusLabel,
|
||||
@@ -42,6 +54,9 @@ import {
|
||||
readAssetNumber,
|
||||
readAssetString,
|
||||
} from '@/shared/utils/listingDisplay'
|
||||
import { calculatePlatformPricing, roundRatio as roundPricingRatio } from '@/shared/utils/pricing'
|
||||
|
||||
const adminSession = useAdminSessionStore()
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(false)
|
||||
@@ -55,6 +70,24 @@ const transferReason = ref('')
|
||||
const userKeyword = ref('')
|
||||
const userOptions = ref<AdminUserItem[]>([])
|
||||
const selectedUser = ref<AdminUserItem | null>(null)
|
||||
const externalAdjustVisible = ref(false)
|
||||
const externalAdjustSaving = ref(false)
|
||||
const salePriceConfig = ref<PublishSalePriceConfig>(emptyListingSalePriceConfig)
|
||||
const externalAdjustModes: Array<{ label: string; value: AdminExternalListingAdjustMode }> = [
|
||||
{ label: '按降价金额', value: 'discount_amount' },
|
||||
{ label: '按发布比例', value: 'sale_ratio' },
|
||||
]
|
||||
const externalAdjustForm = reactive({
|
||||
haf_coin_m: 0,
|
||||
awm_bullets: 0,
|
||||
level6_helmets: 0,
|
||||
level6_armor: 0,
|
||||
deposit_yuan: 0,
|
||||
adjust_mode: 'discount_amount' as AdminExternalListingAdjustMode,
|
||||
discount_amount_yuan: 0,
|
||||
sale_ratio: 0,
|
||||
reason: '',
|
||||
})
|
||||
|
||||
const actionTitle = computed(() =>
|
||||
actionType.value === 'offline' ? '强制下架商品' : '标记商品异常'
|
||||
@@ -66,6 +99,15 @@ const canOperate = computed(
|
||||
!['offline', 'abnormal'].includes(listing.value.status)
|
||||
)
|
||||
const canTransfer = computed(() => !!listing.value && listing.value.status !== 'rented')
|
||||
const canAdjustExternal = computed(
|
||||
() => !!listing.value?.is_external_upload && adminSession.hasPermission('listing:edit_external')
|
||||
)
|
||||
const externalAdjustmentLocked = computed(
|
||||
() =>
|
||||
!listing.value ||
|
||||
listing.value.in_transaction ||
|
||||
!['draft', 'published', 'offline', 'abnormal'].includes(listing.value.status)
|
||||
)
|
||||
const selectedUserAvailable = computed(
|
||||
() => selectedUser.value?.status === 'active' && selectedUser.value.realname_status === 'verified'
|
||||
)
|
||||
@@ -109,6 +151,14 @@ const ownerRows = computed(() => {
|
||||
{ label: '号主损耗', value: moneyYuan(ownerLoss) },
|
||||
{ label: '号主租金合计', value: moneyYuan(ownerTotal) },
|
||||
{ label: '平台费用', value: moneyYuan(platformFee) },
|
||||
...(row.is_external_upload
|
||||
? [
|
||||
{ label: 'API参考比例', value: ratioText(externalReferenceRatio.value) },
|
||||
{ label: '号主发布比例', value: ratioText(sellerSaleRatio(row)) },
|
||||
{ label: '租客售卖比例', value: ratioText(buyerSaleRatio(row)) },
|
||||
{ label: '价格标签', value: row.is_accelerated_sale ? '特惠' : '常规' },
|
||||
]
|
||||
: []),
|
||||
{ label: '押金', value: moneyCent(row.deposit_amount_cent) },
|
||||
{ label: '哈夫币', value: formatHafCoinM(getCoinWan(row)) },
|
||||
{ label: '每日损耗', value: getDailyLoss(row) || '-' },
|
||||
@@ -133,6 +183,68 @@ const hiddenSkinCount = computed(() => {
|
||||
if (!listing.value) return 0
|
||||
return Math.max(0, getSkinNames(listing.value).length - skinNames.value.length)
|
||||
})
|
||||
const externalReferenceRatio = computed(() => {
|
||||
if (!listing.value) return 0
|
||||
return (
|
||||
breakdownNumber(listing.value, 'seller_reference_ratio') ||
|
||||
breakdownNumber(listing.value, 'seller_ratio')
|
||||
)
|
||||
})
|
||||
const externalAdjustmentPreview = computed(() => {
|
||||
const coinM = Math.max(Number(externalAdjustForm.haf_coin_m || 0), 0)
|
||||
const coinWan = coinM * 100
|
||||
const referenceRatio = externalReferenceRatio.value
|
||||
const referenceCoinPrice =
|
||||
coinWan > 0 && referenceRatio > 0 ? roundMoney(coinWan / referenceRatio) : 0
|
||||
let saleRatio = roundPricingRatio(Number(externalAdjustForm.sale_ratio || 0))
|
||||
if (externalAdjustForm.adjust_mode === 'discount_amount') {
|
||||
const desiredPrice = referenceCoinPrice - Number(externalAdjustForm.discount_amount_yuan || 0)
|
||||
saleRatio = desiredPrice > 0 ? roundPricingRatio(coinWan / desiredPrice) : 0
|
||||
}
|
||||
const sellerCoinPrice = saleRatio > 0 ? roundMoney(coinWan / saleRatio) : 0
|
||||
const actualDiscountAmount = Math.max(0, roundMoney(referenceCoinPrice - sellerCoinPrice))
|
||||
const consumablePrice = roundMoney(
|
||||
normalizedQuantity(externalAdjustForm.awm_bullets) * 0.6 +
|
||||
normalizedQuantity(externalAdjustForm.level6_helmets) * 1.5 +
|
||||
normalizedQuantity(externalAdjustForm.level6_armor) * 2.5
|
||||
)
|
||||
const sellerTotalPrice = roundMoney(sellerCoinPrice + consumablePrice)
|
||||
const platformPricing = calculatePlatformPricing({
|
||||
coinMAmount: coinM,
|
||||
coinWanAmount: coinWan,
|
||||
sellerRatio: saleRatio,
|
||||
sellerCoinBasePrice: sellerCoinPrice,
|
||||
sellerTotalPrice,
|
||||
consumablePrice,
|
||||
salePriceConfig: salePriceConfig.value,
|
||||
})
|
||||
const referencePlatformPricing = calculatePlatformPricing({
|
||||
coinMAmount: coinM,
|
||||
coinWanAmount: coinWan,
|
||||
sellerRatio: referenceRatio,
|
||||
sellerCoinBasePrice: referenceCoinPrice,
|
||||
sellerTotalPrice: roundMoney(referenceCoinPrice + consumablePrice),
|
||||
consumablePrice,
|
||||
salePriceConfig: salePriceConfig.value,
|
||||
})
|
||||
return {
|
||||
referenceRatio,
|
||||
maxSaleRatio: roundPricingRatio(referenceRatio + 10),
|
||||
referenceCoinPrice,
|
||||
saleRatio,
|
||||
sellerCoinPrice,
|
||||
sellerTotalPrice,
|
||||
consumablePrice,
|
||||
actualDiscountAmount,
|
||||
buyerRatio: platformPricing.buyerRatio,
|
||||
buyerTotalPrice: platformPricing.buyerTotalPrice,
|
||||
buyerDiscountAmount: Math.max(
|
||||
0,
|
||||
roundMoney(referencePlatformPricing.buyerTotalPrice - platformPricing.buyerTotalPrice)
|
||||
),
|
||||
isSale: referenceRatio > 0 && saleRatio > referenceRatio,
|
||||
}
|
||||
})
|
||||
const statusTone = computed(() => {
|
||||
if (!listing.value) return 'info'
|
||||
if (listing.value.status === 'published') return 'success'
|
||||
@@ -159,6 +271,96 @@ async function loadListing() {
|
||||
}
|
||||
}
|
||||
|
||||
async function openExternalAdjustment() {
|
||||
if (!listing.value || !canAdjustExternal.value || externalAdjustmentLocked.value) return
|
||||
const row = listing.value
|
||||
const referenceRatio = externalReferenceRatio.value
|
||||
const currentSaleRatio = sellerSaleRatio(row) || referenceRatio
|
||||
const referenceCoinPrice = referenceRatio > 0 ? roundMoney(getCoinWan(row) / referenceRatio) : 0
|
||||
const currentCoinPrice = sellerCoinBasePrice(row)
|
||||
externalAdjustForm.haf_coin_m = Number((row.haf_coin_amount / 1000000).toFixed(1))
|
||||
externalAdjustForm.awm_bullets = getResourceQuantity(row, 'awmAmmo')
|
||||
externalAdjustForm.level6_helmets = getResourceQuantity(row, 'helmet6')
|
||||
externalAdjustForm.level6_armor = getResourceQuantity(row, 'armor6')
|
||||
externalAdjustForm.deposit_yuan = centToYuan(row.deposit_amount_cent)
|
||||
externalAdjustForm.adjust_mode = 'discount_amount'
|
||||
externalAdjustForm.discount_amount_yuan = Math.max(
|
||||
0,
|
||||
roundMoney(referenceCoinPrice - currentCoinPrice)
|
||||
)
|
||||
externalAdjustForm.sale_ratio = currentSaleRatio
|
||||
externalAdjustForm.reason = ''
|
||||
externalAdjustVisible.value = true
|
||||
try {
|
||||
salePriceConfig.value = await fetchListingSalePriceConfig()
|
||||
} catch {
|
||||
salePriceConfig.value = emptyListingSalePriceConfig
|
||||
}
|
||||
}
|
||||
|
||||
async function submitExternalAdjustment() {
|
||||
if (!listing.value) return
|
||||
const preview = externalAdjustmentPreview.value
|
||||
if (externalAdjustForm.haf_coin_m <= 0) {
|
||||
ElMessage.warning('请输入有效的纯币数量')
|
||||
return
|
||||
}
|
||||
if (
|
||||
[
|
||||
externalAdjustForm.awm_bullets,
|
||||
externalAdjustForm.level6_helmets,
|
||||
externalAdjustForm.level6_armor,
|
||||
].some(value => !Number.isInteger(Number(value)) || Number(value) < 0)
|
||||
) {
|
||||
ElMessage.warning('额外物品数量必须是非负整数')
|
||||
return
|
||||
}
|
||||
if (
|
||||
preview.saleRatio < preview.referenceRatio ||
|
||||
preview.saleRatio > preview.maxSaleRatio ||
|
||||
preview.sellerCoinPrice <= 0
|
||||
) {
|
||||
ElMessage.warning(
|
||||
`发布比例应在 1:${ratioNumber(preview.referenceRatio)} 至 1:${ratioNumber(preview.maxSaleRatio)} 之间`
|
||||
)
|
||||
return
|
||||
}
|
||||
if (preview.consumablePrice > 0 && externalAdjustForm.deposit_yuan <= preview.consumablePrice) {
|
||||
ElMessage.warning('押金必须大于额外物品总价值')
|
||||
return
|
||||
}
|
||||
if (!externalAdjustForm.reason.trim()) {
|
||||
ElMessage.warning('请填写调整原因')
|
||||
return
|
||||
}
|
||||
externalAdjustSaving.value = true
|
||||
try {
|
||||
listing.value = await adjustExternalListing(listing.value.id, {
|
||||
haf_coin_amount: Math.round(externalAdjustForm.haf_coin_m * 1000000),
|
||||
awm_bullets: normalizedQuantity(externalAdjustForm.awm_bullets),
|
||||
level6_helmets: normalizedQuantity(externalAdjustForm.level6_helmets),
|
||||
level6_armor: normalizedQuantity(externalAdjustForm.level6_armor),
|
||||
deposit_amount_cent: yuanToCent(externalAdjustForm.deposit_yuan),
|
||||
adjust_mode: externalAdjustForm.adjust_mode,
|
||||
discount_amount_cent:
|
||||
externalAdjustForm.adjust_mode === 'discount_amount'
|
||||
? yuanToCent(externalAdjustForm.discount_amount_yuan)
|
||||
: undefined,
|
||||
sale_ratio:
|
||||
externalAdjustForm.adjust_mode === 'sale_ratio'
|
||||
? Number(externalAdjustForm.sale_ratio)
|
||||
: undefined,
|
||||
reason: externalAdjustForm.reason.trim(),
|
||||
})
|
||||
externalAdjustVisible.value = false
|
||||
ElMessage.success('外部商品已调整')
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '调整失败'))
|
||||
} finally {
|
||||
externalAdjustSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function copyListingCode() {
|
||||
if (!listing.value) return
|
||||
try {
|
||||
@@ -302,6 +504,34 @@ function platformMarkupAmount(row: Listing) {
|
||||
return Math.max(0, roundMoney(buyerTotalPrice(row) - sellerTotalPrice(row)))
|
||||
}
|
||||
|
||||
function sellerSaleRatio(row: Listing) {
|
||||
const value = breakdownNumber(row, 'seller_ratio')
|
||||
if (value > 0) return value
|
||||
const price = sellerCoinBasePrice(row)
|
||||
return price > 0 ? roundPricingRatio(getCoinWan(row) / price) : 0
|
||||
}
|
||||
|
||||
function buyerSaleRatio(row: Listing) {
|
||||
const value = breakdownNumber(row, 'buyer_ratio')
|
||||
if (value > 0) return value
|
||||
const price = buyerTotalPrice(row) - getListingConsumablePrice(row)
|
||||
return price > 0 ? roundPricingRatio(getCoinWan(row) / price) : 0
|
||||
}
|
||||
|
||||
function ratioNumber(value: number) {
|
||||
const rounded = roundPricingRatio(value)
|
||||
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)
|
||||
}
|
||||
|
||||
function ratioText(value: number) {
|
||||
return value > 0 ? `1:${ratioNumber(value)}` : '-'
|
||||
}
|
||||
|
||||
function normalizedQuantity(value: number) {
|
||||
const quantity = Number(value || 0)
|
||||
return Number.isFinite(quantity) ? Math.max(0, Math.trunc(quantity)) : 0
|
||||
}
|
||||
|
||||
function assetText(row: Listing, key: string) {
|
||||
const value = readAssetString(row, key)
|
||||
if (value) return value
|
||||
@@ -359,6 +589,9 @@ async function openScreenshot(url: string) {
|
||||
<span>{{ listing.rank_level || '-' }}</span>
|
||||
<span>{{ listing.login_platform || '-' }}</span>
|
||||
<span>账号 {{ listing.account_id }}</span>
|
||||
<span v-if="listing.is_external_upload"
|
||||
>外部 API · {{ listing.source_channel || '未填写' }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
@@ -366,6 +599,15 @@ async function openScreenshot(url: string) {
|
||||
<RouterLink :to="adminPath('listings')">
|
||||
<el-button>返回列表</el-button>
|
||||
</RouterLink>
|
||||
<el-button
|
||||
v-if="canAdjustExternal"
|
||||
type="primary"
|
||||
:icon="EditPen"
|
||||
:disabled="externalAdjustmentLocked"
|
||||
@click="openExternalAdjustment"
|
||||
>
|
||||
调整商品
|
||||
</el-button>
|
||||
<el-button
|
||||
class="transfer-owner-trigger"
|
||||
type="success"
|
||||
@@ -382,6 +624,152 @@ async function openScreenshot(url: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="externalAdjustVisible"
|
||||
title="调整外部商品"
|
||||
width="760px"
|
||||
class="external-adjust-dialog"
|
||||
>
|
||||
<el-form label-width="112px" class="external-adjust-form">
|
||||
<div class="external-adjust-grid">
|
||||
<el-form-item label="纯币数量(M)" required>
|
||||
<el-input-number
|
||||
v-model="externalAdjustForm.haf_coin_m"
|
||||
:min="0.1"
|
||||
:step="1"
|
||||
:precision="1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="押金(元)" required>
|
||||
<el-input-number
|
||||
v-model="externalAdjustForm.deposit_yuan"
|
||||
:min="0"
|
||||
:step="10"
|
||||
:precision="1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="AWM子弹">
|
||||
<el-input-number
|
||||
v-model="externalAdjustForm.awm_bullets"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="六级头">
|
||||
<el-input-number
|
||||
v-model="externalAdjustForm.level6_helmets"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="六级甲">
|
||||
<el-input-number
|
||||
v-model="externalAdjustForm.level6_armor"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item label="调整方式" required>
|
||||
<el-segmented v-model="externalAdjustForm.adjust_mode" :options="externalAdjustModes" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="externalAdjustForm.adjust_mode === 'discount_amount'"
|
||||
label="参考价降价(元)"
|
||||
required
|
||||
>
|
||||
<el-input-number
|
||||
v-model="externalAdjustForm.discount_amount_yuan"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="目标发布比例" required>
|
||||
<el-input-number
|
||||
v-model="externalAdjustForm.sale_ratio"
|
||||
:min="externalAdjustmentPreview.referenceRatio"
|
||||
:max="externalAdjustmentPreview.maxSaleRatio"
|
||||
:step="0.1"
|
||||
:precision="1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<div class="external-adjust-preview">
|
||||
<div>
|
||||
<span>API参考比例</span>
|
||||
<strong>{{ ratioText(externalAdjustmentPreview.referenceRatio) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>调整后比例</span>
|
||||
<strong>{{ ratioText(externalAdjustmentPreview.saleRatio) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>参考纯币价</span>
|
||||
<strong>{{ moneyYuan(externalAdjustmentPreview.referenceCoinPrice) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>调整后纯币价</span>
|
||||
<strong>{{ moneyYuan(externalAdjustmentPreview.sellerCoinPrice) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>号主实际让利</span>
|
||||
<strong>{{ moneyYuan(externalAdjustmentPreview.actualDiscountAmount) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>额外物品</span>
|
||||
<strong>{{ moneyYuan(externalAdjustmentPreview.consumablePrice) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>号主合计</span>
|
||||
<strong>{{ moneyYuan(externalAdjustmentPreview.sellerTotalPrice) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>租客售价</span>
|
||||
<strong>{{ moneyYuan(externalAdjustmentPreview.buyerTotalPrice) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>租客实际降价</span>
|
||||
<strong>{{ moneyYuan(externalAdjustmentPreview.buyerDiscountAmount) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>展示状态</span>
|
||||
<el-tag :type="externalAdjustmentPreview.isSale ? 'danger' : 'info'" effect="light">
|
||||
{{ externalAdjustmentPreview.isSale ? '特惠' : '常规' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form-item label="调整原因" required>
|
||||
<el-input
|
||||
v-model="externalAdjustForm.reason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="255"
|
||||
show-word-limit
|
||||
placeholder="填写用户申请内容或资产修正原因"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="externalAdjustVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="externalAdjustSaving" @click="submitExternalAdjustment">
|
||||
保存调整
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<div v-if="listing" class="listing-summary-bar">
|
||||
<div>
|
||||
<span>商品状态</span>
|
||||
@@ -885,6 +1273,62 @@ async function openScreenshot(url: string) {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.external-adjust-form {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
:global(.external-adjust-dialog) {
|
||||
max-width: calc(100vw - 32px);
|
||||
}
|
||||
|
||||
:global(.external-adjust-dialog .el-dialog__body) {
|
||||
max-height: calc(100vh - 180px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.external-adjust-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 18px;
|
||||
}
|
||||
|
||||
.external-adjust-grid :deep(.el-input-number),
|
||||
.external-adjust-form :deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.external-adjust-preview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 1px;
|
||||
overflow: hidden;
|
||||
margin: 4px 0 18px 112px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
background: #e5e7eb;
|
||||
}
|
||||
|
||||
.external-adjust-preview > div {
|
||||
display: grid;
|
||||
min-height: 70px;
|
||||
align-content: center;
|
||||
gap: 7px;
|
||||
padding: 10px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.external-adjust-preview span {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.external-adjust-preview strong {
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.transfer-dialog {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
@@ -1046,6 +1490,15 @@ async function openScreenshot(url: string) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.external-adjust-grid,
|
||||
.external-adjust-preview {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.external-adjust-preview {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.transfer-arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface Listing {
|
||||
owner_phone?: string
|
||||
owner_nickname?: string
|
||||
source_channel?: string
|
||||
is_external_upload?: boolean
|
||||
title: string
|
||||
description: string
|
||||
game_name: string
|
||||
@@ -255,6 +256,31 @@ export interface AdminListingPriceAdjustPayload {
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export type AdminExternalListingAdjustMode = 'discount_amount' | 'sale_ratio'
|
||||
|
||||
export interface AdminExternalListingAdjustPayload {
|
||||
haf_coin_amount: number
|
||||
awm_bullets: number
|
||||
level6_helmets: number
|
||||
level6_armor: number
|
||||
deposit_amount_cent: number
|
||||
adjust_mode: AdminExternalListingAdjustMode
|
||||
discount_amount_cent?: number
|
||||
sale_ratio?: number
|
||||
reason: string
|
||||
}
|
||||
|
||||
export async function adjustExternalListing(
|
||||
id: number,
|
||||
payload: AdminExternalListingAdjustPayload
|
||||
) {
|
||||
const { data } = await apiClient.put<ApiResponse<Listing>>(
|
||||
`/admin/listings/${id}/external-adjustment`,
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function adjustListingReviewPrice(
|
||||
id: number,
|
||||
payload: AdminListingPriceAdjustPayload
|
||||
|
||||
@@ -33,6 +33,7 @@ export const AUDIT_ACTION_OPTIONS: AuditOption[] = [
|
||||
{ value: 'listing.mark_abnormal', label: '商品标记异常', group: '商品', highRisk: true },
|
||||
{ value: 'listing.transfer_owner', label: '转移商品所有权', group: '商品', highRisk: true },
|
||||
{ value: 'listing.adjust_review_price', label: '审核调价', group: '商品' },
|
||||
{ value: 'listing.adjust_external', label: '调整外部商品', group: '商品', highRisk: true },
|
||||
// 订单
|
||||
{ value: 'order.admin_close', label: '客服关闭订单', group: '订单', highRisk: true },
|
||||
{ value: 'order.admin_seal', label: '客服封存订单', group: '订单', highRisk: true },
|
||||
|
||||
Reference in New Issue
Block a user