feat: 类目联调
This commit is contained in:
parent
51bbd8c170
commit
3ff3977ed0
@ -1,9 +1,22 @@
|
|||||||
const login = {
|
const login = {
|
||||||
/**
|
/**
|
||||||
* 商品列表
|
* 首页商品管理
|
||||||
*/
|
*/
|
||||||
getCommodityList: ['/getCommodityList'], // 获取商品列表
|
getHomeCommodityList: ['/front/manager/list'], // 获取首页商品数据
|
||||||
delCommodity: ['/delCommodity'] // 删除商品
|
sortHomeCommodity: ['/front/manager/sort'], // 首页排序
|
||||||
|
/**
|
||||||
|
* 类目管理
|
||||||
|
*/
|
||||||
|
getCategoryList: ['/category/list'], // 获取类目列表
|
||||||
|
sortCategory: ['/category/sort'], // 排序
|
||||||
|
updateCategory: ['/category/insertOrUpdate'], // 插入或更新
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品管理
|
||||||
|
*/
|
||||||
|
getCommodityList: ['/product/list'], // 获取商品列表
|
||||||
|
getCommodityDetail: ['/product/detail'], // 获取商品详情
|
||||||
|
addOrUpdateCommodity: [''] // 修改商品详情
|
||||||
}
|
}
|
||||||
|
|
||||||
export default login
|
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',
|
name: 'errorpage',
|
||||||
component: () => import('@/views/home/errorpage.vue'),
|
component: () => import('@/views/home/errorpage.vue'),
|
||||||
meta: { title: '错误页', hidden: true }
|
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">
|
<div id="category-tree" class="p-2 bg-white">
|
||||||
<p>类目展示:</p>
|
<p>类目展示:</p>
|
||||||
<el-tree
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
style="max-width: 600px"
|
style="max-width: 600px"
|
||||||
:data="dataSource"
|
:props="defaultProps"
|
||||||
node-key="id"
|
node-key="id"
|
||||||
draggable
|
draggable
|
||||||
default-expand-all
|
:render-after-expand="false"
|
||||||
|
check-strictly
|
||||||
|
:load="handleLoadNode"
|
||||||
|
lazy
|
||||||
|
highlight-current
|
||||||
|
@node-click="onClick"
|
||||||
:expand-on-click-node="false"
|
:expand-on-click-node="false"
|
||||||
@node-drag-start="handleDragStart"
|
@node-drag-start="handleDragStart"
|
||||||
@node-drag-enter="handleDragEnter"
|
@node-drag-enter="handleDragEnter"
|
||||||
@ -17,7 +23,7 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ node, data }">
|
<template #default="{ node, data }">
|
||||||
<div class="category-tree-node">
|
<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
|
<el-input
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
v-else
|
v-else
|
||||||
@ -28,7 +34,7 @@
|
|||||||
@blur="handleInputCancel(node, data)"
|
@blur="handleInputCancel(node, data)"
|
||||||
/>
|
/>
|
||||||
<div>
|
<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="primary" link @click="append(data)" size="small"> 添加 </el-button>
|
||||||
<el-button type="danger" size="small" link @click="remove(node, data)">
|
<el-button type="danger" size="small" link @click="remove(node, data)">
|
||||||
删除
|
删除
|
||||||
@ -43,7 +49,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ElButton } from 'element-plus'
|
import { ElButton } from 'element-plus'
|
||||||
import type { RenderContentContext } from 'element-plus'
|
import type { RenderContentContext } from 'element-plus'
|
||||||
import { dataSource } from './mock'
|
|
||||||
import {
|
import {
|
||||||
handleDragStart,
|
handleDragStart,
|
||||||
handleDragEnter,
|
handleDragEnter,
|
||||||
@ -55,17 +60,21 @@ import {
|
|||||||
|
|
||||||
interface Tree {
|
interface Tree {
|
||||||
id: number
|
id: number
|
||||||
label: string
|
name: string
|
||||||
children?: Tree[]
|
children?: Tree[]
|
||||||
}
|
}
|
||||||
type Node = RenderContentContext['node']
|
type Node = RenderContentContext['node']
|
||||||
type Data = RenderContentContext['data']
|
type Data = RenderContentContext['data']
|
||||||
|
|
||||||
const curId = ref(NaN)
|
const curId = ref(NaN)
|
||||||
|
const curParentId = ref(NaN)
|
||||||
|
// const curId = ref(NaN)
|
||||||
const inputRef = ref()
|
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
|
curId.value = data.id
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
inputRef.value.focus()
|
inputRef.value.focus()
|
||||||
@ -75,11 +84,11 @@ const edit = (data: Data) => {
|
|||||||
// 回车确认
|
// 回车确认
|
||||||
const handleInputConfirm = (data: any) => {
|
const handleInputConfirm = (data: any) => {
|
||||||
if (curId.value === Infinity) {
|
if (curId.value === Infinity) {
|
||||||
// 新增接口
|
api.commodity.updateCategory.post!({ ...data, name: data.label2, parentId: curParentId.value })
|
||||||
} else {
|
} else {
|
||||||
// 修改接口
|
api.commodity.updateCategory.post!({ ...data, name: data.label2 })
|
||||||
}
|
}
|
||||||
data.label = data.label2
|
data.name = data.label2
|
||||||
data.label2 = ''
|
data.label2 = ''
|
||||||
curId.value = NaN
|
curId.value = NaN
|
||||||
}
|
}
|
||||||
@ -92,25 +101,63 @@ const handleInputCancel = (node: Node, data: Data) => {
|
|||||||
curId.value = NaN
|
curId.value = NaN
|
||||||
}
|
}
|
||||||
|
|
||||||
const append = (data: Data) => {
|
// 增加类目
|
||||||
|
const append = async (data: Data) => {
|
||||||
|
const node = treeRef.value.getNode(data.id)
|
||||||
curId.value = Infinity
|
curId.value = Infinity
|
||||||
const newChild = { id: curId.value, label2: 'test', children: [] }
|
curParentId.value = data.id
|
||||||
if (!data.children) {
|
const newChild = { id: curId.value, name: '', label2: 'test', hasChild: false }
|
||||||
data.children = []
|
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)
|
treeRef.value.append(newChild, data)
|
||||||
dataSource.value = [...dataSource.value]
|
node.expanded = true
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
inputRef.value.focus()
|
inputRef.value.focus()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除类目
|
||||||
const remove = (node: Node, data: Data) => {
|
const remove = (node: Node, data: Data) => {
|
||||||
const parent = node.parent
|
const parent = node.parent
|
||||||
const children: Tree[] = parent?.data.children || parent?.data
|
const children: Tree[] = parent?.data.children || parent?.data
|
||||||
const index = children.findIndex((d) => d.id === data.id)
|
const index = children.findIndex((d) => d.id === data.id)
|
||||||
children.splice(index, 1)
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,15 @@ const configData = ref()
|
|||||||
export const initConfig = () => {
|
export const initConfig = () => {
|
||||||
configData.value = pageConfig({
|
configData.value = pageConfig({
|
||||||
search: {
|
search: {
|
||||||
comTitle: { label: '标题', clearable: true },
|
title: { label: '标题', clearable: true },
|
||||||
comCategoryId: { label: '类目', option: [], clearable: true }
|
comCategoryId: { label: '类目', option: [], clearable: true }
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
id: { label: '产品ID' },
|
id: { label: '产品ID' },
|
||||||
comTitle: { label: '标题' },
|
title: { label: '标题' },
|
||||||
sales: { label: '销量' },
|
showSaleCount: { label: '销量' },
|
||||||
price: { label: '价格' },
|
showPromotionPrice: { label: '价格' },
|
||||||
|
showSalePrice: { label: '促销价' },
|
||||||
btn: {
|
btn: {
|
||||||
types: ['primary', 'info', 'warning', 'success', 'danger'],
|
types: ['primary', 'info', 'warning', 'success', 'danger'],
|
||||||
names: ['编辑', '复制', '加入首页', '上下架', '删除'],
|
names: ['编辑', '复制', '加入首页', '上下架', '删除'],
|
||||||
|
|||||||
@ -4,10 +4,11 @@ export const initConfig = () => {
|
|||||||
configData.value = pageConfig({
|
configData.value = pageConfig({
|
||||||
dialog: [
|
dialog: [
|
||||||
{
|
{
|
||||||
comTitle: { label: '商品标题' },
|
title: { label: '商品标题' },
|
||||||
comCategoryId: { label: '类目', slot: 'comCategoryId' },
|
comCategoryId: { label: 'app类目', slot: 'comCategoryId' },
|
||||||
video: { label: '视频', slot: 'video', class: '!w-full' },
|
mainImageUrl: { label: '主图', slot: 'file' },
|
||||||
mainImg: { label: '主图', slot: 'mainImg', class: '!w-full' }
|
videoUrl: { label: '视频', slot: 'file' },
|
||||||
|
subImageUrl: { label: '副图', slot: 'file' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@ -9,11 +9,8 @@
|
|||||||
:data="categoryOptions"
|
:data="categoryOptions"
|
||||||
></el-tree-select>
|
></el-tree-select>
|
||||||
</template>
|
</template>
|
||||||
<template #video>
|
<template #file>
|
||||||
<span>sdfs</span>
|
<resource-upload v-model="fileList" />
|
||||||
</template>
|
|
||||||
<template #mainImg>
|
|
||||||
<p>123</p>
|
|
||||||
</template>
|
</template>
|
||||||
</form-wrap>
|
</form-wrap>
|
||||||
</el-card>
|
</el-card>
|
||||||
@ -72,9 +69,15 @@ import { categoryDataMock, categoryOptionsMock } from './mock'
|
|||||||
import { generateTargetData, useCategoryAddOption, colors, useDrag } from './use-method'
|
import { generateTargetData, useCategoryAddOption, colors, useDrag } from './use-method'
|
||||||
import categoryConfig from './category-config.vue'
|
import categoryConfig from './category-config.vue'
|
||||||
import wanEditor from './editor.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!)
|
const { $dialog, dialog1 } = toRefs(initConfig().value!)
|
||||||
$dialog.value.config = dialog1.value
|
$dialog.value.config = dialog1.value
|
||||||
|
$dialog.value['item-width'] = '100%'
|
||||||
|
|
||||||
const categoryOptions = ref(categoryOptionsMock)
|
const categoryOptions = ref(categoryOptionsMock)
|
||||||
const categoryData = ref(categoryDataMock)
|
const categoryData = ref(categoryDataMock)
|
||||||
@ -88,9 +91,16 @@ console.warn('----- my data is list.value: ', list.value)
|
|||||||
|
|
||||||
const htmlContent = ref('自定义')
|
const htmlContent = ref('自定义')
|
||||||
|
|
||||||
|
const fileList = ref([])
|
||||||
|
|
||||||
|
const onGetResource = (v) => {
|
||||||
|
console.warn('----- my data is v: ', v)
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
console.warn('----- my data is htmlContent: ', htmlContent.value)
|
console.warn('----- my data is htmlContent: ', htmlContent.value)
|
||||||
}
|
}
|
||||||
|
init()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@ -24,21 +24,47 @@
|
|||||||
import { initConfig } from './config'
|
import { initConfig } from './config'
|
||||||
import { useCommodityType } from './use-method'
|
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 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) => {
|
const onDelete = (row: any) => {
|
||||||
handleMessageBox({
|
handleMessageBox({
|
||||||
msg: `是否确认删除吗?`,
|
msg: `是否确认删除吗?`,
|
||||||
success: api.commodity.delCommodity.post!,
|
success: api.commodity.addOrUpdateCommodity.post!,
|
||||||
data: { id: row.id }
|
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(() => {
|
}).then(() => {
|
||||||
table.value.$onGetData(table.value)
|
table.value.$onGetData(table.value)
|
||||||
})
|
})
|
||||||
@ -50,6 +76,7 @@ const { search, table } = handleInit(ConfigData, api.commodity.getCommodityList.
|
|||||||
onAddOrEdit.bind(null, 'edit'),
|
onAddOrEdit.bind(null, 'edit'),
|
||||||
onCopy,
|
onCopy,
|
||||||
onAddHome,
|
onAddHome,
|
||||||
|
onChangeStatus,
|
||||||
onDelete
|
onDelete
|
||||||
])
|
])
|
||||||
const { commodityType, onChangeCommodityType, commodityTypeOptions } = useCommodityType(
|
const { commodityType, onChangeCommodityType, commodityTypeOptions } = useCommodityType(
|
||||||
|
|||||||
@ -37,7 +37,7 @@ const onDelete = (row: any) => {
|
|||||||
|
|
||||||
/** 初始化页面 */
|
/** 初始化页面 */
|
||||||
const ConfigData = initConfig()
|
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'),
|
onAddOrEdit.bind(null, 'edit'),
|
||||||
onCopy,
|
onCopy,
|
||||||
onAddHome,
|
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: {
|
pathname: {
|
||||||
label: '路径',
|
label: '路径',
|
||||||
width: 160,
|
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: {
|
modifyTime: {
|
||||||
label: '修改时间',
|
label: '修改时间',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<search-module :search="search" :table="table">
|
<search-module :search="search" :table="table" @search="onSearch">
|
||||||
<template #btn>
|
<template #btn>
|
||||||
<el-button @click="onAddFolder" type="success">新增文件夹</el-button>
|
<el-button @click="onAddFolder" type="success">新增文件夹</el-button>
|
||||||
<el-button @click="onAddFiles" type="success">添加文件</el-button>
|
<el-button @click="onAddFiles" type="success">添加文件</el-button>
|
||||||
@ -136,7 +136,8 @@ const onAddFolder = () => {
|
|||||||
parentId: search.value.$data.parentId,
|
parentId: search.value.$data.parentId,
|
||||||
defaultSort: 0,
|
defaultSort: 0,
|
||||||
type: 'file',
|
type: 'file',
|
||||||
fileName: ''
|
fileName: '',
|
||||||
|
modifyTime: new Date()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +315,16 @@ const initDraggable = () => {
|
|||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSearch = () => {
|
||||||
|
table.value.$onGetData(
|
||||||
|
table.value,
|
||||||
|
1,
|
||||||
|
search.value.$data.fileName
|
||||||
|
? { parentId: null, fileName: search.value.$data.fileName }
|
||||||
|
: search.value.$data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 监听表格数据变化,自动重新初始化拖拽
|
// 监听表格数据变化,自动重新初始化拖拽
|
||||||
watch(
|
watch(
|
||||||
() => table.value.$data,
|
() => table.value.$data,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user