Skip to content

Commit 59c44d9

Browse files
YunaiVgitee-org
authored andcommitted
!82 商品分类管理
Merge pull request !82 from 孔思宇/feat/mall-product-category
2 parents 7b700d8 + b8ce330 commit 59c44d9

File tree

4 files changed

+390
-0
lines changed

4 files changed

+390
-0
lines changed

src/api/mall/product/category.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import request from '@/config/axios'
2+
3+
/**
4+
* 产品分类
5+
*/
6+
export interface CategoryVO {
7+
/**
8+
* 分类编号
9+
*/
10+
id?: number
11+
/**
12+
* 父分类编号
13+
*/
14+
parentId?: number
15+
/**
16+
* 分类名称
17+
*/
18+
name: string
19+
/**
20+
* 分类图片
21+
*/
22+
picUrl: string
23+
/**
24+
* 分类排序
25+
*/
26+
sort?: number
27+
/**
28+
* 分类描述
29+
*/
30+
description?: string
31+
/**
32+
* 开启状态
33+
*/
34+
status: number
35+
}
36+
37+
// 创建商品分类
38+
export const createCategory = (data: CategoryVO) => {
39+
return request.post({ url: '/product/category/create', data })
40+
}
41+
42+
// 更新商品分类
43+
export const updateCategory = (data: CategoryVO) => {
44+
return request.put({ url: '/product/category/update', data })
45+
}
46+
47+
// 删除商品分类
48+
export const deleteCategory = (id: number) => {
49+
return request.delete({ url: `/product/category/delete?id=${id}` })
50+
}
51+
52+
// 获得商品分类
53+
export const getCategory = (id: number) => {
54+
return request.get({ url: `/product/category/get?id=${id}` })
55+
}
56+
57+
// 获得商品分类列表
58+
export const getCategoryList = (params: any) => {
59+
return request.get({ url: '/product/category/list', params })
60+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<template>
2+
<Dialog :title="modelTitle" v-model="modelVisible">
3+
<el-form
4+
ref="formRef"
5+
:model="formData"
6+
:rules="formRules"
7+
label-width="80px"
8+
v-loading="formLoading"
9+
>
10+
<el-form-item label="上级分类" prop="parentId">
11+
<el-tree-select
12+
v-model="formData.parentId"
13+
:data="parentCategoryOptions"
14+
check-strictly
15+
:render-after-expand="false"
16+
placeholder="上级分类"
17+
:props="{ label: 'name', value: 'id' }"
18+
default-expand-all
19+
/>
20+
</el-form-item>
21+
<el-form-item label="分类名称" prop="name">
22+
<el-input v-model="formData.name" placeholder="请输入分类名称" />
23+
</el-form-item>
24+
<el-form-item label="分类图片" prop="picUrl">
25+
<UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" />
26+
<div v-if="formData.parentId === 0" style="font-size: 10px">推荐 200x100 图片分辨率</div>
27+
<div v-else style="font-size: 10px">推荐 100x100 图片分辨率</div>
28+
</el-form-item>
29+
<el-form-item label="分类排序" prop="sort">
30+
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
31+
</el-form-item>
32+
<el-form-item label="开启状态" prop="status">
33+
<el-radio-group v-model="formData.status">
34+
<el-radio
35+
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
36+
:key="dict.value"
37+
:label="parseInt(dict.value)"
38+
>{{ dict.label }}
39+
</el-radio>
40+
</el-radio-group>
41+
</el-form-item>
42+
<el-form-item label="分类描述">
43+
<el-input v-model="formData.description" type="textarea" placeholder="请输入分类描述" />
44+
</el-form-item>
45+
</el-form>
46+
<template #footer>
47+
<div class="dialog-footer">
48+
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
49+
<el-button @click="modelVisible = false">取 消</el-button>
50+
</div>
51+
</template>
52+
</Dialog>
53+
</template>
54+
<script setup lang="ts">
55+
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
56+
import * as ProductCategoryApi from '@/api/mall/product/category'
57+
import { handleTree } from '@/utils/tree'
58+
59+
const { t } = useI18n() // 国际化
60+
const message = useMessage() // 消息弹窗
61+
62+
const modelVisible = ref(false) // 弹窗的是否展示
63+
const modelTitle = ref('') // 弹窗的标题
64+
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
65+
const formType = ref('') // 表单的类型:create - 新增;update - 修改
66+
67+
const defaultFormData: ProductCategoryApi.CategoryVO = {
68+
id: undefined,
69+
name: '',
70+
picUrl: '',
71+
status: 0,
72+
description: ''
73+
}
74+
const formData = ref({ ...defaultFormData })
75+
const formRules = reactive({
76+
parentId: [{ required: true, message: '请选择上级分类', trigger: 'blur' }],
77+
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
78+
picUrl: [{ required: true, message: '分类图片不能为空', trigger: 'blur' }],
79+
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }],
80+
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
81+
})
82+
const formRef = ref() // 表单 Ref
83+
84+
const list = ref([])
85+
const parentCategoryOptions = ref<any[]>([])
86+
87+
/** 打开弹窗 */
88+
const openModal = async (type: string, id?: number) => {
89+
modelVisible.value = true
90+
modelTitle.value = t('action.' + type)
91+
formType.value = type
92+
resetForm()
93+
getList()
94+
// 修改时,设置数据
95+
if (id) {
96+
formLoading.value = true
97+
try {
98+
formData.value = await ProductCategoryApi.getCategory(id)
99+
} finally {
100+
formLoading.value = false
101+
}
102+
}
103+
}
104+
defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
105+
106+
// 获取上级分类选项
107+
const getList = async () => {
108+
try {
109+
const data = await ProductCategoryApi.getCategoryList({})
110+
list.value = data
111+
const tree = handleTree(data, 'id', 'parentId')
112+
const menu = { id: 0, name: '顶级分类', children: tree }
113+
parentCategoryOptions.value = [menu]
114+
} finally {
115+
}
116+
}
117+
118+
/** 提交表单 */
119+
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
120+
const submitForm = async () => {
121+
// 校验表单
122+
if (!formRef) return
123+
const valid = await formRef.value.validate()
124+
if (!valid) return
125+
// 提交请求
126+
formLoading.value = true
127+
try {
128+
const data = formData.value as ProductCategoryApi.CategoryVO
129+
if (formType.value === 'create') {
130+
await ProductCategoryApi.createCategory(data)
131+
message.success(t('common.createSuccess'))
132+
} else {
133+
await ProductCategoryApi.updateCategory(data)
134+
message.success(t('common.updateSuccess'))
135+
}
136+
modelVisible.value = false
137+
// 发送操作成功的事件
138+
emit('success')
139+
} finally {
140+
formLoading.value = false
141+
}
142+
}
143+
144+
/** 重置表单 */
145+
const resetForm = () => {
146+
formData.value = { ...defaultFormData }
147+
formRef.value?.resetFields()
148+
}
149+
</script>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<template>
2+
<content-wrap>
3+
<!-- 搜索工作栏 -->
4+
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
5+
<el-form-item label="分类名称" prop="name">
6+
<el-input
7+
v-model="queryParams.name"
8+
placeholder="请输入分类名称"
9+
clearable
10+
@keyup.enter="handleQuery"
11+
/>
12+
</el-form-item>
13+
<el-form-item>
14+
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
15+
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
16+
<el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']">
17+
<Icon icon="ep:plus" class="mr-5px" /> 新增
18+
</el-button>
19+
</el-form-item>
20+
</el-form>
21+
22+
<!-- 列表 -->
23+
<el-table v-loading="loading" :data="list" align="center" default-expand-all row-key="id">
24+
<el-table-column label="分类名称" prop="name" sortable />
25+
<el-table-column label="分类图片" align="center" prop="picUrl">
26+
<template #default="scope">
27+
<img
28+
v-if="scope.row.picUrl"
29+
:src="scope.row.picUrl"
30+
alt="分类图片"
31+
style="height: 100px"
32+
/>
33+
</template>
34+
</el-table-column>
35+
<el-table-column label="分类排序" align="center" prop="sort" />
36+
<el-table-column label="开启状态" align="center" prop="status">
37+
<template #default="scope">
38+
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
39+
</template>
40+
</el-table-column>
41+
<el-table-column
42+
label="创建时间"
43+
align="center"
44+
prop="createTime"
45+
width="180"
46+
:formatter="dateFormatter"
47+
/>
48+
<el-table-column label="操作" align="center">
49+
<template #default="scope">
50+
<el-button
51+
link
52+
type="primary"
53+
@click="openModal('update', scope.row.id)"
54+
v-hasPermi="['infra:config:update']"
55+
>
56+
编辑
57+
</el-button>
58+
<el-button
59+
link
60+
type="danger"
61+
@click="handleDelete(scope.row.id)"
62+
v-hasPermi="['infra:config:delete']"
63+
>
64+
删除
65+
</el-button>
66+
</template>
67+
</el-table-column>
68+
</el-table>
69+
</content-wrap>
70+
71+
<!-- 表单弹窗:添加/修改 -->
72+
<config-form ref="modalRef" @success="getList" />
73+
</template>
74+
<script setup lang="ts" name="Config">
75+
import { DICT_TYPE } from '@/utils/dict'
76+
import { dateFormatter } from '@/utils/formatTime'
77+
import * as ProductCategoryApi from '@/api/mall/product/category'
78+
import ConfigForm from './form.vue'
79+
import { handleTree } from '@/utils/tree'
80+
81+
const message = useMessage() // 消息弹窗
82+
const { t } = useI18n() // 国际化
83+
84+
const loading = ref(true) // 列表的加载中
85+
const list = ref<any[]>([]) // 列表的数据
86+
const queryParams = reactive({
87+
name: undefined
88+
})
89+
const queryFormRef = ref() // 搜索的表单
90+
91+
/** 查询参数列表 */
92+
const getList = async () => {
93+
loading.value = true
94+
try {
95+
const data = await ProductCategoryApi.getCategoryList(queryParams)
96+
list.value = handleTree(data, 'id', 'parentId')
97+
console.info(list)
98+
} finally {
99+
loading.value = false
100+
}
101+
}
102+
103+
/** 搜索按钮操作 */
104+
const handleQuery = () => {
105+
getList()
106+
}
107+
108+
/** 重置按钮操作 */
109+
const resetQuery = () => {
110+
queryFormRef.value.resetFields()
111+
handleQuery()
112+
}
113+
114+
/** 添加/修改操作 */
115+
const modalRef = ref()
116+
const openModal = (type: string, id?: number) => {
117+
modalRef.value.openModal(type, id)
118+
}
119+
120+
/** 删除按钮操作 */
121+
const handleDelete = async (id: number) => {
122+
try {
123+
// 删除的二次确认
124+
await message.delConfirm()
125+
// 发起删除
126+
await ProductCategoryApi.deleteCategory(id)
127+
message.success(t('common.delSuccess'))
128+
// 刷新列表
129+
await getList()
130+
} catch {}
131+
}
132+
133+
/** 初始化 **/
134+
onMounted(() => {
135+
getList()
136+
})
137+
</script>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export const parseTime = (time) => {
2+
if (!time) {
3+
return null
4+
}
5+
const format = '{y}-{m}-{d} {h}:{i}:{s}'
6+
let date
7+
if (typeof time === 'object') {
8+
date = time
9+
} else {
10+
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
11+
time = parseInt(time)
12+
} else if (typeof time === 'string') {
13+
time = time
14+
.replace(new RegExp(/-/gm), '/')
15+
.replace('T', ' ')
16+
.replace(new RegExp(/\.[\d]{3}/gm), '')
17+
}
18+
if (typeof time === 'number' && time.toString().length === 10) {
19+
time = time * 1000
20+
}
21+
date = new Date(time)
22+
}
23+
const formatObj = {
24+
y: date.getFullYear(),
25+
m: date.getMonth() + 1,
26+
d: date.getDate(),
27+
h: date.getHours(),
28+
i: date.getMinutes(),
29+
s: date.getSeconds(),
30+
a: date.getDay()
31+
}
32+
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
33+
let value = formatObj[key]
34+
// Note: getDay() returns 0 on Sunday
35+
if (key === 'a') {
36+
return ['日', '一', '二', '三', '四', '五', '六'][value]
37+
}
38+
if (result.length > 0 && value < 10) {
39+
value = '0' + value
40+
}
41+
return value || 0
42+
})
43+
return time_str
44+
}

0 commit comments

Comments
 (0)