mirror of
https://github.com/palxiao/poster-design.git
synced 2025-07-15 16:02:19 +08:00
Merge pull request #70 from JeremyYu-cn/feat-upgrade-vue3
Feat: Convert panel/wrap components to composition API
This commit is contained in:
commit
05cde577b2
@ -15,22 +15,31 @@ type IGetTempListParam = {
|
||||
search: string
|
||||
page: number
|
||||
pageSize: number
|
||||
cate:number
|
||||
cate: number | string
|
||||
}
|
||||
type IGetTempListData = {
|
||||
export type IGetTempListData = {
|
||||
cover: string
|
||||
height: number
|
||||
id: number
|
||||
state: number
|
||||
title: string
|
||||
width: number
|
||||
isDelect: boolean
|
||||
fail: boolean
|
||||
top: number
|
||||
left: number
|
||||
data?: string
|
||||
}
|
||||
type IGetTempListResult = TCommResResult<IGetTempListData>
|
||||
type IGetTempListResult = TPageRequestResult<IGetTempListData[]>
|
||||
|
||||
// 获取模板列表
|
||||
export const getTempList = (params: IGetTempListParam) => fetch<IGetTempListResult>('design/list', params, 'get')
|
||||
|
||||
export const getTempDetail = (params: Type.Object = {}) => fetch('design/temp', params, 'get')
|
||||
type TGetTempDetail = {
|
||||
id: number
|
||||
}
|
||||
|
||||
export const getTempDetail = (params: TGetTempDetail) => fetch<{data: string}>('design/temp', params, 'get')
|
||||
|
||||
type TGetCategoriesParams = {
|
||||
type?: number
|
||||
|
@ -13,9 +13,16 @@ export type TItem2DataParam = {
|
||||
height: number
|
||||
url: string
|
||||
model?: string
|
||||
canvasWidth?: number
|
||||
}
|
||||
|
||||
export default async function setItem2Data(item: TItem2DataParam) {
|
||||
export type TItem2DataResult = {
|
||||
width: number
|
||||
height: number
|
||||
canvasWidth: number
|
||||
}
|
||||
|
||||
export default async function setItem2Data(item: TItem2DataParam): Promise<Required<TItem2DataParam>> {
|
||||
const cloneItem = JSON.parse(JSON.stringify(item))
|
||||
const { width: screenWidth, height: screenHeight } = store.getters.dPage
|
||||
let { width: imgWidth, height: imgHeight } = item
|
||||
|
24
src/components/modules/panel/types/wrap.d.ts
vendored
Normal file
24
src/components/modules/panel/types/wrap.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
type TCommonImgListData = {
|
||||
isDelect: boolean
|
||||
cover: string
|
||||
fail: boolean
|
||||
top: number
|
||||
left: number
|
||||
width: number
|
||||
height: number
|
||||
title: string
|
||||
}
|
||||
|
||||
type TCommonPhotoListData = {
|
||||
listWidth: number
|
||||
gap: number
|
||||
thumb?: string
|
||||
url: string
|
||||
color: string
|
||||
isDelect: boolean
|
||||
width: number
|
||||
height: number
|
||||
model: string
|
||||
}
|
@ -2,137 +2,148 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 15:16:07
|
||||
* @Description: 模板列表
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-22 09:55:59
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<search-header v-model="searchKeyword" @change="cateChange" />
|
||||
<el-divider v-show="title" style="margin-top: 1.7rem" content-position="left">
|
||||
<span style="font-weight: bold">{{ title }}</span>
|
||||
<search-header v-model="state.searchKeyword" @change="cateChange" />
|
||||
<el-divider v-show="state.title" style="margin-top: 1.7rem" content-position="left">
|
||||
<span style="font-weight: bold">{{ state.title }}</span>
|
||||
</el-divider>
|
||||
|
||||
<ul ref="listRef" v-infinite-scroll="load" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<img-water-fall :listData="list" @select="selectItem" />
|
||||
<div v-show="loading" class="loading"><i class="el-icon-loading"></i> 拼命加载中</div>
|
||||
<div v-show="loadDone" class="loading">全部加载完毕</div>
|
||||
<img-water-fall :listData="state.list" @select="selectItem" />
|
||||
<div v-show="state.loading" class="loading"><i class="el-icon-loading"></i> 拼命加载中</div>
|
||||
<div v-show="state.loadDone" class="loading">全部加载完毕</div>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, onMounted, ref } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, defineExpose } from 'vue'
|
||||
import api from '@/api'
|
||||
import { mapActions, mapGetters, useStore } from 'vuex'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import { LocationQueryValue, useRoute, useRouter } from 'vue-router'
|
||||
// import chooseType from './components/chooseType.vue'
|
||||
// import editModel from './components/editModel.vue'
|
||||
import searchHeader from './components/searchHeader.vue'
|
||||
import useConfirm from '@/common/methods/confirm'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
|
||||
import imgWaterFall from './components/imgWaterFall.vue'
|
||||
import { IGetTempListData } from '@/api/home'
|
||||
|
||||
export default defineComponent({
|
||||
components: { searchHeader },
|
||||
setup() {
|
||||
const listRef = ref(null)
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
const state: any = reactive({
|
||||
loading: false,
|
||||
loadDone: false,
|
||||
list: [],
|
||||
title: '推荐模板',
|
||||
searchKeyword: '',
|
||||
})
|
||||
const pageOptions: any = { page: 0, pageSize: 20, cate: 1 }
|
||||
const { cate, edit } = route.query
|
||||
cate && (pageOptions.cate = cate)
|
||||
edit && store.commit('managerEdit', true)
|
||||
type TState = {
|
||||
loading: boolean
|
||||
loadDone: boolean
|
||||
list: IGetTempListData[]
|
||||
title: string
|
||||
searchKeyword: string
|
||||
}
|
||||
|
||||
// onMounted(async () => {})
|
||||
type TPageOptions = {
|
||||
page: number,
|
||||
pageSize: number,
|
||||
cate: number | string
|
||||
state?: string
|
||||
}
|
||||
|
||||
const load = async (init: boolean = false, stat?: string) => {
|
||||
stat && (pageOptions.state = stat)
|
||||
const listRef = ref<HTMLElement | null>(null)
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const state = reactive<TState>({
|
||||
loading: false,
|
||||
loadDone: false,
|
||||
list: [],
|
||||
title: '推荐模板',
|
||||
searchKeyword: '',
|
||||
})
|
||||
|
||||
if (init) {
|
||||
listRef.value.scrollTop = 0
|
||||
state.list = []
|
||||
pageOptions.page = 0
|
||||
state.loadDone = false
|
||||
}
|
||||
if (state.loadDone || state.loading) {
|
||||
return
|
||||
}
|
||||
const { tempEditing, dHistoryParams } = useSetupMapGetters(['tempEditing', 'dHistoryParams'])
|
||||
|
||||
state.loading = true
|
||||
pageOptions.page += 1
|
||||
const pageOptions: TPageOptions = { page: 0, pageSize: 20, cate: 1 }
|
||||
const { cate, edit } = route.query
|
||||
cate && (pageOptions.cate = (cate as LocationQueryValue) || 1)
|
||||
edit && store.commit('managerEdit', true)
|
||||
|
||||
const res = await api.home.getTempList({ search: state.searchKeyword, ...pageOptions })
|
||||
res.list.length <= 0 && (state.loadDone = true)
|
||||
state.list = state.list.concat(res.list)
|
||||
// onMounted(async () => {})
|
||||
|
||||
setTimeout(() => {
|
||||
state.loading = false
|
||||
checkHeight()
|
||||
}, 100)
|
||||
const load = async (init: boolean = false, stat?: string) => {
|
||||
stat && (pageOptions.state = stat)
|
||||
|
||||
if (init && listRef.value) {
|
||||
listRef.value.scrollTop = 0
|
||||
state.list = []
|
||||
pageOptions.page = 0
|
||||
state.loadDone = false
|
||||
}
|
||||
if (state.loadDone || state.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
state.loading = true
|
||||
pageOptions.page += 1
|
||||
|
||||
const res = await api.home.getTempList({ search: state.searchKeyword, ...pageOptions })
|
||||
res.list.length <= 0 && (state.loadDone = true)
|
||||
state.list = state.list.concat(res.list)
|
||||
setTimeout(() => {
|
||||
state.loading = false
|
||||
checkHeight()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function cateChange(type: any) {
|
||||
state.title = type.name
|
||||
const init = pageOptions.cate != type.id
|
||||
pageOptions.cate = type.id
|
||||
load(init, pageOptions.state)
|
||||
}
|
||||
|
||||
function checkHeight() {
|
||||
if (!listRef.value) return
|
||||
// 检查高度是否占满,否则继续请求下一页
|
||||
const isLess = listRef.value.offsetHeight > (listRef.value.firstElementChild as HTMLElement)?.offsetHeight
|
||||
isLess && load()
|
||||
}
|
||||
// ...mapActions(['selectWidget', 'updatePageData', 'setTemplate', 'pushHistory']),
|
||||
async function selectItem(item: IGetTempListData) {
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择框
|
||||
if (dHistoryParams.value.length > 0) {
|
||||
const isPass = await useConfirm('提示', '使用模板后,当前页面将会被替换,是否继续', 'warning')
|
||||
if (!isPass) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
store.commit('managerEdit', false)
|
||||
store.commit('setDWidgets', [])
|
||||
|
||||
function cateChange(type: any) {
|
||||
state.title = type.name
|
||||
const init = pageOptions.cate != type.id
|
||||
pageOptions.cate = type.id
|
||||
load(init, pageOptions.stat)
|
||||
}
|
||||
setTempId(item.id)
|
||||
|
||||
function checkHeight() {
|
||||
// 检查高度是否占满,否则继续请求下一页
|
||||
const isLess = listRef.value.offsetHeight > listRef.value.firstElementChild.offsetHeight
|
||||
isLess && load()
|
||||
}
|
||||
let result = null
|
||||
if (!item.data) {
|
||||
const res = await api.home.getTempDetail({ id: item.id })
|
||||
result = JSON.parse(res.data)
|
||||
} else {
|
||||
result = JSON.parse(item.data)
|
||||
}
|
||||
const { page, widgets } = result
|
||||
console.log(widgets)
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
load,
|
||||
cateChange,
|
||||
listRef,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['tempEditing', 'dHistoryParams']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['selectWidget', 'updatePageData', 'setTemplate', 'pushHistory']),
|
||||
async selectItem(item: any) {
|
||||
this.$store.commit('setShowMoveable', false) // 清理掉上一次的选择框
|
||||
if (this.dHistoryParams.length > 0) {
|
||||
const isPass = await useConfirm('提示', '使用模板后,当前页面将会被替换,是否继续', 'warning')
|
||||
if (!isPass) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
this.$store.commit('managerEdit', false)
|
||||
this.$store.commit('setDWidgets', [])
|
||||
|
||||
this.setTempId(item.id)
|
||||
|
||||
let result = null
|
||||
if (!item.data) {
|
||||
const res = await api.home.getTempDetail({ id: item.id })
|
||||
result = JSON.parse(res.data)
|
||||
} else {
|
||||
result = JSON.parse(item.data)
|
||||
}
|
||||
const { page, widgets } = result
|
||||
console.log(widgets)
|
||||
|
||||
this.$store.commit('setDPage', page)
|
||||
this.setTemplate(widgets)
|
||||
setTimeout(() => {
|
||||
this.$store.commit('zoomScreenChange')
|
||||
}, 300)
|
||||
this.selectWidget({
|
||||
uuid: '-1',
|
||||
})
|
||||
},
|
||||
store.commit('setDPage', page)
|
||||
store.dispatch('setTemplate', widgets)
|
||||
// setTemplate(widgets)
|
||||
setTimeout(() => {
|
||||
store.commit('zoomScreenChange')
|
||||
}, 300)
|
||||
store.dispatch('selectWidget', {
|
||||
uuid: '-1'
|
||||
})
|
||||
// selectWidget({
|
||||
// uuid: '-1',
|
||||
// })
|
||||
}
|
||||
// action({ name, value }: any, item: any, index: number) {
|
||||
// switch (name) {
|
||||
// case 'edit':
|
||||
@ -158,12 +169,17 @@ export default defineComponent({
|
||||
// setTempStat({ id }: any, stat: string) {
|
||||
// api.home.setTempStat({ id, stat })
|
||||
// },
|
||||
setTempId(tempId: number | string) {
|
||||
const { id } = this.$route.query
|
||||
this.$router.push({ path: '/home', query: { tempid: tempId, id }, replace: true })
|
||||
},
|
||||
},
|
||||
function setTempId(tempId: number | string) {
|
||||
const { id } = route.query
|
||||
router.push({ path: '/home', query: { tempid: tempId, id }, replace: true })
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
load,
|
||||
cateChange,
|
||||
listRef,
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-04 02:04:04
|
||||
* @Description: 列表分类头部
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 02:30:59
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<div v-if="!isBack" class="content__wrap">
|
||||
@ -21,22 +21,34 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
props: ['types', 'isBack'],
|
||||
emits: ['select', 'back'],
|
||||
setup(props, { emit }) {
|
||||
const select = (item: any) => {
|
||||
emit('select', item)
|
||||
}
|
||||
const back = () => {
|
||||
emit('back')
|
||||
}
|
||||
return { select, back }
|
||||
},
|
||||
})
|
||||
export type TClassHeaderTypeData = {
|
||||
name: string
|
||||
}
|
||||
|
||||
type TProps = {
|
||||
types: TClassHeaderTypeData[]
|
||||
isBack: boolean
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'select', data: string[]): void
|
||||
(event: 'back'): void
|
||||
}
|
||||
|
||||
const { types, isBack } = defineProps<TProps>()
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const select = (item: any) => {
|
||||
emit('select', item)
|
||||
}
|
||||
const back = () => {
|
||||
emit('back')
|
||||
}
|
||||
|
||||
defineExpose({ select, back })
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-04 19:12:40
|
||||
* @Description: 图片描述ToolTip
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 22:51:06
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<el-tooltip :disabled="!detail.author" :offset="1" effect="light" placement="bottom-start" :hide-after="0" :enterable="false" raw-content>
|
||||
@ -17,15 +17,18 @@
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
export type TImageTipDetailData = {
|
||||
author: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type Tprops = {
|
||||
detail: TImageTipDetailData
|
||||
}
|
||||
|
||||
const { detail } = defineProps<Tprops>()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
detail: {},
|
||||
},
|
||||
setup() {
|
||||
return {}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
@ -2,14 +2,18 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-12-16 16:20:16
|
||||
* @Description: 瀑布流组件
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-12-11 11:45:24
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<div ref="imgWaterFall" :style="{ height: countHeight + 'px' }" class="img-water-fall">
|
||||
<div ref="imgWaterFall" :style="{ height: state.countHeight + 'px' }" class="img-water-fall">
|
||||
<!-- backgroundImage: `url(${item.cover})` -->
|
||||
<div v-for="(item, i) in list" :key="i + 'iwf'" :style="{ top: item.top + 'px', left: item.left + 'px', width: width + 'px', height: item.height + 'px' }" class="img-box" @click.stop="selectItem(item, i)">
|
||||
<edit-model v-if="edit" :options="edit" :data="{ item, i }">
|
||||
<div
|
||||
v-for="(item, i) in state.list" :key="i + 'iwf'"
|
||||
:style="{ top: item.top + 'px', left: item.left + 'px', width: state.width + 'px', height: item.height + 'px' }"
|
||||
class="img-box" @click.stop="selectItem(item, i)"
|
||||
>
|
||||
<edit-model v-if="edit" :options="props.edit" :data="{ item, i }">
|
||||
{{ item.isDelect }}
|
||||
<div v-if="item.isDelect" class="list__mask">已删除</div>
|
||||
<el-image v-if="!item.fail" class="img" :src="item.cover" lazy loading="lazy" @error="loadError(item)" />
|
||||
@ -20,76 +24,82 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const NAME = 'img-water-fall'
|
||||
import { defineComponent, toRefs, reactive, watch } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
// const NAME = 'img-water-fall'
|
||||
import { IGetTempListData } from '@/api/home';
|
||||
import { reactive, watch, defineProps, defineExpose, defineEmits } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: NAME,
|
||||
props: {
|
||||
listData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
edit: {}
|
||||
type TProps = {
|
||||
listData: IGetTempListData[]
|
||||
edit?: Record<string, any>
|
||||
}
|
||||
|
||||
type TState = {
|
||||
width: number
|
||||
countHeight: number
|
||||
list: IGetTempListData[]
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'select', data: IGetTempListData): void
|
||||
(event: 'load'): void
|
||||
}
|
||||
|
||||
const props = defineProps<TProps>()
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const state = reactive<TState>({
|
||||
width: 146, // 图片的宽度
|
||||
list: [],
|
||||
countHeight: 0,
|
||||
})
|
||||
|
||||
const columnHeights: number[] = [] // 列的高度
|
||||
const columnNums = 2 // 总共有多少列
|
||||
const gap = 7 // 图片之间的间隔
|
||||
|
||||
watch(
|
||||
() => props.listData,
|
||||
() => {
|
||||
columnHeights.length = 0
|
||||
const widthLimit = state.width * columnNums // + gap * (columnNums - 1) // 每行宽度
|
||||
const cloneList = JSON.parse(JSON.stringify(props.listData))
|
||||
for (let i = 0; i < cloneList.length; i++) {
|
||||
let index = i % columnNums
|
||||
const item = cloneList[i]
|
||||
item.height = (item.height / item.width) * state.width // 图片高度
|
||||
item.left = index * (widthLimit / columnNums + gap) // 定位
|
||||
item.top = columnHeights[index] + gap || 0 // 定位
|
||||
// columnHeights[index] = isNaN(columnHeights[index]) ? item.height : item.height + columnHeights[index] + gap // 记录列高度
|
||||
// 找出最短边
|
||||
if (isNaN(columnHeights[index])) {
|
||||
columnHeights[index] = item.height
|
||||
} else {
|
||||
index = columnHeights.indexOf(Math.min(...columnHeights))
|
||||
item.left = index * (widthLimit / columnNums + gap)
|
||||
item.top = columnHeights[index] + gap || 0
|
||||
columnHeights[index] = item.height + columnHeights[index] + gap
|
||||
}
|
||||
}
|
||||
state.countHeight = Math.max(...columnHeights)
|
||||
state.list = cloneList
|
||||
},
|
||||
emits: ['select', 'load'],
|
||||
setup(props, { emit }) {
|
||||
const state = reactive({
|
||||
width: 146, // 图片的宽度
|
||||
list: [],
|
||||
countHeight: 0,
|
||||
})
|
||||
)
|
||||
|
||||
const columnHeights = [] // 列的高度
|
||||
const columnNums = 2 // 总共有多少列
|
||||
const gap = 7 // 图片之间的间隔
|
||||
const load = () => {
|
||||
emit('load')
|
||||
}
|
||||
const selectItem = (value: IGetTempListData, index: number) => {
|
||||
emit('select', value)
|
||||
}
|
||||
const loadError = (item: IGetTempListData) => {
|
||||
item.fail = true
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.listData,
|
||||
() => {
|
||||
columnHeights.length = 0
|
||||
const widthLimit = state.width * columnNums // + gap * (columnNums - 1) // 每行宽度
|
||||
const cloneList = JSON.parse(JSON.stringify(props.listData))
|
||||
for (let i = 0; i < cloneList.length; i++) {
|
||||
let index = i % columnNums
|
||||
const item = cloneList[i]
|
||||
item.height = (item.height / item.width) * state.width // 图片高度
|
||||
item.left = index * (widthLimit / columnNums + gap) // 定位
|
||||
item.top = columnHeights[index] + gap || 0 // 定位
|
||||
// columnHeights[index] = isNaN(columnHeights[index]) ? item.height : item.height + columnHeights[index] + gap // 记录列高度
|
||||
// 找出最短边
|
||||
if (isNaN(columnHeights[index])) {
|
||||
columnHeights[index] = item.height
|
||||
} else {
|
||||
index = columnHeights.indexOf(Math.min(...columnHeights))
|
||||
item.left = index * (widthLimit / columnNums + gap)
|
||||
item.top = columnHeights[index] + gap || 0
|
||||
columnHeights[index] = item.height + columnHeights[index] + gap
|
||||
}
|
||||
}
|
||||
state.countHeight = Math.max(...columnHeights)
|
||||
state.list = cloneList
|
||||
},
|
||||
)
|
||||
|
||||
const load = () => {
|
||||
emit('load')
|
||||
}
|
||||
const selectItem = (value, index) => {
|
||||
emit('select', value)
|
||||
}
|
||||
const loadError = (item) => {
|
||||
item.fail = true
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
load,
|
||||
selectItem,
|
||||
loadError,
|
||||
}
|
||||
},
|
||||
defineExpose({
|
||||
load,
|
||||
selectItem,
|
||||
loadError,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -2,13 +2,22 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-23 15:48:52
|
||||
* @Description: 图片列表组件 Bookshelf Layout
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-02-29 16:52:37
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<ul ref="listRef" class="img-list-wrap" :style="{ paddingBottom: isShort ? '15px' : '200px' }" @scroll="scrollEvent($event)">
|
||||
<div class="list">
|
||||
<div v-for="(item, i) in list" :key="i + 'i'" :style="{ width: item.listWidth + 'px', marginRight: item.gap + 'px' }" class="list__img" draggable="false" @mousedown="dragStart($event, i)" @mousemove="mousemove" @mouseup="mouseup" @click.stop="select(i)" @dragstart="dragStart($event, i)">
|
||||
<div
|
||||
v-for="(item, i) in state.list" :key="i + 'i'"
|
||||
:style="{ width: item.listWidth + 'px', marginRight: item.gap + 'px' }"
|
||||
class="list__img" draggable="false"
|
||||
@mousedown="dragStart($event, i)"
|
||||
@mousemove="mousemove"
|
||||
@mouseup="mouseup"
|
||||
@click.stop="select(i)"
|
||||
@dragstart="dragStart($event, i)"
|
||||
>
|
||||
<edit-model v-if="edit" :options="edit" :data="{ item, i }">
|
||||
<div v-if="item.isDelect" class="list__mask">已删除</div>
|
||||
<el-image class="img transparent-bg" :src="item.thumb || item.url" :style="{ height: getInnerHeight(item) + 'px' }" lazy loading="lazy" />
|
||||
@ -24,163 +33,182 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isDone" v-show="loading" class="loading"><i class="el-icon-loading" /> 拼命加载中</div>
|
||||
<div v-if="!isDone" v-show="state.loading" class="loading"><i class="el-icon-loading" /> 拼命加载中</div>
|
||||
<div v-else class="loading">全部加载完毕</div>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, watch, nextTick } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, watch, nextTick, defineProps, defineExpose, defineEmits, ref } from 'vue'
|
||||
import DragHelper from '@/common/hooks/dragHelper'
|
||||
import setImageData from '@/common/methods/DesignFeatures/setImage'
|
||||
import setImageData, { TItem2DataParam } from '@/common/methods/DesignFeatures/setImage'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
listData: {},
|
||||
edit: {},
|
||||
isDone: {},
|
||||
isShort: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['load', 'drag', 'select'],
|
||||
setup(props, context) {
|
||||
const state: any = reactive({
|
||||
loading: true,
|
||||
list: [],
|
||||
listRef: null,
|
||||
})
|
||||
type TProps = {
|
||||
listData: TCommonPhotoListData[]
|
||||
edit: Record<string, any>
|
||||
isDone: Record<string, any>
|
||||
isShort: boolean
|
||||
}
|
||||
|
||||
const dragHelper = new DragHelper()
|
||||
let isDrag = false
|
||||
let startPoint = { x: 99999, y: 99999 }
|
||||
const mouseup = (e: any) => {
|
||||
e.preventDefault()
|
||||
setTimeout(() => {
|
||||
isDrag = false
|
||||
startPoint = { x: 99999, y: 99999 }
|
||||
}, 10)
|
||||
type TEmits = {
|
||||
(event: 'load'): void
|
||||
(event: 'select', data: number): void
|
||||
(event: 'drag', data: number): void
|
||||
|
||||
}
|
||||
|
||||
type TState = {
|
||||
loading: boolean
|
||||
list: TCommonPhotoListData[]
|
||||
}
|
||||
|
||||
const { listData, edit, isDone, isShort } = withDefaults(defineProps<TProps>(), {
|
||||
isShort: false
|
||||
})
|
||||
const emit = defineEmits<TEmits>()
|
||||
const listRef = ref<HTMLElement | null>(null)
|
||||
const state = reactive<TState>({
|
||||
loading: true,
|
||||
list: [],
|
||||
})
|
||||
|
||||
const dragHelper = new DragHelper()
|
||||
let isDrag = false
|
||||
let startPoint = { x: 99999, y: 99999 }
|
||||
|
||||
const mouseup = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
setTimeout(() => {
|
||||
isDrag = false
|
||||
startPoint = { x: 99999, y: 99999 }
|
||||
}, 10)
|
||||
}
|
||||
|
||||
const mousemove = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
if (e.x - startPoint.x > 2 || e.y - startPoint.y > 2) {
|
||||
isDrag = true
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => listData,
|
||||
async (newList: TCommonPhotoListData[], oldList: TCommonPhotoListData[]) => {
|
||||
!oldList && (oldList = [])
|
||||
if (newList.length <= 0) {
|
||||
state.list.length = 0
|
||||
return
|
||||
}
|
||||
const mousemove = (e: any) => {
|
||||
e.preventDefault()
|
||||
if (e.x - startPoint.x > 2 || e.y - startPoint.y > 2) {
|
||||
isDrag = true
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.listData,
|
||||
async (newList: any, oldList: any) => {
|
||||
!oldList && (oldList = [])
|
||||
if (newList.length <= 0) {
|
||||
state.list.length = 0
|
||||
let list = newList.filter((v: TCommonPhotoListData) => !newList.includes(v) || !oldList.includes(v)) // difference
|
||||
list = JSON.parse(JSON.stringify(list))
|
||||
const marginRight = 6 // 间距
|
||||
const limitWidth = (await getFatherWidth()) - marginRight
|
||||
const standardHeight = 280 // 高度阈值
|
||||
const neatArr: TCommonPhotoListData[][] = [] // 整理后的数组
|
||||
function factory(cutArr: TCommonPhotoListData[]) {
|
||||
return new Promise<{ height: number, list: TCommonPhotoListData[] }>((resolve) => {
|
||||
const lineup = list.shift()
|
||||
if (!lineup) {
|
||||
resolve({ height: calculate(cutArr), list: cutArr })
|
||||
return
|
||||
}
|
||||
let list = newList.filter((v: any) => !newList.includes(v) || !oldList.includes(v)) // difference
|
||||
list = JSON.parse(JSON.stringify(list))
|
||||
const marginRight = 6 // 间距
|
||||
const limitWidth = (await getFatherWidth()) - marginRight
|
||||
const standardHeight = 280 // 高度阈值
|
||||
const neatArr: any = [] // 整理后的数组
|
||||
function factory(cutArr: any) {
|
||||
return new Promise((resolve) => {
|
||||
const lineup = list.shift()
|
||||
if (!lineup) {
|
||||
resolve({ height: calculate(cutArr), list: cutArr })
|
||||
return
|
||||
}
|
||||
cutArr.push(lineup)
|
||||
const finalHeight = calculate(cutArr)
|
||||
if (finalHeight > standardHeight) {
|
||||
resolve(factory(cutArr))
|
||||
} else {
|
||||
resolve({ height: finalHeight, list: cutArr })
|
||||
}
|
||||
})
|
||||
}
|
||||
function calculate(cutArr: any) {
|
||||
let cumulate = 0
|
||||
for (const iterator of cutArr) {
|
||||
const { width, height } = iterator
|
||||
cumulate += width / height
|
||||
}
|
||||
return (limitWidth - marginRight * (cutArr.length - 1)) / cumulate
|
||||
}
|
||||
async function handleList() {
|
||||
if (list.length <= 0) {
|
||||
return
|
||||
}
|
||||
const { list: newList, height }: any = await factory([list.shift()])
|
||||
neatArr.push(
|
||||
newList.map((x: any, index) => {
|
||||
x.listWidth = (x.width / x.height) * height
|
||||
x.gap = index !== newList.length - 1 ? marginRight : 0
|
||||
return x
|
||||
}),
|
||||
)
|
||||
if (list.length > 0) {
|
||||
await handleList()
|
||||
}
|
||||
cutArr.push(lineup)
|
||||
const finalHeight = calculate(cutArr)
|
||||
if (finalHeight > standardHeight) {
|
||||
resolve(factory(cutArr))
|
||||
} else {
|
||||
resolve({ height: finalHeight, list: cutArr })
|
||||
}
|
||||
})
|
||||
}
|
||||
function calculate(cutArr: TCommonPhotoListData[]) {
|
||||
let cumulate = 0
|
||||
for (const iterator of cutArr) {
|
||||
const { width, height } = iterator
|
||||
cumulate += width / height
|
||||
}
|
||||
return (limitWidth - marginRight * (cutArr.length - 1)) / cumulate
|
||||
}
|
||||
async function handleList() {
|
||||
if (list.length <= 0) {
|
||||
return
|
||||
}
|
||||
const { list: newList, height } = await factory([(list.shift() as TCommonPhotoListData)])
|
||||
neatArr.push(
|
||||
newList.map((x: TCommonPhotoListData, index: number) => {
|
||||
x.listWidth = (x.width / x.height) * height
|
||||
x.gap = index !== newList.length - 1 ? marginRight : 0
|
||||
return x
|
||||
}),
|
||||
)
|
||||
if (list.length > 0) {
|
||||
await handleList()
|
||||
state.list = state.list.concat(neatArr.flat(1))
|
||||
state.loading = false
|
||||
},
|
||||
)
|
||||
|
||||
async function getFatherWidth() {
|
||||
await nextTick()
|
||||
const dom = state.listRef
|
||||
const father = dom.parentElement || dom.parentNode
|
||||
return father.offsetWidth
|
||||
}
|
||||
|
||||
function getRef() {
|
||||
// 用于在组件外调用内部ref
|
||||
return state.listRef
|
||||
}
|
||||
|
||||
const load = () => {
|
||||
state.loading = true
|
||||
context.emit('load')
|
||||
}
|
||||
const select = (i: number) => {
|
||||
!isDrag && !state.list[i].isDelect && context.emit('select', i)
|
||||
}
|
||||
const dragStart = async (e: Event | any, i: number) => {
|
||||
e.preventDefault()
|
||||
startPoint = { x: e.x, y: e.y }
|
||||
if (!state.list[i].isDelect) {
|
||||
const img = await setImageData(state.list[i])
|
||||
dragHelper.start(e, img.canvasWidth)
|
||||
context.emit('drag', i)
|
||||
}
|
||||
}
|
||||
function delItem(i: number) {
|
||||
state.list[i].isDelect = true
|
||||
}
|
||||
|
||||
const scrollEvent = (e: any) => {
|
||||
if (e.target.scrollTop + e.target.offsetHeight + 200 >= e.target.scrollHeight) {
|
||||
load()
|
||||
}
|
||||
}
|
||||
|
||||
const getInnerHeight = ({ height, listWidth, width }: any) => (height * listWidth) / width
|
||||
|
||||
return {
|
||||
load,
|
||||
dragStart,
|
||||
select,
|
||||
...toRefs(state),
|
||||
delItem,
|
||||
scrollEvent,
|
||||
getRef,
|
||||
mouseup,
|
||||
mousemove,
|
||||
getInnerHeight,
|
||||
}
|
||||
await handleList()
|
||||
state.list = state.list.concat(neatArr.flat(1))
|
||||
state.loading = false
|
||||
},
|
||||
)
|
||||
|
||||
async function getFatherWidth() {
|
||||
await nextTick()
|
||||
if (!listRef.value) return 0
|
||||
const father = listRef.value.parentElement || listRef.value.parentNode
|
||||
if (!father) return 0
|
||||
return (father as HTMLElement).offsetWidth
|
||||
}
|
||||
|
||||
function getRef() {
|
||||
// 用于在组件外调用内部ref
|
||||
return listRef
|
||||
}
|
||||
|
||||
const load = () => {
|
||||
state.loading = true
|
||||
emit('load')
|
||||
}
|
||||
const select = (i: number) => {
|
||||
!isDrag && !state.list[i].isDelect && emit('select', i)
|
||||
}
|
||||
|
||||
const dragStart = async (e: Event | any, i: number) => {
|
||||
e.preventDefault()
|
||||
startPoint = { x: e.x, y: e.y }
|
||||
if (!state.list[i].isDelect) {
|
||||
const setImageParams: TItem2DataParam = {
|
||||
width: state.list[i].width,
|
||||
height: state.list[i].height,
|
||||
url: state.list[i].url || '',
|
||||
model: state.list[i].model
|
||||
}
|
||||
const img = await setImageData(setImageParams)
|
||||
dragHelper.start(e, img.canvasWidth)
|
||||
emit('drag', i)
|
||||
}
|
||||
}
|
||||
function delItem(i: number) {
|
||||
state.list[i].isDelect = true
|
||||
}
|
||||
|
||||
const scrollEvent = (e: any) => {
|
||||
if (e.target.scrollTop + e.target.offsetHeight + 200 >= e.target.scrollHeight) {
|
||||
load()
|
||||
}
|
||||
}
|
||||
|
||||
const getInnerHeight = ({ height, listWidth, width }: any) => (height * listWidth) / width
|
||||
|
||||
defineExpose({
|
||||
load,
|
||||
dragStart,
|
||||
select,
|
||||
delItem,
|
||||
scrollEvent,
|
||||
getRef,
|
||||
mouseup,
|
||||
mousemove,
|
||||
getInnerHeight,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-01-27 11:05:48
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 01:53:10
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<div class="search__wrap">
|
||||
@ -11,66 +11,85 @@
|
||||
<div class="search__type"><i class="iconfont icon-ego-caidan" /></div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="type in materialCates" :key="type.id" @click="action('change', type, type.id)">
|
||||
<span :class="['cate__text', { 'cate--select': +currentIndex === type.id }]">{{ type.name }}</span>
|
||||
<el-dropdown-item
|
||||
v-for="type in state.materialCates" :key="type.id"
|
||||
@click="action('change', type, type.id)"
|
||||
>
|
||||
<span :class="['cate__text', { 'cate--select': + state.currentIndex === type.id }]">{{ type.name }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span v-else style="width: 1rem"></span>
|
||||
|
||||
<el-input v-model="searchValue" size="large" placeholder="输入关键词搜索" class="input-with-select">
|
||||
<el-input v-model="state.searchValue" size="large" placeholder="输入关键词搜索" class="input-with-select">
|
||||
<template #append>
|
||||
<el-button><i class="iconfont icon-search"></i></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, watch } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, toRefs, watch, defineProps, defineEmits, defineExpose } from 'vue'
|
||||
import { ElDropdown, ElDropdownItem, ElDropdownMenu } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import api from '@/api'
|
||||
|
||||
export default defineComponent({
|
||||
components: { ElDropdown, ElDropdownItem, ElDropdownMenu },
|
||||
props: ['type', 'modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, context) {
|
||||
const route = useRoute()
|
||||
const state: any = reactive({
|
||||
searchValue: '',
|
||||
materialCates: [],
|
||||
currentIndex: 1,
|
||||
})
|
||||
type TProps = {
|
||||
type: string
|
||||
modelValue: string
|
||||
}
|
||||
|
||||
if (props.type != 'none') {
|
||||
api.home.getCategories({ type: 1 }).then((list: any) => {
|
||||
list.unshift({ id: 0, name: '全部' })
|
||||
state.materialCates = list
|
||||
const { cate } = route.query
|
||||
cate && (state.currentIndex = cate)
|
||||
cate && action('change', state.materialCates[Number(cate)], Number(cate))
|
||||
})
|
||||
}
|
||||
type TEmits = {
|
||||
(event: 'update:modelValue', data: string): void
|
||||
(event: 'change', data: TMaterialCatesData): void
|
||||
}
|
||||
|
||||
watch(
|
||||
() => state.searchValue,
|
||||
() => {
|
||||
context.emit('update:modelValue', state.searchValue)
|
||||
},
|
||||
)
|
||||
type TMaterialCatesData = {id: string | number, name: string}
|
||||
|
||||
function action(fn: string, type: any, currentIndex: number | string) {
|
||||
currentIndex && (state.currentIndex = currentIndex)
|
||||
context.emit(fn, type)
|
||||
}
|
||||
return {
|
||||
...toRefs(state),
|
||||
action,
|
||||
}
|
||||
},
|
||||
type TState = {
|
||||
searchValue: string
|
||||
materialCates: TMaterialCatesData[]
|
||||
currentIndex: number | string
|
||||
}
|
||||
|
||||
const props = defineProps<TProps>()
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const route = useRoute()
|
||||
const state = reactive<TState>({
|
||||
searchValue: '',
|
||||
materialCates: [],
|
||||
currentIndex: 1,
|
||||
})
|
||||
|
||||
if (props.type != 'none') {
|
||||
api.home.getCategories({ type: 1 }).then((list: any) => {
|
||||
list.unshift({ id: 0, name: '全部' })
|
||||
state.materialCates = list
|
||||
const { cate } = route.query
|
||||
cate && (state.currentIndex = cate as string)
|
||||
cate && action('change', state.materialCates[Number(cate)], Number(cate))
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => state.searchValue,
|
||||
() => {
|
||||
emit('update:modelValue', state.searchValue)
|
||||
},
|
||||
)
|
||||
|
||||
function action(fn: 'change', type: TMaterialCatesData, currentIndex: number | string) {
|
||||
currentIndex && (state.currentIndex = currentIndex)
|
||||
emit(fn, type)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
action
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
Loading…
x
Reference in New Issue
Block a user