-f
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -25,6 +25,7 @@ declare module 'vue' {
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
|
||||
@@ -289,6 +289,14 @@ export const productAdminApi = {
|
||||
updateCategory: (id, data) => request.put(`/admin/product-categories/${id}`, data),
|
||||
deleteCategory: (id) => request.delete(`/admin/product-categories/${id}`),
|
||||
|
||||
// 小类管理
|
||||
getSubCategories: (params) => request.get('/admin/sub-categories', { params }),
|
||||
getSubCategoryDetail: (id) => request.get(`/admin/sub-categories/${id}`),
|
||||
createSubCategory: (data) => request.post('/admin/sub-categories', data),
|
||||
updateSubCategory: (id, data) => request.put(`/admin/sub-categories/${id}`, data),
|
||||
deleteSubCategory: (id) => request.delete(`/admin/sub-categories/${id}`),
|
||||
getSubCategoriesByCategory: (categoryId) => request.get(`/admin/product-categories/${categoryId}/sub-categories`),
|
||||
|
||||
// 订阅管理
|
||||
getSubscriptions: (params) => request.get('/admin/subscriptions', { params }),
|
||||
getSubscriptionStats: () => request.get('/admin/subscriptions/stats'),
|
||||
|
||||
@@ -31,19 +31,16 @@
|
||||
placeholder="请输入产品名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品分类" prop="category_id">
|
||||
<el-select
|
||||
v-model="form.category_id"
|
||||
<el-form-item label="产品分类" prop="category_cascader">
|
||||
<el-cascader
|
||||
v-model="form.category_cascader"
|
||||
:options="categoryOptions"
|
||||
:props="cascaderProps"
|
||||
placeholder="选择产品分类"
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
:label="category.name"
|
||||
:value="category.id"
|
||||
/>
|
||||
</el-select>
|
||||
clearable
|
||||
@change="handleCategoryChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="产品类型" prop="is_package">
|
||||
@@ -380,6 +377,39 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
// 子分类数据
|
||||
const subCategories = ref([])
|
||||
|
||||
// 级联选择器配置
|
||||
const cascaderProps = {
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
children: 'children',
|
||||
emitPath: false,
|
||||
checkStrictly: false
|
||||
}
|
||||
|
||||
// 分类选项(包含子分类)
|
||||
const categoryOptions = computed(() => {
|
||||
return props.categories.map(category => {
|
||||
// 查找该分类下的子分类
|
||||
const children = subCategories.value
|
||||
.filter(sub => sub.category_id === category.id)
|
||||
.map(sub => ({
|
||||
id: sub.id,
|
||||
name: sub.name,
|
||||
code: sub.code
|
||||
}))
|
||||
|
||||
return {
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
code: category.code,
|
||||
children: children.length > 0 ? children : undefined
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success'])
|
||||
|
||||
const visible = ref(false)
|
||||
@@ -408,6 +438,8 @@ const form = reactive({
|
||||
description: '',
|
||||
content: '',
|
||||
category_id: '',
|
||||
sub_category_id: '',
|
||||
category_cascader: [], // 用于级联选择器
|
||||
price: 0,
|
||||
cost_price: 0,
|
||||
remark: '',
|
||||
@@ -431,7 +463,7 @@ const rules = {
|
||||
{ required: true, message: '请输入产品名称', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '产品名称长度在 2 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
category_id: [
|
||||
category_cascader: [
|
||||
{ required: true, message: '请选择产品分类', trigger: 'change' }
|
||||
],
|
||||
price: [
|
||||
@@ -442,10 +474,10 @@ const rules = {
|
||||
{ type: 'number', min: 0, message: '成本价不能小于0', trigger: 'blur' }
|
||||
],
|
||||
ui_component_price: [
|
||||
{
|
||||
type: 'number',
|
||||
min: 0,
|
||||
message: 'UI组件价格不能小于0',
|
||||
{
|
||||
type: 'number',
|
||||
min: 0,
|
||||
message: 'UI组件价格不能小于0',
|
||||
trigger: 'blur',
|
||||
validator: (rule, value, callback) => {
|
||||
// 如果是组合包且出售UI组件,则价格必须大于0
|
||||
@@ -492,6 +524,9 @@ const initForm = async () => {
|
||||
// 重置所有状态
|
||||
resetFormState()
|
||||
|
||||
// 加载子分类
|
||||
await loadSubCategories()
|
||||
|
||||
if (props.product) {
|
||||
// 编辑模式
|
||||
await handleEditMode()
|
||||
@@ -525,7 +560,7 @@ const handleEditMode = async () => {
|
||||
form[key] = props.product[key]
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 确保UI组件相关字段正确加载
|
||||
// 如果后端返回的字段名不同,这里可以添加映射
|
||||
if (props.product.sell_ui_component !== undefined) {
|
||||
@@ -535,6 +570,15 @@ const handleEditMode = async () => {
|
||||
form.ui_component_price = props.product.ui_component_price
|
||||
}
|
||||
|
||||
// 设置级联选择器的值
|
||||
if (props.product.sub_category_id) {
|
||||
// 有二级分类,级联选择器设置为二级分类的ID
|
||||
form.category_cascader = props.product.sub_category_id
|
||||
} else if (props.product.category_id) {
|
||||
// 只有一级分类,级联选择器设置为一级分类的ID
|
||||
form.category_cascader = props.product.category_id
|
||||
}
|
||||
|
||||
// 如果是组合包,处理子产品数据
|
||||
if (props.product.is_package) {
|
||||
await handlePackageData()
|
||||
@@ -676,6 +720,38 @@ const loadAvailableProducts = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载子分类
|
||||
const loadSubCategories = async () => {
|
||||
try {
|
||||
const response = await productAdminApi.getSubCategories({ page: 1, page_size: 100 })
|
||||
subCategories.value = response.data?.items || []
|
||||
} catch (error) {
|
||||
console.error('加载子分类失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理分类选择变化
|
||||
const handleCategoryChange = (value) => {
|
||||
// value可能是单个值(选择叶子节点)或数组(选择非叶子节点)
|
||||
// 查找选择的分类是否是二级分类
|
||||
const isSubCategory = subCategories.value.some(sub => sub.id === value)
|
||||
|
||||
if (isSubCategory) {
|
||||
// 选择的是二级分类,需要同时设置category_id和sub_category_id
|
||||
const subCategory = subCategories.value.find(sub => sub.id === value)
|
||||
form.category_id = subCategory.category_id
|
||||
form.sub_category_id = value
|
||||
} else if (value) {
|
||||
// 选择的是一级分类,只设置category_id
|
||||
form.category_id = value
|
||||
form.sub_category_id = undefined
|
||||
} else {
|
||||
// 清空选择
|
||||
form.category_id = ''
|
||||
form.sub_category_id = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 处理组合包搜索
|
||||
const handlePackageSearch = () => {
|
||||
if (packageSearchTimer) {
|
||||
@@ -825,11 +901,19 @@ const handleSubmit = async () => {
|
||||
submitting.value = true
|
||||
|
||||
const submitData = { ...form }
|
||||
|
||||
|
||||
// 移除级联选择器字段
|
||||
delete submitData.category_cascader
|
||||
|
||||
// 确保布尔值正确传递
|
||||
submitData.is_enabled = Boolean(form.is_enabled)
|
||||
submitData.is_visible = Boolean(form.is_visible)
|
||||
|
||||
// 如果sub_category_id为undefined,移除该字段(不传递)
|
||||
if (submitData.sub_category_id === undefined) {
|
||||
delete submitData.sub_category_id
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
// 编辑模式
|
||||
await productAdminApi.updateProduct(props.product.id, submitData)
|
||||
|
||||
@@ -40,9 +40,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="flex gap-2">
|
||||
<el-button
|
||||
type="info"
|
||||
size="small"
|
||||
@click="handleManageSubCategories(row)"
|
||||
>
|
||||
小类管理
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -128,6 +135,137 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 小类管理弹窗 -->
|
||||
<el-dialog
|
||||
v-model="subCategoryDialogVisible"
|
||||
:title="`${selectedCategory?.name || '分类'} - 小类管理`"
|
||||
width="900px"
|
||||
@close="handleCloseSubCategoryDialog"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<el-button type="primary" @click="handleCreateSubCategory">
|
||||
新增小类
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="subCategoryLoading"
|
||||
:data="subCategories"
|
||||
stripe
|
||||
class="w-full"
|
||||
>
|
||||
<el-table-column prop="code" label="小类编号" width="120" />
|
||||
<el-table-column prop="name" label="小类名称" min-width="150" />
|
||||
<el-table-column prop="description" label="小类描述" min-width="200" />
|
||||
<el-table-column prop="sort" label="排序" width="80" />
|
||||
<el-table-column prop="is_enabled" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.is_enabled ? 'success' : 'danger'" size="small">
|
||||
{{ row.is_enabled ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="is_visible" label="展示" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.is_visible ? 'success' : 'warning'" size="small">
|
||||
{{ row.is_visible ? '显示' : '隐藏' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="flex gap-2">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleEditSubCategory(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="handleDeleteSubCategory(row)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<el-button @click="subCategoryDialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 小类表单弹窗 -->
|
||||
<el-dialog
|
||||
v-model="subCategoryFormDialogVisible"
|
||||
:title="isSubCategoryEdit ? '编辑小类' : '新增小类'"
|
||||
width="600px"
|
||||
>
|
||||
<el-form
|
||||
ref="subCategoryFormRef"
|
||||
:model="subCategoryForm"
|
||||
:rules="subCategoryRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="小类编号" prop="code">
|
||||
<el-input
|
||||
v-model="subCategoryForm.code"
|
||||
placeholder="请输入小类编号"
|
||||
:disabled="isSubCategoryEdit"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="小类名称" prop="name">
|
||||
<el-input
|
||||
v-model="subCategoryForm.name"
|
||||
placeholder="请输入小类名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="小类描述" prop="description">
|
||||
<el-input
|
||||
v-model="subCategoryForm.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入小类描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number
|
||||
v-model="subCategoryForm.sort"
|
||||
:min="0"
|
||||
:max="999"
|
||||
placeholder="排序值"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="是否启用" prop="is_enabled">
|
||||
<el-switch v-model="subCategoryForm.is_enabled" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="是否展示" prop="is_visible">
|
||||
<el-switch v-model="subCategoryForm.is_visible" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-3">
|
||||
<el-button @click="handleSubCategoryCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubCategorySubmit" :loading="subCategorySubmitting">
|
||||
{{ isSubCategoryEdit ? '保存修改' : '创建小类' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
</ListPageLayout>
|
||||
</template>
|
||||
@@ -145,6 +283,16 @@ const submitting = ref(false)
|
||||
const editingCategory = ref(null)
|
||||
const formRef = ref(null)
|
||||
|
||||
// 小类管理相关数据
|
||||
const subCategoryDialogVisible = ref(false)
|
||||
const subCategoryFormDialogVisible = ref(false)
|
||||
const subCategoryLoading = ref(false)
|
||||
const subCategories = ref([])
|
||||
const selectedCategory = ref(null)
|
||||
const editingSubCategory = ref(null)
|
||||
const subCategoryFormRef = ref(null)
|
||||
const subCategorySubmitting = ref(false)
|
||||
|
||||
// 表单初始值
|
||||
const initialFormData = {
|
||||
code: '',
|
||||
@@ -173,8 +321,38 @@ const rules = {
|
||||
]
|
||||
}
|
||||
|
||||
// 小类表单初始值
|
||||
const initialSubCategoryFormData = {
|
||||
category_id: '',
|
||||
code: '',
|
||||
name: '',
|
||||
description: '',
|
||||
sort: 0,
|
||||
is_enabled: true,
|
||||
is_visible: true
|
||||
}
|
||||
|
||||
// 小类表单数据
|
||||
const subCategoryForm = reactive({ ...initialSubCategoryFormData })
|
||||
|
||||
// 小类表单验证规则
|
||||
const subCategoryRules = {
|
||||
code: [
|
||||
{ required: true, message: '请输入小类编号', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '小类编号长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '请输入小类名称', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '小类名称长度在 2 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: '请输入小类描述', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const isEdit = computed(() => !!editingCategory.value)
|
||||
const isSubCategoryEdit = computed(() => !!editingSubCategory.value)
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
@@ -299,6 +477,138 @@ const handleCancel = () => {
|
||||
editingCategory.value = null
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 小类管理相关方法
|
||||
|
||||
// 管理小类
|
||||
const handleManageSubCategories = async (category) => {
|
||||
selectedCategory.value = category
|
||||
subCategoryDialogVisible.value = true
|
||||
await loadSubCategories(category.id)
|
||||
}
|
||||
|
||||
// 加载小类列表
|
||||
const loadSubCategories = async (categoryId) => {
|
||||
subCategoryLoading.value = true
|
||||
try {
|
||||
const response = await productAdminApi.getSubCategories({ category_id: categoryId, page: 1, page_size: 100 })
|
||||
subCategories.value = response.data?.items || []
|
||||
} catch (error) {
|
||||
console.error('加载小类失败:', error)
|
||||
ElMessage.error('加载小类失败')
|
||||
} finally {
|
||||
subCategoryLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 新增小类
|
||||
const handleCreateSubCategory = () => {
|
||||
if (!selectedCategory.value) {
|
||||
ElMessage.warning('请先选择分类')
|
||||
return
|
||||
}
|
||||
editingSubCategory.value = null
|
||||
resetSubCategoryForm()
|
||||
subCategoryForm.category_id = selectedCategory.value.id
|
||||
subCategoryFormDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑小类
|
||||
const handleEditSubCategory = (subCategory) => {
|
||||
editingSubCategory.value = { ...subCategory }
|
||||
Object.keys(subCategoryForm).forEach(key => {
|
||||
if (subCategory[key] !== undefined) {
|
||||
subCategoryForm[key] = subCategory[key]
|
||||
}
|
||||
})
|
||||
subCategoryFormDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 删除小类
|
||||
const handleDeleteSubCategory = async (subCategory) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除小类"${subCategory.name}"吗?此操作不可撤销。`,
|
||||
'确认删除',
|
||||
{
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
await productAdminApi.deleteSubCategory(subCategory.id)
|
||||
ElMessage.success('小类删除成功')
|
||||
await loadSubCategories(selectedCategory.value.id)
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除小类失败:', error)
|
||||
ElMessage.error('删除小类失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交小类表单
|
||||
const handleSubCategorySubmit = async () => {
|
||||
if (!subCategoryFormRef.value) return
|
||||
try {
|
||||
await subCategoryFormRef.value.validate()
|
||||
subCategorySubmitting.value = true
|
||||
|
||||
const submitData = { ...subCategoryForm }
|
||||
|
||||
if (isSubCategoryEdit.value) {
|
||||
await productAdminApi.updateSubCategory(editingSubCategory.value.id, submitData)
|
||||
ElMessage.success('小类更新成功')
|
||||
} else {
|
||||
await productAdminApi.createSubCategory(submitData)
|
||||
ElMessage.success('小类创建成功')
|
||||
}
|
||||
|
||||
subCategoryFormDialogVisible.value = false
|
||||
await loadSubCategories(selectedCategory.value.id)
|
||||
} catch (error) {
|
||||
if (error !== false) { // 不是表单验证错误
|
||||
console.error('提交小类失败:', error)
|
||||
ElMessage.error(isSubCategoryEdit.value ? '更新小类失败' : '创建小类失败')
|
||||
}
|
||||
} finally {
|
||||
subCategorySubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置小类表单
|
||||
const resetSubCategoryForm = () => {
|
||||
subCategoryForm.category_id = ''
|
||||
subCategoryForm.code = ''
|
||||
subCategoryForm.name = ''
|
||||
subCategoryForm.description = ''
|
||||
subCategoryForm.sort = 0
|
||||
subCategoryForm.is_enabled = true
|
||||
subCategoryForm.is_visible = true
|
||||
|
||||
// 清除表单验证状态
|
||||
nextTick(() => {
|
||||
if (subCategoryFormRef.value) {
|
||||
subCategoryFormRef.value.clearValidate()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 取消小类操作
|
||||
const handleSubCategoryCancel = () => {
|
||||
subCategoryFormDialogVisible.value = false
|
||||
setTimeout(() => {
|
||||
resetSubCategoryForm()
|
||||
editingSubCategory.value = null
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 关闭小类管理弹窗
|
||||
const handleCloseSubCategoryDialog = () => {
|
||||
selectedCategory.value = null
|
||||
subCategories.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
</label>
|
||||
|
||||
<!-- 图片上传字段(photo_data) -->
|
||||
<div v-if="field.name === 'photo_data' && field.type === 'textarea'" class="space-y-2">
|
||||
<div v-if="field.name === 'photo_data' || field.name === 'vlphoto_data' && field.type === 'textarea'" class="space-y-2">
|
||||
<div class="flex gap-2 mb-2">
|
||||
<el-upload
|
||||
:auto-upload="false"
|
||||
|
||||
Reference in New Issue
Block a user