第一次上传
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
<template>
|
||||
<view v-if="visible" class="popup-shell" @touchmove.stop.prevent="">
|
||||
<view class="popup-mask" @click="emit('close')"></view>
|
||||
<view class="surface-card popup-panel">
|
||||
<view class="popup-head">
|
||||
<view>
|
||||
<text class="section-title">{{ form.id ? '编辑账单' : '新增账单' }}</text>
|
||||
<text class="section-subtitle">3 步完成记录,所有数据仅保存在本机</text>
|
||||
</view>
|
||||
<text class="close-text" @click="emit('close')">关闭</text>
|
||||
</view>
|
||||
<scroll-view scroll-y class="popup-body">
|
||||
<view class="field-block">
|
||||
<text class="field-label">收支类型</text>
|
||||
<view class="pill-row">
|
||||
<view
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
class="pill-button"
|
||||
:class="{ active: form.type === item.value }"
|
||||
@click="form.type = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field-block">
|
||||
<text class="field-label">金额</text>
|
||||
<view class="input-shell amount-shell">
|
||||
<text class="prefix-text">¥</text>
|
||||
<input v-model="form.amount" type="digit" placeholder="输入金额" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field-block">
|
||||
<text class="field-label">分类</text>
|
||||
<view class="chip-grid">
|
||||
<view
|
||||
v-for="item in currentCategories"
|
||||
:key="item.id"
|
||||
class="chip-item"
|
||||
:class="{ active: form.categoryId === item.id }"
|
||||
@click="form.categoryId = item.id"
|
||||
>
|
||||
<view class="chip-dot" :style="{ background: item.color }"></view>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field-block">
|
||||
<text class="field-label">账户</text>
|
||||
<view class="chip-grid">
|
||||
<view
|
||||
v-for="item in accounts"
|
||||
:key="item.id"
|
||||
class="chip-item"
|
||||
:class="{ active: form.accountId === item.id }"
|
||||
@click="form.accountId = item.id"
|
||||
>
|
||||
<view class="chip-dot" :style="{ background: item.color }"></view>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field-block">
|
||||
<text class="field-label">日期</text>
|
||||
<picker mode="date" :value="form.date" @change="onDateChange">
|
||||
<view class="input-shell picker-shell">
|
||||
<text>{{ form.date }}</text>
|
||||
<text class="tiny-text">选择</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="field-block">
|
||||
<text class="field-label">备注</text>
|
||||
<view class="input-shell textarea-shell">
|
||||
<textarea
|
||||
v-model="form.note"
|
||||
maxlength="40"
|
||||
placeholder="补充说明,便于后续搜索"
|
||||
></textarea>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="popup-foot">
|
||||
<view class="ghost-button" @click="emit('close')">取消</view>
|
||||
<view class="primary-button save-button" @click="handleSave">保存账单</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, watch } from 'vue'
|
||||
import { toDateKey } from '../utils/date'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
entry: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
categories: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
accounts: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
defaultType: {
|
||||
type: String,
|
||||
default: 'expense'
|
||||
},
|
||||
initialCategoryId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const typeOptions = [
|
||||
{ label: '支出', value: 'expense' },
|
||||
{ label: '收入', value: 'income' }
|
||||
]
|
||||
|
||||
const form = reactive({
|
||||
id: '',
|
||||
type: 'expense',
|
||||
amount: '',
|
||||
categoryId: '',
|
||||
accountId: '',
|
||||
date: toDateKey(),
|
||||
note: '',
|
||||
createdAt: 0
|
||||
})
|
||||
|
||||
const currentCategories = computed(() => props.categories[form.type] || [])
|
||||
|
||||
function hydrateForm() {
|
||||
const source = props.entry || {}
|
||||
const nextType = source.type || props.defaultType || 'expense'
|
||||
const defaultCategory = props.initialCategoryId && (props.categories[nextType] || []).some((item) => item.id === props.initialCategoryId)
|
||||
? props.initialCategoryId
|
||||
: (props.categories[nextType]?.[0]?.id || '')
|
||||
|
||||
form.id = source.id || ''
|
||||
form.type = nextType
|
||||
form.amount = source.amount ? String(source.amount) : ''
|
||||
form.categoryId = source.categoryId || defaultCategory
|
||||
form.accountId = source.accountId || props.accounts[0]?.id || ''
|
||||
form.date = source.date || toDateKey()
|
||||
form.note = source.note || ''
|
||||
form.createdAt = source.createdAt || 0
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
hydrateForm()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => form.type,
|
||||
(nextType) => {
|
||||
const availableIds = (props.categories[nextType] || []).map((item) => item.id)
|
||||
if (!availableIds.includes(form.categoryId)) {
|
||||
form.categoryId = availableIds[0] || ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function onDateChange(event) {
|
||||
form.date = event.detail.value
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
if (!Number(form.amount)) {
|
||||
uni.showToast({
|
||||
title: '请输入有效金额',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!form.categoryId || !form.accountId) {
|
||||
uni.showToast({
|
||||
title: '请选择分类和账户',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
emit('save', {
|
||||
id: form.id,
|
||||
type: form.type,
|
||||
amount: Number(form.amount),
|
||||
categoryId: form.categoryId,
|
||||
accountId: form.accountId,
|
||||
date: form.date,
|
||||
note: form.note.trim(),
|
||||
createdAt: form.createdAt
|
||||
})
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-shell {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.popup-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(3, 12, 21, 0.42);
|
||||
}
|
||||
|
||||
.popup-panel {
|
||||
position: absolute;
|
||||
left: 16rpx;
|
||||
right: 16rpx;
|
||||
bottom: 16rpx;
|
||||
max-height: 84vh;
|
||||
padding: 28rpx 28rpx 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.popup-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.close-text {
|
||||
padding: 10rpx 0;
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
max-height: 58vh;
|
||||
}
|
||||
|
||||
.field-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.pill-row,
|
||||
.chip-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.chip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 22rpx;
|
||||
background: var(--surface-muted);
|
||||
color: var(--text-secondary);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.chip-item.active {
|
||||
background: var(--brand-soft);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.chip-dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.amount-shell {
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.prefix-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.picker-shell {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.textarea-shell {
|
||||
padding: 20rpx 24rpx;
|
||||
min-height: 160rpx;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.textarea-shell textarea {
|
||||
min-height: 120rpx;
|
||||
}
|
||||
|
||||
.popup-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.popup-foot .ghost-button {
|
||||
flex: 0 0 180rpx;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user