1755 lines
40 KiB
Plaintext
1755 lines
40 KiB
Plaintext
<template>
|
||
<AdminLayout current-page="product-list">
|
||
<view class="product-management">
|
||
<!-- 页面标题 -->
|
||
<view class="page-header">
|
||
<text class="page-title">商品管理</text>
|
||
<text class="page-subtitle">管理系统商品信息和库存</text>
|
||
</view>
|
||
|
||
<!-- Tab 切换栏 -->
|
||
<view class="tab-bar">
|
||
<view
|
||
v-for="tab in tabs"
|
||
:key="tab.key"
|
||
class="tab-item"
|
||
:class="{ 'active': activeTab === tab.key }"
|
||
@click="switchTab(tab.key)"
|
||
>
|
||
<text class="iconfont tab-icon">{{ tab.icon }}</text>
|
||
<text class="tab-title">{{ tab.title }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品列表Tab -->
|
||
<view v-if="activeTab === 'product-list'">
|
||
<!-- 统计卡片 -->
|
||
<view class="stats-cards">
|
||
<view class="stat-card">
|
||
<view class="stat-icon">📦</view>
|
||
<view class="stat-content">
|
||
<text class="stat-value">{{ totalProducts }}</text>
|
||
<text class="stat-label">总商品数</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">✅</view>
|
||
<view class="stat-content">
|
||
<text class="stat-value">{{ onSaleProducts }}</text>
|
||
<text class="stat-label">在售商品</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">🚫</view>
|
||
<view class="stat-content">
|
||
<text class="stat-value">{{ offShelfProducts }}</text>
|
||
<text class="stat-label">已下架</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">⚠️</view>
|
||
<view class="stat-content">
|
||
<text class="stat-value">{{ lowStockProducts }}</text>
|
||
<text class="stat-label">库存不足</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 搜索和筛选 -->
|
||
<view class="search-section">
|
||
<view class="search-bar">
|
||
<view class="search-input-wrapper">
|
||
<input
|
||
v-model="searchKeyword"
|
||
class="search-input"
|
||
placeholder="搜索商品名称、编号、分类"
|
||
@confirm="handleSearch"
|
||
/>
|
||
<view class="search-btn" @click="handleSearch">
|
||
<text class="iconfont icon-search"></text>
|
||
</view>
|
||
</view>
|
||
<view class="advanced-toggle" @click="showAdvancedSearch = !showAdvancedSearch">
|
||
<text>{{ showAdvancedSearch ? '收起' : '展开' }}筛选</text>
|
||
<text class="iconfont">{{ showAdvancedSearch ? 'icon-up' : 'icon-down' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 高级搜索 -->
|
||
<view v-if="showAdvancedSearch" class="advanced-search">
|
||
<view class="filter-row">
|
||
<view class="filter-item">
|
||
<text class="filter-label">商品状态:</text>
|
||
<picker
|
||
mode="selector"
|
||
:range="statusOptions"
|
||
:value="selectedStatus"
|
||
@change="handleStatusChange"
|
||
>
|
||
<view class="picker-display">
|
||
<text>{{ statusOptions[selectedStatus] }}</text>
|
||
<text class="iconfont icon-down"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">商品分类:</text>
|
||
<picker
|
||
mode="selector"
|
||
:range="categoryOptions"
|
||
:value="selectedCategory"
|
||
@change="handleCategoryChange"
|
||
>
|
||
<view class="picker-display">
|
||
<text>{{ categoryOptions[selectedCategory] }}</text>
|
||
<text class="iconfont icon-down"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="filter-row">
|
||
<view class="filter-item">
|
||
<text class="filter-label">价格区间:</text>
|
||
<view class="price-range">
|
||
<input
|
||
v-model="minPrice"
|
||
class="price-input"
|
||
placeholder="最低价"
|
||
type="number"
|
||
/>
|
||
<text class="price-separator">-</text>
|
||
<input
|
||
v-model="maxPrice"
|
||
class="price-input"
|
||
placeholder="最高价"
|
||
type="number"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="filter-actions">
|
||
<button class="btn-secondary" @click="resetFilters">重置</button>
|
||
<button class="btn-primary" @click="handleAdvancedSearch">搜索</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="action-bar">
|
||
<view class="left-actions">
|
||
<button class="btn-primary" @click="showAddModal = true">
|
||
<text class="iconfont icon-add"></text>
|
||
添加商品
|
||
</button>
|
||
<button class="btn-secondary" @click="handleBatchOnSale">
|
||
<text class="iconfont icon-onsale"></text>
|
||
批量上架
|
||
</button>
|
||
<button class="btn-warning" @click="handleBatchOffShelf">
|
||
<text class="iconfont icon-offshelf"></text>
|
||
批量下架
|
||
</button>
|
||
</view>
|
||
<view class="right-actions">
|
||
<view class="select-all">
|
||
<checkbox :checked="selectAll" @change="handleSelectAll" />
|
||
<text>全选</text>
|
||
</view>
|
||
<button class="btn-danger" :disabled="selectedProducts.length === 0" @click="handleBatchDelete">
|
||
<text class="iconfont icon-delete"></text>
|
||
批量删除
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品列表 -->
|
||
<view class="product-table">
|
||
<view class="table-header">
|
||
<view class="table-row">
|
||
<view class="col-select">选择</view>
|
||
<view class="col-image">商品图片</view>
|
||
<view class="col-info">商品信息</view>
|
||
<view class="col-price">价格</view>
|
||
<view class="col-stock">库存</view>
|
||
<view class="col-status">状态</view>
|
||
<view class="col-sales">销量</view>
|
||
<view class="col-actions">操作</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="table-body">
|
||
<view
|
||
v-for="product in productList"
|
||
:key="product.id"
|
||
class="table-row"
|
||
:class="{ 'selected': selectedProducts.includes(product.id) }"
|
||
>
|
||
<view class="col-select">
|
||
<checkbox
|
||
:checked="selectedProducts.includes(product.id)"
|
||
@change="handleProductSelect(product.id, $event)"
|
||
/>
|
||
</view>
|
||
<view class="col-image">
|
||
<image :src="product.image" class="product-image" />
|
||
</view>
|
||
<view class="col-info">
|
||
<view class="product-name">{{ product.name }}</view>
|
||
<view class="product-code">{{ product.code }}</view>
|
||
<view class="product-category">{{ product.category }}</view>
|
||
</view>
|
||
<view class="col-price">
|
||
<view class="original-price" v-if="product.originalPrice > product.price">
|
||
¥{{ product.originalPrice }}
|
||
</view>
|
||
<view class="current-price">¥{{ product.price }}</view>
|
||
</view>
|
||
<view class="col-stock">
|
||
<view class="stock-text" :class="{ 'low-stock': product.stock < 10 }">
|
||
{{ product.stock }}
|
||
</view>
|
||
</view>
|
||
<view class="col-status">
|
||
<view class="status-badge" :class="product.status">
|
||
{{ product.status === 'on_sale' ? '在售' : product.status === 'off_shelf' ? '下架' : '待审核' }}
|
||
</view>
|
||
</view>
|
||
<view class="col-sales">
|
||
<text class="sales-text">{{ product.sales }}</text>
|
||
</view>
|
||
<view class="col-actions">
|
||
<button class="action-btn edit" @click="handleEdit(product)">
|
||
<text class="iconfont icon-edit"></text>
|
||
</button>
|
||
<button class="action-btn onsale" @click="handleOnSale(product)" v-if="product.status === 'off_shelf'">
|
||
<text class="iconfont icon-onsale"></text>
|
||
</button>
|
||
<button class="action-btn offshelf" @click="handleOffShelf(product)" v-else-if="product.status === 'on_sale'">
|
||
<text class="iconfont icon-offshelf"></text>
|
||
</button>
|
||
<button class="action-btn delete" @click="handleDelete(product)">
|
||
<text class="iconfont icon-delete"></text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分页 -->
|
||
<view class="pagination">
|
||
<view class="page-info">
|
||
<text>共 {{ totalProducts }} 条记录,显示 {{ (currentPage - 1) * pageSize + 1 }}-{{ Math.min(currentPage * pageSize, totalProducts) }} 条</text>
|
||
</view>
|
||
<view class="page-controls">
|
||
<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="showAddModal || showEditModal" class="modal-overlay" @click="closeModal">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">{{ showAddModal ? '添加商品' : '编辑商品' }}</text>
|
||
<view class="modal-close" @click="closeModal">
|
||
<text class="iconfont icon-close"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-body">
|
||
<view class="form-row">
|
||
<view class="form-group">
|
||
<text class="form-label">商品名称</text>
|
||
<input
|
||
v-model="productForm.name"
|
||
class="form-input"
|
||
placeholder="请输入商品名称"
|
||
/>
|
||
</view>
|
||
|
||
<view class="form-group">
|
||
<text class="form-label">商品编号</text>
|
||
<input
|
||
v-model="productForm.code"
|
||
class="form-input"
|
||
placeholder="请输入商品编号"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-row">
|
||
<view class="form-group">
|
||
<text class="form-label">商品分类</text>
|
||
<picker
|
||
mode="selector"
|
||
:range="categoryOptions"
|
||
:value="productForm.category"
|
||
@change="handleFormCategoryChange"
|
||
>
|
||
<view class="form-select">
|
||
<text>{{ categoryOptions[productForm.category] }}</text>
|
||
<text class="iconfont icon-down"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-group">
|
||
<text class="form-label">商品状态</text>
|
||
<picker
|
||
mode="selector"
|
||
:range="statusOptions"
|
||
:value="productForm.status"
|
||
@change="handleFormStatusChange"
|
||
>
|
||
<view class="form-select">
|
||
<text>{{ statusOptions[productForm.status] }}</text>
|
||
<text class="iconfont icon-down"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-row">
|
||
<view class="form-group">
|
||
<text class="form-label">原价</text>
|
||
<input
|
||
v-model="productForm.originalPrice"
|
||
class="form-input"
|
||
placeholder="请输入原价"
|
||
type="number"
|
||
/>
|
||
</view>
|
||
|
||
<view class="form-group">
|
||
<text class="form-label">现价</text>
|
||
<input
|
||
v-model="productForm.price"
|
||
class="form-input"
|
||
placeholder="请输入现价"
|
||
type="number"
|
||
/>
|
||
</view>
|
||
|
||
<view class="form-group">
|
||
<text class="form-label">库存</text>
|
||
<input
|
||
v-model="productForm.stock"
|
||
class="form-input"
|
||
placeholder="请输入库存数量"
|
||
type="number"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-group">
|
||
<text class="form-label">商品描述</text>
|
||
<textarea
|
||
v-model="productForm.description"
|
||
class="form-textarea"
|
||
placeholder="请输入商品描述"
|
||
:maxlength="500"
|
||
/>
|
||
</view>
|
||
|
||
<view class="form-group">
|
||
<text class="form-label">商品图片</text>
|
||
<view class="image-upload">
|
||
<view class="upload-area" @click="handleImageUpload">
|
||
<text class="iconfont icon-upload"></text>
|
||
<text class="upload-text">点击上传图片</text>
|
||
</view>
|
||
<view v-if="productForm.image" class="image-preview">
|
||
<image :src="productForm.image" class="preview-image" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<button class="btn-secondary" @click="closeModal">取消</button>
|
||
<button class="btn-primary" @click="handleSaveProduct">
|
||
{{ showAddModal ? '添加' : '保存' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 删除确认模态框 -->
|
||
<view v-if="showDeleteModal" class="modal-overlay" @click="closeModal">
|
||
<view class="modal-content delete-modal" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">确认删除</text>
|
||
</view>
|
||
|
||
<view class="modal-body">
|
||
<text class="delete-text">
|
||
确定要删除商品 "{{ deleteProduct?.name }}" 吗?此操作不可恢复。
|
||
</text>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<button class="btn-secondary" @click="closeModal">取消</button>
|
||
<button class="btn-danger" @click="confirmDelete">确认删除</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品分类Tab -->
|
||
<view v-if="activeTab === 'category'" class="category-section">
|
||
<view class="category-management">
|
||
<view class="category-header">
|
||
<text class="section-title">商品分类管理</text>
|
||
<button class="btn-primary" @click="showCategoryModal = true">
|
||
<text class="iconfont icon-add"></text>
|
||
添加分类
|
||
</button>
|
||
</view>
|
||
|
||
<view class="category-tree">
|
||
<view class="category-item" v-for="category in categories" :key="category.id">
|
||
<view class="category-info">
|
||
<text class="category-name">{{ category.name }}</text>
|
||
<text class="category-count">({{ category.productCount }}商品)</text>
|
||
</view>
|
||
<view class="category-actions">
|
||
<button class="action-btn edit" @click="editCategory(category)">
|
||
<text class="iconfont icon-edit"></text>
|
||
</button>
|
||
<button class="action-btn delete" @click="deleteCategory(category)">
|
||
<text class="iconfont icon-delete"></text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</AdminLayout>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, computed } from 'vue'
|
||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||
|
||
// Tab 相关
|
||
const activeTab = ref('product-list')
|
||
const tabs = ref([
|
||
{ key: 'product-list', title: '商品列表', icon: 'icon-list' },
|
||
{ key: 'product-add', title: '添加商品', icon: 'icon-add' },
|
||
{ key: 'category', title: '商品分类', icon: 'icon-category' }
|
||
])
|
||
|
||
// 响应式数据
|
||
const searchKeyword = ref('')
|
||
const showAdvancedSearch = ref(false)
|
||
const totalProducts = ref(856)
|
||
const onSaleProducts = ref(789)
|
||
const offShelfProducts = ref(45)
|
||
const lowStockProducts = ref(22)
|
||
|
||
// 筛选条件
|
||
const selectedStatus = ref(0)
|
||
const selectedCategory = ref(0)
|
||
const minPrice = ref('')
|
||
const maxPrice = ref('')
|
||
|
||
// 选项数据
|
||
const statusOptions = ['全部状态', '在售', '下架', '待审核']
|
||
const categoryOptions = ['全部分类', '电子产品', '服装鞋帽', '食品饮料', '家居用品', '图书文具']
|
||
|
||
// 商品列表
|
||
const productList = ref([
|
||
{
|
||
id: 1,
|
||
name: 'iPhone 15 Pro',
|
||
code: 'IP15P001',
|
||
category: '电子产品',
|
||
price: 8999,
|
||
originalPrice: 9999,
|
||
stock: 50,
|
||
status: 'on_sale',
|
||
sales: 1250,
|
||
image: '/static/products/iphone15.jpg',
|
||
description: '最新款 iPhone 15 Pro,性能卓越'
|
||
},
|
||
{
|
||
id: 2,
|
||
name: 'Nike 运动鞋',
|
||
code: 'NK001',
|
||
category: '服装鞋帽',
|
||
price: 599,
|
||
originalPrice: 699,
|
||
stock: 120,
|
||
status: 'on_sale',
|
||
sales: 890,
|
||
image: '/static/products/nike.jpg',
|
||
description: '舒适透气的运动鞋'
|
||
},
|
||
{
|
||
id: 3,
|
||
name: '联想笔记本',
|
||
code: 'LN001',
|
||
category: '电子产品',
|
||
price: 4599,
|
||
originalPrice: 4999,
|
||
stock: 3,
|
||
status: 'on_sale',
|
||
sales: 456,
|
||
image: '/static/products/lenovo.jpg',
|
||
description: '高性能商务笔记本'
|
||
},
|
||
{
|
||
id: 4,
|
||
name: '咖啡机',
|
||
code: 'CF001',
|
||
category: '家居用品',
|
||
price: 1299,
|
||
originalPrice: 1599,
|
||
stock: 25,
|
||
status: 'off_shelf',
|
||
sales: 234,
|
||
image: '/static/products/coffee.jpg',
|
||
description: '全自动咖啡机'
|
||
}
|
||
])
|
||
|
||
// 分页相关
|
||
const currentPage = ref(1)
|
||
const pageSize = ref(10)
|
||
const totalPages = computed(() => Math.ceil(totalProducts.value / pageSize.value))
|
||
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 selectAll = ref(false)
|
||
const selectedProducts = ref<number[]>([])
|
||
|
||
// 模态框相关
|
||
const showAddModal = ref(false)
|
||
const showEditModal = ref(false)
|
||
const showDeleteModal = ref(false)
|
||
const deleteProduct = ref(null)
|
||
|
||
// 分类数据
|
||
const categories = ref([
|
||
{ id: 1, name: '电子产品', productCount: 156 },
|
||
{ id: 2, name: '服装鞋帽', productCount: 89 },
|
||
{ id: 3, name: '食品饮料', productCount: 67 },
|
||
{ id: 4, name: '家居用品', productCount: 45 },
|
||
{ id: 5, name: '图书文具', productCount: 34 }
|
||
])
|
||
|
||
const showCategoryModal = ref(false)
|
||
|
||
// 表单数据
|
||
const productForm = ref({
|
||
id: 0,
|
||
name: '',
|
||
code: '',
|
||
category: 0,
|
||
price: '',
|
||
originalPrice: '',
|
||
stock: '',
|
||
status: 0,
|
||
description: '',
|
||
image: ''
|
||
})
|
||
|
||
// 方法
|
||
const handleSearch = () => {
|
||
console.log('搜索:', searchKeyword.value)
|
||
// TODO: 实现搜索逻辑
|
||
}
|
||
|
||
const handleStatusChange = (e: any) => {
|
||
selectedStatus.value = e.detail.value
|
||
}
|
||
|
||
const handleCategoryChange = (e: any) => {
|
||
selectedCategory.value = e.detail.value
|
||
}
|
||
|
||
const handleFormCategoryChange = (e: any) => {
|
||
productForm.value.category = e.detail.value
|
||
}
|
||
|
||
const handleFormStatusChange = (e: any) => {
|
||
productForm.value.status = e.detail.value
|
||
}
|
||
|
||
const resetFilters = () => {
|
||
selectedStatus.value = 0
|
||
selectedCategory.value = 0
|
||
minPrice.value = ''
|
||
maxPrice.value = ''
|
||
searchKeyword.value = ''
|
||
}
|
||
|
||
const handleAdvancedSearch = () => {
|
||
console.log('高级搜索:', {
|
||
status: statusOptions[selectedStatus.value],
|
||
category: categoryOptions[selectedCategory.value],
|
||
minPrice: minPrice.value,
|
||
maxPrice: maxPrice.value,
|
||
keyword: searchKeyword.value
|
||
})
|
||
// TODO: 实现高级搜索逻辑
|
||
}
|
||
|
||
const handleSelectAll = (e: any) => {
|
||
selectAll.value = e.detail.value
|
||
if (selectAll.value) {
|
||
selectedProducts.value = productList.value.map(product => product.id)
|
||
} else {
|
||
selectedProducts.value = []
|
||
}
|
||
}
|
||
|
||
const handleProductSelect = (productId: number, e: any) => {
|
||
if (e.detail.value) {
|
||
selectedProducts.value.push(productId)
|
||
} else {
|
||
selectedProducts.value = selectedProducts.value.filter(id => id !== productId)
|
||
}
|
||
selectAll.value = selectedProducts.value.length === productList.value.length
|
||
}
|
||
|
||
const handleBatchOnSale = () => {
|
||
if (selectedProducts.value.length === 0) return
|
||
|
||
uni.showModal({
|
||
title: '确认上架',
|
||
content: `确定要上架 ${selectedProducts.value.length} 个商品吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现批量上架逻辑
|
||
selectedProducts.value.forEach(id => {
|
||
const product = productList.value.find(p => p.id === id)
|
||
if (product) product.status = 'on_sale'
|
||
})
|
||
|
||
uni.showToast({
|
||
title: '批量上架成功',
|
||
icon: 'success'
|
||
})
|
||
selectedProducts.value = []
|
||
selectAll.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleBatchOffShelf = () => {
|
||
if (selectedProducts.value.length === 0) return
|
||
|
||
uni.showModal({
|
||
title: '确认下架',
|
||
content: `确定要下架 ${selectedProducts.value.length} 个商品吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现批量下架逻辑
|
||
selectedProducts.value.forEach(id => {
|
||
const product = productList.value.find(p => p.id === id)
|
||
if (product) product.status = 'off_shelf'
|
||
})
|
||
|
||
uni.showToast({
|
||
title: '批量下架成功',
|
||
icon: 'success'
|
||
})
|
||
selectedProducts.value = []
|
||
selectAll.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleBatchDelete = () => {
|
||
if (selectedProducts.value.length === 0) return
|
||
|
||
uni.showModal({
|
||
title: '确认删除',
|
||
content: `确定要删除 ${selectedProducts.value.length} 个商品吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现批量删除逻辑
|
||
selectedProducts.value.forEach(id => {
|
||
const index = productList.value.findIndex(p => p.id === id)
|
||
if (index > -1) {
|
||
productList.value.splice(index, 1)
|
||
totalProducts.value--
|
||
}
|
||
})
|
||
|
||
uni.showToast({
|
||
title: '批量删除成功',
|
||
icon: 'success'
|
||
})
|
||
selectedProducts.value = []
|
||
selectAll.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleEdit = (product: any) => {
|
||
productForm.value = {
|
||
id: product.id,
|
||
name: product.name,
|
||
code: product.code,
|
||
category: categoryOptions.indexOf(product.category),
|
||
price: product.price.toString(),
|
||
originalPrice: product.originalPrice.toString(),
|
||
stock: product.stock.toString(),
|
||
status: statusOptions.indexOf(product.status === 'on_sale' ? '在售' : product.status === 'off_shelf' ? '下架' : '待审核'),
|
||
description: product.description,
|
||
image: product.image
|
||
}
|
||
showEditModal.value = true
|
||
}
|
||
|
||
const handleOnSale = (product: any) => {
|
||
uni.showModal({
|
||
title: '确认上架',
|
||
content: `确定要上架商品 "${product.name}" 吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现上架逻辑
|
||
product.status = 'on_sale'
|
||
uni.showToast({
|
||
title: '上架成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleOffShelf = (product: any) => {
|
||
uni.showModal({
|
||
title: '确认下架',
|
||
content: `确定要下架商品 "${product.name}" 吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现下架逻辑
|
||
product.status = 'off_shelf'
|
||
uni.showToast({
|
||
title: '下架成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleDelete = (product: any) => {
|
||
deleteProduct.value = product
|
||
showDeleteModal.value = true
|
||
}
|
||
|
||
const confirmDelete = () => {
|
||
// TODO: 实现删除逻辑
|
||
const index = productList.value.findIndex(p => p.id === deleteProduct.value.id)
|
||
if (index > -1) {
|
||
productList.value.splice(index, 1)
|
||
totalProducts.value--
|
||
}
|
||
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
closeModal()
|
||
}
|
||
|
||
const handleSaveProduct = () => {
|
||
// 表单验证
|
||
if (!productForm.value.name || !productForm.value.code) {
|
||
uni.showToast({
|
||
title: '请填写必填信息',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (showAddModal.value) {
|
||
// TODO: 实现添加商品逻辑
|
||
const newProduct = {
|
||
id: Date.now(),
|
||
name: productForm.value.name,
|
||
code: productForm.value.code,
|
||
category: categoryOptions[productForm.value.category],
|
||
price: parseFloat(productForm.value.price),
|
||
originalPrice: parseFloat(productForm.value.originalPrice),
|
||
stock: parseInt(productForm.value.stock),
|
||
status: statusOptions[productForm.value.status] === '在售' ? 'on_sale' : statusOptions[productForm.value.status] === '下架' ? 'off_shelf' : 'pending',
|
||
sales: 0,
|
||
image: productForm.value.image || '/static/products/default.jpg',
|
||
description: productForm.value.description
|
||
}
|
||
productList.value.unshift(newProduct)
|
||
totalProducts.value++
|
||
|
||
uni.showToast({
|
||
title: '添加成功',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
// TODO: 实现编辑商品逻辑
|
||
const product = productList.value.find(p => p.id === productForm.value.id)
|
||
if (product) {
|
||
product.name = productForm.value.name
|
||
product.code = productForm.value.code
|
||
product.category = categoryOptions[productForm.value.category]
|
||
product.price = parseFloat(productForm.value.price)
|
||
product.originalPrice = parseFloat(productForm.value.originalPrice)
|
||
product.stock = parseInt(productForm.value.stock)
|
||
product.status = statusOptions[productForm.value.status] === '在售' ? 'on_sale' : statusOptions[productForm.value.status] === '下架' ? 'off_shelf' : 'pending'
|
||
product.description = productForm.value.description
|
||
product.image = productForm.value.image
|
||
}
|
||
|
||
uni.showToast({
|
||
title: '保存成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
|
||
closeModal()
|
||
}
|
||
|
||
const handleImageUpload = () => {
|
||
uni.showToast({
|
||
title: '图片上传功能开发中',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
const closeModal = () => {
|
||
showAddModal.value = false
|
||
showEditModal.value = false
|
||
showDeleteModal.value = false
|
||
deleteProduct.value = null
|
||
|
||
// 重置表单
|
||
productForm.value = {
|
||
id: 0,
|
||
name: '',
|
||
code: '',
|
||
category: 0,
|
||
price: '',
|
||
originalPrice: '',
|
||
stock: '',
|
||
status: 0,
|
||
description: '',
|
||
image: ''
|
||
}
|
||
}
|
||
|
||
const goToPage = (page: number) => {
|
||
currentPage.value = page
|
||
// TODO: 实现分页数据加载
|
||
}
|
||
|
||
// Tab 切换方法
|
||
const switchTab = (tabKey: string) => {
|
||
activeTab.value = tabKey
|
||
|
||
// 根据不同tab切换显示内容
|
||
if (tabKey === 'product-add') {
|
||
showAddModal.value = true
|
||
} else {
|
||
showAddModal.value = false
|
||
}
|
||
}
|
||
|
||
// 分类管理方法
|
||
const editCategory = (category: any) => {
|
||
uni.showToast({
|
||
title: '编辑分类功能开发中',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
const deleteCategory = (category: any) => {
|
||
uni.showModal({
|
||
title: '确认删除',
|
||
content: `确定要删除分类 "${category.name}" 吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
const index = categories.value.findIndex(c => c.id === category.id)
|
||
if (index > -1) {
|
||
categories.value.splice(index, 1)
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 页面生命周期
|
||
onLoad((options: any) => {
|
||
// 处理页面参数,切换到对应的tab
|
||
if (options && options.action) {
|
||
if (options.action === 'add') {
|
||
activeTab.value = 'product-add'
|
||
showAddModal.value = true
|
||
} else {
|
||
activeTab.value = 'product-list'
|
||
}
|
||
} else if (options && options.tab) {
|
||
if (options.tab === 'category') {
|
||
activeTab.value = 'category'
|
||
} else {
|
||
activeTab.value = 'product-list'
|
||
}
|
||
} else {
|
||
activeTab.value = 'product-list'
|
||
}
|
||
|
||
console.log('商品管理页面加载,参数:', options)
|
||
})
|
||
|
||
onMounted(() => {
|
||
// 初始化数据
|
||
console.log('商品管理页面初始化')
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
/* Tab 栏样式 */
|
||
.tab-bar {
|
||
display: flex;
|
||
background-color: #ffffff;
|
||
border-radius: 8rpx;
|
||
padding: 8rpx;
|
||
margin-bottom: 24rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
border-radius: 6rpx;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
background-color: #f5f5f5;
|
||
color: #666666;
|
||
}
|
||
|
||
.tab-item:hover {
|
||
background-color: #e8e8e8;
|
||
}
|
||
|
||
.tab-item.active {
|
||
background-color: #1890ff;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.tab-icon {
|
||
font-size: 16rpx;
|
||
}
|
||
|
||
.tab-title {
|
||
font-size: 14rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 分类管理样式 */
|
||
.category-section {
|
||
background-color: #ffffff;
|
||
border-radius: 8rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.category-management {
|
||
padding: 32rpx;
|
||
}
|
||
|
||
.category-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 18rpx;
|
||
font-weight: 600;
|
||
color: #262626;
|
||
}
|
||
|
||
.category-tree {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.category-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20rpx 24rpx;
|
||
background-color: #fafafa;
|
||
border-radius: 6rpx;
|
||
border: 1rpx solid #e8e8e8;
|
||
}
|
||
|
||
.category-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.category-name {
|
||
font-size: 16rpx;
|
||
font-weight: 500;
|
||
color: #262626;
|
||
}
|
||
|
||
.category-count {
|
||
font-size: 14rpx;
|
||
color: #666666;
|
||
}
|
||
|
||
.category-actions {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.action-btn {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 6rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
border: none;
|
||
font-size: 16rpx;
|
||
}
|
||
|
||
.action-btn.edit {
|
||
background-color: #1890ff;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.action-btn.delete {
|
||
background-color: #ff4d4f;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.product-management {
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.page-header {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.page-title {
|
||
display: block;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.page-subtitle {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.stats-cards {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 40rpx;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.stat-card {
|
||
flex: 1;
|
||
min-width: 200rpx;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 16rpx;
|
||
padding: 40rpx 30rpx;
|
||
text-align: center;
|
||
color: #fff;
|
||
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3);
|
||
}
|
||
|
||
.stat-icon {
|
||
font-size: 48rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.stat-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.stat-value {
|
||
display: block;
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.stat-label {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.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-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.search-input-wrapper {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
padding: 16rpx 20rpx;
|
||
border: none;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.search-btn {
|
||
padding: 16rpx 20rpx;
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
border: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.advanced-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
color: #1890ff;
|
||
cursor: pointer;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.advanced-search {
|
||
border-top: 1rpx solid #eee;
|
||
padding-top: 30rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.filter-row {
|
||
display: flex;
|
||
gap: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.filter-item {
|
||
flex: 1;
|
||
min-width: 200rpx;
|
||
}
|
||
|
||
.filter-label {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.picker-display {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16rpx 20rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
background-color: #fff;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.price-range {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.price-input {
|
||
flex: 1;
|
||
padding: 16rpx 20rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.price-separator {
|
||
color: #666;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.filter-actions {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
justify-content: flex-end;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.action-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.left-actions,
|
||
.right-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.select-all {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
font-size: 26rpx;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background-color: #fff;
|
||
color: #666;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
font-size: 26rpx;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.btn-warning {
|
||
background-color: #faad14;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
font-size: 26rpx;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.btn-danger {
|
||
background-color: #ff4d4f;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
font-size: 26rpx;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.btn-danger:disabled {
|
||
background-color: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.product-table {
|
||
background-color: #fff;
|
||
border-radius: 12rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.table-header {
|
||
background-color: #f5f5f5;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.table-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.table-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.table-row.selected {
|
||
background-color: #f0f8ff;
|
||
}
|
||
|
||
.col-select {
|
||
width: 80rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.col-image {
|
||
width: 120rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.col-info {
|
||
flex: 2;
|
||
}
|
||
|
||
.col-price,
|
||
.col-stock,
|
||
.col-sales {
|
||
flex: 1;
|
||
}
|
||
|
||
.col-status {
|
||
flex: 1.2;
|
||
}
|
||
|
||
.col-actions {
|
||
width: 250rpx;
|
||
display: flex;
|
||
gap: 10rpx;
|
||
justify-content: center;
|
||
}
|
||
|
||
.product-image {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 8rpx;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.product-name {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.product-code {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.product-category {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.original-price {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
text-decoration: line-through;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.current-price {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
.stock-text {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.stock-text.low-stock {
|
||
color: #ff4d4f;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: 6rpx 12rpx;
|
||
border-radius: 12rpx;
|
||
font-size: 22rpx;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
}
|
||
|
||
.status-badge.on_sale {
|
||
background-color: #52c41a;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-badge.off_shelf {
|
||
background-color: #ff4d4f;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-badge.pending {
|
||
background-color: #faad14;
|
||
color: #fff;
|
||
}
|
||
|
||
.sales-text {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.action-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
border: none;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.action-btn.edit {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.onsale {
|
||
background-color: #52c41a;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.offshelf {
|
||
background-color: #faad14;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.delete {
|
||
background-color: #ff4d4f;
|
||
color: #fff;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 30rpx;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.page-info {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.page-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.page-btn {
|
||
padding: 12rpx 20rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 6rpx;
|
||
background-color: #fff;
|
||
color: #333;
|
||
cursor: pointer;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.page-btn:disabled {
|
||
background-color: #f5f5f5;
|
||
color: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.page-numbers {
|
||
display: flex;
|
||
gap: 8rpx;
|
||
margin: 0 20rpx;
|
||
}
|
||
|
||
.page-number {
|
||
width: 50rpx;
|
||
height: 50rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 6rpx;
|
||
background-color: #fff;
|
||
color: #333;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.page-number.active {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
border-color: #1890ff;
|
||
}
|
||
|
||
.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: 90%;
|
||
max-width: 700rpx;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.delete-modal {
|
||
max-width: 400rpx;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.modal-close {
|
||
cursor: pointer;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
padding: 10rpx;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.form-group {
|
||
flex: 1;
|
||
min-width: 200rpx;
|
||
}
|
||
|
||
.form-label {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
margin-bottom: 10rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-input,
|
||
.form-textarea {
|
||
width: 100%;
|
||
padding: 16rpx 20rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.form-textarea {
|
||
min-height: 120rpx;
|
||
resize: vertical;
|
||
}
|
||
|
||
.form-select {
|
||
padding: 16rpx 20rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
background-color: #fff;
|
||
cursor: pointer;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.image-upload {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.upload-area {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border: 2rpx dashed #ddd;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
color: #999;
|
||
}
|
||
|
||
.upload-text {
|
||
font-size: 24rpx;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.preview-image {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 8rpx;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
justify-content: flex-end;
|
||
padding: 30rpx;
|
||
border-top: 1rpx solid #eee;
|
||
}
|
||
|
||
.delete-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
text-align: center;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.iconfont {
|
||
font-family: 'iconfont';
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media screen and (max-width: 750rpx) {
|
||
.stats-cards {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.filter-row {
|
||
flex-direction: column;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.action-bar {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.left-actions,
|
||
.right-actions {
|
||
justify-content: center;
|
||
}
|
||
|
||
.table-row {
|
||
flex-wrap: wrap;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.col-info {
|
||
flex: 1 1 100%;
|
||
}
|
||
|
||
.form-row {
|
||
flex-direction: column;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.pagination {
|
||
flex-direction: column;
|
||
text-align: center;
|
||
}
|
||
}
|
||
</style> |