consumer模块完成度95%,能编译在安卓端运行,在解决数据获取和页面布局问题

This commit is contained in:
cyh666666
2026-02-26 17:27:15 +08:00
parent e606c597ca
commit b34f960624
1412 changed files with 3304 additions and 804 deletions

View File

@@ -0,0 +1,80 @@
-- 更新分类图标为 emoji 格式
-- 运行此脚本修复分类图标显示问题
-- 更新一级分类图标
UPDATE public.ml_categories
SET icon_url =
CASE
WHEN slug = 'digital' THEN '📱'
WHEN slug = 'fashion' THEN '👕'
WHEN slug = 'home' THEN '🏠'
WHEN slug = 'food' THEN '🍎'
WHEN slug = 'beauty' THEN '💄'
WHEN slug = 'sports' THEN ''
WHEN slug = 'books' THEN '📚'
WHEN slug = 'baby' THEN '👶'
WHEN slug = 'health' THEN '💊'
ELSE icon_url
END
WHERE level = 1;
-- 更新二级分类图标
UPDATE public.ml_categories
SET icon_url =
CASE
-- 数码电器二级分类
WHEN slug = 'mobile' THEN '📱'
WHEN slug = 'computer' THEN '💻'
WHEN slug = 'appliance' THEN '🎥'
WHEN slug = 'accessories' THEN '🔌'
-- 服装鞋帽二级分类
WHEN slug = 'mens-wear' THEN '👔'
WHEN slug = 'womens-wear' THEN '👗'
WHEN slug = 'mens-shoes' THEN '👞'
WHEN slug = 'womens-shoes' THEN '👠'
-- 家居用品二级分类
WHEN slug = 'furniture' THEN '🛋️'
WHEN slug = 'decoration' THEN '🖼️'
WHEN slug = 'kitchen' THEN '🍳'
WHEN slug = 'daily' THEN '🧹'
-- 食品饮料二级分类
WHEN slug = 'fruits' THEN '🍊'
WHEN slug = 'meat' THEN '🥩'
WHEN slug = 'snacks' THEN '🍪'
WHEN slug = 'drinks' THEN '🍺'
-- 美妆护肤二级分类
WHEN slug = 'skincare' THEN '🧴'
WHEN slug = 'makeup' THEN '💅'
-- 运动户外二级分类
WHEN slug = 'outdoor' THEN '🏕️'
WHEN slug = 'fitness' THEN '🏋️'
-- 母婴用品二级分类
WHEN slug = 'toys' THEN '🧸'
WHEN slug = 'feeding' THEN '🍼'
-- 图书文娱二级分类
WHEN slug = 'stationery' THEN '✏️'
WHEN slug = 'audio' THEN '🎵'
ELSE icon_url
END
WHERE level = 2;
-- 如果有 icon_url 为 icon-xxx 格式的记录,也进行更新
UPDATE public.ml_categories
SET icon_url =
CASE
WHEN icon_url = 'icon-digital' THEN '📱'
WHEN icon_url = 'icon-fashion' THEN '👕'
WHEN icon_url = 'icon-home' THEN '🏠'
WHEN icon_url = 'icon-food' THEN '🍎'
WHEN icon_url = 'icon-beauty' THEN '💄'
WHEN icon_url = 'icon-sports' THEN ''
WHEN icon_url = 'icon-books' THEN '📚'
WHEN icon_url = 'icon-baby' THEN '👶'
WHEN icon_url = 'icon-health' THEN '💊'
ELSE icon_url
END
WHERE icon_url LIKE 'icon-%';
-- 查看更新结果
SELECT name, slug, icon_url FROM public.ml_categories WHERE level = 1 ORDER BY sort_order;
SELECT name, slug, icon_url FROM public.ml_categories WHERE level = 2 ORDER BY sort_order;

View File

@@ -1,10 +0,0 @@
{
"pages": [
{
"path": "pages/minimal",
"style": {
"navigationBarTitleText": "最小测试"
}
}
]
}

View File

@@ -28,6 +28,17 @@
- 使用可选链 `?.` 和空值合并 `??` 处理可空类型
- `uni.showModal` 的 `success` 回调不能是 `async` 函数
- 函数必须在使用前定义
- Promise.all 需要显式指定类型参数,如 Promise.all<any>([...])
- CSS 伪类选择器 :nth-child() 不支持,需要移除
- 如果函数 A 调用了函数 B则函数 B 必须在函数 A 之前定义(不仅是调用顺序,而是定义顺序)
- 不支持 Record<K, V> 对象字面量语法,需要改用 if-else 或 Map
- Promise.all 在某些情况下类型推断失败,可以改用顺序执行 await
- onLoad 的 options 参数是 any 类型,需要转换为 UTSJSONObject 后使用 getString() 方法访问属性
- decodeURIComponent 返回 String? 类型,需要检查 null 后再赋值
- 条件判断必须使用布尔类型,字符串要用 `!= ''` 代替 truthy 判断
- 数组元素类型要明确,不要用 any[],否则模板中无法访问元素属性
- 可空数组类型调用方法需要使用 `!` 非空断言,如 `arr!.join(',')`
- v-model 绑定的变量需要明确类型,如 `quantity: 1 as number`
- 不支持 `Record<K, V>` 对象字面量语法
- 模板中可空类型必须使用 `?.` 安全访问
- `uni.showModal` 的 `success` 回调不能是 `async` 函数

