1155 lines
25 KiB
Plaintext
1155 lines
25 KiB
Plaintext
<!-- 商品管理页面 - 基于CRMEB设计 -->
|
|
<template>
|
|
<view class="product-management">
|
|
<!-- 搜索和筛选区域 -->
|
|
<view class="search-section">
|
|
<view class="search-form">
|
|
<view class="search-row">
|
|
<view class="form-item">
|
|
<text class="label">商品搜索:</text>
|
|
<input
|
|
v-model="searchForm.keyword"
|
|
placeholder="请输入商品名称/关键字/ID"
|
|
clearable
|
|
class="search-input"
|
|
/>
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="label">商品类型:</text>
|
|
<picker mode="selector" :range="productTypes" range-key="label" @change="onProductTypeChange">
|
|
<view class="picker-text">{{ selectedProductType || '全部' }}</view>
|
|
</picker>
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="label">商品分类:</text>
|
|
<view class="cascader-wrapper">
|
|
<picker mode="multiSelector" :range="categoryTree" @change="onCategoryChange">
|
|
<view class="picker-text">{{ selectedCategoryText || '请选择分类' }}</view>
|
|
</picker>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 高级搜索选项 -->
|
|
<view v-if="showAdvancedSearch" class="search-row advanced">
|
|
<view class="form-item">
|
|
<text class="label">配送方式:</text>
|
|
<picker mode="selector" :range="deliveryTypes" range-key="label" @change="onDeliveryTypeChange">
|
|
<view class="picker-text">{{ selectedDeliveryType || '全部' }}</view>
|
|
</picker>
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="label">商品规格:</text>
|
|
<picker mode="selector" :range="specTypes" range-key="label" @change="onSpecTypeChange">
|
|
<view class="picker-text">{{ selectedSpecType || '全部' }}</view>
|
|
</picker>
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="label">会员专属:</text>
|
|
<picker mode="selector" :range="vipOptions" range-key="label" @change="onVipChange">
|
|
<view class="picker-text">{{ selectedVip || '全部' }}</view>
|
|
</picker>
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="label">添加时间:</text>
|
|
<view class="date-range">
|
|
<picker mode="date" @change="onStartDateChange">
|
|
<view class="date-picker">{{ startDate || '开始日期' }}</view>
|
|
</picker>
|
|
<text class="date-separator">至</text>
|
|
<picker mode="date" @change="onEndDateChange">
|
|
<view class="date-picker">{{ endDate || '结束日期' }}</view>
|
|
</picker>
|
|
</view>
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="label">库存范围:</text>
|
|
<view class="number-range">
|
|
<input
|
|
v-model="searchForm.stockMin"
|
|
placeholder="最小值"
|
|
type="number"
|
|
class="number-input"
|
|
/>
|
|
<text class="range-separator">~</text>
|
|
<input
|
|
v-model="searchForm.stockMax"
|
|
placeholder="最大值"
|
|
type="number"
|
|
class="number-input"
|
|
/>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="form-actions">
|
|
<button class="btn btn-primary" @click="handleSearch">搜索</button>
|
|
<button class="btn btn-default" @click="handleReset">重置</button>
|
|
<text class="toggle-search" @click="showAdvancedSearch = !showAdvancedSearch">
|
|
{{ showAdvancedSearch ? '收起' : '展开' }} <text class="icon">{{ showAdvancedSearch ? '▲' : '▼' }}</text>
|
|
</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 操作按钮区域 -->
|
|
<view class="action-bar">
|
|
<view class="action-buttons">
|
|
<button class="btn btn-success" @click="addProduct">添加商品</button>
|
|
<button class="btn btn-warning" @click="batchOnShelf" :disabled="!selectedProducts.length">批量上架</button>
|
|
<button class="btn btn-danger" @click="batchOffShelf" :disabled="!selectedProducts.length">批量下架</button>
|
|
<button class="btn btn-info" @click="batchDelete" :disabled="!selectedProducts.length">批量删除</button>
|
|
</view>
|
|
<view class="data-info">
|
|
<text class="total-count">共 {{ totalProducts }} 个商品</text>
|
|
<text class="page-info">{{ currentPage }}/{{ totalPages }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 商品列表 -->
|
|
<view class="product-list">
|
|
<!-- 表头 -->
|
|
<view class="table-header">
|
|
<view class="table-row">
|
|
<view class="table-cell checkbox-cell">
|
|
<checkbox :checked="selectAll" @change="onSelectAllChange" />
|
|
</view>
|
|
<view class="table-cell product-cell">商品信息</view>
|
|
<view class="table-cell">价格</view>
|
|
<view class="table-cell">库存</view>
|
|
<view class="table-cell">销量</view>
|
|
<view class="table-cell">分类</view>
|
|
<view class="table-cell">状态</view>
|
|
<view class="table-cell">操作</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 表格内容 -->
|
|
<view class="table-body">
|
|
<view v-for="product in productList" :key="product.id" class="table-row data-row">
|
|
<view class="table-cell checkbox-cell">
|
|
<checkbox :checked="selectedProducts.includes(product.id)" @change="onProductSelectChange(product.id)" />
|
|
</view>
|
|
<view class="table-cell product-cell">
|
|
<view class="product-info">
|
|
<image :src="product.image || '/static/default-product.png'" class="product-image" />
|
|
<view class="product-details">
|
|
<text class="product-name">{{ product.title }}</text>
|
|
<text class="product-id">ID: {{ product.id }}</text>
|
|
<view class="product-tags">
|
|
<text v-if="product.is_hot" class="tag hot">热销</text>
|
|
<text v-if="product.is_recommend" class="tag recommend">推荐</text>
|
|
<text v-if="product.is_new" class="tag new">新品</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view class="table-cell">
|
|
<view class="price-info">
|
|
<text class="price">¥{{ product.price }}</text>
|
|
<text v-if="product.ot_price" class="original-price">¥{{ product.ot_price }}</text>
|
|
</view>
|
|
</view>
|
|
<view class="table-cell">
|
|
<text class="stock">{{ product.stock }}</text>
|
|
</view>
|
|
<view class="table-cell">
|
|
<text class="sales">{{ product.sales }}</text>
|
|
</view>
|
|
<view class="table-cell">
|
|
<text class="category">{{ getCategoryName(product.category_id) }}</text>
|
|
</view>
|
|
<view class="table-cell">
|
|
<text class="status-tag" :class="product.is_show ? 'active' : 'inactive'">
|
|
{{ product.is_show ? '上架' : '下架' }}
|
|
</text>
|
|
</view>
|
|
<view class="table-cell action-cell">
|
|
<view class="action-buttons">
|
|
<text class="action-link" @click="viewProduct(product.id)">查看</text>
|
|
<text class="action-link" @click="editProduct(product.id)">编辑</text>
|
|
<text class="action-link" :class="product.is_show ? 'danger' : 'success'"
|
|
@click="toggleProductStatus(product.id, product.is_show)">
|
|
{{ product.is_show ? '下架' : '上架' }}
|
|
</text>
|
|
<text class="action-link danger" @click="deleteProduct(product.id)">删除</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 分页 -->
|
|
<view class="pagination">
|
|
<view class="page-buttons">
|
|
<button class="page-btn" :disabled="currentPage === 1" @click="goToPage(currentPage - 1)">上一页</button>
|
|
<view class="page-numbers">
|
|
<button
|
|
v-for="page in visiblePages"
|
|
:key="page"
|
|
class="page-number"
|
|
:class="{ active: page === currentPage }"
|
|
@click="goToPage(page)"
|
|
>{{ page }}</button>
|
|
</view>
|
|
<button class="page-btn" :disabled="currentPage === totalPages" @click="goToPage(currentPage + 1)">下一页</button>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 确认删除弹窗 -->
|
|
<view v-if="showDeleteConfirm" class="modal-overlay" @click="showDeleteConfirm = false">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-header">
|
|
<text class="modal-title">确认删除</text>
|
|
<text class="modal-close" @click="showDeleteConfirm = false">✕</text>
|
|
</view>
|
|
<view class="modal-body">
|
|
<text class="confirm-text">确定要删除选中的商品吗?此操作不可恢复。</text>
|
|
</view>
|
|
<view class="modal-footer">
|
|
<button class="btn btn-default" @click="showDeleteConfirm = false">取消</button>
|
|
<button class="btn btn-danger" @click="confirmDelete">确认删除</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import { ref, onMounted, computed } from 'vue'
|
|
import supa from '@/components/supadb/aksupainstance.uts'
|
|
|
|
// 响应式数据
|
|
const showAdvancedSearch = ref(false)
|
|
const showDeleteConfirm = ref(false)
|
|
const selectAll = ref(false)
|
|
const selectedProducts = ref<number[]>([])
|
|
const currentPage = ref(1)
|
|
const pageSize = ref(20)
|
|
const totalProducts = ref(0)
|
|
const totalPages = ref(0)
|
|
const productToDelete = ref<number | null>(null)
|
|
|
|
// 搜索表单
|
|
const searchForm = ref({
|
|
keyword: '',
|
|
type: '',
|
|
category: [],
|
|
delivery: '',
|
|
spec: '',
|
|
vip: '',
|
|
startDate: '',
|
|
endDate: '',
|
|
stockMin: '',
|
|
stockMax: ''
|
|
})
|
|
|
|
// 筛选选项数据
|
|
const productTypes = ref([
|
|
{ value: '', label: '全部' },
|
|
{ value: '0', label: '普通商品' },
|
|
{ value: '1', label: '卡密商品' },
|
|
{ value: '2', label: '优惠券商品' },
|
|
{ value: '3', label: '虚拟商品' }
|
|
])
|
|
|
|
const deliveryTypes = ref([
|
|
{ value: '', label: '全部' },
|
|
{ value: '1', label: '快递配送' },
|
|
{ value: '2', label: '到店自提' }
|
|
])
|
|
|
|
const specTypes = ref([
|
|
{ value: '', label: '全部' },
|
|
{ value: '0', label: '单规格' },
|
|
{ value: '1', label: '多规格' }
|
|
])
|
|
|
|
const vipOptions = ref([
|
|
{ value: '', label: '全部' },
|
|
{ value: '0', label: '否' },
|
|
{ value: '1', label: '是' }
|
|
])
|
|
|
|
// 分类树数据
|
|
const categoryTree = ref<any[]>([])
|
|
const categories = ref<any[]>([])
|
|
|
|
// 商品列表
|
|
const productList = ref([])
|
|
|
|
// 选中的筛选值
|
|
const selectedProductType = ref('')
|
|
const selectedDeliveryType = ref('')
|
|
const selectedSpecType = ref('')
|
|
const selectedVip = ref('')
|
|
const selectedCategory = ref<number[]>([])
|
|
const selectedCategoryText = ref('')
|
|
const startDate = ref('')
|
|
const endDate = ref('')
|
|
|
|
// 计算属性
|
|
const visiblePages = computed(() => {
|
|
const pages = []
|
|
const start = Math.max(1, currentPage.value - 2)
|
|
const end = Math.min(totalPages.value, currentPage.value + 2)
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
pages.push(i)
|
|
}
|
|
return pages
|
|
})
|
|
|
|
// 方法
|
|
const onProductTypeChange = (e: any) => {
|
|
selectedProductType.value = productTypes.value[e.detail.value].label
|
|
searchForm.value.type = productTypes.value[e.detail.value].value
|
|
}
|
|
|
|
const onDeliveryTypeChange = (e: any) => {
|
|
selectedDeliveryType.value = deliveryTypes.value[e.detail.value].label
|
|
searchForm.value.delivery = deliveryTypes.value[e.detail.value].value
|
|
}
|
|
|
|
const onSpecTypeChange = (e: any) => {
|
|
selectedSpecType.value = specTypes.value[e.detail.value].label
|
|
searchForm.value.spec = specTypes.value[e.detail.value].value
|
|
}
|
|
|
|
const onVipChange = (e: any) => {
|
|
selectedVip.value = vipOptions.value[e.detail.value].label
|
|
searchForm.value.vip = vipOptions.value[e.detail.value].value
|
|
}
|
|
|
|
const onCategoryChange = (e: any) => {
|
|
selectedCategory.value = e.detail.value
|
|
// 这里需要根据选中的索引构建分类文本和ID
|
|
const selectedIds = selectedCategory.value.map((index: number) => {
|
|
return categoryTree.value[index]?.id || ''
|
|
})
|
|
searchForm.value.category = selectedIds
|
|
selectedCategoryText.value = selectedIds.join(', ')
|
|
}
|
|
|
|
const onStartDateChange = (e: any) => {
|
|
startDate.value = e.detail.value
|
|
searchForm.value.startDate = e.detail.value
|
|
}
|
|
|
|
const onEndDateChange = (e: any) => {
|
|
endDate.value = e.detail.value
|
|
searchForm.value.endDate = e.detail.value
|
|
}
|
|
|
|
const onSelectAllChange = (e: any) => {
|
|
selectAll.value = e.detail.value
|
|
if (selectAll.value) {
|
|
selectedProducts.value = productList.value.map((product: any) => product.id)
|
|
} else {
|
|
selectedProducts.value = []
|
|
}
|
|
}
|
|
|
|
const onProductSelectChange = (productId: number) => {
|
|
const index = selectedProducts.value.indexOf(productId)
|
|
if (index > -1) {
|
|
selectedProducts.value.splice(index, 1)
|
|
} else {
|
|
selectedProducts.value.push(productId)
|
|
}
|
|
selectAll.value = selectedProducts.value.length === productList.value.length
|
|
}
|
|
|
|
const handleSearch = () => {
|
|
loadProductList()
|
|
}
|
|
|
|
const handleReset = () => {
|
|
searchForm.value = {
|
|
keyword: '',
|
|
type: '',
|
|
category: [],
|
|
delivery: '',
|
|
spec: '',
|
|
vip: '',
|
|
startDate: '',
|
|
endDate: '',
|
|
stockMin: '',
|
|
stockMax: ''
|
|
}
|
|
selectedProductType.value = ''
|
|
selectedDeliveryType.value = ''
|
|
selectedSpecType.value = ''
|
|
selectedVip.value = ''
|
|
selectedCategory.value = []
|
|
selectedCategoryText.value = ''
|
|
startDate.value = ''
|
|
endDate.value = ''
|
|
loadProductList()
|
|
}
|
|
|
|
const addProduct = () => {
|
|
uni.navigateTo({
|
|
url: '/pages/mall/admin/product-add'
|
|
})
|
|
}
|
|
|
|
const batchOnShelf = () => {
|
|
if (selectedProducts.value.length === 0) return
|
|
batchUpdateStatus(selectedProducts.value, true)
|
|
}
|
|
|
|
const batchOffShelf = () => {
|
|
if (selectedProducts.value.length === 0) return
|
|
batchUpdateStatus(selectedProducts.value, false)
|
|
}
|
|
|
|
const batchDelete = () => {
|
|
if (selectedProducts.value.length === 0) return
|
|
showDeleteConfirm.value = true
|
|
}
|
|
|
|
const viewProduct = (productId: number) => {
|
|
uni.navigateTo({
|
|
url: `/pages/mall/admin/product-detail?id=${productId}`
|
|
})
|
|
}
|
|
|
|
const editProduct = (productId: number) => {
|
|
uni.navigateTo({
|
|
url: `/pages/mall/admin/product-edit?id=${productId}`
|
|
})
|
|
}
|
|
|
|
const toggleProductStatus = async (productId: number, currentStatus: boolean) => {
|
|
try {
|
|
const newStatus = !currentStatus
|
|
await supa.from('products').update({ is_show: newStatus }).eq('id', productId)
|
|
|
|
uni.showToast({
|
|
title: newStatus ? '上架成功' : '下架成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
loadProductList()
|
|
} catch (error) {
|
|
console.error('更新商品状态失败:', error)
|
|
uni.showToast({
|
|
title: '操作失败',
|
|
icon: 'error'
|
|
})
|
|
}
|
|
}
|
|
|
|
const deleteProduct = (productId: number) => {
|
|
productToDelete.value = productId
|
|
showDeleteConfirm.value = true
|
|
}
|
|
|
|
const confirmDelete = async () => {
|
|
try {
|
|
if (productToDelete.value) {
|
|
// 单个删除
|
|
await supa.from('products').update({ is_del: true }).eq('id', productToDelete.value)
|
|
} else if (selectedProducts.value.length > 0) {
|
|
// 批量删除
|
|
await supa.from('products').update({ is_del: true }).in('id', selectedProducts.value)
|
|
}
|
|
|
|
uni.showToast({
|
|
title: '删除成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
showDeleteConfirm.value = false
|
|
productToDelete.value = null
|
|
selectedProducts.value = []
|
|
selectAll.value = false
|
|
|
|
loadProductList()
|
|
} catch (error) {
|
|
console.error('删除商品失败:', error)
|
|
uni.showToast({
|
|
title: '删除失败',
|
|
icon: 'error'
|
|
})
|
|
}
|
|
}
|
|
|
|
const batchUpdateStatus = async (productIds: number[], status: boolean) => {
|
|
try {
|
|
await supa.from('products').update({ is_show: status }).in('id', productIds)
|
|
|
|
uni.showToast({
|
|
title: status ? '批量上架成功' : '批量下架成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
selectedProducts.value = []
|
|
selectAll.value = false
|
|
|
|
loadProductList()
|
|
} catch (error) {
|
|
console.error('批量更新状态失败:', error)
|
|
uni.showToast({
|
|
title: '操作失败',
|
|
icon: 'error'
|
|
})
|
|
}
|
|
}
|
|
|
|
const goToPage = (page: number) => {
|
|
if (page >= 1 && page <= totalPages.value) {
|
|
currentPage.value = page
|
|
loadProductList()
|
|
}
|
|
}
|
|
|
|
const getCategoryName = (categoryId: number) => {
|
|
const category = categories.value.find((c: any) => c.id === categoryId)
|
|
return category?.name || '未分类'
|
|
}
|
|
|
|
// 数据加载方法
|
|
const loadCategories = async () => {
|
|
try {
|
|
const { data } = await supa.from('categories').select('*').order('sort', { ascending: true })
|
|
categories.value = data || []
|
|
|
|
// 构建分类树
|
|
categoryTree.value = buildCategoryTree(data || [])
|
|
} catch (error) {
|
|
console.error('加载分类失败:', error)
|
|
}
|
|
}
|
|
|
|
const buildCategoryTree = (categories: any[]) => {
|
|
// 简化的分类树构建逻辑
|
|
const tree = categories.map(cat => ({
|
|
id: cat.id,
|
|
name: cat.name,
|
|
pid: cat.pid
|
|
}))
|
|
|
|
// 这里应该实现完整的树形结构构建
|
|
return tree
|
|
}
|
|
|
|
const loadProductList = async () => {
|
|
try {
|
|
let query = supa.from('products').select(`
|
|
*,
|
|
categories(name)
|
|
`).eq('is_del', false)
|
|
|
|
// 搜索条件
|
|
if (searchForm.value.keyword) {
|
|
query = query.ilike('title', `%${searchForm.value.keyword}%`)
|
|
}
|
|
|
|
if (searchForm.value.type) {
|
|
query = query.eq('type', searchForm.value.type)
|
|
}
|
|
|
|
if (searchForm.value.category && searchForm.value.category.length > 0) {
|
|
query = query.in('category_id', searchForm.value.category)
|
|
}
|
|
|
|
// 其他筛选条件...
|
|
|
|
// 分页
|
|
const from = (currentPage.value - 1) * pageSize.value
|
|
const to = from + pageSize.value - 1
|
|
|
|
const { data, count } = await query.range(from, to)
|
|
productList.value = data || []
|
|
totalProducts.value = count || 0
|
|
totalPages.value = Math.ceil(totalProducts.value / pageSize.value)
|
|
|
|
} catch (error) {
|
|
console.error('加载商品列表失败:', error)
|
|
uni.showToast({
|
|
title: '加载失败',
|
|
icon: 'error'
|
|
})
|
|
}
|
|
}
|
|
|
|
// 页面初始化
|
|
onMounted(async () => {
|
|
await loadCategories()
|
|
await loadProductList()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.product-management {
|
|
padding: 30rpx;
|
|
background-color: #f5f5f5;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
// 搜索区域样式
|
|
.search-section {
|
|
background-color: #fff;
|
|
border-radius: 12rpx;
|
|
padding: 30rpx;
|
|
margin-bottom: 30rpx;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.search-form {
|
|
.search-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 20rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
&.advanced {
|
|
border-top: 1rpx solid #e8e8e8;
|
|
padding-top: 20rpx;
|
|
}
|
|
}
|
|
|
|
.form-item {
|
|
display: flex;
|
|
align-items: center;
|
|
min-width: 300rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.label {
|
|
font-size: 28rpx;
|
|
color: #666;
|
|
margin-right: 20rpx;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
.search-input {
|
|
flex: 1;
|
|
height: 60rpx;
|
|
border: 1rpx solid #ddd;
|
|
border-radius: 6rpx;
|
|
padding: 0 20rpx;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.picker-text {
|
|
padding: 0 20rpx;
|
|
height: 60rpx;
|
|
line-height: 60rpx;
|
|
border: 1rpx solid #ddd;
|
|
border-radius: 6rpx;
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
min-width: 200rpx;
|
|
}
|
|
|
|
.cascader-wrapper {
|
|
flex: 1;
|
|
}
|
|
|
|
.date-range {
|
|
display: flex;
|
|
align-items: center;
|
|
flex: 1;
|
|
gap: 10rpx;
|
|
|
|
.date-picker {
|
|
flex: 1;
|
|
padding: 0 20rpx;
|
|
height: 60rpx;
|
|
line-height: 60rpx;
|
|
border: 1rpx solid #ddd;
|
|
border-radius: 6rpx;
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
}
|
|
|
|
.date-separator {
|
|
color: #666;
|
|
}
|
|
}
|
|
|
|
.number-range {
|
|
display: flex;
|
|
align-items: center;
|
|
flex: 1;
|
|
gap: 10rpx;
|
|
|
|
.number-input {
|
|
flex: 1;
|
|
height: 60rpx;
|
|
border: 1rpx solid #ddd;
|
|
border-radius: 6rpx;
|
|
padding: 0 20rpx;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.range-separator {
|
|
color: #666;
|
|
}
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
margin-top: 20rpx;
|
|
|
|
.btn {
|
|
padding: 12rpx 24rpx;
|
|
border-radius: 6rpx;
|
|
font-size: 26rpx;
|
|
border: none;
|
|
cursor: pointer;
|
|
|
|
&.btn-primary {
|
|
background-color: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
&.btn-default {
|
|
background-color: #f5f5f5;
|
|
color: #333;
|
|
}
|
|
}
|
|
|
|
.toggle-search {
|
|
margin-left: auto;
|
|
color: #007bff;
|
|
font-size: 26rpx;
|
|
cursor: pointer;
|
|
|
|
.icon {
|
|
font-size: 20rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 操作栏样式
|
|
.action-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background-color: #fff;
|
|
padding: 20rpx 30rpx;
|
|
border-radius: 12rpx;
|
|
margin-bottom: 30rpx;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
|
|
.btn {
|
|
padding: 12rpx 24rpx;
|
|
border-radius: 6rpx;
|
|
font-size: 26rpx;
|
|
border: none;
|
|
cursor: pointer;
|
|
|
|
&:disabled {
|
|
background-color: #ccc;
|
|
color: #666;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
&.btn-success {
|
|
background-color: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
&.btn-warning {
|
|
background-color: #ffc107;
|
|
color: #212529;
|
|
}
|
|
|
|
&.btn-danger {
|
|
background-color: #dc3545;
|
|
color: white;
|
|
}
|
|
|
|
&.btn-info {
|
|
background-color: #17a2b8;
|
|
color: white;
|
|
}
|
|
}
|
|
}
|
|
|
|
.data-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
|
|
.total-count,
|
|
.page-info {
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 商品列表样式
|
|
.product-list {
|
|
background-color: #fff;
|
|
border-radius: 12rpx;
|
|
overflow: hidden;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.table-header {
|
|
background-color: #f8f9fa;
|
|
border-bottom: 1rpx solid #e9ecef;
|
|
|
|
.table-row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20rpx 30rpx;
|
|
font-weight: bold;
|
|
font-size: 26rpx;
|
|
color: #495057;
|
|
}
|
|
}
|
|
|
|
.table-body {
|
|
.data-row {
|
|
border-bottom: 1rpx solid #e9ecef;
|
|
|
|
&:hover {
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.table-row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20rpx 30rpx;
|
|
min-height: 120rpx;
|
|
|
|
.table-cell {
|
|
flex: 1;
|
|
font-size: 26rpx;
|
|
color: #495057;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
&.checkbox-cell {
|
|
flex: 0 0 60rpx;
|
|
justify-content: center;
|
|
}
|
|
|
|
&.product-cell {
|
|
flex: 3;
|
|
}
|
|
|
|
&.action-cell {
|
|
flex: 0 0 240rpx;
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
}
|
|
|
|
.product-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
|
|
.product-image {
|
|
width: 80rpx;
|
|
height: 80rpx;
|
|
border-radius: 8rpx;
|
|
flex-shrink: 0;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.product-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8rpx;
|
|
flex: 1;
|
|
|
|
.product-name {
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
color: #212529;
|
|
line-height: 1.4;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.product-id {
|
|
font-size: 24rpx;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.product-tags {
|
|
display: flex;
|
|
gap: 8rpx;
|
|
|
|
.tag {
|
|
padding: 2rpx 8rpx;
|
|
border-radius: 4rpx;
|
|
font-size: 20rpx;
|
|
font-weight: bold;
|
|
|
|
&.hot {
|
|
background-color: #ffebee;
|
|
color: #c62828;
|
|
}
|
|
|
|
&.recommend {
|
|
background-color: #e8f5e8;
|
|
color: #2e7d32;
|
|
}
|
|
|
|
&.new {
|
|
background-color: #fff3e0;
|
|
color: #ef6c00;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.price-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4rpx;
|
|
|
|
.price {
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.original-price {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
text-decoration: line-through;
|
|
}
|
|
}
|
|
|
|
.stock,
|
|
.sales {
|
|
font-weight: bold;
|
|
color: #28a745;
|
|
}
|
|
|
|
.category {
|
|
color: #6c757d;
|
|
}
|
|
|
|
.status-tag {
|
|
padding: 4rpx 12rpx;
|
|
border-radius: 12rpx;
|
|
font-size: 24rpx;
|
|
font-weight: bold;
|
|
|
|
&.active {
|
|
background-color: #d4edda;
|
|
color: #155724;
|
|
}
|
|
|
|
&.inactive {
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
|
|
.action-link {
|
|
color: #007bff;
|
|
font-size: 24rpx;
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
&.danger {
|
|
color: #dc3545;
|
|
}
|
|
|
|
&.success {
|
|
color: #28a745;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 分页样式
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-top: 30rpx;
|
|
|
|
.page-buttons {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10rpx;
|
|
}
|
|
|
|
.page-btn,
|
|
.page-number {
|
|
padding: 12rpx 20rpx;
|
|
border: 1rpx solid #ddd;
|
|
background-color: #fff;
|
|
color: #333;
|
|
border-radius: 6rpx;
|
|
font-size: 26rpx;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
|
|
&:disabled {
|
|
background-color: #f5f5f5;
|
|
color: #999;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: #007bff;
|
|
color: white;
|
|
border-color: #007bff;
|
|
}
|
|
|
|
&.active {
|
|
background-color: #007bff;
|
|
color: white;
|
|
border-color: #007bff;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 弹窗样式
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal-content {
|
|
background-color: #fff;
|
|
border-radius: 12rpx;
|
|
width: 80%;
|
|
max-width: 500rpx;
|
|
max-height: 60vh;
|
|
overflow: hidden;
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 30rpx;
|
|
border-bottom: 1rpx solid #e9ecef;
|
|
|
|
.modal-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #212529;
|
|
}
|
|
|
|
.modal-close {
|
|
font-size: 36rpx;
|
|
color: #999;
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
color: #333;
|
|
}
|
|
}
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 30rpx;
|
|
|
|
.confirm-text {
|
|
font-size: 28rpx;
|
|
color: #666;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 20rpx;
|
|
padding: 30rpx;
|
|
border-top: 1rpx solid #e9ecef;
|
|
|
|
.btn {
|
|
padding: 12rpx 24rpx;
|
|
border-radius: 6rpx;
|
|
font-size: 26rpx;
|
|
border: none;
|
|
cursor: pointer;
|
|
|
|
&.btn-default {
|
|
background-color: #f5f5f5;
|
|
color: #333;
|
|
}
|
|
|
|
&.btn-danger {
|
|
background-color: #dc3545;
|
|
color: white;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 响应式设计
|
|
@media (max-width: 750rpx) {
|
|
.search-row {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.form-item {
|
|
min-width: auto;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.table-row {
|
|
flex-wrap: wrap;
|
|
padding: 15rpx;
|
|
|
|
.table-cell {
|
|
min-width: 200rpx;
|
|
margin-bottom: 10rpx;
|
|
|
|
&.product-cell {
|
|
min-width: 300rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.action-bar {
|
|
flex-direction: column;
|
|
gap: 20rpx;
|
|
align-items: stretch;
|
|
|
|
.action-buttons {
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
}
|
|
}
|
|
</style>
|