refactor: attachment selection modal (halo-dev/console#420)

* refactor: attachment selection modal

Signed-off-by: Ryan Wang <i@ryanc.cc>

* feat: support view attachment detail

Signed-off-by: Ryan Wang <i@ryanc.cc>

* chore: remove AttachmentSelectDrawer.vue

Signed-off-by: Ryan Wang <i@ryanc.cc>

* chore: remove AttachmentDrawer.vue

Signed-off-by: Ryan Wang <i@ryanc.cc>

* perf: add selected icon

Signed-off-by: Ryan Wang <i@ryanc.cc>

* perf: add search form

Signed-off-by: Ryan Wang <i@ryanc.cc>

* perf: support upload file

Signed-off-by: Ryan Wang <i@ryanc.cc>

* refactor: attachment selection modal

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/3445/head
Ryan Wang 2022-01-25 15:25:58 +08:00 committed by GitHub
parent 867f78aa4a
commit 727a4d6db4
19 changed files with 630 additions and 647 deletions

View File

@ -108,52 +108,14 @@
<script>
import { mixin, mixinDevice } from '@/mixins/mixin.js'
import apiClient from '@/utils/api-client'
const attachmentType = {
LOCAL: {
type: 'LOCAL',
text: '本地'
},
SMMS: {
type: 'SMMS',
text: 'SM.MS'
},
UPOSS: {
type: 'UPOSS',
text: '又拍云'
},
QINIUOSS: {
type: 'QINIUOSS',
text: '七牛云'
},
ALIOSS: {
type: 'ALIOSS',
text: '阿里云'
},
BAIDUBOS: {
type: 'BAIDUBOS',
text: '百度云'
},
TENCENTCOS: {
type: 'TENCENTCOS',
text: '腾讯云'
},
HUAWEIOBS: {
type: 'HUAWEIOBS',
text: '华为云'
},
MINIO: {
type: 'MINIO',
text: 'MinIO'
}
}
import { attachmentTypes } from '@/core/constant'
export default {
name: 'AttachmentDetailModal',
mixins: [mixin, mixinDevice],
filters: {
typeText(type) {
return type ? attachmentType[type].text : ''
return type ? attachmentTypes[type].text : ''
}
},
props: {

View File

@ -1,169 +0,0 @@
<template>
<div>
<a-drawer
:afterVisibleChange="handleAfterVisibleChanged"
:title="title"
:visible="visible"
:width="isMobile() ? '100%' : drawerWidth"
closable
destroyOnClose
@close="onClose"
>
<a-row align="middle" type="flex">
<a-input-search v-model="queryParam.keyword" enterButton placeholder="搜索" @search="handleQuery()" />
</a-row>
<a-divider />
<a-row align="middle" type="flex">
<a-col :span="24">
<a-spin :spinning="loading" class="attachments-group">
<a-empty v-if="attachments.length === 0" />
<div
v-for="(item, index) in attachments"
v-else
:key="index"
class="attach-item attachments-group-item"
@click="handleSelectAttachment(item)"
>
<span v-if="!handleJudgeMediaType(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-spin>
</a-col>
</a-row>
<a-divider />
<div class="page-wrapper">
<a-pagination
:current="pagination.page"
:defaultPageSize="pagination.size"
:total="pagination.total"
showLessItems
@change="handlePaginationChange"
></a-pagination>
</div>
<a-divider class="divider-transparent" />
<div class="bottom-control">
<a-space>
<a-button v-if="isChooseAvatar" type="dashed" @click="handleSelectGravatar">使 Gravatar</a-button>
<a-button type="primary" @click="handleShowUploadModal"></a-button>
</a-space>
</div>
</a-drawer>
<AttachmentUploadModal :visible.sync="uploadVisible" @close="onUploadClose" />
</div>
</template>
<script>
import { mixin, mixinDevice } from '@/mixins/mixin.js'
import apiClient from '@/utils/api-client'
export default {
name: 'AttachmentSelectDrawer',
mixins: [mixin, mixinDevice],
model: {
prop: 'visible',
event: 'close'
},
props: {
visible: {
type: Boolean,
required: false,
default: false
},
drawerWidth: {
type: Number,
required: false,
default: 480
},
title: {
type: String,
required: false,
default: '选择附件'
},
isChooseAvatar: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
uploadVisible: false,
loading: true,
pagination: {
page: 1,
size: 12,
sort: null,
total: 1
},
queryParam: {
page: 0,
size: 12,
sort: null,
keyword: null
},
attachments: []
}
},
methods: {
handleShowUploadModal() {
this.uploadVisible = true
},
handleListAttachments() {
this.loading = true
this.queryParam.page = this.pagination.page - 1
this.queryParam.size = this.pagination.size
this.queryParam.sort = this.pagination.sort
apiClient.attachment
.list(this.queryParam)
.then(response => {
this.attachments = response.data.content
this.pagination.total = response.data.total
})
.finally(() => {
this.loading = false
})
},
handleQuery() {
this.handlePaginationChange(1, this.pagination.size)
},
handleSelectAttachment(item) {
this.$emit('listenToSelect', item)
},
handleSelectGravatar() {
this.$emit('listenToSelectGravatar')
},
handlePaginationChange(page, pageSize) {
this.pagination.page = page
this.pagination.size = pageSize
this.handleListAttachments()
},
onUploadClose() {
this.handlePaginationChange(1, this.pagination.size)
},
handleAfterVisibleChanged(visible) {
if (visible) {
this.handleListAttachments()
}
},
handleJudgeMediaType(attachment) {
const mediaType = attachment.mediaType
//
if (mediaType) {
const prefix = mediaType.split('/')[0]
return prefix === 'image'
}
return false
},
onClose() {
this.$emit('close', false)
}
}
}
</script>

View File

@ -0,0 +1,461 @@
<template>
<a-modal v-model="modalVisible" :afterClose="onAfterClose" :title="title" :width="1024" destroyOnClose>
<div class="table-page-search-wrapper">
<a-form layout="inline">
<a-row :gutter="24">
<a-col :md="6" :sm="24">
<a-form-item label="关键词:">
<a-input v-model="list.params.keyword" @keyup.enter="handleSearch()" />
</a-form-item>
</a-col>
<a-col :md="6" :sm="24">
<a-form-item label="存储位置:">
<a-select
v-model="list.params.attachmentType"
:loading="types.loading"
allowClear
@change="handleSearch()"
>
<a-select-option v-for="item in types.data" :key="item" :value="item">
{{ item | typeText }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :md="6" :sm="24">
<a-form-item label="文件类型:">
<a-select
v-model="list.params.mediaType"
:loading="mediaTypes.loading"
allowClear
@change="handleSearch()"
>
<a-select-option v-for="(item, index) in mediaTypes.data" :key="index" :value="item">
{{ item }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :md="6" :sm="24">
<span class="table-page-search-submitButtons">
<a-space>
<a-button type="primary" @click="handleSearch()"></a-button>
<a-button @click="handleResetParam(), handleListAttachments()">重置</a-button>
</a-space>
</span>
</a-col>
</a-row>
</a-form>
</div>
<div class="mb-0 table-operator">
<a-button icon="cloud-upload" type="primary" @click="upload.visible = true">上传</a-button>
</div>
<a-divider />
<a-list
:dataSource="list.data"
:grid="{ gutter: 6, xs: 2, sm: 2, md: 4, lg: 6, xl: 6, xxl: 6 }"
: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"
/>
</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>
<div class="page-wrapper"></div>
<div class="flex justify-between">
<a-popover placement="right" title="预览" trigger="click">
<template slot="content">
<a-tabs v-if="list.selected.length" default-active-key="markdown" tab-position="left">
<a-tab-pane key="markdown" tab="Markdown">
<div class="text-slate-400" v-html="markdownSyntaxList.join('<br />')"></div>
</a-tab-pane>
<a-tab-pane key="html" force-render tab="HTML">
<div class="text-slate-400">
<span v-for="(item, index) in htmlSyntaxList" :key="index" class="text-slate-400">
{{ item }}<br />
</span>
</div>
</a-tab-pane>
</a-tabs>
<div v-else class="text-slate-400">未选择附件</div>
</template>
<a-tooltip placement="top" title="点击预览">
<div class="self-center text-slate-400 select-none cursor-pointer hover:text-blue-400 transition-all">
已选择 {{ list.selected.length }}
</div>
</a-tooltip>
</a-popover>
<div class="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"
showLessItems
showSizeChanger
@change="handlePageChange"
@showSizeChange="handlePageSizeChange"
/>
</div>
</div>
<template slot="footer">
<a-button @click="modalVisible = false">取消</a-button>
<a-button type="primary" :disabled="!list.selected.length" @click="handleConfirm"></a-button>
</template>
<AttachmentUploadModal :visible.sync="upload.visible" @close="handleSearch" />
<AttachmentDetailModal :attachment="list.current" :visible.sync="detailVisible" @delete="handleListAttachments()">
<template #extraFooter>
<a-button :disabled="selectPreviousButtonDisabled" @click="handleSelectPrevious"></a-button>
<a-button :disabled="selectNextButtonDisabled" @click="handleSelectNext"></a-button>
<a-button @click="handleItemClick(list.current)" type="primary">
{{ list.selected.findIndex(item => item.id === list.current.id) > -1 ? '取消选择' : '选择' }}
</a-button>
</template>
</AttachmentDetailModal>
</a-modal>
</template>
<script>
import apiClient from '@/utils/api-client'
import { attachmentTypes } from '@/core/constant'
export default {
name: 'AttachmentSelectModal',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: '选择附件'
},
multiSelect: {
type: Boolean,
default: true
}
},
data() {
return {
list: {
data: [],
total: 0,
hasNext: false,
hasPrevious: false,
loading: false,
params: {
page: 0,
size: 12,
keyword: undefined,
mediaType: undefined,
attachmentType: undefined
},
selected: [],
current: {}
},
mediaTypes: {
data: [],
loading: false
},
types: {
data: [],
loading: false
},
upload: {
visible: false
},
detailVisible: false
}
},
computed: {
modalVisible: {
get() {
return this.visible
},
set(value) {
this.$emit('update:visible', value)
}
},
pagination() {
return {
page: this.list.params.page + 1,
size: this.list.params.size,
total: this.list.total
}
},
selectPreviousButtonDisabled() {
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.current.id)
return index === this.list.data.length - 1 && !this.list.hasNext
},
isImage() {
return function (attachment) {
if (!attachment || !attachment.mediaType) {
return false
}
return attachment.mediaType.startsWith('image')
}
},
isItemSelect() {
return function (attachment) {
return this.list.selected.findIndex(item => item.id === attachment.id) > -1
}
},
markdownSyntaxList() {
if (!this.list.selected.length) {
return []
}
return this.list.selected.map(item => {
return `![${item.name}](${encodeURI(item.path)})`
})
},
htmlSyntaxList() {
if (!this.list.selected.length) {
return []
}
return this.list.selected.map(item => {
return `<img src="${encodeURI(item.path)}" alt="${item.name}">`
})
}
},
watch: {
modalVisible(value) {
if (value) {
this.handleListAttachments()
this.handleListMediaTypes()
this.handleListTypes()
}
}
},
methods: {
/**
* List attachments
*/
async handleListAttachments() {
try {
this.list.loading = true
const response = await apiClient.attachment.list(this.list.params)
this.list.data = response.data.content
this.list.total = response.data.total
this.list.hasNext = response.data.hasNext
this.list.hasPrevious = response.data.hasPrevious
} catch (error) {
this.$log.error(error)
} finally {
this.list.loading = false
}
},
/**
* List attachment media types
*/
async handleListMediaTypes() {
try {
this.mediaTypes.loading = true
const response = await apiClient.attachment.listMediaTypes()
this.mediaTypes.data = response.data
} catch (error) {
this.$log.error(error)
} finally {
this.mediaTypes.loading = false
}
},
/**
* List attachment upload types
*/
async handleListTypes() {
try {
this.types.loading = true
const response = await apiClient.attachment.listTypes()
this.types.data = response.data
} catch (error) {
this.$log.error(error)
} finally {
this.types.loading = false
}
},
/**
* Handle page change
*/
handlePageChange(page = 1) {
this.list.params.page = page - 1
this.handleListAttachments()
},
/**
* Search attachments
*/
handleSearch() {
this.handlePageChange(1)
},
/**
* Reset search params
*/
handleResetParam() {
this.list.params = {
page: 0,
size: 12,
keyword: undefined,
mediaType: undefined,
attachmentType: undefined
}
},
/**
* Handle page size change
*/
handlePageSizeChange(current, size) {
this.$log.debug(`Current: ${current}, PageSize: ${size}`)
this.list.params.page = 0
this.list.params.size = size
this.handleListAttachments()
},
handleItemClick(attachment) {
// single select
if (!this.multiSelect) {
this.$emit('confirm', {
raw: [attachment],
markdown: [`![${attachment.name}](${encodeURI(attachment.path)})`],
html: [`<img src="${encodeURI(attachment.path)}" alt="${attachment.name}">`]
})
this.modalVisible = false
return
}
const isSelect = this.list.selected.findIndex(item => item.id === attachment.id) > -1
isSelect ? 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)
},
handleConfirm() {
this.$emit('confirm', {
raw: this.list.selected,
markdown: this.markdownSyntaxList,
html: this.htmlSyntaxList
})
this.modalVisible = false
},
handleOpenDetail(attachment) {
this.list.current = attachment
this.detailVisible = true
},
/**
* Select previous attachment
*/
async handleSelectPrevious() {
const index = this.list.data.findIndex(item => item.id === this.list.current.id)
if (index > 0) {
this.list.current = this.list.data[index - 1]
return
}
if (index === 0 && this.list.hasPrevious) {
this.list.params.page--
await this.handleListAttachments()
this.list.current = this.list.data[this.list.data.length - 1]
}
},
/**
* Select next attachment
*/
async handleSelectNext() {
const index = this.list.data.findIndex(item => item.id === this.list.current.id)
if (index < this.list.data.length - 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.current = this.list.data[0]
}
},
onAfterClose() {
this.handleResetParam()
this.list.selected = []
}
},
filters: {
typeText(type) {
return attachmentTypes[type].text
}
}
}
</script>

