313 lines
7.0 KiB
Vue
313 lines
7.0 KiB
Vue
|
|
<script setup lang="ts">
|
|||
|
|
import { computed } from 'vue'
|
|||
|
|
import { Edit, Delete } from '@element-plus/icons-vue'
|
|||
|
|
import type { Ledger } from '@/types'
|
|||
|
|
import { LedgerTypeMap, LedgerCategoryMap, displayName } from '@/types'
|
|||
|
|
import { pb } from '@/api/pocketbase'
|
|||
|
|
import { useGroupStore } from '@/stores/group'
|
|||
|
|
|
|||
|
|
const props = defineProps<{
|
|||
|
|
ledger: Ledger
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
edit: [ledgerId: string]
|
|||
|
|
delete: [ledgerId: string]
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const groupStore = useGroupStore()
|
|||
|
|
|
|||
|
|
const isCreator = computed(() => {
|
|||
|
|
return props.ledger.creator === pb.authStore.model?.id
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const isGroupOwner = computed(() => {
|
|||
|
|
return pb.authStore.model?.id === groupStore.currentGroup?.owner
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const typeLabel = computed(() => LedgerTypeMap[props.ledger.type])
|
|||
|
|
const categoryLabel = computed(() => LedgerCategoryMap[props.ledger.category])
|
|||
|
|
const isIncome = computed(() => props.ledger.type === 'income')
|
|||
|
|
|
|||
|
|
const formattedAmount = computed(() => {
|
|||
|
|
const prefix = isIncome.value ? '+' : '-'
|
|||
|
|
return `${prefix}¥${props.ledger.amount.toFixed(2)}`
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const formattedDate = computed(() => {
|
|||
|
|
if (!props.ledger.occurredAt) return ''
|
|||
|
|
return props.ledger.occurredAt.slice(0, 10)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const creatorName = computed(() => {
|
|||
|
|
return displayName(props.ledger.expand?.creator)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const relatedMemberNames = computed(() => {
|
|||
|
|
const members = props.ledger.expand?.relatedMembers
|
|||
|
|
if (!members || members.length === 0) return []
|
|||
|
|
return members.map((m) => displayName(m))
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<div class="ledger-card">
|
|||
|
|
<div class="ledger-card__main">
|
|||
|
|
<!-- 左侧:类型标签 + 金额 -->
|
|||
|
|
<div class="ledger-card__left">
|
|||
|
|
<span
|
|||
|
|
class="ledger-card__type-tag"
|
|||
|
|
:class="isIncome ? 'ledger-card__type-tag--income' : 'ledger-card__type-tag--expense'"
|
|||
|
|
>
|
|||
|
|
{{ typeLabel }}
|
|||
|
|
</span>
|
|||
|
|
<span
|
|||
|
|
class="ledger-card__amount"
|
|||
|
|
:class="isIncome ? 'ledger-card__amount--income' : 'ledger-card__amount--expense'"
|
|||
|
|
>
|
|||
|
|
{{ formattedAmount }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 中间:描述、分类、日期 -->
|
|||
|
|
<div class="ledger-card__center">
|
|||
|
|
<div class="ledger-card__desc">{{ ledger.description || '无备注' }}</div>
|
|||
|
|
<div class="ledger-card__meta">
|
|||
|
|
<span class="ledger-card__category-tag">{{ categoryLabel }}</span>
|
|||
|
|
<span class="ledger-card__date">{{ formattedDate }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 右侧:关联成员 + 创建者 -->
|
|||
|
|
<div class="ledger-card__right">
|
|||
|
|
<div v-if="relatedMemberNames.length > 0" class="ledger-card__members">
|
|||
|
|
<span
|
|||
|
|
v-for="(name, index) in relatedMemberNames.slice(0, 3)"
|
|||
|
|
:key="index"
|
|||
|
|
class="ledger-card__member"
|
|||
|
|
>
|
|||
|
|
{{ name }}
|
|||
|
|
</span>
|
|||
|
|
<span v-if="relatedMemberNames.length > 3" class="ledger-card__member ledger-card__member--more">
|
|||
|
|
+{{ relatedMemberNames.length - 3 }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="ledger-card__creator">
|
|||
|
|
{{ creatorName }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 操作按钮 -->
|
|||
|
|
<div v-if="isCreator || isGroupOwner" class="ledger-card__actions">
|
|||
|
|
<button v-if="isCreator" class="ledger-card__action-btn" @click.stop="emit('edit', ledger.id)">
|
|||
|
|
<el-icon><Edit /></el-icon>
|
|||
|
|
编辑
|
|||
|
|
</button>
|
|||
|
|
<button class="ledger-card__action-btn ledger-card__action-btn--danger" @click.stop="emit('delete', ledger.id)">
|
|||
|
|
<el-icon><Delete /></el-icon>
|
|||
|
|
删除
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.ledger-card {
|
|||
|
|
background: var(--gg-bg-card);
|
|||
|
|
border: 1px solid var(--gg-border);
|
|||
|
|
border-radius: var(--gg-radius-md, 8px);
|
|||
|
|
padding: 14px 18px;
|
|||
|
|
transition: border-color 0.2s, box-shadow 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card:hover {
|
|||
|
|
border-color: var(--gg-primary-light);
|
|||
|
|
box-shadow: 0 0 16px rgba(5, 150, 105, 0.08);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__main {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
gap: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 左侧 */
|
|||
|
|
.ledger-card__left {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
min-width: 80px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__type-tag {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 2px 10px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
line-height: 18px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__type-tag--income {
|
|||
|
|
background: rgba(16, 185, 129, 0.1);
|
|||
|
|
color: var(--gg-success);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__type-tag--expense {
|
|||
|
|
background: rgba(239, 68, 68, 0.1);
|
|||
|
|
color: var(--gg-danger);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__amount {
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__amount--income {
|
|||
|
|
color: var(--gg-success);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__amount--expense {
|
|||
|
|
color: var(--gg-danger);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 中间 */
|
|||
|
|
.ledger-card__center {
|
|||
|
|
flex: 1;
|
|||
|
|
min-width: 0;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__desc {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: var(--gg-text);
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__meta {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__category-tag {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 1px 8px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
line-height: 18px;
|
|||
|
|
background: rgba(5, 150, 105, 0.08);
|
|||
|
|
color: var(--gg-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__date {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--gg-text-muted);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 右侧 */
|
|||
|
|
.ledger-card__right {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: flex-end;
|
|||
|
|
gap: 6px;
|
|||
|
|
min-width: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__members {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 4px;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__member {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 1px 8px;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
line-height: 18px;
|
|||
|
|
background: var(--gg-bg-elevated);
|
|||
|
|
color: var(--gg-text-secondary);
|
|||
|
|
max-width: 60px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__member--more {
|
|||
|
|
background: var(--gg-bg-elevated);
|
|||
|
|
color: var(--gg-text-muted);
|
|||
|
|
max-width: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__creator {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--gg-text-muted);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 操作按钮 */
|
|||
|
|
.ledger-card__actions {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
margin-top: 10px;
|
|||
|
|
padding-top: 10px;
|
|||
|
|
border-top: 1px solid var(--gg-border);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__action-btn {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 4px;
|
|||
|
|
padding: 4px 10px;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
background: transparent;
|
|||
|
|
color: var(--gg-text-secondary);
|
|||
|
|
font-size: 12px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: color 0.2s, background 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__action-btn:hover {
|
|||
|
|
color: var(--gg-primary);
|
|||
|
|
background: rgba(5, 150, 105, 0.08);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__action-btn--danger:hover {
|
|||
|
|
color: var(--gg-danger);
|
|||
|
|
background: rgba(239, 68, 68, 0.08);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 640px) {
|
|||
|
|
.ledger-card__main {
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__left {
|
|||
|
|
flex-direction: row;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
min-width: auto;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ledger-card__right {
|
|||
|
|
align-items: flex-start;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|