9.20/1
This commit is contained in:
74
frontend/pages/account/form.vue
Normal file
74
frontend/pages/account/form.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="form">
|
||||
<view class="field"><text class="label">账户名称</text><input class="input" v-model="form.name" placeholder="必填"/></view>
|
||||
<view class="field" @click="showType=true">
|
||||
<text class="label">账户类型</text>
|
||||
<text class="value">{{ typeLabel(form.type) }}</text>
|
||||
</view>
|
||||
<view v-if="form.type==='bank'" class="field"><text class="label">银行名称</text><input class="input" v-model="form.bankName" placeholder="选填"/></view>
|
||||
<view v-if="form.type==='bank'" class="field"><text class="label">银行账号</text><input class="input" v-model="form.bankAccount" placeholder="选填"/></view>
|
||||
<view class="field"><text class="label">当前余额</text><input class="input" type="number" v-model="form.openingBalance" placeholder="0.00"/></view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="primary" @click="save">保存</button>
|
||||
</view>
|
||||
<uni-popup ref="popup" type="bottom" v-model="showType">
|
||||
<view class="sheet">
|
||||
<view class="sheet-item" v-for="t in types" :key="t.key" @click="form.type=t.key;showType=false">{{ t.name }}</view>
|
||||
<view class="sheet-cancel" @click="showType=false">取消</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { post, put, get } from '../../common/http.js'
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
id: null,
|
||||
form: { name: '', type: 'cash', bankName: '', bankAccount: '', openingBalance: '' },
|
||||
showType: false,
|
||||
types: [
|
||||
{ key: 'cash', name: '现金' },
|
||||
{ key: 'bank', name: '银行存款' },
|
||||
{ key: 'wechat', name: '微信' },
|
||||
{ key: 'alipay', name: '支付宝' },
|
||||
{ key: 'other', name: '其他' }
|
||||
]
|
||||
}
|
||||
},
|
||||
onLoad(q){ this.id = q && q.id ? Number(q.id) : null; if (this.id) this.load(); },
|
||||
methods: {
|
||||
typeLabel(t){ const m = {cash:'现金', bank:'银行存款', wechat:'微信', alipay:'支付宝', other:'其他'}; return m[t]||t },
|
||||
async load(){ try { const list = await get('/api/accounts'); const a = (Array.isArray(list)?list:(list?.list||[])).find(x=>x.id==this.id); if (a) { this.form={ name:a.name, type:a.type, bankName:a.bank_name||a.bankName||'', bankAccount:a.bank_account||a.bankAccount||'', openingBalance:'' } } } catch(e){} },
|
||||
async save(){
|
||||
if (!this.form.name) { uni.showToast({ title: '请输入名称', icon: 'none' }); return }
|
||||
try {
|
||||
const body = { ...this.form, openingBalance: Number(this.form.openingBalance||0) }
|
||||
if (this.id) await put(`/api/accounts/${this.id}`, body)
|
||||
else await post('/api/accounts', { ...body, status: 1 })
|
||||
uni.showToast({ title: '已保存', icon: 'success' })
|
||||
setTimeout(()=>uni.navigateBack(), 300)
|
||||
} catch(e) { uni.showToast({ title: '保存失败', icon: 'none' }) }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page { display:flex; flex-direction: column; height: 100vh; }
|
||||
.form { background:#fff; }
|
||||
.field { display:flex; align-items:center; justify-content: space-between; padding: 18rpx 20rpx; border-bottom:1rpx solid #f3f3f3; }
|
||||
.label { color:#666; }
|
||||
.input { flex:1; text-align: right; color:#333; }
|
||||
.value { color:#333; }
|
||||
.actions { margin-top: 20rpx; padding: 0 20rpx; }
|
||||
.primary { width: 100%; background: #3c9cff; color:#fff; border-radius: 8rpx; padding: 22rpx 0; }
|
||||
.sheet { background:#fff; }
|
||||
.sheet-item { padding: 26rpx; text-align:center; border-bottom:1rpx solid #f2f2f2; }
|
||||
.sheet-cancel { padding: 26rpx; text-align:center; color:#666; }
|
||||
</style>
|
||||
|
||||
|
||||
87
frontend/pages/account/ledger.vue
Normal file
87
frontend/pages/account/ledger.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="filters">
|
||||
<picker mode="date" :value="startDate" @change="e=>{startDate=e.detail.value;load()}">
|
||||
<view class="field"><text class="label">开始</text><text class="value">{{ startDate || '—' }}</text></view>
|
||||
</picker>
|
||||
<picker mode="date" :value="endDate" @change="e=>{endDate=e.detail.value;load()}">
|
||||
<view class="field"><text class="label">结束</text><text class="value">{{ endDate || '—' }}</text></view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="summary">
|
||||
<view class="sum-item"><text class="k">收入</text><text class="v">{{ fmt(income) }}</text></view>
|
||||
<view class="sum-item"><text class="k">支出</text><text class="v">{{ fmt(expense) }}</text></view>
|
||||
<view class="sum-item"><text class="k">期初</text><text class="v">{{ fmt(opening) }}</text></view>
|
||||
<view class="sum-item"><text class="k">期末</text><text class="v">{{ fmt(ending) }}</text></view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="list">
|
||||
<view class="item" v-for="it in list" :key="it.id">
|
||||
<view class="row">
|
||||
<text class="title">{{ it.src==='other' ? (it.category || '其他') : (it.remark || '收付款') }}</text>
|
||||
<text class="amount" :class="{ in: it.direction==='in', out: it.direction==='out' }">{{ it.direction==='in' ? '+' : '-' }}{{ fmt(it.amount) }}</text>
|
||||
</view>
|
||||
<view class="meta">{{ formatDate(it.tx_time || it.txTime) }} · {{ it.remark || '-' }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from '../../common/http.js'
|
||||
export default {
|
||||
data() {
|
||||
return { accountId: null, startDate: '', endDate: '', list: [], opening: 0, income: 0, expense: 0, ending: 0 }
|
||||
},
|
||||
onLoad(query) {
|
||||
this.accountId = Number(query && query.id)
|
||||
this.quickInit()
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
quickInit() {
|
||||
// 默认本月
|
||||
const now = new Date()
|
||||
const y = now.getFullYear(), m = now.getMonth()+1
|
||||
this.startDate = `${y}-${String(m).padStart(2,'0')}-01`
|
||||
const lastDay = new Date(y, m, 0).getDate()
|
||||
this.endDate = `${y}-${String(m).padStart(2,'0')}-${String(lastDay).padStart(2,'0')}`
|
||||
},
|
||||
async load(page=1, size=50) {
|
||||
try {
|
||||
const res = await get(`/api/accounts/${this.accountId}/ledger`, { startDate: this.startDate, endDate: this.endDate, page, size })
|
||||
this.list = (res && res.list) || []
|
||||
this.opening = Number(res && res.opening || 0)
|
||||
this.income = Number(res && res.income || 0)
|
||||
this.expense = Number(res && res.expense || 0)
|
||||
this.ending = Number(res && res.ending || 0)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fmt(v) { return (typeof v === 'number' ? v : Number(v||0)).toFixed(2) },
|
||||
formatDate(s) { if (!s) return '-'; try { const d=new Date(s); const pad=n=>String(n).padStart(2,'0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` } catch(e){ return s } }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page { display:flex; flex-direction: column; height: 100vh; }
|
||||
.filters { display:flex; gap: 16rpx; padding: 16rpx; background:#fff; }
|
||||
.field { display:flex; justify-content: space-between; align-items:center; padding: 16rpx; border:1rpx solid #eee; border-radius: 12rpx; min-width: 300rpx; }
|
||||
.label { color:#666; }
|
||||
.value { color:#333; }
|
||||
.summary { display:grid; grid-template-columns: repeat(4,1fr); gap: 12rpx; padding: 12rpx 16rpx; background:#fff; border-top:1rpx solid #f1f1f1; border-bottom:1rpx solid #f1f1f1; }
|
||||
.sum-item { padding: 12rpx; text-align:center; }
|
||||
.k { display:block; color:#888; font-size: 24rpx; }
|
||||
.v { display:block; margin-top:6rpx; font-weight:700; color:#333; }
|
||||
.list { flex:1; }
|
||||
.item { padding: 18rpx 16rpx; border-bottom:1rpx solid #f4f4f4; background:#fff; }
|
||||
.row { display:flex; align-items:center; justify-content: space-between; margin-bottom: 6rpx; }
|
||||
.title { color:#333; }
|
||||
.amount { font-weight:700; }
|
||||
.amount.in { color:#2a9d8f; }
|
||||
.amount.out { color:#d35b5b; }
|
||||
.meta { color:#999; font-size: 24rpx; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<view class="meta">{{ typeLabel(a.type) }} · 余额:{{ a.balance?.toFixed ? a.balance.toFixed(2) : a.balance }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="fab" @click="create">+</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -13,8 +14,9 @@
|
||||
import { get } from '../../common/http.js'
|
||||
const TYPE_MAP = { cash: '现金', bank: '银行', alipay: '支付宝', wechat: '微信', other: '其他' }
|
||||
export default {
|
||||
data() { return { accounts: [] } },
|
||||
async onLoad() {
|
||||
data() { return { accounts: [], mode: 'view' } },
|
||||
async onLoad(q) {
|
||||
this.mode = (q && q.mode) || 'view'
|
||||
try {
|
||||
const res = await get('/api/accounts')
|
||||
this.accounts = Array.isArray(res) ? res : (res?.list || [])
|
||||
@@ -22,13 +24,18 @@
|
||||
},
|
||||
methods: {
|
||||
select(a) {
|
||||
const opener = getCurrentPages()[getCurrentPages().length-2]
|
||||
if (opener && opener.$vm) {
|
||||
opener.$vm.selectedAccountId = a.id
|
||||
opener.$vm.selectedAccountName = a.name
|
||||
if (this.mode === 'pick') {
|
||||
const opener = getCurrentPages()[getCurrentPages().length-2]
|
||||
if (opener && opener.$vm) {
|
||||
opener.$vm.selectedAccountId = a.id
|
||||
opener.$vm.selectedAccountName = a.name
|
||||
}
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.navigateTo({ url: `/pages/account/ledger?id=${a.id}` })
|
||||
}
|
||||
uni.navigateBack()
|
||||
},
|
||||
create() { uni.navigateTo({ url: '/pages/account/form' }) },
|
||||
typeLabel(t) { return TYPE_MAP[t] || t }
|
||||
}
|
||||
}
|
||||
@@ -40,6 +47,7 @@
|
||||
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
|
||||
.name { color:#333; margin-bottom: 6rpx; }
|
||||
.meta { color:#888; font-size: 24rpx; }
|
||||
.fab { position: fixed; right: 32rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; border-radius: 50%; background:#3c9cff; color:#fff; display:flex; align-items:center; justify-content:center; font-size: 52rpx; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.18); }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user