View File

@ -1,11 +1,15 @@
<template>
<div>
<a-input :defaultValue="defaultValue" :placeholder="placeholder" :value="value" @change="onInputChange">
<a slot="addonAfter" href="javascript:void(0);" @click="attachmentDrawerVisible = true">
<a slot="addonAfter" href="javascript:void(0);" @click="attachmentModalVisible = true">
<a-icon type="picture" />
</a>
</a-input>
<AttachmentSelectDrawer v-model="attachmentDrawerVisible" :title="title" @listenToSelect="handleSelectAttachment" />
<AttachmentSelectModal
:multiSelect="false"
:visible.sync="attachmentModalVisible"
@confirm="handleSelectAttachment"
/>
</div>
</template>
<script>
@ -31,16 +35,17 @@ export default {
},
data() {
return {
attachmentDrawerVisible: false
attachmentModalVisible: false
}
},
methods: {
onInputChange(e) {
this.$emit('input', e.target.value)
},
handleSelectAttachment(data) {
this.$emit('input', encodeURI(data.path))
this.attachmentDrawerVisible = false
handleSelectAttachment({ raw }) {
if (raw.length) {
this.$emit('input', encodeURI(raw[0].path))
}
}
}
}

View File

@ -3,8 +3,9 @@ import Vue from 'vue'
import Ellipsis from '@/components/Ellipsis'
import FooterToolbar from '@/components/FooterToolbar'
import FilePondUpload from '@/components/Upload/FilePondUpload'
import AttachmentSelectDrawer from './Attachment/AttachmentSelectDrawer'
import AttachmentUploadModal from './Attachment/AttachmentUploadModal'
import AttachmentSelectModal from './Attachment/AttachmentSelectModal'
import AttachmentDetailModal from './Attachment/AttachmentDetailModal'
import ReactiveButton from './Button/ReactiveButton'
import PostTag from './Post/PostTag'
import AttachmentInput from './Input/AttachmentInput'
@ -13,8 +14,9 @@ const _components = {
Ellipsis,
FooterToolbar,
FilePondUpload,
AttachmentSelectDrawer,
AttachmentUploadModal,
AttachmentSelectModal,
AttachmentDetailModal,
ReactiveButton,
PostTag,
AttachmentInput

View File

@ -56,3 +56,42 @@ export const actionLogTypes = {
text: '登录验证'
}
}
export const attachmentTypes = {
LOCAL: {
type: 'LOCAL',
text: '本地'
},
SMMS: {
type: 'SMMS',
text: 'SM.MS'
},
UPOSS: {
type: 'UPOSS',
text: '又拍云'
},
QINIUOSS: {
type: 'QINIUOSS',
text: '七牛云'
},
ALIOSS: {
type: 'ALIOSS',
text: '阿里云'
},
BAIDUBOS: {
type: 'BAIDUBOS',
text: '百度云'
},
TENCENTCOS: {
type: 'TENCENTCOS',
text: '腾讯云'
},
HUAWEIOBS: {
type: 'HUAWEIOBS',
text: '华为云'
},
MINIO: {
type: 'MINIO',
text: 'MinIO'
}
}

