feat: 类目联调
This commit is contained in:
parent
51bbd8c170
commit
3ff3977ed0
@ -1,9 +1,22 @@
|
||||
const login = {
|
||||
/**
|
||||
* 商品列表
|
||||
* 首页商品管理
|
||||
*/
|
||||
getCommodityList: ['/getCommodityList'], // 获取商品列表
|
||||
delCommodity: ['/delCommodity'] // 删除商品
|
||||
getHomeCommodityList: ['/front/manager/list'], // 获取首页商品数据
|
||||
sortHomeCommodity: ['/front/manager/sort'], // 首页排序
|
||||
/**
|
||||
* 类目管理
|
||||
*/
|
||||
getCategoryList: ['/category/list'], // 获取类目列表
|
||||
sortCategory: ['/category/sort'], // 排序
|
||||
updateCategory: ['/category/insertOrUpdate'], // 插入或更新
|
||||
|
||||
/**
|
||||
* 商品管理
|
||||
*/
|
||||
getCommodityList: ['/product/list'], // 获取商品列表
|
||||
getCommodityDetail: ['/product/detail'], // 获取商品详情
|
||||
addOrUpdateCommodity: [''] // 修改商品详情
|
||||
}
|
||||
|
||||
export default login
|
||||
|
||||
150
src/components/ResourceUpload/index.vue
Normal file
150
src/components/ResourceUpload/index.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="resource-upload">
|
||||
<div class="resource-upload__wrapper">
|
||||
<el-button type="primary" class="relative !text-white">
|
||||
选择文件
|
||||
<input
|
||||
ref="inputRef"
|
||||
class="absolute top-0 left-0 cursor-pointer opacity-0"
|
||||
type="file"
|
||||
multiple
|
||||
accept="image/*,video/*"
|
||||
@change="onFilesChange"
|
||||
/>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="fileList.length" class="image-list">
|
||||
<div v-for="item in fileList" :key="item.id" class="image-item">
|
||||
<img
|
||||
v-if="item.fileType === 'image'"
|
||||
:src="item.url"
|
||||
class="image-item-media"
|
||||
alt="preview"
|
||||
@click="handlePictureCardPreview(item)"
|
||||
/>
|
||||
<video
|
||||
v-else
|
||||
:src="item.url"
|
||||
class="image-item-media"
|
||||
@click="handlePictureCardPreview(item)"
|
||||
/>
|
||||
<div class="meta">
|
||||
<span class="name" :title="item.name">{{ item.name }}</span>
|
||||
<el-button link type="danger" size="small" @click="remove(item.id)">移除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showReviewImgDialog" @close="onDialogClose">
|
||||
<img w-full v-if="reviewImg.fileType === 'image'" :src="reviewImg.url" alt="Preview Image" />
|
||||
<video v-else ref="reviewVideoRef" :src="reviewImg.url" autoplay />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface PreviewItem {
|
||||
id: number
|
||||
name: string
|
||||
url: string
|
||||
fileType: 'image' | 'video'
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', files: File[]): void
|
||||
}>()
|
||||
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
const fileList = ref<PreviewItem[]>([])
|
||||
|
||||
const formData = ref([])
|
||||
|
||||
const onFilesChange = (e: Event) => {
|
||||
const input = e.target as HTMLInputElement
|
||||
const files = Array.from(input.files || [])
|
||||
if (!files.length) return
|
||||
files.forEach((file: any) => {
|
||||
const url = URL.createObjectURL(file)
|
||||
formData.value.push(file)
|
||||
const fileType: 'image' | 'video' = file.type.startsWith('image') ? 'image' : 'video'
|
||||
fileList.value.push({ id: Math.random(), name: file.name, url, fileType })
|
||||
})
|
||||
// reset input so selecting the same files again will still trigger change
|
||||
if (inputRef.value) inputRef.value.value = ''
|
||||
emit('change', files)
|
||||
}
|
||||
|
||||
const remove = (id: number) => {
|
||||
const idx = fileList.value.findIndex((x) => x.id === id)
|
||||
if (idx === -1) return
|
||||
const [removed] = fileList.value.splice(idx, 1)
|
||||
formData.value.splice(idx, 1)
|
||||
if (removed?.url && objectUrls.has(removed.url)) {
|
||||
URL.revokeObjectURL(removed.url)
|
||||
}
|
||||
}
|
||||
|
||||
const showReviewImgDialog = ref(false)
|
||||
const reviewImg = ref({ url: '', fileType: '' })
|
||||
const handlePictureCardPreview = (item: { url: string; fileType: 'image' | 'video' }) => {
|
||||
reviewImg.value = item
|
||||
showReviewImgDialog.value = true
|
||||
}
|
||||
|
||||
const reviewVideoRef = ref<HTMLVideoElement | null>(null)
|
||||
const onDialogClose = () => {
|
||||
const video = reviewVideoRef.value
|
||||
if (video) {
|
||||
try {
|
||||
video.pause()
|
||||
video.currentTime = 0
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.resource-upload {
|
||||
.image-list {
|
||||
margin-top: 12px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
.image-item-media {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -18,6 +18,12 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
name: 'errorpage',
|
||||
component: () => import('@/views/home/errorpage.vue'),
|
||||
meta: { title: '错误页', hidden: true }
|
||||
},
|
||||
{
|
||||
path: '/goods/detail',
|
||||
name: '/goods/detail',
|
||||
component: () => import('@/views/goods/commodity/detail-dialog/index.vue'),
|
||||
meta: { title: '商品详情', hidden: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -2,11 +2,17 @@
|
||||
<div id="category-tree" class="p-2 bg-white">
|
||||
<p>类目展示:</p>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
style="max-width: 600px"
|
||||
:data="dataSource"
|
||||
:props="defaultProps"
|
||||
node-key="id"
|
||||
draggable
|
||||
default-expand-all
|
||||
:render-after-expand="false"
|
||||
check-strictly
|
||||
:load="handleLoadNode"
|
||||
lazy
|
||||
highlight-current
|
||||
@node-click="onClick"
|
||||
:expand-on-click-node="false"
|
||||
@node-drag-start="handleDragStart"
|
||||
@node-drag-enter="handleDragEnter"
|
||||
@ -17,7 +23,7 @@
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="category-tree-node">
|
||||
<span v-if="curId !== data.id">{{ data.label }}</span>
|
||||
<span v-if="curId !== data.id">{{ data.name }}</span>
|
||||
<el-input
|
||||
ref="inputRef"
|
||||
v-else
|
||||
@ -28,7 +34,7 @@
|
||||
@blur="handleInputCancel(node, data)"
|
||||
/>
|
||||
<div>
|
||||
<el-button type="primary" link @click="edit(data)" size="small">编辑 </el-button>
|
||||
<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-button type="danger" size="small" link @click="remove(node, data)">
|
||||
删除
|
||||
@ -43,7 +49,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { ElButton } from 'element-plus'
|
||||
import type { RenderContentContext } from 'element-plus'
|
||||
import { dataSource } from './mock'
|
||||
import {
|
||||
handleDragStart,
|
||||
handleDragEnter,
|
||||
@ -55,17 +60,21 @@ import {
|
||||
|
||||
interface Tree {
|
||||
id: number
|
||||
label: string
|
||||
name: string
|
||||
children?: Tree[]
|
||||
}
|
||||
type Node = RenderContentContext['node']
|
||||
type Data = RenderContentContext['data']
|
||||
|
||||
const curId = ref(NaN)
|
||||
const curParentId = ref(NaN)
|
||||
// const curId = ref(NaN)
|
||||
const inputRef = ref()
|
||||
const treeRef = ref()
|
||||
|
||||
const edit = (data: Data) => {
|
||||
data.label2 = data.label
|
||||
// 编辑类目名称
|
||||
const onEdit = (data: Data) => {
|
||||
data.label2 = data.name
|
||||
curId.value = data.id
|
||||
nextTick(() => {
|
||||
inputRef.value.focus()
|
||||
@ -75,11 +84,11 @@ const edit = (data: Data) => {
|
||||
// 回车确认
|
||||
const handleInputConfirm = (data: any) => {
|
||||
if (curId.value === Infinity) {
|
||||
// 新增接口
|
||||
api.commodity.updateCategory.post!({ ...data, name: data.label2, parentId: curParentId.value })
|
||||
} else {
|
||||
// 修改接口
|
||||
api.commodity.updateCategory.post!({ ...data, name: data.label2 })
|
||||
}
|
||||
data.label = data.label2
|
||||
data.name = data.label2
|
||||
data.label2 = ''
|
||||
curId.value = NaN
|
||||
}
|
||||
@ -92,25 +101,63 @@ const handleInputCancel = (node: Node, data: Data) => {
|
||||
curId.value = NaN
|
||||
}
|
||||
|
||||
const append = (data: Data) => {
|
||||
// 增加类目
|
||||
const append = async (data: Data) => {
|
||||
const node = treeRef.value.getNode(data.id)
|
||||
curId.value = Infinity
|
||||
const newChild = { id: curId.value, label2: 'test', children: [] }
|
||||
if (!data.children) {
|
||||
data.children = []
|
||||
curParentId.value = data.id
|
||||
const newChild = { id: curId.value, name: '', label2: 'test', hasChild: false }
|
||||
if (!node.expanded) {
|
||||
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
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
data.children.push(newChild)
|
||||
dataSource.value = [...dataSource.value]
|
||||
treeRef.value.append(newChild, data)
|
||||
node.expanded = true
|
||||
nextTick(() => {
|
||||
inputRef.value.focus()
|
||||
})
|
||||
}
|
||||
|
||||
// 删除类目
|
||||
const remove = (node: Node, data: Data) => {
|
||||
const parent = node.parent
|
||||
const children: Tree[] = parent?.data.children || parent?.data
|
||||
const index = children.findIndex((d) => d.id === data.id)
|
||||
children.splice(index, 1)
|
||||
dataSource.value = [...dataSource.value]
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
label: 'name',
|
||||
children: 'children',
|
||||
disabled: 'disabled',
|
||||
isLeaf: (data: any) => {
|
||||
return !data.hasChild
|
||||
}
|
||||
}
|
||||
|
||||
// 获取类目列表
|
||||
const handleLoadNode = (node, resolve) => {
|
||||
api.commodity.getCategoryList.post!<any>({ parentId: node.data.id || 0 }).then((res) => {
|
||||
resolve(
|
||||
res.data.map((item: any) => ({
|
||||
...item,
|
||||
children: item.hasChild ? [] : undefined
|
||||
}))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const onClick = (data) => {
|
||||
console.warn('----- my data is data111: ', data)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -2,14 +2,15 @@ const configData = ref()
|
||||
export const initConfig = () => {
|
||||
configData.value = pageConfig({
|
||||
search: {
|
||||
comTitle: { label: '标题', clearable: true },
|
||||
title: { label: '标题', clearable: true },
|
||||
comCategoryId: { label: '类目', option: [], clearable: true }
|
||||
},
|
||||
table: {
|
||||
id: { label: '产品ID' },
|
||||
comTitle: { label: '标题' },
|
||||
sales: { label: '销量' },
|
||||
price: { label: '价格' },
|
||||
title: { label: '标题' },
|
||||
showSaleCount: { label: '销量' },
|
||||
showPromotionPrice: { label: '价格' },
|
||||
showSalePrice: { label: '促销价' },
|
||||
btn: {
|
||||
types: ['primary', 'info', 'warning', 'success', 'danger'],
|
||||
names: ['编辑', '复制', '加入首页', '上下架', '删除'],
|
||||
|
||||
@ -4,10 +4,11 @@ export const initConfig = () => {
|
||||
configData.value = pageConfig({
|
||||
dialog: [
|
||||
{
|
||||
comTitle: { label: '商品标题' },
|
||||
comCategoryId: { label: '类目', slot: 'comCategoryId' },
|
||||
video: { label: '视频', slot: 'video', class: '!w-full' },
|
||||
mainImg: { label: '主图', slot: 'mainImg', class: '!w-full' }
|
||||
title: { label: '商品标题' },
|
||||
comCategoryId: { label: 'app类目', slot: 'comCategoryId' },
|
||||
mainImageUrl: { label: '主图', slot: 'file' },
|
||||
videoUrl: { label: '视频', slot: 'file' },
|
||||
subImageUrl: { label: '副图', slot: 'file' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@ -9,11 +9,8 @@
|
||||
:data="categoryOptions"
|
||||
></el-tree-select>
|
||||
</template>
|
||||
<template #video>
|
||||
<span>sdfs</span>
|
||||
</template>
|
||||
<template #mainImg>
|
||||
<p>123</p>
|
||||
<template #file>
|
||||
<resource-upload v-model="fileList" />
|
||||
</template>
|
||||
</form-wrap>
|
||||
</el-card>
|
||||
@ -72,9 +69,15 @@ import { categoryDataMock, categoryOptionsMock } from './mock'
|
||||
import { generateTargetData, useCategoryAddOption, colors, useDrag } from './use-method'
|
||||
import categoryConfig from './category-config.vue'
|
||||
import wanEditor from './editor.vue'
|
||||
import resourceUpload from '@/components/ResourceUpload/index.vue'
|
||||
const route = useRoute()
|
||||
const init = async () => {
|
||||
await api.commodity.getCommodityDetail.post!({ productId: route.query.id })
|
||||
}
|
||||
|
||||
const { $dialog, dialog1 } = toRefs(initConfig().value!)
|
||||
$dialog.value.config = dialog1.value
|
||||
$dialog.value['item-width'] = '100%'
|
||||
|
||||
const categoryOptions = ref(categoryOptionsMock)
|
||||
const categoryData = ref(categoryDataMock)
|
||||
@ -88,9 +91,16 @@ console.warn('----- my data is list.value: ', list.value)
|
||||
|
||||
const htmlContent = ref('自定义')
|
||||
|
||||
const fileList = ref([])
|
||||
|
||||
const onGetResource = (v) => {
|
||||
console.warn('----- my data is v: ', v)
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
console.warn('----- my data is htmlContent: ', htmlContent.value)
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@ -24,21 +24,47 @@
|
||||
import { initConfig } from './config'
|
||||
import { useCommodityType } from './use-method'
|
||||
|
||||
const router = useRouter()
|
||||
/** 新增&编辑 */
|
||||
const onAddOrEdit = (type: string, row: any) => {}
|
||||
const onAddOrEdit = (type: string, row: any) => {
|
||||
router.push({ name: '/goods/detail', query: { type, id: row.id } })
|
||||
}
|
||||
|
||||
/** 复制 */
|
||||
const onCopy = (row: any) => {}
|
||||
|
||||
// 加入首页
|
||||
const onAddHome = (row: any) => {}
|
||||
const onAddHome = (row: any) => {
|
||||
handleMessageBox({
|
||||
msg: `是否加入首页?`,
|
||||
success: api.commodity.addOrUpdateCommodity.post!,
|
||||
data: { id: row.id, frontPage: 1 }
|
||||
}).then(() => {
|
||||
table.value.$onGetData(table.value)
|
||||
})
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onDelete = (row: any) => {
|
||||
handleMessageBox({
|
||||
msg: `是否确认删除吗?`,
|
||||
success: api.commodity.delCommodity.post!,
|
||||
data: { id: row.id }
|
||||
success: api.commodity.addOrUpdateCommodity.post!,
|
||||
data: { id: row.id, status: 'delete' }
|
||||
}).then(() => {
|
||||
table.value.$onGetData(table.value)
|
||||
})
|
||||
}
|
||||
|
||||
// 改变上下架
|
||||
const onChangeStatus = (row: any) => {
|
||||
const type = row.status === 'online' ? '下架' : '上架'
|
||||
handleMessageBox({
|
||||
msg: `是否确认${type}吗?`,
|
||||
success: api.commodity.addOrUpdateCommodity.post!,
|
||||
data: {
|
||||
id: row.id,
|
||||
status: row.status === 'online' ? 'down' : 'online'
|
||||
}
|
||||
}).then(() => {
|
||||
table.value.$onGetData(table.value)
|
||||
})
|
||||
@ -50,6 +76,7 @@ const { search, table } = handleInit(ConfigData, api.commodity.getCommodityList.
|
||||
onAddOrEdit.bind(null, 'edit'),
|
||||
onCopy,
|
||||
onAddHome,
|
||||
onChangeStatus,
|
||||
onDelete
|
||||
])
|
||||
const { commodityType, onChangeCommodityType, commodityTypeOptions } = useCommodityType(
|
||||
|
||||
@ -37,7 +37,7 @@ const onDelete = (row: any) => {
|
||||
|
||||
/** 初始化页面 */
|
||||
const ConfigData = initConfig()
|
||||
const { search, table } = handleInit(ConfigData, api.commodity.getCommodityList.post, [
|
||||
const { search, table } = handleInit(ConfigData, api.commodity.getHomeCommodityList.post, [
|
||||
onAddOrEdit.bind(null, 'edit'),
|
||||
onCopy,
|
||||
onAddHome,
|
||||
|
||||
@ -1,131 +0,0 @@
|
||||
<template>
|
||||
<div id="category-tree" class="p-2 bg-white">
|
||||
<p>资源展示:</p>
|
||||
<el-tree
|
||||
style="max-width: 600px"
|
||||
:data="transformedDataSource"
|
||||
node-key="id"
|
||||
draggable
|
||||
show-checkbox
|
||||
:default-expanded-keys="[2, 3]"
|
||||
:default-checked-keys="[5]"
|
||||
default-expand-all
|
||||
:expand-on-click-node="false"
|
||||
@node-drag-start="handleDragStart"
|
||||
@node-drag-enter="handleDragEnter"
|
||||
@node-drag-leave="handleDragLeave"
|
||||
@node-drag-over="handleDragOver"
|
||||
@node-drag-end="handleDragEnd"
|
||||
@node-drop="handleDrop"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="category-tree-node">
|
||||
<span v-if="curId !== data.id">{{ data.fileName }}</span>
|
||||
<el-input
|
||||
ref="inputRef"
|
||||
v-else
|
||||
v-model="data.label2"
|
||||
size="small"
|
||||
class="w-40"
|
||||
@keyup.enter="handleInputConfirm(data)"
|
||||
@blur="handleInputCancel(node, data)"
|
||||
/>
|
||||
<div>
|
||||
<el-button type="primary" link @click="edit(data)" size="small">编辑 </el-button>
|
||||
<el-button type="primary" link @click="append(data)" size="small"> 添加 </el-button>
|
||||
<el-button type="danger" size="small" link @click="remove(node, data)">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElButton } from 'element-plus'
|
||||
import type { RenderContentContext } from 'element-plus'
|
||||
import { transformedDataSource } from './mock'
|
||||
import {
|
||||
handleDragStart,
|
||||
handleDragEnter,
|
||||
handleDragOver,
|
||||
handleDragLeave,
|
||||
handleDragEnd,
|
||||
handleDrop
|
||||
} from './use-drag'
|
||||
|
||||
interface Tree {
|
||||
id: number
|
||||
label: string
|
||||
children?: Tree[]
|
||||
}
|
||||
type Node = RenderContentContext['node']
|
||||
type Data = RenderContentContext['data']
|
||||
|
||||
const curId = ref(NaN)
|
||||
const inputRef = ref()
|
||||
|
||||
const edit = (data: Data) => {
|
||||
data.label2 = data.label
|
||||
curId.value = data.id
|
||||
nextTick(() => {
|
||||
inputRef.value.focus()
|
||||
})
|
||||
}
|
||||
|
||||
// 回车确认
|
||||
const handleInputConfirm = (data: any) => {
|
||||
if (curId.value === Infinity) {
|
||||
// 新增接口
|
||||
} else {
|
||||
// 修改接口
|
||||
}
|
||||
data.label = data.label2
|
||||
data.label2 = ''
|
||||
curId.value = NaN
|
||||
}
|
||||
|
||||
// 失焦
|
||||
const handleInputCancel = (node: Node, data: Data) => {
|
||||
if (data.id === Infinity) {
|
||||
remove(node, data)
|
||||
}
|
||||
curId.value = NaN
|
||||
}
|
||||
|
||||
const append = (data: Data) => {
|
||||
curId.value = Infinity
|
||||
const newChild = { id: curId.value, label2: 'test', children: [] }
|
||||
if (!data.children) {
|
||||
data.children = []
|
||||
}
|
||||
data.children.push(newChild)
|
||||
transformedDataSource.value = [...transformedDataSource.value]
|
||||
nextTick(() => {
|
||||
inputRef.value.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const remove = (node: Node, data: Data) => {
|
||||
const parent = node.parent
|
||||
const children: Tree[] = parent?.data.children || parent?.data
|
||||
const index = children.findIndex((d) => d.id === data.id)
|
||||
children.splice(index, 1)
|
||||
transformedDataSource.value = [...transformedDataSource.value]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#category-tree {
|
||||
.category-tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,162 +0,0 @@
|
||||
interface Tree {
|
||||
id: number
|
||||
resourceUrl: string
|
||||
type: string
|
||||
fileName: string
|
||||
defaultSort: number
|
||||
children?: Tree[]
|
||||
}
|
||||
interface OriginDataItem {
|
||||
id: number
|
||||
resourceUrl: string
|
||||
fileName: string
|
||||
type: string
|
||||
defaultSort: number
|
||||
parentId?: number
|
||||
}
|
||||
|
||||
export const originData: OriginDataItem[] = [
|
||||
{
|
||||
id: 12,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 10,
|
||||
defaultSort: 2
|
||||
},
|
||||
{
|
||||
id: 78,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 77,
|
||||
defaultSort: 1
|
||||
},
|
||||
{
|
||||
id: 80,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 78,
|
||||
defaultSort: 4
|
||||
},
|
||||
{
|
||||
id: 82,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 81,
|
||||
defaultSort: 1
|
||||
},
|
||||
{
|
||||
id: 84,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 83,
|
||||
defaultSort: 1
|
||||
},
|
||||
{
|
||||
id: 86,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 85,
|
||||
defaultSort: 1
|
||||
},
|
||||
{
|
||||
id: 88,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 87,
|
||||
defaultSort: 1
|
||||
},
|
||||
{
|
||||
id: 90,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 89,
|
||||
defaultSort: 1
|
||||
},
|
||||
{
|
||||
id: 92,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 91,
|
||||
defaultSort: 1
|
||||
},
|
||||
{
|
||||
id: 94,
|
||||
resourceUrl: 'bbb',
|
||||
type: 'image',
|
||||
fileName: '张三',
|
||||
parentId: 93,
|
||||
defaultSort: 1
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 将 originData 转换为 dataSource 的树形结构
|
||||
* @param data 原始数据数组
|
||||
* @returns 转换后的树形结构数据
|
||||
*/
|
||||
export function transformToTreeData(data: OriginDataItem[]): Tree[] {
|
||||
// 创建映射表,用于快速查找
|
||||
const itemMap = new Map<number, Tree>()
|
||||
const result: Tree[] = []
|
||||
|
||||
// 首先创建所有节点
|
||||
data.forEach((item) => {
|
||||
itemMap.set(item.id, {
|
||||
id: item.id,
|
||||
resourceUrl: item.resourceUrl,
|
||||
type: item.type,
|
||||
fileName: item.fileName,
|
||||
defaultSort: item.defaultSort,
|
||||
children: []
|
||||
})
|
||||
})
|
||||
|
||||
// 构建树形结构
|
||||
data.forEach((item) => {
|
||||
const treeNode = itemMap.get(item.id)!
|
||||
|
||||
if (item.parentId) {
|
||||
const parentNode = itemMap.get(item.parentId)
|
||||
if (parentNode) {
|
||||
parentNode.children!.push(treeNode)
|
||||
} else {
|
||||
// 如果找不到父节点,作为根节点处理
|
||||
result.push(treeNode)
|
||||
}
|
||||
} else {
|
||||
// 没有父节点,作为根节点
|
||||
result.push(treeNode)
|
||||
}
|
||||
})
|
||||
|
||||
// 递归排序函数
|
||||
function sortChildren(node: Tree) {
|
||||
if (node.children && node.children.length > 0) {
|
||||
// 重新排列 children
|
||||
node.children = node.children.sort((a, b) => a.defaultSort - b.defaultSort)
|
||||
|
||||
// 递归处理子节点
|
||||
node.children.forEach((child) => sortChildren(child))
|
||||
}
|
||||
}
|
||||
|
||||
// 重新排列根节点
|
||||
const sortedResult = result.sort((a, b) => a.defaultSort - b.defaultSort)
|
||||
|
||||
// 递归排序所有子节点
|
||||
sortedResult.forEach((node) => sortChildren(node))
|
||||
|
||||
return sortedResult
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
export const transformedDataSource = ref(transformToTreeData(originData))
|
||||
@ -1,39 +0,0 @@
|
||||
import type { DragEvents } from 'element-plus/es/components/tree/src/model/useDragNode'
|
||||
import type { NodeDropType, RenderContentContext } from 'element-plus'
|
||||
|
||||
type Node = RenderContentContext['node']
|
||||
|
||||
// 开始拖拽
|
||||
export const handleDragStart = (node: Node, ev: DragEvents) => {
|
||||
console.log('drag start', node)
|
||||
}
|
||||
// 拖拽进入其他节点时触发的事件
|
||||
export const handleDragEnter = (draggingNode: Node, dropNode: Node, ev: DragEvents) => {
|
||||
console.log('tree drag enter:', dropNode.label)
|
||||
}
|
||||
// 拖拽离开某个节点时触发的事件
|
||||
export const handleDragLeave = (draggingNode: Node, dropNode: Node, ev: DragEvents) => {
|
||||
console.log('tree drag leave:', dropNode.label)
|
||||
}
|
||||
// 在拖拽节点时触发的事件(类似浏览器的 mouseover 事件)
|
||||
export const handleDragOver = (draggingNode: Node, dropNode: Node, ev: DragEvents) => {
|
||||
console.log('tree drag over:', dropNode.label)
|
||||
}
|
||||
// 拖拽结束时(可能未成功)触发的事件
|
||||
export const handleDragEnd = (
|
||||
draggingNode: Node,
|
||||
dropNode: Node,
|
||||
dropType: NodeDropType,
|
||||
ev: DragEvents
|
||||
) => {
|
||||
console.log('tree drag end:', dropNode && dropNode.label, dropType)
|
||||
}
|
||||
// 拖拽成功完成时触发的事件
|
||||
export const handleDrop = (
|
||||
draggingNode: Node,
|
||||
dropNode: Node,
|
||||
dropType: NodeDropType,
|
||||
ev: DragEvents
|
||||
) => {
|
||||
console.log('tree drop:', dropNode.label, dropType)
|
||||
}
|
||||
@ -11,7 +11,7 @@ export const initConfig = () => {
|
||||
pathname: {
|
||||
label: '路径',
|
||||
width: 160,
|
||||
formatter: (row: any) => row.locations.map((item: any) => item.fileName).join('/')
|
||||
formatter: (row: any) => row.locations?.map((item: any) => item.fileName).join('/')
|
||||
},
|
||||
modifyTime: {
|
||||
label: '修改时间',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-module :search="search" :table="table">
|
||||
<search-module :search="search" :table="table" @search="onSearch">
|
||||
<template #btn>
|
||||
<el-button @click="onAddFolder" type="success">新增文件夹</el-button>
|
||||
<el-button @click="onAddFiles" type="success">添加文件</el-button>
|
||||
@ -136,7 +136,8 @@ const onAddFolder = () => {
|
||||
parentId: search.value.$data.parentId,
|
||||
defaultSort: 0,
|
||||
type: 'file',
|
||||
fileName: ''
|
||||
fileName: '',
|
||||
modifyTime: new Date()
|
||||
})
|
||||
}
|
||||
|
||||
@ -314,6 +315,16 @@ const initDraggable = () => {
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const onSearch = () => {
|
||||
table.value.$onGetData(
|
||||
table.value,
|
||||
1,
|
||||
search.value.$data.fileName
|
||||
? { parentId: null, fileName: search.value.$data.fileName }
|
||||
: search.value.$data
|
||||
)
|
||||
}
|
||||
|
||||
// 监听表格数据变化,自动重新初始化拖拽
|
||||
watch(
|
||||
() => table.value.$data,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user