style: 优化布局和交互(Loading/空状态/骨架屏)
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<!-- 商品参数管理主对话框 -->
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
title="商品参数管理"
|
||||
width="900px"
|
||||
@update:model-value="$emit('update:visible', $event)"
|
||||
>
|
||||
<div class="filter-section" style="border: none; padding: 0 0 16px 0;">
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAddParameter">
|
||||
<el-icon><Plus /></el-icon>新增参数
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchParameterList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="paramLoading"
|
||||
:data="parameterList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="参数ID" width="80" />
|
||||
<el-table-column prop="name" label="参数名称" min-width="120" />
|
||||
<el-table-column prop="type" label="参数类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getArgTypeTag(row.type)">{{ getArgTypeText(row.type) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数值配置" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.type === 'number'">
|
||||
<span class="number-config">步进: {{ row.step || '-' }} | 范围: {{ row.min ?? '-' }} ~ {{ row.max ?? '-' }}</span>
|
||||
</template>
|
||||
<span v-else class="text-muted">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEditParameter(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleViewParamValues(row)">查看参数值</el-button>
|
||||
<el-button type="danger" link @click="handleDeleteParameter(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="暂无参数数据" :image-size="80" />
|
||||
</template>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 商品参数表单对话框 -->
|
||||
<el-dialog
|
||||
v-model="paramFormDialogVisible"
|
||||
:title="paramFormType === 'add' ? '新增商品参数' : '编辑商品参数'"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form ref="paramFormRef" :model="paramForm" :rules="paramRules" label-width="120px">
|
||||
<el-form-item label="参数名称" prop="arg_name">
|
||||
<el-input v-model="paramForm.arg_name" placeholder="请输入参数名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数类型" prop="arg_type">
|
||||
<el-radio-group v-model="paramForm.arg_type">
|
||||
<el-radio label="string">字符串</el-radio>
|
||||
<el-radio label="number">数字</el-radio>
|
||||
<el-radio label="select">选择</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否必选" prop="must">
|
||||
<el-switch v-model="paramForm.must" :active-value="true" :inactive-value="false" active-text="必选" inactive-text="可选" />
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">权限控制</el-divider>
|
||||
<el-form-item label="允许单独购买">
|
||||
<el-switch v-model="paramForm.user_add" active-text="允许" inactive-text="不允许" />
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 4px">购买后是否允许单独追加购买</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户组优惠">
|
||||
<el-switch v-model="paramForm.use_user_group_discount" active-text="允许" inactive-text="不允许" />
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 4px">是否允许使用用户组优惠</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户优惠">
|
||||
<el-switch v-model="paramForm.use_user_discount" active-text="允许" inactive-text="不允许" />
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 4px">是否允许使用用户优惠(代金券与优惠码)</div>
|
||||
</el-form-item>
|
||||
<template v-if="paramForm.arg_type === 'number'">
|
||||
<el-divider content-position="left">数值参数配置</el-divider>
|
||||
<el-form-item label="步进值" prop="arg_step">
|
||||
<el-input-number v-model="paramForm.arg_step" :min="1" placeholder="步进值" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最小值" prop="arg_min">
|
||||
<el-input-number v-model="paramForm.arg_min" placeholder="最小值" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最大值" prop="arg_max">
|
||||
<el-input-number v-model="paramForm.arg_max" placeholder="最大值" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="paramFormDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitParamForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 参数值管理对话框 -->
|
||||
<el-dialog v-model="paramValuesDialogVisible" title="参数值管理" width="800px" append-to-body>
|
||||
<div class="values-header">
|
||||
<span>参数:{{ currentParam?.name }}</span>
|
||||
<el-button type="primary" @click="handleAddParamValue">
|
||||
<el-icon><Plus /></el-icon>添加参数值
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="paramValuesLoading"
|
||||
:data="paramValueList"
|
||||
style="width: 100%; margin-top: 20px"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="值ID" width="80" />
|
||||
<el-table-column prop="name" label="值名称" min-width="120" />
|
||||
<el-table-column label="值/范围" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<template v-if="currentParam?.type === 'select'">{{ row.value || '-' }}</template>
|
||||
<template v-else-if="currentParam?.type === 'number'">
|
||||
<el-tag size="small" type="info">{{ getRangeTypeText(row.rangeType) }} {{ row.range }}</el-tag>
|
||||
</template>
|
||||
<template v-else>{{ row.value || '-' }}</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="index" label="排序" width="80" />
|
||||
<el-table-column label="价格" width="100">
|
||||
<template #default="{ row }">¥{{ (row.price / 100).toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEditParamValue(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDeleteParamValue(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="暂无参数值" :image-size="60" />
|
||||
</template>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 参数值表单对话框 -->
|
||||
<el-dialog
|
||||
v-model="paramValueFormDialogVisible"
|
||||
:title="paramValueFormType === 'add' ? '添加参数值' : '编辑参数值'"
|
||||
width="550px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form ref="paramValueFormRef" :model="paramValueForm" :rules="paramValueRules" label-width="120px">
|
||||
<el-form-item label="值名称" prop="attr_name">
|
||||
<el-input v-model="paramValueForm.attr_name" placeholder="请输入值名称" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="currentParam?.type === 'select'" label="参数值" prop="attr_value">
|
||||
<el-input v-model="paramValueForm.attr_value" placeholder="请输入参数值" />
|
||||
</el-form-item>
|
||||
<template v-if="currentParam?.type === 'number'">
|
||||
<el-divider content-position="left">数值范围配置(phase)</el-divider>
|
||||
<el-form-item label="范围类型" prop="range_type">
|
||||
<el-select v-model="paramValueForm.range_type" placeholder="请选择范围类型" style="width: 100%">
|
||||
<el-option label="小于等于 (before)" value="before" />
|
||||
<el-option label="大于等于 (after)" value="after" />
|
||||
<el-option label="等于 (equal)" value="equal" />
|
||||
</el-select>
|
||||
<div class="form-tip">before: 数值 ≤ phase 时匹配 | after: 数值 ≥ phase 时匹配</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="阈值" prop="attr_range">
|
||||
<el-input-number v-model="paramValueForm.attr_range" :min="0" placeholder="范围阈值" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item label="排序索引" prop="index">
|
||||
<el-input-number v-model="paramValueForm.index" :min="0" placeholder="排序索引" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格" prop="attr_price">
|
||||
<el-input-number v-model="paramValueForm.attr_price" :min="0" placeholder="请输入价格" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="paramValueFormDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitParamValueForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getProductParameterList,
|
||||
getProductParameterDetail,
|
||||
createProductParameter,
|
||||
updateProductParameter,
|
||||
deleteProductParameter,
|
||||
addProductParameterValue,
|
||||
updateProductParameterValue,
|
||||
deleteProductParameterValue
|
||||
} from '@/api/admin/product'
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
goodId: { type: Number, default: null }
|
||||
})
|
||||
const emit = defineEmits(['update:visible'])
|
||||
|
||||
const paramLoading = ref(false)
|
||||
const parameterList = ref([])
|
||||
|
||||
const paramFormDialogVisible = ref(false)
|
||||
const paramFormType = ref('add')
|
||||
const paramFormRef = ref(null)
|
||||
const paramForm = reactive({
|
||||
arg_id: undefined,
|
||||
arg_name: '',
|
||||
arg_type: 'string',
|
||||
must: false,
|
||||
arg_step: 1,
|
||||
arg_min: 0,
|
||||
arg_max: 100,
|
||||
user_add: false,
|
||||
use_user_group_discount: false,
|
||||
use_user_discount: false
|
||||
})
|
||||
const paramRules = {
|
||||
arg_name: [{ required: true, message: '请输入参数名称', trigger: 'blur' }],
|
||||
arg_type: [{ required: true, message: '请选择参数类型', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const paramValuesDialogVisible = ref(false)
|
||||
const paramValuesLoading = ref(false)
|
||||
const paramValueList = ref([])
|
||||
const currentParam = ref(null)
|
||||
|
||||
const paramValueFormDialogVisible = ref(false)
|
||||
const paramValueFormType = ref('add')
|
||||
const paramValueFormRef = ref(null)
|
||||
const paramValueForm = reactive({
|
||||
attr_id: undefined,
|
||||
attr_name: '',
|
||||
attr_value: '',
|
||||
attr_price: 0,
|
||||
index: 0,
|
||||
attr_range: 0,
|
||||
range_type: 'equal'
|
||||
})
|
||||
const paramValueRules = {
|
||||
attr_name: [{ required: true, message: '请输入值名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const getArgTypeText = (type) => {
|
||||
const typeMap = { 'string': '字符串', 'number': '数字', 'select': '选择' }
|
||||
return typeMap[type] || '未知'
|
||||
}
|
||||
const getArgTypeTag = (type) => {
|
||||
const tagMap = { 'string': 'primary', 'number': 'success', 'select': 'warning' }
|
||||
return tagMap[type] || 'info'
|
||||
}
|
||||
const getRangeTypeText = (type) => {
|
||||
const typeMap = { 'after': '大于 >', 'before': '小于 <', 'equal': '等于 =' }
|
||||
return typeMap[type] || type || '-'
|
||||
}
|
||||
|
||||
const fetchParameterList = async () => {
|
||||
if (!props.goodId) return
|
||||
paramLoading.value = true
|
||||
try {
|
||||
const res = await getProductParameterList({ good_id: props.goodId })
|
||||
if (res.data.code === 200) {
|
||||
parameterList.value = res.data.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('获取参数列表失败')
|
||||
} finally {
|
||||
paramLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddParameter = () => {
|
||||
paramFormType.value = 'add'
|
||||
paramFormDialogVisible.value = true
|
||||
Object.assign(paramForm, { arg_id: undefined, arg_name: '', arg_type: 'string', must: false, arg_step: 1, arg_min: 0, arg_max: 100, user_add: false, use_user_group_discount: false, use_user_discount: false })
|
||||
nextTick(() => { paramFormRef.value?.resetFields() })
|
||||
}
|
||||
|
||||
const handleEditParameter = (row) => {
|
||||
paramFormType.value = 'edit'
|
||||
paramFormDialogVisible.value = true
|
||||
Object.assign(paramForm, { arg_id: row.id, arg_name: row.name, arg_type: row.type, must: row.must || false, arg_step: row.step || 1, arg_min: row.min || 0, arg_max: row.max || 100, user_add: row.userAdd ?? row.user_add ?? false, use_user_group_discount: row.useUserGroupDiscount ?? row.use_user_group_discount ?? false, use_user_discount: row.useUserDiscount ?? row.use_user_discount ?? false })
|
||||
}
|
||||
|
||||
const handleDeleteParameter = (row) => {
|
||||
ElMessageBox.confirm(`确认删除参数 ${row.name} 吗?`, '警告', {
|
||||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await deleteProductParameter({ good_id: props.goodId, arg_id: row.id })
|
||||
if (res.data.code === 200) { ElMessage.success('删除成功'); fetchParameterList() }
|
||||
} catch (error) { ElMessage.error('删除失败') }
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
const submitParamForm = () => {
|
||||
paramFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const submitData = { good_id: Number(props.goodId), arg_name: paramForm.arg_name, arg_type: paramForm.arg_type, must: paramForm.must === true, user_add: paramForm.user_add === true, use_user_group_discount: paramForm.use_user_group_discount === true, use_user_discount: paramForm.use_user_discount === true }
|
||||
if (paramForm.arg_type === 'number') {
|
||||
submitData.arg_step = Number(paramForm.arg_step)
|
||||
submitData.arg_min = Number(paramForm.arg_min)
|
||||
submitData.arg_max = Number(paramForm.arg_max)
|
||||
}
|
||||
if (paramFormType.value === 'edit') submitData.arg_id = paramForm.arg_id
|
||||
const res = paramFormType.value === 'add' ? await createProductParameter(submitData) : await updateProductParameter(submitData)
|
||||
if (res.data.code === 200) { ElMessage.success(paramFormType.value === 'add' ? '新增成功' : '修改成功'); paramFormDialogVisible.value = false; fetchParameterList() }
|
||||
} catch (error) { ElMessage.error(error.response?.data?.message || '操作失败') }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleViewParamValues = (row) => {
|
||||
currentParam.value = row
|
||||
paramValuesDialogVisible.value = true
|
||||
fetchParamValuesList()
|
||||
}
|
||||
|
||||
const fetchParamValuesList = async () => {
|
||||
if (!props.goodId || !currentParam.value) return
|
||||
paramValuesLoading.value = true
|
||||
try {
|
||||
const res = await getProductParameterDetail({ good_id: props.goodId, arg_id: currentParam.value.id })
|
||||
if (res.data.code === 200) paramValueList.value = res.data.data.attrs || []
|
||||
} catch (error) { ElMessage.error('获取参数值列表失败') }
|
||||
finally { paramValuesLoading.value = false }
|
||||
}
|
||||
|
||||
const handleAddParamValue = () => {
|
||||
paramValueFormType.value = 'add'
|
||||
paramValueFormDialogVisible.value = true
|
||||
Object.assign(paramValueForm, { attr_id: undefined, attr_name: '', attr_value: '', attr_price: 0, index: 0, attr_range: 0, range_type: 'equal' })
|
||||
nextTick(() => { paramValueFormRef.value?.resetFields() })
|
||||
}
|
||||
|
||||
const handleEditParamValue = (row) => {
|
||||
paramValueFormType.value = 'edit'
|
||||
paramValueFormDialogVisible.value = true
|
||||
Object.assign(paramValueForm, { attr_id: row.id, attr_name: row.name, attr_value: row.value || '', attr_price: row.price / 100 || 0, index: row.index || 0, attr_range: row.phase || 0, range_type: row.rangeType || 'equal' })
|
||||
}
|
||||
|
||||
const handleDeleteParamValue = (row) => {
|
||||
ElMessageBox.confirm(`确认删除参数值 ${row.name} 吗?`, '警告', {
|
||||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await deleteProductParameterValue({ good_id: props.goodId, attr_id: row.id })
|
||||
if (res.data.code === 200) { ElMessage.success('删除成功'); fetchParamValuesList() }
|
||||
} catch (error) { ElMessage.error('删除失败') }
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
const submitParamValueForm = () => {
|
||||
paramValueFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const submitData = { good_id: Number(props.goodId), arg_id: Number(currentParam.value.id), attr_name: paramValueForm.attr_name, index: Number(paramValueForm.index), attr_price: Number(paramValueForm.attr_price) }
|
||||
if (currentParam.value.type === 'select') submitData.attr_value = paramValueForm.attr_value
|
||||
if (currentParam.value.type === 'number') { submitData.attr_range = Number(paramValueForm.attr_range); submitData.range_type = paramValueForm.range_type }
|
||||
if (paramValueFormType.value === 'edit') submitData.attr_id = paramValueForm.attr_id
|
||||
const res = paramValueFormType.value === 'add' ? await addProductParameterValue(submitData) : await updateProductParameterValue(submitData)
|
||||
if (res.data.code === 200) { ElMessage.success(paramValueFormType.value === 'add' ? '添加成功' : '修改成功'); paramValueFormDialogVisible.value = false; fetchParamValuesList() }
|
||||
} catch (error) { ElMessage.error(error.response?.data?.message || '操作失败') }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val && props.goodId) fetchParameterList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-section { padding: 0; background: transparent; }
|
||||
.action-bar { display: flex; gap: 12px; flex-shrink: 0; flex-wrap: wrap; align-items: center; }
|
||||
.action-buttons { display: flex; gap: 4px; align-items: center; flex-wrap: nowrap; }
|
||||
.action-buttons .el-button { padding: 4px 8px; }
|
||||
.text-muted { color: #c0c4cc; font-size: 12px; }
|
||||
.number-config { color: #909399; font-size: 13px; }
|
||||
.values-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.dialog-footer { display: flex; justify-content: flex-end; gap: 12px; padding: 0; }
|
||||
.form-tip { font-size: 12px; color: #909399; margin-top: 4px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user