合并merchant文件
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
<<<<<<< HEAD
|
||||
<!-- 商家端 - 商品编辑页面 -->
|
||||
=======
|
||||
<!-- 商家端 - 商品编辑页面 (已修复缓存) -->
|
||||
>>>>>>> local-backup-root-cyj
|
||||
<template>
|
||||
<view class="product-edit-page">
|
||||
<!-- 商品基本信息 -->
|
||||
@@ -131,8 +135,28 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<<<<<<< HEAD
|
||||
<view class="form-item">
|
||||
<text class="label">总库存 *</text>
|
||||
=======
|
||||
<view class="form-item">
|
||||
<text class="label">VIP独立折扣</text>
|
||||
<switch :checked="product.is_vip_discount" @change="e => { product.is_vip_discount = e.detail.value as boolean }" />
|
||||
</view>
|
||||
|
||||
<view class="form-item" v-if="product.is_vip_discount">
|
||||
<text class="label">VIP折扣率</text>
|
||||
<input
|
||||
class="input"
|
||||
type="digit"
|
||||
v-model="product.vip_discount_rate"
|
||||
placeholder="如 0.85 代表 85 折(空代表采用全局)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">总库存 *</text>
|
||||
>>>>>>> local-backup-root-cyj
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
@@ -152,6 +176,28 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
<!-- 会员阶梯价 -->
|
||||
<view class="section">
|
||||
<view class="section-title">会员等级价格 (选填)</view>
|
||||
<view class="section-desc">若不填写则按照商品销售价或默认折扣计算</view>
|
||||
|
||||
<view v-for="(level, index) in memberLevels" :key="index" class="form-item">
|
||||
<text class="label">{{ level.name }}价格</text>
|
||||
<view class="price-input">
|
||||
<text class="unit">¥</text>
|
||||
<input
|
||||
class="input"
|
||||
v-model="level.price"
|
||||
type="digit"
|
||||
placeholder="专属折扣价"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
>>>>>>> local-backup-root-cyj
|
||||
<!-- 商品属性 -->
|
||||
<view class="section">
|
||||
<view class="section-title">商品属性</view>
|
||||
@@ -231,6 +277,17 @@
|
||||
logo_url: string
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
type MemberLevelType = {
|
||||
id: string
|
||||
name: string
|
||||
level_rank: number
|
||||
discount_rate: number
|
||||
price: string // 绑定输入框用
|
||||
}
|
||||
|
||||
>>>>>>> local-backup-root-cyj
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -242,6 +299,10 @@
|
||||
brands: [] as BrandType[],
|
||||
brandIndex: -1,
|
||||
selectedBrand: null as BrandType | null,
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
memberLevels: [] as MemberLevelType[],
|
||||
>>>>>>> local-backup-root-cyj
|
||||
product: {
|
||||
name: '',
|
||||
subtitle: '',
|
||||
@@ -255,9 +316,17 @@
|
||||
total_stock: '',
|
||||
warning_stock: '10',
|
||||
unit: '件',
|
||||
<<<<<<< HEAD
|
||||
is_hot: false,
|
||||
is_new: false,
|
||||
is_featured: false,
|
||||
=======
|
||||
is_hot: false,
|
||||
is_new: false,
|
||||
is_featured: false,
|
||||
is_vip_discount: true,
|
||||
vip_discount_rate: '',
|
||||
>>>>>>> local-backup-root-cyj
|
||||
description: ''
|
||||
},
|
||||
merchantId: ''
|
||||
@@ -265,15 +334,52 @@
|
||||
},
|
||||
|
||||
onLoad(options: any) {
|
||||
<<<<<<< HEAD
|
||||
const productId = options.productId as string
|
||||
if (productId) {
|
||||
this.productId = productId
|
||||
this.isEdit = true
|
||||
this.loadProductDetail(productId)
|
||||
=======
|
||||
let productId = ''
|
||||
if (options) {
|
||||
const keys = Object.keys(options as object)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (keys[i] === 'productId') {
|
||||
productId = String((options as Record<string, any>)[keys[i]])
|
||||
}
|
||||
}
|
||||
if (!productId && options['productId']) {
|
||||
productId = String(options['productId'])
|
||||
}
|
||||
// 兼容某些平台
|
||||
if (!productId) {
|
||||
try {
|
||||
const optsStr = JSON.stringify(options)
|
||||
const optsObj = JSON.parse(optsStr) as Record<string, any>
|
||||
if (optsObj['productId']) {
|
||||
productId = String(optsObj['productId'])
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (productId && productId !== '') {
|
||||
this.productId = productId
|
||||
this.isEdit = true
|
||||
uni.setNavigationBarTitle({ title: '编辑商品' })
|
||||
this.loadProductDetail(productId)
|
||||
} else {
|
||||
uni.setNavigationBarTitle({ title: '添加商品' })
|
||||
>>>>>>> local-backup-root-cyj
|
||||
}
|
||||
this.initMerchantId()
|
||||
this.loadCategories()
|
||||
this.loadBrands()
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
this.loadMemberLevels()
|
||||
>>>>>>> local-backup-root-cyj
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -281,7 +387,11 @@
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
if (session != null && session.user != null) {
|
||||
<<<<<<< HEAD
|
||||
this.merchantId = session.user.getString('id') || ''
|
||||
=======
|
||||
this.merchantId = (session.user as any)['id'] != null ? String((session.user as any)['id']) : ''
|
||||
>>>>>>> local-backup-root-cyj
|
||||
}
|
||||
if (!this.merchantId) {
|
||||
this.merchantId = uni.getStorageSync('user_id') || ''
|
||||
@@ -291,6 +401,78 @@
|
||||
}
|
||||
},
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
async loadMemberLevels() {
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_member_levels')
|
||||
.select('*')
|
||||
.eq('is_active', true)
|
||||
.order('level_rank', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
console.error('获取会员等级失败:', response.error)
|
||||
return
|
||||
}
|
||||
|
||||
const rawData = response.data as any[]
|
||||
if (rawData == null) return
|
||||
|
||||
this.memberLevels = []
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
const item = rawData[i] as any
|
||||
this.memberLevels.push({
|
||||
id: item['id'] != null ? String(item['id']) : '',
|
||||
name: item['name'] != null ? String(item['name']) : '',
|
||||
level_rank: item['level_rank'] != null ? parseInt(String(item['level_rank'])) : 0,
|
||||
discount_rate: item['discount_rate'] != null ? parseFloat(String(item['discount_rate'])) : 1.0,
|
||||
price: ''
|
||||
} as MemberLevelType)
|
||||
}
|
||||
|
||||
// 如果是编辑模式,还需要加载已有的会员价
|
||||
if (this.isEdit) {
|
||||
this.loadMemberPrices()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取会员等级异常:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async loadMemberPrices() {
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_product_member_prices')
|
||||
.select('*')
|
||||
.eq('product_id', this.productId)
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
console.error('获取会员价失败:', response.error)
|
||||
return
|
||||
}
|
||||
|
||||
const rawData = response.data as any[]
|
||||
if (rawData == null || rawData.length == 0) return
|
||||
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
const item = rawData[i] as any
|
||||
const levelId = String(item['level_id'])
|
||||
const price = String(item['member_price'])
|
||||
|
||||
const index = this.memberLevels.findIndex(lv => lv.id === levelId)
|
||||
if (index >= 0) {
|
||||
this.memberLevels[index].price = price
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取会员价异常:', e)
|
||||
}
|
||||
},
|
||||
|
||||
>>>>>>> local-backup-root-cyj
|
||||
async loadCategories() {
|
||||
try {
|
||||
const response = await supa
|
||||
@@ -309,10 +491,17 @@
|
||||
if (rawData == null) return
|
||||
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
<<<<<<< HEAD
|
||||
const item = rawData[i] as UTSJSONObject
|
||||
this.categories.push({
|
||||
id: item.getString('id') || '',
|
||||
name: item.getString('name') || ''
|
||||
=======
|
||||
const item = rawData[i] as any
|
||||
this.categories.push({
|
||||
id: item['id'] != null ? String(item['id']) : '',
|
||||
name: item['name'] != null ? String(item['name']) : ''
|
||||
>>>>>>> local-backup-root-cyj
|
||||
} as CategoryType)
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -338,11 +527,19 @@
|
||||
if (rawData == null) return
|
||||
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
<<<<<<< HEAD
|
||||
const item = rawData[i] as UTSJSONObject
|
||||
this.brands.push({
|
||||
id: item.getString('id') || '',
|
||||
name: item.getString('name') || '',
|
||||
logo_url: item.getString('logo_url') || ''
|
||||
=======
|
||||
const item = rawData[i] as any
|
||||
this.brands.push({
|
||||
id: item['id'] != null ? String(item['id']) : '',
|
||||
name: item['name'] != null ? String(item['name']) : '',
|
||||
logo_url: item['logo_url'] != null ? String(item['logo_url']) : ''
|
||||
>>>>>>> local-backup-root-cyj
|
||||
} as BrandType)
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -352,6 +549,10 @@
|
||||
|
||||
async loadProductDetail(productId: string) {
|
||||
try {
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
uni.showLoading({ title: '加载商品中...' })
|
||||
>>>>>>> local-backup-root-cyj
|
||||
const response = await supa
|
||||
.from('ml_products')
|
||||
.select('*')
|
||||
@@ -359,6 +560,7 @@
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (response.error != null) {
|
||||
console.error('获取商品详情失败:', response.error)
|
||||
return
|
||||
@@ -386,6 +588,47 @@
|
||||
description: rawData.getString('description') || ''
|
||||
}
|
||||
|
||||
=======
|
||||
uni.hideLoading()
|
||||
if (response.error != null) {
|
||||
console.error('获取详情失败:', response.error)
|
||||
uni.showToast({ title: '没有找到该商品', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
let rawData = response.data as any
|
||||
if (rawData == null) return
|
||||
|
||||
// 防止Supabase某些版本把single()仍返回数组的坑
|
||||
if (Array.isArray(rawData) && rawData.length > 0) {
|
||||
rawData = rawData[0]
|
||||
}
|
||||
|
||||
const getStr = (key: string): string => { try { return rawData[key] != null ? String(rawData[key]) : '' } catch(e){ return '' } }
|
||||
const getBool = (key: string): boolean => { try { return rawData[key] === true || rawData[key] === 'true' } catch(e){ return false } }
|
||||
|
||||
this.product.name = getStr('name')
|
||||
this.product.subtitle = getStr('subtitle')
|
||||
this.product.category_id = getStr('category_id')
|
||||
this.product.brand_id = getStr('brand_id')
|
||||
this.product.main_image_url = getStr('main_image_url')
|
||||
this.product.imageList = this.parseImageUrls(getStr('image_urls'))
|
||||
this.product.base_price = getStr('base_price')
|
||||
this.product.market_price = getStr('market_price')
|
||||
this.product.cost_price = getStr('cost_price')
|
||||
this.product.total_stock = getStr('total_stock')
|
||||
this.product.warning_stock = getStr('warning_stock') || '10'
|
||||
this.product.unit = getStr('unit') || '件'
|
||||
this.product.is_hot = getBool('is_hot')
|
||||
this.product.is_new = getBool('is_new')
|
||||
this.product.is_featured = getBool('is_featured')
|
||||
|
||||
const _isVip = rawData['is_vip_discount']
|
||||
this.product.is_vip_discount = _isVip == null ? true : getBool('is_vip_discount')
|
||||
this.product.vip_discount_rate = getStr('vip_discount_rate')
|
||||
this.product.description = getStr('description')
|
||||
|
||||
>>>>>>> local-backup-root-cyj
|
||||
if (this.product.category_id) {
|
||||
this.categoryIndex = this.categories.findIndex(c => c.id === this.product.category_id)
|
||||
if (this.categoryIndex >= 0) {
|
||||
@@ -400,7 +643,13 @@
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
<<<<<<< HEAD
|
||||
console.error('获取商品详情异常:', e)
|
||||
=======
|
||||
uni.hideLoading()
|
||||
console.error('获取商品详情异常:', e)
|
||||
uni.showToast({ title: '加载异常: ' + String(e), icon: 'none', duration: 3000 })
|
||||
>>>>>>> local-backup-root-cyj
|
||||
}
|
||||
},
|
||||
|
||||
@@ -453,6 +702,7 @@
|
||||
this.product.imageList.splice(index, 1)
|
||||
},
|
||||
|
||||
<<<<<<< HEAD
|
||||
async saveProduct() {
|
||||
if (!this.product.name) {
|
||||
uni.showToast({ title: '请输入商品名称', icon: 'none' })
|
||||
@@ -533,6 +783,203 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
=======
|
||||
async uploadImageToSupa(localPath: string): Promise<string> {
|
||||
if (localPath.startsWith('http://') || localPath.startsWith('https://')) {
|
||||
return localPath
|
||||
}
|
||||
|
||||
let ext = '.jpg'
|
||||
const dotIndex = localPath.lastIndexOf('.')
|
||||
if (dotIndex > -1) {
|
||||
ext = localPath.substring(dotIndex).toLowerCase()
|
||||
}
|
||||
|
||||
const uuid = Date.now().toString() + '_' + Math.floor(Math.random() * 1000)
|
||||
const remotePath = `products/${this.merchantId}_${uuid}${ext}`
|
||||
|
||||
try {
|
||||
const uploadResult = await supa.storage.from('zhipao').upload(remotePath, localPath, {})
|
||||
if (uploadResult.error != null) {
|
||||
console.error('上传图片失败:', uploadResult.error)
|
||||
return localPath
|
||||
}
|
||||
|
||||
return supa.storage.getPublicUrl('zhipao', remotePath)
|
||||
} catch (e) {
|
||||
console.error('上传图片异常:', e)
|
||||
return localPath
|
||||
}
|
||||
},
|
||||
|
||||
async saveProduct() {
|
||||
if (!this.product.name) {
|
||||
uni.showToast({ title: '请输入商品名称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!this.product.category_id) {
|
||||
uni.showToast({ title: '请选择商品分类', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!this.product.base_price) {
|
||||
uni.showToast({ title: '请输入销售价', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!this.product.total_stock) {
|
||||
uni.showToast({ title: '请输入总库存', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (this.product.is_vip_discount && this.product.vip_discount_rate !== '') {
|
||||
const rate = parseFloat(this.product.vip_discount_rate)
|
||||
if (isNaN(rate) || rate <= 0 || rate > 1) {
|
||||
uni.showToast({ title: 'VIP折扣率需在0~1之间', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
try {
|
||||
let finalMainImage = this.product.main_image_url
|
||||
if (finalMainImage != '') {
|
||||
finalMainImage = await this.uploadImageToSupa(finalMainImage)
|
||||
}
|
||||
|
||||
const finalImageList = [] as string[]
|
||||
for (let i = 0; i < this.product.imageList.length; i++) {
|
||||
const img = await this.uploadImageToSupa(this.product.imageList[i])
|
||||
finalImageList.push(img)
|
||||
}
|
||||
|
||||
const imageUrlsStr = JSON.stringify(finalImageList)
|
||||
|
||||
const productData = {
|
||||
merchant_id: this.merchantId,
|
||||
name: this.product.name,
|
||||
subtitle: this.product.subtitle,
|
||||
category_id: this.product.category_id,
|
||||
brand_id: this.product.brand_id || null,
|
||||
main_image_url: finalMainImage,
|
||||
image_urls: imageUrlsStr,
|
||||
base_price: this.product.base_price ? parseFloat(this.product.base_price) : 0,
|
||||
market_price: this.product.market_price ? parseFloat(this.product.market_price) : null,
|
||||
cost_price: this.product.cost_price ? parseFloat(this.product.cost_price) : null,
|
||||
total_stock: parseInt(this.product.total_stock),
|
||||
available_stock: parseInt(this.product.total_stock),
|
||||
is_hot: this.product.is_hot,
|
||||
is_new: this.product.is_new,
|
||||
is_featured: this.product.is_featured,
|
||||
is_vip_discount: this.product.is_vip_discount,
|
||||
vip_discount_rate: this.product.vip_discount_rate ? parseFloat(this.product.vip_discount_rate) : null,
|
||||
description: this.product.description,
|
||||
status: 1,
|
||||
updated_at: new Date().toISOString()
|
||||
} as UTSJSONObject
|
||||
|
||||
let response : any = null
|
||||
if (this.isEdit) {
|
||||
const updateData = {} as UTSJSONObject
|
||||
const keys = UTSJSONObject.keys(productData)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (key != 'status') {
|
||||
updateData[key] = productData[key]
|
||||
}
|
||||
}
|
||||
|
||||
console.log('执行产品更新, ID:', this.productId)
|
||||
const updateResponse = await supa
|
||||
.from('ml_products')
|
||||
.update(updateData)
|
||||
.eq('id', this.productId)
|
||||
.execute()
|
||||
|
||||
if (updateResponse.error != null) {
|
||||
throw new Error('产品更新失败: ' + String(updateResponse.error!.message))
|
||||
}
|
||||
response = updateResponse
|
||||
} else {
|
||||
productData['created_at'] = new Date().toISOString()
|
||||
productData['product_code'] = 'P' + Date.now().toString()
|
||||
console.log('执行新产品插入')
|
||||
const insertResponse = await supa
|
||||
.from('ml_products')
|
||||
.insert(productData)
|
||||
.execute()
|
||||
|
||||
if (insertResponse.error != null) {
|
||||
throw new Error('产品发布失败: ' + String(insertResponse.error!.message))
|
||||
}
|
||||
response = insertResponse
|
||||
}
|
||||
|
||||
// 保存会员价
|
||||
let targetProductId = this.isEdit ? this.productId : ''
|
||||
if (response != null && response.data != null) {
|
||||
const responseData = response.data
|
||||
if (Array.isArray(responseData)) {
|
||||
const dataArr = responseData as any[]
|
||||
if (dataArr.length > 0) {
|
||||
const firstRow = dataArr[0] as UTSJSONObject
|
||||
if (firstRow['id'] != null) {
|
||||
targetProductId = String(firstRow['id'])
|
||||
}
|
||||
}
|
||||
} else if (responseData instanceof UTSJSONObject) {
|
||||
const dataObj = responseData as UTSJSONObject
|
||||
if (dataObj['id'] != null) {
|
||||
targetProductId = String(dataObj['id'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('最终目标产品ID:', targetProductId)
|
||||
|
||||
if (targetProductId && targetProductId !== '' && targetProductId !== 'undefined') {
|
||||
// 1. 先删除旧的会员价
|
||||
if (this.isEdit) {
|
||||
console.log('删除旧会员价:', targetProductId)
|
||||
await supa.from('ml_product_member_prices').delete().eq('product_id', targetProductId).execute()
|
||||
}
|
||||
|
||||
// 2. 插入新的会员价
|
||||
for (let i = 0; i < this.memberLevels.length; i++) {
|
||||
const level = this.memberLevels[i]
|
||||
if (level.price && level.price > 0) {
|
||||
const memberPriceData = {
|
||||
product_id: targetProductId,
|
||||
level_id: level.id,
|
||||
member_price: level.price,
|
||||
created_at: new Date().toISOString()
|
||||
} as UTSJSONObject
|
||||
|
||||
const insertRes = await supa
|
||||
.from('ml_product_member_prices')
|
||||
.insert(memberPriceData)
|
||||
.execute()
|
||||
|
||||
if (insertRes.error != null) {
|
||||
console.error('插入会员价失败', insertRes.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
console.error('保存商品异常:', e)
|
||||
uni.showToast({ title: '保存异常: ' + String(e), icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
>>>>>>> local-backup-root-cyj
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -557,6 +1004,16 @@
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
.section-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: -20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
>>>>>>> local-backup-root-cyj
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
@@ -713,3 +1170,9 @@
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
|
||||
|
||||
>>>>>>> local-backup-root-cyj
|
||||
|
||||
Reference in New Issue
Block a user