<template>
	<view class="uploader">
		<view class="grid" :style="{height: areaHeight+'rpx'}">
			<movable-area class="area" :style="{height: areaHeight+'rpx'}">
				<movable-view
					v-for="(img, index) in innerList"
					:key="img.uid"
					class="cell"
					:style="cellStyle(index)"
					:direction="'all'"
					:damping="40"
					:friction="2"
					:x="img.x"
					:y="img.y"
					@change="onMoving(index, $event)"
					@touchend="onMoveEnd(index)"
				>
					<image :src="img.url" mode="aspectFill" class="thumb" @click="preview(index)" />
					<view class="remove" @click.stop="remove(index)">×</view>
				</movable-view>

				<view v-if="innerList.length < max" class="adder" @click="choose">
					<text>＋</text>
				</view>
			</movable-area>
		</view>
	</view>
</template>

<script>
import { upload } from '../common/http.js'

const ITEM_SIZE = 210 // rpx
const GAP = 18 // rpx
const COLS = 3

function px(rpx) {
	// 以 750 设计稿计算；此函数仅用于内部位置计算，不写入样式
	return rpx
}

export default {
	name: 'ImageUploader',
	props: {
		modelValue: { type: Array, default: () => [] },
		max: { type: Number, default: 9 },
		uploadPath: { type: String, default: '/api/attachments' },
		uploadFieldName: { type: String, default: 'file' },
		formData: { type: Object, default: () => ({ ownerType: 'product' }) }
	},
	data() {
		return {
			innerList: []
		}
	},
	computed: {
		areaHeight() {
			const rows = Math.ceil((this.innerList.length + 1) / COLS) || 1
			return rows * ITEM_SIZE + (rows - 1) * GAP
		}
	},
	watch: {
		modelValue: {
			immediate: true,
			handler(list) {
				const mapped = (list || []).map((u, i) => ({
					uid: String(i) + '_' + (u.id || u.url || Math.random().toString(36).slice(2)),
					url: typeof u === 'string' ? u : (u.url || ''),
					x: this.posOf(i).x,
					y: this.posOf(i).y
				}))
				this.innerList = mapped
			}
		}
	},
	methods: {
		posOf(index) {
			const row = Math.floor(index / COLS)
			const col = index % COLS
			return { x: px(col * (ITEM_SIZE + GAP)), y: px(row * (ITEM_SIZE + GAP)) }
		},
		cellStyle(index) {
			return {
				width: ITEM_SIZE + 'rpx',
				height: ITEM_SIZE + 'rpx'
			}
		},
		preview(index) {
			uni.previewImage({ urls: this.innerList.map(i => i.url), current: index })
		},
		remove(index) {
			this.innerList.splice(index, 1)
			this.reflow()
			this.emit()
		},
		choose() {
			const remain = this.max - this.innerList.length
			if (remain <= 0) return
			uni.chooseImage({ count: remain, success: async (res) => {
				for (const path of res.tempFilePaths) {
					await this.doUpload(path)
				}
			}})
		},
		async doUpload(filePath) {
			try {
				const resp = await upload(this.uploadPath, filePath, this.formData, this.uploadFieldName)
				const url = resp?.url || resp?.data?.url || resp?.path || ''
				if (!url) throw new Error('上传响应无 url')
				this.innerList.push({ uid: Math.random().toString(36).slice(2), url, ...this.posOf(this.innerList.length) })
				this.reflow()
				this.emit()
			} catch (e) {
				uni.showToast({ title: '上传失败', icon: 'none' })
			}
		},
		onMoving(index, e) {
			// 实时更新移动中元素的位置
			const { x, y } = e.detail
			this.innerList[index].x = x
			this.innerList[index].y = y
		},
		onMoveEnd(index) {
			// 根据落点推算新的索引
			const mv = this.innerList[index]
			const col = Math.round(mv.x / (ITEM_SIZE + GAP))
			const row = Math.round(mv.y / (ITEM_SIZE + GAP))
			let newIndex = row * COLS + col
			newIndex = Math.max(0, Math.min(newIndex, this.innerList.length - 1))
			if (newIndex !== index) {
				const moved = this.innerList.splice(index, 1)[0]
				this.innerList.splice(newIndex, 0, moved)
			}
			this.reflow()
			this.emit()
		},
		reflow() {
			this.innerList.forEach((it, i) => {
				const p = this.posOf(i)
				it.x = p.x
				it.y = p.y
			})
		},
		emit() {
			this.$emit('update:modelValue', this.innerList.map(i => i.url))
			this.$emit('change', this.innerList.map(i => i.url))
		}
	}
}
</script>

<style>
.uploader { padding: 12rpx; background: #fff; }
.grid { position: relative; }
.area { width: 100%; position: relative; }
.cell { position: absolute; border-radius: 12rpx; overflow: hidden; box-shadow: 0 0 1rpx rgba(0,0,0,0.08); }
.thumb { width: 100%; height: 100%; }
.remove { position: absolute; right: 6rpx; top: 6rpx; background: rgba(0,0,0,0.45); color: #fff; width: 40rpx; height: 40rpx; text-align: center; line-height: 40rpx; border-radius: 20rpx; font-size: 28rpx; }
.adder { width: 210rpx; height: 210rpx; border: 2rpx dashed #ccc; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; color: #999; position: absolute; left: 0; top: 0; }
</style>







