mirror of https://github.com/halo-dev/halo-admin
parent
51842f2afa
commit
ea955d49dd
|
@ -23,7 +23,7 @@
|
|||
"@codemirror/basic-setup": "^0.19.1",
|
||||
"@codemirror/lang-html": "^0.19.4",
|
||||
"@codemirror/lang-java": "^0.19.1",
|
||||
"@halo-dev/admin-api": "^1.0.0-alpha.49",
|
||||
"@halo-dev/admin-api": "^1.0.0-alpha.50",
|
||||
"@halo-dev/editor": "^3.0.0-alpha.2",
|
||||
"ant-design-vue": "^1.7.8",
|
||||
"dayjs": "^1.10.7",
|
||||
|
|
|
@ -6,7 +6,7 @@ specifiers:
|
|||
'@codemirror/basic-setup': ^0.19.1
|
||||
'@codemirror/lang-html': ^0.19.4
|
||||
'@codemirror/lang-java': ^0.19.1
|
||||
'@halo-dev/admin-api': ^1.0.0-alpha.49
|
||||
'@halo-dev/admin-api': ^1.0.0-alpha.50
|
||||
'@halo-dev/editor': ^3.0.0-alpha.2
|
||||
'@vue/cli-plugin-babel': ~5.0.1
|
||||
'@vue/cli-plugin-eslint': ~5.0.1
|
||||
|
@ -51,7 +51,7 @@ dependencies:
|
|||
'@codemirror/basic-setup': 0.19.1
|
||||
'@codemirror/lang-html': 0.19.4
|
||||
'@codemirror/lang-java': 0.19.1
|
||||
'@halo-dev/admin-api': 1.0.0-alpha.49
|
||||
'@halo-dev/admin-api': 1.0.0-alpha.50
|
||||
'@halo-dev/editor': 3.0.0-alpha.2
|
||||
ant-design-vue: 1.7.8_9065e7474e033a8e4b95615fc8e6c36c
|
||||
dayjs: 1.10.7
|
||||
|
@ -1575,11 +1575,11 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@halo-dev/admin-api/1.0.0-alpha.49:
|
||||
resolution: {integrity: sha512-549LNmiVDyjLfg9uvVYYCCJmfge8+hrNDk4Q9M0BUT9XB9xeQcV0S06Tz2MKR/3IAZhq8h2V3g5gIqaY7N9e9A==}
|
||||
/@halo-dev/admin-api/1.0.0-alpha.50:
|
||||
resolution: {integrity: sha512-/kfkBPRqnhMRCMhKWMWz+rboUAxGCM5Tcv13BaBKlG04wEY7ewgrXrFMvxfYd0luIik+knluwKj1OwWix+2ZhA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@halo-dev/rest-api-client': 1.0.0-alpha.49
|
||||
'@halo-dev/rest-api-client': 1.0.0-alpha.50
|
||||
tslib: 2.3.1
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
@ -1614,18 +1614,18 @@ packages:
|
|||
vue: 2.6.14
|
||||
dev: false
|
||||
|
||||
/@halo-dev/logger/1.0.0-alpha.49:
|
||||
resolution: {integrity: sha512-gshHffq0ntrPjAHCwRt6J3NJYvQfnlZsRgbBjnFyI5TQOSB5uFZ2dcxRAc75wDLTRFi2Brpsdm//odRe7PWGpg==}
|
||||
/@halo-dev/logger/1.0.0-alpha.50:
|
||||
resolution: {integrity: sha512-Wl+MA6LfdCWecvmOdQkNzpWpddnfv3LlsM2AdmH8t5tznK984EAp2Zs+JgsIKyqyNdaFg3H70qoyAMiZQyzUVA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
|
||||
/@halo-dev/rest-api-client/1.0.0-alpha.49:
|
||||
resolution: {integrity: sha512-tv2y4EvfJt55iAuUASKjqVv3Qt6xhh4oTG/qO+vr+Z+DEcMos+iX/fpeZwHW5/LmR0wdercBncOEP1qgOX9L0g==}
|
||||
/@halo-dev/rest-api-client/1.0.0-alpha.50:
|
||||
resolution: {integrity: sha512-aEmCQAgboJ4rsjtSz3bCM2I97198bnxXvVAzpljReIUBk+W3bY1MNrJsvnqwBGGQqfcmjYnOoX2IPs7STyriKg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@halo-dev/logger': 1.0.0-alpha.49
|
||||
'@halo-dev/logger': 1.0.0-alpha.50
|
||||
axios: 0.24.0
|
||||
form-data: 4.0.0
|
||||
js-base64: 3.7.2
|
||||
|
|
|
@ -807,7 +807,7 @@ body {
|
|||
}
|
||||
|
||||
// 附件图片样式
|
||||
.attachments-group {
|
||||
.attachments-group, .photos-group {
|
||||
&-item {
|
||||
padding: 0;
|
||||
height: 130px;
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
<a-row :gutter="48">
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="关键词:">
|
||||
<a-input v-model="list.queryParam.keyword" />
|
||||
<a-input v-model="list.params.keyword" allowClear @keyup.enter="handleQuery" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="分组:">
|
||||
<a-select v-model="list.queryParam.team" @change="handleQuery()">
|
||||
<a-select-option v-for="(item, index) in computedTeams" :key="index" :value="item"
|
||||
>{{ item }}
|
||||
<a-select v-model="list.params.team" allowClear @change="handleQuery()">
|
||||
<a-select-option v-for="(item, index) in computedTeams" :key="index" :value="item">
|
||||
{{ item }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
@ -32,46 +32,108 @@
|
|||
</a-form>
|
||||
</div>
|
||||
<div class="mb-0 table-operator">
|
||||
<a-button icon="plus" type="primary" @click="form.visible = true">添加</a-button>
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="single" @click="handleOpenForm({})"> 添加</a-menu-item>
|
||||
<a-menu-item key="batch" @click="attachmentSelectModal.visible = true"> 批量添加</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button icon="plus" type="primary">
|
||||
添加
|
||||
<a-icon type="down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-button v-show="list.selected.length" icon="check-circle" type="primary" @click="handleSelectAll">
|
||||
全选
|
||||
</a-button>
|
||||
<a-button v-show="list.selected.length" icon="delete" type="danger" @click="handleDeletePhotoInBatch">
|
||||
删除
|
||||
</a-button>
|
||||
<a-button v-show="list.selected.length" icon="delete" @click="handleOpenUpdateTeamForm">
|
||||
更改分组
|
||||
</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="photos-group"
|
||||
>
|
||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
||||
<a-card :bodyStyle="{ padding: 0 }" hoverable @click="handleOpenEditForm(item)">
|
||||
<div class="photo-thumb">
|
||||
<img :src="item.thumbnail" loading="lazy" />
|
||||
<template #renderItem="item, index">
|
||||
<a-list-item
|
||||
:key="index"
|
||||
@click="handleItemClick(item)"
|
||||
@mouseenter="$set(item, 'hover', true)"
|
||||
@mouseleave="$set(item, 'hover', false)"
|
||||
>
|
||||
<div
|
||||
:class="`${isItemSelect(item) ? 'border-blue-600' : 'border-slate-200'}`"
|
||||
class="border border-solid"
|
||||
>
|
||||
<div class="photo-thumb photos-group-item">
|
||||
<span
|
||||
:style="`background-image:url(${encodeURI(item.thumbnail)})`"
|
||||
class="photos-group-item-img"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<a-card-meta class="p-2 cursor-pointer">
|
||||
<template #description>
|
||||
<a-tooltip :title="item.name">
|
||||
<div class="truncate">
|
||||
<span class="mr-1">{{ item.name }}</span>
|
||||
<span v-if="item.team" class="text-gray-500 text-xs">#{{ item.team }}</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
|
||||
<a-icon
|
||||
v-show="!isItemSelect(item) && item.hover"
|
||||
:style="{ fontSize: '18px', color: 'rgb(37 99 235)' }"
|
||||
class="absolute top-1 right-2 font-bold cursor-pointer transition-all"
|
||||
theme="twoTone"
|
||||
type="plus-circle"
|
||||
@click.stop="handleSelect(item)"
|
||||
/>
|
||||
<a-icon
|
||||
v-show="isItemSelect(item)"
|
||||
:style="{ fontSize: '18px', color: 'rgb(37 99 235)' }"
|
||||
class="absolute top-1 right-2 font-bold cursor-pointer transition-all"
|
||||
theme="twoTone"
|
||||
type="check-circle"
|
||||
/>
|
||||
</div>
|
||||
<a-card-meta class="p-3">
|
||||
<ellipsis slot="description" :length="isMobile() ? 12 : 16" tooltip>{{ item.name }}</ellipsis>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-list-item>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:current="list.pagination.page"
|
||||
:defaultPageSize="list.pagination.size"
|
||||
:current="pagination.page"
|
||||
:defaultPageSize="pagination.size"
|
||||
:pageSizeOptions="['18', '36', '54', '72', '90', '108']"
|
||||
:total="list.pagination.total"
|
||||
:total="pagination.total"
|
||||
showLessItems
|
||||
showSizeChanger
|
||||
@change="handlePaginationChange"
|
||||
@showSizeChange="handlePaginationChange"
|
||||
@change="handlePageChange"
|
||||
@showSizeChange="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style="position: fixed; bottom: 30px; right: 30px">
|
||||
<a-button icon="setting" shape="circle" size="large" type="primary" @click="optionFormVisible = true"></a-button>
|
||||
</div>
|
||||
|
||||
<a-modal v-model="optionFormVisible" :afterClose="() => (optionFormVisible = false)" title="页面设置">
|
||||
<template slot="footer">
|
||||
<template #footer>
|
||||
<a-button key="submit" type="primary" @click="handleSaveOptions()">保存</a-button>
|
||||
</template>
|
||||
<a-form layout="vertical">
|
||||
|
@ -83,144 +145,92 @@
|
|||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<a-drawer
|
||||
:title="`图片${form.model.id ? '修改' : '添加'}`"
|
||||
:visible="form.visible"
|
||||
:width="isMobile() ? '100%' : '480'"
|
||||
closable
|
||||
destroyOnClose
|
||||
@close="onDrawerClose"
|
||||
>
|
||||
<a-form-model ref="photoForm" :model="form.model" :rules="form.rules" layout="vertical">
|
||||
<a-form-model-item label="图片地址:" prop="url">
|
||||
<div class="pb-2">
|
||||
<img
|
||||
:src="form.model.url || '/images/placeholder.jpg'"
|
||||
class="w-full cursor-pointer"
|
||||
style="border-radius: 4px"
|
||||
@click="attachmentSelectModal.visible = true"
|
||||
/>
|
||||
</div>
|
||||
<a-input v-model="form.model.url" placeholder="点击封面图选择图片,或者输入外部链接" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="缩略图地址:" prop="thumbnail">
|
||||
<a-input v-model="form.model.thumbnail" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="图片名称:" prop="name">
|
||||
<a-input v-model="form.model.name" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="拍摄日期:" prop="takeTime">
|
||||
<a-date-picker
|
||||
:defaultValue="takeTimeDefaultValue"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
showTime
|
||||
|
||||
<a-modal v-model="updateTeamForm.visible" title="更改分组">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="分组名称:">
|
||||
<a-auto-complete
|
||||
ref="teamInput"
|
||||
v-model="updateTeamForm.team"
|
||||
:dataSource="computedTeams"
|
||||
allowClear
|
||||
style="width: 100%"
|
||||
@change="onTakeTimeChange"
|
||||
@ok="onTakeTimeSelect"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="拍摄地点:" prop="location">
|
||||
<a-input v-model="form.model.location" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="分组:" prop="team">
|
||||
<a-auto-complete v-model="form.model.team" :dataSource="computedTeams" allowClear style="width: 100%" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="描述:" prop="description">
|
||||
<a-input v-model="form.model.description" :autoSize="{ minRows: 5 }" type="textarea" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-space>
|
||||
<ReactiveButton
|
||||
:errored="form.saveErrored"
|
||||
:erroredText="`${form.model.id ? '修改' : '添加'}失败`"
|
||||
:loadedText="`${form.model.id ? '修改' : '添加'}成功`"
|
||||
:loading="form.saving"
|
||||
:text="`${form.model.id ? '修改' : '添加'}`"
|
||||
@callback="handleCreateOrUpdateCallback"
|
||||
@click="handleCreateOrUpdate"
|
||||
></ReactiveButton>
|
||||
<a-popconfirm
|
||||
v-if="form.model.id"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
title="你确定要删除该图片?"
|
||||
@confirm="handleDelete"
|
||||
>
|
||||
<ReactiveButton
|
||||
:errored="form.deleteErrored"
|
||||
:loading="form.deleting"
|
||||
erroredText="删除失败"
|
||||
loadedText="删除成功"
|
||||
text="删除"
|
||||
type="danger"
|
||||
@callback="handleDeleteCallback"
|
||||
@click="() => {}"
|
||||
></ReactiveButton>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</div>
|
||||
<AttachmentSelectModal
|
||||
:multiSelect="false"
|
||||
:visible.sync="attachmentSelectModal.visible"
|
||||
@confirm="handleAttachmentSelected"
|
||||
/>
|
||||
</a-drawer>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<ReactiveButton
|
||||
:errored="updateTeamForm.saveErrored"
|
||||
:loading="updateTeamForm.saving"
|
||||
erroredText="更改失败"
|
||||
loadedText="更改成功"
|
||||
text="确定"
|
||||
@callback="handleUpdateTeamInBatchCallback"
|
||||
@click="handleUpdateTeamInBatch"
|
||||
></ReactiveButton>
|
||||
<a-button @click="updateTeamForm.visible = false">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<PhotoFormModal :photo="list.current" :teams="computedTeams" :visible.sync="formVisible" @succeed="onSaveSucceed">
|
||||
<template #extraFooter>
|
||||
<a-button :disabled="selectPreviousButtonDisabled" @click="handleSelectPrevious">上一项</a-button>
|
||||
<a-button :disabled="selectNextButtonDisabled" @click="handleSelectNext">下一项</a-button>
|
||||
</template>
|
||||
</PhotoFormModal>
|
||||
|
||||
<AttachmentSelectModal :visible.sync="attachmentSelectModal.visible" @confirm="handleAttachmentSelected" />
|
||||
</page-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// components
|
||||
import { PageView } from '@/layouts'
|
||||
import PhotoFormModal from './components/PhotoFormModal'
|
||||
|
||||
import { mapActions } from 'vuex'
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import apiClient from '@/utils/api-client'
|
||||
import { datetimeFormat } from '@/utils/datetime'
|
||||
|
||||
export default {
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: { PageView },
|
||||
components: { PageView, PhotoFormModal },
|
||||
data() {
|
||||
return {
|
||||
list: {
|
||||
data: [],
|
||||
loading: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 18,
|
||||
sort: null,
|
||||
total: 1
|
||||
},
|
||||
queryParam: {
|
||||
params: {
|
||||
page: 0,
|
||||
size: 18,
|
||||
sort: null,
|
||||
sort: ['createTime,desc', 'id,asc'],
|
||||
keyword: null,
|
||||
team: null
|
||||
}
|
||||
},
|
||||
|
||||
form: {
|
||||
model: {},
|
||||
visible: false,
|
||||
rules: {
|
||||
url: [{ required: true, message: '* 图片地址不能为空', trigger: ['change'] }],
|
||||
thumbnail: [{ required: true, message: '* 缩略图地址不能为空', trigger: ['change'] }],
|
||||
name: [{ required: true, message: '* 图片名称不能为空', trigger: ['change'] }]
|
||||
team: undefined
|
||||
},
|
||||
saving: false,
|
||||
saveErrored: false,
|
||||
deleting: false,
|
||||
deleteErrored: false
|
||||
total: 0,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
selected: [],
|
||||
current: {}
|
||||
},
|
||||
|
||||
attachmentSelectModal: {
|
||||
visible: false
|
||||
},
|
||||
|
||||
optionFormVisible: false,
|
||||
updateTeamForm: {
|
||||
team: undefined,
|
||||
visible: false,
|
||||
saving: false,
|
||||
saveErrored: false
|
||||
},
|
||||
|
||||
formVisible: false,
|
||||
|
||||
teams: [],
|
||||
options: []
|
||||
options: [],
|
||||
optionFormVisible: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -229,140 +239,214 @@ export default {
|
|||
this.handleListOptions()
|
||||
},
|
||||
computed: {
|
||||
takeTimeDefaultValue() {
|
||||
if (this.form.model.takeTime) {
|
||||
const date = new Date(this.form.model.takeTime)
|
||||
return datetimeFormat(date, 'YYYY-MM-DD HH:mm:ss')
|
||||
pagination() {
|
||||
return {
|
||||
page: this.list.params.page + 1,
|
||||
size: this.list.params.size,
|
||||
total: this.list.total
|
||||
}
|
||||
return datetimeFormat(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
||||
},
|
||||
computedTeams() {
|
||||
return this.teams.filter(item => {
|
||||
return item !== ''
|
||||
})
|
||||
},
|
||||
isItemSelect() {
|
||||
return function (photo) {
|
||||
return this.list.selected.findIndex(item => item.id === photo.id) > -1
|
||||
}
|
||||
},
|
||||
selectPreviousButtonDisabled() {
|
||||
const index = this.list.data.findIndex(photo => photo.id === this.list.current.id)
|
||||
return index === 0 && !this.list.hasPrevious
|
||||
},
|
||||
selectNextButtonDisabled() {
|
||||
const index = this.list.data.findIndex(photo => photo.id === this.list.current.id)
|
||||
return index === this.list.data.length - 1 && !this.list.hasNext
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['refreshOptionsCache']),
|
||||
handleListPhotos() {
|
||||
this.list.loading = true
|
||||
this.list.queryParam.page = this.list.pagination.page - 1
|
||||
this.list.queryParam.size = this.list.pagination.size
|
||||
this.list.queryParam.sort = this.list.pagination.sort
|
||||
apiClient.photo
|
||||
.list(this.list.queryParam)
|
||||
.then(response => {
|
||||
this.list.data = response.data.content
|
||||
this.list.pagination.total = response.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
this.list.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.handlePaginationChange(1, this.list.pagination.size)
|
||||
},
|
||||
handleListOptions() {
|
||||
apiClient.option.list().then(response => {
|
||||
this.options = response.data
|
||||
})
|
||||
|
||||
async handleListPhotos() {
|
||||
try {
|
||||
this.list.loading = true
|
||||
|
||||
const response = await apiClient.photo.list(this.list.params)
|
||||
|
||||
this.list.data = response.data.content
|
||||
this.list.total = response.data.total
|
||||
this.list.hasPrevious = response.data.hasPrevious
|
||||
this.list.hasNext = response.data.hasNext
|
||||
} catch (e) {
|
||||
this.$log.error('Failed to get photos', e)
|
||||
} finally {
|
||||
this.list.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
handleListPhotoTeams() {
|
||||
apiClient.photo.listTeams().then(response => {
|
||||
this.teams = response.data
|
||||
})
|
||||
},
|
||||
handleCreateOrUpdate() {
|
||||
|
||||
/**
|
||||
* Handle page change
|
||||
*/
|
||||
handlePageChange(page = 1) {
|
||||
this.list.params.page = page - 1
|
||||
this.handleListPhotos()
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.handleListPhotos()
|
||||
},
|
||||
|
||||
handleQuery() {
|
||||
this.handlePageChange(1)
|
||||
},
|
||||
|
||||
handleResetParam() {
|
||||
this.list.params.keyword = undefined
|
||||
this.list.params.team = undefined
|
||||
this.handlePageChange(1)
|
||||
this.handleListPhotoTeams()
|
||||
},
|
||||
|
||||
handleItemClick(photo) {
|
||||
if (this.list.selected.length <= 0) {
|
||||
this.handleOpenForm(photo)
|
||||
return
|
||||
}
|
||||
this.isItemSelect(photo) ? this.handleUnselect(photo) : this.handleSelect(photo)
|
||||
},
|
||||
|
||||
handleOpenForm(photo) {
|
||||
this.list.current = photo
|
||||
this.formVisible = true
|
||||
},
|
||||
|
||||
handleSelect(photo) {
|
||||
this.list.selected = [...this.list.selected, photo]
|
||||
},
|
||||
|
||||
handleUnselect(photo) {
|
||||
this.list.selected = this.list.selected.filter(item => item.id !== photo.id)
|
||||
},
|
||||
|
||||
handleSelectAll() {
|
||||
this.list.selected = this.list.data
|
||||
},
|
||||
|
||||
async handleAttachmentSelected({ raw }) {
|
||||
if (!raw.length) {
|
||||
return
|
||||
}
|
||||
const photosToStage = raw.map(attachment => {
|
||||
return {
|
||||
name: attachment.name,
|
||||
url: attachment.path,
|
||||
thumbnail: attachment.thumbPath
|
||||
}
|
||||
})
|
||||
try {
|
||||
await apiClient.photo.createInBatch(photosToStage)
|
||||
this.$message.success('添加成功')
|
||||
} catch (e) {
|
||||
this.$log.error('Failed to create photos in batch', e)
|
||||
} finally {
|
||||
await this.handleListPhotos()
|
||||
this.handleListPhotoTeams()
|
||||
}
|
||||
},
|
||||
|
||||
async handleDeletePhotoInBatch() {
|
||||
if (this.list.selected.length <= 0) {
|
||||
this.$message.warn('你还未选择任何图片,请至少选择一个!')
|
||||
return
|
||||
}
|
||||
const _this = this
|
||||
_this.$refs.photoForm.validate(valid => {
|
||||
if (valid) {
|
||||
_this.form.saving = true
|
||||
if (_this.form.model.id) {
|
||||
apiClient.photo
|
||||
.update(_this.form.model.id, _this.form.model)
|
||||
.catch(() => {
|
||||
_this.form.saveErrored = true
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
_this.form.saving = false
|
||||
}, 400)
|
||||
})
|
||||
} else {
|
||||
apiClient.photo
|
||||
.create(_this.form.model)
|
||||
.catch(() => {
|
||||
_this.form.saveErrored = true
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
_this.form.saving = false
|
||||
}, 400)
|
||||
})
|
||||
this.$confirm({
|
||||
title: '确定要批量删除选中的图片吗?',
|
||||
content: '一旦删除不可恢复,请谨慎操作',
|
||||
async onOk() {
|
||||
try {
|
||||
const photoIds = _this.list.selected.map(photo => photo.id)
|
||||
|
||||
await apiClient.photo.deleteInBatch(photoIds)
|
||||
|
||||
_this.list.selected = []
|
||||
_this.$message.success('删除成功')
|
||||
} catch (e) {
|
||||
_this.$log.error('Failed to delete selected photos', e)
|
||||
} finally {
|
||||
await _this.handleListPhotos()
|
||||
_this.handleListPhotoTeams()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCreateOrUpdateCallback() {
|
||||
if (this.form.saveErrored) {
|
||||
this.form.saveErrored = false
|
||||
|
||||
async handleUpdateTeamInBatch() {
|
||||
const photosToStage = this.list.selected.map(photo => {
|
||||
return {
|
||||
...photo,
|
||||
team: this.updateTeamForm.team
|
||||
}
|
||||
})
|
||||
try {
|
||||
this.updateTeamForm.saving = true
|
||||
|
||||
await apiClient.photo.updateInBatch(photosToStage)
|
||||
|
||||
this.$message.success('更改成功')
|
||||
} catch (e) {
|
||||
this.updateTeamForm.saveErrored = true
|
||||
this.$log.error('Failed to change team in batch', e)
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.updateTeamForm.saving = false
|
||||
}, 400)
|
||||
}
|
||||
},
|
||||
|
||||
handleUpdateTeamInBatchCallback() {
|
||||
if (this.updateTeamForm.saveErrored) {
|
||||
this.updateTeamForm.saveErrored = false
|
||||
} else {
|
||||
this.form.model = {}
|
||||
this.form.visible = false
|
||||
this.updateTeamForm.visible = false
|
||||
this.updateTeamForm.team = undefined
|
||||
this.list.selected = []
|
||||
this.handleListPhotos()
|
||||
this.handleListPhotoTeams()
|
||||
}
|
||||
},
|
||||
handleOpenEditForm(photo) {
|
||||
this.form.model = photo
|
||||
this.form.visible = true
|
||||
|
||||
handleOpenUpdateTeamForm() {
|
||||
this.updateTeamForm.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.teamInput.focus()
|
||||
})
|
||||
},
|
||||
handlePaginationChange(page, size) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${size}`)
|
||||
this.list.pagination.page = page
|
||||
this.list.pagination.size = size
|
||||
this.handleListPhotos()
|
||||
},
|
||||
handleDelete() {
|
||||
this.form.deleting = true
|
||||
apiClient.photo
|
||||
.delete(this.form.model.id)
|
||||
.catch(() => {
|
||||
this.form.deleteErrored = true
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.form.deleting = false
|
||||
}, 400)
|
||||
})
|
||||
},
|
||||
handleDeleteCallback() {
|
||||
if (this.form.deleteErrored) {
|
||||
this.form.deleteErrored = false
|
||||
} else {
|
||||
this.form.model = {}
|
||||
this.form.visible = false
|
||||
this.handleListPhotos()
|
||||
this.handleListPhotoTeams()
|
||||
}
|
||||
},
|
||||
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
|
||||
this.list.queryParam.team = null
|
||||
this.handlePaginationChange(1, this.list.pagination.size)
|
||||
|
||||
async onSaveSucceed(photo) {
|
||||
await this.handleListPhotos()
|
||||
this.list.current = photo
|
||||
this.handleListPhotoTeams()
|
||||
},
|
||||
onDrawerClose() {
|
||||
this.form.visible = false
|
||||
this.form.model = {}
|
||||
|
||||
handleListOptions() {
|
||||
apiClient.option.list().then(response => {
|
||||
this.options = response.data
|
||||
})
|
||||
},
|
||||
|
||||
handleSaveOptions() {
|
||||
apiClient.option
|
||||
.save(this.options)
|
||||
|
@ -375,11 +459,39 @@ export default {
|
|||
this.refreshOptionsCache()
|
||||
})
|
||||
},
|
||||
onTakeTimeChange(value) {
|
||||
this.form.model.takeTime = value.valueOf()
|
||||
|
||||
/**
|
||||
* Select previous photo
|
||||
*/
|
||||
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.handleListPhotos()
|
||||
|
||||
this.list.current = this.list.data[this.list.data.length - 1]
|
||||
}
|
||||
},
|
||||
onTakeTimeSelect(value) {
|
||||
this.form.model.takeTime = value.valueOf()
|
||||
|
||||
/**
|
||||
* Select next photo
|
||||
*/
|
||||
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.handleListPhotos()
|
||||
|
||||
this.list.current = this.list.data[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
<template>
|
||||
<a-modal v-model="modalVisible" :afterClose="onClosed" :maskClosable="false" :width="680" destroyOnClose>
|
||||
<template #title>
|
||||
{{ form.model.id ? '修改' : '添加' }}图片
|
||||
<a-icon v-if="loading" type="loading" />
|
||||
</template>
|
||||
|
||||
<a-form-model
|
||||
ref="photoForm"
|
||||
:label-col="{ span: 4 }"
|
||||
:model="form.model"
|
||||
:rules="form.rules"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
labelAlign="left"
|
||||
>
|
||||
<a-form-model-item label="图片地址:" prop="url">
|
||||
<a-space direction="vertical">
|
||||
<img
|
||||
:src="form.model.url || '/images/placeholder.jpg'"
|
||||
class="w-1/2 cursor-pointer"
|
||||
style="border-radius: 4px"
|
||||
@click="attachmentSelectModal.visible = true"
|
||||
/>
|
||||
<a-input v-model="form.model.url" allow-clear placeholder="点击封面图选择图片,或者输入外部链接" />
|
||||
</a-space>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="缩略图地址:" prop="thumbnail">
|
||||
<a-input v-model="form.model.thumbnail" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="图片名称:" prop="name">
|
||||
<a-input v-model="form.model.name" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="拍摄日期:" prop="takeTime">
|
||||
<a-date-picker
|
||||
:defaultValue="takeTimeDefaultValue"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
showTime
|
||||
style="width: 100%"
|
||||
@change="onTakeTimeChange"
|
||||
@ok="onTakeTimeChange"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="拍摄地点:" prop="location">
|
||||
<a-input v-model="form.model.location" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="分组:" prop="team">
|
||||
<a-auto-complete v-model="form.model.team" :dataSource="teams" allowClear style="width: 100%" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="描述:" prop="description">
|
||||
<a-input v-model="form.model.description" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
|
||||
<template #footer>
|
||||
<slot name="extraFooter" />
|
||||
<ReactiveButton
|
||||
:errored="form.saveErrored"
|
||||
:loading="form.saving"
|
||||
erroredText="保存失败"
|
||||
loadedText="保存成功"
|
||||
text="保存"
|
||||
@callback="handleSaveCallback"
|
||||
@click="handleSave"
|
||||
></ReactiveButton>
|
||||
<a-button :disabled="loading" @click="modalVisible = false"> 关闭</a-button>
|
||||
</template>
|
||||
|
||||
<AttachmentSelectModal
|
||||
:multiSelect="false"
|
||||
:visible.sync="attachmentSelectModal.visible"
|
||||
@confirm="handleAttachmentSelected"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import { datetimeFormat } from '@/utils/datetime'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'PhotoFormModal',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
photo: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
teams: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
model: {},
|
||||
rules: {
|
||||
url: [{ required: true, message: '* 图片地址不能为空', trigger: ['change'] }],
|
||||
thumbnail: [{ required: true, message: '* 缩略图地址不能为空', trigger: ['change'] }],
|
||||
name: [{ required: true, message: '* 图片名称不能为空', trigger: ['change'] }]
|
||||
},
|
||||
saving: false,
|
||||
saveErrored: false
|
||||
},
|
||||
attachmentSelectModal: {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
modalVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:visible', value)
|
||||
}
|
||||
},
|
||||
takeTimeDefaultValue() {
|
||||
if (this.form.model.takeTime) {
|
||||
const date = new Date(this.form.model.takeTime)
|
||||
return datetimeFormat(date, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
return datetimeFormat(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modalVisible(value) {
|
||||
if (value) {
|
||||
this.form.model = Object.assign({}, this.photo)
|
||||
}
|
||||
},
|
||||
photo: {
|
||||
deep: true,
|
||||
handler(value) {
|
||||
this.form.model = Object.assign({}, value)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClosed() {},
|
||||
|
||||
handleAttachmentSelected({ raw }) {
|
||||
if (raw.length) {
|
||||
const { path, thumbPath, name } = raw[0]
|
||||
this.$set(this.form.model, 'url', encodeURI(path))
|
||||
this.$set(this.form.model, 'thumbnail', encodeURI(thumbPath))
|
||||
this.$set(this.form.model, 'name', name)
|
||||
}
|
||||
this.attachmentSelectModal.visible = false
|
||||
},
|
||||
|
||||
onTakeTimeChange(value) {
|
||||
this.form.model.takeTime = value.valueOf()
|
||||
},
|
||||
|
||||
handleSave() {
|
||||
const _this = this
|
||||
_this.$refs.photoForm.validate(async valid => {
|
||||
if (valid) {
|
||||
_this.form.saving = true
|
||||
|
||||
try {
|
||||
if (_this.form.model.id) {
|
||||
await apiClient.photo.update(_this.form.model.id, _this.form.model)
|
||||
} else {
|
||||
const { data } = await apiClient.photo.create(_this.form.model)
|
||||
this.form.model = data
|
||||
}
|
||||
} catch (e) {
|
||||
_this.form.saveErrored = true
|
||||
this.$log.error('Failed to save this photo', e)
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
_this.form.saving = false
|
||||
}, 400)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleSaveCallback() {
|
||||
if (this.form.saveErrored) {
|
||||
this.form.saveErrored = false
|
||||
} else {
|
||||
this.$emit('succeed', this.form.model)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue