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
Ryan Wang 2022-02-10 16:00:17 +08:00 committed by GitHub
parent a471f91c90
commit 10bf358e33
2 changed files with 155 additions and 156 deletions

View File

@ -60,52 +60,50 @@
:loading="list.loading" :loading="list.loading"
class="attachments-group" class="attachments-group"
> >
<a-list-item <template #renderItem="item, index">
@mouseenter="$set(item, 'hover', true)" <a-list-item
@mouseleave="$set(item, 'hover', false)" @mouseenter="$set(item, 'hover', true)"
:key="index" @mouseleave="$set(item, 'hover', false)"
slot="renderItem" :key="index"
slot-scope="item, index" @click="handleItemClick(item)"
@click="handleItemClick(item)" >
> <div :class="`${isItemSelect(item) ? 'border-blue-600' : 'border-slate-200'}`" class="border border-solid">
<div :class="`${isItemSelect(item) ? 'border-blue-600' : 'border-slate-200'}`" class="border border-solid"> <div class="attach-thumb attachments-group-item">
<div class="attach-thumb attachments-group-item"> <span v-if="!isImage(item)" class="attachments-group-item-type">{{ item.suffix }}</span>
<span v-if="!isImage(item)" class="attachments-group-item-type">{{ item.suffix }}</span> <span
<span v-else
v-else :style="`background-image:url(${item.thumbPath})`"
:style="`background-image:url(${item.thumbPath})`" class="attachments-group-item-img"
class="attachments-group-item-img" loading="lazy"
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> </div>
<a-card-meta class="p-2 cursor-pointer"> </a-list-item>
<template #description> </template>
<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> </a-list>
<div class="page-wrapper"></div>
<div class="flex justify-between"> <div class="flex justify-between">
<a-popover placement="right" title="预览" trigger="click"> <a-popover placement="right" title="预览" trigger="click">
<template slot="content"> <template slot="content">
@ -130,13 +128,13 @@
</a-tooltip> </a-tooltip>
</a-popover> </a-popover>
<div class="flex justify-end self-center"> <div class="page-wrapper flex justify-end self-center">
<a-pagination <a-pagination
:current="pagination.page" :current="pagination.page"
:defaultPageSize="pagination.size" :defaultPageSize="pagination.size"
:pageSizeOptions="['12', '18', '24', '30', '36', '42']" :pageSizeOptions="['12', '18', '24', '30', '36', '42']"
:total="pagination.total" :total="pagination.total"
class="pagination" class="pagination !mt-0"
showLessItems showLessItems
showSizeChanger showSizeChanger
@change="handlePageChange" @change="handlePageChange"

View File

