fix: 样式修复、创作者头像修复
This commit is contained in:
parent
feb98dd865
commit
4aa11903f4
59
CLAUDE.md
59
CLAUDE.md
@ -36,3 +36,62 @@ Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need.
|
|||||||
2. Use `detect_changes` for code review.
|
2. Use `detect_changes` for code review.
|
||||||
3. Use `get_affected_flows` to understand impact.
|
3. Use `get_affected_flows` to understand impact.
|
||||||
4. Use `query_graph` pattern="tests_for" to check coverage.
|
4. Use `query_graph` pattern="tests_for" to check coverage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据库操作规范
|
||||||
|
|
||||||
|
### PostgreSQL 序列同步规则(强制)
|
||||||
|
|
||||||
|
**问题背景**:项目使用 `BIGSERIAL` / `autoIncrement` 自增主键。当通过 SQL 手动 `INSERT` 指定 `id` 值时,PostgreSQL 序列不会自动跟进,导致后续 GORM 插入报 `duplicate key value violates unique constraint`。
|
||||||
|
|
||||||
|
**触发场景**:
|
||||||
|
- 测试数据脚本(如 `create_gallery_test_users.go`)硬编码 ID
|
||||||
|
- 数据迁移脚本手动插入记录
|
||||||
|
- DBeaver / psql 手动补数据
|
||||||
|
|
||||||
|
**规范要求**:
|
||||||
|
|
||||||
|
1. **任何手动指定 ID 的 INSERT 语句,末尾必须同步重置序列**:
|
||||||
|
```sql
|
||||||
|
-- 错误示例(会导致序列不同步)
|
||||||
|
INSERT INTO assets (id, name, ...) VALUES (1000, 'xxx', ...);
|
||||||
|
|
||||||
|
-- 正确示例
|
||||||
|
INSERT INTO assets (id, name, ...) VALUES (1000, 'xxx', ...);
|
||||||
|
SELECT setval('assets_id_seq', (SELECT MAX(id) FROM assets));
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **脚本文件规范**:所有输出 SQL 的 Go 脚本(如 `backend/scripts/*.go`),必须在生成的 SQL 末尾包含序列重置语句:
|
||||||
|
```go
|
||||||
|
fmt.Printf(`SELECT setval('%s_id_seq', (SELECT MAX(id) FROM %s));\n`, tableName, tableName)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **新表创建时**:预留足够的序列起始值给测试数据:
|
||||||
|
```sql
|
||||||
|
CREATE SEQUENCE assets_id_seq START WITH 10000;
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **定期检查**:环境部署后执行以下 SQL 确认序列健康:
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
schemaname, sequencename, last_value,
|
||||||
|
(SELECT MAX(id) FROM assets) AS table_max_id,
|
||||||
|
last_value >= (SELECT MAX(id) FROM assets) AS is_healthy
|
||||||
|
FROM pg_sequences
|
||||||
|
WHERE sequencename = 'assets_id_seq';
|
||||||
|
```
|
||||||
|
|
||||||
|
**受影响表**(使用 autoIncrement 主键):
|
||||||
|
- `assets`
|
||||||
|
- `asset_registry`
|
||||||
|
- `users`
|
||||||
|
- `stars`
|
||||||
|
- `activity_assets`
|
||||||
|
- `collection_assets`
|
||||||
|
- `materials`
|
||||||
|
- `exhibitions`
|
||||||
|
- `galleries`
|
||||||
|
- 以及其他所有 `id BIGSERIAL PRIMARY KEY` 的表
|
||||||
|
|
||||||
|
**违规后果**:生产环境报 `duplicate key` 导致用户铸造/创建失败,需紧急修复序列。
|
||||||
|
|||||||
@ -437,8 +437,9 @@ func (s *assetService) GetAsset(req *pb.GetAssetRequest, userID, starID int64) (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 获取所有者的昵称(在该star下的nickname)
|
// 3. 获取所有者的昵称和头像(在该star下的nickname)
|
||||||
var ownerNickname string
|
var ownerNickname string
|
||||||
|
var ownerAvatar string
|
||||||
profile, err := s.userClient.GetFanProfile(context.Background(), asset.OwnerUID, asset.StarID)
|
profile, err := s.userClient.GetFanProfile(context.Background(), asset.OwnerUID, asset.StarID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger.Warn("Failed to get owner fan profile, using fallback nickname",
|
logger.Logger.Warn("Failed to get owner fan profile, using fallback nickname",
|
||||||
@ -450,6 +451,7 @@ func (s *assetService) GetAsset(req *pb.GetAssetRequest, userID, starID int64) (
|
|||||||
ownerNickname = fmt.Sprintf("User%d", asset.OwnerUID)
|
ownerNickname = fmt.Sprintf("User%d", asset.OwnerUID)
|
||||||
} else {
|
} else {
|
||||||
ownerNickname = profile.Nickname
|
ownerNickname = profile.Nickname
|
||||||
|
ownerAvatar = profile.AvatarUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 检查当前用户是否已点赞(需要获取当前展出中的 exhibition_id)
|
// 4. 检查当前用户是否已点赞(需要获取当前展出中的 exhibition_id)
|
||||||
@ -513,7 +515,7 @@ func (s *assetService) GetAsset(req *pb.GetAssetRequest, userID, starID int64) (
|
|||||||
Message: "",
|
Message: "",
|
||||||
Timestamp: time.Now().UnixMilli(),
|
Timestamp: time.Now().UnixMilli(),
|
||||||
},
|
},
|
||||||
Asset: ModelToProtoAssetDetail(asset, ownerNickname, isLiked, displayStatus, earnings, hourlyEarnings, exhibitionExpireAt, grade),
|
Asset: ModelToProtoAssetDetail(asset, ownerNickname, ownerAvatar, isLiked, displayStatus, earnings, hourlyEarnings, exhibitionExpireAt, grade),
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Logger.Debug("Get asset successful",
|
logger.Logger.Debug("Get asset successful",
|
||||||
@ -662,11 +664,21 @@ func ModelToProtoAsset(asset *models.Asset) *pb.AssetListItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ModelToProtoAssetDetail 将数据库模型转换为Proto格式(Asset详情)
|
// ModelToProtoAssetDetail 将数据库模型转换为Proto格式(Asset详情)
|
||||||
func ModelToProtoAssetDetail(asset *models.Asset, ownerNickname string, isLiked bool, displayStatus int32, earnings int64, hourlyEarnings float64, exhibitionExpireAt int64, grade int32) *pb.Asset {
|
func ModelToProtoAssetDetail(asset *models.Asset, ownerNickname string, ownerAvatar string, isLiked bool, displayStatus int32, earnings int64, hourlyEarnings float64, exhibitionExpireAt int64, grade int32) *pb.Asset {
|
||||||
if asset == nil {
|
if asset == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建持有者信息
|
||||||
|
var ownerInfo *pb.OwnerInfo
|
||||||
|
if ownerAvatar != "" || ownerNickname != "" {
|
||||||
|
ownerInfo = &pb.OwnerInfo{
|
||||||
|
UserId: asset.OwnerUID,
|
||||||
|
Nickname: ownerNickname,
|
||||||
|
Avatar: ownerAvatar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &pb.Asset{
|
return &pb.Asset{
|
||||||
AssetId: asset.ID,
|
AssetId: asset.ID,
|
||||||
OwnerUid: asset.OwnerUID,
|
OwnerUid: asset.OwnerUID,
|
||||||
@ -685,14 +697,14 @@ func ModelToProtoAssetDetail(asset *models.Asset, ownerNickname string, isLiked
|
|||||||
CreatedAt: asset.CreatedAt,
|
CreatedAt: asset.CreatedAt,
|
||||||
UpdatedAt: asset.UpdatedAt,
|
UpdatedAt: asset.UpdatedAt,
|
||||||
MintedAt: getInt64Value(asset.MintedAt),
|
MintedAt: getInt64Value(asset.MintedAt),
|
||||||
Owner: nil, // 保留用于兼容性
|
Owner: ownerInfo,
|
||||||
OwnerNickname: ownerNickname, // 持有者昵称
|
OwnerNickname: ownerNickname,
|
||||||
IsLiked: isLiked, // 当前用户是否已点赞
|
IsLiked: isLiked,
|
||||||
Info: asset.Info,
|
Info: asset.Info,
|
||||||
DisplayStatus: displayStatus, // 展示状态:0=待展示, 1=已展示
|
DisplayStatus: displayStatus,
|
||||||
Earnings: earnings, // 当前展出收益(实时计算)
|
Earnings: earnings,
|
||||||
HourlyEarnings: hourlyEarnings, // 每小时收益(实时计算)
|
HourlyEarnings: hourlyEarnings,
|
||||||
ExhibitionExpireAt: exhibitionExpireAt, // 展出过期时间
|
ExhibitionExpireAt: exhibitionExpireAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -453,8 +453,9 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st
|
|||||||
|
|
||||||
// 5. 无需异步 AI 处理,cover_url 已在步骤 3.2 中直接设置
|
// 5. 无需异步 AI 处理,cover_url 已在步骤 3.2 中直接设置
|
||||||
|
|
||||||
// 5. 获取所有者的昵称(创建时所有者就是当前用户)
|
// 5. 获取所有者的昵称和头像(创建时所有者就是当前用户)
|
||||||
var ownerNickname string
|
var ownerNickname string
|
||||||
|
var ownerAvatar string
|
||||||
profile, err := s.userClient.GetFanProfile(context.Background(), userID, starID)
|
profile, err := s.userClient.GetFanProfile(context.Background(), userID, starID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger.Warn("Failed to get owner fan profile, will return without nickname",
|
logger.Logger.Warn("Failed to get owner fan profile, will return without nickname",
|
||||||
@ -465,6 +466,7 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st
|
|||||||
ownerNickname = ""
|
ownerNickname = ""
|
||||||
} else {
|
} else {
|
||||||
ownerNickname = profile.Nickname
|
ownerNickname = profile.Nickname
|
||||||
|
ownerAvatar = profile.AvatarUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 构建响应
|
// 6. 构建响应
|
||||||
@ -476,7 +478,7 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st
|
|||||||
Timestamp: time.Now().UnixMilli(),
|
Timestamp: time.Now().UnixMilli(),
|
||||||
},
|
},
|
||||||
Order: ModelToProtoMintOrder(mintOrder),
|
Order: ModelToProtoMintOrder(mintOrder),
|
||||||
Asset: ModelToProtoAssetDetail(asset, ownerNickname, false, 0, 0, 0, 0, getInt32Value(asset.Grade)), // 新创建的资产,is_liked 为 false,display_status 默认为 0,earnings、hourlyEarnings 和 exhibitionExpireAt 为 0,grade 从 asset.Grade 获取
|
Asset: ModelToProtoAssetDetail(asset, ownerNickname, ownerAvatar, false, 0, 0, 0, 0, getInt32Value(asset.Grade)), // 新创建的资产,is_liked 为 false,display_status 默认为 0,earnings、hourlyEarnings 和 exhibitionExpireAt 为 0,grade 从 asset.Grade 获取
|
||||||
CostCrystal: capturedCostCrystal,
|
CostCrystal: capturedCostCrystal,
|
||||||
BalanceAfter: newBalance,
|
BalanceAfter: newBalance,
|
||||||
}
|
}
|
||||||
@ -570,18 +572,20 @@ func (s *mintService) GetMintOrder(orderID string, userID, starID int64) (*pb.Ge
|
|||||||
if order.AssetID != nil {
|
if order.AssetID != nil {
|
||||||
asset, err := s.assetRepo.GetByID(*order.AssetID)
|
asset, err := s.assetRepo.GetByID(*order.AssetID)
|
||||||
if err == nil && asset != nil {
|
if err == nil && asset != nil {
|
||||||
// 获取所有者昵称
|
// 获取所有者昵称和头像
|
||||||
var ownerNickname string
|
var ownerNickname string
|
||||||
|
var ownerAvatar string
|
||||||
profile, err := s.userClient.GetFanProfile(context.Background(), asset.OwnerUID, asset.StarID)
|
profile, err := s.userClient.GetFanProfile(context.Background(), asset.OwnerUID, asset.StarID)
|
||||||
if err == nil && profile != nil {
|
if err == nil && profile != nil {
|
||||||
ownerNickname = profile.Nickname
|
ownerNickname = profile.Nickname
|
||||||
|
ownerAvatar = profile.AvatarUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为 Proto(这里需要调用 ModelToProtoAssetDetail,但需要 is_liked 参数)
|
// 转换为 Proto(这里需要调用 ModelToProtoAssetDetail,但需要 is_liked 参数)
|
||||||
// 由于是查询自己的订单,is_liked 设为 false(简化处理)
|
// 由于是查询自己的订单,is_liked 设为 false(简化处理)
|
||||||
// 获取 display_status
|
// 获取 display_status
|
||||||
displayStatus, _ := s.assetRepo.GetDisplayStatusByAssetID(asset.ID)
|
displayStatus, _ := s.assetRepo.GetDisplayStatusByAssetID(asset.ID)
|
||||||
assetProto = ModelToProtoAssetDetail(asset, ownerNickname, false, displayStatus, 0, 0, 0, getInt32Value(asset.Grade)) // 新创建的资产,earnings、hourlyEarnings 和 exhibitionExpireAt 为 0
|
assetProto = ModelToProtoAssetDetail(asset, ownerNickname, ownerAvatar, false, displayStatus, 0, 0, 0, getInt32Value(asset.Grade)) // 新创建的资产,earnings、hourlyEarnings 和 exhibitionExpireAt 为 0
|
||||||
|
|
||||||
// 如果 cover_url 存在,生成预签名 URL
|
// 如果 cover_url 存在,生成预签名 URL
|
||||||
if assetProto.CoverUrl != "" {
|
if assetProto.CoverUrl != "" {
|
||||||
|
|||||||
@ -97,7 +97,7 @@
|
|||||||
<view class="info-item">
|
<view class="info-item">
|
||||||
<text class="info-label">创作者</text>
|
<text class="info-label">创作者</text>
|
||||||
<view class="info-nickname">
|
<view class="info-nickname">
|
||||||
<image v-if="userAvatarUrl" class="info-avatar" :src="userAvatarUrl" mode="aspectFill">
|
<image v-if="assetData.owner?.avatar" class="info-avatar" :src="assetData.owner.avatar" mode="aspectFill">
|
||||||
</image>
|
</image>
|
||||||
<text class="info-value">{{ assetData.owner_nickname || '未知' }}</text>
|
<text class="info-value">{{ assetData.owner_nickname || '未知' }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@ -56,7 +56,7 @@
|
|||||||
<image class="creation-image" :src="item.cover_image" mode="aspectFill"></image>
|
<image class="creation-image" :src="item.cover_image" mode="aspectFill"></image>
|
||||||
<view class="creation-info">
|
<view class="creation-info">
|
||||||
<view class="creation-id">
|
<view class="creation-id">
|
||||||
<text class="id-text">#{{ item.certificate_id }}</text>
|
<text class="id-text">链上编号: #{{ item.certificate_id }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="creation-meta">
|
<view class="creation-meta">
|
||||||
<view class="creator-info">
|
<view class="creator-info">
|
||||||
|
|||||||
@ -178,8 +178,8 @@ const newsItems = [
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: -40rpx;
|
top: -40rpx;
|
||||||
right: -24rpx;
|
right: -24rpx;
|
||||||
width: 29%;
|
width: 16%;
|
||||||
height: 29%;
|
height: 16%;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,19 +31,19 @@
|
|||||||
|
|
||||||
<!-- 子标签内容区 -->
|
<!-- 子标签内容区 -->
|
||||||
<view class="sub-content-area">
|
<view class="sub-content-area">
|
||||||
<!-- 周边 -->
|
|
||||||
<ZhoubianContent v-show="activeSubTab === 0" class="sub-content-fill" />
|
|
||||||
|
|
||||||
<!-- 同款 -->
|
<!-- 同款 -->
|
||||||
<TongkuanContent v-show="activeSubTab === 1" class="sub-content-fill" />
|
<TongkuanContent v-show="activeSubTab === 0" class="sub-content-fill" />
|
||||||
|
|
||||||
<!-- 见面 -->
|
<!-- 见面 -->
|
||||||
<JianmianContent v-show="activeSubTab === 2" class="sub-content-fill" />
|
<JianmianContent v-show="activeSubTab === 1" class="sub-content-fill" />
|
||||||
|
|
||||||
<!-- 装扮 -->
|
<!-- 装扮 -->
|
||||||
<view v-show="activeSubTab === 3" class="sub-content-fill dressup-wrap">
|
<view v-show="activeSubTab === 2" class="sub-content-fill dressup-wrap">
|
||||||
<DressupContent />
|
<DressupContent />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 周边 -->
|
||||||
|
<ZhoubianContent v-show="activeSubTab === 3" class="sub-content-fill" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@ -58,10 +58,10 @@ import DressupContent from './DressupContent.vue';
|
|||||||
const activeSubTab = ref(0);
|
const activeSubTab = ref(0);
|
||||||
|
|
||||||
const subTabs = [
|
const subTabs = [
|
||||||
{ name: '周边' },
|
|
||||||
{ name: '同款' },
|
{ name: '同款' },
|
||||||
{ name: '见面' },
|
{ name: '见面' },
|
||||||
{ name: '装扮' }
|
{ name: '道具' },
|
||||||
|
{ name: '福利' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const switchSubTab = (index) => {
|
const switchSubTab = (index) => {
|
||||||
|
|||||||
@ -56,6 +56,7 @@ import { ref } from 'vue';
|
|||||||
const drawerOpen = ref(false);
|
const drawerOpen = ref(false);
|
||||||
|
|
||||||
const toggleDrawer = () => {
|
const toggleDrawer = () => {
|
||||||
|
return;
|
||||||
drawerOpen.value = !drawerOpen.value;
|
drawerOpen.value = !drawerOpen.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,8 +160,8 @@ const drawerProducts = [
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10rpx;
|
top: -10rpx;
|
||||||
right: -10rpx;
|
right: -10rpx;
|
||||||
width: 29%;
|
width: 16%;
|
||||||
height: 29%;
|
height: 16%;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,24 +77,24 @@
|
|||||||
const bigCards = [
|
const bigCards = [
|
||||||
{
|
{
|
||||||
label: '限时上架',
|
label: '限时上架',
|
||||||
name: '肖战TOPFANS\n2026联名款',
|
name: 'TOPFANS\n2026联名款',
|
||||||
image: '/static/starcity/zhoubian/xiaoka-card.png',
|
image: '/static/starcity/zhoubian/xiaoka-card-2.jpg',
|
||||||
backImage: '/static/starcity/zhoubian/xiaoka-card-back2.png',
|
backImage: '/static/starcity/zhoubian/xiaoka-card-back2.png',
|
||||||
price: 288
|
price: 288
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '限时上架',
|
label: '限时上架',
|
||||||
name: '肖战2025巡回\n演唱会现场版',
|
name: '2025巡回\n演唱会现场版',
|
||||||
image: '/static/starcity/zhoubian/xiaoka-card2.png',
|
image: '/static/starcity/zhoubian/xiaoka-card-1.jpg',
|
||||||
backImage: '/static/starcity/zhoubian/xiaoka-card-back.png',
|
backImage: '/static/starcity/zhoubian/xiaoka-card-back.png',
|
||||||
price: 188
|
price: 188
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const smallCards = [
|
const smallCards = [
|
||||||
{ name: '肖战jun', image: '/static/starcity/zhoubian/merchandise1.png', price: 299, isNew: true },
|
{ name: '卡通手办', image: '/static/starcity/zhoubian/merchandise1.png', price: 299, isNew: true },
|
||||||
{ name: '签名项链', image: '/static/starcity/zhoubian/merchandise2.png', price: 179, isNew: false },
|
{ name: '签名项链', image: '/static/starcity/zhoubian/merchandise2.png', price: 179, isNew: false },
|
||||||
{ name: '肖战漂流2022黑胶', image: '/static/starcity/zhoubian/merchandise3.png', price: 399, isNew: false },
|
{ name: '漂流2022黑胶', image: '/static/starcity/zhoubian/merchandise3.png', price: 399, isNew: false },
|
||||||
{ name: '手串', image: '/static/starcity/zhoubian/merchandise4.png', price: 259, isNew: false }
|
{ name: '手串', image: '/static/starcity/zhoubian/merchandise4.png', price: 259, isNew: false }
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
@ -144,8 +144,8 @@ const smallCards = [
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: -40rpx;
|
top: -40rpx;
|
||||||
right: -24rpx;
|
right: -24rpx;
|
||||||
width: 29%;
|
width: 16%;
|
||||||
height: 29%;
|
height: 16%;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<view class="wf-like-wave wf-like-wave-inner" :class="{ 'wf-like-wave-active': likingMap[item.id] }" />
|
<view class="wf-like-wave wf-like-wave-inner" :class="{ 'wf-like-wave-active': likingMap[item.id] }" />
|
||||||
<view class="creation-info">
|
<view class="creation-info">
|
||||||
<view class="creation-id">
|
<view class="creation-id">
|
||||||
<text class="id-text">#{{ item.certificate_id }}</text>
|
<text class="id-text">链上编号: #{{ item.certificate_id }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="creation-meta">
|
<view class="creation-meta">
|
||||||
<view class="creator-info">
|
<view class="creator-info">
|
||||||
|
|||||||
BIN
frontend/static/starcity/zhoubian/xiaoka-card-1.jpg
Normal file
BIN
frontend/static/starcity/zhoubian/xiaoka-card-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
BIN
frontend/static/starcity/zhoubian/xiaoka-card-2.jpg
Normal file
BIN
frontend/static/starcity/zhoubian/xiaoka-card-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 251 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 501 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 481 KiB |
Loading…
Reference in New Issue
Block a user