- 将图片显示统一替换为 getCompressedImageUrl 函数处理压缩 - 覆盖 35 个文件,包括 4 个组件和 31 个页面 - 主要组件:LazyImage、ProductCard、OrderCard、SkuSelector 内部自动压缩图片 - 主要页面:购物车、订单、积分、活动、拼团、秒杀、收藏、浏览历史等 - 跳过用户头像、二维码、商品详情大图及所有 Taro.previewImage 调用 - 调整压缩默认宽度为 300,保持质量90,启用压缩功能
678 lines
26 KiB
TypeScript
678 lines
26 KiB
TypeScript
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
||
import { View, Text, Image, ScrollView, Input, Textarea } from '@tarojs/components'
|
||
import Taro, { useDidShow } from '@tarojs/taro'
|
||
import { pageShopGoods, updateShopGoods } from '@/api/shop/shopGoods'
|
||
import { listShopGoodsCategory } from '@/api/shop/shopGoodsCategory'
|
||
import { uploadFile } from '@/api/system/file'
|
||
import type { ShopGoods, ShopGoodsParam } from '@/api/shop/shopGoods/model'
|
||
import type { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model'
|
||
import { getCompressedImageUrl } from '@/utils/image'
|
||
|
||
definePageConfig({
|
||
navigationBarTitleText: '商品管理',
|
||
})
|
||
|
||
// ─── Tab 配置 ──────────────────────────────────────────────────
|
||
type TabKey = 'all' | 'on_sale' | 'pending' | 'sold_out'
|
||
|
||
const TABS: { key: TabKey; label: string; param: Partial<ShopGoodsParam> }[] = [
|
||
{ key: 'all', label: '全部', param: {} },
|
||
{ key: 'on_sale', label: '已上架', param: { status: 0 } },
|
||
{ key: 'pending', label: '待上架', param: { status: 1 } },
|
||
{ key: 'sold_out', label: '已售罄', param: { stock: 0 } },
|
||
]
|
||
|
||
// ─── 状态辅助函数 ──────────────────────────────────────────────────
|
||
function getStatusText(status?: number): string {
|
||
const map: Record<number, string> = { 0: '已上架', 1: '待上架', 2: '待审核', 3: '审核不通过' }
|
||
return map[status ?? 0] || '未知'
|
||
}
|
||
|
||
function getStatusColor(status?: number): string {
|
||
const map: Record<number, string> = { 0: '#16a34a', 1: '#ea580c', 2: '#9333ea', 3: '#dc2626' }
|
||
return map[status ?? 0] || '#999'
|
||
}
|
||
|
||
// ─── 页面组件 ──────────────────────────────────────────────────────
|
||
export default function StoreGoodsPage() {
|
||
const [activeTab, setActiveTab] = useState<TabKey>('all')
|
||
|
||
// 商品列表与分页
|
||
const [goodsList, setGoodsList] = useState<ShopGoods[]>([])
|
||
const [page, setPage] = useState(1)
|
||
const [hasMore, setHasMore] = useState(true)
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
// 搜索与筛选
|
||
const [keyword, setKeyword] = useState('')
|
||
const [searchText, setSearchText] = useState('')
|
||
const [categories, setCategories] = useState<ShopGoodsCategory[]>([])
|
||
const [selectedCategory, setSelectedCategory] = useState<number | undefined>(undefined)
|
||
const [showCategoryPicker, setShowCategoryPicker] = useState(false)
|
||
|
||
// 编辑弹窗
|
||
const [showEditModal, setShowEditModal] = useState(false)
|
||
const [editGoods, setEditGoods] = useState<ShopGoods | null>(null)
|
||
const [editForm, setEditForm] = useState({
|
||
price: '',
|
||
salePrice: '',
|
||
dealerPrice: '',
|
||
stock: '',
|
||
sortNumber: '',
|
||
comments: '',
|
||
files: [] as Array<{ uid?: number; url: string; status?: string }>,
|
||
})
|
||
const [submitting, setSubmitting] = useState(false)
|
||
const [uploadingImage, setUploadingImage] = useState(false)
|
||
const [uploadingBanner, setUploadingBanner] = useState(false)
|
||
|
||
const pageSize = 10
|
||
const loadingRef = useRef(false)
|
||
|
||
/** 加载商品列表 */
|
||
const loadGoods = useCallback(async (tab: TabKey, pageNo: number = 1, append = false, categoryId?: number) => {
|
||
if (loadingRef.current) return
|
||
loadingRef.current = true
|
||
setLoading(true)
|
||
|
||
try {
|
||
const tabConfig = TABS.find(t => t.key === tab)
|
||
if (!tabConfig) return
|
||
|
||
const params: ShopGoodsParam = {
|
||
...tabConfig.param,
|
||
page: pageNo,
|
||
limit: pageSize,
|
||
}
|
||
if (keyword) params.keywords = keyword
|
||
const effectiveCategoryId = categoryId !== undefined ? categoryId : selectedCategory
|
||
if (effectiveCategoryId) params.categoryId = effectiveCategoryId
|
||
|
||
const res = await pageShopGoods(params)
|
||
const list = res?.list || []
|
||
|
||
// 已售罄 Tab 需要客户端二次过滤 stock <= 0
|
||
const filtered = tab === 'sold_out' ? list.filter(g => (g.stock ?? 0) <= 0) : list
|
||
|
||
setGoodsList(prev => append ? [...prev, ...filtered] : filtered)
|
||
setPage(pageNo)
|
||
setHasMore(list.length >= pageSize)
|
||
} catch (e: any) {
|
||
Taro.showToast({ title: e.message || '加载失败', icon: 'none' })
|
||
if (!append) setGoodsList([])
|
||
} finally {
|
||
loadingRef.current = false
|
||
setLoading(false)
|
||
}
|
||
}, [keyword, selectedCategory])
|
||
|
||
// 加载分类列表
|
||
useEffect(() => {
|
||
listShopGoodsCategory({})
|
||
.then(data => setCategories(data || []))
|
||
.catch(() => {})
|
||
}, [])
|
||
|
||
// 切换 tab 时重新加载
|
||
useEffect(() => {
|
||
loadGoods(activeTab, 1)
|
||
}, [activeTab, loadGoods])
|
||
|
||
// 页面重新显示时刷新
|
||
useDidShow(() => {
|
||
loadGoods(activeTab, 1)
|
||
})
|
||
|
||
/** 加载更多 */
|
||
const handleLoadMore = () => {
|
||
if (hasMore && !loadingRef.current) {
|
||
loadGoods(activeTab, page + 1, true)
|
||
}
|
||
}
|
||
|
||
/** 执行搜索 */
|
||
const handleSearch = () => {
|
||
setKeyword(searchText)
|
||
loadGoods(activeTab, 1)
|
||
}
|
||
|
||
/** 选择分类 */
|
||
const handleSelectCategory = (cat: ShopGoodsCategory | undefined) => {
|
||
setSelectedCategory(cat?.categoryId)
|
||
setShowCategoryPicker(false)
|
||
loadGoods(activeTab, 1, false, cat?.categoryId)
|
||
}
|
||
|
||
/** 快速上架/下架 */
|
||
const handleToggleStatus = async (goods: ShopGoods) => {
|
||
const newStatus = goods.status === 0 ? 1 : 0
|
||
const actionText = newStatus === 0 ? '上架' : '下架'
|
||
|
||
Taro.showModal({
|
||
title: '提示',
|
||
content: `确定要${actionText}商品「${goods.name}」吗?`,
|
||
success: async (res) => {
|
||
if (!res.confirm) return
|
||
try {
|
||
Taro.showLoading({ title: `${actionText}中...` })
|
||
await updateShopGoods({ ...goods, status: newStatus })
|
||
Taro.hideLoading()
|
||
Taro.showToast({ title: `${actionText}成功`, icon: 'success' })
|
||
loadGoods(activeTab, 1)
|
||
} catch (e: any) {
|
||
Taro.hideLoading()
|
||
Taro.showToast({ title: e.message || `${actionText}失败`, icon: 'none' })
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
/** 切换推荐 */
|
||
const handleToggleRecommend = async (goods: ShopGoods) => {
|
||
const newRecommend = goods.recommend === 1 ? 0 : 1
|
||
try {
|
||
await updateShopGoods({ ...goods, recommend: newRecommend })
|
||
Taro.showToast({ title: newRecommend === 1 ? '已推荐' : '已取消推荐', icon: 'none' })
|
||
loadGoods(activeTab, 1)
|
||
} catch (e: any) {
|
||
Taro.showToast({ title: e.message || '操作失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
/** 打开编辑弹窗 */
|
||
const openEditModal = (goods: ShopGoods) => {
|
||
setEditGoods(goods)
|
||
const bannerFiles: Array<{ uid?: number; url: string; status?: string }> = []
|
||
if (goods.files) {
|
||
try {
|
||
const parsed = JSON.parse(goods.files)
|
||
if (Array.isArray(parsed)) {
|
||
bannerFiles.push(
|
||
...parsed
|
||
.map((item: any) =>
|
||
typeof item === 'string'
|
||
? { url: item }
|
||
: { uid: item.uid, url: item.url || '', status: item.status }
|
||
)
|
||
.filter((item: any) => item.url)
|
||
)
|
||
}
|
||
} catch {
|
||
// 非 JSON 数组时按逗号分隔兜底
|
||
bannerFiles.push(...goods.files.split(',').filter(Boolean).map(url => ({ url })))
|
||
}
|
||
}
|
||
setEditForm({
|
||
price: goods.price || '',
|
||
salePrice: goods.salePrice || '',
|
||
dealerPrice: goods.dealerPrice || '',
|
||
stock: String(goods.stock ?? ''),
|
||
sortNumber: String(goods.sortNumber ?? ''),
|
||
comments: goods.comments || '',
|
||
files: bannerFiles,
|
||
})
|
||
setShowEditModal(true)
|
||
}
|
||
|
||
/** 关闭编辑弹窗 */
|
||
const closeEditModal = () => {
|
||
setShowEditModal(false)
|
||
setEditGoods(null)
|
||
}
|
||
|
||
/** 上传/更换商品图片 */
|
||
const handleUploadImage = async () => {
|
||
if (uploadingImage) return
|
||
setUploadingImage(true)
|
||
try {
|
||
const res = await uploadFile()
|
||
const imageUrl = res.path || ''
|
||
if (imageUrl) {
|
||
setEditGoods(prev => prev ? { ...prev, image: imageUrl } : null)
|
||
Taro.showToast({ title: '上传成功', icon: 'success' })
|
||
}
|
||
} catch (e: any) {
|
||
Taro.showToast({ title: e.message || '上传失败', icon: 'none' })
|
||
} finally {
|
||
setUploadingImage(false)
|
||
}
|
||
}
|
||
|
||
/** 上传轮播图 */
|
||
const handleAddBanner = async () => {
|
||
if (uploadingBanner) return
|
||
setUploadingBanner(true)
|
||
try {
|
||
const res = await uploadFile()
|
||
const imageUrl = res.path || ''
|
||
if (imageUrl) {
|
||
setEditForm(prev => ({
|
||
...prev,
|
||
files: [...prev.files, { uid: res.id, url: imageUrl, status: 'done' }],
|
||
}))
|
||
Taro.showToast({ title: '上传成功', icon: 'success' })
|
||
}
|
||
} catch (e: any) {
|
||
Taro.showToast({ title: e.message || '上传失败', icon: 'none' })
|
||
} finally {
|
||
setUploadingBanner(false)
|
||
}
|
||
}
|
||
|
||
/** 删除轮播图 */
|
||
const handleRemoveBanner = (index: number) => {
|
||
setEditForm(prev => ({
|
||
...prev,
|
||
files: prev.files.filter((_, i) => i !== index),
|
||
}))
|
||
}
|
||
|
||
/** 提交编辑 */
|
||
const submitEdit = async () => {
|
||
if (!editGoods) return
|
||
|
||
const price = parseFloat(editForm.price)
|
||
if (isNaN(price) || price < 0) {
|
||
Taro.showToast({ title: '请输入有效的价格', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
const stock = parseInt(editForm.stock, 10)
|
||
if (isNaN(stock) || stock < 0) {
|
||
Taro.showToast({ title: '请输入有效的库存', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
setSubmitting(true)
|
||
try {
|
||
await updateShopGoods({
|
||
...editGoods,
|
||
image: editGoods.image,
|
||
price: editForm.price,
|
||
salePrice: editForm.salePrice || undefined,
|
||
dealerPrice: editForm.dealerPrice || undefined,
|
||
stock,
|
||
sortNumber: parseInt(editForm.sortNumber, 10) || 0,
|
||
comments: editForm.comments || undefined,
|
||
files: JSON.stringify(editForm.files),
|
||
})
|
||
Taro.showToast({ title: '保存成功', icon: 'success' })
|
||
closeEditModal()
|
||
loadGoods(activeTab, 1)
|
||
} catch (e: any) {
|
||
Taro.showToast({ title: e.message || '保存失败', icon: 'none' })
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
/** 渲染单个商品卡片 */
|
||
const renderGoodsCard = (goods: ShopGoods) => {
|
||
const isSoldOut = (goods.stock ?? 0) <= 0
|
||
const isOnSale = goods.status === 0
|
||
|
||
return (
|
||
<View key={goods.goodsId} className='bg-white rounded-xl mx-3 mt-3 p-4'>
|
||
{/* 商品头部:图片 + 名称 + 状态 */}
|
||
<View className='flex items-start gap-3 mb-3'>
|
||
{goods.image ? (
|
||
<Image
|
||
className='w-20 h-20 rounded-lg bg-gray-50 flex-shrink-0'
|
||
src={getCompressedImageUrl(goods.image,{ width: 120, quality: 90 })}
|
||
mode='aspectFill'
|
||
onClick={() => Taro.previewImage({ urls: [goods.image!] })}
|
||
/>
|
||
) : (
|
||
<View className='w-20 h-20 rounded-lg bg-gray-100 flex items-center justify-center flex-shrink-0'>
|
||
<Text className='text-2xl text-gray-300'>📦</Text>
|
||
</View>
|
||
)}
|
||
<View className='flex-1 min-w-0'>
|
||
<View className='flex items-center gap-2 mb-1'>
|
||
<Text
|
||
className='text-xs px-1.5 py-0.5 rounded'
|
||
style={{ color: getStatusColor(goods.status), background: getStatusColor(goods.status) + '15' }}
|
||
>
|
||
{getStatusText(goods.status)}
|
||
</Text>
|
||
{isSoldOut && isOnSale && (
|
||
<Text className='text-xs px-1.5 py-0.5 rounded bg-red-50 text-red-500'>售罄</Text>
|
||
)}
|
||
{goods.recommend === 1 && (
|
||
<Text className='text-xs px-1.5 py-0.5 rounded bg-amber-50 text-amber-600'>推荐</Text>
|
||
)}
|
||
</View>
|
||
<Text className='text-sm text-gray-800 block' style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||
{goods.name}
|
||
</Text>
|
||
{goods.categoryName && (
|
||
<Text className='text-xs text-gray-400 mt-1 block'>{goods.categoryName}</Text>
|
||
)}
|
||
</View>
|
||
</View>
|
||
|
||
{/* 价格信息 */}
|
||
<View className='flex items-center gap-4 mb-3 bg-gray-50 rounded-lg p-3'>
|
||
<View className='flex-1'>
|
||
<Text className='text-xs text-gray-400 block'>到手价</Text>
|
||
<Text className='text-base text-red-500 font-medium'>¥{goods.price || '0'}</Text>
|
||
</View>
|
||
{goods.salePrice && (
|
||
<View className='flex-1'>
|
||
<Text className='text-xs text-gray-400 block'>市场价</Text>
|
||
<Text className='text-sm text-gray-400 line-through'>¥{goods.salePrice}</Text>
|
||
</View>
|
||
)}
|
||
{goods.dealerPrice && (
|
||
<View className='flex-1'>
|
||
<Text className='text-xs text-gray-400 block'>VIP价</Text>
|
||
<Text className='text-sm text-purple-500'>¥{goods.dealerPrice}</Text>
|
||
</View>
|
||
)}
|
||
<View className='flex-1'>
|
||
<Text className='text-xs text-gray-400 block'>销量</Text>
|
||
<Text className='text-sm text-gray-700'>{goods.sales || 0}</Text>
|
||
</View>
|
||
<View className='flex-1'>
|
||
<Text className='text-xs text-gray-400 block'>库存</Text>
|
||
<Text className={`text-sm ${isSoldOut ? 'text-red-500' : 'text-gray-700'}`}>{goods.stock ?? 0}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 操作按钮 */}
|
||
<View className='flex items-center gap-2 border-t border-gray-50 pt-3'>
|
||
{/* 上架/下架 */}
|
||
<View
|
||
className={`flex-1 py-2 rounded-lg text-center ${isOnSale ? 'bg-orange-50' : 'bg-green-50'}`}
|
||
onClick={() => handleToggleStatus(goods)}
|
||
>
|
||
<Text className={`text-sm ${isOnSale ? 'text-orange-500' : 'text-green-500'}`}>
|
||
{isOnSale ? '下架' : '上架'}
|
||
</Text>
|
||
</View>
|
||
|
||
{/* 推荐 */}
|
||
<View
|
||
className={`flex-1 py-2 rounded-lg text-center ${goods.recommend === 1 ? 'bg-amber-50' : 'bg-gray-50'}`}
|
||
onClick={() => handleToggleRecommend(goods)}
|
||
>
|
||
<Text className={`text-sm ${goods.recommend === 1 ? 'text-amber-600' : 'text-gray-500'}`}>
|
||
{goods.recommend === 1 ? '取消推荐' : '推荐'}
|
||
</Text>
|
||
</View>
|
||
|
||
{/* 编辑 */}
|
||
<View
|
||
className='flex-1 py-2 rounded-lg text-center bg-blue-50'
|
||
onClick={() => openEditModal(goods)}
|
||
>
|
||
<Text className='text-sm text-blue-500'>编辑</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<View className='min-h-full bg-gray-50'>
|
||
{/* 搜索栏 */}
|
||
<View className='bg-white px-3 py-2 flex items-center gap-2'>
|
||
<View className='flex-1 flex items-center bg-gray-100 rounded-lg px-3 py-1.5'>
|
||
<Input
|
||
className='flex-1 text-sm'
|
||
placeholder='搜索商品名称'
|
||
value={searchText}
|
||
onInput={(e) => setSearchText(e.detail.value)}
|
||
onConfirm={handleSearch}
|
||
confirmType='search'
|
||
/>
|
||
{searchText ? (
|
||
<Text
|
||
className='text-gray-400 text-lg px-1'
|
||
onClick={() => { setSearchText(''); setKeyword(''); loadGoods(activeTab, 1) }}
|
||
>×</Text>
|
||
) : null}
|
||
</View>
|
||
<View
|
||
className='px-3 py-1.5 rounded-lg bg-gray-100 flex items-center'
|
||
onClick={() => setShowCategoryPicker(true)}
|
||
>
|
||
<Text className='text-sm text-gray-600'>
|
||
{selectedCategory
|
||
? categories.find(c => c.categoryId === selectedCategory)?.title || '分类'
|
||
: '分类'}
|
||
</Text>
|
||
<Text className='text-gray-400 text-xs ml-1'>▾</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Tab 栏 */}
|
||
<View className='bg-white flex border-b border-gray-50'>
|
||
{TABS.map(tab => (
|
||
<View
|
||
key={tab.key}
|
||
className={`flex-1 text-center py-3 border-b-2 ${
|
||
activeTab === tab.key ? 'border-cyan-500' : 'border-transparent'
|
||
}`}
|
||
onClick={() => setActiveTab(tab.key)}
|
||
>
|
||
<Text
|
||
className={`text-sm ${activeTab === tab.key ? 'text-cyan-500 font-medium' : 'text-gray-500'}`}
|
||
>
|
||
{tab.label}
|
||
</Text>
|
||
</View>
|
||
))}
|
||
</View>
|
||
|
||
{/* 商品列表 */}
|
||
<ScrollView
|
||
scrollY
|
||
style={{ height: 'calc(100vh - 100px)' }}
|
||
onScrollToLower={handleLoadMore}
|
||
lowerThreshold={100}
|
||
>
|
||
{loading && goodsList.length === 0 ? (
|
||
<View className='flex justify-center items-center py-20'>
|
||
<Text className='text-gray-400'>加载中...</Text>
|
||
</View>
|
||
) : goodsList.length === 0 ? (
|
||
<View className='flex justify-center items-center py-20'>
|
||
<Text className='text-gray-400'>暂无商品</Text>
|
||
</View>
|
||
) : (
|
||
goodsList.map(renderGoodsCard)
|
||
)}
|
||
{loading && goodsList.length > 0 && (
|
||
<View className='flex justify-center items-center py-4'>
|
||
<Text className='text-gray-400 text-sm'>加载中...</Text>
|
||
</View>
|
||
)}
|
||
{!hasMore && goodsList.length > 0 && (
|
||
<View className='flex justify-center items-center py-4'>
|
||
<Text className='text-gray-300 text-xs'>没有更多了</Text>
|
||
</View>
|
||
)}
|
||
<View className='h-6' />
|
||
</ScrollView>
|
||
|
||
{/* 分类选择弹窗 */}
|
||
{showCategoryPicker && (
|
||
<View className='fixed inset-0 z-50' onClick={() => setShowCategoryPicker(false)}>
|
||
<View className='absolute inset-0 bg-black/50' />
|
||
<View
|
||
className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl max-h-[60vh] overflow-y-auto'
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
<View className='sticky top-0 bg-white border-b border-gray-50 px-5 py-4 flex justify-between items-center'>
|
||
<Text className='text-base font-medium text-gray-800'>选择分类</Text>
|
||
<Text className='text-gray-400 text-lg' onClick={() => setShowCategoryPicker(false)}>×</Text>
|
||
</View>
|
||
<View
|
||
className='px-5 py-3.5 border-b border-gray-50'
|
||
onClick={() => handleSelectCategory(undefined)}
|
||
>
|
||
<Text className='text-sm text-gray-600'>全部分类</Text>
|
||
</View>
|
||
{categories.map(cat => (
|
||
<View
|
||
key={cat.categoryId}
|
||
className='px-5 py-3.5 border-b border-gray-50 flex justify-between items-center'
|
||
onClick={() => handleSelectCategory(cat)}
|
||
>
|
||
<Text className='text-sm text-gray-700'>{cat.title}</Text>
|
||
{selectedCategory === cat.categoryId && (
|
||
<Text className='text-cyan-500'>✓</Text>
|
||
)}
|
||
</View>
|
||
))}
|
||
<View className='h-8' />
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* 编辑弹窗 */}
|
||
{showEditModal && editGoods && (
|
||
<View className='fixed inset-0 z-50 flex items-end justify-center'>
|
||
<View className='absolute inset-0 bg-black/50' onClick={closeEditModal} />
|
||
<View className='relative bg-white rounded-t-2xl w-full px-5 pt-6 pb-10 max-h-[80vh] overflow-y-auto'>
|
||
<Text className='text-lg font-medium text-gray-800 text-center mb-5 block'>编辑商品</Text>
|
||
|
||
{/* 商品信息 */}
|
||
<View className='bg-gray-50 rounded-xl p-4 mb-5 flex items-center gap-3'>
|
||
<View className='relative' onClick={handleUploadImage}>
|
||
{editGoods.image ? (
|
||
<Image className='w-16 h-16 rounded-lg' src={getCompressedImageUrl(editGoods.image)} mode='aspectFill' />
|
||
) : (
|
||
<View className='w-16 h-16 rounded-lg bg-gray-200 flex items-center justify-center'>
|
||
<Text className='text-2xl text-gray-300'>📷</Text>
|
||
</View>
|
||
)}
|
||
<View className='absolute inset-0 rounded-lg bg-black/30 flex items-center justify-center'>
|
||
<Text className='text-white text-xs'>
|
||
{uploadingImage ? '上传中...' : '更换'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
<View className='flex-1 min-w-0'>
|
||
<Text className='text-sm text-gray-800 block' style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||
{editGoods.name}
|
||
</Text>
|
||
<Text className='text-xs text-gray-400 mt-1 block'>ID: {editGoods.goodsId}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 轮播图 */}
|
||
<View className='mb-5'>
|
||
<Text className='text-sm text-gray-600 mb-2 block'>轮播图</Text>
|
||
<View className='flex flex-wrap gap-3'>
|
||
{editForm.files.map((file, index) => (
|
||
<View key={file.url + index} className='relative w-20 h-20'>
|
||
<Image className='w-20 h-20 rounded-lg bg-gray-100' src={getCompressedImageUrl(file.url)} mode='aspectFill' />
|
||
<View
|
||
className='absolute -top-1.5 -right-1.5 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center'
|
||
onClick={() => handleRemoveBanner(index)}
|
||
>
|
||
<Text className='text-white text-xs'>×</Text>
|
||
</View>
|
||
</View>
|
||
))}
|
||
<View
|
||
className='w-20 h-20 rounded-lg border border-dashed border-gray-300 flex flex-col items-center justify-center'
|
||
onClick={handleAddBanner}
|
||
>
|
||
<Text className='text-2xl text-gray-300 mb-0.5'>{uploadingBanner ? '...' : '+'}</Text>
|
||
<Text className='text-xs text-gray-400'>{uploadingBanner ? '上传中' : '上传'}</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 表单字段 */}
|
||
<View className='mb-4'>
|
||
<Text className='text-sm text-gray-600 mb-2 block'>到手价 (¥)</Text>
|
||
<Input
|
||
className='bg-gray-50 rounded-lg px-4 py-3 text-sm'
|
||
type='digit'
|
||
placeholder='请输入价格'
|
||
value={editForm.price}
|
||
onInput={(e) => setEditForm(prev => ({ ...prev, price: e.detail.value }))}
|
||
/>
|
||
</View>
|
||
|
||
<View className='mb-4'>
|
||
<Text className='text-sm text-gray-600 mb-2 block'>市场价 (¥)</Text>
|
||
<Input
|
||
className='bg-gray-50 rounded-lg px-4 py-3 text-sm'
|
||
type='digit'
|
||
placeholder='选填,划线价'
|
||
value={editForm.salePrice}
|
||
onInput={(e) => setEditForm(prev => ({ ...prev, salePrice: e.detail.value }))}
|
||
/>
|
||
</View>
|
||
|
||
<View className='mb-4'>
|
||
<Text className='text-sm text-gray-600 mb-2 block'>会员价 (¥)</Text>
|
||
<Input
|
||
className='bg-gray-50 rounded-lg px-4 py-3 text-sm'
|
||
type='digit'
|
||
placeholder='VIP/经销商专享价'
|
||
value={editForm.dealerPrice}
|
||
onInput={(e) => setEditForm(prev => ({ ...prev, dealerPrice: e.detail.value }))}
|
||
/>
|
||
</View>
|
||
|
||
<View className='mb-4'>
|
||
<Text className='text-sm text-gray-600 mb-2 block'>库存</Text>
|
||
<Input
|
||
className='bg-gray-50 rounded-lg px-4 py-3 text-sm'
|
||
type='number'
|
||
placeholder='请输入库存数量'
|
||
value={editForm.stock}
|
||
onInput={(e) => setEditForm(prev => ({ ...prev, stock: e.detail.value }))}
|
||
/>
|
||
</View>
|
||
|
||
<View className='mb-4'>
|
||
<Text className='text-sm text-gray-600 mb-2 block'>排序号</Text>
|
||
<Input
|
||
className='bg-gray-50 rounded-lg px-4 py-3 text-sm'
|
||
type='number'
|
||
placeholder='数字越小越靠前'
|
||
value={editForm.sortNumber}
|
||
onInput={(e) => setEditForm(prev => ({ ...prev, sortNumber: e.detail.value }))}
|
||
/>
|
||
</View>
|
||
|
||
<View className='mb-6'>
|
||
<Text className='text-sm text-gray-600 mb-2 block'>备注</Text>
|
||
<Textarea
|
||
className='bg-gray-50 rounded-lg px-4 py-3 text-sm w-full'
|
||
placeholder='选填'
|
||
value={editForm.comments}
|
||
onInput={(e) => setEditForm(prev => ({ ...prev, comments: e.detail.value }))}
|
||
maxlength={200}
|
||
style={{ minHeight: '60px' }}
|
||
/>
|
||
</View>
|
||
|
||
{/* 操作按钮 */}
|
||
<View className='flex gap-3'>
|
||
<View className='flex-1 py-3 rounded-xl bg-gray-100 text-center' onClick={closeEditModal}>
|
||
<Text className='text-sm text-gray-600'>取消</Text>
|
||
</View>
|
||
<View
|
||
className='flex-1 py-3 rounded-xl bg-cyan-500 text-center'
|
||
onClick={submitting ? undefined : submitEdit}
|
||
>
|
||
<Text className='text-sm text-white'>
|
||
{submitting ? '保存中...' : '保存'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
)
|
||
}
|