-
@@ -118,9 +349,25 @@
import { ref, reactive, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
-import { ArrowLeft } from '@element-plus/icons-vue'
+import {
+ Box,
+ ArrowLeft,
+ InfoFilled,
+ Money,
+ Picture,
+ Document,
+ Setting,
+ Shop,
+ Coin,
+ CreditCard,
+ Check,
+ Refresh
+} from '@element-plus/icons-vue'
import api from '@/utils/api'
import ImageUpload from '@/components/ImageUpload.vue'
+import MediaUpload from '@/components/MediaUpload.vue'
+import HorizontalImageDisplay from '@/components/HorizontalImageDisplay.vue'
+import RichTextEditor from '@/components/RichTextEditor.vue'
const route = useRoute()
const router = useRouter()
@@ -130,13 +377,32 @@ const categories = ref(['数码产品', '生活用品', '食品饮料', '图书
const isEdit = computed(() => !!route.params.id)
+// 商品状态开关
+const statusActive = computed({
+ get: () => form.status,
+ set: (value) => {
+ form.status = value
+ }
+})
+
+// 处理状态变化
+const handleStatusChange = (value) => {
+ form.status = value
+}
+
const form = reactive({
name: '',
category: '',
price: null,
points: null,
+ rongdou_price: null,
stock: null,
image: '',
+ images: [],
+ videos: [],
+ shop_name: '',
+ shop_avatar: '',
+ payment_methods: ['points'],
description: '',
details: '',
status: 'active'
@@ -158,6 +424,12 @@ const rules = {
{ required: true, message: '请输入积分价格', trigger: 'blur' },
{ type: 'number', min: 1, message: '积分价格必须大于0', trigger: 'blur' }
],
+ rongdou_price: [
+ { type: 'number', min: 0, message: '融豆价格不能小于0', trigger: 'blur' }
+ ],
+ payment_methods: [
+ { required: true, message: '请选择至少一种支付方式', trigger: 'change' }
+ ],
stock: [
{ required: true, message: '请输入库存数量', trigger: 'blur' },
{ type: 'number', min: 0, message: '库存数量不能小于0', trigger: 'blur' }
@@ -170,8 +442,19 @@ const rules = {
{ min: 10, max: 500, message: '商品描述长度在 10 到 500 个字符', trigger: 'blur' }
],
details: [
- { required: true, message: '请输入商品详情', trigger: 'blur' },
- { min: 20, max: 2000, message: '商品详情长度在 20 到 2000 个字符', trigger: 'blur' }
+ { required: true, message: '请输入商品详情', trigger: 'change' },
+ {
+ validator: (rule, value, callback) => {
+ if (!value || value.trim() === '' || value === '
') {
+ callback(new Error('请输入商品详情'))
+ } else if (value.length < 20) {
+ callback(new Error('商品详情内容过少,请详细描述商品信息'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
],
status: [
{ required: true, message: '请选择商品状态', trigger: 'change' }
@@ -183,23 +466,40 @@ const loadProduct = async () => {
if (!isEdit.value) return
try {
- const response = await api.get(`/products/${route.params.id}`)
+ const response = await api.products.getProductById(route.params.id)
const {data} = response.data
let product = data.product
- console.log(data,'1111');
+
+
+ // 检查 product 是否存在
+ if (!product) {
+ ElMessage.error('商品数据不存在')
+ router.push('/products')
+ return
+ }
// 将后端字段映射到前端字段名
+ console.log(form,product);
+
Object.assign(form, {
- name: product.name,
- category: product.category,
- price: product.price,
- points: product.points, // 后端 points_price -> 前端 points
- stock: product.stock,
- image: product.image_url, // 后端 image_url -> 前端 image
- description: product.description,
- details: product.details,
- status: product.status
+ name: product.name || '',
+ category: product.category || '',
+ price: product.price || 0,
+ points: product.points_price || 0, // 后端 points_price -> 前端 points
+ rongdou_price: product.rongdou_price || 0,
+ stock: product.stock || 0,
+ image: product.image_url || '', // 后端 image_url -> 前端 image
+ images: product.images || [],
+ videos: product.videos || [],
+ shop_name: product.shop_name || '',
+ shop_avatar: product.shop_avatar || '',
+ payment_methods: product.payment_methods || [],
+ description: product.description || '',
+ details: product.details || '',
+ status: product.status || 'active'
})
+ console.log('表单数据:', form);
+
} catch (error) {
ElMessage.error('加载商品信息失败',error)
// router.back()
@@ -220,18 +520,24 @@ const submitForm = async () => {
category: form.category,
price: form.price,
points_price: form.points, // 前端 points -> 后端 points_price
+ rongdou_price: form.rongdou_price || 0,
stock: form.stock,
image_url: form.image, // 前端 image -> 后端 image_url
+ images: JSON.stringify(form.images),
+ videos: JSON.stringify(form.videos),
+ shop_name: form.shop_name,
+ shop_avatar: form.shop_avatar,
+ payment_methods: JSON.stringify(form.payment_methods),
description: form.description,
details: form.details,
status: form.status
}
if (isEdit.value) {
- await api.put(`/products/${route.params.id}`, submitData)
+ await api.products.updateProduct(route.params.id, submitData)
ElMessage.success('商品更新成功')
} else {
- await api.post('/products', submitData)
+ await api.products.createProduct(submitData)
ElMessage.success('商品创建成功')
}
@@ -258,9 +564,54 @@ const handleImageUploadSuccess = (data) => {
console.log('图片上传成功:', data)
}
-// 图片上传失败处理
+// 店家头像上传成功处理
+const handleShopAvatarUploadSuccess = (data) => {
+ console.log('店家头像上传成功:', data)
+}
+
+// 商品相册上传成功处理
+const handleImagesUploadSuccess = (data) => {
+ console.log('商品相册上传成功:', data)
+ // 不需要手动添加图片,HorizontalImageDisplay组件已经通过v-model自动更新了form.images
+ ElMessage.success('图片上传成功')
+}
+
+// 商品视频上传成功处理
+const handleVideosUploadSuccess = (data) => {
+ console.log('商品视频上传成功:', data)
+ // 不需要手动添加视频,HorizontalImageDisplay组件已经通过v-model自动更新了form.videos
+ ElMessage.success('视频上传成功')
+}
+
+// 图片/视频上传失败处理
const handleImageUploadError = (error) => {
- console.error('图片上传失败:', error)
+ console.error('上传失败:', error)
+
+ // 根据错误类型显示不同的提示信息
+ let errorMessage = '上传失败,请重试'
+
+ if (error && error.message) {
+ if (error.message.includes('size')) {
+ errorMessage = '文件大小超出限制'
+ } else if (error.message.includes('type')) {
+ errorMessage = '文件格式不支持'
+ } else if (error.message.includes('network')) {
+ errorMessage = '网络错误,请检查网络连接'
+ } else {
+ errorMessage = error.message
+ }
+ }
+
+ ElMessage.error(errorMessage)
+}
+
+// 处理富文本编辑器内容变化
+const handleDetailsChange = (content) => {
+ form.details = content
+ // 触发表单验证
+ if (formRef.value) {
+ formRef.value.validateField('details')
+ }
}
onMounted(() => {
@@ -270,27 +621,485 @@ onMounted(() => {
\ No newline at end of file
diff --git a/src/views/ProductSpecifications.vue b/src/views/ProductSpecifications.vue
new file mode 100644
index 0000000..8772595
--- /dev/null
+++ b/src/views/ProductSpecifications.vue
@@ -0,0 +1,315 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.price_adjustment > 0 ? '+' : '' }}{{ row.price_adjustment }}
+
+
+
+
+
+
+ {{ formatDate(row.created_at) }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/views/Products.vue b/src/views/Products.vue
index b4cdbc5..7dfae5e 100644
--- a/src/views/Products.vue
+++ b/src/views/Products.vue
@@ -60,6 +60,19 @@
{{ row.points }} 积分
+
+
+ {{ row.rongdou_price || 0 }} 融豆
+
+
+
+
+
+
+ {{ row.shop_name || '默认店铺' }}
+
+
+
@@ -83,6 +96,13 @@
>
编辑
+
+ 规格
+
{
...filters
}
- const {data} = await api.get('/products', { params })
+ const {data} = await api.products.getProducts(params)
products.value = data.data.products
pagination.total = data.data.total
} catch (error) {
@@ -163,7 +183,7 @@ const loadProducts = async () => {
// 加载商品分类
const loadCategories = async () => {
try {
- const {data} = await api.get('/products/categories')
+ const {data} = await api.products.getCategories()
categories.value = data.data.categories
} catch (error) {
console.error('加载分类失败:', error)
@@ -201,7 +221,7 @@ const toggleStatus = async (product) => {
)
const newStatus = product.status === 'active' ? 'inactive' : 'active'
- await api.put(`/products/${product.id}`, { status: newStatus })
+ await api.products.updateProduct(product.id, { status: newStatus })
ElMessage.success(`${action}成功`)
loadProducts()
@@ -225,7 +245,7 @@ const deleteProduct = async (product) => {
}
)
- await api.delete(`/products/${product.id}`)
+ await api.products.deleteProduct(product.id)
ElMessage.success('删除成功')
loadProducts()
} catch (error) {
@@ -235,6 +255,11 @@ const deleteProduct = async (product) => {
}
}
+// 查看商品规格
+const viewSpecs = (id) => {
+ router.push(`/products/${id}/specifications`)
+}
+
// 格式化日期
const formatDate = (dateString) => {
return new Date(dateString).toLocaleString('zh-CN')
@@ -279,6 +304,22 @@ onMounted(() => {
font-weight: 500;
}
+.rongdou-text {
+ color: #67c23a;
+ font-weight: 500;
+}
+
+.shop-info {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.shop-info span {
+ font-size: 12px;
+ color: #606266;
+}
+
.pagination {
margin-top: 20px;
display: flex;
diff --git a/vite.config.js b/vite.config.js
index 4ffd7ec..125e8ff 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -4,8 +4,8 @@ import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
- base: '/admin',
- // base: '/',
+ // base: '/admin',
+ base: '/',
plugins: [vue()],
resolve: {
alias: {
@@ -19,10 +19,10 @@ export default defineConfig({
target: 'http://localhost:3000',
changeOrigin: true
},
- '/admin': {
- target: 'http://localhost:3000',
- changeOrigin: true
- },
+ // '/admin': {
+ // target: 'http://localhost:3000',
+ // changeOrigin: true
+ // },
'/uploads': {
target: 'http://localhost:3000',
changeOrigin: true