mirror of https://github.com/halo-dev/halo-admin
parent
51842f2afa
commit
ea955d49dd
|
@ -23,7 +23,7 @@
|
||||||
"@codemirror/basic-setup": "^0.19.1",
|
"@codemirror/basic-setup": "^0.19.1",
|
||||||
"@codemirror/lang-html": "^0.19.4",
|
"@codemirror/lang-html": "^0.19.4",
|
||||||
"@codemirror/lang-java": "^0.19.1",
|
"@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",
|
"@halo-dev/editor": "^3.0.0-alpha.2",
|
||||||
"ant-design-vue": "^1.7.8",
|
"ant-design-vue": "^1.7.8",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.10.7",
|
||||||
|
|
|
@ -6,7 +6,7 @@ specifiers:
|
||||||
'@codemirror/basic-setup': ^0.19.1
|
'@codemirror/basic-setup': ^0.19.1
|
||||||
'@codemirror/lang-html': ^0.19.4
|
'@codemirror/lang-html': ^0.19.4
|
||||||
'@codemirror/lang-java': ^0.19.1
|
'@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
|
'@halo-dev/editor': ^3.0.0-alpha.2
|
||||||
'@vue/cli-plugin-babel': ~5.0.1
|
'@vue/cli-plugin-babel': ~5.0.1
|
||||||
'@vue/cli-plugin-eslint': ~5.0.1
|
'@vue/cli-plugin-eslint': ~5.0.1
|
||||||
|
@ -51,7 +51,7 @@ dependencies:
|
||||||
'@codemirror/basic-setup': 0.19.1
|
'@codemirror/basic-setup': 0.19.1
|
||||||
'@codemirror/lang-html': 0.19.4
|
'@codemirror/lang-html': 0.19.4
|
||||||
'@codemirror/lang-java': 0.19.1
|
'@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
|
'@halo-dev/editor': 3.0.0-alpha.2
|
||||||
ant-design-vue: 1.7.8_9065e7474e033a8e4b95615fc8e6c36c
|
ant-design-vue: 1.7.8_9065e7474e033a8e4b95615fc8e6c36c
|
||||||
dayjs: 1.10.7
|
dayjs: 1.10.7
|
||||||
|
@ -1575,11 +1575,11 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@halo-dev/admin-api/1.0.0-alpha.49:
|
/@halo-dev/admin-api/1.0.0-alpha.50:
|
||||||
resolution: {integrity: sha512-549LNmiVDyjLfg9uvVYYCCJmfge8+hrNDk4Q9M0BUT9XB9xeQcV0S06Tz2MKR/3IAZhq8h2V3g5gIqaY7N9e9A==}
|
resolution: {integrity: sha512-/kfkBPRqnhMRCMhKWMWz+rboUAxGCM5Tcv13BaBKlG04wEY7ewgrXrFMvxfYd0luIik+knluwKj1OwWix+2ZhA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dependencies:
|
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
|
tslib: 2.3.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
@ -1614,18 +1614,18 @@ packages:
|
||||||
vue: 2.6.14
|
vue: 2.6.14
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@halo-dev/logger/1.0.0-alpha.49:
|
/@halo-dev/logger/1.0.0-alpha.50:
|
||||||
resolution: {integrity: sha512-gshHffq0ntrPjAHCwRt6J3NJYvQfnlZsRgbBjnFyI5TQOSB5uFZ2dcxRAc75wDLTRFi2Brpsdm//odRe7PWGpg==}
|
resolution: {integrity: sha512-Wl+MA6LfdCWecvmOdQkNzpWpddnfv3LlsM2AdmH8t5tznK984EAp2Zs+JgsIKyqyNdaFg3H70qoyAMiZQyzUVA==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@halo-dev/rest-api-client/1.0.0-alpha.49:
|
/@halo-dev/rest-api-client/1.0.0-alpha.50:
|
||||||
resolution: {integrity: sha512-tv2y4EvfJt55iAuUASKjqVv3Qt6xhh4oTG/qO+vr+Z+DEcMos+iX/fpeZwHW5/LmR0wdercBncOEP1qgOX9L0g==}
|
resolution: {integrity: sha512-aEmCQAgboJ4rsjtSz3bCM2I97198bnxXvVAzpljReIUBk+W3bY1MNrJsvnqwBGGQqfcmjYnOoX2IPs7STyriKg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@halo-dev/logger': 1.0.0-alpha.49
|
'@halo-dev/logger': 1.0.0-alpha.50
|
||||||
axios: 0.24.0
|
axios: 0.24.0
|
||||||
form-data: 4.0.0
|
form-data: 4.0.0
|
||||||
js-base64: 3.7.2
|
js-base64: 3.7.2
|
||||||
|
|
|
@ -807,7 +807,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 附件图片样式
|
// 附件图片样式
|
||||||
.attachments-group {
|
.attachments-group, .photos-group {
|
||||||
&-item {
|
&-item {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 130px;
|
height: 130px;
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
<a-row :gutter="48">
|
<a-row :gutter="48">
|
||||||
<a-col :md="6" :sm="24">
|
<a-col :md="6" :sm="24">
|
||||||
<a-form-item label="关键词:">
|
<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-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :md="6" :sm="24">
|
<a-col :md="6" :sm="24">
|
||||||
<a-form-item label="分组:">
|
<a-form-item label="分组:">
|
||||||
<a-select v-model="list.queryParam.team" @change="handleQuery()">
|
<a-select v-model="list.params.team" allowClear @change="handleQuery()">
|
||||||
<a-select-option v-for="(item, index) in computedTeams" :key="index" :value="item"
|
<a-select-option v-for="(item, index) in computedTeams" :key="index" :value="item">
|
||||||
>{{ item }}
|
{{ item }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -32,46 +32,108 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-0 table-operator">
|
<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>
|
</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="photos-group"
|
||||||
>
|
>
|
||||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
<template #renderItem="item, index">
|
||||||
<a-card :bodyStyle="{ padding: 0 }" hoverable @click="handleOpenEditForm(item)">
|
<a-list-item
|
||||||
<div class="photo-thumb">
|
:key="index"
|
||||||
<img :src="item.thumbnail" loading="lazy" />
|
@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>
|
</div>
|
||||||
<a-card-meta class="p-3">
|
</a-list-item>
|
||||||
<ellipsis slot="description" :length="isMobile() ? 12 : 16" tooltip>{{ item.name }}</ellipsis>
|
</template>
|
||||||
</a-card-meta>
|
|
||||||
</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="list.pagination.page"
|
:current="pagination.page"
|
||||||
:defaultPageSize="list.pagination.size"
|
:defaultPageSize="pagination.size"
|
||||||
:pageSizeOptions="['18', '36', '54', '72', '90', '108']"
|
:pageSizeOptions="['18', '36', '54', '72', '90', '108']"
|
||||||
:total="list.pagination.total"
|
:total="pagination.total"
|
||||||
showLessItems
|
showLessItems
|
||||||
showSizeChanger
|
showSizeChanger
|
||||||
@change="handlePaginationChange"
|
@change="handlePageChange"
|
||||||
@showSizeChange="handlePaginationChange"
|
@showSizeChange="handlePageSizeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="position: fixed; bottom: 30px; right: 30px">
|
<div style="position: fixed; bottom: 30px; right: 30px">
|
||||||
<a-button icon="setting" shape="circle" size="large" type="primary" @click="optionFormVisible = true"></a-button>
|
<a-button icon="setting" shape="circle" size="large" type="primary" @click="optionFormVisible = true"></a-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-modal v-model="optionFormVisible" :afterClose="() => (optionFormVisible = false)" title="页面设置">
|
<a-modal v-model="optionFormVisible" :afterClose="() => (optionFormVisible = false)" title="页面设置">
|
||||||
<template slot="footer">
|
<template #footer>
|
||||||
<a-button key="submit" type="primary" @click="handleSaveOptions()">保存</a-button>
|
<a-button key="submit" type="primary" @click="handleSaveOptions()">保存</a-button>
|
||||||
</template>
|
</template>
|
||||||
<a-form layout="vertical">
|
<a-form layout="vertical">
|
||||||
|
@ -83,144 +145,92 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<a-drawer
|
|
||||||
:title="`图片${form.model.id ? '修改' : '添加'}`"
|
<a-modal v-model="updateTeamForm.visible" title="更改分组">
|
||||||
:visible="form.visible"
|
<a-form layout="vertical">
|
||||||
:width="isMobile() ? '100%' : '480'"
|
<a-form-item label="分组名称:">
|
||||||
closable
|
<a-auto-complete
|
||||||
destroyOnClose
|
ref="teamInput"
|
||||||
@close="onDrawerClose"
|
v-model="updateTeamForm.team"
|
||||||
>
|
:dataSource="computedTeams"
|
||||||
<a-form-model ref="photoForm" :model="form.model" :rules="form.rules" layout="vertical">
|
allowClear
|
||||||
<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
|
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="onTakeTimeChange"
|
|
||||||
@ok="onTakeTimeSelect"
|
|
||||||
/>
|
/>
|
||||||
</a-form-model-item>
|
</a-form-item>
|
||||||
<a-form-model-item label="拍摄地点:" prop="location">
|
</a-form>
|
||||||
<a-input v-model="form.model.location" />
|
|
||||||
</a-form-model-item>
|
<template #footer>
|
||||||
<a-form-model-item label="分组:" prop="team">
|
<ReactiveButton
|
||||||
<a-auto-complete v-model="form.model.team" :dataSource="computedTeams" allowClear style="width: 100%" />
|
:errored="updateTeamForm.saveErrored"
|
||||||
</a-form-model-item>
|
:loading="updateTeamForm.saving"
|
||||||
<a-form-model-item label="描述:" prop="description">
|
erroredText="更改失败"
|
||||||
<a-input v-model="form.model.description" :autoSize="{ minRows: 5 }" type="textarea" />
|
loadedText="更改成功"
|
||||||
</a-form-model-item>
|
text="确定"
|
||||||
</a-form-model>
|
@callback="handleUpdateTeamInBatchCallback"
|
||||||
<a-divider class="divider-transparent" />
|
@click="handleUpdateTeamInBatch"
|
||||||
<div class="bottom-control">
|
></ReactiveButton>
|
||||||
<a-space>
|
<a-button @click="updateTeamForm.visible = false">关闭</a-button>
|
||||||
<ReactiveButton
|
</template>
|
||||||
:errored="form.saveErrored"
|
</a-modal>
|
||||||
:erroredText="`${form.model.id ? '修改' : '添加'}失败`"
|
|
||||||
:loadedText="`${form.model.id ? '修改' : '添加'}成功`"
|
<PhotoFormModal :photo="list.current" :teams="computedTeams" :visible.sync="formVisible" @succeed="onSaveSucceed">
|
||||||
:loading="form.saving"
|
<template #extraFooter>
|
||||||
:text="`${form.model.id ? '修改' : '添加'}`"
|
<a-button :disabled="selectPreviousButtonDisabled" @click="handleSelectPrevious">上一项</a-button>
|
||||||
@callback="handleCreateOrUpdateCallback"
|
<a-button :disabled="selectNextButtonDisabled" @click="handleSelectNext">下一项</a-button>
|
||||||
@click="handleCreateOrUpdate"
|
</template>
|
||||||
></ReactiveButton>
|
</PhotoFormModal>
|
||||||
<a-popconfirm
|
|
||||||
v-if="form.model.id"
|
<AttachmentSelectModal :visible.sync="attachmentSelectModal.visible" @confirm="handleAttachmentSelected" />
|
||||||
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>
|
|
||||||
</page-view>
|
</page-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// components
|
||||||
import { PageView } from '@/layouts'
|
import { PageView } from '@/layouts'
|
||||||
|
import PhotoFormModal from './components/PhotoFormModal'
|
||||||
|
|
||||||
import { mapActions } from 'vuex'
|
import { mapActions } from 'vuex'
|
||||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||||
import apiClient from '@/utils/api-client'
|
import apiClient from '@/utils/api-client'
|
||||||
import { datetimeFormat } from '@/utils/datetime'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [mixin, mixinDevice],
|
mixins: [mixin, mixinDevice],
|
||||||
components: { PageView },
|
components: { PageView, PhotoFormModal },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
list: {
|
list: {
|
||||||
data: [],
|
data: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
pagination: {
|
params: {
|
||||||
page: 1,
|
|
||||||
size: 18,
|
|
||||||
sort: null,
|
|
||||||
total: 1
|
|
||||||
},
|
|
||||||
queryParam: {
|
|
||||||
page: 0,
|
page: 0,
|
||||||
size: 18,
|
size: 18,
|
||||||
sort: null,
|
sort: ['createTime,desc', 'id,asc'],
|
||||||
keyword: null,
|
keyword: null,
|
||||||
team: null
|
team: undefined
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
form: {
|
|
||||||
model: {},
|
|
||||||
visible: false,
|
|
||||||
rules: {
|
|
||||||
url: [{ required: true, message: '* 图片地址不能为空', trigger: ['change'] }],
|
|
||||||
thumbnail: [{ required: true, message: '* 缩略图地址不能为空', trigger: ['change'] }],
|
|
||||||
name: [{ required: true, message: '* 图片名称不能为空', trigger: ['change'] }]
|
|
||||||
},
|
},
|
||||||
saving: false,
|
total: 0,
|
||||||
saveErrored: false,
|
hasPrevious: false,
|
||||||
deleting: false,
|
hasNext: false,
|
||||||
deleteErrored: false
|
selected: [],
|
||||||
|
current: {}
|
||||||
},
|
},
|
||||||
|
|
||||||
attachmentSelectModal: {
|
attachmentSelectModal: {
|
||||||
visible: false
|
visible: false
|
||||||
},
|
},
|
||||||
|
|
||||||
optionFormVisible: false,
|
updateTeamForm: {
|
||||||
|
team: undefined,
|
||||||
|
visible: false,
|
||||||
|
saving: false,
|
||||||
|
saveErrored: false
|
||||||
|
},
|
||||||
|
|
||||||
|
formVisible: false,
|
||||||
|
|
||||||
teams: [],
|
teams: [],
|
||||||
options: []
|
options: [],
|
||||||
|
optionFormVisible: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -229,140 +239,214 @@ export default {
|
||||||
this.handleListOptions()
|
this.handleListOptions()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
takeTimeDefaultValue() {
|
pagination() {
|
||||||
if (this.form.model.takeTime) {
|
return {
|
||||||
const date = new Date(this.form.model.takeTime)
|
page: this.list.params.page + 1,
|
||||||
return datetimeFormat(date, 'YYYY-MM-DD HH:mm:ss')
|
size: this.list.params.size,
|
||||||
|
total: this.list.total
|
||||||
}
|
}
|
||||||
return datetimeFormat(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
|
||||||
},
|
},
|
||||||
computedTeams() {
|
computedTeams() {
|
||||||
return this.teams.filter(item => {
|
return this.teams.filter(item => {
|
||||||
return 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: {
|
methods: {
|
||||||
...mapActions(['refreshOptionsCache']),
|
...mapActions(['refreshOptionsCache']),
|
||||||
handleListPhotos() {
|
|
||||||
this.list.loading = true
|
async handleListPhotos() {
|
||||||
this.list.queryParam.page = this.list.pagination.page - 1
|
try {
|
||||||
this.list.queryParam.size = this.list.pagination.size
|
this.list.loading = true
|
||||||
this.list.queryParam.sort = this.list.pagination.sort
|
|
||||||
apiClient.photo
|
const response = await apiClient.photo.list(this.list.params)
|
||||||
.list(this.list.queryParam)
|
|
||||||
.then(response => {
|
this.list.data = response.data.content
|
||||||
this.list.data = response.data.content
|
this.list.total = response.data.total
|
||||||
this.list.pagination.total = response.data.total
|
this.list.hasPrevious = response.data.hasPrevious
|
||||||
})
|
this.list.hasNext = response.data.hasNext
|
||||||
.finally(() => {
|
} catch (e) {
|
||||||
this.list.loading = false
|
this.$log.error('Failed to get photos', e)
|
||||||
})
|
} finally {
|
||||||
},
|
this.list.loading = false
|
||||||
handleQuery() {
|
}
|
||||||
this.handlePaginationChange(1, this.list.pagination.size)
|
|
||||||
},
|
|
||||||
handleListOptions() {
|
|
||||||
apiClient.option.list().then(response => {
|
|
||||||
this.options = response.data
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleListPhotoTeams() {
|
handleListPhotoTeams() {
|
||||||
apiClient.photo.listTeams().then(response => {
|
apiClient.photo.listTeams().then(response => {
|
||||||
this.teams = response.data
|
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
|
const _this = this
|
||||||
_this.$refs.photoForm.validate(valid => {
|
this.$confirm({
|
||||||
if (valid) {
|
title: '确定要批量删除选中的图片吗?',
|
||||||
_this.form.saving = true
|
content: '一旦删除不可恢复,请谨慎操作',
|
||||||
if (_this.form.model.id) {
|
async onOk() {
|
||||||
apiClient.photo
|
try {
|
||||||
.update(_this.form.model.id, _this.form.model)
|
const photoIds = _this.list.selected.map(photo => photo.id)
|
||||||
.catch(() => {
|
|
||||||
_this.form.saveErrored = true
|
await apiClient.photo.deleteInBatch(photoIds)
|
||||||
})
|
|
||||||
.finally(() => {
|
_this.list.selected = []
|
||||||
setTimeout(() => {
|
_this.$message.success('删除成功')
|
||||||
_this.form.saving = false
|
} catch (e) {
|
||||||
}, 400)
|
_this.$log.error('Failed to delete selected photos', e)
|
||||||
})
|
} finally {
|
||||||
} else {
|
await _this.handleListPhotos()
|
||||||
apiClient.photo
|
_this.handleListPhotoTeams()
|
||||||
.create(_this.form.model)
|
|
||||||
.catch(() => {
|
|
||||||
_this.form.saveErrored = true
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
_this.form.saving = false
|
|
||||||
}, 400)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleCreateOrUpdateCallback() {
|
|
||||||
if (this.form.saveErrored) {
|
async handleUpdateTeamInBatch() {
|
||||||
this.form.saveErrored = false
|
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 {
|
} else {
|
||||||
this.form.model = {}
|
this.updateTeamForm.visible = false
|
||||||
this.form.visible = false
|
this.updateTeamForm.team = undefined
|
||||||
|
this.list.selected = []
|
||||||
this.handleListPhotos()
|
this.handleListPhotos()
|
||||||
this.handleListPhotoTeams()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleOpenEditForm(photo) {
|
|
||||||
this.form.model = photo
|
handleOpenUpdateTeamForm() {
|
||||||
this.form.visible = true
|
this.updateTeamForm.visible = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.teamInput.focus()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
handlePaginationChange(page, size) {
|
|
||||||
this.$log.debug(`Current: ${page}, PageSize: ${size}`)
|
async onSaveSucceed(photo) {
|
||||||
this.list.pagination.page = page
|
await this.handleListPhotos()
|
||||||
this.list.pagination.size = size
|
this.list.current = photo
|
||||||
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)
|
|
||||||
this.handleListPhotoTeams()
|
this.handleListPhotoTeams()
|
||||||
},
|
},
|
||||||
onDrawerClose() {
|
|
||||||
this.form.visible = false
|
handleListOptions() {
|
||||||
this.form.model = {}
|
apiClient.option.list().then(response => {
|
||||||
|
this.options = response.data
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSaveOptions() {
|
handleSaveOptions() {
|
||||||
apiClient.option
|
apiClient.option
|
||||||
.save(this.options)
|
.save(this.options)
|
||||||
|
@ -375,11 +459,39 @@ export default {
|
||||||
this.refreshOptionsCache()
|
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