From f131a23a52ce248a63982ddbf66227d1e4c4db0f Mon Sep 17 00:00:00 2001 From: 18278715334 <18278715334@163.com> Date: Fri, 9 Jan 2026 15:58:17 +0800 Subject: [PATCH] -f --- components.d.ts | 1 + src/api/index.js | 8 + src/components/admin/ProductFormDialog.vue | 120 ++++++-- src/pages/admin/categories/index.vue | 312 ++++++++++++++++++++- src/pages/api/ApiDebugger.vue | 2 +- 5 files changed, 423 insertions(+), 20 deletions(-) diff --git a/components.d.ts b/components.d.ts index b314e97..4db8f92 100644 --- a/components.d.ts +++ b/components.d.ts @@ -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'] diff --git a/src/api/index.js b/src/api/index.js index 1b98333..c08eb1f 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -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'), diff --git a/src/components/admin/ProductFormDialog.vue b/src/components/admin/ProductFormDialog.vue index e73e8cb..343af36 100644 --- a/src/components/admin/ProductFormDialog.vue +++ b/src/components/admin/ProductFormDialog.vue @@ -31,19 +31,16 @@ placeholder="请输入产品名称" /> - - + - - + clearable + @change="handleCategoryChange" + /> @@ -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) diff --git a/src/pages/admin/categories/index.vue b/src/pages/admin/categories/index.vue index f051583..05cd76e 100644 --- a/src/pages/admin/categories/index.vue +++ b/src/pages/admin/categories/index.vue @@ -40,9 +40,16 @@ - + + + 小类管理 + + + + + + + 新增小类 + + + + + + + + + + + + {{ row.is_enabled ? '启用' : '禁用' }} + + + + + + + {{ row.is_visible ? '显示' : '隐藏' }} + + + + + + + + 编辑 + + + 删除 + + + + + + + + + 关闭 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + + {{ isSubCategoryEdit ? '保存修改' : '创建小类' }} + + + + @@ -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 = [] +}