10 KiB
10 KiB
图集功能使用指南
概述
新增的 images JSONB 字段支持在单个内容条目中存储多张图片,适用于:
- 📸 图片相册/图集
- 🎠 轮播图展示
- 🖼️ 产品多图展示
- 📱 社交媒体多图帖子
- 🏞️ 风景/旅游图集
images 字段结构
推荐的 JSONB 数据结构:
{
"count": 5,
"cover_index": 0,
"layout": "grid",
"items": [
{
"url": "https://example.com/image1.jpg",
"thumbnail": "https://example.com/image1_thumb.jpg",
"width": 1920,
"height": 1080,
"size": 245760,
"format": "jpg",
"alt": "图片描述",
"caption": "图片标题",
"order": 0,
"metadata": {
"camera": "iPhone 14 Pro",
"location": "北京",
"taken_at": "2025-01-15T10:30:00Z"
}
},
{
"url": "https://example.com/image2.jpg",
"thumbnail": "https://example.com/image2_thumb.jpg",
"width": 1920,
"height": 1080,
"size": 198432,
"format": "jpg",
"alt": "第二张图片",
"caption": "美丽的风景",
"order": 1,
"metadata": {}
}
]
}
数据库操作示例
1. 插入图集内容
INSERT INTO ak_contents (
title,
content_type,
summary,
images
) VALUES (
'北京旅游图集',
'image',
'记录北京之旅的美好时光',
'{
"count": 3,
"cover_index": 0,
"layout": "masonry",
"items": [
{
"url": "https://example.com/beijing1.jpg",
"thumbnail": "https://example.com/beijing1_thumb.jpg",
"width": 1920,
"height": 1280,
"size": 345600,
"format": "jpg",
"alt": "天安门广场",
"caption": "庄严的天安门广场",
"order": 0
},
{
"url": "https://example.com/beijing2.jpg",
"thumbnail": "https://example.com/beijing2_thumb.jpg",
"width": 1920,
"height": 1080,
"size": 298400,
"format": "jpg",
"alt": "故宫博物院",
"caption": "金碧辉煌的紫禁城",
"order": 1
},
{
"url": "https://example.com/beijing3.jpg",
"thumbnail": "https://example.com/beijing3_thumb.jpg",
"width": 1920,
"height": 1440,
"size": 412800,
"format": "jpg",
"alt": "长城",
"caption": "雄伟的万里长城",
"order": 2
}
]
}'::jsonb
);
2. 查询图集数据
-- 查询所有图集内容
SELECT
id,
title,
images->>'count' as image_count,
images->>'layout' as layout_type,
images->'items'->0->>'url' as cover_image
FROM ak_contents
WHERE content_type = 'image'
AND images IS NOT NULL;
-- 查询包含特定图片数量的图集
SELECT * FROM ak_contents
WHERE content_type = 'image'
AND (images->>'count')::int >= 5;
-- 查询包含特定格式图片的图集
SELECT * FROM ak_contents
WHERE content_type = 'image'
AND images @> '{"items": [{"format": "jpg"}]}';
3. 更新图集
-- 添加新图片到图集
UPDATE ak_contents
SET images = jsonb_set(
images,
'{items}',
(images->'items') || '[{
"url": "https://example.com/new_image.jpg",
"thumbnail": "https://example.com/new_image_thumb.jpg",
"width": 1920,
"height": 1080,
"size": 256000,
"format": "jpg",
"alt": "新图片",
"caption": "新添加的图片",
"order": 3
}]'::jsonb
)
WHERE id = 'your-content-id';
-- 更新图片数量
UPDATE ak_contents
SET images = jsonb_set(images, '{count}', '4')
WHERE id = 'your-content-id';
前端组件设计建议
1. 图集展示组件 (ImageGallery.uvue)
<template>
<view class="image-gallery">
<!-- 图集头部信息 -->
<view class="gallery-header">
<text class="image-count">{{ galleryData.count }} {{ $t('images.unit') }}</text>
<text class="layout-type">{{ $t('images.layout.' + galleryData.layout) }}</text>
</view>
<!-- 图片网格 -->
<view class="image-grid" :class="['layout-' + galleryData.layout]">
<view
v-for="(item, index) in galleryData.items"
:key="index"
class="image-item"
@click="openImageViewer(index)">
<image
:src="item.thumbnail || item.url"
:alt="item.alt"
class="gallery-image"
mode="aspectFill" />
<view v-if="item.caption" class="image-caption">
{{ item.caption }}
</view>
</view>
</view>
<!-- 图片查看器 -->
<ImageViewer
v-if="showViewer"
:images="galleryData.items"
:currentIndex="currentImageIndex"
@close="closeImageViewer" />
</view>
</template>
<script setup>
const props = defineProps({
galleryData: Object,
contentId: String
})
const showViewer = ref(false)
const currentImageIndex = ref(0)
const openImageViewer = (index) => {
currentImageIndex.value = index
showViewer.value = true
// 记录图片查看行为
recordImageView(props.contentId, index)
}
const closeImageViewer = () => {
showViewer.value = false
}
const recordImageView = async (contentId, imageIndex) => {
// 记录图片浏览行为到数据库
await supa.from('ak_image_view_records').insert({
content_id: contentId,
user_id: getCurrentUserId(),
view_duration: 0,
zoom_level: 1.0,
device_type: getDeviceType()
})
}
</script>
2. 图集上传组件 (ImageUploader.uvue)
<template>
<view class="image-uploader">
<view class="upload-area" @click="selectImages">
<text class="upload-text">{{ $t('images.upload.selectImages') }}</text>
</view>
<view class="image-preview-grid">
<view
v-for="(image, index) in uploadedImages"
:key="index"
class="preview-item">
<image :src="image.thumbnail" class="preview-image" />
<input
v-model="image.caption"
:placeholder="$t('images.upload.captionPlaceholder')"
class="caption-input" />
<view class="remove-btn" @click="removeImage(index)">×</view>
</view>
</view>
<view class="upload-controls">
<picker :value="selectedLayout" @change="onLayoutChange" :range="layoutOptions">
<text>{{ $t('images.layout.title') }}: {{ $t('images.layout.' + selectedLayout) }}</text>
</picker>
<button @click="uploadGallery" :disabled="!canUpload">
{{ $t('images.upload.submit') }}
</button>
</view>
</view>
</template>
<script setup>
const uploadedImages = ref([])
const selectedLayout = ref('grid')
const layoutOptions = ['grid', 'masonry', 'carousel', 'list']
const selectImages = () => {
uni.chooseImage({
count: 9,
success: (res) => {
processSelectedImages(res.tempFilePaths)
}
})
}
const processSelectedImages = async (filePaths) => {
for (const filePath of filePaths) {
// 上传图片并获取 URL
const uploadResult = await uploadImage(filePath)
uploadedImages.value.push({
url: uploadResult.url,
thumbnail: uploadResult.thumbnail,
width: uploadResult.width,
height: uploadResult.height,
size: uploadResult.size,
format: uploadResult.format,
alt: '',
caption: '',
order: uploadedImages.value.length
})
}
}
const uploadGallery = async () => {
const galleryData = {
count: uploadedImages.value.length,
cover_index: 0,
layout: selectedLayout.value,
items: uploadedImages.value
}
// 保存到数据库
await supa.from('ak_contents').insert({
title: '新图集',
content_type: 'image',
images: galleryData
})
}
</script>
多语言支持
需要在 i18n 文件中添加:
// zh-CN
"images": {
"unit": "张图片",
"layout": {
"title": "布局方式",
"grid": "网格布局",
"masonry": "瀑布流",
"carousel": "轮播图",
"list": "列表布局"
},
"upload": {
"selectImages": "选择图片",
"captionPlaceholder": "添加图片说明",
"submit": "上传图集"
},
"viewer": {
"prev": "上一张",
"next": "下一张",
"zoom": "缩放",
"download": "下载"
}
}
// en-US
"images": {
"unit": "images",
"layout": {
"title": "Layout",
"grid": "Grid",
"masonry": "Masonry",
"carousel": "Carousel",
"list": "List"
},
"upload": {
"selectImages": "Select Images",
"captionPlaceholder": "Add caption",
"submit": "Upload Gallery"
},
"viewer": {
"prev": "Previous",
"next": "Next",
"zoom": "Zoom",
"download": "Download"
}
}
查询优化建议
1. 常用查询索引
-- 为常用的 JSONB 查询创建表达式索引
CREATE INDEX idx_contents_image_count ON ak_contents ((images->>'count')::int)
WHERE content_type = 'image';
CREATE INDEX idx_contents_image_layout ON ak_contents ((images->>'layout'))
WHERE content_type = 'image';
2. 查询示例
-- 查询热门图集(按图片数量排序)
SELECT
id, title,
(images->>'count')::int as image_count,
view_count
FROM vw_image_content_detail
WHERE images IS NOT NULL
ORDER BY image_count DESC, view_count DESC
LIMIT 20;
-- 查询特定布局的图集
SELECT * FROM ak_contents
WHERE content_type = 'image'
AND images->>'layout' = 'masonry'
ORDER BY created_at DESC;
总结
通过添加 images JSONB 字段,系统现在可以:
- ✅ 灵活存储多图片数据 - 支持任意数量的图片及其元数据
- ✅ 支持多种展示布局 - 网格、瀑布流、轮播等
- ✅ 保持查询性能 - 通过 GIN 索引优化 JSONB 查询
- ✅ 向后兼容 - 原有单图片字段依然可用
- ✅ 扩展性强 - 可轻松添加新的图片属性和元数据
这个设计为构建丰富的图片社交功能提供了强大的数据基础!