View File

@ -818,7 +818,7 @@ body {
.attachments-group {
&-item {
padding: 0;
height: 140px;
height: 130px;
&-img {
display: block;

View File

@ -92,8 +92,12 @@
loading="lazy"
/>
</div>
<a-card-meta class="p-3">
<ellipsis slot="description" :length="isMobile() ? 12 : 16" tooltip>{{ item.name }}</ellipsis>
<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"
@ -140,58 +144,18 @@
<script>
import { mixin, mixinDevice } from '@/mixins/mixin.js'
import { PageView } from '@/layouts'
import AttachmentDetailModal from './components/AttachmentDetailModal.vue'
import apiClient from '@/utils/api-client'
import { mapGetters } from 'vuex'
const attachmentType = {
LOCAL: {
type: 'LOCAL',
text: '本地'
},
SMMS: {
type: 'SMMS',
text: 'SM.MS'
},
UPOSS: {
type: 'UPOSS',
text: '又拍云'
},
QINIUOSS: {
type: 'QINIUOSS',
text: '七牛云'
},
ALIOSS: {
type: 'ALIOSS',
text: '阿里云'
},
BAIDUBOS: {
type: 'BAIDUBOS',
text: '百度云'
},
TENCENTCOS: {
type: 'TENCENTCOS',
text: '腾讯云'
},
HUAWEIOBS: {
type: 'HUAWEIOBS',
text: '华为云'
},
MINIO: {
type: 'MINIO',
text: 'MinIO'
}
}
import { attachmentTypes } from '@/core/constant'
export default {
components: {
PageView,
AttachmentDetailModal
PageView
},
mixins: [mixin, mixinDevice],
filters: {
typeText(type) {
return attachmentType[type].text
return attachmentTypes[type].text
}
},
data() {

View File

@ -1,250 +0,0 @@
<template>
<div>
<a-drawer
:afterVisibleChange="handleAfterVisibleChanged"
:visible="visible"
:width="isMobile() ? '100%' : '480'"
closable
destroyOnClose
title="附件库"
@close="onClose"
>
<a-row align="middle" type="flex">
<a-input-search v-model="queryParam.keyword" enterButton placeholder="搜索附件" @search="handleQuery()" />
</a-row>
<a-divider />
<a-row align="middle" type="flex">
<a-col :span="24">
<a-spin :spinning="loading" class="attachments-group">
<a-empty v-if="formattedDatas.length === 0" />
<div
v-for="(item, index) in formattedDatas"
v-else
:key="index"
class="attach-item attachments-group-item"
@click="handleShowDetailDrawer(item)"
@contextmenu.prevent="handleContextMenu($event, item)"
>
<span v-if="!handleJudgeMediaType(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-spin>
</a-col>
</a-row>
<a-divider />
<div class="page-wrapper">
<a-pagination
:current="pagination.page"
:defaultPageSize="pagination.size"
:total="pagination.total"
showLessItems
@change="handlePaginationChange"
></a-pagination>
</div>
<!-- <AttachmentDetailDrawer
v-model="detailVisible"
v-if="selectedAttachment"
:attachment="selectedAttachment"
@delete="handleListAttachments"
/> -->
<a-divider class="divider-transparent" />
<div class="bottom-control">
<a-button type="primary" @click="uploadVisible = true">上传附件</a-button>
</div>
</a-drawer>
<AttachmentUploadModal :visible.sync="uploadVisible" @close="onUploadClose" />
</div>
</template>
<script>
import { mixin, mixinDevice } from '@/mixins/mixin.js'
// import AttachmentDetailDrawer from './AttachmentDetailDrawer'
import apiClient from '@/utils/api-client'
export default {
name: 'AttachmentDrawer',
mixins: [mixin, mixinDevice],
components: {
// AttachmentDetailDrawer
},
model: {
prop: 'visible',
event: 'close'
},
props: {
visible: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
attachmentType: {
LOCAL: {
type: 'LOCAL',
text: '本地'
},
SMMS: {
type: 'SMMS',
text: 'SM.MS'
},
UPOSS: {
type: 'UPOSS',
text: '又拍云'
},
QINIUOSS: {
type: 'QINIUOSS',
text: '七牛云'
},
ALIOSS: {
type: 'ALIOSS',
text: '阿里云'
},
BAIDUBOS: {
type: 'BAIDUBOS',
text: '百度云'
},
TENCENTCOS: {
type: 'TENCENTCOS',
text: '腾讯云'
},
HUAWEIOBS: {
type: 'HUAWEIOBS',
text: '华为云'
},
MINIO: {
type: 'MINIO',
text: 'MinIO'
}
},
detailVisible: false,
attachmentDrawerVisible: false,
uploadVisible: false,
loading: true,
pagination: {
page: 1,
size: 12,
sort: null,
total: 1
},
queryParam: {
page: 0,
size: 12,
sort: null,
keyword: null
},
attachments: [],
selectedAttachment: {}
}
},
computed: {
formattedDatas() {
return this.attachments.map(attachment => {
attachment.typeProperty = this.attachmentType[attachment.type]
return attachment
})
}
},
methods: {
handleShowDetailDrawer(attachment) {
this.selectedAttachment = attachment
this.$log.debug('Show detail of', attachment)
this.detailVisible = true
},
handleContextMenu(event, item) {
this.$contextmenu({
items: [
{
label: '复制图片链接',
onClick: () => {
const text = `${encodeURI(item.path)}`
this.$copyText(text)
.then(message => {
this.$log.debug('copy', message)
this.$message.success('复制成功!')
})
.catch(err => {
this.$log.debug('copy.err', err)
this.$message.error('复制失败!')
})
},
divided: true
},
{
label: '复制 Markdown 格式链接',
onClick: () => {
const text = `![${item.name}](${encodeURI(item.path)})`
this.$copyText(text)
.then(message => {
this.$log.debug('copy', message)
this.$message.success('复制成功!')
})
.catch(err => {
this.$log.debug('copy.err', err)
this.$message.error('复制失败!')
})
}
}
],
event,
zIndex: 1001,
minWidth: 210
})
return false
},
handleListAttachments() {
this.loading = true
this.queryParam.page = this.pagination.page - 1
this.queryParam.size = this.pagination.size
this.queryParam.sort = this.pagination.sort
apiClient.attachment
.list(this.queryParam)
.then(response => {
this.attachments = response.data.content
this.pagination.total = response.data.total
})
.finally(() => {
this.loading = false
})
},
handleQuery() {
this.handlePaginationChange(1, this.pagination.size)
},
handlePaginationChange(page, pageSize) {
this.pagination.page = page
this.pagination.size = pageSize
this.handleListAttachments()
},
onUploadClose() {
this.handlePaginationChange(1, this.pagination.size)
},
handleAfterVisibleChanged(visible) {
if (visible) {
this.handleListAttachments()
}
},
handleJudgeMediaType(attachment) {
const mediaType = attachment.mediaType
//
if (mediaType) {
const prefix = mediaType.split('/')[0]
return prefix === 'image'
}
// false
return false
},
onClose() {
this.$emit('close', false)
}
}
}
</script>

View File

@ -122,10 +122,10 @@
</a-col>
</a-row>
<AttachmentSelectDrawer
v-model="attachmentDrawerVisible"
title="选择附件"
@listenToSelect="handleSelectAttachment"
<AttachmentSelectModal
:multiSelect="false"
:visible.sync="attachmentSelectVisible"
@confirm="handleSelectAttachment"
/>
<footer-tool-bar v-if="themeConfigurations.length > 0" class="w-full">
@ -167,7 +167,7 @@ export default {
},
data() {
return {
attachmentDrawerVisible: false,
attachmentSelectVisible: false,
themeConfigurations: [],
themeSettings: [],
settingLoading: true,
@ -251,12 +251,14 @@ export default {
},
handleShowSelectAttachment(field) {
this.selectedField = field
this.attachmentDrawerVisible = true
this.attachmentSelectVisible = true
},
handleSelectAttachment(data) {
this.$set(this.themeSettings, this.selectedField, encodeURI(data.path))
handleSelectAttachment({ raw }) {
if (raw.length) {
this.$set(this.themeSettings, this.selectedField, encodeURI(raw[0].path))
}
// this.themeSettings[this.selectedField] = encodeURI(data.path)
this.attachmentDrawerVisible = false
this.attachmentSelectVisible = false
},
handleAfterVisibleChanged(visible) {
if (visible) {

View File

@ -14,7 +14,7 @@
></ReactiveButton>
<a-button :loading="previewSaving" @click="handlePreview"></a-button>
<a-button type="primary" @click="postSettingVisible = true">发布</a-button>
<a-button type="dashed" @click="attachmentDrawerVisible = true">附件库</a-button>
<a-button type="dashed" @click="attachmentSelectVisible = true">附件库</a-button>
</a-space>
</template>
<a-row :gutter="12">
@ -39,31 +39,31 @@
@onUpdate="onUpdateFromSetting"
/>
<AttachmentDrawer v-model="attachmentDrawerVisible" />
<AttachmentSelectModal :visible.sync="attachmentSelectVisible" @confirm="handleSelectAttachment" />
</page-view>
</template>
<script>
import { mixin, mixinDevice, mixinPostEdit } from '@/mixins/mixin.js'
import { datetimeFormat } from '@/utils/datetime'
// components
import PostSettingModal from './components/PostSettingModal'
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
import MarkdownEditor from '@/components/Editor/MarkdownEditor'
import { PageView } from '@/layouts'
// libs
import { mixin, mixinDevice, mixinPostEdit } from '@/mixins/mixin.js'
import { datetimeFormat } from '@/utils/datetime'
import apiClient from '@/utils/api-client'
export default {
mixins: [mixin, mixinDevice, mixinPostEdit],
components: {
PostSettingModal,
AttachmentDrawer,
MarkdownEditor,
PageView
},
data() {
return {
attachmentDrawerVisible: false,
attachmentSelectVisible: false,
postSettingVisible: false,
postToStage: {},
contentChanges: 0,
@ -84,18 +84,11 @@ export default {
})
},
destroyed() {
if (this.attachmentDrawerVisible) {
this.attachmentDrawerVisible = false
}
if (window.onbeforeunload) {
window.onbeforeunload = null
}
},
beforeRouteLeave(to, from, next) {
if (this.attachmentDrawerVisible) {
this.attachmentDrawerVisible = false
}
if (this.contentChanges <= 1) {
next()
} else {
@ -219,6 +212,13 @@ export default {
})
}
},
handleSelectAttachment({ markdown }) {
this.$set(
this.postToStage,
'originalContent',
(this.postToStage.originalContent || '') + '\n' + markdown.join(`\n`)
)
},
handleRestoreSavedStatus() {
this.contentChanges = 0
},

View File

@ -144,10 +144,10 @@
@click="handleCreateOrUpdate()"
></ReactiveButton>
</template>
<AttachmentSelectDrawer
v-model="attachmentSelectVisible"
:drawerWidth="480"
@listenToSelect="handleSelectPostThumb"
<AttachmentSelectModal
:multiSelect="false"
:visible.sync="attachmentSelectVisible"
@confirm="handleSelectPostThumbnail"
/>
</a-modal>
</template>
@ -374,8 +374,10 @@ export default {
* Select post thumbnail
* @param data
*/
handleSelectPostThumb(data) {
this.form.model.thumbnail = encodeURI(data.path)
handleSelectPostThumbnail({ raw }) {
if (raw.length) {
this.form.model.thumbnail = encodeURI(raw[0].path)
}
this.attachmentSelectVisible = false
},

View File

@ -14,7 +14,7 @@
></ReactiveButton>
<a-button :loading="previewSaving" @click="handlePreview"></a-button>
<a-button type="primary" @click="sheetSettingVisible = true">发布</a-button>
<a-button type="dashed" @click="attachmentDrawerVisible = true">附件库</a-button>
<a-button type="dashed" @click="attachmentSelectVisible = true">附件库</a-button>
</a-space>
</template>
<a-row :gutter="12">
@ -40,7 +40,7 @@
@onUpdate="onUpdateFromSetting"
/>
<AttachmentDrawer v-model="attachmentDrawerVisible" />
<AttachmentSelectModal :visible.sync="attachmentSelectVisible" @confirm="handleSelectAttachment" />
</page-view>
</template>
@ -49,21 +49,19 @@ import { mixin, mixinDevice, mixinPostEdit } from '@/mixins/mixin.js'
import { datetimeFormat } from '@/utils/datetime'
import { PageView } from '@/layouts'
import SheetSettingModal from './components/SheetSettingModal'
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
import MarkdownEditor from '@/components/Editor/MarkdownEditor'
import apiClient from '@/utils/api-client'
export default {
components: {
PageView,
AttachmentDrawer,
SheetSettingModal,
MarkdownEditor
},
mixins: [mixin, mixinDevice, mixinPostEdit],
data() {
return {
attachmentDrawerVisible: false,
attachmentSelectVisible: false,
sheetSettingVisible: false,
sheetToStage: {},
contentChanges: 0,
@ -85,23 +83,11 @@ export default {
})
},
destroyed: function () {
if (this.sheetSettingVisible) {
this.sheetSettingVisible = false
}
if (this.attachmentDrawerVisible) {
this.attachmentDrawerVisible = false
}
if (window.onbeforeunload) {
window.onbeforeunload = null
}
},
beforeRouteLeave(to, from, next) {
if (this.sheetSettingVisible) {
this.sheetSettingVisible = false
}
if (this.attachmentDrawerVisible) {
this.attachmentDrawerVisible = false
}
if (this.contentChanges <= 1) {
next()
} else {
@ -221,6 +207,13 @@ export default {
})
}
},
handleSelectAttachment({ markdown }) {
this.$set(
this.sheetToStage,
'originalContent',
(this.sheetToStage.originalContent || '') + '\n' + markdown.join(`\n`)
)
},
handleRestoreSavedStatus() {
this.contentChanges = 0
},

View File

@ -94,8 +94,8 @@
</div>
<a-divider class="divider-transparent" />
</div>
<AttachmentSelectDrawer v-model="thumbDrawerVisible" :drawerWidth="480" @listenToSelect="handleSelectSheetThumb" />
<AttachmentSelectModal :multiSelect="false" :visible.sync="thumbDrawerVisible" @confirm="handleSelectSheetThumb" />
<a-drawer
:visible="advancedVisible"
:width="isMobile() ? '100%' : '480'"
@ -311,8 +311,10 @@ export default {
this.customTplsLoading = false
})
},
handleSelectSheetThumb(data) {
this.selectedSheet.thumbnail = encodeURI(data.path)
handleSelectSheetThumb({ raw }) {
if (raw.length) {
this.selectedSheet.thumbnail = encodeURI(raw[0].path)
}
this.thumbDrawerVisible = false
},
handlePublishClick() {

View File

@ -132,10 +132,10 @@
@click="handleCreateOrUpdate()"
></ReactiveButton>
</template>
<AttachmentSelectDrawer
v-model="attachmentSelectVisible"
:drawerWidth="480"
@listenToSelect="handleSelectSheetThumb"
<AttachmentSelectModal
:multiSelect="false"
:visible.sync="attachmentSelectVisible"
@confirm="handleSelectSheetThumbnail"
/>
</a-modal>
</template>
@ -326,8 +326,10 @@ export default {
* Select sheet thumbnail
* @param data
*/
handleSelectSheetThumb(data) {
this.form.model.thumbnail = encodeURI(data.path)
handleSelectSheetThumbnail({ raw }) {
if (raw.length) {
this.form.model.thumbnail = encodeURI(raw[0].path)
}
this.attachmentSelectVisible = false
},
/**

View File

@ -135,7 +135,7 @@
</a-tooltip>
</template>
<template slot="footer">
<a-button type="dashed" @click="attachmentDrawer.visible = true">附件库</a-button>
<a-button type="dashed" @click="attachmentSelect.visible = true">附件库</a-button>
<ReactiveButton
:errored="form.saveErrored"
:loading="form.saving"
@ -170,7 +170,7 @@
@close="onJournalCommentsDrawerClose"
/>
<AttachmentDrawer v-model="attachmentDrawer.visible" />
<AttachmentSelectModal :visible.sync="attachmentSelect.visible" @confirm="handleSelectAttachment" />
</page-view>
</template>
@ -178,7 +178,6 @@
// components
import { PageView } from '@/layouts'
import TargetCommentDrawer from '../../comment/components/TargetCommentDrawer'
import AttachmentDrawer from '../../attachment/components/AttachmentDrawer'
// libs
import { mixin, mixinDevice } from '@/mixins/mixin.js'
@ -187,7 +186,7 @@ import apiClient from '@/utils/api-client'
export default {
mixins: [mixin, mixinDevice],
components: { PageView, TargetCommentDrawer, AttachmentDrawer },
components: { PageView, TargetCommentDrawer },
data() {
return {
list: {
@ -224,7 +223,7 @@ export default {
journalCommentDrawer: {
visible: false
},
attachmentDrawer: {
attachmentSelect: {
visible: false
},
optionModal: {
@ -377,6 +376,9 @@ export default {
this.handleListOptions()
this.refreshOptionsCache()
})
},
handleSelectAttachment({ markdown }) {
this.$set(this.form.model, 'sourceContent', (this.form.model.sourceContent || '') + '\n' + markdown.join('\n'))
}
}
}

View File

@ -98,7 +98,7 @@
:src="form.model.url || '/images/placeholder.jpg'"
class="w-full cursor-pointer"
style="border-radius: 4px"
@click="attachmentSelectDrawer.visible = true"
@click="attachmentSelectModal.visible = true"
/>
</div>
<a-input v-model="form.model.url" placeholder="点击封面图选择图片,或者输入外部链接" />
@ -161,11 +161,10 @@
</a-popconfirm>
</a-space>
</div>
<AttachmentSelectDrawer
v-model="attachmentSelectDrawer.visible"
:drawerWidth="480"
@listenToSelect="handleAttachmentSelected"
<AttachmentSelectModal
:multiSelect="false"
:visible.sync="attachmentSelectModal.visible"
@confirm="handleAttachmentSelected"
/>
</a-drawer>
</page-view>
@ -215,7 +214,7 @@ export default {
deleteErrored: false
},
attachmentSelectDrawer: {
attachmentSelectModal: {
visible: false
},
@ -347,10 +346,12 @@ export default {
this.handleListPhotoTeams()
}
},
handleAttachmentSelected(data) {
this.form.model.url = encodeURI(data.path)
this.form.model.thumbnail = encodeURI(data.thumbPath)
this.attachmentSelectDrawer.visible = false
handleAttachmentSelected({ raw }) {
if (raw.length) {
this.form.model.url = encodeURI(raw[0].path)
this.form.model.thumbnail = encodeURI(raw[0].thumbPath)
}
this.attachmentSelectModal.visible = false
},
handleResetParam() {
this.list.queryParam.keyword = null

View File

@ -18,8 +18,8 @@
</a-form-model-item>
<a-form-model-item label="存储位置:">
<a-select v-model="options.attachment_type">
<a-select-option v-for="item in Object.keys(attachmentType)" :key="item" :value="item">
{{ attachmentType[item].text }}
<a-select-option v-for="item in Object.keys(attachmentTypes)" :key="item" :value="item">
{{ attachmentTypes[item].text }}
</a-select-option>
</a-select>
</a-form-model-item>
@ -272,6 +272,8 @@
</div>
</template>
<script>
import { attachmentTypes } from '@/core/constant'
const tencentCosRegions = [
{
text: '北京一区',
@ -324,44 +326,6 @@ const qiniuOssZones = [
value: 'as0'
}
]
const attachmentType = {
LOCAL: {
type: 'LOCAL',
text: '本地'
},
SMMS: {
type: 'SMMS',
text: 'SM.MS'
},
UPOSS: {
type: 'UPOSS',
text: '又拍云'
},
QINIUOSS: {
type: 'QINIUOSS',
text: '七牛云'
},
ALIOSS: {
type: 'ALIOSS',
text: '阿里云'
},
BAIDUBOS: {
type: 'BAIDUBOS',
text: '百度云'
},
TENCENTCOS: {
type: 'TENCENTCOS',
text: '腾讯云'
},
HUAWEIOBS: {
type: 'HUAWEIOBS',
text: '华为云'
},
MINIO: {
type: 'MINIO',
text: 'MinIO'
}
}
export default {
name: 'AttachmentTab',
props: {
@ -380,7 +344,6 @@ export default {
},
data() {
return {
attachmentType,
wrapperCol: {
xl: { span: 8 },
lg: { span: 8 },
@ -389,6 +352,7 @@ export default {
},
tencentCosRegions,
qiniuOssZones,
attachmentTypes,
rules: {}
}
},

View File

@ -9,7 +9,7 @@
:size="104"
:src="userForm.model.avatar || '//cn.gravatar.com/avatar/?s=256&d=mm'"
class="cursor-pointer"
@click="attachmentDrawer.visible = true"
@click="attachmentSelect.visible = true"
/>
</a-tooltip>
<div class="mt-4 mb-1 text-xl font-medium leading-5" style="color: rgba(0, 0, 0, 0.85)">
@ -52,15 +52,15 @@
<a-tab-pane key="1">
<span slot="tab"> <a-icon type="idcard" />基本资料 </span>
<a-form-model
ref="userForm"
:model="userForm.model"
:rules="userForm.rules"
:wrapperCol="{
xl: { span: 15 },
lg: { span: 15 },
sm: { span: 15 },
xs: { span: 24 }
}"
ref="userForm"
:model="userForm.model"
:rules="userForm.rules"
layout="vertical"
>
<a-form-model-item label="用户名:" prop="username">
@ -92,15 +92,15 @@
<a-tab-pane key="2">
<span slot="tab"> <a-icon type="lock" />密码 </span>
<a-form-model
ref="passwordForm"
:model="passwordForm.model"
:rules="passwordForm.rules"
:wrapperCol="{
xl: { span: 15 },
lg: { span: 15 },
sm: { span: 15 },
xs: { span: 24 }
}"
ref="passwordForm"
:model="passwordForm.model"
:rules="passwordForm.rules"
layout="vertical"
>
<a-form-model-item label="原密码:" prop="oldPassword">
@ -196,14 +196,6 @@
</a-col>
</a-row>
<AttachmentSelectDrawer
v-model="attachmentDrawer.visible"
isChooseAvatar
title="选择头像"
@listenToSelect="handleSelectAvatar"
@listenToSelectGravatar="handleSelectGravatar"
/>
<a-modal
:centered="true"
:closable="false"
@ -216,7 +208,7 @@
icon="safety-certificate"
>
<template slot="footer">
<a-button key="back" @click="handleCloseMFAuthModal"> </a-button>
<a-button key="back" @click="handleCloseMFAuthModal"> </a-button>
<ReactiveButton
key="submit"
:errored="mfaParam.errored"
@ -250,6 +242,13 @@
</a-form-model-item>
</a-form-model>
</a-modal>
<AttachmentSelectModal
:multiSelect="false"
:visible.sync="attachmentSelect.visible"
title="选择头像"
@confirm="handleSelectAvatar"
/>
</div>
</template>
@ -268,7 +267,7 @@ export default {
}
}
return {
attachmentDrawer: {
attachmentSelect: {
visible: false
},
userForm: {
@ -432,14 +431,16 @@ export default {
this.userForm.errored = false
}
},
handleSelectAvatar(data) {
this.userForm.model.avatar = encodeURI(data.path)
this.attachmentDrawer.visible = false
handleSelectAvatar({ raw }) {
if (raw.length) {
this.userForm.model.avatar = encodeURI(raw[0].path)
}
this.attachmentSelect.visible = false
},
handleSelectGravatar() {
this.userForm.model.avatar =
'//cn.gravatar.com/avatar/' + new MD5().update(this.userForm.model.email).digest('hex') + '&d=mm'
this.attachmentDrawer.visible = false
this.attachmentSelect.visible = false
},
handleMFASwitch(useMFAuth) {
this.mfaParam.switch.loading = true