feat: 商品联调
This commit is contained in:
parent
3ff3977ed0
commit
5561b8d4bb
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DC信贷系统</title>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@ -10,6 +10,12 @@ const login = {
|
||||
getCategoryList: ['/category/list'], // 获取类目列表
|
||||
sortCategory: ['/category/sort'], // 排序
|
||||
updateCategory: ['/category/insertOrUpdate'], // 插入或更新
|
||||
/**
|
||||
* app类目管理
|
||||
*/
|
||||
getAppCategoryList: ['/app/category/list'], // 获取类目列表
|
||||
sortAppCategory: ['/app/category/sort'], // 排序
|
||||
updateAppCategory: ['/app/category/insertOrUpdate'], // 插入或更新
|
||||
|
||||
/**
|
||||
* 商品管理
|
||||
|
||||
BIN
src/assets/images/login_bg1.png
Normal file
BIN
src/assets/images/login_bg1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
294
src/components/FileExplorerDialog/index.vue
Normal file
294
src/components/FileExplorerDialog/index.vue
Normal file
@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="选择资源" width="70%" :before-close="handleClose">
|
||||
<div class="file-explorer">
|
||||
<!-- 面包屑导航 -->
|
||||
<div class="breadcrumb">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item
|
||||
v-for="(path, index) in currentPathArray"
|
||||
:key="index"
|
||||
:class="{ 'is-link': index < currentPathArray.length - 1 }"
|
||||
@click="navigateTo(index)"
|
||||
>
|
||||
{{ path.name }}
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
|
||||
<!-- 文件网格视图 -->
|
||||
<div class="file-list">
|
||||
<div class="grid-container" v-loading="loading">
|
||||
<div
|
||||
v-for="item in fileList"
|
||||
:key="item.id"
|
||||
class="file-grid-item"
|
||||
:class="{ 'is-selected': item.type === 'file' && selectedFilesMap[item.id] }"
|
||||
@click="handleItemClick(item)"
|
||||
@dblclick="handleItemDblClick(item)"
|
||||
>
|
||||
<!-- 文件夹图标 -->
|
||||
<div class="file-icon" v-if="item.type === 'file'">
|
||||
<el-icon class="directory-icon"><Folder /></el-icon>
|
||||
</div>
|
||||
<!-- 文件图标或缩略图 -->
|
||||
<div class="file-icon" v-else>
|
||||
<!-- 图片文件显示缩略图 -->
|
||||
<img v-if="item.type === 'image'" :src="item.resourceUrl" class="thumbnail" />
|
||||
<video v-if="item.type === 'video'" :src="item.resourceUrl" class="thumbnail"></video>
|
||||
<!-- 文件选择复选框 -->
|
||||
<el-checkbox
|
||||
v-if="['image', 'video'].includes(item.type)"
|
||||
v-model="selectedFilesMap[item.id]"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
<!-- 文件名称 -->
|
||||
<div class="file-name" :title="item.fileName">{{ item.fileName }}</div>
|
||||
<!-- 文件大小 -->
|
||||
<!-- <div class="file-size" v-if="showSize && item.type === 'file'">
|
||||
{{ formatFileSize(item.size) }}
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button
|
||||
class="!text-white"
|
||||
type="primary"
|
||||
@click="confirmSelection"
|
||||
:disabled="selectedFiles.length === 0"
|
||||
>
|
||||
确认选择 ({{ selectedFiles.length }})
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Folder } from '@element-plus/icons-vue'
|
||||
|
||||
interface FileItem {
|
||||
id: number
|
||||
fileName: string
|
||||
type: 'file' | 'image' | 'video'
|
||||
resourceUrl?: string
|
||||
}
|
||||
|
||||
const model = defineModel<boolean>()
|
||||
const initPathArray = defineModel<{ name: string; id: number }[]>('initPathArray')
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'select', files: FileItem[]): void
|
||||
}>()
|
||||
|
||||
// 对话框可见性
|
||||
const dialogVisible = computed({
|
||||
get: () => model.value,
|
||||
set: (value) => (model.value = value)
|
||||
})
|
||||
|
||||
const currentFileId = ref<number>(0) // 当前路径id
|
||||
const currentPathArray = ref<{ name: string; id: number }[]>([]) // 当前路径数组
|
||||
|
||||
// 文件列表
|
||||
const fileList = ref<FileItem[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 选中的文件映射和数组
|
||||
const selectedFilesMap = ref<Record<number, boolean>>({})
|
||||
const selectedFiles = ref<FileItem[]>([])
|
||||
|
||||
// 加载文件列表
|
||||
const loadFileList = async (parentId = 0) => {
|
||||
loading.value = true
|
||||
const result = await api.resource.getResourceList.post!<any>({
|
||||
parentId: parentId
|
||||
}).finally(() => (loading.value = false))
|
||||
fileList.value = result.data.rows
|
||||
// 清除选择
|
||||
selectedFilesMap.value = {}
|
||||
selectedFiles.value = []
|
||||
}
|
||||
|
||||
// 导航到指定路径
|
||||
const navigateTo = (index: number) => {
|
||||
if (index >= currentPathArray.value.length - 1) return
|
||||
currentFileId.value = (currentPathArray.value[index] as any).id
|
||||
currentPathArray.value = currentPathArray.value.slice(0, index + 1)
|
||||
loadFileList(currentFileId.value)
|
||||
}
|
||||
|
||||
// 处理项目点击
|
||||
const handleItemClick = (item: FileItem) => {
|
||||
if (['image', 'video'].includes(item.type)) {
|
||||
selectedFilesMap.value[item.id] = !selectedFilesMap.value[item.id]
|
||||
if (selectedFilesMap.value[item.id]) {
|
||||
selectedFiles.value.push(item)
|
||||
} else {
|
||||
selectedFiles.value = selectedFiles.value.filter((f) => f.id !== item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理项目双击
|
||||
const handleItemDblClick = (item: FileItem) => {
|
||||
if (item.type === 'file') {
|
||||
currentFileId.value = item.id
|
||||
currentPathArray.value.push({ id: item.id, name: item.fileName })
|
||||
loadFileList(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
const confirmSelection = () => {
|
||||
if (selectedFiles.value.length === 0) {
|
||||
return ElMessage.warning('请至少选择一个文件')
|
||||
}
|
||||
emit('select', selectedFiles.value)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
/*
|
||||
// 格式化文件大小
|
||||
const formatFileSize = (size: number) => {
|
||||
if (!size) return '0 B'
|
||||
if (size < 1024) return `${size} B`
|
||||
if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`
|
||||
if (size < 1024 * 1024 * 1024) return `${(size / 1024 / 1024).toFixed(2)} MB`
|
||||
return `${(size / 1024 / 1024 / 1024).toFixed(2)} GB`
|
||||
} */
|
||||
|
||||
// 初始加载
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
currentPathArray.value = initPathArray.value || []
|
||||
currentFileId.value = initPathArray.value![initPathArray.value!.length - 1].id
|
||||
loadFileList(currentFileId.value)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.file-explorer {
|
||||
height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.file-grid-item {
|
||||
width: 120px;
|
||||
height: 150px;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.file-grid-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.file-grid-item.is-selected {
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.directory-icon {
|
||||
font-size: 100px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.file-type-icon {
|
||||
font-size: 50px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.image-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
font-size: 30px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
:deep(.is-link) {
|
||||
cursor: pointer;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 5px;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
65
src/components/FileUploadBtn/index.vue
Normal file
65
src/components/FileUploadBtn/index.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="resource-upload">
|
||||
<div class="resource-upload__wrapper">
|
||||
<el-button type="primary" :size="size" class="relative !text-white">
|
||||
{{ buttonText }}
|
||||
<input
|
||||
ref="inputRef"
|
||||
class="!w-20 !h-8 absolute top-0 left-0 cursor-pointer opacity-0"
|
||||
type="file"
|
||||
:multiple="multiple"
|
||||
:accept="accept"
|
||||
@change="onFilesChange"
|
||||
/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
/** 是否允许多选文件 */
|
||||
multiple?: boolean
|
||||
size?: 'small' | 'normal'
|
||||
/** 接受的文件类型 */
|
||||
accept?: 'image/*,video/*' | 'image/*' | 'video/*'
|
||||
/** 按钮文本 */
|
||||
buttonText?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
multiple: false,
|
||||
accept: 'image/*,video/*',
|
||||
buttonText: '选择文件',
|
||||
size: 'normal'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', files: File[]): void
|
||||
}>()
|
||||
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const onFilesChange = async (e: Event) => {
|
||||
const input = e.target as HTMLInputElement
|
||||
const files = Array.from(input.files || [])
|
||||
if (!files.length) return
|
||||
|
||||
// reset input so selecting the same files again will still trigger change
|
||||
if (inputRef.value) inputRef.value.value = ''
|
||||
const form = new FormData()
|
||||
// 关键:一次性把所有文件塞到同一个键 'files'
|
||||
files.forEach((f) => {
|
||||
form.append('files', f)
|
||||
})
|
||||
const res = await api.resource.uploadFile.post!<any>(form)
|
||||
const result = res.data.map((item: any) => ({
|
||||
fileName: item.srcFileName,
|
||||
resourceUrl: item.url,
|
||||
type: item.srcFileName.endsWith('.mp4') ? 'video' : 'image'
|
||||
}))
|
||||
emit('change', result)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
125
src/components/ResourceReview/index.vue
Normal file
125
src/components/ResourceReview/index.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="resource-upload">
|
||||
<div v-if="fileList.length" class="image-list">
|
||||
<div v-for="item in fileList" :key="item.resourceUrl" class="image-item">
|
||||
<img
|
||||
v-if="item.type === 'image'"
|
||||
:src="item.resourceUrl"
|
||||
class="image-item-media"
|
||||
alt="preview"
|
||||
@click="handlePictureCardPreview(item)"
|
||||
/>
|
||||
<video
|
||||
v-else
|
||||
:src="item.resourceUrl"
|
||||
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.resourceUrl)"
|
||||
>移除</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showReviewImgDialog" @close="onDialogClose">
|
||||
<img
|
||||
w-full
|
||||
v-if="reviewImg.type === 'image'"
|
||||
:src="reviewImg.resourceUrl"
|
||||
alt="Preview Image"
|
||||
/>
|
||||
<video v-else ref="reviewVideoRef" :src="reviewImg.resourceUrl" autoplay />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface PreviewItem {
|
||||
fileName: string
|
||||
name: string
|
||||
resourceUrl: string
|
||||
type: 'image' | 'video'
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove', file: PreviewItem): void
|
||||
}>()
|
||||
|
||||
const fileList = defineModel<PreviewItem[]>('fileList', {
|
||||
type: Array as PropType<PreviewItem[]>,
|
||||
default: () => []
|
||||
})
|
||||
|
||||
const remove = (url: string) => {
|
||||
const idx = fileList.value.findIndex((x) => x.resourceUrl === url)
|
||||
if (idx === -1) return
|
||||
const [removed] = fileList.value.splice(idx, 1)
|
||||
emit('remove', removed)
|
||||
}
|
||||
|
||||
const showReviewImgDialog = ref(false)
|
||||
const reviewImg = ref<PreviewItem>({})
|
||||
const handlePictureCardPreview = (item: PreviewItem) => {
|
||||
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>
|
||||
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="resource-upload">
|
||||
<div class="resource-upload__wrapper">
|
||||
<el-button type="primary" class="relative !text-white">
|
||||
<el-button type="primary" size="small" class="relative !text-white">
|
||||
选择文件
|
||||
<input
|
||||
ref="inputRef"
|
||||
class="absolute top-0 left-0 cursor-pointer opacity-0"
|
||||
class="!w-20 absolute top-0 left-0 cursor-pointer opacity-0"
|
||||
type="file"
|
||||
multiple
|
||||
accept="image/*,video/*"
|
||||
|
||||
@ -43,7 +43,12 @@ const handleLogout = () => {
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<div class="left-menu">
|
||||
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
<hamburger
|
||||
id="hamburger-container"
|
||||
:is-active="sidebar.opened"
|
||||
class="hamburger-container"
|
||||
@toggleClick="toggleSideBar"
|
||||
/>
|
||||
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
|
||||
</div>
|
||||
<div class="right-menu">
|
||||
@ -74,7 +79,7 @@ const handleLogout = () => {
|
||||
justify-content: space-between;
|
||||
.left-menu {
|
||||
display: flex;
|
||||
.hamburger-container {
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||
</router-link>
|
||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/home">
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo" style="margin-left: -60px;" />
|
||||
<h1 class="sidebar-title">DC信贷系统</h1>
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo" style="margin-left: -60px" />
|
||||
<h1 class="sidebar-title">购得着后台</h1>
|
||||
</router-link>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
@ -49,7 +49,7 @@ const classObj = computed(() => ({
|
||||
const { user } = useStore()
|
||||
const phone = user.userPhone.slice(-4)
|
||||
const watermark = new Watermark({
|
||||
content: `DC信贷系统 ${user.userNickName}_${phone}`,
|
||||
content: `购得着后台 ${user.userNickName}_${phone}`,
|
||||
tip: `${handleData.formatTime(new Date(), 'YYYY-MM-DD hh:mm:ss')}`,
|
||||
alpha: '0.2',
|
||||
rotate: 340,
|
||||
|
||||
@ -12,14 +12,10 @@
|
||||
:load="handleLoadNode"
|
||||
lazy
|
||||
highlight-current
|
||||
@node-click="onClick"
|
||||
:expand-on-click-node="false"
|
||||
@node-click="handleNodeClick"
|
||||
@node-drag-start="handleDragStart"
|
||||
@node-drag-enter="handleDragEnter"
|
||||
@node-drag-leave="handleDragLeave"
|
||||
@node-drag-over="handleDragOver"
|
||||
@node-drag-end="handleDragEnd"
|
||||
@node-drop="handleDrop"
|
||||
@node-drop="handleDropFinish"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="category-tree-node">
|
||||
@ -47,16 +43,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElButton } from 'element-plus'
|
||||
import { ElButton, ElMessage } from 'element-plus'
|
||||
import type { RenderContentContext } from 'element-plus'
|
||||
import {
|
||||
handleDragStart,
|
||||
handleDragEnter,
|
||||
handleDragOver,
|
||||
handleDragLeave,
|
||||
handleDragEnd,
|
||||
handleDrop
|
||||
} from './use-drag'
|
||||
import { handleDragStart, handleDropFinish } from './use-drag'
|
||||
|
||||
interface Tree {
|
||||
id: number
|
||||
@ -68,7 +57,6 @@ type Data = RenderContentContext['data']
|
||||
|
||||
const curId = ref(NaN)
|
||||
const curParentId = ref(NaN)
|
||||
// const curId = ref(NaN)
|
||||
const inputRef = ref()
|
||||
const treeRef = ref()
|
||||
|
||||
@ -82,12 +70,17 @@ const onEdit = (data: Data) => {
|
||||
}
|
||||
|
||||
// 回车确认
|
||||
const handleInputConfirm = (data: any) => {
|
||||
const handleInputConfirm = async (data: any) => {
|
||||
if (curId.value === Infinity) {
|
||||
api.commodity.updateCategory.post!({ ...data, name: data.label2, parentId: curParentId.value })
|
||||
await api.commodity.updateCategory.post!({
|
||||
...data,
|
||||
name: data.label2,
|
||||
parentId: curParentId.value
|
||||
})
|
||||
} else {
|
||||
api.commodity.updateCategory.post!({ ...data, name: data.label2 })
|
||||
await api.commodity.updateCategory.post!({ ...data, name: data.label2 })
|
||||
}
|
||||
ElMessage.success('更新成功')
|
||||
data.name = data.label2
|
||||
data.label2 = ''
|
||||
curId.value = NaN
|
||||
@ -156,8 +149,9 @@ const handleLoadNode = (node, resolve) => {
|
||||
})
|
||||
}
|
||||
|
||||
const onClick = (data) => {
|
||||
console.warn('----- my data is data111: ', data)
|
||||
// 点击类目
|
||||
const handleNodeClick = (node: Node, data: Data) => {
|
||||
console.log('click', node, data)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,39 +1,24 @@
|
||||
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) => {
|
||||
export const handleDragStart = (node: Node) => {
|
||||
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)
|
||||
|
||||
export const handleDropFinish = (draggingNode: Node, dropNode: Node, dropType: NodeDropType) => {
|
||||
// 获取拖拽之后最新的ids
|
||||
const getSortIds = () => {
|
||||
const parentNode = dropType === 'inner' ? dropNode : dropNode.parent // 获取父节点
|
||||
if (!parentNode || !parentNode.childNodes) return []
|
||||
return parentNode.childNodes.map((node) => node.data.id)
|
||||
}
|
||||
// 获取拖拽到的位置索引
|
||||
const sortIds = getSortIds()
|
||||
if (!sortIds.length) return
|
||||
api.commodity.sortCategory.post!({ categoryIds: sortIds }).then(() => {
|
||||
console.log('tree drop:', draggingNode, dropNode.label, dropType)
|
||||
})
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
// 选择资源库图片
|
||||
export const useImportFile = () => {
|
||||
const showFileExplorer = ref(false)
|
||||
const fileType = {
|
||||
mainImageUrl: '主图',
|
||||
videoUrl: '视频',
|
||||
subImageUrl: '副图'
|
||||
}
|
||||
const curFileType = ref<'mainImageUrl' | 'videoUrl' | 'subImage'>('mainImageUrl')
|
||||
const currentPathArray = ref<{ name: string; id: number }[]>([{ name: '根目录', id: 0 }])
|
||||
|
||||
// 处理文件选择
|
||||
const handleChooseResourceFileCallback = (files: any[] = []) => {
|
||||
handleChooseFiles(curFileType.value, files)
|
||||
}
|
||||
|
||||
const onClickChooseResourceBtn = (type: 'mainImageUrl' | 'videoUrl' | 'subImage') => {
|
||||
curFileType.value = type
|
||||
showFileExplorer.value = true
|
||||
}
|
||||
return {
|
||||
showFileExplorer,
|
||||
currentPathArray,
|
||||
handleChooseResourceFileCallback,
|
||||
onClickChooseResourceBtn
|
||||
}
|
||||
}
|
||||
|
||||
export const fileList = ref<any[]>([])
|
||||
export const handleChooseFiles = (
|
||||
fileType: 'mainImageUrl' | 'videoUrl' | 'subImage',
|
||||
files: any = []
|
||||
) => {
|
||||
const fileNameMap = {
|
||||
mainImageUrl: '主图',
|
||||
videoUrl: '视频',
|
||||
subImage: '副图'
|
||||
}
|
||||
if (['mainImageUrl', 'videoUrl'].includes(fileType)) {
|
||||
const index = fileList.value.findIndex((item: any) => item.fileType === fileType)
|
||||
if (index !== -1) {
|
||||
fileList.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
fileList.value.push(
|
||||
...files.map((item: any) => ({ ...item, fileType, name: fileNameMap[fileType] }))
|
||||
)
|
||||
}
|
||||
@ -4,11 +4,12 @@ export const initConfig = () => {
|
||||
configData.value = pageConfig({
|
||||
dialog: [
|
||||
{
|
||||
title: { label: '商品标题' },
|
||||
comCategoryId: { label: 'app类目', slot: 'comCategoryId' },
|
||||
mainImageUrl: { label: '主图', slot: 'file' },
|
||||
videoUrl: { label: '视频', slot: 'file' },
|
||||
subImageUrl: { label: '副图', slot: 'file' }
|
||||
title: { label: '商品标题', class: '!w-full' },
|
||||
appCategoryId: { label: 'app类目', slot: 'appCategoryId' },
|
||||
mainImageUrl: { label: '主图', slot: 'mainFile' },
|
||||
videoUrl: { label: '视频', slot: 'videoFile' },
|
||||
subImageUrl: { label: '副图', slot: 'subFile' },
|
||||
fileReviewList: { label: '图片预览', slot: 'fileReviewList' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@ -3,19 +3,75 @@
|
||||
<el-card>
|
||||
<h4 class="font-bold">基本信息:</h4>
|
||||
<form-wrap :form="$dialog">
|
||||
<template #comCategoryId>
|
||||
<template #appCategoryId>
|
||||
<el-tree-select
|
||||
v-model="$dialog.data.comCategoryId"
|
||||
:data="categoryOptions"
|
||||
></el-tree-select>
|
||||
show-checkbox
|
||||
v-model="$dialog.data.appCategoryId"
|
||||
:props="defaultAdminCategoryTreeProps"
|
||||
:render-after-expand="false"
|
||||
@check="
|
||||
(checkedNode: any, checkedStatus: any) =>
|
||||
handleCheckChange(checkedNode, checkedStatus, 'app')
|
||||
"
|
||||
:load="handleLoadNode2"
|
||||
lazy
|
||||
>
|
||||
</el-tree-select>
|
||||
</template>
|
||||
<template #file>
|
||||
<resource-upload v-model="fileList" />
|
||||
<template #mainFile>
|
||||
<file-upload-btn
|
||||
accept="image/*"
|
||||
size="normal"
|
||||
@change="(val) => handleChooseFiles('mainImageUrl', val)"
|
||||
/>
|
||||
<el-button class="ml-2" @click="onClickChooseResourceBtn('mainImageUrl')"
|
||||
>资源库导入</el-button
|
||||
>
|
||||
</template>
|
||||
<template #videoFile>
|
||||
<file-upload-btn
|
||||
size="normal"
|
||||
accept="video/*"
|
||||
@change="(val) => handleChooseFiles('videoUrl', val)"
|
||||
/>
|
||||
<el-button class="ml-2" @click="onClickChooseResourceBtn('videoUrl')"
|
||||
>资源库导入</el-button
|
||||
>
|
||||
</template>
|
||||
<template #subFile>
|
||||
<file-upload-btn
|
||||
size="normal"
|
||||
multiple
|
||||
accept="image/*"
|
||||
@change="(val) => handleChooseFiles('subImage', val)"
|
||||
/>
|
||||
<el-button class="ml-2" @click="onClickChooseResourceBtn('subImage')"
|
||||
>资源库导入</el-button
|
||||
>
|
||||
</template>
|
||||
<template #fileReviewList>
|
||||
<resource-review v-model:fileList="fileList" />
|
||||
</template>
|
||||
</form-wrap>
|
||||
</el-card>
|
||||
<el-card class="mt-2 sku-info">
|
||||
<h4 class="font-bold mb-2">SKU信息:</h4>
|
||||
<el-form-item label="后台类目">
|
||||
<el-tree-select
|
||||
show-checkbox
|
||||
v-model="$dialog.data.categoryId"
|
||||
:props="defaultAdminCategoryTreeProps"
|
||||
:render-after-expand="false"
|
||||
@check="
|
||||
(checkedNode: any, checkedStatus: any) =>
|
||||
handleCheckChange(checkedNode, checkedStatus, 'admin')
|
||||
"
|
||||
:load="handleLoadNode"
|
||||
lazy
|
||||
style="width: 240px"
|
||||
>
|
||||
</el-tree-select>
|
||||
</el-form-item>
|
||||
<div ref="dragRef">
|
||||
<dl v-for="(item, index) in categoryData" :key="item.typeId" class="flex items-center">
|
||||
<dt class="text-sm text-[#666] mb-2">{{ item.typeName }}:</dt>
|
||||
@ -60,26 +116,52 @@
|
||||
<wan-editor v-model="htmlContent"></wan-editor>
|
||||
</el-card>
|
||||
<el-button type="primary" @click="onSubmit()">提交</el-button>
|
||||
<FileExplorerDialog
|
||||
v-model="showFileExplorer"
|
||||
v-model:initPathArray="currentPathArray"
|
||||
@select="handleChooseResourceFileCallback"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { initConfig } from './config'
|
||||
import { categoryDataMock, categoryOptionsMock } from './mock'
|
||||
import { generateTargetData, useCategoryAddOption, colors, useDrag } from './use-method'
|
||||
import {
|
||||
generateTargetData,
|
||||
useCategoryAddOption,
|
||||
colors,
|
||||
useDrag,
|
||||
handleLoadNode,
|
||||
handleLoadNode2,
|
||||
handleCheckChange,
|
||||
defaultAdminCategoryTreeProps,
|
||||
checkedNodes
|
||||
} from './use-method'
|
||||
import categoryConfig from './category-config.vue'
|
||||
import wanEditor from './editor.vue'
|
||||
import resourceUpload from '@/components/ResourceUpload/index.vue'
|
||||
import fileUploadBtn from '@/components/FileUploadBtn/index.vue'
|
||||
import resourceReview from '@/components/ResourceReview/index.vue'
|
||||
import { useImportFile, fileList, handleChooseFiles } from './choose-file-method'
|
||||
import FileExplorerDialog from '@/components/FileExplorerDialog/index.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const init = async () => {
|
||||
checkedNodes.value = { admin: [], app: [] }
|
||||
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 {
|
||||
showFileExplorer,
|
||||
currentPathArray,
|
||||
handleChooseResourceFileCallback,
|
||||
onClickChooseResourceBtn
|
||||
} = useImportFile()
|
||||
|
||||
const categoryOptions123 = ref(categoryOptionsMock)
|
||||
const categoryData = ref(categoryDataMock)
|
||||
const { inputValue, InputRefs, inputVisibles, onAddCategoryDataOption, handleInputConfirm } =
|
||||
useCategoryAddOption(categoryData)
|
||||
@ -91,12 +173,6 @@ 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)
|
||||
}
|
||||
|
||||
@ -95,3 +95,49 @@ export const useDrag = (list: any) => {
|
||||
}
|
||||
return { dragRef, createDrag }
|
||||
}
|
||||
|
||||
export const defaultAdminCategoryTreeProps = {
|
||||
label: 'label',
|
||||
children: 'children',
|
||||
disabled: 'disabled',
|
||||
isLeaf: (data: any) => {
|
||||
return !data.hasChild
|
||||
}
|
||||
}
|
||||
|
||||
// 树结构懒加载后台类目
|
||||
export const handleLoadNode = (node: any, resolve: any) => {
|
||||
api.commodity.getCategoryList.post!<any>({ parentId: node.data.value || 0 }).then((res) => {
|
||||
resolve(
|
||||
res.data.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
hasChild: item.hasChild,
|
||||
disabled: item.hasChild,
|
||||
categoryPropertyList: item.vvCategoryPropertyDTOList
|
||||
}))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// 树结构懒加载后台类目
|
||||
export const handleLoadNode2 = (node: any, resolve: any) => {
|
||||
api.commodity.getAppCategoryList.post!<any>({ parentId: node.data.value || 0 }).then((res) => {
|
||||
resolve(
|
||||
res.data.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
hasChild: item.hasChild,
|
||||
disabled: item.hasChild,
|
||||
categoryPropertyList: item.vvCategoryPropertyDTOList
|
||||
}))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export const checkedNodes = ref<{ admin: number[]; app: number[] }>({ admin: [], app: [] })
|
||||
export const handleCheckChange = (_: any, checkedStatus: any, type: 'admin' | 'app') => {
|
||||
// checkedStatus: { checkedKeys, checkedNodes, halfCheckedKeys, halfCheckedNodes }
|
||||
checkedNodes.value[type] = [...checkedStatus.halfCheckedKeys, ...checkedStatus.checkedKeys]
|
||||
console.warn('----- my data is checkedNodes: ', checkedNodes)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div id="home">
|
||||
<img src="~@/assets/images/home_bg.jpg" />
|
||||
<p>欢迎来到DC信贷系统</p>
|
||||
<p>欢迎来到购得着后台</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ const handleLogin = () => {
|
||||
// width: 420px;
|
||||
// height: 430px;
|
||||
// background-color: #ecf5ff;
|
||||
border-radius: 30px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
padding: 10px 20px;
|
||||
position: absolute;
|
||||
@ -130,7 +130,7 @@ const handleLogin = () => {
|
||||
}
|
||||
}
|
||||
.login_submit_btn {
|
||||
margin: 30px 0;
|
||||
margin: 30px 0 0;
|
||||
.el-button {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user