diff --git a/src/api/product.js b/src/api/product.js
index eba69f1..480379b 100644
--- a/src/api/product.js
+++ b/src/api/product.js
@@ -72,5 +72,35 @@ export default {
*/
getStockAlerts() {
return api.request('/products/alerts', 'GET')
+ },
+
+ // ============ 种类属性 ============
+ /**
+ * 获取种类的属性定义
+ */
+ getCategoryAttributes(categoryId) {
+ return api.request(`/products/categories/${categoryId}/attributes`, 'GET')
+ },
+
+ /**
+ * 保存种类的属性定义
+ */
+ saveCategoryAttributes(categoryId, attrs) {
+ return api.request(`/products/categories/${categoryId}/attributes`, 'POST', {}, attrs)
+ },
+
+ // ============ 商品属性 ============
+ /**
+ * 获取商品的属性值
+ */
+ getProductAttributes(productId) {
+ return api.request(`/products/${productId}/attributes`, 'GET')
+ },
+
+ /**
+ * 保存商品的属性值
+ */
+ saveProductAttributes(productId, attrs) {
+ return api.request(`/products/${productId}/attributes`, 'POST', {}, attrs)
}
}
diff --git a/src/pages/category/index.vue b/src/pages/category/index.vue
index 7a23c1b..bae3729 100644
--- a/src/pages/category/index.vue
+++ b/src/pages/category/index.vue
@@ -13,6 +13,7 @@
{{ cat.description || '暂无描述' }}
+ 属性配置
编辑
删除
@@ -51,6 +52,33 @@
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+ + 添加属性
+
+
+
+
+
@@ -67,7 +95,11 @@ export default {
categoryId: '',
name: '',
description: ''
- }
+ },
+ // 属性配置相关
+ showAttrDialog: false,
+ currentCategory: {},
+ attributes: []
}
},
onLoad() {
@@ -149,6 +181,48 @@ export default {
uni.hideLoading()
uni.showToast({ title: '检查失败', icon: 'none' })
})
+ },
+ // ============ 属性配置 ============
+ async editAttributes(cat) {
+ this.currentCategory = cat
+ this.showAttrDialog = true
+ try {
+ const attrs = await productApi.getCategoryAttributes(cat.categoryId)
+ this.attributes = (attrs || []).map(a => ({
+ name: a.name,
+ attrType: a.attrType || 'number',
+ unit: a.unit || '',
+ attrId: a.attrId
+ }))
+ if (this.attributes.length === 0) {
+ this.attributes.push({ name: '', attrType: 'number', unit: '', attrId: '' })
+ }
+ } catch (e) {
+ this.attributes = [{ name: '', attrType: 'number', unit: '', attrId: '' }]
+ }
+ },
+ addAttr() {
+ this.attributes.push({ name: '', attrType: 'number', unit: '', attrId: '' })
+ },
+ removeAttr(index) {
+ this.attributes.splice(index, 1)
+ },
+ closeAttrDialog() {
+ this.showAttrDialog = false
+ },
+ async saveAttributes() {
+ const validAttrs = this.attributes.filter(a => a.name.trim())
+ if (validAttrs.length === 0) {
+ uni.showToast({ title: '请至少添加一个属性', icon: 'none' })
+ return
+ }
+ try {
+ await productApi.saveCategoryAttributes(this.currentCategory.categoryId, validAttrs)
+ uni.showToast({ title: '保存成功', icon: 'success' })
+ this.closeAttrDialog()
+ } catch (e) {
+ uni.showToast({ title: '保存失败', icon: 'none' })
+ }
}
}
}
@@ -220,6 +294,11 @@ export default {
color: #1890ff;
}
+.action-btn.attr {
+ background: #f6ffed;
+ color: #52c41a;
+}
+
.action-btn.delete {
background: #fff1f0;
color: #ff4d4f;
@@ -330,4 +409,60 @@ export default {
color: #667eea;
font-weight: bold;
}
+
+/* 属性配置弹窗 */
+.dialog-wide {
+ width: 90%;
+ max-height: 80vh;
+}
+
+.dialog-body {
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+.attr-item {
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+ margin-bottom: 20rpx;
+}
+
+.attr-name {
+ flex: 2;
+}
+
+.attr-type {
+ flex: 1;
+ background: #f5f5f5;
+ border-radius: 12rpx;
+ padding: 0 10rpx;
+ height: 70rpx;
+ font-size: 24rpx;
+}
+
+.attr-unit {
+ flex: 1;
+}
+
+.attr-delete {
+ width: 60rpx;
+ height: 60rpx;
+ line-height: 60rpx;
+ text-align: center;
+ font-size: 36rpx;
+ color: #ff4d4f;
+ background: #fff1f0;
+ border-radius: 8rpx;
+}
+
+.add-attr-btn {
+ padding: 20rpx;
+ text-align: center;
+ color: #667eea;
+ font-size: 28rpx;
+ border: 2rpx dashed #667eea;
+ border-radius: 12rpx;
+ margin-top: 20rpx;
+}
\ No newline at end of file
diff --git a/src/pages/product/manage.vue b/src/pages/product/manage.vue
index 27f088a..f13989b 100644
--- a/src/pages/product/manage.vue
+++ b/src/pages/product/manage.vue
@@ -81,6 +81,13 @@
+
+
+
+ {{ attr.name }}{{ attr.unit ? '(' + attr.unit + ')' : '' }}
+
+
+
备注
@@ -116,7 +123,9 @@ export default {
categoryId: '',
remark: '',
status: 1
- }
+ },
+ categoryAttributes: [],
+ productAttrs: []
}
},
onLoad() {
@@ -164,12 +173,34 @@ export default {
remark: '',
status: 1
}
+ this.categoryAttributes = []
+ this.productAttrs = []
this.showModal = true
},
editProduct(item) {
this.isEdit = true
this.form = { ...item }
this.showModal = true
+ // 加载分类属性和商品属性值
+ this.form.categoryId = item.categoryId
+ this.loadProductAttributes(item.productId)
+ this.loadCategoryAttributes()
+ },
+ async loadProductAttributes(productId) {
+ try {
+ const attrs = await productApi.getProductAttributes(productId)
+ if (attrs && attrs.length > 0) {
+ // 匹配属性值
+ attrs.forEach(a => {
+ const idx = this.categoryAttributes.findIndex(ca => ca.attrId === a.attrId)
+ if (idx >= 0) {
+ this.productAttrs[idx] = a.attrValue
+ }
+ })
+ }
+ } catch (e) {
+ console.error(e)
+ }
},
closeModal() {
this.showModal = false
@@ -177,6 +208,20 @@ export default {
onCategoryChange(e) {
const index = e.detail.value
this.form.categoryId = this.categories[index].categoryId
+ this.loadCategoryAttributes()
+ },
+ async loadCategoryAttributes() {
+ if (!this.form.categoryId) {
+ this.categoryAttributes = []
+ return
+ }
+ try {
+ const attrs = await productApi.getCategoryAttributes(this.form.categoryId)
+ this.categoryAttributes = attrs || []
+ this.productAttrs = this.categoryAttributes.map(() => '')
+ } catch (e) {
+ this.categoryAttributes = []
+ }
},
getCategoryName(categoryId) {
const cat = this.categories.find(c => c.categoryId === categoryId)
@@ -197,13 +242,30 @@ export default {
}
try {
+ let productId
if (this.isEdit) {
- await productApi.updateProduct(this.form)
+ const res = await productApi.updateProduct(this.form)
+ productId = this.form.productId
uni.showToast({ title: '更新成功', icon: 'success' })
} else {
- await productApi.createProduct(this.form)
+ const res = await productApi.createProduct(this.form)
+ productId = res.productId
uni.showToast({ title: '创建成功', icon: 'success' })
}
+
+ // 保存商品属性值
+ if (productId && this.categoryAttributes.length > 0) {
+ const attrs = this.categoryAttributes.map((attr, idx) => ({
+ attrId: attr.attrId,
+ attrName: attr.name,
+ attrValue: this.productAttrs[idx] || ''
+ })).filter(a => a.attrValue)
+
+ if (attrs.length > 0) {
+ await productApi.saveProductAttributes(productId, attrs)
+ }
+ }
+
this.closeModal()
this.loadProducts()
} catch (e) {