@ -52,65 +52,81 @@
</div> </div>
<div class="mb-0 table-operator"> <div class="mb-0 table-operator">
<a-button icon="cloud-upload" type="primary" @click="upload.visible = true">上传</a-button> <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>
<a-button <a-button v-show="list.selected.length" icon="delete" type="danger" @click="handleDeleteAttachmentInBatch">
v-show="supportMultipleSelection"
icon="delete"
type="danger"
@click="handleDeleteAttachmentInBatch"
>
删除 删除
</a-button> </a-button>
<a-button v-show="supportMultipleSelection" icon="close" @click="handleCancelMultipleSelection"> <a-button v-show="list.selected.length" icon="close" @click="list.selected = []"> </a-button>
取消
</a-button>
</div> </div>
</a-card> </a-card>
</a-col> </a-col>
<a-col :span="24"> <a-col :span="24">
<a-list <a-list
:dataSource="list.data" :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" :loading="list.loading"
class="attachments-group" class="attachments-group"
> >
<a-list-item :key="index" slot="renderItem" slot-scope="item, index"> <template #renderItem="item, index">
<a-card <a-list-item
:bodyStyle="{ padding: 0 }" @mouseenter="$set(item, 'hover', true)"
hoverable @mouseleave="$set(item, 'hover', false)"
@click="handleOpenDetail(item)" :key="index"
@click="handleItemClick(item)"
@contextmenu.prevent="handleContextMenu($event, item)" @contextmenu.prevent="handleContextMenu($event, item)"
> >
<div class="attach-thumb attachments-group-item"> <div
<span v-if="!isImage(item)" class="attachments-group-item-type">{{ item.suffix }}</span> :class="`${isItemSelect(item) ? 'border-blue-600' : 'border-slate-200'}`"
<span class="border border-solid"
v-else >
:style="`background-image:url(${item.thumbPath})`" <div class="attach-thumb attachments-group-item">
class="attachments-group-item-img" <span v-if="!isImage(item)" class="attachments-group-item-type">{{ item.suffix }}</span>
loading="lazy" <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> </div>
<a-card-meta class="p-2"> </a-list-item>
<template #description> </template>
<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> </a-list>
</a-col> </a-col>
</a-row> </a-row>
<div class="page-wrapper"> <div class="page-wrapper">
<a-pagination <a-pagination
:current="pagination.page" :current="pagination.page"
@ -129,7 +145,7 @@
<AttachmentDetailModal <AttachmentDetailModal
:addToPhoto="true" :addToPhoto="true"
:attachment="list.selected" :attachment="list.current"
:visible.sync="detailVisible" :visible.sync="detailVisible"
@delete="handleListAttachments()" @delete="handleListAttachments()"
> >
@ -145,7 +161,6 @@
import { mixin, mixinDevice } from '@/mixins/mixin.js' import { mixin, mixinDevice } from '@/mixins/mixin.js'
import { PageView } from '@/layouts' import { PageView } from '@/layouts'
import apiClient from '@/utils/api-client' import apiClient from '@/utils/api-client'
import { mapGetters } from 'vuex'
import { attachmentTypes } from '@/core/constant' import { attachmentTypes } from '@/core/constant'
export default { export default {
@ -166,14 +181,15 @@ export default {
total: 0, total: 0,
hasNext: false, hasNext: false,
hasPrevious: false, hasPrevious: false,
selected: {},
params: { params: {
page: 0, page: 0,
size: 18, size: 18,
keyword: undefined, keyword: undefined,
mediaType: undefined, mediaType: undefined,
attachmentType: undefined attachmentType: undefined
} },
selected: [],
current: {}
}, },
mediaTypes: { mediaTypes: {
@ -190,19 +206,10 @@ export default {
visible: false visible: false
}, },
detailVisible: false, detailVisible: false
supportMultipleSelection: false,
selectedAttachmentCheckbox: {},
batchSelectedAttachments: []
} }
}, },
computed: { computed: {
selectedAttachmentStyle() {
return {
border: `2px solid ${this.color()}`
}
},
isImage() { isImage() {
return function (attachment) { return function (attachment) {
if (!attachment || !attachment.mediaType) { if (!attachment || !attachment.mediaType) {
@ -211,6 +218,11 @@ export default {
return attachment.mediaType.startsWith('image') return attachment.mediaType.startsWith('image')
} }
}, },
isItemSelect() {
return function (attachment) {
return this.list.selected.findIndex(item => item.id === attachment.id) > -1
}
},
pagination() { pagination() {
return { return {
page: this.list.params.page + 1, page: this.list.params.page + 1,
@ -219,11 +231,11 @@ export default {
} }
}, },
selectPreviousButtonDisabled() { 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 return index === 0 && !this.list.hasPrevious
}, },
selectNextButtonDisabled() { 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 return index === this.list.data.length - 1 && !this.list.hasNext
} }
}, },
@ -233,8 +245,6 @@ export default {
this.handleListTypes() this.handleListTypes()
}, },
methods: { methods: {
...mapGetters(['color']),
/** /**
* List attachments * List attachments
*/ */
@ -293,8 +303,28 @@ export default {
* Handle open attachment detail modal event * Handle open attachment detail modal event
*/ */
handleOpenDetail(attachment) { handleOpenDetail(attachment) {
this.list.selected = attachment this.list.current = attachment
this.detailVisible = !this.supportMultipleSelection 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 () => { onOk: async () => {
await apiClient.attachment.delete(item.id) await apiClient.attachment.delete(item.id)
await this.handleListAttachments() await this.handleListAttachments()
this.handleUnselect(item)
} }
}) })
} }
@ -394,67 +425,37 @@ export default {
handleQuery() { handleQuery() {
this.handlePageChange() this.handlePageChange()
}, },
onUploadClose() { onUploadClose() {
this.handlePageChange() this.handlePageChange()
this.handleListMediaTypes() this.handleListMediaTypes()
this.handleListTypes() 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)
// idid
const index = this.batchSelectedAttachments.indexOf(item.id)
this.batchSelectedAttachments.splice(index, 1)
}
},
/** /**
* Deletes selected attachments * Deletes selected attachments
*/ */
handleDeleteAttachmentInBatch() { handleDeleteAttachmentInBatch() {
const that = this const _this = this
if (this.batchSelectedAttachments.length <= 0) { if (this.list.selected.length <= 0) {
this.$message.warn('你还未选择任何附件,请至少选择一个!') this.$message.warn('你还未选择任何附件,请至少选择一个!')
return return
} }
this.$confirm({ this.$confirm({
title: '确定要批量删除选中的附件吗?', title: '确定要批量删除选中的附件吗?',
content: '一旦删除不可恢复,请谨慎操作', content: '一旦删除不可恢复,请谨慎操作',
onOk() { async onOk() {
apiClient.attachment try {
.deleteInBatch(that.batchSelectedAttachments) const attachmentIds = _this.list.selected.map(attachment => attachment.id)
.then(() => { await apiClient.attachment.deleteInBatch(attachmentIds)
that.handleCancelMultipleSelection() _this.list.selected = []
that.$message.success('删除成功') _this.$message.success('删除成功')
}) } catch (e) {
.finally(() => { _this.$log.error('Failed to delete selected attachments', e)
that.handleListAttachments() } finally {
}) await _this.handleListAttachments()
}, }
onCancel() {} }
}) })
}, },
@ -462,16 +463,16 @@ export default {
* Select previous attachment * Select previous attachment
*/ */
async handleSelectPrevious() { 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) { if (index > 0) {
this.list.selected = this.list.data[index - 1] this.list.current = this.list.data[index - 1]
return return
} }
if (index === 0 && this.list.hasPrevious) { if (index === 0 && this.list.hasPrevious) {
this.list.params.page-- this.list.params.page--
await this.handleListAttachments() 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 * Select next attachment
*/ */
async handleSelectNext() { 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) { if (index < this.list.data.length - 1) {
this.list.selected = this.list.data[index + 1] this.list.current = this.list.data[index + 1]
return return
} }
if (index === this.list.data.length - 1 && this.list.hasNext) { if (index === this.list.data.length - 1 && this.list.hasNext) {
this.list.params.page++ this.list.params.page++
await this.handleListAttachments() await this.handleListAttachments()
this.list.selected = this.list.data[0] this.list.current = this.list.data[0]
} }
} }
} }