View File

@@ -83,21 +83,44 @@
<text class="section-desc">快速定位</text>
</view>
<view class="category-grid" v-if="categoryTab === 'category'">
<!-- 一级分类 -->
<view
v-for="category in categories"
v-for="category in parentCategories"
:key="category.id"
class="category-card"
@click="switchCategory(category)"
@click="onParentCategoryClick(category)"
:style="{ '--card-color': category.color }"
>
<view class="card-icon">
<text class="card-icon-text">{{ category.icon }}</text>
</view>
<text class="card-name">{{ category.name }}</text>
<text class="card-desc">{{ category.description }}</text>
</view>
</view>
<view class="category-grid" v-else>
<!-- 二级分类 -->
<view v-if="categoryTab === 'category' && showSubCategories && subCategories.length > 0" class="sub-category-grid">
<view class="sub-category-header">
<text class="sub-category-title">{{ selectedParentCategory?.name }}分类</text>
<text class="sub-category-close" @click="showSubCategories = false">✕</text>
</view>
<view class="sub-category-wrapper">
<view
v-for="subCat in subCategories"
:key="subCat.id"
class="sub-category-card"
@click="onSubCategoryClick(subCat)"
>
<view class="card-icon">
<text class="card-icon-text">{{ subCat.icon }}</text>
</view>
<text class="card-name">{{ subCat.name }}</text>
</view>
</view>
</view>
<!-- 品牌列表 -->
<view class="category-grid" v-if="categoryTab === 'brand'">
<view
v-for="brand in brands"
:key="brand.id"
@@ -304,6 +327,12 @@ const categoryTab = ref<string>('category')
const categories = ref<Category[]>([])
const brands = ref<Brand[]>([])
// 一级分类和二级分类
const parentCategories = ref<Category[]>([])
const subCategories = ref<Category[]>([])
const selectedParentCategory = ref<Category | null>(null)
const showSubCategories = ref(false)
// 排序标签类型
type SortTab = {
id: string
@@ -342,38 +371,60 @@ const healthNews = [
}
]
// 获取分类数据
// 获取一级分类数据
const loadCategories = async (): Promise<void> => {
try {
const categoriesData = await supabaseService.getCategories()
// 映射字段根据ml_categories表结构映射
const mappedCategories: Category[] = []
const rawList = categoriesData as any[]
for (let i = 0; i < rawList.length; i++) {
const raw = rawList[i]
const catObj = (raw instanceof UTSJSONObject) ? (raw as UTSJSONObject) : (JSON.parse(JSON.stringify(raw)) as UTSJSONObject)
const name = catObj.getString('name') ?? ''
// 过滤掉医药健康相关分类
if (name.includes('医药') || name.includes('健康')) {
continue
}
const id = catObj.getString('id') ?? ''
const description = catObj.getString('description') ?? ''
const icon = catObj.getString('icon') ?? catObj.getString('icon_url') ?? '📦'
const color = catObj.getString('color') ?? '#4CAF50'
// 使用 JSON.parse 创建对象,避免接口实例化问题
const categoryItem = JSON.parse(`{"id":"${id}","name":"${name}","icon":"${icon}","description":"${description}","color":"${color}"}`) as Category
mappedCategories.push(categoryItem)
}
// 保持原始顺序或按ID排序移除随机打乱
categories.value = mappedCategories
const categoriesData = await supabaseService.getParentCategories()
parentCategories.value = categoriesData
// 兼容其他使用 categories 的地方
categories.value = categoriesData
} catch (error) {
console.error('加载分类数据失败:', error)
// 如果加载失败,使用默认分类作为后备
parentCategories.value = []
categories.value = []
}
}
// 获取二级分类数据
const loadSubCategories = async (parentId: string): Promise<void> => {
try {
const subData = await supabaseService.getSubCategories(parentId)
subCategories.value = subData
} catch (error) {
console.error('加载子分类数据失败:', error)
subCategories.value = []
}
}
// 点击一级分类
const onParentCategoryClick = async (category: Category): Promise<void> => {
// 如果已经选中,则切换显示/隐藏二级分类
if (selectedParentCategory.value != null && selectedParentCategory.value.id === category.id) {
showSubCategories.value = !showSubCategories.value
return
}
// 选中新的分类
selectedParentCategory.value = category
showSubCategories.value = true
// 加载二级分类
await loadSubCategories(category.id)
}
// 点击二级分类
const onSubCategoryClick = (category: Category): void => {
// 跳转到分类页面
uni.setStorageSync('selectedCategory', category.id)
const timestamp = Date.now()
const randomParam = Math.random().toString(36).substring(2, 8)
const url = `/pages/mall/consumer/category?categoryId=${category.id}&name=${encodeURIComponent(category.name)}&timestamp=${timestamp}&random=${randomParam}`
uni.switchTab({
url: '/pages/mall/consumer/category'
})
}
// 获取品牌数据
const loadBrands = async () => {
try {
@@ -1138,7 +1189,7 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
}
.category-card {
width: 47%; /* 50 - 3 */
width: 23%; /* 一行4个 */
margin: 0 1.5% 16px 1.5%;
display: flex;
flex-direction: column;
@@ -1187,6 +1238,78 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
text-align: center;
}
/* 二级分类样式 */
.sub-category-grid {
background: #f8f9fa;
border-radius: 12px;
padding: 16px;
margin-top: 16px;
}
.sub-category-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #e0e0e0;
}
.sub-category-title {
font-size: 14px;
font-weight: bold;
color: #333;
}
.sub-category-close {
font-size: 16px;
color: #999;
padding: 4px 8px;
}
.sub-category-wrapper {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
}
.sub-category-card {
width: 23%;
background: white;
border-radius: 8px;
padding: 10px 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid #eee;
margin-right: 2%;
margin-bottom: 10px;
}
.sub-category-card .card-icon {
width: 36px;
height: 36px;
border-radius: 18px;
margin-bottom: 6px;
display: flex;
align-items: center;
justify-content: center;
}
.sub-category-card .card-icon-text {
font-size: 18px;
}
.sub-category-card .card-name {
font-size: 11px;
color: #333;
text-align: center;
lines: 1;
text-overflow: ellipsis;
}
/* 健康资讯 */
.health-news {
background: white;

View File

@@ -55,24 +55,8 @@ const totalPoints = ref<number>(0)
const records = ref<PointRecord[]>([])
const loading = ref<boolean>(true)
onMounted(() => {
loadData()
})
const loadData = async () => {
loading.value = true
await Promise.all([
loadPoints(),
loadRecords()
])
loading.value = false
}
const loadPoints = async () => {
// 调用 service 获取积分 (需要supabaseService支持)
// 暂时如果service没更新先用mock
// const res = await supabaseService.getUserPoints()
// if (res != null) totalPoints.value = res
// 函数必须按调用顺序定义:先定义被调用的函数
const loadPoints = async (): Promise<void> => {
try {
const points = await supabaseService.getUserPoints()
totalPoints.value = points
@@ -81,15 +65,27 @@ const loadPoints = async () => {
}
}
const loadRecords = async () => {
const loadRecords = async (): Promise<void> => {
try {
const list = await supabaseService.getPointRecords()
records.value = list
records.value = list as PointRecord[]
} catch (e) {
console.error('获取积分记录失败', e)
}
}
// loadData 在 loadPoints 和 loadRecords 之后定义
const loadData = async (): Promise<void> => {
loading.value = true
await loadPoints()
await loadRecords()
loading.value = false
}
onMounted(() => {
loadData()
})
const handleExchange = () => {
uni.showToast({
title: '积分商城开发中',
@@ -98,14 +94,20 @@ const handleExchange = () => {
}
const getTypeText = (type: string): string => {
const map: Record<string, string> = {
'signin': '每日签到',
'shopping': '购物奖励',
'redeem': '积分兑换',
'admin': '系统调整',
'register': '注册赠送'
// 不支持 Record<string, string>,使用 if-else
if (type == 'signin') {
return '每日签到'
} else if (type == 'shopping') {
return '购物奖励'
} else if (type == 'redeem') {
return '积分兑换'
} else if (type == 'admin') {
return '系统调整'
} else if (type == 'register') {
return '注册赠送'
} else {
return '积分变动'
}
return map[type] ?? '积分变动'
}
const formatTime = (timeStr: string): string => {

View File

@@ -81,7 +81,7 @@
</view>
<input class="quantity-input"
type="number"
v-model="quantity"
:value="quantity"
:min="1"
:max="getMaxQuantity()"
@input="validateQuantity" />
@@ -97,7 +97,7 @@
<view class="section-title">商品详情</view>
<text class="description-text">{{ product.description ?? '暂无详细描述' }}</text>
<!-- 商品详情图片 -->
<view class="detail-images" v-if="product.images && product.images.length > 0">
<view class="detail-images" v-if="product.images != null && product.images.length > 0">
<image v-for="(img, index) in product.images"
:key="index"
:src="img"
@@ -187,9 +187,9 @@
<text class="params-label">批准文号</text>
<text class="params-value">{{ product.approval_number }}</text>
</view>
<view class="params-item" v-if="product.tags && product.tags.length > 0">
<view class="params-item" v-if="product.tags != null && product.tags.length > 0">
<text class="params-label">标签</text>
<text class="params-value">{{ product.tags.join(', ') }}</text>
<text class="params-value">{{ product.tags!.join(', ') }}</text>
</view>
</scroll-view>
</view>
@@ -226,7 +226,7 @@
</template>
<script>
import { ProductType, MerchantType, ProductSkuType } from '@/types/mall-types.uts'
import { ProductType, MerchantType, ProductSkuType, CouponTemplateType } from '@/types/mall-types.uts'
import { supabaseService } from '@/utils/supabaseService.uts'
export default {
@@ -265,40 +265,49 @@ export default {
showSpec: false,
selectedSkuId: '',
selectedSpec: '',
quantity: 1,
quantity: 1 as number,
isFavorite: false,
showParams: false,
// 新增: 优惠券相关
coupons: [] as any[],
coupons: [] as CouponTemplateType[],
showCoupons: false
}
},
onLoad(options: any) {
const productId = (options['productId'] ?? options['id']) as string
const productPrice = options.price ? parseFloat(options.price) : null
const productOriginalPrice = options.originalPrice ? parseFloat(options.originalPrice) : null
const opts = options as UTSJSONObject
const productId = (opts.getString('productId') ?? opts.getString('id')) ?? ''
const priceStr = opts.getString('price')
const productPrice = priceStr != null ? parseFloat(priceStr) : null
const originalPriceStr = opts.getString('originalPrice')
const productOriginalPrice = originalPriceStr != null ? parseFloat(originalPriceStr) : null
// 处理商品名称:如果是编码的则解码,否则直接使用
let productName = options.name as string
if (productName) {
let productName = opts.getString('name') ?? ''
if (productName != '') {
try {
// 尝试解码如果失败不是有效的URI组件则使用原值
productName = decodeURIComponent(productName)
const decoded = decodeURIComponent(productName)
if (decoded != null) {
productName = decoded
}
} catch (e) {
console.warn('ProductName decode failed, using original:', productName)
}
}
let productImage = options.image as string
if (productImage) {
let productImage = opts.getString('image') ?? ''
if (productImage != '') {
try {
productImage = decodeURIComponent(productImage)
const decoded = decodeURIComponent(productImage)
if (decoded != null) {
productImage = decoded
}
} catch (e) {
console.warn('ProductImage decode failed, using original:', productImage)
}
}
if (productId) {
if (productId != '') {
this.loadProductDetail(productId, {
price: productPrice,
originalPrice: productOriginalPrice,
@@ -309,7 +318,7 @@ export default {
this.saveFootprint(productId)
// 设置导航栏标题为商品名称
if (productName) {
if (productName != '') {
uni.setNavigationBarTitle({
title: productName
})
@@ -318,9 +327,9 @@ export default {
},
computed: {
displayPrice(): number {
if (this.selectedSkuId) {
if (this.selectedSkuId != '') {
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
if (sku) return sku.price
if (sku != null) return sku.price
}
return this.product.price
}
@@ -334,10 +343,10 @@ export default {
}
})
const footprintData = uni.getStorageSync('footprints')
const footprintData = uni.getStorageSync('footprints') as string
let footprints: any[] = []
if (footprintData) {
if (footprintData != null && footprintData !== '') {
try {
footprints = JSON.parse(footprintData as string) as any[]
} catch (e) {
@@ -346,7 +355,12 @@ export default {
}
// 移除已存在的相同商品(为了将其移到最新位置)
footprints = footprints.filter(item => item.id !== productId)
const productIdStr = productId
footprints = footprints.filter(function(item: any): boolean {
const itemObj = item as UTSJSONObject
const itemId = itemObj.getString('id') ?? ''
return itemId != productIdStr
})
// 添加到头部
footprints.unshift({
@@ -373,54 +387,62 @@ export default {
try {
const dbProductResponse = await supabaseService.getProductById(productId)
let dbProduct: any | null = null
if (Array.isArray(dbProductResponse) && dbProductResponse.length > 0) {
dbProduct = dbProductResponse[0]
} else if (dbProductResponse && !Array.isArray(dbProductResponse)) {
if (Array.isArray(dbProductResponse) && (dbProductResponse as any[]).length! > 0) {
dbProduct = (dbProductResponse as any[])[0]
} else if (dbProductResponse != null) {
dbProduct = dbProductResponse
}
if (dbProduct) {
if (dbProduct != null) {
// Map DB product to local product
const dbObj = dbProduct as UTSJSONObject
this.product = {
id: dbProduct.id,
merchant_id: dbProduct.merchant_id ?? dbProduct.shop_id ?? '',
category_id: dbProduct.category_id ?? '',
name: dbProduct.name,
description: dbProduct.description ?? '',
id: dbObj.getString('id') ?? '',
merchant_id: dbObj.getString('merchant_id') ?? dbObj.getString('shop_id') ?? '',
category_id: dbObj.getString('category_id') ?? '',
name: dbObj.getString('name') ?? '',
description: dbObj.getString('description') ?? '',
images: [] as string[],
price: dbProduct.base_price ?? dbProduct.price ?? 0,
original_price: dbProduct.market_price ?? dbProduct.original_price ?? 0,
stock: dbProduct.available_stock ?? dbProduct.total_stock ?? dbProduct.stock ?? 0,
sales: dbProduct.sale_count ?? dbProduct.sales ?? 0,
status: dbProduct.status !== undefined ? dbProduct.status : 1,
created_at: dbProduct.created_at ?? new Date().toISOString(),
price: dbObj.getNumber('base_price') ?? dbObj.getNumber('price') ?? 0,
original_price: dbObj.getNumber('market_price') ?? dbObj.getNumber('original_price') ?? 0,
stock: dbObj.getNumber('available_stock') ?? dbObj.getNumber('total_stock') ?? dbObj.getNumber('stock') ?? 0,
sales: dbObj.getNumber('sale_count') ?? dbObj.getNumber('sales') ?? 0,
status: dbObj.getNumber('status') ?? 1,
created_at: dbObj.getString('created_at') ?? new Date().toISOString(),
// Attributes
specification: dbProduct.specification ?? null,
usage: dbProduct.usage ?? null,
side_effects: dbProduct.side_effects ?? null,
precautions: dbProduct.precautions ?? null,
expiry_date: dbProduct.expiry_date ?? null,
storage_conditions: dbProduct.storage_conditions ?? null,
approval_number: dbProduct.approval_number ?? null,
specification: dbObj.getString('specification') ?? null,
usage: dbObj.getString('usage') ?? null,
side_effects: dbObj.getString('side_effects') ?? null,
precautions: dbObj.getString('precautions') ?? null,
expiry_date: dbObj.getString('expiry_date') ?? null,
storage_conditions: dbObj.getString('storage_conditions') ?? null,
approval_number: dbObj.getString('approval_number') ?? null,
tags: [] as string[]
} as ProductType
// Handle Images
if (dbProduct.image_urls) {
const imageUrls = dbObj.get('image_urls')
if (imageUrls != null) {
try {
const parsed = typeof dbProduct.image_urls === 'string' ? JSON.parse(dbProduct.image_urls) : dbProduct.image_urls
const parsed = typeof imageUrls === 'string' ? JSON.parse(imageUrls as string) : imageUrls
if (Array.isArray(parsed)) {
this.product.images = parsed.map((i: any) => String(i))
this.product.images = parsed.map((i: any): string => '' + i)
}
} catch (e) { console.error('Error parsing image_urls', e) }
}
// Fallback to main_image_url if no images found
if (this.product.images.length === 0 && dbProduct.main_image_url) {
this.product.images.push(dbProduct.main_image_url)
if (this.product.images.length === 0) {
const mainImg = dbObj.getString('main_image_url')
if (mainImg != null && mainImg != '') {
this.product.images.push(mainImg)
}
}
// Fallback to 'image' field (legacy)
if (this.product.images.length === 0 && dbProduct.image) {
this.product.images.push(dbProduct.image)
if (this.product.images.length === 0) {
const legacyImg = dbObj.getString('image')
if (legacyImg != null && legacyImg != '') {
this.product.images.push(legacyImg)
}
}
// Final fallback
if (this.product.images.length === 0) {
@@ -428,24 +450,28 @@ export default {
}
// Handle Tags
if (dbProduct.tags) {
const tagsVal = dbObj.get('tags')
if (tagsVal != null) {
try {
const parsedTags = typeof dbProduct.tags === 'string' ? JSON.parse(dbProduct.tags) : dbProduct.tags
const parsedTags = typeof tagsVal === 'string' ? JSON.parse(tagsVal as string) : tagsVal
if (Array.isArray(parsedTags)) {
this.product.tags = parsedTags.map((t: any) => String(t))
this.product.tags = parsedTags.map((t: any): string => '' + t)
}
} catch (e) {}
}
// Handle JSON attributes if present
if (dbProduct.attributes && typeof dbProduct.attributes === 'string') {
const attrsVal = dbObj.get('attributes')
if (attrsVal != null && typeof attrsVal === 'string') {
try {
const attrs = JSON.parse(dbProduct.attributes)
if (attrs) {
// Merge attributes into product if they match keys
if (attrs.specification) this.product.specification = attrs.specification
if (attrs.usage) this.product.usage = attrs.usage
// ... augment as needed
const attrsObj = JSON.parse(attrsVal as string) as UTSJSONObject
const specVal = attrsObj.getString('specification')
if (specVal != null) {
this.product.specification = specVal
}
const usageVal = attrsObj.getString('usage')
if (usageVal != null) {
this.product.usage = usageVal
}
} catch(e) {}
}
@@ -456,18 +482,22 @@ export default {
console.error('Failed to load product detail:', e)
// Fallback to options if available
this.product.id = productId
this.product.name = options.name ? decodeURIComponent(options.name) : '未知商品'
this.product.price = options.price ? parseFloat(options.price) : 0
this.product.images = options.image ? [decodeURIComponent(options.image)] : ['/static/default-product.png']
const optsFallback = options as UTSJSONObject
const nameVal = optsFallback.getString('name')
this.product.name = (nameVal != null && nameVal != '') ? decodeURIComponent(nameVal) : '未知商品'
const priceVal = optsFallback.getString('price')
this.product.price = (priceVal != null && priceVal != '') ? parseFloat(priceVal) : 0
const imageVal = optsFallback.getString('image')
this.product.images = (imageVal != null && imageVal != '') ? [decodeURIComponent(imageVal)] : ['/static/default-product.png']
}
// Load Merchant and SKUs
if (this.product.merchant_id) {
if (this.product.merchant_id != '') {
await this.loadMerchantInfo(this.product.merchant_id)
// 加载优惠券
this.loadCoupons()
}
if (this.product.id) {
if (this.product.id != '') {
this.loadProductSkus(this.product.id)
}
@@ -647,15 +677,20 @@ export default {
},
getSkuSpecText(sku: ProductSkuType): string {
if (sku.specifications) {
const specs: any = sku.specifications
return Object.keys(specs).map(key => `${key}: ${specs[key]}`).join(', ')
if (sku.specifications != null) {
const specs = sku.specifications as Record<string, any>
const parts: string[] = []
// 使用 for-in 循环代替 Object.keys
for (const key in specs) {
parts.push(`${key}: ${specs[key]}`)
}
return sku.sku_code
return parts.join(', ')
}
return sku.sku_code ?? ''
},
async addToCart() {
if (this.productSkus.length > 0 && !this.selectedSkuId) {
if (this.productSkus.length > 0 && this.selectedSkuId == '') {
uni.showToast({
title: '请选择规格',
icon: 'none'
@@ -669,7 +704,8 @@ export default {
const success = await supabaseService.addToCart(
this.product.id,
this.quantity,
this.selectedSkuId
this.selectedSkuId,
this.product.merchant_id
)
uni.hideLoading()
@@ -687,7 +723,7 @@ export default {
},
buyNow() {
if (this.productSkus.length > 0 && !this.selectedSkuId) {
if (this.productSkus.length > 0 && this.selectedSkuId == '') {
uni.showToast({
title: '请选择规格',
icon: 'none'
@@ -695,16 +731,16 @@ export default {
return
}
const sku = this.selectedSkuId ? this.productSkus.find(s => s.id === this.selectedSkuId) : null
const sku = this.selectedSkuId != '' ? this.productSkus.find(s => s.id === this.selectedSkuId) : null
const selectedItem = {
id: this.selectedSkuId,
product_id: this.product.id,
sku_id: this.selectedSkuId,
product_name: this.product.name,
product_image: (sku && sku.image_url) ? sku.image_url : this.product.images[0],
sku_specifications: sku ? sku.specifications : {},
price: Number(parseFloat((sku ? sku.price : this.product.price).toString()).toFixed(2)),
product_image: (sku != null && sku.image_url != '') ? sku.image_url : this.product.images[0],
sku_specifications: sku != null ? sku.specifications : {},
price: Number(parseFloat((sku != null ? sku.price : this.product.price).toString()).toFixed(2)),
quantity: Number(this.quantity)
}
@@ -761,7 +797,7 @@ export default {
},
goToShop() {
if (this.merchant.user_id) {
if (this.merchant.user_id != '') {
uni.navigateTo({
url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.id}`
})
@@ -788,7 +824,7 @@ export default {
},
validateQuantity() {
let num = parseInt(this.quantity)
let num = Number(this.quantity)
if (isNaN(num)) num = 1
const maxQuantity = this.getMaxQuantity()
if (num < 1) num = 1
@@ -799,10 +835,10 @@ export default {
this.quantity = num
},
getMaxQuantity() {
if (this.selectedSkuId) {
getMaxQuantity(): number {
if (this.selectedSkuId != '') {
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
if (sku) return sku.stock
if (sku != null) return sku.stock
}
return this.product.stock
},

685
pagesme.json Normal file
View File

@@ -0,0 +1,685 @@
{
"pages": [
{
"path": "pages/user/login",
"style": {
"navigationBarTitleText": "用户登录",
"navigationStyle": "custom"
}
},
// {
// "path": "pages/mall/admin/homePage/index",
// "style": {
// "navigationBarTitleText": "管理后台",
// "navigationStyle": "custom"
// }
// },
{
"path": "pages/user/boot",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/user/register",
"style": {
"navigationBarTitleText": "注册"
}
},
{
"path": "pages/user/forgot-password",
"style": {
"navigationBarTitleText": "忘记密码"
}
},
{
"path": "pages/user/terms",
"style": {
"navigationBarTitleText": "用户协议与隐私政策"
}
},
{
"path": "pages/user/center",
"style": {
"navigationBarTitleText": "用户中心"
}
},
{
"path": "pages/user/profile",
"style": {
"navigationBarTitleText": "个人资料"
}
},
{
"path": "pages/user/change-password",
"style": {
"navigationBarTitleText": "修改密码"
}
},
{
"path": "pages/user/bind-phone",
"style": {
"navigationBarTitleText": "绑定手机"
}
},
{
"path": "pages/user/bind-email",
"style": {
"navigationBarTitleText": "绑定邮箱"
}
},
{
"path": "pages/mall/consumer/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/mall/consumer/category",
"style": {
"navigationBarTitleText": "分类",
"navigationStyle": "custom"
}
},
{
"path": "pages/mall/consumer/messages",
"style": {
"navigationBarTitleText": "消息",
"enablePullDownRefresh": true
}
},
{
"path": "pages/mall/consumer/cart",
"style": {
"navigationBarTitleText": "购物车"
}
},
{
"path": "pages/mall/consumer/profile",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"subPackages": [
{
"root": "pages/mall/consumer",
"pages": [
{
"path": "settings",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "wallet",
"style": {
"navigationBarTitleText": "我的钱包"
}
},
{
"path": "withdraw",
"style": {
"navigationBarTitleText": "余额提现"
}
},
{
"path": "search",
"style": {
"navigationBarTitleText": "搜索",
"navigationStyle": "custom"
}
},
{
"path": "product-detail",
"style": {
"navigationBarTitleText": "商品详情"
}
},
{
"path": "shop-detail",
"style": {
"navigationBarTitleText": "店铺详情"
}
},
{
"path": "coupons",
"style": {
"navigationBarTitleText": "我的优惠券"
}
},
{
"path": "favorites",
"style": {
"navigationBarTitleText": "我的收藏"
}
},
{
"path": "footprint",
"style": {
"navigationBarTitleText": "我的足迹"
}
},
{
"path": "address-list",
"style": {
"navigationBarTitleText": "收货地址"
}
},
{
"path": "address-edit",
"style": {
"navigationBarTitleText": "编辑地址"
}
},
{
"path": "checkout",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "payment",
"style": {
"navigationBarTitleText": "收银台"
}
},
{
"path": "payment-success",
"style": {
"navigationBarTitleText": "支付成功",
"navigationStyle": "custom"
}
},
{
"path": "orders",
"style": {
"navigationBarTitleText": "我的订单",
"enablePullDownRefresh": true
}
},
{
"path": "order-detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "logistics",
"style": {
"navigationBarTitleText": "物流详情"
}
},
{
"path": "review",
"style": {
"navigationBarTitleText": "评价晒单"
}
},
{
"path": "refund",
"style": {
"navigationBarTitleText": "退款/售后"
}
},
{
"path": "apply-refund",
"style": {
"navigationBarTitleText": "申请售后"
}
},
{
"path": "refund-review",
"style": {
"navigationBarTitleText": "服务评价"
}
},
{
"path": "chat",
"style": {
"navigationBarTitleText": "客服聊天",
"navigationStyle": "custom"
}
},
{
"path": "subscription/plan-list",
"style": {
"navigationBarTitleText": "软件订阅"
}
},
{
"path": "subscription/plan-detail",
"style": {
"navigationBarTitleText": "订阅详情"
}
},
{
"path": "subscription/subscribe-checkout",
"style": {
"navigationBarTitleText": "确认订阅"
}
},
{
"path": "subscription/my-subscriptions",
"style": {
"navigationBarTitleText": "我的订阅"
}
},
{
"path": "subscription/followed-shops",
"style": {
"navigationBarTitleText": "关注店铺"
}
},
{
"path": "points/index",
"style": {
"navigationBarTitleText": "积分管理"
}
},
{
"path": "red-packets/index",
"style": {
"navigationBarTitleText": "我的红包"
}
},
{
"path": "bank-cards/index",
"style": {
"navigationBarTitleText": "银行卡管理"
}
},
{
"path": "bank-cards/add",
"style": {
"navigationBarTitleText": "添加银行卡"
}
}
]
}
// {
// "root": "pages/mall/delivery",
// "pages": [
// {
// "path": "index",
// "style": {
// "navigationBarTitleText": "配送中心",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "order-detail",
// "style": {
// "navigationBarTitleText": "订单详情页",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "profile",
// "style": {
// "navigationBarTitleText": "配送个人中心",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "order-history",
// "style": {
// "navigationBarTitleText": "历史记录",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "earnings",
// "style": {
// "navigationBarTitleText": "收入明细",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "tasks",
// "style": {
// "navigationBarTitleText": "全部任务",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "task-detail",
// "style": {
// "navigationBarTitleText": "任务详情",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "profile-edit",
// "style": {
// "navigationBarTitleText": "编辑个人资料",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "ratings",
// "style": {
// "navigationBarTitleText": "评价",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "vehicle",
// "style": {
// "navigationBarTitleText": "车辆管理",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "vehicle-add",
// "style": {
// "navigationBarTitleText": "添加车辆",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "vehicle-edit",
// "style": {
// "navigationBarTitleText": "编辑车辆",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "help-center",
// "style": {
// "navigationBarTitleText": "帮助中心",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "about",
// "style": {
// "navigationBarTitleText": "关于我们",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "feedback",
// "style": {
// "navigationBarTitleText": "意见反馈",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "test",
// "style": {
// "navigationBarTitleText": "test",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "settings",
// "style": {
// "navigationBarTitleText": "设置",
// "navigationStyle": "custom"
// }
// }
// ]
// },
// {
// "root": "pages/mall/analytics",
// "pages": [
// {
// "path": "index",
// "style": {
// "navigationBarTitleText": "数据分析",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "profile",
// "style": {
// "navigationBarTitleText": "数据分析个人中心"
// }
// },
// {
// "path": "sales-report",
// "style": {
// "navigationBarTitleText": "销售报表"
// }
// },
// {
// "path": "user-analysis",
// "style": {
// "navigationBarTitleText": "用户分析"
// }
// },
// {
// "path": "product-insights",
// "style": {
// "navigationBarTitleText": "商品洞察"
// }
// },
// {
// "path": "delivery-analysis",
// "style": {
// "navigationBarTitleText": "配送效率分析"
// }
// },
// {
// "path": "coupon-analysis",
// "style": {
// "navigationBarTitleText": "优惠券效果分析"
// }
// },
// {
// "path": "market-trends",
// "style": {
// "navigationBarTitleText": "市场趋势"
// }
// },
// {
// "path": "custom-report",
// "style": {
// "navigationBarTitleText": "自定义报表"
// }
// },
// {
// "path": "report-detail",
// "style": {
// "navigationBarTitleText": "报表详情",
// "enablePullDownRefresh": false
// }
// },
// {
// "path": "data-detail",
// "style": {
// "navigationBarTitleText": "数据分析详情",
// "enablePullDownRefresh": false
// }
// },
// {
// "path": "insight-detail",
// "style": {
// "navigationBarTitleText": "数据洞察详情",
// "enablePullDownRefresh": false
// }
// }
// ]
// },
// {
// "root": "pages/mall/admin",
// "pages": [
// {
// "path": "user-management",
// "style": {
// "navigationBarTitleText": "用户管理",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "product-management",
// "style": {
// "navigationBarTitleText": "商品管理",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "order-management",
// "style": {
// "navigationBarTitleText": "订单管理",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "finance/record",
// "style": {
// "navigationBarTitleText": "财务管理",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "user-statistics",
// "style": {
// "navigationBarTitleText": "用户统计",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "system-settings",
// "style": {
// "navigationBarTitleText": "系统设置",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "subscription/plan-management",
// "style": {
// "navigationBarTitleText": "订阅方案管理"
// }
// },
// {
// "path": "subscription/user-subscriptions",
// "style": {
// "navigationBarTitleText": "用户订阅管理"
// }
// },
// {
// "path": "marketing/coupon/list",
// "style": {
// "navigationBarTitleText": "优惠券列表"
// }
// },
// {
// "path": "marketing/coupon/receive",
// "style": {
// "navigationBarTitleText": "用户领取记录"
// }
// },
// {
// "path": "marketing/signin/rule",
// "style": {
// "navigationBarTitleText": "签到规则"
// }
// },
// {
// "path": "marketing/signin/record",
// "style": {
// "navigationBarTitleText": "签到记录"
// }
// }
// ]
// },
// {
// "root": "pages/mall/merchant",
// "pages": [
// {
// "path": "index",
// "style": {
// "navigationBarTitleText": "商家中心",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "product-detail",
// "style": {
// "navigationBarTitleText": "商品管理详情",
// "enablePullDownRefresh": false
// }
// },
// {
// "path": "profile",
// "style": {
// "navigationBarTitleText": "个人资料"
// }
// }
// ]
// },
// {
// "root": "pages/mall/service",
// "pages": [
// {
// "path": "index",
// "style": {
// "navigationBarTitleText": "客服工作台",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "profile",
// "style": {
// "navigationBarTitleText": "客服个人中心"
// }
// },
// {
// "path": "ticket-detail",
// "style": {
// "navigationBarTitleText": "工单详情",
// "enablePullDownRefresh": false
// }
// }
// ]
// }
],
"tabBar": {
"color": "#999999",
"selectedColor": "#ff5000",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/mall/consumer/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/mall/consumer/category",
"text": "分类",
"iconPath": "static/tabbar/category.png",
"selectedIconPath": "static/tabbar/category-active.png"
},
{
"pagePath": "pages/mall/consumer/messages",
"text": "消息",
"iconPath": "static/tabbar/messages.png",
"selectedIconPath": "static/tabbar/messages-active.png"
},
{
"pagePath": "pages/mall/consumer/cart",
"text": "购物车",
"iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart-active.png"
},
{
"pagePath": "pages/mall/consumer/profile",
"text": "我的",
"iconPath": "static/tabbar/profile.png",
"selectedIconPath": "static/tabbar/profile-active.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "mall",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More