mirror of https://github.com/halo-dev/halo
refactor: refactoring the batch operation logic of attachment management (halo-dev/console#431)
* refactor: refactoring the batch operation logic of attachment management Signed-off-by: Ryan Wang <i@ryanc.cc> * feat: support select all Signed-off-by: Ryan Wang <i@ryanc.cc>pull/3445/head
parent
a471f91c90
commit
10bf358e33
|
@ -60,52 +60,50 @@
|
|||
:loading="list.loading"
|
||||
class="attachments-group"
|
||||
>
|
||||
<a-list-item
|
||||
@mouseenter="$set(item, 'hover', true)"
|
||||
@mouseleave="$set(item, 'hover', false)"
|
||||
:key="index"
|
||||
slot="renderItem"
|
||||
slot-scope="item, index"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<div :class="`${isItemSelect(item) ? 'border-blue-600' : 'border-slate-200'}`" class="border border-solid">
|
||||
<div class="attach-thumb attachments-group-item">
|
||||
<span v-if="!isImage(item)" class="attachments-group-item-type">{{ item.suffix }}</span>
|
||||
<span
|
||||
v-else
|
||||
:style="`background-image:url(${item.thumbPath})`"
|
||||
class="attachments-group-item-img"
|
||||
loading="lazy"
|
||||
<template #renderItem="item, index">
|
||||
<a-list-item
|
||||
@mouseenter="$set(item, 'hover', true)"
|
||||
@mouseleave="$set(item, 'hover', false)"
|
||||
:key="index"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<div :class="`${isItemSelect(item) ? 'border-blue-600' : 'border-slate-200'}`" class="border border-solid">
|
||||
<div class="attach-thumb attachments-group-item">
|
||||
<span v-if="!isImage(item)" class="attachments-group-item-type">{{ item.suffix }}</span>
|
||||
<span
|
||||
v-else
|
||||
:style="`background-image:url(${item.thumbPath})`"
|
||||
class="attachments-group-item-img"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<a-card-meta class="p-2 cursor-pointer">
|
||||
<template #description>
|
||||
<a-tooltip :title="item.name">
|
||||
<div class="truncate">{{ item.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
<a-icon
|
||||
v-show="isItemSelect(item) && !item.hover"
|
||||
type="check-circle"
|
||||
theme="twoTone"
|
||||
class="absolute top-1 right-2 font-bold cursor-pointer transition-all"
|
||||
:style="{ fontSize: '18px', color: 'rgb(37 99 235)' }"
|
||||
/>
|
||||
<a-icon
|
||||
v-show="item.hover"
|
||||
type="profile"
|
||||
theme="twoTone"
|
||||
class="absolute top-1 right-2 font-bold cursor-pointer transition-all"
|
||||
@click.stop="handleOpenDetail(item)"
|
||||
:style="{ fontSize: '18px' }"
|
||||
/>
|
||||
</div>
|
||||
<a-card-meta class="p-2 cursor-pointer">
|
||||
<template #description>
|
||||
<a-tooltip :title="item.name">
|
||||
<div class="truncate">{{ item.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
<a-icon
|
||||
v-show="isItemSelect(item) && !item.hover"
|
||||
type="check-circle"
|
||||
theme="twoTone"
|
||||
class="absolute top-1 right-2 font-bold transition-all"
|
||||
:style="{ fontSize: '18px', color: 'rgb(37 99 235)' }"
|
||||
/>
|
||||
<a-icon
|
||||
v-show="item.hover"
|
||||
type="profile"
|
||||
theme="twoTone"
|
||||
class="absolute top-1 right-2 font-bold cursor-pointer hover:text-blue-400 transition-all"
|
||||
@click.stop="handleOpenDetail(item)"
|
||||
:style="{ fontSize: '18px' }"
|
||||
/>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
|
||||
<div class="page-wrapper"></div>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<a-popover placement="right" title="预览" trigger="click">
|
||||
<template slot="content">
|
||||
|
@ -130,13 +128,13 @@
|
|||
</a-tooltip>
|
||||
</a-popover>
|
||||
|
||||
<div class="flex justify-end self-center">
|
||||
<div class="page-wrapper flex justify-end self-center">
|
||||
<a-pagination
|
||||
:current="pagination.page"
|
||||
:defaultPageSize="pagination.size"
|
||||
:pageSizeOptions="['12', '18', '24', '30', '36', '42']"
|
||||
:total="pagination.total"
|
||||
class="pagination"
|
||||
class="pagination !mt-0"
|
||||
showLessItems
|
||||
showSizeChanger
|
||||
@change="handlePageChange"
|
||||
|
|
|
@ -52,65 +52,81 @@
|
|||
</div>
|
||||
<div class="mb-0 table-operator">
|
||||
<a-button icon="cloud-upload" type="primary" @click="upload.visible = true">上传</a-button>
|
||||
<a-button v-show="!supportMultipleSelection" icon="select" @click="handleMultipleSelection">
|
||||
批量操作
|
||||
<a-button v-show="list.selected.length" icon="check-circle" type="primary" @click="handleSelectAll">
|
||||
全选
|
||||
</a-button>
|
||||
<a-button
|
||||
v-show="supportMultipleSelection"
|
||||
icon="delete"
|
||||
type="danger"
|
||||
@click="handleDeleteAttachmentInBatch"
|
||||
>
|
||||
<a-button v-show="list.selected.length" icon="delete" type="danger" @click="handleDeleteAttachmentInBatch">
|
||||
删除
|
||||
</a-button>
|
||||
<a-button v-show="supportMultipleSelection" icon="close" @click="handleCancelMultipleSelection">
|
||||
取消
|
||||
</a-button>
|
||||
<a-button v-show="list.selected.length" icon="close" @click="list.selected = []"> 取消</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-list
|
||||
:dataSource="list.data"
|
||||
:grid="{ gutter: 12, xs: 2, sm: 2, md: 4, lg: 6, xl: 6, xxl: 6 }"
|
||||
:grid="{ gutter: 6, xs: 2, sm: 2, md: 4, lg: 6, xl: 6, xxl: 6 }"
|
||||
:loading="list.loading"
|
||||
class="attachments-group"
|
||||
>
|
||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
||||
<a-card
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
hoverable
|
||||
@click="handleOpenDetail(item)"
|
||||
<template #renderItem="item, index">
|
||||
<a-list-item
|
||||
@mouseenter="$set(item, 'hover', true)"
|
||||
@mouseleave="$set(item, 'hover', false)"
|
||||
:key="index"
|
||||
@click="handleItemClick(item)"
|
||||
@contextmenu.prevent="handleContextMenu($event, item)"
|
||||
>
|
||||
<div class="attach-thumb attachments-group-item">
|
||||
<span v-if="!isImage(item)" class="attachments-group-item-type">{{ item.suffix }}</span>
|
||||
<span
|
||||
v-else
|
||||
:style="`background-image:url(${item.thumbPath})`"
|
||||
class="attachments-group-item-img"
|
||||
loading="lazy"
|
||||
<div
|
||||
:class="`${isItemSelect(item) ? 'border-blue-600' : 'border-slate-200'}`"
|
||||
class="border border-solid"
|
||||
>
|
||||
<div class="attach-thumb attachments-group-item">
|
||||
<span v-if="!isImage(item)" class="attachments-group-item-type">{{ item.suffix }}</span>
|
||||
<span
|
||||
v-else
|
||||
:style="`background-image:url(${item.thumbPath})`"
|
||||
class="attachments-group-item-img"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<a-card-meta class="p-2 cursor-pointer">
|
||||
<template #description>
|
||||
<a-tooltip :title="item.name">
|
||||
<div class="truncate">{{ item.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
<a-icon
|
||||
v-show="!isItemSelect(item) && item.hover"
|
||||
type="plus-circle"
|
||||
theme="twoTone"
|
||||
class="absolute top-1 right-2 font-bold cursor-pointer transition-all"
|
||||
:style="{ fontSize: '18px', color: 'rgb(37 99 235)' }"
|
||||
@click.stop="handleSelect(item)"
|
||||
/>
|
||||
<a-icon
|
||||
v-show="isItemSelect(item)"
|
||||
type="check-circle"
|
||||
theme="twoTone"
|
||||
class="absolute top-1 right-2 font-bold cursor-pointer transition-all"
|
||||
:style="{ fontSize: '18px', color: 'rgb(37 99 235)' }"
|
||||
/>
|
||||
<a-icon
|
||||
v-show="item.hover && list.selected.length > 0"
|
||||
type="profile"
|
||||
theme="twoTone"
|
||||
class="absolute top-1 left-2 font-bold cursor-pointer transition-all"
|
||||
@click.stop="handleOpenDetail(item)"
|
||||
:style="{ fontSize: '18px' }"
|
||||
/>
|
||||
</div>
|
||||
<a-card-meta class="p-2">
|
||||
<template #description>
|
||||
<a-tooltip :title="item.name">
|
||||
<div class="truncate">{{ item.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
<a-checkbox
|
||||
v-show="supportMultipleSelection"
|
||||
:checked="getCheckStatus(item.id)"
|
||||
:style="getCheckStatus(item.id) ? selectedAttachmentStyle : ''"
|
||||
class="select-attachment-checkbox"
|
||||
@click="handleAttachmentSelectionChanged($event, item)"
|
||||
></a-checkbox>
|
||||
</a-card>
|
||||
</a-list-item>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:current="pagination.page"
|
||||
|
@ -129,7 +145,7 @@
|
|||
|
||||
<AttachmentDetailModal
|
||||
:addToPhoto="true"
|
||||
:attachment="list.selected"
|
||||
:attachment="list.current"
|
||||
:visible.sync="detailVisible"
|
||||
@delete="handleListAttachments()"
|
||||
>
|
||||
|
@ -145,7 +161,6 @@
|
|||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import { PageView } from '@/layouts'
|
||||
import apiClient from '@/utils/api-client'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { attachmentTypes } from '@/core/constant'
|
||||
|
||||
export default {
|
||||
|
@ -166,14 +181,15 @@ export default {
|
|||
total: 0,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
selected: {},
|
||||
params: {
|
||||
page: 0,
|
||||
size: 18,
|
||||
keyword: undefined,
|
||||
mediaType: undefined,
|
||||
attachmentType: undefined
|
||||
}
|
||||
},
|
||||
selected: [],
|
||||
current: {}
|
||||
},
|
||||
|
||||
mediaTypes: {
|
||||
|
@ -190,19 +206,10 @@ export default {
|
|||
visible: false
|
||||
},
|
||||
|
||||
detailVisible: false,
|
||||
|
||||
supportMultipleSelection: false,
|
||||
selectedAttachmentCheckbox: {},
|
||||
batchSelectedAttachments: []
|
||||
detailVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedAttachmentStyle() {
|
||||
return {
|
||||
border: `2px solid ${this.color()}`
|
||||
}
|
||||
},
|
||||
isImage() {
|
||||
return function (attachment) {
|
||||
if (!attachment || !attachment.mediaType) {
|
||||
|
@ -211,6 +218,11 @@ export default {
|
|||
return attachment.mediaType.startsWith('image')
|
||||
}
|
||||
},
|
||||
isItemSelect() {
|
||||
return function (attachment) {
|
||||
return this.list.selected.findIndex(item => item.id === attachment.id) > -1
|
||||
}
|
||||
},
|
||||
pagination() {
|
||||
return {
|
||||
page: this.list.params.page + 1,
|
||||
|
@ -219,11 +231,11 @@ export default {
|
|||
}
|
||||
},
|
||||
selectPreviousButtonDisabled() {
|
||||
const index = this.list.data.findIndex(attachment => attachment.id === this.list.selected.id)
|
||||
const index = this.list.data.findIndex(attachment => attachment.id === this.list.current.id)
|
||||
return index === 0 && !this.list.hasPrevious
|
||||
},
|
||||
selectNextButtonDisabled() {
|
||||
const index = this.list.data.findIndex(attachment => attachment.id === this.list.selected.id)
|
||||
const index = this.list.data.findIndex(attachment => attachment.id === this.list.current.id)
|
||||
return index === this.list.data.length - 1 && !this.list.hasNext
|
||||
}
|
||||
},
|
||||
|
@ -233,8 +245,6 @@ export default {
|
|||
this.handleListTypes()
|
||||
},
|
||||
methods: {
|
||||
...mapGetters(['color']),
|
||||
|
||||
/**
|
||||
* List attachments
|
||||
*/
|
||||
|
@ -293,8 +303,28 @@ export default {
|
|||
* Handle open attachment detail modal event
|
||||
*/
|
||||
handleOpenDetail(attachment) {
|
||||
this.list.selected = attachment
|
||||
this.detailVisible = !this.supportMultipleSelection
|
||||
this.list.current = attachment
|
||||
this.detailVisible = true
|
||||
},
|
||||
|
||||
handleItemClick(attachment) {
|
||||
if (this.list.selected.length <= 0) {
|
||||
this.handleOpenDetail(attachment)
|
||||
return
|
||||
}
|
||||
this.isItemSelect(attachment) ? this.handleUnselect(attachment) : this.handleSelect(attachment)
|
||||
},
|
||||
|
||||
handleSelect(attachment) {
|
||||
this.list.selected = [...this.list.selected, attachment]
|
||||
},
|
||||
|
||||
handleUnselect(attachment) {
|
||||
this.list.selected = this.list.selected.filter(item => item.id !== attachment.id)
|
||||
},
|
||||
|
||||
handleSelectAll() {
|
||||
this.list.selected = this.list.data
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -347,6 +377,7 @@ export default {
|
|||
onOk: async () => {
|
||||
await apiClient.attachment.delete(item.id)
|
||||
await this.handleListAttachments()
|
||||
this.handleUnselect(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -394,67 +425,37 @@ export default {
|
|||
handleQuery() {
|
||||
this.handlePageChange()
|
||||
},
|
||||
|
||||
onUploadClose() {
|
||||
this.handlePageChange()
|
||||
this.handleListMediaTypes()
|
||||
this.handleListTypes()
|
||||
},
|
||||
getCheckStatus(key) {
|
||||
return this.selectedAttachmentCheckbox[key] || false
|
||||
},
|
||||
handleMultipleSelection() {
|
||||
this.supportMultipleSelection = true
|
||||
// 不允许附件详情抽屉显示
|
||||
this.detailVisible = false
|
||||
this.list.data.forEach(item => {
|
||||
this.$set(this.selectedAttachmentCheckbox, item.id, false)
|
||||
})
|
||||
},
|
||||
handleCancelMultipleSelection() {
|
||||
this.supportMultipleSelection = false
|
||||
this.detailVisible = false
|
||||
this.batchSelectedAttachments = []
|
||||
for (const key in this.selectedCheckbox) {
|
||||
this.$set(this.selectedAttachmentCheckbox, key, false)
|
||||
}
|
||||
},
|
||||
handleAttachmentSelectionChanged(e, item) {
|
||||
const isChecked = e.target.checked || false
|
||||
if (isChecked) {
|
||||
this.$set(this.selectedAttachmentCheckbox, item.id, true)
|
||||
this.batchSelectedAttachments.push(item.id)
|
||||
} else {
|
||||
this.$set(this.selectedAttachmentCheckbox, item.id, false)
|
||||
// 从选中id集合中删除id
|
||||
const index = this.batchSelectedAttachments.indexOf(item.id)
|
||||
this.batchSelectedAttachments.splice(index, 1)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes selected attachments
|
||||
*/
|
||||
handleDeleteAttachmentInBatch() {
|
||||
const that = this
|
||||
if (this.batchSelectedAttachments.length <= 0) {
|
||||
const _this = this
|
||||
if (this.list.selected.length <= 0) {
|
||||
this.$message.warn('你还未选择任何附件,请至少选择一个!')
|
||||
return
|
||||
}
|
||||
this.$confirm({
|
||||
title: '确定要批量删除选中的附件吗?',
|
||||
title: '确定要批量删除选中的附件吗?',
|
||||
content: '一旦删除不可恢复,请谨慎操作',
|
||||
onOk() {
|
||||
apiClient.attachment
|
||||
.deleteInBatch(that.batchSelectedAttachments)
|
||||
.then(() => {
|
||||
that.handleCancelMultipleSelection()
|
||||
that.$message.success('删除成功')
|
||||
})
|
||||
.finally(() => {
|
||||
that.handleListAttachments()
|
||||
})
|
||||
},
|
||||
onCancel() {}
|
||||
async onOk() {
|
||||
try {
|
||||
const attachmentIds = _this.list.selected.map(attachment => attachment.id)
|
||||
await apiClient.attachment.deleteInBatch(attachmentIds)
|
||||
_this.list.selected = []
|
||||
_this.$message.success('删除成功')
|
||||
} catch (e) {
|
||||
_this.$log.error('Failed to delete selected attachments', e)
|
||||
} finally {
|
||||
await _this.handleListAttachments()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -462,16 +463,16 @@ export default {
|
|||
* Select previous attachment
|
||||
*/
|
||||
async handleSelectPrevious() {
|
||||
const index = this.list.data.findIndex(item => item.id === this.list.selected.id)
|
||||
const index = this.list.data.findIndex(item => item.id === this.list.current.id)
|
||||
if (index > 0) {
|
||||
this.list.selected = this.list.data[index - 1]
|
||||
this.list.current = this.list.data[index - 1]
|
||||
return
|
||||
}
|
||||
if (index === 0 && this.list.hasPrevious) {
|
||||
this.list.params.page--
|
||||
await this.handleListAttachments()
|
||||
|
||||
this.list.selected = this.list.data[this.list.data.length - 1]
|
||||
this.list.current = this.list.data[this.list.data.length - 1]
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -479,16 +480,16 @@ export default {
|
|||
* Select next attachment
|
||||
*/
|
||||
async handleSelectNext() {
|
||||
const index = this.list.data.findIndex(item => item.id === this.list.selected.id)
|
||||
const index = this.list.data.findIndex(item => item.id === this.list.current.id)
|
||||
if (index < this.list.data.length - 1) {
|
||||
this.list.selected = this.list.data[index + 1]
|
||||
this.list.current = this.list.data[index + 1]
|
||||
return
|
||||
}
|
||||
if (index === this.list.data.length - 1 && this.list.hasNext) {
|
||||
this.list.params.page++
|
||||
await this.handleListAttachments()
|
||||
|
||||
this.list.selected = this.list.data[0]
|
||||
this.list.current = this.list.data[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue