feat: 优惠管理合并重构与商品续费价格参数
- 合并优惠码/代金券为商品管理下优惠管理页面,卡片化展示与过期遮罩 - 用户组新增优惠绑定,商品关联改用懒加载树选择器 - 商品/套餐表单新增 renew_price、renew_recommend_rebate、renew_fixed_price Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -161,47 +161,39 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="选择类型" prop="select_type" v-if="dialogType === 'add'">
|
||||
<el-radio-group v-model="form.select_type" @change="handleSelectTypeChange">
|
||||
<el-radio value="product">商品</el-radio>
|
||||
<el-radio value="product_group">商品组</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="选择商品" prop="selected_product" v-if="dialogType === 'add' && form.select_type === 'product'">
|
||||
<el-select
|
||||
v-model="form.selected_product"
|
||||
placeholder="请选择商品"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleProductChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in productOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (ID: ${item.id})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="选择商品组" prop="selected_group" v-if="dialogType === 'add' && form.select_type === 'product_group'">
|
||||
<el-select
|
||||
v-model="form.selected_group"
|
||||
placeholder="请选择商品组"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleProductGroupChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in productGroupOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (ID: ${item.id})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-form-item label="选择关联对象" v-if="dialogType === 'add'">
|
||||
<div class="goods-tree-wrapper">
|
||||
<div class="goods-tree-toolbar">
|
||||
<span class="tree-tip">可自由勾选商品组与商品,展开层级查看下属内容</span>
|
||||
<div class="tree-summary">
|
||||
已选 <b>{{ checkedSummary.groupCount }}</b> 个商品组 / <b>{{ checkedSummary.productCount }}</b> 个商品
|
||||
</div>
|
||||
</div>
|
||||
<el-tree
|
||||
ref="goodsTreeRef"
|
||||
:props="treeProps"
|
||||
:load="loadTreeNode"
|
||||
lazy
|
||||
show-checkbox
|
||||
check-strictly
|
||||
node-key="key"
|
||||
class="goods-tree"
|
||||
@check="handleTreeCheck"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="tree-node">
|
||||
<el-tag size="small" :type="data.nodeType === 'group' ? 'warning' : 'primary'" effect="plain">
|
||||
{{ data.nodeType === 'group' ? '组' : '品' }}
|
||||
</el-tag>
|
||||
<span class="tree-node-label">{{ data.label }}</span>
|
||||
<span class="tree-node-id">ID: {{ data.rawId }}</span>
|
||||
<span v-if="data.nodeType === 'product' && data.price != null" class="tree-node-price">
|
||||
¥{{ (data.price / 100).toFixed(2) }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 编辑模式显示字段 -->
|
||||
@@ -234,7 +226,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { ref, reactive, onMounted, watch, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Delete, Search, Plus, Refresh } from '@element-plus/icons-vue'
|
||||
import {
|
||||
@@ -276,36 +268,27 @@ const form = reactive({
|
||||
code_id: undefined,
|
||||
goods_id: undefined,
|
||||
goods_name: '',
|
||||
goods_type: '',
|
||||
select_type: 'product', // 选择类型:product 或 product_group
|
||||
selected_product: undefined, // 选中的商品ID
|
||||
selected_group: undefined // 选中的商品组ID
|
||||
goods_type: ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
code_id: [
|
||||
{ required: true, message: '请选择代金券', trigger: 'change' }
|
||||
],
|
||||
select_type: [
|
||||
{ required: true, message: '请选择类型', trigger: 'change' }
|
||||
],
|
||||
selected_product: [
|
||||
{ required: true, message: '请选择商品', trigger: 'change' }
|
||||
],
|
||||
selected_group: [
|
||||
{ required: true, message: '请选择商品组', trigger: 'change' }
|
||||
],
|
||||
goods_id: [
|
||||
{ required: true, message: '请输入商品ID', trigger: 'blur' }
|
||||
],
|
||||
goods_name: [
|
||||
{ required: true, message: '请输入商品名称', trigger: 'blur' }
|
||||
],
|
||||
goods_type: [
|
||||
{ required: true, message: '请选择商品类型', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 折叠层级选择器相关
|
||||
const goodsTreeRef = ref(null)
|
||||
const treeProps = {
|
||||
label: 'label',
|
||||
children: 'children',
|
||||
isLeaf: 'isLeaf'
|
||||
}
|
||||
const checkedSummary = reactive({ groupCount: 0, productCount: 0 })
|
||||
|
||||
// 状态数据
|
||||
const loading = ref(false)
|
||||
const goodsList = ref([])
|
||||
@@ -436,33 +419,79 @@ const fetchProductGroupList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 选择类型变化
|
||||
const handleSelectTypeChange = (type) => {
|
||||
form.selected_product = undefined
|
||||
form.selected_group = undefined
|
||||
form.goods_id = undefined
|
||||
form.goods_name = ''
|
||||
form.goods_type = ''
|
||||
}
|
||||
// 折叠层级选择器:懒加载节点
|
||||
// node.level === 0 时加载顶级商品组;展开商品组时加载其子分组与下属商品
|
||||
const loadTreeNode = async (node, resolve) => {
|
||||
try {
|
||||
// 根节点:仅加载 level=1 的顶级商品组
|
||||
if (node.level === 0) {
|
||||
const res = await getProductGroupList({ level: 1 })
|
||||
if (res.data.code === 200) {
|
||||
const groups = res.data.data?.data || []
|
||||
return resolve(groups.map(buildGroupNode))
|
||||
}
|
||||
return resolve([])
|
||||
}
|
||||
|
||||
// 选择商品变化
|
||||
const handleProductChange = (productId) => {
|
||||
const product = productOptions.value.find(item => item.id === productId)
|
||||
if (product) {
|
||||
form.goods_id = product.id
|
||||
form.goods_name = product.goodsName || product.name || ''
|
||||
form.goods_type = 'product'
|
||||
// 商品组节点:逐级加载子分组 + 下属商品
|
||||
if (node.data?.nodeType === 'group') {
|
||||
const groupId = node.data.rawId
|
||||
const childLevel = (node.data.level || 1) + 1
|
||||
const tasks = [
|
||||
getProductList({ good_group_id: groupId, delete: false })
|
||||
]
|
||||
// 仅当存在子分组时才请求下一级分组
|
||||
if (node.data.existSub) {
|
||||
tasks.push(getProductGroupList({ parent_id: groupId, level: childLevel }))
|
||||
}
|
||||
const results = await Promise.all(tasks)
|
||||
|
||||
const productRes = results[0]
|
||||
const productNodes = (productRes.data.code === 200 ? (productRes.data.data?.data || []) : [])
|
||||
.map(buildProductNode)
|
||||
|
||||
let groupNodes = []
|
||||
if (node.data.existSub && results[1]?.data.code === 200) {
|
||||
groupNodes = (results[1].data.data?.data || []).map(buildGroupNode)
|
||||
}
|
||||
|
||||
return resolve([...groupNodes, ...productNodes])
|
||||
}
|
||||
|
||||
return resolve([])
|
||||
} catch (error) {
|
||||
console.error('加载层级数据失败:', error)
|
||||
ElMessage.error('加载层级数据失败')
|
||||
return resolve([])
|
||||
}
|
||||
}
|
||||
|
||||
// 选择商品组变化
|
||||
const handleProductGroupChange = (groupId) => {
|
||||
const group = productGroupOptions.value.find(item => item.id === groupId)
|
||||
if (group) {
|
||||
form.goods_id = group.id
|
||||
form.goods_name = group.name || ''
|
||||
form.goods_type = 'product_group'
|
||||
}
|
||||
// 构建商品组节点
|
||||
const buildGroupNode = (group) => ({
|
||||
key: `group_${group.id}`,
|
||||
rawId: group.id,
|
||||
nodeType: 'group',
|
||||
label: group.name,
|
||||
level: group.level || 1,
|
||||
existSub: group.existSub || false,
|
||||
isLeaf: false
|
||||
})
|
||||
|
||||
// 构建商品节点
|
||||
const buildProductNode = (product) => ({
|
||||
key: `product_${product.id}`,
|
||||
rawId: product.id,
|
||||
nodeType: 'product',
|
||||
label: product.name,
|
||||
price: product.price,
|
||||
isLeaf: true
|
||||
})
|
||||
|
||||
// 勾选变化时更新汇总
|
||||
const handleTreeCheck = () => {
|
||||
const nodes = goodsTreeRef.value?.getCheckedNodes() || []
|
||||
checkedSummary.groupCount = nodes.filter(n => n.nodeType === 'group').length
|
||||
checkedSummary.productCount = nodes.filter(n => n.nodeType === 'product').length
|
||||
}
|
||||
|
||||
// 获取商品关联列表
|
||||
@@ -527,16 +556,15 @@ const handleAdd = () => {
|
||||
code_id: queryParams.code_id ? Number(queryParams.code_id) : undefined,
|
||||
goods_id: undefined,
|
||||
goods_name: '',
|
||||
goods_type: '',
|
||||
select_type: 'product',
|
||||
selected_product: undefined,
|
||||
selected_group: undefined
|
||||
goods_type: ''
|
||||
})
|
||||
checkedSummary.groupCount = 0
|
||||
checkedSummary.productCount = 0
|
||||
formRef.value?.resetFields()
|
||||
|
||||
// 加载商品和商品组列表
|
||||
fetchProductList()
|
||||
fetchProductGroupList()
|
||||
// 等待对话框渲染后清空树的勾选状态
|
||||
nextTick(() => {
|
||||
goodsTreeRef.value?.setCheckedKeys([])
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑商品关联
|
||||
@@ -641,80 +669,77 @@ const handleBatchDelete = () => {
|
||||
|
||||
// 提交表单
|
||||
const submitForm = () => {
|
||||
// 新增模式下的额外验证
|
||||
if (dialogType.value === 'add') {
|
||||
if (!form.code_id) {
|
||||
ElMessage.warning('请选择代金券')
|
||||
return
|
||||
}
|
||||
if (form.select_type === 'product' && !form.selected_product) {
|
||||
ElMessage.warning('请选择商品')
|
||||
return
|
||||
}
|
||||
if (form.select_type === 'product_group' && !form.selected_group) {
|
||||
ElMessage.warning('请选择商品组')
|
||||
return
|
||||
}
|
||||
if (!form.goods_id || !form.goods_name || !form.goods_type) {
|
||||
ElMessage.warning('请先选择商品或商品组')
|
||||
return
|
||||
}
|
||||
if (!form.code_id) {
|
||||
ElMessage.warning('请选择代金券')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (dialogType.value === 'add') {
|
||||
// 收集树中勾选的商品组与商品
|
||||
const checkedNodes = goodsTreeRef.value?.getCheckedNodes() || []
|
||||
const goodIds = checkedNodes.filter(n => n.nodeType === 'product').map(n => n.rawId)
|
||||
const goodGroupIds = checkedNodes.filter(n => n.nodeType === 'group').map(n => n.rawId)
|
||||
|
||||
if (goodIds.length === 0 && goodGroupIds.length === 0) {
|
||||
ElMessage.warning('请至少勾选一个商品或商品组')
|
||||
return
|
||||
}
|
||||
|
||||
submitAdd(goodIds, goodGroupIds)
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑模式
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const submitData = {
|
||||
code_id: String(form.code_id)
|
||||
}
|
||||
|
||||
// 根据选择类型决定传 good_id 还是 good_group_id
|
||||
if (dialogType.value === 'add') {
|
||||
if (form.select_type === 'product') {
|
||||
// 选择的是商品,传 good_id
|
||||
submitData.good_ids = String(form.goods_id)
|
||||
} else if (form.select_type === 'product_group') {
|
||||
// 选择的是商品组,传 good_group_id
|
||||
submitData.good_group_ids = String(form.goods_id)
|
||||
}
|
||||
} else {
|
||||
// 编辑模式:传 discount_good_id
|
||||
submitData.discount_good_id = String(form.id)
|
||||
|
||||
// 根据 goods_type 判断传 good_id 还是 good_group_id
|
||||
if (form.goods_type === 'product') {
|
||||
submitData.good_id = String(form.goods_id)
|
||||
} else if (form.goods_type === 'product_group') {
|
||||
submitData.good_group_id = String(form.goods_id)
|
||||
} else {
|
||||
// 其他类型默认使用 good_id
|
||||
submitData.good_id = String(form.goods_id)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('提交商品关联数据:', submitData)
|
||||
|
||||
let res
|
||||
if (dialogType.value === 'add') {
|
||||
res = await addDiscountGoods(submitData)
|
||||
} else {
|
||||
res = await updateDiscountGoods(submitData)
|
||||
}
|
||||
|
||||
console.log('提交响应:', res.data)
|
||||
if (res.data.code === 200) {
|
||||
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
|
||||
dialogVisible.value = false
|
||||
fetchGoodsList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error)
|
||||
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||
if (!valid) return
|
||||
try {
|
||||
const submitData = {
|
||||
code_id: String(form.code_id),
|
||||
discount_good_id: String(form.id)
|
||||
}
|
||||
if (form.goods_type === 'product_group') {
|
||||
submitData.good_group_id = String(form.goods_id)
|
||||
} else {
|
||||
submitData.good_id = String(form.goods_id)
|
||||
}
|
||||
|
||||
const res = await updateDiscountGoods(submitData)
|
||||
if (res.data.code === 200) {
|
||||
ElMessage.success('修改成功')
|
||||
dialogVisible.value = false
|
||||
fetchGoodsList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error)
|
||||
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 新增提交:根据勾选构建 good_ids / good_group_ids(逗号分隔)
|
||||
const submitAdd = async (goodIds, goodGroupIds) => {
|
||||
try {
|
||||
const submitData = { code_id: String(form.code_id) }
|
||||
if (goodIds.length > 0) {
|
||||
submitData.good_ids = goodIds.join(',')
|
||||
}
|
||||
if (goodGroupIds.length > 0) {
|
||||
submitData.good_group_ids = goodGroupIds.join(',')
|
||||
}
|
||||
|
||||
console.log('提交商品关联数据:', submitData)
|
||||
const res = await addDiscountGoods(submitData)
|
||||
if (res.data.code === 200) {
|
||||
ElMessage.success('新增成功')
|
||||
dialogVisible.value = false
|
||||
fetchGoodsList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error)
|
||||
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchVoucherListOptions()
|
||||
@@ -798,6 +823,57 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 折叠层级选择器样式 */
|
||||
.goods-tree-wrapper {
|
||||
width: 100%;
|
||||
border: 1px solid #e1e8ed;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-tree-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #fafbfc;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.tree-summary b {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.goods-tree {
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tree-node-label {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.tree-node-id {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tree-node-price {
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user