增加产品分类
This commit is contained in:
parent
0ad1dc31a1
commit
f9db706769
@ -51,3 +51,55 @@ export function deleteProducts(id) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品分类列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getProductsTypesList(params) {
|
||||
return request({
|
||||
url: '/admin/productsTypesList',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加产品分类
|
||||
* @param {Object} data - 分类数据
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function addProductsTypes(data) {
|
||||
return request({
|
||||
url: '/admin/addProductsTypes',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新产品分类
|
||||
* @param {number} id - 分类ID
|
||||
* @param {Object} data - 分类数据
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function updateProductsTypes(id, data) {
|
||||
return request({
|
||||
url: `/admin/editProductsTypes/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除产品分类
|
||||
* @param {number} id - 分类ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function deleteProductsTypes(id) {
|
||||
return request({
|
||||
url: `/admin/deleteProductsTypes/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
:default-active="route.path"
|
||||
>
|
||||
<!-- 菜单标题 -->
|
||||
<h3>{{ isCollapse ? "管理" : currentModule?.title || "子菜单" }}</h3>
|
||||
<h3>{{ isCollapse ? "管理" : asideTitle }}</h3>
|
||||
|
||||
<!-- 无模块时显示提示 -->
|
||||
<el-menu-item v-if="!currentModule" index="/home">
|
||||
@ -248,12 +248,30 @@ const displayMenus = computed(() => {
|
||||
if (!currentModule.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentPath = route.path;
|
||||
// 访问“父级 index 页面”时不展开子菜单;只有进入“子页面”后才展示 children。
|
||||
// 例如:/apps/cms/products(父级)时不展示其 children(如 /apps/cms/products/types)。
|
||||
if (currentPath === currentModule.value.path) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return currentModule.value.children || [];
|
||||
});
|
||||
|
||||
const asideTitle = computed(() => {
|
||||
if (isCollapse.value) return "管理";
|
||||
if (!currentModule.value) return "子菜单";
|
||||
return displayMenus.value.length > 0 ? (currentModule.value.title || "子菜单") : "子菜单";
|
||||
});
|
||||
|
||||
const processMenus = (menus) => {
|
||||
return menus
|
||||
.filter((menu) => {
|
||||
// is_visible 控制“侧边栏是否展示”,不参与动态路由与缓存层面的过滤
|
||||
if (menu.is_visible !== undefined && Number(menu.is_visible) === 0) {
|
||||
return false;
|
||||
}
|
||||
if (menu.path && menu.path.trim() !== "") return true;
|
||||
if (menu.children && menu.children.length > 0) return true;
|
||||
return false;
|
||||
|
||||
@ -1,54 +1,174 @@
|
||||
import { createComponentLoader } from '@/utils/pathResolver';
|
||||
|
||||
function computeFullPath(menuPath, parentPath) {
|
||||
if (!menuPath) return parentPath || '';
|
||||
if (menuPath.startsWith('/')) {
|
||||
return menuPath.replace(/\/+/g, '/');
|
||||
}
|
||||
const base = (parentPath || '').replace(/\/$/, '');
|
||||
return `${base}/${menuPath}`.replace(/\/+/g, '/');
|
||||
}
|
||||
|
||||
/** 将子路由的绝对路径转为相对父布局的路径,供 Vue Router 嵌套使用 */
|
||||
function toRelativeChildPath(parentAbs, childAbs) {
|
||||
const base = (parentAbs || '').replace(/\/$/, '');
|
||||
const target = (childAbs || '').replace(/\/$/, '');
|
||||
if (!target) return '';
|
||||
if (target === base) return '';
|
||||
const prefix = `${base}/`;
|
||||
if (target.startsWith(prefix)) {
|
||||
return target.slice(prefix.length);
|
||||
}
|
||||
// 兜底:取最后一段(菜单 path 配置异常时)
|
||||
const parts = target.split('/').filter(Boolean);
|
||||
return parts.length ? parts[parts.length - 1] : '';
|
||||
}
|
||||
|
||||
function hasPageComponent(menu) {
|
||||
return menu.type === 4 || (menu.component_path && String(menu.component_path).trim() !== '');
|
||||
}
|
||||
|
||||
function resolvePageComponent(menu) {
|
||||
if (menu.type === 4) {
|
||||
return () => import('@/views/onepage/index.vue');
|
||||
}
|
||||
if (menu.component_path && String(menu.component_path).trim() !== '') {
|
||||
return createComponentLoader(menu.component_path);
|
||||
}
|
||||
return () => import('@/views/404/404.vue');
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单子节点 -> 嵌套路由(path 相对 layoutAbsPath)
|
||||
*/
|
||||
function convertNestedMenuChildren(children, layoutAbsPath) {
|
||||
if (!children || children.length === 0) return [];
|
||||
return children.map((child) => nestedMenuToRoute(child, layoutAbsPath));
|
||||
}
|
||||
|
||||
function nestedMenuToRoute(menu, layoutAbsPath) {
|
||||
const childAbs = computeFullPath(menu.path, layoutAbsPath);
|
||||
const relPath = toRelativeChildPath(layoutAbsPath, childAbs);
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const ownPage = hasPageComponent(menu);
|
||||
|
||||
const meta = {
|
||||
title: menu.title,
|
||||
icon: menu.icon,
|
||||
id: menu.id,
|
||||
componentPath: menu.component_path,
|
||||
};
|
||||
|
||||
// 既有自己的页面又有子菜单:套一层 EmptyLayout,避免父页面组件里没有 <router-view> 导致子路由无法渲染
|
||||
if (hasChildren && ownPage) {
|
||||
return {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: `menu_${menu.id}_index`,
|
||||
meta: { ...meta },
|
||||
component: resolvePageComponent(menu),
|
||||
},
|
||||
...convertNestedMenuChildren(menu.children, childAbs),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 纯目录 + 子节点
|
||||
if (hasChildren && !ownPage) {
|
||||
const route = {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: convertNestedMenuChildren(menu.children, childAbs),
|
||||
};
|
||||
const firstChild = menu.children[0];
|
||||
if (firstChild && firstChild.path) {
|
||||
const firstAbs = computeFullPath(firstChild.path, childAbs);
|
||||
const firstRel = toRelativeChildPath(childAbs, firstAbs);
|
||||
if (firstRel) {
|
||||
route.redirect = firstRel;
|
||||
}
|
||||
}
|
||||
return route;
|
||||
}
|
||||
|
||||
// 叶子页面
|
||||
return {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: resolvePageComponent(menu),
|
||||
};
|
||||
}
|
||||
|
||||
// 递归转换嵌套菜单为嵌套路由
|
||||
export function convertMenusToRoutes(menus, parentPath = '') {
|
||||
if (!menus || menus.length === 0) return [];
|
||||
|
||||
return menus.map(menu => {
|
||||
// 拼接完整的路由路径(处理相对路径)
|
||||
const fullPath = menu.path ?
|
||||
(menu.path.startsWith('/') ? menu.path : `${parentPath}/${menu.path}`)
|
||||
return menus.map((menu) => {
|
||||
const fullPath = menu.path
|
||||
? menu.path.startsWith('/')
|
||||
? menu.path.replace(/\/+/g, '/')
|
||||
: `${(parentPath || '').replace(/\/$/, '')}/${menu.path}`.replace(/\/+/g, '/')
|
||||
: '';
|
||||
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const ownPage = hasPageComponent(menu);
|
||||
|
||||
const meta = {
|
||||
title: menu.title,
|
||||
icon: menu.icon,
|
||||
id: menu.id,
|
||||
componentPath: menu.component_path,
|
||||
};
|
||||
|
||||
// 顶层:有页面 + 有子菜单 -> EmptyLayout + 默认子路由 + 相对 path 子路由
|
||||
if (hasChildren && ownPage) {
|
||||
return {
|
||||
path: fullPath || menu.path || '',
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: `menu_${menu.id}_index`,
|
||||
meta: { ...meta },
|
||||
component: resolvePageComponent(menu),
|
||||
},
|
||||
...convertNestedMenuChildren(menu.children, fullPath),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const route = {
|
||||
path: fullPath || menu.path || '',
|
||||
name: `menu_${menu.id}`,
|
||||
meta: {
|
||||
title: menu.title,
|
||||
icon: menu.icon,
|
||||
id: menu.id,
|
||||
componentPath: menu.component_path
|
||||
}
|
||||
meta,
|
||||
};
|
||||
|
||||
// 1. 处理组件加载
|
||||
if (menu.type === 4) {
|
||||
// 单页类型
|
||||
route.component = () => import('@/views/onepage/index.vue');
|
||||
} else if (menu.component_path && menu.component_path.trim() !== '') {
|
||||
// 正常页面
|
||||
route.component = createComponentLoader(menu.component_path);
|
||||
} else if (menu.children && menu.children.length > 0) {
|
||||
// 目录节点:必须给组件,否则父级无法渲染子级
|
||||
} else if (hasChildren) {
|
||||
route.component = () => import('@/views/layouts/EmptyLayout.vue');
|
||||
} else {
|
||||
// 异常:既没路径也没子菜单
|
||||
route.component = () => import('@/views/404/404.vue');
|
||||
}
|
||||
|
||||
// 2. 递归子路由(传递当前完整路径作为父路径)
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
route.children = convertMenusToRoutes(menu.children, fullPath);
|
||||
|
||||
// 目录节点添加重定向,防止点击父菜单页面空白
|
||||
const firstChild = menu.children[0];
|
||||
if (firstChild && firstChild.path) {
|
||||
// 计算第一个子路由的完整路径
|
||||
const childFullPath = firstChild.path.startsWith('/') ?
|
||||
firstChild.path :
|
||||
`${fullPath}/${firstChild.path}`;
|
||||
const childFullPath = firstChild.path.startsWith('/')
|
||||
? firstChild.path
|
||||
: `${fullPath}/${firstChild.path}`;
|
||||
route.redirect = childFullPath;
|
||||
}
|
||||
} else {
|
||||
route.component = () => import('@/views/404/404.vue');
|
||||
}
|
||||
|
||||
return route;
|
||||
|
||||
@ -3,16 +3,16 @@
|
||||
<div class="header-bar">
|
||||
<h2>企业产品管理</h2>
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" @click="handleTypes">
|
||||
<i class="fa-solid fa-layer-group"></i>
|
||||
分类管理
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
添加产品
|
||||
</el-button>
|
||||
<el-button @click="refresh" :loading="loading">
|
||||
<el-icon>
|
||||
<Refresh />
|
||||
</el-icon>
|
||||
<i class="fa-solid fa-refresh"></i>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
@ -96,6 +96,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, Search, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
getProductsList,
|
||||
updateProducts,
|
||||
@ -106,6 +107,8 @@ import EditDialog from './components/edit.vue'
|
||||
// @ts-ignore
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 获取图片完整URL
|
||||
// 接口可能返回:
|
||||
// 1) 绝对地址:http(s)://...
|
||||
@ -244,28 +247,16 @@ const handleDelete = async (row) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 状态变化
|
||||
const handleStatusChange = async (row, val) => {
|
||||
try {
|
||||
const res = await updateProducts(row.id, { status: val })
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('状态更新成功')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '状态更新失败')
|
||||
row.status = val === 1 ? 0 : 1
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新企业产品状态失败:', error)
|
||||
ElMessage.error('更新企业产品状态失败')
|
||||
row.status = val === 1 ? 0 : 1
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑成功回调
|
||||
const handleSuccess = () => {
|
||||
fetchList()
|
||||
}
|
||||
|
||||
// 跳转到产品分类管理页
|
||||
const handleTypes = () => {
|
||||
router.push('/apps/cms/products/types')
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchList()
|
||||
|
||||
211
src/views/apps/cms/products/types/components/edit.vue
Normal file
211
src/views/apps/cms/products/types/components/edit.vue
Normal file
@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="分类名称" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入分类名称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="父级分类" prop="pid">
|
||||
<el-select
|
||||
v-model="formData.pid"
|
||||
placeholder="请选择父级分类"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="顶级分类" :value="0" />
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.id"
|
||||
:label="item.title"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="分类描述" prop="desc">
|
||||
<el-input
|
||||
v-model="formData.desc"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入分类描述(可选)"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" :min="0" :max="999" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
:loading="submitLoading"
|
||||
>
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
addProductsTypes,
|
||||
updateProductsTypes,
|
||||
getProductsTypesList
|
||||
} from '@/api/products'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '添加分类'
|
||||
},
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rowData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success'])
|
||||
|
||||
const visible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const formRef = ref(null)
|
||||
|
||||
const typeOptions = ref([])
|
||||
|
||||
const formData = reactive({
|
||||
title: '',
|
||||
pid: 0,
|
||||
desc: '',
|
||||
sort: 0
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
title: [
|
||||
{ required: true, message: '请输入分类名称', trigger: 'blur' },
|
||||
{ max: 100, message: '长度不超过100个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
formData.title = ''
|
||||
formData.pid = 0
|
||||
formData.desc = ''
|
||||
formData.sort = 0
|
||||
}
|
||||
|
||||
const loadTypeOptions = async () => {
|
||||
try {
|
||||
const res = await getProductsTypesList({ page: 1, limit: 1000, keyword: '' })
|
||||
if (res.code === 200 && Array.isArray(res.data?.list)) {
|
||||
const curId = props.isEdit ? Number(props.rowData?.id ?? 0) : 0
|
||||
typeOptions.value = curId
|
||||
? res.data.list.filter((item) => Number(item.id) !== curId)
|
||||
: res.data.list
|
||||
} else {
|
||||
typeOptions.value = []
|
||||
}
|
||||
} catch (e) {
|
||||
// 下拉选项失败不影响弹窗可用性
|
||||
console.error('加载父级分类失败:', e)
|
||||
typeOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 打开/关闭
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (val) => {
|
||||
visible.value = val
|
||||
if (val) {
|
||||
resetForm()
|
||||
if (props.isEdit && props.rowData) {
|
||||
Object.assign(formData, props.rowData)
|
||||
formData.pid = Number(props.rowData?.pid ?? 0)
|
||||
formData.sort = Number(props.rowData?.sort ?? 0)
|
||||
}
|
||||
await loadTypeOptions()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(visible, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
let res
|
||||
const payload = {
|
||||
title: formData.title,
|
||||
pid: Number(formData.pid ?? 0),
|
||||
desc: formData.desc ?? '',
|
||||
sort: Number(formData.sort ?? 0)
|
||||
}
|
||||
|
||||
if (props.isEdit) {
|
||||
res = await updateProductsTypes(Number(props.rowData?.id), payload)
|
||||
} else {
|
||||
res = await addProductsTypes(payload)
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(props.isEdit ? '更新分类成功' : '添加分类成功')
|
||||
handleClose()
|
||||
emit('success')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('提交分类失败:', e)
|
||||
ElMessage.error('操作失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
240
src/views/apps/cms/products/types/index.vue
Normal file
240
src/views/apps/cms/products/types/index.vue
Normal file
@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="container-box">
|
||||
<div class="header-bar">
|
||||
<h2>企业产品分类管理</h2>
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
添加分类
|
||||
</el-button>
|
||||
<el-button @click="refresh" :loading="loading">
|
||||
<i class="fa-solid fa-refresh"></i>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="请输入分类名称搜索"
|
||||
clearable
|
||||
style="width: 200px; margin-right: 10px"
|
||||
@keyup.enter="handleSearch" />
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon>
|
||||
<Search />
|
||||
</el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="typesList" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||||
<el-table-column prop="title" label="分类名称" min-width="180" align="center" />
|
||||
<el-table-column prop="desc" label="分类描述" min-width="200" align="center" />
|
||||
<el-table-column prop="pid" label="父级ID" width="110" align="center" />
|
||||
<el-table-column prop="sort" label="排序" width="90" align="center" />
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" align="center" />
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">
|
||||
<el-icon>
|
||||
<Edit />
|
||||
</el-icon>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.limit"
|
||||
:page-sizes="[10, 20, 50, 100]" :total="pagination.total" layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange" @current-change="handlePageChange" />
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑组件 -->
|
||||
<EditDialog v-model="dialogVisible" :title="dialogTitle" :is-edit="isEdit" :row-data="currentRow"
|
||||
@success="handleSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, Search, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getProductsTypesList,
|
||||
deleteProductsTypes
|
||||
} from '@/api/products'
|
||||
import EditDialog from './components/edit.vue'
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
keyword: ''
|
||||
})
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 数据列表
|
||||
const typesList = ref([])
|
||||
|
||||
// 对话框
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const isEdit = ref(false)
|
||||
const currentRow = ref({})
|
||||
|
||||
// 获取列表
|
||||
const fetchList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getProductsTypesList({
|
||||
page: pagination.page,
|
||||
limit: pagination.limit,
|
||||
keyword: searchForm.keyword
|
||||
})
|
||||
if (res.code === 200) {
|
||||
typesList.value = res.data.list
|
||||
pagination.total = res.data.total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取企业产品分类列表失败:', error)
|
||||
ElMessage.error('获取企业产品分类列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
searchForm.keyword = ''
|
||||
pagination.page = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const refresh = () => {
|
||||
fetchList()
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handleSizeChange = (val) => {
|
||||
pagination.limit = val
|
||||
fetchList()
|
||||
}
|
||||
|
||||
const handlePageChange = (val) => {
|
||||
pagination.page = val
|
||||
fetchList()
|
||||
}
|
||||
|
||||
// 添加
|
||||
const handleAdd = () => {
|
||||
isEdit.value = false
|
||||
dialogTitle.value = '添加企业产品分类'
|
||||
currentRow.value = {}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row) => {
|
||||
isEdit.value = true
|
||||
dialogTitle.value = '编辑企业产品分类'
|
||||
currentRow.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该企业产品分类吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
const res = await deleteProductsTypes(row.id)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除分类成功')
|
||||
fetchList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除企业产品分类失败:', error)
|
||||
ElMessage.error('删除企业产品分类失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑成功回调
|
||||
const handleSuccess = () => {
|
||||
fetchList()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container-box {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-bar h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -78,6 +78,14 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="是否显示" prop="is_visible">
|
||||
<el-switch
|
||||
v-model="currentMenu.is_visible"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="权限标识" prop="permission">
|
||||
<el-input
|
||||
v-model="currentMenu.permission"
|
||||
@ -109,6 +117,7 @@ interface Menu {
|
||||
icon: string;
|
||||
sort: number;
|
||||
status: 0 | 1;
|
||||
is_visible: 0 | 1;
|
||||
type: 1 | 2 | 3;
|
||||
permission: string;
|
||||
children?: Menu[];
|
||||
@ -152,6 +161,7 @@ const currentMenu = ref<Partial<Menu>>({
|
||||
icon: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
is_visible: 1,
|
||||
type: 1,
|
||||
permission: '',
|
||||
});
|
||||
@ -220,6 +230,7 @@ watch(() => props.menu, (newMenu) => {
|
||||
icon: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
is_visible: 1,
|
||||
type: 1,
|
||||
permission: '',
|
||||
};
|
||||
@ -256,6 +267,7 @@ watch(() => props.visible, (newVisible) => {
|
||||
icon: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
is_visible: 1,
|
||||
type: 1,
|
||||
permission: '',
|
||||
};
|
||||
|
||||
@ -89,6 +89,12 @@
|
||||
align="center"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column prop="is_visible" label="是否显示" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.is_visible === 1 ? 'success' : 'danger'">{{ scope.row.is_visible === 1 ? '显示' : '隐藏' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
|
||||
Loading…
Reference in New Issue
Block a user