|
@@ -0,0 +1,405 @@
|
|
|
+<template>
|
|
|
+ <div class="min-h-screen bg-dopamine-50 dark:bg-dopamine-900">
|
|
|
+ <!-- 顶部导航栏 -->
|
|
|
+ <nav class="bg-white dark:bg-gray-800 shadow">
|
|
|
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
|
+ <div class="flex justify-between h-16">
|
|
|
+ <div class="flex">
|
|
|
+ <div class="flex-shrink-0 flex items-center">
|
|
|
+ <h1 class="text-xl font-bold text-gray-900 dark:text-white">商品管理</h1>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center">
|
|
|
+ <button
|
|
|
+ @click="showAddProductModal = true"
|
|
|
+ class="ml-4 inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-dopamine-600 hover:bg-dopamine-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-dopamine-500"
|
|
|
+ >
|
|
|
+ <PlusIcon class="h-4 w-4 mr-2" />
|
|
|
+ 新增
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ @click="showSearchModal = true"
|
|
|
+ class="ml-4 inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-dopamine-600 hover:bg-dopamine-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-dopamine-500"
|
|
|
+ >
|
|
|
+ <SearchIcon class="h-4 w-4 mr-2" />
|
|
|
+ 查询
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </nav>
|
|
|
+
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
+ <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
|
|
+ <!-- 搜索与筛选区域 -->
|
|
|
+ <div class="mb-6 bg-white dark:bg-gray-800 shadow rounded-lg p-4">
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
+ <div class="relative">
|
|
|
+ <input
|
|
|
+ v-model="searchQuery"
|
|
|
+ type="text"
|
|
|
+ class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-dopamine-500 dark:bg-gray-700 dark:text-white"
|
|
|
+ placeholder="搜索商品名称"
|
|
|
+ />
|
|
|
+ <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
|
+ <SearchIcon class="h-5 w-5 text-gray-400" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 商品列表 -->
|
|
|
+ <div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
|
|
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
+ <thead class="bg-gray-50 dark:bg-gray-700">
|
|
|
+ <tr>
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
+ 商品ID
|
|
|
+ </th>
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
+ 商品名称
|
|
|
+ </th>
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
+ 价格
|
|
|
+ </th>
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
+ 库存
|
|
|
+ </th>
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
+ 供应商信息
|
|
|
+ </th>
|
|
|
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
+ 操作
|
|
|
+ </th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
+ <tr v-for="product in filteredProducts" :key="product.id">
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ product.id }}</div>
|
|
|
+ </td>
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ product.name }}</div>
|
|
|
+ </td>
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">¥{{ product.price }}</div>
|
|
|
+ </td>
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ product.stock }}</div>
|
|
|
+ </td>
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ product.supplier }}</div>
|
|
|
+ </td>
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
|
+ <button
|
|
|
+ @click="editProduct(product)"
|
|
|
+ class="text-dopamine-600 hover:text-dopamine-900 dark:text-dopamine-400 dark:hover:text-dopamine-300 mr-4"
|
|
|
+ >
|
|
|
+ 编辑
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ @click="deleteProduct(product)"
|
|
|
+ class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </button>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div class="mt-4 flex justify-between items-center">
|
|
|
+ <div class="text-sm text-gray-700 dark:text-gray-300">
|
|
|
+ 显示 {{ (currentPage - 1) * pageSize + 1 }} 到 {{ Math.min(currentPage * pageSize, totalProducts) }} 条,共 {{ totalProducts }} 条
|
|
|
+ </div>
|
|
|
+ <div class="flex space-x-2">
|
|
|
+ <button
|
|
|
+ @click="prevPage"
|
|
|
+ :disabled="currentPage === 1"
|
|
|
+ class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
|
|
|
+ >
|
|
|
+ 上一页
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ @click="nextPage"
|
|
|
+ :disabled="currentPage * pageSize >= totalProducts"
|
|
|
+ class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
|
|
|
+ >
|
|
|
+ 下一页
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </main>
|
|
|
+
|
|
|
+ <!-- 添加/编辑商品弹窗 -->
|
|
|
+ <div v-if="showAddProductModal || showEditProductModal" class="fixed inset-0 z-50 overflow-y-auto">
|
|
|
+ <div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
|
|
+ <div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
|
|
+ <div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
|
|
|
+ </div>
|
|
|
+ <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
|
|
+ <div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
|
|
|
+ <div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
|
+ <div class="sm:flex sm:items-start">
|
|
|
+ <div class="mt-3 text-center sm:mt-0 sm:text-left w-full">
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
|
|
+ {{ isEditing ? '编辑商品' : '新增商品' }}
|
|
|
+ </h3>
|
|
|
+ <button @click="closeModal" class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300">
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <form @submit.prevent="saveProduct" class="space-y-4">
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">商品名称</label>
|
|
|
+ <input
|
|
|
+ v-model="productForm.name"
|
|
|
+ type="text"
|
|
|
+ required
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-dopamine-500 focus:border-dopamine-500 dark:bg-gray-700 dark:text-white"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">商品价格</label>
|
|
|
+ <input
|
|
|
+ v-model.number="productForm.price"
|
|
|
+ type="number"
|
|
|
+ min="0"
|
|
|
+ step="0.01"
|
|
|
+ required
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-dopamine-500 focus:border-dopamine-500 dark:bg-gray-700 dark:text-white"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">商品库存</label>
|
|
|
+ <input
|
|
|
+ v-model.number="productForm.stock"
|
|
|
+ type="number"
|
|
|
+ min="0"
|
|
|
+ required
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-dopamine-500 focus:border-dopamine-500 dark:bg-gray-700 dark:text-white"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">供应商信息</label>
|
|
|
+ <input
|
|
|
+ v-model="productForm.supplier"
|
|
|
+ type="text"
|
|
|
+ required
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-dopamine-500 focus:border-dopamine-500 dark:bg-gray-700 dark:text-white"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
|
|
+ <button
|
|
|
+ type="submit"
|
|
|
+ class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-dopamine-600 text-base font-medium text-white hover:bg-dopamine-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-dopamine-500 sm:ml-3 sm:w-auto sm:text-sm"
|
|
|
+ >
|
|
|
+ 保存
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ @click="closeModal"
|
|
|
+ class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-700 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-dopamine-500 sm:mt-0 sm:w-auto sm:text-sm"
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 查询弹窗 -->
|
|
|
+ <div v-if="showSearchModal" class="fixed inset-0 z-50 overflow-y-auto">
|
|
|
+ <div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
|
|
+ <div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
|
|
+ <div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
|
|
|
+ </div>
|
|
|
+ <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
|
|
+ <div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
|
|
|
+ <div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
|
+ <div class="sm:flex sm:items-start">
|
|
|
+ <div class="mt-3 text-center sm:mt-0 sm:text-left w-full">
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
|
|
+ 查询商品
|
|
|
+ </h3>
|
|
|
+ <button @click="showSearchModal = false" class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300">
|
|
|
+ <XIcon class="h-6 w-6" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <form @submit.prevent="performSearch" class="space-y-4">
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">商品名称</label>
|
|
|
+ <input
|
|
|
+ v-model="searchQuery"
|
|
|
+ type="text"
|
|
|
+ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-dopamine-500 focus:border-dopamine-500 dark:bg-gray-700 dark:text-white"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
|
|
+ <button
|
|
|
+ type="submit"
|
|
|
+ class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-dopamine-600 text-base font-medium text-white hover:bg-dopamine-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-dopamine-500 sm:ml-3 sm:w-auto sm:text-sm"
|
|
|
+ >
|
|
|
+ 查询
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ @click="showSearchModal = false"
|
|
|
+ class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-700 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-dopamine-500 sm:mt-0 sm:w-auto sm:text-sm"
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed } from 'vue'
|
|
|
+import {
|
|
|
+ SearchIcon,
|
|
|
+ PlusIcon,
|
|
|
+ XIcon
|
|
|
+} from 'lucide-vue-next'
|
|
|
+
|
|
|
+// 商品表单相关状态
|
|
|
+const showAddProductModal = ref(false)
|
|
|
+const showEditProductModal = ref(false)
|
|
|
+const isEditing = ref(false)
|
|
|
+const productForm = ref({
|
|
|
+ id: '',
|
|
|
+ name: '',
|
|
|
+ price: 0,
|
|
|
+ stock: 0,
|
|
|
+ supplier: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 商品列表相关状态
|
|
|
+const products = ref([
|
|
|
+ { id: '1', name: '商品A', price: 100, stock: 50, supplier: '供应商A' },
|
|
|
+ { id: '2', name: '商品B', price: 200, stock: 30, supplier: '供应商B' },
|
|
|
+ { id: '3', name: '商品C', price: 150, stock: 20, supplier: '供应商C' },
|
|
|
+ { id: '4', name: '商品D', price: 250, stock: 10, supplier: '供应商D' },
|
|
|
+ { id: '5', name: '商品E', price: 300, stock: 40, supplier: '供应商E' },
|
|
|
+ { id: '6', name: '商品F', price: 120, stock: 60, supplier: '供应商F' },
|
|
|
+ { id: '7', name: '商品G', price: 180, stock: 25, supplier: '供应商G' },
|
|
|
+ { id: '8', name: '商品H', price: 220, stock: 15, supplier: '供应商H' },
|
|
|
+ { id: '9', name: '商品I', price: 280, stock: 35, supplier: '供应商I' },
|
|
|
+ { id: '10', name: '商品J', price: 190, stock: 45, supplier: '供应商J' }
|
|
|
+])
|
|
|
+const searchQuery = ref('')
|
|
|
+const currentPage = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+const totalProducts = ref(products.value.length)
|
|
|
+
|
|
|
+// 计算筛选后的商品列表
|
|
|
+const filteredProducts = computed(() => {
|
|
|
+ return products.value
|
|
|
+ .filter(product => product.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
|
|
+ .slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value)
|
|
|
+})
|
|
|
+
|
|
|
+// 处理新增商品按钮点击
|
|
|
+const addProduct = () => {
|
|
|
+ isEditing.value = false
|
|
|
+ productForm.value = {
|
|
|
+ id: '',
|
|
|
+ name: '',
|
|
|
+ price: 0,
|
|
|
+ stock: 0,
|
|
|
+ supplier: ''
|
|
|
+ }
|
|
|
+ showAddProductModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 处理编辑商品按钮点击
|
|
|
+const editProduct = (product) => {
|
|
|
+ isEditing.value = true
|
|
|
+ productForm.value = { ...product }
|
|
|
+ showEditProductModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 处理保存商品
|
|
|
+const saveProduct = () => {
|
|
|
+ if (isEditing.value) {
|
|
|
+ // 更新商品
|
|
|
+ const index = products.value.findIndex(p => p.id === productForm.value.id)
|
|
|
+ if (index !== -1) {
|
|
|
+ products.value[index] = { ...productForm.value }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 添加新商品
|
|
|
+ productForm.value.id = String(products.value.length + 1)
|
|
|
+ products.value.push({ ...productForm.value })
|
|
|
+ totalProducts.value++
|
|
|
+ }
|
|
|
+ closeModal()
|
|
|
+}
|
|
|
+
|
|
|
+// 处理删除商品
|
|
|
+const deleteProduct = (product) => {
|
|
|
+ const index = products.value.findIndex(p => p.id === product.id)
|
|
|
+ if (index !== -1) {
|
|
|
+ products.value.splice(index, 1)
|
|
|
+ totalProducts.value--
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理查询提交
|
|
|
+const performSearch = () => {
|
|
|
+ currentPage.value = 1
|
|
|
+ showSearchModal.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 处理分页
|
|
|
+const prevPage = () => {
|
|
|
+ if (currentPage.value > 1) {
|
|
|
+ currentPage.value--
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const nextPage = () => {
|
|
|
+ if (currentPage.value * pageSize.value < totalProducts.value) {
|
|
|
+ currentPage.value++
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭模态框
|
|
|
+const closeModal = () => {
|
|
|
+ showAddProductModal.value = false
|
|
|
+ showEditProductModal.value = false
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+:root {
|
|
|
+ --dopamine-50: #fff5f5;
|
|
|
+ --dopamine-100: #fed7d7;
|
|
|
+ --dopamine-200: #feb2b2;
|
|
|
+ --dopamine-300: #fc8181;
|
|
|
+ --dopamine-400: #f56565;
|
|
|
+ --dopamine-500: #e53e3e;
|
|
|
+ --dopamine-600: #c53030;
|
|
|
+ --dopamine-700: #9b2c2c;
|
|
|
+ --dopamine-800: #822727;
|
|
|
+ --dopamine-900: #63171b;
|
|
|
+}
|
|
|
+</style>
|