优化快手小店核销界面

This commit is contained in:
yml2213
2026-05-26 12:55:15 +08:00
parent 6e85002724
commit 569d6a6e3d
7 changed files with 570 additions and 465 deletions
@@ -98,10 +98,6 @@ const {
hydrateKuaishouEticketConfig,
addKuaishouEticketShop,
removeKuaishouEticketShop,
isKuaishouEticketShopExpanded,
toggleKuaishouEticketShop,
expandAllKuaishouEticketShops,
collapseAllKuaishouEticketShops,
setKuaishouEticketDebugShop,
describeKuaishouEticketShop,
getKuaishouEticketShopInitial,
@@ -361,10 +357,6 @@ onMounted(loadConfigs)
:kuaishou-eticket-stats="kuaishouEticketStats"
:add-kuaishou-eticket-shop="addKuaishouEticketShop"
:remove-kuaishou-eticket-shop="removeKuaishouEticketShop"
:is-kuaishou-eticket-shop-expanded="isKuaishouEticketShopExpanded"
:toggle-kuaishou-eticket-shop="toggleKuaishouEticketShop"
:expand-all-kuaishou-eticket-shops="expandAllKuaishouEticketShops"
:collapse-all-kuaishou-eticket-shops="collapseAllKuaishouEticketShops"
:set-kuaishou-eticket-debug-shop="setKuaishouEticketDebugShop"
:describe-kuaishou-eticket-shop="describeKuaishouEticketShop"
:get-kuaishou-eticket-shop-initial="getKuaishouEticketShopInitial"
@@ -1,9 +1,8 @@
<script setup lang="ts">
import type {
AdminKuaishouEticketDetailResult,
AdminKuaishouEticketConsumeResult,
AdminKuaishouEticketDetailResult,
} from '@/types/admin'
import AdminResultCard from '@/components/admin/AdminResultCard.vue'
type KuaishouEticketDebugForm = {
selectedShopId: string
@@ -27,158 +26,137 @@ type Props = {
}
defineProps<Props>()
function formatDetailStatus(result: AdminKuaishouEticketDetailResult | null) {
if (!result) return '未查询'
if (result.ok) return result.alreadyConsumed ? '已核销' : '可核销'
return '查询异常'
}
function formatConsumeStatus(result: AdminKuaishouEticketConsumeResult | null) {
if (!result) return '未执行'
if (result.consumed) return '核销完成'
if (result.alreadyConsumed) return '已核销'
return result.ok ? '已返回' : '执行异常'
}
</script>
<template>
<el-card class="section-card">
<el-card class="section-card debug-card">
<template #header>
<div class="card-header">
<div>
<span class="card-title">快手小店核销调试</span>
<p class="card-desc">
这里使用"当前调试店铺" Cookie 执行查询与核销eTicketId / oid / formToken
都来自查询结果
</p>
<span class="card-title">核销调试</span>
<p class="card-desc">查询券码后自动回填 eTicketIdoidformToken再执行核销</p>
</div>
<div class="card-actions">
<el-button :loading="kuaishouEticketDetailing" @click="handleKuaishouEticketQueryDetail"
>查询核销信息</el-button
<el-button
:loading="kuaishouEticketDetailing"
@click="handleKuaishouEticketQueryDetail"
>
查询核销信息
</el-button>
<el-button
:loading="kuaishouEticketConsuming"
type="primary"
@click="handleKuaishouEticketConsume"
>执行核销</el-button
>
执行核销
</el-button>
</div>
</div>
</template>
<div class="debug-layout">
<div class="debug-main">
<el-form label-width="auto" label-position="top">
<el-form-item label="调试店铺">
<el-select
v-model="kuaishouEticketForm.selectedShopId"
placeholder="请选择店铺"
class="w-full"
>
<el-option
v-for="shop in kuaishouEticketShops"
:key="shop.id"
:label="shop.kshopName || shop.shopId || '未命名店铺'"
:value="shop.id"
<div class="debug-grid">
<section class="query-panel">
<el-form label-position="top">
<div class="query-grid">
<el-form-item label="调试店铺">
<el-select
v-model="kuaishouEticketForm.selectedShopId"
placeholder="请选择店铺"
class="w-full"
>
<el-option
v-for="shop in kuaishouEticketShops"
:key="shop.id"
:label="shop.kshopName || shop.shopId || '未命名店铺'"
:value="shop.id"
/>
</el-select>
</el-form-item>
<el-form-item label="查询券码">
<el-input
v-model="kuaishouEticketForm.queryTicketCode"
placeholder="用于查询核销信息"
/>
</el-select>
</el-form-item>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="查询券码">
<el-input
v-model="kuaishouEticketForm.queryTicketCode"
placeholder="用于查询核销信息"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="核销数量">
<el-input-number
v-model="kuaishouEticketForm.num"
:min="1"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="storeId">
<el-input v-model="kuaishouEticketForm.storeId" placeholder="默认 0" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<el-card shadow="never" class="auto-fill-card">
<template #header>
<div>
<span class="card-title">自动回填参数</span>
<span class="text-muted ml-2">查询后生成</span>
</el-form-item>
<el-form-item label="核销数量">
<el-input-number
v-model="kuaishouEticketForm.num"
:min="1"
controls-position="right"
class="w-full"
/>
</el-form-item>
<el-form-item label="storeId">
<el-input v-model="kuaishouEticketForm.storeId" placeholder="默认 0" />
</el-form-item>
</div>
</template>
<el-form label-width="auto" label-position="top">
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="eTicketId">
<el-input
:model-value="kuaishouEticketForm.eTicketId"
readonly
placeholder="查询后自动回填"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="oid">
<el-input
:model-value="kuaishouEticketForm.oid"
readonly
placeholder="查询后自动回填"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="formToken">
<el-input
:model-value="kuaishouEticketForm.formToken"
readonly
placeholder="查询后自动回填"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
<div class="result-grid mt-4">
<AdminResultCard
label="详情查询"
:value="
kuaishouEticketDetailResult
? kuaishouEticketDetailResult.ok
? '成功'
: '已返回'
: '未执行'
"
>
<p v-if="kuaishouEticketDetailResult?.detail">
{{
kuaishouEticketDetailResult.goods?.itemTitle ||
kuaishouEticketDetailResult.goods?.skuDesc ||
'-'
}} 剩余 {{ kuaishouEticketDetailResult.detail.leftCount }} /
{{ kuaishouEticketDetailResult.detail.totalCount }}
</p>
<p v-else>先通过查询券码获取核销详情并自动带出核销参数</p>
</AdminResultCard>
<AdminResultCard
label="核销执行"
:value="
kuaishouEticketConsumeResult
? kuaishouEticketConsumeResult.consumed
? '已处理'
: '已返回'
: '未执行'
"
>
<p v-if="kuaishouEticketConsumeResult">
{{
kuaishouEticketConsumeResult.errorMessage ||
(kuaishouEticketConsumeResult.ok ? '核销成功' : '接口已返回结果')
}}
</p>
<p v-else>查询详情后再执行核销会更稳妥</p>
</AdminResultCard>
<div class="auto-fill-row">
<div class="auto-fill-item">
<span>eTicketId</span>
<strong>{{ kuaishouEticketForm.eTicketId || '查询后回填' }}</strong>
</div>
<div class="auto-fill-item">
<span>oid</span>
<strong>{{ kuaishouEticketForm.oid || '查询后回填' }}</strong>
</div>
<div class="auto-fill-item">
<span>formToken</span>
<strong>{{ kuaishouEticketForm.formToken || '查询后回填' }}</strong>
</div>
</div>
</section>
<aside class="result-panel">
<div class="status-card">
<span>详情查询</span>
<strong>{{ formatDetailStatus(kuaishouEticketDetailResult) }}</strong>
<p v-if="kuaishouEticketDetailResult?.detail">
{{
kuaishouEticketDetailResult.goods?.itemTitle ||
kuaishouEticketDetailResult.goods?.skuDesc ||
'商品信息未返回'
}}
</p>
<p v-else>
{{ kuaishouEticketDetailResult?.errorMessage || '先输入券码并查询详情' }}
</p>
<div v-if="kuaishouEticketDetailResult?.detail" class="count-row">
<span>剩余 {{ kuaishouEticketDetailResult.detail.leftCount }}</span>
<span>总数 {{ kuaishouEticketDetailResult.detail.totalCount }}</span>
<span>状态 {{ kuaishouEticketDetailResult.detail.status || '-' }}</span>
</div>
</div>
<div class="status-card">
<span>核销执行</span>
<strong>{{ formatConsumeStatus(kuaishouEticketConsumeResult) }}</strong>
<p>
{{
kuaishouEticketConsumeResult?.errorMessage ||
(kuaishouEticketConsumeResult?.ok ? '接口已返回结果。' : '查询成功后再执行核销。')
}}
</p>
<div v-if="kuaishouEticketConsumeResult" class="count-row">
<span>数量 {{ kuaishouEticketConsumeResult.num }}</span>
<span>requestId {{ kuaishouEticketConsumeResult.requestId || '-' }}</span>
</div>
</div>
</aside>
</div>
</el-card>
</template>
@@ -195,29 +173,103 @@ defineProps<Props>()
flex-wrap: wrap;
}
.debug-layout {
.debug-grid {
display: grid;
grid-template-columns: minmax(0, 1.7fr) minmax(260px, 0.9fr);
grid-template-columns: minmax(0, 1.6fr) minmax(300px, 0.8fr);
gap: var(--space-4);
}
.auto-fill-card {
background: var(--bg-subtle);
.query-panel,
.result-panel {
min-width: 0;
}
.result-grid {
.query-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: minmax(180px, 1.2fr) minmax(220px, 1.6fr) minmax(120px, 0.8fr) minmax(
120px,
0.8fr
);
gap: var(--space-3);
}
.text-muted {
font-size: var(--text-xs);
color: var(--text-muted);
.auto-fill-row {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-3);
margin-top: var(--space-2);
}
@media (max-width: 900px) {
.debug-layout {
.auto-fill-item,
.status-card {
min-width: 0;
padding: var(--space-3);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
background: var(--bg-subtle);
}
.auto-fill-item {
display: grid;
gap: var(--space-1);
}
.auto-fill-item span,
.status-card > span {
color: var(--text-muted);
font-size: var(--text-xs);
}
.auto-fill-item strong,
.status-card strong {
color: var(--text-primary);
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: 700;
overflow-wrap: anywhere;
}
.result-panel {
display: grid;
gap: var(--space-3);
align-content: start;
}
.status-card {
display: grid;
gap: var(--space-2);
}
.status-card p {
margin: 0;
color: var(--text-muted);
font-size: var(--text-xs);
line-height: 1.6;
}
.count-row {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
color: var(--text-muted);
font-size: var(--text-xs);
}
.count-row span {
padding: 2px var(--space-2);
border-radius: var(--radius-sm);
background: var(--bg-card);
}
@media (max-width: 1080px) {
.debug-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 760px) {
.query-grid,
.auto-fill-row {
grid-template-columns: 1fr;
}
}
@@ -7,27 +7,67 @@ defineProps<Props>()
</script>
<template>
<el-card class="section-card">
<template #header>
<span class="card-title">说明信息</span>
</template>
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="配置文件">{{
kuaishouEticketFilePath || '-'
}}</el-descriptions-item>
<el-descriptions-item label="职责">
这是独立的新流程只负责快手小店券核销调试与配置不接入当前已完成的订单来源和履约主链路
</el-descriptions-item>
<el-descriptions-item label="建议流程">
先为每个店铺录入 kshopName + Cookie可用 info 接口自动识别店铺名与店铺
ID保存后选择目标店铺再查询券码并执行核销
</el-descriptions-item>
</el-descriptions>
</el-card>
<section class="info-strip">
<div class="info-main">
<span class="card-title">快手小店核销</span>
<p class="card-desc">
独立维护店铺 Cookie选择目标店铺后查询券码并执行核销
</p>
</div>
<div class="info-meta">
<span>配置文件</span>
<strong>{{ kuaishouEticketFilePath || '-' }}</strong>
</div>
</section>
</template>
<style scoped>
.section-card {
.info-strip {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
padding: var(--space-4) var(--space-5);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
background: var(--bg-card);
}
.info-main {
min-width: 0;
}
.info-main .card-desc {
margin-top: var(--space-1);
}
.info-meta {
display: grid;
gap: var(--space-1);
min-width: 260px;
padding: var(--space-3);
border-radius: var(--radius-md);
background: var(--bg-subtle);
color: var(--text-muted);
font-size: var(--text-xs);
}
.info-meta strong {
color: var(--text-primary);
font-family: var(--font-mono);
font-weight: 600;
overflow-wrap: anywhere;
}
@media (max-width: 760px) {
.info-strip {
align-items: stretch;
flex-direction: column;
padding: var(--space-4);
}
.info-meta {
min-width: 0;
}
}
</style>
@@ -1,9 +1,6 @@
<script setup lang="ts">
import type { EditableKuaishouEticketShop } from '../../composables/types'
import type {
AdminKuaishouEticketShopInfoResult,
} from '@/types/admin'
import AdminResultCard from '@/components/admin/AdminResultCard.vue'
import type { AdminKuaishouEticketShopInfoResult } from '@/types/admin'
type KuaishouEticketStats = {
configuredShopCount: number
@@ -27,10 +24,6 @@ type Props = {
kuaishouEticketStats: KuaishouEticketStats
addKuaishouEticketShop: () => void
removeKuaishouEticketShop: (id: string) => void
isKuaishouEticketShopExpanded: (id: string) => boolean
toggleKuaishouEticketShop: (id: string) => void
expandAllKuaishouEticketShops: () => void
collapseAllKuaishouEticketShops: () => void
setKuaishouEticketDebugShop: (id: string) => void
describeKuaishouEticketShop: (shop: EditableKuaishouEticketShop) => string
getKuaishouEticketShopInitial: (shop: EditableKuaishouEticketShop) => string
@@ -42,214 +35,187 @@ defineProps<Props>()
</script>
<template>
<el-card class="section-card">
<el-card class="section-card shop-workspace">
<template #header>
<div class="card-header">
<div>
<span class="card-title">快手小店店铺配置</span>
<p class="card-desc">
支持多店铺配置建议先粘贴 Cookie再用 info 接口自动识别 kshopName /
shopId最后统一保存到文件
</p>
<span class="card-title">店铺配置</span>
<p class="card-desc">维护多店铺 Cookie当前选中店铺会直接用于下方查询与核销</p>
</div>
<div class="card-actions">
<el-button
:disabled="kuaishouEticketShops.length === 0"
@click="expandAllKuaishouEticketShops"
>全部展开</el-button
>
<el-button
:disabled="kuaishouEticketShops.length === 0"
@click="collapseAllKuaishouEticketShops"
>全部折叠</el-button
>
<el-button @click="addKuaishouEticketShop">新增店铺</el-button>
<el-button
:loading="kuaishouEticketSaving"
type="primary"
@click="handleKuaishouEticketSaveSource"
>保存配置</el-button
>
保存配置
</el-button>
</div>
</div>
</template>
<el-form label-width="auto" label-position="top">
<el-form-item label="Base URL">
<el-input
v-model="kuaishouEticketForm.baseUrl"
placeholder="https://s.kwaixiaodian.com"
/>
</el-form-item>
</el-form>
<div class="workspace-grid">
<aside class="shop-sidebar">
<div class="sidebar-head">
<div>
<strong>{{ kuaishouEticketStats.configuredShopCount }} 家店铺</strong>
<p>Cookie 就绪 {{ kuaishouEticketStats.cookieReadyCount }} </p>
</div>
<el-tag
:type="kuaishouEticketStats.selectedShopReady ? 'success' : 'warning'"
effect="plain"
>
{{ kuaishouEticketStats.selectedShopReady ? '可调试' : '待补全' }}
</el-tag>
</div>
<el-empty
v-if="kuaishouEticketShops.length === 0"
description="当前还没有快手小店店铺配置,先新增一条。"
/>
<div v-else class="shop-panels">
<div class="shop-list">
<el-collapse
:model-value="
kuaishouEticketShops
.filter((s) => isKuaishouEticketShopExpanded(s.id))
.map((s) => s.id)
"
>
<el-collapse-item
<div v-if="kuaishouEticketShops.length > 0" class="shop-list">
<button
v-for="shop in kuaishouEticketShops"
:key="shop.id"
:name="shop.id"
:class="[
'shop-panel',
{
'is-debug': kuaishouEticketForm.selectedShopId === shop.id,
},
]"
type="button"
class="shop-item"
:class="{ 'shop-item--active': kuaishouEticketForm.selectedShopId === shop.id }"
@click="setKuaishouEticketDebugShop(shop.id)"
>
<template #title>
<div class="shop-title-bar">
<div class="shop-avatar">
<img
v-if="shop.userAvatar"
:src="shop.userAvatar"
:alt="shop.kshopName || shop.shopId || '店铺头像'"
/>
<span v-else>{{ getKuaishouEticketShopInitial(shop) }}</span>
</div>
<div class="shop-info">
<div class="shop-name-row">
<strong>{{ shop.kshopName || shop.shopId || '未命名店铺' }}</strong>
<el-tag :type="shop.enabled ? 'success' : 'info'" size="small">{{
shop.enabled ? '已启用' : '已停用'
}}</el-tag>
<el-tag
v-if="kuaishouEticketForm.selectedShopId === shop.id"
type="primary"
size="small"
>当前调试</el-tag
>
</div>
<div class="shop-meta">
<span>店铺 ID{{ shop.shopId || '等待 info 自动识别' }}</span>
<el-tag
:type="Boolean(shop.cookie.trim()) ? 'success' : 'warning'"
size="small"
>
{{ shop.cookie.trim() ? 'Cookie 已粘贴' : '待补 Cookie' }}
</el-tag>
<el-tag
:type="Boolean(shop.userAvatar || shop.shopId.trim()) ? 'success' : 'info'"
size="small"
>
{{ shop.userAvatar || shop.shopId.trim() ? '信息已识别' : '未读取信息' }}
</el-tag>
</div>
</div>
<div class="shop-avatar">
<img
v-if="shop.userAvatar"
:src="shop.userAvatar"
:alt="shop.kshopName || shop.shopId || '店铺头像'"
/>
<span v-else>{{ getKuaishouEticketShopInitial(shop) }}</span>
</div>
<div class="shop-summary">
<div class="shop-name">
<strong>{{ shop.kshopName || shop.shopId || '未命名店铺' }}</strong>
<el-tag :type="shop.enabled ? 'success' : 'info'" size="small">
{{ shop.enabled ? '启用' : '停用' }}
</el-tag>
</div>
</template>
<p>{{ shop.shopId || '等待识别 shopId' }}</p>
<div class="shop-flags">
<span :class="{ 'is-ok': shop.cookie.trim() }">
{{ shop.cookie.trim() ? 'Cookie 已填' : '缺 Cookie' }}
</span>
<span v-if="kuaishouEticketForm.selectedShopId === shop.id">当前调试</span>
</div>
</div>
</button>
</div>
<el-form label-width="auto" label-position="top">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="kshopName">
<el-input v-model="shop.kshopName" placeholder="例如 稚嫩游戏交易店" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="店铺 ID">
<el-input
v-model="shop.shopId"
readonly
placeholder="通过 info 接口自动识别"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="Cookie">
<div v-else class="compact-empty">
<strong>还没有店铺</strong>
<p>新增店铺后粘贴 Cookie再读取店铺信息确认归属</p>
<el-button type="primary" plain @click="addKuaishouEticketShop">新增店铺</el-button>
</div>
</aside>
<section class="shop-editor">
<el-form label-position="top" class="editor-form">
<div class="base-row">
<el-form-item label="Base URL">
<el-input
v-model="kuaishouEticketForm.baseUrl"
placeholder="https://s.kwaixiaodian.com"
/>
</el-form-item>
</div>
<template v-if="activeKuaishouEticketShop">
<div class="editor-head">
<div>
<strong>
{{
activeKuaishouEticketShop.kshopName ||
activeKuaishouEticketShop.shopId ||
'未命名店铺'
}}
</strong>
<p>{{ describeKuaishouEticketShop(activeKuaishouEticketShop) }}</p>
</div>
<div class="editor-actions">
<el-switch
v-model="activeKuaishouEticketShop.enabled"
inline-prompt
active-text="启用"
inactive-text="停用"
/>
<el-button
type="danger"
plain
size="small"
@click="removeKuaishouEticketShop(activeKuaishouEticketShop.id)"
>
删除
</el-button>
</div>
</div>
<div class="form-grid">
<el-form-item label="kshopName">
<el-input
v-model="shop.cookie"
type="textarea"
:rows="4"
placeholder="直接粘贴该店铺请求头中的完整 Cookie"
v-model="activeKuaishouEticketShop.kshopName"
placeholder="例如:稚嫩游戏交易店"
/>
</el-form-item>
</el-form>
<el-form-item label="店铺 ID">
<el-input
v-model="activeKuaishouEticketShop.shopId"
readonly
placeholder="通过 info 接口自动识别"
/>
</el-form-item>
</div>
<div class="shop-footer">
<div class="footer-left">
<el-switch v-model="shop.enabled" active-text="启用该店铺" />
<span v-if="shop.userAvatar" class="text-muted"
>已识别头像,可直接作为调试店铺</span
>
<span v-else class="text-muted">建议先读取店铺信息确认 Cookie 归属</span>
<el-form-item label="Cookie">
<el-input
v-model="activeKuaishouEticketShop.cookie"
type="textarea"
:rows="5"
placeholder="直接粘贴该店铺请求头中的完整 Cookie"
/>
</el-form-item>
<div class="editor-footer">
<div class="recognize-result">
<span>最近识别</span>
<strong>
{{
kuaishouEticketShopInfoResult?.shop.kshopName ||
kuaishouEticketShopInfoResult?.shop.shopId ||
'未执行'
}}
</strong>
<small v-if="kuaishouEticketShopInfoResult">
结算状态 {{ kuaishouEticketShopInfoResult.shop.settleStatus }}
</small>
</div>
<div class="footer-actions">
<el-button
:loading="kuaishouEticketResolvingShopId === shop.id"
size="small"
@click="handleKuaishouEticketResolveShopInfo(shop)"
:loading="kuaishouEticketResolvingShopId === activeKuaishouEticketShop.id"
@click="handleKuaishouEticketResolveShopInfo(activeKuaishouEticketShop)"
>
读取店铺信息
</el-button>
<el-button
size="small"
:type="kuaishouEticketForm.selectedShopId === shop.id ? 'success' : 'primary'"
@click="setKuaishouEticketDebugShop(shop.id)"
type="primary"
plain
@click="setKuaishouEticketDebugShop(activeKuaishouEticketShop.id)"
>
{{
kuaishouEticketForm.selectedShopId === shop.id
? '当前调试店铺'
: '设为调试店铺'
}}
设为调试店铺
</el-button>
<el-button size="small" type="danger" @click="removeKuaishouEticketShop(shop.id)"
>删除</el-button
>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</template>
<div class="side-panel">
<AdminResultCard label="店铺总数" :value="kuaishouEticketStats.configuredShopCount">
<p>其中 Cookie 就绪 {{ kuaishouEticketStats.cookieReadyCount }} 家。</p>
</AdminResultCard>
<AdminResultCard
label="当前调试店铺"
:value="
activeKuaishouEticketShop?.kshopName || activeKuaishouEticketShop?.shopId || '未选择'
"
>
<p>
{{
kuaishouEticketStats.selectedShopReady
? '已具备调试条件,可直接查询核销信息。'
: '请选择店铺并补全 Cookie。'
}}
</p>
</AdminResultCard>
<AdminResultCard
label="最近识别结果"
:value="
kuaishouEticketShopInfoResult?.shop.kshopName ||
kuaishouEticketShopInfoResult?.shop.shopId ||
'未执行'
"
>
<p v-if="kuaishouEticketShopInfoResult">
结算状态 {{ kuaishouEticketShopInfoResult.shop.settleStatus }}
</p>
<p v-else>可用"读取店铺信息"校验 Cookie 是否属于预期店铺。</p>
</AdminResultCard>
<AdminResultCard label="建议顺序" value="先配置再调试">
<p>1. 新增店铺并粘贴 Cookie</p>
<p>2. 读取店铺信息确认 kshopName / shopId</p>
<p>3. 设为调试店铺后查询券码并执行核销</p>
</AdminResultCard>
</div>
<div v-else class="editor-empty">
<strong>请选择或新增店铺</strong>
<p>左侧选择店铺后这里会显示 Cookie店铺 ID 和识别结果</p>
</div>
</el-form>
</section>
</div>
<el-alert
@@ -268,6 +234,10 @@ defineProps<Props>()
border-radius: var(--radius-lg);
}
.shop-workspace :deep(.el-card__body) {
padding: 0;
}
.card-actions {
display: flex;
gap: var(--space-2);
@@ -275,50 +245,90 @@ defineProps<Props>()
flex-wrap: wrap;
}
.shop-panels {
.workspace-grid {
display: grid;
grid-template-columns: minmax(0, 1.7fr) minmax(260px, 0.9fr);
gap: var(--space-4);
margin-top: var(--space-4);
grid-template-columns: 320px minmax(0, 1fr);
min-height: 460px;
}
.shop-sidebar {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
gap: var(--space-3);
padding: var(--space-4);
border-right: 1px solid var(--border-default);
background: var(--bg-subtle);
}
.sidebar-head,
.editor-head,
.editor-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
}
.sidebar-head strong,
.editor-head strong,
.compact-empty strong,
.editor-empty strong,
.recognize-result strong {
color: var(--text-primary);
}
.sidebar-head p,
.editor-head p,
.compact-empty p,
.editor-empty p {
margin: var(--space-1) 0 0;
color: var(--text-muted);
font-size: var(--text-xs);
}
.shop-list {
min-width: 0;
}
.side-panel {
display: grid;
gap: var(--space-3);
gap: var(--space-2);
align-content: start;
overflow-y: auto;
padding-right: var(--space-1);
}
.shop-panel.is-debug {
border: 1px solid var(--border-hover);
border-radius: var(--radius-md);
}
.shop-title-bar {
display: flex;
align-items: center;
.shop-item {
display: grid;
grid-template-columns: 42px minmax(0, 1fr);
gap: var(--space-3);
flex: 1;
min-width: 0;
padding-right: var(--space-4);
width: 100%;
padding: var(--space-3);
border: 1px solid transparent;
border-radius: var(--radius-md);
background: transparent;
color: inherit;
text-align: left;
cursor: pointer;
transition:
border-color 0.2s ease,
background 0.2s ease;
}
.shop-item:hover,
.shop-item--active {
border-color: var(--border-hover);
background: var(--bg-card);
}
.shop-avatar {
width: 44px;
height: 44px;
width: 42px;
height: 42px;
border: 1px solid var(--border-hover);
border-radius: var(--radius-md);
overflow: hidden;
background: linear-gradient(135deg, #dbeafe, #eff6ff);
background: #eef6ff;
color: var(--color-primary-dark);
font-size: 18px;
font-weight: 800;
display: grid;
place-items: center;
flex-shrink: 0;
border: 1px solid var(--border-hover);
overflow: hidden;
font-size: 17px;
font-weight: 800;
}
.shop-avatar img {
@@ -327,59 +337,134 @@ defineProps<Props>()
object-fit: cover;
}
.shop-info {
.shop-summary {
min-width: 0;
}
.shop-name-row {
.shop-name {
display: flex;
align-items: center;
gap: var(--space-2);
flex-wrap: wrap;
min-width: 0;
}
.shop-meta {
display: flex;
align-items: center;
gap: var(--space-2);
flex-wrap: wrap;
font-size: var(--text-xs);
.shop-name strong {
overflow: hidden;
color: var(--text-primary);
text-overflow: ellipsis;
white-space: nowrap;
}
.shop-summary p {
margin: 2px 0 0;
color: var(--text-muted);
margin-top: 2px;
font-size: var(--text-xs);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.shop-footer {
.shop-flags {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--space-2);
flex-wrap: wrap;
gap: var(--space-3);
margin-top: var(--space-3);
padding-top: var(--space-3);
border-top: 1px solid var(--border-default);
margin-top: var(--space-2);
color: var(--text-muted);
font-size: var(--text-xs);
}
.footer-left {
display: flex;
align-items: center;
gap: var(--space-3);
flex-wrap: wrap;
.shop-flags .is-ok {
color: var(--color-success);
}
.compact-empty,
.editor-empty {
display: grid;
place-items: start;
align-content: center;
gap: var(--space-2);
min-height: 220px;
padding: var(--space-4);
border: 1px dashed var(--border-default);
border-radius: var(--radius-md);
background: var(--bg-card);
}
.shop-editor {
min-width: 0;
padding: var(--space-5);
}
.editor-form {
display: grid;
gap: var(--space-4);
}
.base-row {
max-width: 560px;
}
.editor-head {
padding: var(--space-4);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
background: var(--bg-subtle);
}
.editor-actions,
.footer-actions {
display: flex;
align-items: center;
gap: var(--space-2);
flex-wrap: wrap;
}
.text-muted {
.form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-4);
}
.editor-footer {
align-items: flex-end;
padding-top: var(--space-2);
}
.recognize-result {
display: grid;
gap: 2px;
color: var(--text-muted);
font-size: var(--text-xs);
}
.recognize-result small {
color: var(--text-muted);
}
@media (max-width: 900px) {
.shop-panels {
@media (max-width: 980px) {
.workspace-grid {
grid-template-columns: 1fr;
}
.shop-sidebar {
border-right: 0;
border-bottom: 1px solid var(--border-default);
}
}
@media (max-width: 640px) {
.shop-editor {
padding: var(--space-4);
}
.form-grid {
grid-template-columns: 1fr;
}
.editor-head,
.editor-footer {
align-items: stretch;
flex-direction: column;
}
}
</style>
@@ -43,10 +43,6 @@ type Props = {
kuaishouEticketStats: KuaishouEticketStats
addKuaishouEticketShop: () => void
removeKuaishouEticketShop: (id: string) => void
isKuaishouEticketShopExpanded: (id: string) => boolean
toggleKuaishouEticketShop: (id: string) => void
expandAllKuaishouEticketShops: () => void
collapseAllKuaishouEticketShops: () => void
setKuaishouEticketDebugShop: (id: string) => void
describeKuaishouEticketShop: (shop: EditableKuaishouEticketShop) => string
getKuaishouEticketShopInitial: (shop: EditableKuaishouEticketShop) => string
@@ -74,10 +70,6 @@ defineProps<Props>()
:kuaishou-eticket-stats="kuaishouEticketStats"
:add-kuaishou-eticket-shop="addKuaishouEticketShop"
:remove-kuaishou-eticket-shop="removeKuaishouEticketShop"
:is-kuaishou-eticket-shop-expanded="isKuaishouEticketShopExpanded"
:toggle-kuaishou-eticket-shop="toggleKuaishouEticketShop"
:expand-all-kuaishou-eticket-shops="expandAllKuaishouEticketShops"
:collapse-all-kuaishou-eticket-shops="collapseAllKuaishouEticketShops"
:set-kuaishou-eticket-debug-shop="setKuaishouEticketDebugShop"
:describe-kuaishou-eticket-shop="describeKuaishouEticketShop"
:get-kuaishou-eticket-shop-initial="getKuaishouEticketShopInitial"
@@ -81,9 +81,6 @@ export function useAdminKuaishouEticketPlatform() {
storeId: '0',
}
kuaishouEticketForm.value.selectedShopId = shopManager.kuaishouEticketShops.value[0]?.id || ''
shopManager.expandedKuaishouEticketShopIds.value = shopManager.kuaishouEticketShops.value[0]?.id
? [shopManager.kuaishouEticketShops.value[0].id]
: []
}
function buildKuaishouEticketConfigPayload(): AdminKuaishouEticketSourceConfig {
@@ -116,7 +113,6 @@ export function useAdminKuaishouEticketPlatform() {
function setKuaishouEticketDebugShop(id: string) {
kuaishouEticketForm.value.selectedShopId = id
shopManager.expandKuaishouEticketShop(id)
}
function ensureSelectedKuaishouEticketShop() {
@@ -229,9 +225,6 @@ export function useAdminKuaishouEticketPlatform() {
shopManager.findShopByShopId(previousSelectedShopId)?.id ||
shopManager.kuaishouEticketShops.value[0]?.id ||
''
shopManager.expandedKuaishouEticketShopIds.value = kuaishouEticketForm.value.selectedShopId
? [kuaishouEticketForm.value.selectedShopId]
: []
showSuccess('快手小店核销配置已保存')
} catch (error) {
kuaishouEticketResultError.value =
@@ -310,7 +303,6 @@ export function useAdminKuaishouEticketPlatform() {
return {
kuaishouEticketFilePath,
kuaishouEticketShops: shopManager.kuaishouEticketShops,
expandedKuaishouEticketShopIds: shopManager.expandedKuaishouEticketShopIds,
kuaishouEticketForm,
kuaishouEticketSaving,
kuaishouEticketResolvingShopId,
@@ -325,12 +317,6 @@ export function useAdminKuaishouEticketPlatform() {
hydrateKuaishouEticketConfig,
addKuaishouEticketShop,
removeKuaishouEticketShop,
isKuaishouEticketShopExpanded: shopManager.isKuaishouEticketShopExpanded,
expandKuaishouEticketShop: shopManager.expandKuaishouEticketShop,
collapseKuaishouEticketShop: shopManager.collapseKuaishouEticketShop,
toggleKuaishouEticketShop: shopManager.toggleKuaishouEticketShop,
expandAllKuaishouEticketShops: shopManager.expandAllKuaishouEticketShops,
collapseAllKuaishouEticketShops: shopManager.collapseAllKuaishouEticketShops,
setKuaishouEticketDebugShop,
describeKuaishouEticketShop: shopManager.describeKuaishouEticketShop,
getKuaishouEticketShopInitial: shopManager.getKuaishouEticketShopInitial,
@@ -4,7 +4,6 @@ import type { AdminKuaishouEticketShopConfigItem } from '@/types/admin'
export function useKuaishouEticketShopManager() {
const kuaishouEticketShops = ref<EditableKuaishouEticketShop[]>([])
const expandedKuaishouEticketShopIds = ref<string[]>([])
function addKuaishouEticketShop() {
const shop = createEmptyKuaishouEticketShop()
@@ -14,46 +13,12 @@ export function useKuaishouEticketShopManager() {
function removeKuaishouEticketShop(id: string, currentSelectedId: string) {
kuaishouEticketShops.value = kuaishouEticketShops.value.filter((item) => item.id !== id)
collapseKuaishouEticketShop(id)
if (currentSelectedId === id) {
return kuaishouEticketShops.value[0]?.id || ''
}
return currentSelectedId
}
function isKuaishouEticketShopExpanded(id: string) {
return expandedKuaishouEticketShopIds.value.includes(id)
}
function expandKuaishouEticketShop(id: string) {
if (expandedKuaishouEticketShopIds.value.includes(id)) {
return
}
expandedKuaishouEticketShopIds.value = [id, ...expandedKuaishouEticketShopIds.value]
}
function collapseKuaishouEticketShop(id: string) {
expandedKuaishouEticketShopIds.value = expandedKuaishouEticketShopIds.value.filter(
(item) => item !== id,
)
}
function toggleKuaishouEticketShop(id: string) {
if (isKuaishouEticketShopExpanded(id)) {
collapseKuaishouEticketShop(id)
return
}
expandKuaishouEticketShop(id)
}
function expandAllKuaishouEticketShops() {
expandedKuaishouEticketShopIds.value = kuaishouEticketShops.value.map((item) => item.id)
}
function collapseAllKuaishouEticketShops() {
expandedKuaishouEticketShopIds.value = []
}
function describeKuaishouEticketShop(shop: EditableKuaishouEticketShop) {
const status = shop.cookie.trim() ? 'Cookie 已就绪' : '待补 Cookie'
const identity = shop.shopId.trim() ? '已识别店铺' : '待读取信息'
@@ -76,15 +41,8 @@ export function useKuaishouEticketShopManager() {
return {
kuaishouEticketShops,
expandedKuaishouEticketShopIds,
addKuaishouEticketShop,
removeKuaishouEticketShop,
isKuaishouEticketShopExpanded,
expandKuaishouEticketShop,
collapseKuaishouEticketShop,
toggleKuaishouEticketShop,
expandAllKuaishouEticketShops,
collapseAllKuaishouEticketShops,
describeKuaishouEticketShop,
getKuaishouEticketShopInitial,
setShopsFromConfig,