perf: 类目管理完成

This commit is contained in:
zc 2025-10-29 17:47:47 +08:00
parent a31beb08f2
commit 88b0c436b8
4 changed files with 238 additions and 139 deletions

View File

@ -16,7 +16,7 @@
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.8",
"element-plus": "^2.7.6",
"element-plus": "^2.11.5",
"lz-utils-lib": "^1.0.51",
"pinia": "^2.1.7",
"trans-config": "^0.0.4",

View File

@ -1,6 +1,24 @@
<template>
<div id="category-tree" class="p-2 bg-white">
<p>类目展示</p>
<p>
类目展示
<el-input
v-if="addRootCategory.showInput"
class="w-20 !h-[22px]"
v-model="addRootCategory.inputValue"
ref="addRootCategoryInputRef"
@keyup.enter="onAddRootCategory"
@blur="addRootCategory.showInput = false"
></el-input>
<el-button
v-else
class="w-20 !h-[22px]"
type="primary"
size="small"
@click="onClickAddRootCategory"
>添加根类目</el-button
>
</p>
<el-tree
ref="treeRef"
:props="defaultProps"
@ -19,38 +37,31 @@
<template #default="{ node, data }">
<div class="category-tree-node">
<div class="flex-1">
<span v-if="curId !== data.id">{{ data.name }}</span>
<span v-if="editChildCategoryId !== data.id">{{ data.name }}</span>
<el-input
ref="inputRef"
ref="editChildCategoryInputRef"
v-else
v-model="data.label2"
size="small"
class="w-40"
@keyup.enter="handleInputConfirm(data)"
@blur="treeRef.remove(node)"
@keyup.enter="handleEditChildCategoryConfirm(node, data)"
@blur="editChildCategoryId = NaN"
/>
<template v-if="!data.hasOriginChild && !data.isProperty">
<el-input
ref="propertyTypeInputRef"
class="w-20"
size="small"
v-if="visiblePropertyType[data.id]"
v-model="inputPropertyTypeValue"
@keyup.enter="onAddPropertyType(data)"
@blur="visiblePropertyType[data.id] = false"
></el-input>
<el-button
v-else
type="primary"
size="small"
class="ml-2 !text-[10px] !px-2 !py-1 !pb-0.5 -mt-0.5"
@click="onClickPropertyTypeBtn(data)"
>新增属性</el-button
>
</template>
<template v-if="data.isProperty">
<dl class="flex items-center">
<dt class="text-sm text-[#666] mb-2">{{ data.categoryPropertyName }}</dt>
<dt class="text-sm text-[#666] mb-2">
属性
<span v-if="editPropertyTypeId !== data.id">{{ data.categoryPropertyName }}</span>
<el-input
ref="editPropertyTypeInputRef"
v-else
v-model="data.label2"
size="small"
class="w-40"
@keyup.enter="handleEditPropertyTypeConfirm(node, data)"
@blur="editPropertyTypeId = NaN"
/>
</dt>
<dd class="flex mb-2 flex-wrap">
<div v-for="child in data.vvPropertyValueList" :key="data.id + '-' + child.id">
<el-input
@ -90,6 +101,13 @@
@click="onClickPropertyBtn(data)"
>新增属性值</el-button
>
<el-button
type="warning"
size="small"
class="!text-[10px] !px-2 !py-1 !pb-0.5"
@click="onEditPropertyType(node, data.id)"
>修改属性</el-button
>
<el-button
type="danger"
size="small"
@ -102,8 +120,38 @@
</template>
</div>
<div v-if="!data.isProperty">
<template v-if="!data.hasOriginChild">
<el-input
ref="propertyTypeInputRef"
class="w-20"
size="small"
v-if="visiblePropertyType[data.id]"
v-model="inputPropertyTypeValue"
@keyup.enter="onAddPropertyType(node, data)"
@blur="visiblePropertyType[data.id] = false"
></el-input>
<el-button
v-else
type="success"
link
size="small"
@click="onClickPropertyTypeBtn(data)"
>新增属性</el-button
>
</template>
<el-button type="primary" link @click="onEdit(data)" size="small">编辑 </el-button>
<el-button type="primary" link @click="append(data)" size="small"> 添加 </el-button>
<el-input
ref="addChildCategoryInputRef"
class="w-20"
size="small"
v-if="data.id === addChildCategoryId"
v-model="childCategoryInputValue"
@keyup.enter="handleAddChildCategoryConfirm(node, data)"
@blur="addChildCategoryId = NaN"
></el-input>
<el-button v-else type="warning" link @click="onAppend(node, data)" size="small">
添加子类目
</el-button>
<el-button type="danger" size="small" link @click="onDelete(node)"> 删除 </el-button>
</div>
</div>
@ -117,85 +165,27 @@ import { ElButton, ElMessage } from 'element-plus'
import type { RenderContentContext } from 'element-plus'
import { handleDragStart, handleDropFinish } from './use-drag'
import { handleMessageBox } from '@/utils/page'
import {
treeRef,
onEdit,
onAppend,
handleAddChildCategoryConfirm,
handleEditChildCategoryConfirm,
onDelete,
editChildCategoryId,
addChildCategoryId,
childCategoryInputValue,
editChildCategoryInputRef,
addChildCategoryInputRef,
addRootCategory,
addRootCategoryInputRef,
onClickAddRootCategory,
onAddRootCategory
} from './init-method'
interface Tree {
id: number
name: string
children?: Tree[]
}
type Node = RenderContentContext['node']
type Data = RenderContentContext['data']
const curId = ref(NaN)
const curParentId = ref(NaN)
const inputRef = ref()
const treeRef = ref()
//
const onEdit = (data: Data) => {
data.label2 = data.name
curId.value = data.id
nextTick(() => {
inputRef.value.focus()
})
}
//
const handleInputConfirm = async (data: any) => {
if (curId.value === Infinity) {
await api.commodity.updateCategory.post!({
...data,
name: data.label2,
parentId: curParentId.value
})
} else {
await api.commodity.updateCategory.post!({ ...data, name: data.label2 })
}
ElMessage.success('更新成功')
data.name = data.label2
data.label2 = ''
curId.value = NaN
}
//
const onDelete = async (node: Node) => {
await api.commodity.updateCategory.post!({
id: node.data.id,
isDelete: 1
})
treeRef.value.remove(node)
ElMessage.success('删除成功')
}
//
const append = async (data: Data) => {
const node = treeRef.value.getNode(data.id)
curId.value = Infinity
curParentId.value = data.id
const newChild = { id: curId.value, name: '', label2: 'test', hasChild: false }
if (!node.childNodes.length) {
await api.commodity.getCategoryList.post!<any>({ parentId: data.id || 0 }).then((res) => {
res.data.forEach((item: any) => {
treeRef.value.append(
{
...item,
children: item.hasChild ? [] : undefined
},
data
)
})
})
}
treeRef.value.append(newChild, data)
node.expanded = true
nextTick(() => {
const timer = setTimeout(() => {
clearTimeout(timer)
inputRef.value.focus()
}, 500)
})
}
const defaultProps = {
label: 'name',
children: 'children',
@ -207,6 +197,7 @@ const defaultProps = {
//
const handleLoadNode = (node, resolve) => {
console.warn('----- my data is 222: ', 222)
if (node.data.vvCategoryPropertyDTOList?.length) {
resolve(
node.data.vvCategoryPropertyDTOList.map((item: any) => ({
@ -257,9 +248,8 @@ const onAddPropertyValue = async (data: any = {}) => {
ElMessage.success('新增成功')
}
//
const onAddPropertyType = async (data: any = {}) => {
const node = treeRef.value.getNode(data.id)
//
const onAddPropertyType = async (node: Node, data: Data) => {
if (inputPropertyTypeValue.value) {
const res = await api.commodity.updateCategoryProperty.post!<{
id: number
@ -276,14 +266,8 @@ const onAddPropertyType = async (data: any = {}) => {
vvPropertyValueList: []
}
data.vvCategoryPropertyDTOList.push(newData)
if (!node.childNodes.length) {
data.vvCategoryPropertyDTOList.forEach((item: any) => {
treeRef.value.append({ ...item, hasChild: 0, isProperty: 1 }, data)
})
} else {
treeRef.value.append(newData, data)
}
node.expanded = true
node.loaded = false
node.expand()
visiblePropertyType.value[data.id] = false
inputPropertyTypeValue.value = ''
ElMessage.success('新增成功')
@ -302,6 +286,7 @@ const onDeleteProperty = async (node: Node, id: number) => {
}
const propertyTypeInputRef = ref()
//
const onClickPropertyTypeBtn = (data: any) => {
visiblePropertyType.value[data.id] = true
nextTick(() => {
@ -309,6 +294,7 @@ const onClickPropertyTypeBtn = (data: any) => {
})
}
const propertyValueInputRef = ref()
//
const onClickPropertyBtn = (data: any) => {
visiblePropertyValue.value[data.id] = true
nextTick(() => {
@ -319,6 +305,7 @@ const onClickPropertyBtn = (data: any) => {
const curPropertyValueId = ref(NaN)
const inputEditPropertyValue = ref('')
const editPropertyValueInputRef = ref()
//
const onClickPropertyValue = (data: any) => {
curPropertyValueId.value = data.id
inputEditPropertyValue.value = data.categoryPropertyValue
@ -329,6 +316,7 @@ const onClickPropertyValue = (data: any) => {
}, 500)
})
}
//
const onEditPropertyValue = async (data: any) => {
await api.commodity.updateCategoryPropertyValue.post!({
id: data.id,
@ -339,6 +327,23 @@ const onEditPropertyValue = async (data: any) => {
curPropertyValueId.value = NaN
inputEditPropertyValue.value = ''
}
//
const editPropertyTypeId = ref(NaN)
const editPropertyTypeInputRef = ref()
const onEditPropertyType = (node: Node, id: number) => {
editPropertyTypeId.value = id
nextTick(() => {
editPropertyTypeInputRef.value.input!.focus()
})
}
//
const handleEditPropertyTypeConfirm = async (node: Node, data: any) => {
await api.commodity.updateCategoryProperty.post!({ ...data, categoryPropertyName: data.label2 })
ElMessage.success('编辑成功')
data.categoryPropertyName = data.label2
editPropertyTypeId.value = NaN
}
</script>
<style lang="scss" scoped>

View File

@ -0,0 +1,100 @@
import { ElMessage } from 'element-plus'
import type { RenderContentContext } from 'element-plus'
type Node = RenderContentContext['node']
type Data = RenderContentContext['data']
/**
*
*/
export const editChildCategoryId = ref(NaN)
export const editChildCategoryInputRef = ref()
export const addChildCategoryId = ref(NaN)
export const addChildCategoryInputRef = ref()
export const childCategoryInputValue = ref('')
export const treeRef = ref()
// 编辑子类目名称
export const onEdit = (data: Data) => {
data.label2 = data.name
editChildCategoryId.value = data.id
nextTick(() => {
editChildCategoryInputRef.value.focus()
})
}
// 增加子类目
export const onAppend = async (node: Node, data: Data) => {
addChildCategoryId.value = data.id
nextTick(() => {
addChildCategoryInputRef.value.focus()
})
}
// 增加子类目回车确认
export const handleAddChildCategoryConfirm = async (node: Node, data: any) => {
if (childCategoryInputValue.value) {
await api.commodity.updateCategory.post!<{ id: number }>({
name: childCategoryInputValue.value,
parentId: data.id
})
ElMessage.success('更新成功')
}
node.loaded = false
node.expand()
childCategoryInputValue.value = ''
console.warn('----- my data is node: ', node)
addChildCategoryId.value = NaN
}
// 编辑类目回车确认
export const handleEditChildCategoryConfirm = async (node: Node, data: any) => {
await api.commodity.updateCategory.post!({ ...data, name: data.label2 })
ElMessage.success('更新成功')
data.name = data.label2
editChildCategoryId.value = NaN
}
// 删除类目
export const onDelete = async (node: Node) => {
await api.commodity.updateCategory.post!({
id: node.data.id,
isDelete: 1
})
treeRef.value.remove(node)
ElMessage.success('删除成功')
}
/**
*
*/
export const addRootCategory = reactive({ showInput: false, inputValue: '' })
export const addRootCategoryInputRef = ref()
// 点击添加根类目按钮
export const onClickAddRootCategory = () => {
addRootCategory.showInput = true
nextTick(() => {
addRootCategoryInputRef.value.input!.focus()
})
}
// 添加根类目回车确定
export const onAddRootCategory = async () => {
const newRoot = { parentId: 0, name: addRootCategory.inputValue }
const res = await api.commodity.updateCategory.post!<{ id: number }>(newRoot)
treeRef.value.insertAfter(
{
...newRoot,
children: [],
id: res.data.id,
hasChild: 0,
hasOriginChild: 0,
vvCategoryPropertyDTOList: []
},
treeRef.value.store.root.childNodes.slice(-1)[0]
)
addRootCategory.showInput = false
addRootCategory.inputValue = ''
ElMessage.success('添加成功')
}

View File

@ -246,9 +246,9 @@
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31"
integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
"@element-plus/icons-vue@^2.3.1":
"@element-plus/icons-vue@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz#7e9cb231fb738b2056f33e22c3a29e214b538dcf"
resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz#7e9cb231fb738b2056f33e22c3a29e214b538dcf"
integrity sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==
"@esbuild/aix-ppc64@0.21.5":
@ -743,14 +743,14 @@
resolved "https://registry.yarnpkg.com/@types/event-emitter/-/event-emitter-0.3.5.tgz#ce9b513f72c50dcf0443a12165a93a79ba7a7092"
integrity sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==
"@types/lodash-es@^4.17.6":
"@types/lodash-es@^4.17.12":
version "4.17.12"
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.12.tgz#65f6d1e5f80539aa7cfbfc962de5def0cf4f341b"
resolved "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz#65f6d1e5f80539aa7cfbfc962de5def0cf4f341b"
integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==
dependencies:
"@types/lodash" "*"
"@types/lodash@*", "@types/lodash@^4.14.182":
"@types/lodash@*", "@types/lodash@^4.17.20":
version "4.17.20"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93"
integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==
@ -1772,10 +1772,10 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
dayjs@^1.11.13:
version "1.11.13"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
dayjs@^1.11.18:
version "1.11.18"
resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.18.tgz#835fa712aac52ab9dec8b1494098774ed7070a11"
integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==
de-indent@^1.0.2:
version "1.0.2"
@ -1964,24 +1964,23 @@ electron-to-chromium@^1.5.204:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz#609c29502fd7257b4d721e3446f3ae391a0ca1b3"
integrity sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==
element-plus@^2.7.6:
version "2.11.1"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-2.11.1.tgz#10cb0c149e157d14944d293ea68db114b2303c37"
integrity sha512-weYFIniyNXTAe9vJZnmZpYzurh4TDbdKhBsJwhbzuo0SDZ8PLwHVll0qycJUxc6SLtH+7A9F7dvdDh5CnqeIVA==
element-plus@^2.11.5:
version "2.11.5"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.5.tgz#752c2c4f70e86d615e577686c2f08054860a0902"
integrity sha512-O+bIVHQCjUDm4GiIznDXRoS8ar2TpWLwfOGnN/Aam0VXf5kbuc4SxdKKJdovWNxmxeqbcwjsSZPKgtXNcqys4A==
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.3.1"
"@element-plus/icons-vue" "^2.3.2"
"@floating-ui/dom" "^1.0.1"
"@popperjs/core" "npm:@sxzz/popperjs-es@^2.11.7"
"@types/lodash" "^4.14.182"
"@types/lodash-es" "^4.17.6"
"@types/lodash" "^4.17.20"
"@types/lodash-es" "^4.17.12"
"@vueuse/core" "^9.1.0"
async-validator "^4.2.5"
dayjs "^1.11.13"
escape-html "^1.0.3"
dayjs "^1.11.18"
lodash "^4.17.21"
lodash-es "^4.17.21"
lodash-unified "^1.0.2"
lodash-unified "^1.0.3"
memoize-one "^6.0.0"
normalize-wheel-es "^1.2.0"
@ -2172,11 +2171,6 @@ escalade@^3.2.0:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
escape-html@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@ -3342,9 +3336,9 @@ lodash-es@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash-unified@^1.0.2:
lodash-unified@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/lodash-unified/-/lodash-unified-1.0.3.tgz#80b1eac10ed2eb02ed189f08614a29c27d07c894"
resolved "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz#80b1eac10ed2eb02ed189f08614a29c27d07c894"
integrity sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==
lodash.camelcase@^4.3.0: