后端可视化调试
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import AdminPaginationBar from '@/components/admin/AdminPaginationBar.vue'
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
@@ -7,11 +7,12 @@ import { showConfirm, showError, showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
createAdminInventoryItem,
|
||||
fetchAdminInventoryItems,
|
||||
fetchAdminInventorySkuSuggestions,
|
||||
importAdminInventoryItems,
|
||||
invalidateAdminInventoryItem,
|
||||
releaseAdminInventoryItem,
|
||||
} from '@/services/admin'
|
||||
import type { AdminInventoryItemListItem, AdminPagination } from '@/types/admin'
|
||||
import type { AdminInventoryItemListItem, AdminInventorySkuSuggestion, AdminPagination } from '@/types/admin'
|
||||
import { hasAdminRole } from '@/utils/admin-auth'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import {
|
||||
@@ -55,6 +56,8 @@ const entryCredentialType = ref('tencent_code')
|
||||
const entryBatchNo = ref('')
|
||||
const singleDisplayValue = ref('')
|
||||
const bulkText = ref('')
|
||||
const skuSuggestionsLoading = ref(false)
|
||||
const inventorySkuSuggestions = ref<AdminInventorySkuSuggestion[]>([])
|
||||
|
||||
const items = ref<AdminInventoryItemListItem[]>([])
|
||||
const pagination = ref<AdminPagination>({
|
||||
@@ -158,6 +161,19 @@ const pageOverview = computed(() => ({
|
||||
groupCount: inventoryGroups.value.length,
|
||||
}))
|
||||
|
||||
const entrySkuSuggestions = computed(() => {
|
||||
const keyword = entrySkuCode.value.trim().toLowerCase()
|
||||
const suggestions = inventorySkuSuggestions.value.filter((item) => item.credentialType === entryCredentialType.value)
|
||||
|
||||
if (!keyword) {
|
||||
return suggestions.slice(0, 12)
|
||||
}
|
||||
|
||||
return suggestions
|
||||
.filter((item) => item.skuCode.toLowerCase().includes(keyword))
|
||||
.slice(0, 12)
|
||||
})
|
||||
|
||||
function getCredentialTypeLabel(value: string) {
|
||||
const normalized = String(value || '').trim()
|
||||
const option = adminInventoryCredentialTypeOptions.find((item) => item.value === normalized)
|
||||
@@ -168,7 +184,7 @@ function getCredentialTypeHint(value: string) {
|
||||
const normalized = String(value || '').trim()
|
||||
|
||||
if (normalized === 'tencent_code') {
|
||||
return '当前腾讯自动领取链路会优先预占这一类兑换码,三角洲行动这类场景就使用这个类型。'
|
||||
return '当前腾讯自动领取链路会优先预占这一类兑换码,三角洲行动这类场景就使用这个类型。内部 SKU 支持中文、英文和数字混用。'
|
||||
}
|
||||
|
||||
if (normalized === 'card_password') {
|
||||
@@ -190,6 +206,14 @@ function getCredentialTypeHint(value: string) {
|
||||
return '库存预占时会按凭据类型与内部履约 SKU 一起匹配。'
|
||||
}
|
||||
|
||||
function getSkuNamingHint() {
|
||||
if (entrySkuSuggestions.value.length > 0) {
|
||||
return `当前凭据类型下已有 ${entrySkuSuggestions.value.length} 个可复用的内部 SKU,也可以直接输入新的中文或英文名称。`
|
||||
}
|
||||
|
||||
return '内部 SKU 支持中文、英文、数字和常见连接符。建议同一类商品长期使用同一个命名,方便共用库存池。'
|
||||
}
|
||||
|
||||
function formatInventoryStatus(item: AdminInventoryItemListItem) {
|
||||
if (item.status === 'consumed') {
|
||||
return '已完成发放'
|
||||
@@ -241,6 +265,10 @@ function useGroupAsEntry(group: InventoryPoolGroup) {
|
||||
showSuccess(`已把 ${group.skuCode} / ${group.credentialType} 填入入库表单`)
|
||||
}
|
||||
|
||||
function applySkuSuggestion(skuCode: string) {
|
||||
entrySkuCode.value = String(skuCode || '').trim()
|
||||
}
|
||||
|
||||
function buildImportRows() {
|
||||
const lines = bulkText.value
|
||||
.split('\n')
|
||||
@@ -312,6 +340,22 @@ async function loadInventoryItems(page = pagination.value.page) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInventorySkuSuggestions() {
|
||||
skuSuggestionsLoading.value = true
|
||||
|
||||
try {
|
||||
const response = await fetchAdminInventorySkuSuggestions({
|
||||
credentialType: entryCredentialType.value.trim() || 'tencent_code',
|
||||
limit: 50,
|
||||
})
|
||||
inventorySkuSuggestions.value = response.data.items
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '读取内部 SKU 建议失败')
|
||||
} finally {
|
||||
skuSuggestionsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
filterSkuCode.value = ''
|
||||
filterCredentialType.value = ''
|
||||
@@ -402,7 +446,16 @@ async function runRowAction(
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadInventoryItems)
|
||||
watch(entryCredentialType, () => {
|
||||
void loadInventorySkuSuggestions()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
loadInventoryItems(),
|
||||
loadInventorySkuSuggestions(),
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -463,12 +516,27 @@ onMounted(loadInventoryItems)
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<h2>入库与导入</h2>
|
||||
<p>这里填写的是内部履约 SKU,不是平台侧 `sku_code`。例如三角洲行动自动领取统一使用 `sjz_hdl_test01`,这样同类订单才能共用一个库存池。</p>
|
||||
<p>这里填写的是内部履约 SKU,不是平台侧 `sku_code`。内部 SKU 支持中文命名;同一类商品建议长期复用同一个名字,这样订单才能稳定共用库存池。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-grid">
|
||||
<input v-model="entrySkuCode" class="text-input" placeholder="内部履约 SKU,例如 sjz_hdl_test01" />
|
||||
<div class="sku-input-wrap">
|
||||
<input
|
||||
v-model="entrySkuCode"
|
||||
list="inventory-sku-suggestions"
|
||||
class="text-input"
|
||||
placeholder="内部履约 SKU,可选已有同类型,也可自定义新的中文名"
|
||||
/>
|
||||
<datalist id="inventory-sku-suggestions">
|
||||
<option
|
||||
v-for="suggestion in entrySkuSuggestions"
|
||||
:key="`${suggestion.credentialType}-${suggestion.skuCode}`"
|
||||
:value="suggestion.skuCode"
|
||||
:label="`${suggestion.skuCode} · 可用 ${suggestion.availableCount} / 共 ${suggestion.totalCount}`"
|
||||
/>
|
||||
</datalist>
|
||||
</div>
|
||||
<select v-model="entryCredentialType" class="text-input select-input">
|
||||
<option
|
||||
v-for="option in adminInventoryCredentialTypeOptions.filter((item) => item.value)"
|
||||
@@ -481,6 +549,24 @@ onMounted(loadInventoryItems)
|
||||
<input v-model="entryBatchNo" class="text-input" placeholder="批次号,可选" />
|
||||
</div>
|
||||
|
||||
<div class="sku-helper-bar">
|
||||
<span class="section-tip sku-helper-copy">{{ getSkuNamingHint() }}</span>
|
||||
<span v-if="skuSuggestionsLoading" class="section-tip">正在加载同类型内部 SKU…</span>
|
||||
</div>
|
||||
|
||||
<div v-if="entrySkuSuggestions.length > 0" class="sku-chip-row">
|
||||
<button
|
||||
v-for="suggestion in entrySkuSuggestions"
|
||||
:key="`${suggestion.credentialType}-${suggestion.skuCode}-chip`"
|
||||
type="button"
|
||||
class="sku-chip"
|
||||
@click="applySkuSuggestion(suggestion.skuCode)"
|
||||
>
|
||||
<span class="sku-chip-title">{{ suggestion.skuCode }}</span>
|
||||
<span class="sku-chip-meta">可用 {{ suggestion.availableCount }} / 共 {{ suggestion.totalCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="helper-grid">
|
||||
<div class="helper-item">
|
||||
<span class="helper-title">当前默认匹配</span>
|
||||
@@ -756,6 +842,10 @@ onMounted(loadInventoryItems)
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sku-input-wrap {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.single-row,
|
||||
.action-row {
|
||||
display: flex;
|
||||
@@ -771,6 +861,52 @@ onMounted(loadInventoryItems)
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.sku-helper-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.sku-helper-copy {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sku-chip-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.sku-chip {
|
||||
display: inline-grid;
|
||||
gap: 4px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(86, 108, 138, 0.16);
|
||||
background: linear-gradient(180deg, rgba(244, 248, 255, 0.92), rgba(255, 255, 255, 0.98));
|
||||
color: #1d3555;
|
||||
cursor: pointer;
|
||||
transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
|
||||
}
|
||||
|
||||
.sku-chip:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: rgba(37, 89, 167, 0.26);
|
||||
box-shadow: 0 12px 24px rgba(37, 89, 167, 0.08);
|
||||
}
|
||||
|
||||
.sku-chip-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sku-chip-meta {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.helper-item {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
@@ -1057,7 +1193,8 @@ onMounted(loadInventoryItems)
|
||||
}
|
||||
|
||||
.single-row,
|
||||
.action-row {
|
||||
.action-row,
|
||||
.sku-helper-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user