admin模块接入数据库
This commit is contained in:
@@ -9,37 +9,170 @@
|
||||
</view>
|
||||
|
||||
<view class="content-card">
|
||||
<view class="product-info">
|
||||
<image class="p-img" src="https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500" mode="aspectFill" />
|
||||
<text class="p-name">UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫</text>
|
||||
<view v-if="isLoading" class="loading-box">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<view class="price-table">
|
||||
<view class="th-row">
|
||||
<view class="th flex-2"><text>规格名称</text></view>
|
||||
<view class="th flex-1"><text>售价</text></view>
|
||||
<view class="th flex-1"><text>普通会员价</text></view>
|
||||
<view class="th flex-1"><text>黄金会员价</text></view>
|
||||
<view class="th flex-1"><text>铂金会员价</text></view>
|
||||
<template v-else>
|
||||
<view class="product-info">
|
||||
<image class="p-img" :src="productImage || '/static/logo.png'" mode="aspectFill" />
|
||||
<text class="p-name">{{ productName }}</text>
|
||||
</view>
|
||||
<view class="tr-row">
|
||||
<view class="td flex-2"><text>XL,卡其</text></view>
|
||||
<view class="td flex-1"><text>¥99.00</text></view>
|
||||
<view class="td flex-1"><input class="price-input" value="89.00" /></view>
|
||||
<view class="td flex-1"><input class="price-input" value="79.00" /></view>
|
||||
<view class="td flex-1"><input class="price-input" value="69.00" /></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-btns">
|
||||
<button class="btn-save">保存设置</button>
|
||||
</view>
|
||||
<view class="price-table">
|
||||
<view class="th-row">
|
||||
<view class="th flex-2"><text>规格名称</text></view>
|
||||
<view class="th flex-1"><text>售价</text></view>
|
||||
<view v-for="level in levels" :key="level.id" class="th flex-1">
|
||||
<text>{{ level.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="skus.length === 0" class="empty-table">
|
||||
<text>该商品暂无规格信息</text>
|
||||
</view>
|
||||
|
||||
<view v-for="(sku, sIdx) in skus" :key="sku.id" class="tr-row">
|
||||
<view class="td flex-2"><text>{{ formatSpecs(sku.specifications) }}</text></view>
|
||||
<view class="td flex-1"><text>¥{{ sku.price.toFixed(2) }}</text></view>
|
||||
<view v-for="level in levels" :key="level.id" class="td flex-1">
|
||||
<input
|
||||
class="price-input"
|
||||
type="digit"
|
||||
v-model="priceMatrix[sku.id][level.id]"
|
||||
:placeholder="calcDefaultPrice(sku.price, level.discount_percent)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-btns">
|
||||
<button class="btn-save" :disabled="isSaving" @click="handleSave">保存设置</button>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import { openRoute } from '@/layouts/admin/store/adminNavStore.uts'
|
||||
import {
|
||||
fetchActiveUserLevels,
|
||||
fetchProductSkus,
|
||||
fetchMemberPrices,
|
||||
saveMemberPrices,
|
||||
UserLevel,
|
||||
ProductSku
|
||||
} from '@/services/admin/productMemberPriceService.uts'
|
||||
|
||||
// --- State ---
|
||||
const productId = ref<string>('')
|
||||
const productName = ref<string>('加载中...')
|
||||
const productImage = ref<string>('')
|
||||
const levels = ref<UserLevel[]>([])
|
||||
const skus = ref<ProductSku[]>([])
|
||||
const priceMatrix = reactive<Record<string, Record<string, string>>>({})
|
||||
|
||||
const isLoading = ref(true)
|
||||
const isSaving = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
// 从路由参数获取 productId
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const options = currentPage.options as Record<string, string | undefined>
|
||||
productId.value = options['id'] ?? ''
|
||||
|
||||
if (!productId.value) {
|
||||
uni.showToast({ title: '未找到商品ID', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
initData()
|
||||
})
|
||||
|
||||
async function initData() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
// 并行获取等级、SKU和已设价格
|
||||
const [levelRes, skuRes, priceRes] = await Promise.all([
|
||||
fetchActiveUserLevels(),
|
||||
fetchProductSkus(productId.value),
|
||||
fetchMemberPrices(productId.value)
|
||||
])
|
||||
|
||||
levels.value = levelRes
|
||||
skus.value = skuRes
|
||||
|
||||
// 初始化矩阵并填充已设价格
|
||||
skuRes.forEach(sku => {
|
||||
priceMatrix[sku.id] = {}
|
||||
levelRes.forEach(level => {
|
||||
// 查找是否已有定价
|
||||
const existing = priceRes.find(p => p.sku_id === sku.id && p.level_id === level.id)
|
||||
priceMatrix[sku.id][level.id] = existing != null ? String(existing.member_price) : ''
|
||||
})
|
||||
})
|
||||
|
||||
// 如果有 SKU,拿第一个的信息展示在头部
|
||||
if (skuRes.length > 0) {
|
||||
// 实际开发中建议单独查一次 Product 信息,这里简化处理
|
||||
productImage.value = skuRes[0].image_url ?? ''
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载数据失败', icon: 'none' })
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function formatSpecs(specs: any): string {
|
||||
if (specs == null) return '默认规格'
|
||||
// 假设规格存的是对象 { "颜色": "红色", "尺寸": "XL" }
|
||||
if (typeof specs === 'object') {
|
||||
const vals = Object.values(specs as Record<string, any>)
|
||||
return vals.join(',')
|
||||
}
|
||||
return String(specs)
|
||||
}
|
||||
|
||||
function calcDefaultPrice(basePrice: number, discount: number): string {
|
||||
return (basePrice * discount / 100).toFixed(2)
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
isSaving.value = true
|
||||
try {
|
||||
const saveRows: Array<{ sku_id: string; level_id: string; member_price: number }> = []
|
||||
|
||||
// 遍历矩阵,只保存有输入数值的项
|
||||
Object.keys(priceMatrix).forEach(skuId => {
|
||||
const levelPrices = priceMatrix[skuId]
|
||||
Object.keys(levelPrices).forEach(levelId => {
|
||||
const val = levelPrices[levelId]
|
||||
if (val != null && val.trim() !== '') {
|
||||
saveRows.push({
|
||||
sku_id: skuId,
|
||||
level_id: levelId,
|
||||
member_price: parseFloat(val)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const ok = await saveMemberPrices(productId.value, saveRows)
|
||||
if (ok) {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
} else {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '保存异常', icon: 'none' })
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
openRoute('product_productList')
|
||||
@@ -52,17 +185,21 @@ function goBack() {
|
||||
.back-link { display: flex; flex-direction: row; align-items: center; gap: 4px; color: #666; cursor: pointer; }
|
||||
.header-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
}
|
||||
.content-card { background: #fff; border-radius: 4px; padding: 24px; }
|
||||
.content-card { background: #fff; border-radius: 4px; padding: 24px; min-height: 400px; }
|
||||
.loading-box, .empty-table { padding: 60px 0; text-align: center; color: #999; font-size: 14px; }
|
||||
|
||||
.product-info { display: flex; flex-direction: row; align-items: center; gap: 16px; margin-bottom: 30px;
|
||||
.p-img { width: 64px; height: 64px; border-radius: 4px; }
|
||||
.p-name { font-size: 15px; font-weight: bold; color: #333; }
|
||||
.p-img { width: 64px; height: 64px; border-radius: 4px; background: #f5f5f5; }
|
||||
.p-name { font-size: 15px; font-weight: bold; color: #333; flex: 1; }
|
||||
}
|
||||
.price-table { border: 1px solid #f0f0f0; border-radius: 4px; }
|
||||
.th-row { display: flex; flex-direction: row; background: #f8f9fa; border-bottom: 1px solid #f0f0f0; }
|
||||
.th { padding: 12px; font-size: 14px; font-weight: 500; color: #333; text-align: center; }
|
||||
.tr-row { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; &:last-child { border-bottom: none; } }
|
||||
.price-table { border: 1px solid #f0f0f0; border-radius: 4px; overflow-x: auto; }
|
||||
.th-row { display: flex; flex-direction: row; background: #f8f9fa; border-bottom: 1px solid #f0f0f0; min-width: 800px; }
|
||||
.th { padding: 12px; font-size: 14px; font-weight: bold; color: #333; text-align: center; }
|
||||
.tr-row { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; min-width: 800px; &:last-child { border-bottom: none; } }
|
||||
.td { padding: 16px 12px; font-size: 14px; color: #666; text-align: center; display: flex; align-items: center; justify-content: center; }
|
||||
.flex-1 { flex: 1; } .flex-2 { flex: 2; }
|
||||
.price-input { border: 1px solid #dcdfe6; border-radius: 4px; height: 32px; padding: 0 8px; text-align: center; width: 80%; font-size: 13px; }
|
||||
.footer-btns { margin-top: 40px; display: flex; justify-content: center; .btn-save { background: #1890ff; color: #fff; border: none; padding: 0 32px; height: 40px; border-radius: 4px; } }
|
||||
.flex-1 { flex: 1; min-width: 120px; } .flex-2 { flex: 2; min-width: 200px; }
|
||||
.price-input { border: 1px solid #dcdfe6; border-radius: 4px; height: 32px; padding: 0 8px; text-align: center; width: 90%; font-size: 13px; background: #fff; }
|
||||
.footer-btns { margin-top: 40px; display: flex; justify-content: center; .btn-save { background: #1890ff; color: #fff; border: none; padding: 0 32px; height: 40px; border-radius: 4px; font-size: 14px;
|
||||
&[disabled] { opacity: 0.6; }
|
||||
} }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user