优化店铺配置相关

This commit is contained in:
yml
2026-04-14 16:15:17 +08:00
parent ed45eab26c
commit bb47d168a9
20 changed files with 425 additions and 198 deletions
+7 -1
View File
@@ -1,5 +1,6 @@
import { apiGet, apiGetBlob, apiPost } from '@/lib/http'
import type {
AdminAgisoMessagingDefaults,
AdminAgisoObservedShopItem,
AdminAgisoShopConfigItem,
AdminAuditLogItem,
@@ -67,14 +68,19 @@ export function fetchAdminAuditLogs(params?: Record<string, unknown>) {
export function fetchAdminAgisoShopConfigs() {
return apiGet<{
filePath: string
defaults: AdminAgisoMessagingDefaults
shops: AdminAgisoShopConfigItem[]
observedShops: AdminAgisoObservedShopItem[]
}>('/api/v1/admin/platform-config/agiso-shops')
}
export function saveAdminAgisoShopConfigs(payload: { shops: Array<Record<string, unknown>> }) {
export function saveAdminAgisoShopConfigs(payload: {
defaults: AdminAgisoMessagingDefaults
shops: Array<Record<string, unknown>>
}) {
return apiPost<{
filePath: string
defaults: AdminAgisoMessagingDefaults
shops: AdminAgisoShopConfigItem[]
}>('/api/v1/admin/platform-config/agiso-shops', payload)
}
+5
View File
@@ -61,6 +61,11 @@ export interface AdminAgisoShopConfigItem {
sendMessageEndpoint: string
}
export interface AdminAgisoMessagingDefaults {
messageTemplate: string
autoDeliveryMessageTemplate: string
}
export interface AdminAgisoObservedShopItem {
shopId: string
detectedShopName: string
@@ -3,10 +3,19 @@ import { onMounted, ref } from 'vue'
import { showError, showSuccess } from '@/lib/feedback'
import { fetchAdminAgisoShopConfigs, saveAdminAgisoShopConfigs } from '@/services/admin'
import type { AdminAgisoObservedShopItem, AdminAgisoShopConfigItem } from '@/types/admin'
import type {
AdminAgisoMessagingDefaults,
AdminAgisoObservedShopItem,
AdminAgisoShopConfigItem,
} from '@/types/admin'
import { hasAdminRole } from '@/utils/admin-auth'
import { formatAdminDateTime } from '@/utils/admin-time'
type EditableDefaults = {
messageTemplate: string
autoDeliveryMessageTemplate: string
}
type EditableShop = {
id: string
shopId: string
@@ -21,8 +30,17 @@ const loading = ref(true)
const saving = ref(false)
const errorMessage = ref('')
const filePath = ref('')
const defaults = ref<EditableDefaults>(createEmptyDefaults())
const shops = ref<EditableShop[]>([])
const observedShops = ref<AdminAgisoObservedShopItem[]>([])
const expandedShopIds = ref<string[]>([])
function createEmptyDefaults(): EditableDefaults {
return {
messageTemplate: '',
autoDeliveryMessageTemplate: '',
}
}
function createEmptyShop(): EditableShop {
return {
@@ -36,6 +54,13 @@ function createEmptyShop(): EditableShop {
}
}
function mapEditableDefaults(item?: AdminAgisoMessagingDefaults): EditableDefaults {
return {
messageTemplate: item?.messageTemplate ?? '',
autoDeliveryMessageTemplate: item?.autoDeliveryMessageTemplate ?? '',
}
}
function mapEditableShop(item: AdminAgisoShopConfigItem): EditableShop {
return {
id: crypto.randomUUID(),
@@ -60,8 +85,10 @@ async function loadConfigs() {
try {
const response = await fetchAdminAgisoShopConfigs()
filePath.value = response.data.filePath
defaults.value = mapEditableDefaults(response.data.defaults)
shops.value = response.data.shops.map(mapEditableShop)
observedShops.value = response.data.observedShops
expandedShopIds.value = []
} catch (error) {
errorMessage.value = error instanceof Error ? error.message : '读取店铺配置失败'
} finally {
@@ -70,15 +97,18 @@ async function loadConfigs() {
}
function addShop() {
shops.value.unshift(createEmptyShop())
const shop = createEmptyShop()
shops.value.unshift(shop)
expandShop(shop.id)
}
function removeShop(id: string) {
shops.value = shops.value.filter((item) => item.id !== id)
collapseShop(id)
}
function importObservedShop(item: AdminAgisoObservedShopItem) {
shops.value.unshift({
const shop: EditableShop = {
id: crypto.randomUUID(),
shopId: item.shopId,
shopName: item.detectedShopName || item.displayShopName || '',
@@ -86,10 +116,67 @@ function importObservedShop(item: AdminAgisoObservedShopItem) {
enabled: true,
messageTemplate: '',
autoDeliveryMessageTemplate: '',
})
}
shops.value.unshift(shop)
expandShop(shop.id)
}
function isShopExpanded(id: string) {
return expandedShopIds.value.includes(id)
}
function expandShop(id: string) {
if (expandedShopIds.value.includes(id)) {
return
}
expandedShopIds.value = [id, ...expandedShopIds.value]
}
function collapseShop(id: string) {
expandedShopIds.value = expandedShopIds.value.filter((item) => item !== id)
}
function toggleShop(id: string) {
if (isShopExpanded(id)) {
collapseShop(id)
return
}
expandShop(id)
}
function expandAllShops() {
expandedShopIds.value = shops.value.map((item) => item.id)
}
function collapseAllShops() {
expandedShopIds.value = []
}
function describeShop(shop: EditableShop) {
const overrides: string[] = []
if (shop.messageTemplate.trim()) {
overrides.push('领取模板')
}
if (shop.autoDeliveryMessageTemplate.trim()) {
overrides.push('发货模板')
}
if (overrides.length === 0) {
return '使用默认模板'
}
return `已覆盖 ${overrides.join('、')}`
}
async function saveConfigs() {
const payloadDefaults = {
messageTemplate: defaults.value.messageTemplate.trim(),
autoDeliveryMessageTemplate: defaults.value.autoDeliveryMessageTemplate.trim(),
}
const payload = shops.value
.map((item) => ({
shopId: item.shopId.trim(),
@@ -104,8 +191,12 @@ async function saveConfigs() {
saving.value = true
try {
const response = await saveAdminAgisoShopConfigs({ shops: payload })
const response = await saveAdminAgisoShopConfigs({
defaults: payloadDefaults,
shops: payload,
})
filePath.value = response.data.filePath
defaults.value = mapEditableDefaults(response.data.defaults)
shops.value = response.data.shops.map(mapEditableShop)
showSuccess('店铺配置已保存')
await loadConfigs()
@@ -124,7 +215,7 @@ onMounted(loadConfigs)
<header class="panel-header">
<div>
<h1>Agiso 店铺配置</h1>
<p>Agiso 多店铺配置改为文件维护保存后立即生效不需要再手改 `.env`</p>
<p>Agiso 默认消息模板和店铺覆盖配置改为文件维护保存后立即生效不需要再手改 `.env`</p>
</div>
<div class="header-actions">
<el-button round @click="loadConfigs">刷新</el-button>
@@ -142,7 +233,7 @@ onMounted(loadConfigs)
</div>
<div class="meta-line">
<span class="meta-label">说明</span>
<span>店铺级 `accessToken` 优先于全局 `AGISO_ACCESS_TOKEN`未配置的店铺仍会继续使用全局兜底</span>
<span>全局默认消息模板与店铺级覆盖统一保存在此文件中店铺留空时会回退到上面的默认模板与全局兜底配置</span>
</div>
</section>
@@ -153,56 +244,105 @@ onMounted(loadConfigs)
<section class="table-card">
<div class="section-title-row">
<div>
<h3>默认消息模板</h3>
<p>留空时会回退到系统内置模板店铺未单独配置时默认使用这里的内容</p>
</div>
</div>
<div class="shop-grid">
<label class="field-block field-wide">
<span>默认领取链接消息模板</span>
<textarea
v-model="defaults.messageTemplate"
class="text-area"
placeholder="例如:您的订单 {platformOrderId} 已创建领取链接,请尽快打开并完成领取:{claimUrl}"
/>
</label>
<label class="field-block field-wide">
<span>默认自动发货消息模板</span>
<textarea
v-model="defaults.autoDeliveryMessageTemplate"
class="text-area"
placeholder="例如:您的订单 {platformOrderId} 已完成自动发货,请注意查收。"
/>
</label>
</div>
</section>
<section class="table-card">
<div class="section-title-row">
<div>
<h3>已配置店铺</h3>
<p>至少填写 `shopId` `accessToken``shopName` 为空时界面会退回显示店铺 ID</p>
<p>默认折叠显示至少填写 `shopId` `accessToken``shopName` 为空时界面会退回显示店铺 ID</p>
</div>
<div class="section-actions">
<el-button :disabled="shops.length === 0" text @click="expandAllShops">全部展开</el-button>
<el-button :disabled="shops.length === 0" text @click="collapseAllShops">全部折叠</el-button>
<el-button :loading="saving" round type="primary" @click="saveConfigs">保存配置</el-button>
</div>
<el-button :loading="saving" round type="primary" @click="saveConfigs">保存配置</el-button>
</div>
<div v-if="shops.length === 0" class="empty-inline">当前还没有店铺配置先新增一条</div>
<div v-for="shop in shops" :key="shop.id" class="shop-card">
<div class="shop-grid">
<label class="field-block">
<span>店铺 ID</span>
<input v-model="shop.shopId" class="text-input" placeholder="例如 693760716" />
</label>
<button type="button" class="shop-summary" @click="toggleShop(shop.id)">
<div class="shop-summary-main">
<strong>{{ shop.shopName || shop.shopId || '未命名店铺' }}</strong>
<span class="cell-subtle">ID: {{ shop.shopId || '未填写' }}</span>
</div>
<div class="shop-summary-side">
<span class="shop-status" :class="{ 'is-disabled': !shop.enabled }">
{{ shop.enabled ? '已启用' : '已停用' }}
</span>
<span class="shop-summary-copy">{{ describeShop(shop) }}</span>
<span class="shop-toggle">{{ isShopExpanded(shop.id) ? '收起' : '展开' }}</span>
</div>
</button>
<label class="field-block">
<span>店铺名称</span>
<input v-model="shop.shopName" class="text-input" placeholder="例如 大锤商行" />
</label>
<div v-if="isShopExpanded(shop.id)" class="shop-body">
<div class="shop-grid">
<label class="field-block">
<span>店铺 ID</span>
<input v-model="shop.shopId" class="text-input" placeholder="例如 693760716" />
</label>
<label class="field-block field-wide">
<span>AccessToken</span>
<input v-model="shop.accessToken" class="text-input" placeholder="AldsIdle..." />
</label>
<label class="field-block">
<span>店铺名称</span>
<input v-model="shop.shopName" class="text-input" placeholder="例如 大锤商行" />
</label>
<label class="field-block field-wide">
<span>领取链接消息模板</span>
<textarea
v-model="shop.messageTemplate"
class="text-area"
placeholder="留空则沿用全局 AGISO_MESSAGE_TEMPLATE"
/>
</label>
<label class="field-block field-wide">
<span>AccessToken</span>
<input v-model="shop.accessToken" class="text-input" placeholder="AldsIdle..." />
</label>
<label class="field-block field-wide">
<span>自动发货消息模板</span>
<textarea
v-model="shop.autoDeliveryMessageTemplate"
class="text-area"
placeholder="留空则沿用全局 AGISO_AUTO_DELIVERY_MESSAGE_TEMPLATE"
/>
</label>
</div>
<label class="field-block field-wide">
<span>领取链接消息模板</span>
<textarea
v-model="shop.messageTemplate"
class="text-area"
placeholder="留空则沿用上面的默认领取链接模板"
/>
</label>
<div class="shop-actions">
<label class="checkbox-line">
<input v-model="shop.enabled" type="checkbox" />
<span>启用店铺级覆盖</span>
</label>
<el-button link type="danger" @click="removeShop(shop.id)">删除</el-button>
<label class="field-block field-wide">
<span>自动发货消息模板</span>
<textarea
v-model="shop.autoDeliveryMessageTemplate"
class="text-area"
placeholder="留空则沿用上面的默认自动发货模板"
/>
</label>
</div>
<div class="shop-actions">
<label class="checkbox-line">
<input v-model="shop.enabled" type="checkbox" />
<span>启用店铺级覆盖</span>
</label>
<el-button link type="danger" @click="removeShop(shop.id)">删除</el-button>
</div>
</div>
</div>
</section>
@@ -290,6 +430,13 @@ onMounted(loadConfigs)
gap: 10px;
}
.section-actions {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.meta-card,
.table-card,
.empty-block,
@@ -318,10 +465,66 @@ onMounted(loadConfigs)
.shop-card {
margin-top: 14px;
padding: 16px;
border-radius: 18px;
background: #f8fafc;
border: 1px solid rgba(148, 163, 184, 0.18);
overflow: hidden;
}
.shop-summary {
width: 100%;
border: 0;
padding: 16px;
background: transparent;
display: flex;
justify-content: space-between;
gap: 16px;
align-items: center;
text-align: left;
cursor: pointer;
}
.shop-summary-main,
.shop-summary-side {
display: grid;
gap: 4px;
}
.shop-summary-side {
justify-items: end;
}
.shop-summary-main strong,
.shop-toggle {
color: #1d3555;
}
.shop-summary-copy {
color: #64748b;
font-size: 12px;
}
.shop-status {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 28px;
padding: 0 10px;
border-radius: 999px;
background: rgba(22, 163, 74, 0.12);
color: #166534;
font-size: 12px;
font-weight: 700;
}
.shop-status.is-disabled {
background: rgba(148, 163, 184, 0.14);
color: #475467;
}
.shop-body {
padding: 0 16px 16px;
border-top: 1px solid rgba(148, 163, 184, 0.18);
}
.shop-grid {
@@ -426,5 +629,14 @@ onMounted(loadConfigs)
.shop-grid {
grid-template-columns: 1fr;
}
.shop-summary {
flex-direction: column;
align-items: stretch;
}
.shop-summary-side {
justify-items: start;
}
}
</style>