v68:财务补齐(改一笔/删一笔/账本改名/共享给谁)
用户 2026-10-20:「还有这个财务的显示也有问题」——v65 修的是**显示口径**
(端上按正负号重加 vs 服务端负债取绝对值);这一批补的是**动作**:
设置里「共享给谁」「账本名字」一直是灰条,账目也改不了删不掉。
## 根因:不是端上没写,是服务端没有路由
`Ledger::rename / setShare / unshare / shares / updateEntry / removeEntry`
**一直都有**,`ApiHandler::ledger()` 却只开了「建账本 / 记一笔 / 删账本」
⇒ 端上只能把这两条画成"暂未开放"。
## 端上做了什么
- 点一行账目 → 同一个浮层进**编辑态**(类别 / 金额 / 名称 / 备注 +
删除二次确认)。`aboutToAppear` 里把 @Param 拷进 @Local,
点第二行才不会还是上一笔的内容。
- 设置「账本名字」→ 改名浮层;「共享给谁」→ 共享浮层(列表 + 选人 +
逐个人取消)。列表解析走 `{items:[…]}`(`FbApiResult.data` 是原始 JSON)。
- 三个浮层共用 `recountSheets()` 让悬浮栏让位(原来只有"记一笔"有)。
## 顺手修的两处「对着服务端抄错」
1. `FbLedgerBook.myPerm` 的注释写 `owner / read / write`,
而服务端**从来不给 `owner`**(主人也是 `write`)⇒ 端上"是不是我建的"永远 false。
现在用服务端新补的 `isOwner` 布尔。
2. `FbLedgerEntry` 没有 `note` 字段 ⇒ 编辑时备注框永远是空的。补上类型与解析。
## 顺带
- 撤掉名册家谱那颗「补关系」按钮:从来没有 `onTap`,服务端 `relations`
只有读接口、网页版也没这个功能 ⇒ 留个永远点不动的按钮只会被当成"坏了"。
## 验证
- `hvigorw assembleHap` → BUILD SUCCESSFUL
- `_fb_arkts_guard.js` → 827 / 0(新增 groupV68 29 条)
- `_fb_v59d_falsify.py` → 139 / 0(v68 新增 9 条注入全部变红)
- ⚠️ 设备仍断连(`hdc list targets` → `[Empty]`),真机未验收
This commit is contained in:
@@ -17,7 +17,7 @@ import { FbColors, FbMetric, FB_MASK, fbAlpha } from '../common/Theme';
|
|||||||
import { FbSheetClose } from './FbSheetClose';
|
import { FbSheetClose } from './FbSheetClose';
|
||||||
import { FbSheetRing } from './FbSheetRing';
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||||
import { addLedgerEntry } from '../data/FbLedgerRepo';
|
import { addLedgerEntry, updateLedgerEntry, deleteLedgerEntry } from '../data/FbLedgerRepo';
|
||||||
import { FbApiResult } from '../data/FbHttp';
|
import { FbApiResult } from '../data/FbHttp';
|
||||||
|
|
||||||
/** 类别键(顺序与 `Ledger::KINDS` 一致) */
|
/** 类别键(顺序与 `Ledger::KINDS` 一致) */
|
||||||
@@ -35,6 +35,19 @@ export struct FbLedgerEntrySheet {
|
|||||||
/** 记到哪本账 */
|
/** 记到哪本账 */
|
||||||
@Param ledgerId: number = 0;
|
@Param ledgerId: number = 0;
|
||||||
@Param ledgerName: string = '';
|
@Param ledgerName: string = '';
|
||||||
|
/**
|
||||||
|
* 编辑模式:> 0 = 改这一条(`0` = 新记一笔)。
|
||||||
|
*
|
||||||
|
* 🔴 v68(用户 2026-10-20「财务的显示也有问题」的后续):
|
||||||
|
* 原来这里只有「记一笔」,底部还写着"改/删去网页端"——因为服务端没有路由。
|
||||||
|
* v68 服务端补了 `PATCH/DELETE /ledger/<id>/entries`(id 走 body),
|
||||||
|
* 端上就地把**改一笔 / 删一笔**做完。
|
||||||
|
*/
|
||||||
|
@Param editId: number = 0;
|
||||||
|
@Param editKind: string = 'deposit';
|
||||||
|
@Param editAmount: string = '';
|
||||||
|
@Param editTitle: string = '';
|
||||||
|
@Param editNote: string = '';
|
||||||
|
|
||||||
@Event onClose: () => void = (): void => {};
|
@Event onClose: () => void = (): void => {};
|
||||||
@Event onSaved: () => void = (): void => {};
|
@Event onSaved: () => void = (): void => {};
|
||||||
@@ -45,6 +58,28 @@ export struct FbLedgerEntrySheet {
|
|||||||
@Local note: string = '';
|
@Local note: string = '';
|
||||||
@Local saving: boolean = false;
|
@Local saving: boolean = false;
|
||||||
@Local err: string = '';
|
@Local err: string = '';
|
||||||
|
/** 删除的二次确认(删账目不可逆,必须先问一句) */
|
||||||
|
@Local confirmingDelete: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑态要**把 @Param 拷进 @Local**。
|
||||||
|
*
|
||||||
|
* ⚠️ 不能在字段初始化里写 `@Local amount: string = this.editAmount`:
|
||||||
|
* V2 的成员初始化顺序不保证,`@Param` 那时可能还没赋值(拿到空串)。
|
||||||
|
* 浮层每次打开都是新实例 ⇒ 在 `aboutToAppear` 拷一次就够。
|
||||||
|
*/
|
||||||
|
aboutToAppear(): void {
|
||||||
|
if (this.editId > 0) {
|
||||||
|
if (this.editKind !== '') { this.kind = this.editKind; }
|
||||||
|
this.amount = this.editAmount;
|
||||||
|
this.title = this.editTitle;
|
||||||
|
this.note = this.editNote;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isEdit(): boolean {
|
||||||
|
return this.editId > 0;
|
||||||
|
}
|
||||||
|
|
||||||
private kindNameOf(k: string): string {
|
private kindNameOf(k: string): string {
|
||||||
const i: number = KIND_KEYS.indexOf(k);
|
const i: number = KIND_KEYS.indexOf(k);
|
||||||
@@ -77,7 +112,7 @@ export struct FbLedgerEntrySheet {
|
|||||||
Column({ space: 12 }) {
|
Column({ space: 12 }) {
|
||||||
Row({ space: 8 }) {
|
Row({ space: 8 }) {
|
||||||
Column({ space: 2 }) {
|
Column({ space: 2 }) {
|
||||||
Text('记一笔')
|
Text(this.isEdit() ? '改这一笔' : '记一笔')
|
||||||
.fontSize(FbMetric.fontTitle)
|
.fontSize(FbMetric.fontTitle)
|
||||||
.fontWeight(FontWeight.Medium)
|
.fontWeight(FontWeight.Medium)
|
||||||
.fontColor(FbColors.ink)
|
.fontColor(FbColors.ink)
|
||||||
@@ -213,11 +248,30 @@ export struct FbLedgerEntrySheet {
|
|||||||
.width('100%')
|
.width('100%')
|
||||||
.alignItems(VerticalAlign.Top)
|
.alignItems(VerticalAlign.Top)
|
||||||
|
|
||||||
Text('已有账目的金额修改 / 删除还在网页端 —— 接口只开放了"记一笔"。')
|
if (this.isEdit()) {
|
||||||
.fontSize(FbMetric.fontTiny)
|
if (!this.confirmingDelete) {
|
||||||
.fontColor(FbColors.faint)
|
Text('删掉这一笔')
|
||||||
.width('100%')
|
.fontSize(FbMetric.fontTiny)
|
||||||
.maxLines(3)
|
.fontColor(FbColors.red)
|
||||||
|
.width('100%')
|
||||||
|
.textAlign(TextAlign.Center)
|
||||||
|
.onClick((): void => {
|
||||||
|
this.confirmingDelete = true;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Text('再点一次就删掉了(不可恢复)')
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.textAlign(TextAlign.Center)
|
||||||
|
.width('100%')
|
||||||
|
.height(38)
|
||||||
|
.borderRadius(FbMetric.radiusS)
|
||||||
|
.backgroundColor('#C0533F')
|
||||||
|
.onClick((): void => {
|
||||||
|
this.remove();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
@@ -235,7 +289,8 @@ export struct FbLedgerEntrySheet {
|
|||||||
.maxLines(3)
|
.maxLines(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(this.saving ? '记入中…' : '记下来')
|
Text(this.saving ? (this.isEdit() ? '保存中…' : '记入中…')
|
||||||
|
: (this.isEdit() ? '保存改动' : '记下来'))
|
||||||
.fontSize(FbMetric.fontBody)
|
.fontSize(FbMetric.fontBody)
|
||||||
.fontWeight(FontWeight.Medium)
|
.fontWeight(FontWeight.Medium)
|
||||||
.fontColor(this.saving ? FbColors.faint : '#FFFFFF')
|
.fontColor(this.saving ? FbColors.faint : '#FFFFFF')
|
||||||
@@ -271,8 +326,11 @@ export struct FbLedgerEntrySheet {
|
|||||||
}
|
}
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
this.err = '';
|
this.err = '';
|
||||||
const res: FbApiResult = await addLedgerEntry(FB_TOKEN_STORE.get(), this.ledgerId,
|
const res: FbApiResult = this.isEdit()
|
||||||
this.kind, amt, this.title.trim(), this.note.trim());
|
? await updateLedgerEntry(FB_TOKEN_STORE.get(), this.ledgerId, this.editId,
|
||||||
|
this.kind, amt, this.title.trim(), this.note.trim())
|
||||||
|
: await addLedgerEntry(FB_TOKEN_STORE.get(), this.ledgerId,
|
||||||
|
this.kind, amt, this.title.trim(), this.note.trim());
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.err = res.error !== '' ? res.error : '没记上,请稍后再试。';
|
this.err = res.error !== '' ? res.error : '没记上,请稍后再试。';
|
||||||
@@ -280,4 +338,21 @@ export struct FbLedgerEntrySheet {
|
|||||||
}
|
}
|
||||||
this.onSaved();
|
this.onSaved();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 删掉这一笔(服务端 `DELETE /ledger/<id>/entries`,id 走 body + query) */
|
||||||
|
private async remove(): Promise<void> {
|
||||||
|
if (this.saving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.saving = true;
|
||||||
|
const res: FbApiResult = await deleteLedgerEntry(FB_TOKEN_STORE.get(), this.ledgerId,
|
||||||
|
this.editId);
|
||||||
|
this.saving = false;
|
||||||
|
if (!res.ok) {
|
||||||
|
this.err = res.error !== '' ? res.error : '没删掉,请稍后再试。';
|
||||||
|
this.confirmingDelete = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onSaved();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,385 @@
|
|||||||
|
/**
|
||||||
|
* 家书 FamilyBook · 账本设置的两个浮层(鸿蒙端,v68)
|
||||||
|
*
|
||||||
|
* 对应 `FB_SET_LEDGER` 里原来那两条**灰条**:
|
||||||
|
* 「账本名字」→ `FbLedgerRenameSheet`(PATCH /ledger/<id>)
|
||||||
|
* 「共享给谁」→ `FbLedgerShareSheet`(GET/POST/DELETE /ledger/<id>/shares)
|
||||||
|
*
|
||||||
|
* 🔴 用户 2026-10-20:「财务的显示也有问题」,v65 修的是**显示口径**
|
||||||
|
* (端上按正负号重加 vs 服务端负债取绝对值);这一批补的是**动作**:
|
||||||
|
* 模型 `Ledger::rename/setShare/unshare/shares` 一直都有,只是没有路由,
|
||||||
|
* 端上就只能把这两条画成灰的。
|
||||||
|
*
|
||||||
|
* ⚠️ 只有**账本主人**能改共享(服务端也是这个判断,403 会说原话)——
|
||||||
|
* 界面先把"只有主人"写出来,别让被共享的人点了才收到 403。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FbColors, FbMetric, FB_MASK } from '../common/Theme';
|
||||||
|
import { FbSheetClose } from './FbSheetClose';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
|
import { FbPersonPickSheet } from './FbPersonPickSheet';
|
||||||
|
import { FbPersonRepo, FbRosterPerson } from '../data/FbPersonRepo';
|
||||||
|
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||||
|
import { FbApiResult } from '../data/FbHttp';
|
||||||
|
import { renameLedger, fetchLedgerShares, shareLedger, unshareLedger } from '../data/FbLedgerRepo';
|
||||||
|
|
||||||
|
/** 一条共享记录(服务端 `Ledger::shares()`:s.* + p.name / avatar) */
|
||||||
|
interface FbLedgerShareRow {
|
||||||
|
personId: number;
|
||||||
|
name: string;
|
||||||
|
perm: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 服务端原始行(`Api::ok(['items' => …])`)—— 字段名照抄数据库列名 */
|
||||||
|
interface RawShareRow {
|
||||||
|
person_id?: number;
|
||||||
|
name?: string | null;
|
||||||
|
perm?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RawShareList {
|
||||||
|
items?: RawShareRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@ComponentV2
|
||||||
|
export struct FbLedgerRenameSheet {
|
||||||
|
@Param accent: string = '#5E7A3C';
|
||||||
|
@Param ledgerId: number = 0;
|
||||||
|
@Param currentName: string = '';
|
||||||
|
|
||||||
|
@Event onClose: () => void = (): void => {};
|
||||||
|
@Event onSaved: () => void = (): void => {};
|
||||||
|
|
||||||
|
@Local name: string = '';
|
||||||
|
@Local saving: boolean = false;
|
||||||
|
@Local err: string = '';
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
this.name = this.currentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
|
Column()
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.backgroundColor(FB_MASK)
|
||||||
|
.onClick((): void => {
|
||||||
|
this.onClose();
|
||||||
|
})
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accent }) {
|
||||||
|
Column({ space: 12 }) {
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
Column({ space: 2 }) {
|
||||||
|
Text('账本名字')
|
||||||
|
.fontSize(FbMetric.fontTitle)
|
||||||
|
.fontWeight(FontWeight.Medium)
|
||||||
|
.fontColor(FbColors.ink)
|
||||||
|
Text('只改名字,账目不动')
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.faint)
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
.layoutWeight(1)
|
||||||
|
|
||||||
|
FbSheetClose({
|
||||||
|
onClose: (): void => {
|
||||||
|
this.onClose();
|
||||||
|
},
|
||||||
|
color: this.accent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
|
||||||
|
TextInput({ text: this.name, placeholder: '如:家里的账 / 装修账' })
|
||||||
|
.placeholderColor(FbColors.faint)
|
||||||
|
.placeholderFont({ size: FbMetric.fontTiny })
|
||||||
|
.fontSize(FbMetric.fontBody)
|
||||||
|
.fontColor(FbColors.ink)
|
||||||
|
.backgroundColor(FbColors.inputBg)
|
||||||
|
.borderRadius(FbMetric.radiusS)
|
||||||
|
.height(44)
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 10, right: 10 })
|
||||||
|
.onChange((v: string): void => {
|
||||||
|
this.name = v;
|
||||||
|
this.err = '';
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.err !== '') {
|
||||||
|
Text(this.err)
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.red)
|
||||||
|
.width('100%')
|
||||||
|
.maxLines(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(this.saving ? '保存中…' : '保存')
|
||||||
|
.fontSize(FbMetric.fontBody)
|
||||||
|
.fontWeight(FontWeight.Medium)
|
||||||
|
.fontColor(this.saving ? FbColors.faint : '#FFFFFF')
|
||||||
|
.textAlign(TextAlign.Center)
|
||||||
|
.width('100%')
|
||||||
|
.height(44)
|
||||||
|
.borderRadius(FbMetric.radiusS)
|
||||||
|
.backgroundColor(this.accent)
|
||||||
|
.onClick((): void => {
|
||||||
|
this.save();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 18, right: 18, top: 18, bottom: 18 })
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.hitTestBehavior(HitTestMode.Transparent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async save(): Promise<void> {
|
||||||
|
if (this.saving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nm: string = this.name.trim();
|
||||||
|
if (nm === '') {
|
||||||
|
this.err = '名字不能空着。';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.saving = true;
|
||||||
|
const res: FbApiResult = await renameLedger(FB_TOKEN_STORE.get(), this.ledgerId, nm);
|
||||||
|
this.saving = false;
|
||||||
|
if (!res.ok) {
|
||||||
|
this.err = res.error !== '' ? res.error : '没改成,请稍后再试。';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onSaved();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ComponentV2
|
||||||
|
export struct FbLedgerShareSheet {
|
||||||
|
@Param accent: string = '#5E7A3C';
|
||||||
|
@Param ledgerId: number = 0;
|
||||||
|
@Param ledgerName: string = '';
|
||||||
|
/** 只有主人能改 —— 服务端也是这个判断 */
|
||||||
|
@Param isOwner: boolean = true;
|
||||||
|
|
||||||
|
@Event onClose: () => void = (): void => {};
|
||||||
|
/** 共享关系变了(页面可以提示一句) */
|
||||||
|
@Event onChanged: () => void = (): void => {};
|
||||||
|
|
||||||
|
@Local rows: FbLedgerShareRow[] = [];
|
||||||
|
@Local roster: FbRosterPerson[] = [];
|
||||||
|
@Local picking: boolean = false;
|
||||||
|
@Local busy: boolean = false;
|
||||||
|
@Local note: string = '';
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
this.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async reload(): Promise<void> {
|
||||||
|
const res: FbApiResult = await fetchLedgerShares(FB_TOKEN_STORE.get(), this.ledgerId);
|
||||||
|
if (!res.ok) {
|
||||||
|
this.note = res.error !== '' ? res.error : '共享列表没拉到。';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* ⚠️ `FbApiResult.data` 是**原始 JSON 片段**(不是已解析的对象),
|
||||||
|
而且列表在信封里是 `{items:[…]}` —— 两层都要拆。 */
|
||||||
|
let parsed: RawShareList = {};
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(res.data) as RawShareList;
|
||||||
|
} catch (e) {
|
||||||
|
this.note = '共享列表格式看不懂。';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rawRows: RawShareRow[] = parsed.items !== undefined ? parsed.items : [];
|
||||||
|
const raw: FbLedgerShareRow[] = [];
|
||||||
|
for (const it of rawRows) {
|
||||||
|
raw.push({
|
||||||
|
personId: it.person_id !== undefined ? it.person_id : 0,
|
||||||
|
name: it.name !== undefined && it.name !== null ? it.name : '(无名)',
|
||||||
|
perm: it.perm !== undefined && it.perm !== null ? it.perm : 'read'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.rows = raw;
|
||||||
|
this.note = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private async openPicker(): Promise<void> {
|
||||||
|
if (this.roster.length === 0) {
|
||||||
|
try {
|
||||||
|
this.roster = await FbPersonRepo.inst().list();
|
||||||
|
} catch (e) {
|
||||||
|
this.roster = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.picking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addShares(ids: number[]): Promise<void> {
|
||||||
|
this.picking = false;
|
||||||
|
if (ids.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.busy = true;
|
||||||
|
let bad: string = '';
|
||||||
|
for (const id of ids) {
|
||||||
|
const res: FbApiResult = await shareLedger(FB_TOKEN_STORE.get(), this.ledgerId, id, 'read');
|
||||||
|
if (!res.ok && bad === '') {
|
||||||
|
bad = res.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.busy = false;
|
||||||
|
this.note = bad !== '' ? bad : '';
|
||||||
|
await this.reload();
|
||||||
|
this.onChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async drop(personId: number): Promise<void> {
|
||||||
|
this.busy = true;
|
||||||
|
const res: FbApiResult = await unshareLedger(FB_TOKEN_STORE.get(), this.ledgerId, personId);
|
||||||
|
this.busy = false;
|
||||||
|
if (!res.ok) {
|
||||||
|
this.note = res.error !== '' ? res.error : '没取消成。';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.reload();
|
||||||
|
this.onChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
|
Column()
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.backgroundColor(FB_MASK)
|
||||||
|
.onClick((): void => {
|
||||||
|
this.onClose();
|
||||||
|
})
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accent }) {
|
||||||
|
Column({ space: 10 }) {
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
Column({ space: 2 }) {
|
||||||
|
Text('共享给谁')
|
||||||
|
.fontSize(FbMetric.fontTitle)
|
||||||
|
.fontWeight(FontWeight.Medium)
|
||||||
|
.fontColor(FbColors.ink)
|
||||||
|
Text('「' + this.ledgerName + '」· 被共享的人只能看')
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.faint)
|
||||||
|
.maxLines(1)
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
.layoutWeight(1)
|
||||||
|
.flexShrink(1)
|
||||||
|
|
||||||
|
FbSheetClose({
|
||||||
|
onClose: (): void => {
|
||||||
|
this.onClose();
|
||||||
|
},
|
||||||
|
color: this.accent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
|
||||||
|
if (!this.isOwner) {
|
||||||
|
Text('这本账不是你建的 —— 只有账本主人能改共享关系。')
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.muted)
|
||||||
|
.width('100%')
|
||||||
|
.maxLines(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rows.length === 0) {
|
||||||
|
Text('还没共享给任何人')
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.faint)
|
||||||
|
.width('100%')
|
||||||
|
} else {
|
||||||
|
ForEach(this.rows, (r: FbLedgerShareRow) => {
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
Text(r.name)
|
||||||
|
.fontSize(FbMetric.fontBody)
|
||||||
|
.fontColor(FbColors.ink)
|
||||||
|
.layoutWeight(1)
|
||||||
|
.maxLines(1)
|
||||||
|
Text(r.perm === 'write' ? '可改' : '只看')
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.faint)
|
||||||
|
if (this.isOwner) {
|
||||||
|
Text('取消')
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.red)
|
||||||
|
.padding({ left: 8, right: 4, top: 4, bottom: 4 })
|
||||||
|
.onClick((): void => {
|
||||||
|
this.drop(r.personId);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ top: 8, bottom: 8 })
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
}, (r: FbLedgerShareRow): string => 'lsh_' + r.personId.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.note !== '') {
|
||||||
|
Text(this.note)
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.red)
|
||||||
|
.width('100%')
|
||||||
|
.maxLines(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isOwner) {
|
||||||
|
Text(this.busy ? '处理中…' : '共享给…')
|
||||||
|
.fontSize(FbMetric.fontBody)
|
||||||
|
.fontWeight(FontWeight.Medium)
|
||||||
|
.fontColor(this.busy ? FbColors.faint : '#FFFFFF')
|
||||||
|
.textAlign(TextAlign.Center)
|
||||||
|
.width('100%')
|
||||||
|
.height(44)
|
||||||
|
.borderRadius(FbMetric.radiusS)
|
||||||
|
.backgroundColor(this.accent)
|
||||||
|
.onClick((): void => {
|
||||||
|
this.openPicker();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 18, right: 18, top: 18, bottom: 18 })
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
|
||||||
|
/* 选人浮层:复用名册那颗(多选 + 确定),选完逐个人 POST */
|
||||||
|
if (this.picking) {
|
||||||
|
FbPersonPickSheet({
|
||||||
|
people: this.roster,
|
||||||
|
selected: [],
|
||||||
|
title: '共享给谁',
|
||||||
|
accent: this.accent,
|
||||||
|
onConfirm: (ids: number[]): void => {
|
||||||
|
this.addShares(ids);
|
||||||
|
},
|
||||||
|
onClose: (): void => {
|
||||||
|
this.picking = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.hitTestBehavior(HitTestMode.Transparent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,19 +3,25 @@
|
|||||||
*
|
*
|
||||||
* 对应服务端 `app/ApiHandler.php` 的 `ledger()`:
|
* 对应服务端 `app/ApiHandler.php` 的 `ledger()`:
|
||||||
* POST /api/v1/ledger 新建账本(name)
|
* POST /api/v1/ledger 新建账本(name)
|
||||||
* POST /api/v1/ledger/<id>/entries 记一笔(kind / amount / title / note / person_id)
|
* PATCH /api/v1/ledger/<id> 账本改名(name) ← v68
|
||||||
* DELETE /api/v1/ledger/<id> 删账本
|
* DELETE /api/v1/ledger/<id> 删账本
|
||||||
|
* POST /api/v1/ledger/<id>/entries 记一笔(kind/amount/title/note/person_id)
|
||||||
|
* PATCH /api/v1/ledger/<id>/entries 改一笔(**id 在 body 里**) ← v68
|
||||||
|
* DELETE /api/v1/ledger/<id>/entries 删一笔(**id 在 body/query**)← v68
|
||||||
|
* GET /api/v1/ledger/<id>/shares 共享给了谁 ← v68
|
||||||
|
* POST /api/v1/ledger/<id>/shares 共享给某人(person_id/perm)← v68
|
||||||
|
* DELETE /api/v1/ledger/<id>/shares 取消共享(person_id) ← v68
|
||||||
*
|
*
|
||||||
* 🔴 用户 2026-10-20:「还有这个财务的显示也有问题。」
|
* 🔴 用户 2026-10-20:「还有这个财务的显示也有问题。」(v65 已修显示口径)
|
||||||
* ⇒ 端上「记一笔」按钮**没有 `onTap`**、账本切换**不重拉明细**、
|
* v68 补的是**动作**:设置里「共享给谁」「账本名字」原来是灰条,
|
||||||
* 资产/负债是**端上自己按正负号重新加**的(与净资产口径不一致)。见 `FbLedgerPage`。
|
* 账目也没有"改一笔/删一笔" —— 模型里这些方法早就有,只差路由与端上入口。
|
||||||
*
|
*
|
||||||
* ⚠️ **金额传「元」的字符串**:服务端 `Ledger::toCents()` 负责转分
|
* ⚠️ **金额传「元」的字符串**:服务端 `Ledger::toCents()` 负责转分
|
||||||
* (App 与网页表单同一口径)。这里**不要**先转分再传 —— 会双重换算,差 100 倍。
|
* (App 与网页表单同一口径)。这里**不要**先转分再传 —— 会双重换算,差 100 倍。
|
||||||
*
|
*
|
||||||
* ⚠️ **没有"改一笔"和"删一笔"的接口**(`Api::` 路由只解析到第 3 段,
|
* 🔴 **被改/被删的那一笔,id 只能放 body 或 query,不能拼进路径**:
|
||||||
* `/ledger/<id>/entries/<eid>` 的第 4 段会被丢掉)。所以端上只做"记一笔",
|
* `Api::dispatch()` 只解析 `/<res>/<id>/<sub>` **三段**,
|
||||||
* 改了金额请去网页端操作 —— 这一条要显式写在界面上,别让用户以为存不上。
|
* `/ledger/3/entries/17` 的第 4 段会被直接丢掉(会落到"整本账本"上)。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fbApi, FbApiResult, FbApiOptions } from './FbHttp';
|
import { fbApi, FbApiResult, FbApiOptions } from './FbHttp';
|
||||||
@@ -33,3 +39,65 @@ export async function addLedgerEntry(token: string, ledgerId: number, kind: stri
|
|||||||
const opts: FbApiOptions = { method: 'POST', token: token, body: body };
|
const opts: FbApiOptions = { method: 'POST', token: token, body: body };
|
||||||
return await fbApi('/api/v1/ledger/' + ledgerId.toString() + '/entries', opts);
|
return await fbApi('/api/v1/ledger/' + ledgerId.toString() + '/entries', opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 改一笔(金额/名称/备注/收支/经手人)。`entryId` 必须在 body 里,见文件头说明。 */
|
||||||
|
export async function updateLedgerEntry(token: string, ledgerId: number, entryId: number,
|
||||||
|
kind: string, amountYuan: string, title: string, note: string): Promise<FbApiResult> {
|
||||||
|
const body: Record<string, Object> = {};
|
||||||
|
body['id'] = entryId;
|
||||||
|
body['kind'] = kind;
|
||||||
|
body['amount'] = amountYuan;
|
||||||
|
body['title'] = title;
|
||||||
|
body['note'] = note;
|
||||||
|
const opts: FbApiOptions = { method: 'PATCH', token: token, body: body };
|
||||||
|
return await fbApi('/api/v1/ledger/' + ledgerId.toString() + '/entries', opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删一笔 */
|
||||||
|
export async function deleteLedgerEntry(token: string, ledgerId: number,
|
||||||
|
entryId: number): Promise<FbApiResult> {
|
||||||
|
/* ⚠️ DELETE 也带 body:服务端的 `Api::body()` 认 JSON body;
|
||||||
|
同时也放进 query(`?id=`)—— 某些代理会丢 DELETE 的 body。 */
|
||||||
|
const path: string = '/api/v1/ledger/' + ledgerId.toString() + '/entries?id=' +
|
||||||
|
entryId.toString();
|
||||||
|
const body: Record<string, Object> = {};
|
||||||
|
body['id'] = entryId;
|
||||||
|
const opts: FbApiOptions = { method: 'DELETE', token: token, body: body };
|
||||||
|
return await fbApi(path, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 账本改名 */
|
||||||
|
export async function renameLedger(token: string, ledgerId: number,
|
||||||
|
name: string): Promise<FbApiResult> {
|
||||||
|
const body: Record<string, Object> = {};
|
||||||
|
body['name'] = name;
|
||||||
|
const opts: FbApiOptions = { method: 'PATCH', token: token, body: body };
|
||||||
|
return await fbApi('/api/v1/ledger/' + ledgerId.toString(), opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 共享给了谁(服务端回 `{items:[…]}`,每项含 person_id / name / perm) */
|
||||||
|
export async function fetchLedgerShares(token: string, ledgerId: number): Promise<FbApiResult> {
|
||||||
|
const opts: FbApiOptions = { token: token, authProbe: true };
|
||||||
|
return await fbApi('/api/v1/ledger/' + ledgerId.toString() + '/shares', opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 共享给某人(perm = read / write) */
|
||||||
|
export async function shareLedger(token: string, ledgerId: number, personId: number,
|
||||||
|
perm: string): Promise<FbApiResult> {
|
||||||
|
const body: Record<string, Object> = {};
|
||||||
|
body['person_id'] = personId;
|
||||||
|
body['perm'] = perm;
|
||||||
|
const opts: FbApiOptions = { method: 'POST', token: token, body: body };
|
||||||
|
return await fbApi('/api/v1/ledger/' + ledgerId.toString() + '/shares', opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消某人的共享 */
|
||||||
|
export async function unshareLedger(token: string, ledgerId: number,
|
||||||
|
personId: number): Promise<FbApiResult> {
|
||||||
|
const path: string = '/api/v1/ledger/' + ledgerId.toString() + '/shares?person_id=' +
|
||||||
|
personId.toString();
|
||||||
|
const body: Record<string, Object> = {};
|
||||||
|
body['person_id'] = personId;
|
||||||
|
const opts: FbApiOptions = { method: 'DELETE', token: token, body: body };
|
||||||
|
return await fbApi(path, opts);
|
||||||
|
}
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ interface RawTaskBoard {
|
|||||||
interface RawLedgerBook {
|
interface RawLedgerBook {
|
||||||
id?: number; name?: string; my_perm?: string | null; entry_count?: number;
|
id?: number; name?: string; my_perm?: string | null; entry_count?: number;
|
||||||
share_perm?: string | null;
|
share_perm?: string | null;
|
||||||
|
/** v68:服务端补的"是不是我建的"(`my_perm` 只有 read/write,分不出主人) */
|
||||||
|
is_owner?: boolean | number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RawLedgerEntry {
|
interface RawLedgerEntry {
|
||||||
@@ -390,6 +392,7 @@ export function parseLedger(json: string): FbLedgerBook[] {
|
|||||||
name: s(r.name),
|
name: s(r.name),
|
||||||
netCent: 0, // 概览接口不返回净资产,进详情页时再取 summary
|
netCent: 0, // 概览接口不返回净资产,进详情页时再取 summary
|
||||||
myPerm: s(r.my_perm) !== '' ? s(r.my_perm) : 'read',
|
myPerm: s(r.my_perm) !== '' ? s(r.my_perm) : 'read',
|
||||||
|
isOwner: b(r.is_owner),
|
||||||
entryCount: n(r.entry_count),
|
entryCount: n(r.entry_count),
|
||||||
};
|
};
|
||||||
out.push(bk);
|
out.push(bk);
|
||||||
@@ -421,6 +424,7 @@ export function parseLedgerEntries(json: string): FbLedgerDetail {
|
|||||||
amountCent: n(r.amount_cents),
|
amountCent: n(r.amount_cents),
|
||||||
// 「挂名的人」优先用昵称(我习惯叫他什么),没有就用本名
|
// 「挂名的人」优先用昵称(我习惯叫他什么),没有就用本名
|
||||||
ownerName: s(r.person_nick) !== '' ? s(r.person_nick) : s(r.person_name),
|
ownerName: s(r.person_nick) !== '' ? s(r.person_nick) : s(r.person_name),
|
||||||
|
note: s(r.note),
|
||||||
updatedAt: datePart(s(r.updated_at)),
|
updatedAt: datePart(s(r.updated_at)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,21 +246,21 @@ export const FB_DEMO_TASK_BOARDS: string[] = ['家务', '家人健康', '孩子'
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
export const FB_DEMO_LEDGER_BOOKS: FbLedgerBook[] = [
|
export const FB_DEMO_LEDGER_BOOKS: FbLedgerBook[] = [
|
||||||
{ id: 1, name: '家庭总账', netCent: 86420000, myPerm: 'owner', entryCount: 9 },
|
{ id: 1, name: '家庭总账', netCent: 86420000, myPerm: 'write', isOwner: true, entryCount: 9 },
|
||||||
{ id: 2, name: '我的私房账', netCent: 1260000, myPerm: 'owner', entryCount: 3 },
|
{ id: 2, name: '我的私房账', netCent: 1260000, myPerm: 'write', isOwner: true, entryCount: 3 },
|
||||||
{ id: 3, name: '爸妈的养老账', netCent: 4580000, myPerm: 'write', entryCount: 5 }
|
{ id: 3, name: '爸妈的养老账', netCent: 4580000, myPerm: 'write', isOwner: false, entryCount: 5 }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const FB_DEMO_LEDGER_ENTRIES: FbLedgerEntry[] = [
|
export const FB_DEMO_LEDGER_ENTRIES: FbLedgerEntry[] = [
|
||||||
{ id: 1, name: '工资卡', kind: 'deposit', amountCent: 18600000, ownerName: '我', updatedAt: '2026-10-01' },
|
{ id: 1, name: '工资卡', kind: 'deposit', amountCent: 18600000, note: '', ownerName: '我', updatedAt: '2026-10-01' },
|
||||||
{ id: 2, name: '公积金', kind: 'deposit', amountCent: 24300000, ownerName: '我', updatedAt: '2026-10-01' },
|
{ id: 2, name: '公积金', kind: 'deposit', amountCent: 24300000, note: '', ownerName: '我', updatedAt: '2026-10-01' },
|
||||||
{ id: 3, name: '指数基金', kind: 'fund', amountCent: 34800000, ownerName: '我', updatedAt: '2026-09-30' },
|
{ id: 3, name: '指数基金', kind: 'fund', amountCent: 34800000, note: '', ownerName: '我', updatedAt: '2026-09-30' },
|
||||||
{ id: 4, name: '重疾险', kind: 'insurance', amountCent: 500000, ownerName: '爱人', updatedAt: '2026-08-12' },
|
{ id: 4, name: '重疾险', kind: 'insurance', amountCent: 500000, note: '', ownerName: '爱人', updatedAt: '2026-08-12' },
|
||||||
{ id: 5, name: '自住房', kind: 'house', amountCent: 180000000, ownerName: '我们', updatedAt: '2026-06-20' },
|
{ id: 5, name: '自住房', kind: 'house', amountCent: 180000000, note: '', ownerName: '我们', updatedAt: '2026-06-20' },
|
||||||
{ id: 6, name: '代步车', kind: 'car', amountCent: 8200000, ownerName: '我', updatedAt: '2026-06-20' },
|
{ id: 6, name: '代步车', kind: 'car', amountCent: 8200000, note: '', ownerName: '我', updatedAt: '2026-06-20' },
|
||||||
{ id: 7, name: '房贷余额', kind: 'debt', amountCent: -142600000, ownerName: '我们', updatedAt: '2026-10-01' },
|
{ id: 7, name: '房贷余额', kind: 'debt', amountCent: -142600000, note: '', ownerName: '我们', updatedAt: '2026-10-01' },
|
||||||
{ id: 8, name: '信用卡待还', kind: 'debt', amountCent: -3860000, ownerName: '爱人', updatedAt: '2026-10-08' },
|
{ id: 8, name: '信用卡待还', kind: 'debt', amountCent: -3860000, note: '', ownerName: '爱人', updatedAt: '2026-10-08' },
|
||||||
{ id: 9, name: '车位', kind: 'house', amountCent: 12000000, ownerName: '我们', updatedAt: '2026-06-20' }
|
{ id: 9, name: '车位', kind: 'house', amountCent: 12000000, note: '', ownerName: '我们', updatedAt: '2026-06-20' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ export const FB_SET_DAYS: FbSettingItem[] = [
|
|||||||
|
|
||||||
/** 账本(templates/ledger/_settings_menu.php) */
|
/** 账本(templates/ledger/_settings_menu.php) */
|
||||||
export const FB_SET_LEDGER: FbSettingItem[] = [
|
export const FB_SET_LEDGER: FbSettingItem[] = [
|
||||||
{ icon: 'share', label: '共享给谁', hint: '对方就能记能看', action: '' },
|
{ icon: 'share', label: '共享给谁', hint: '对方就能看这本账', action: 'ledgershare' },
|
||||||
{ icon: 'edit', label: '账本名字', hint: '改这本账本的名字', action: '' }
|
{ icon: 'edit', label: '账本名字', hint: '改这本账本的名字', action: 'ledgername' }
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 任务(templates/tasks/_settings_menu.php)
|
/** 任务(templates/tasks/_settings_menu.php)
|
||||||
|
|||||||
@@ -686,8 +686,16 @@ export interface FbLedgerBook {
|
|||||||
name: string;
|
name: string;
|
||||||
/** 净资产(分) */
|
/** 净资产(分) */
|
||||||
netCent: number;
|
netCent: number;
|
||||||
/** 我的权限:owner / read / write */
|
/**
|
||||||
|
* 我的权限:**只有 `read` / `write` 两档**(服务端 `Ledger::find` 就是这么给的)。
|
||||||
|
*
|
||||||
|
* 🔴 v68:这里原来写的是 `owner / read / write` —— 服务端**从来不会**给 `owner`,
|
||||||
|
* 于是端上"是不是我建的账本"永远判成 false(共享浮层会一直说"只有主人能改")。
|
||||||
|
* 现在用服务端补的 `isOwner` 布尔。
|
||||||
|
*/
|
||||||
myPerm: string;
|
myPerm: string;
|
||||||
|
/** 这本账是不是我建的(只有主人能改共享关系) */
|
||||||
|
isOwner: boolean;
|
||||||
/** 账目条数 */
|
/** 账目条数 */
|
||||||
entryCount: number;
|
entryCount: number;
|
||||||
}
|
}
|
||||||
@@ -703,6 +711,8 @@ export interface FbLedgerEntry {
|
|||||||
amountCent: number;
|
amountCent: number;
|
||||||
/** 挂名的人 */
|
/** 挂名的人 */
|
||||||
ownerName: string;
|
ownerName: string;
|
||||||
|
/** 备注(v68:改一笔时要回填,原来类型里没有 ⇒ 编辑框永远是空的) */
|
||||||
|
note: string;
|
||||||
/** 记录/盘点时间 */
|
/** 记录/盘点时间 */
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
|
|||||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||||
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
|
||||||
import { FbLedgerEntrySheet } from '../../components/FbLedgerEntrySheet';
|
import { FbLedgerEntrySheet } from '../../components/FbLedgerEntrySheet';
|
||||||
|
import { FbLedgerRenameSheet, FbLedgerShareSheet } from '../../components/FbLedgerSettingsSheets';
|
||||||
import { FB_BACK } from '../../common/FbBack';
|
import { FB_BACK } from '../../common/FbBack';
|
||||||
import { FB_SET_LEDGER } from '../../model/FbSettingsMenus';
|
import { FB_SET_LEDGER } from '../../model/FbSettingsMenus';
|
||||||
|
|
||||||
@@ -77,6 +78,20 @@ export struct FbLedgerPage {
|
|||||||
@Local debtNow: number = 0;
|
@Local debtNow: number = 0;
|
||||||
/** 「记一笔」浮层是否打开 */
|
/** 「记一笔」浮层是否打开 */
|
||||||
@Local entryOpen: boolean = false;
|
@Local entryOpen: boolean = false;
|
||||||
|
/**
|
||||||
|
* 正在编辑哪一笔(0 = 记新的)。
|
||||||
|
*
|
||||||
|
* 🔴 v68:服务端补了 `PATCH/DELETE /ledger/<id>/entries`(id 走 body),
|
||||||
|
* 端上点一行就能改/删 —— 这四组初值就是点进来的那一行。
|
||||||
|
*/
|
||||||
|
@Local editId: number = 0;
|
||||||
|
@Local editKind: string = 'deposit';
|
||||||
|
@Local editAmount: string = '';
|
||||||
|
@Local editTitle: string = '';
|
||||||
|
@Local editNote: string = '';
|
||||||
|
/** 设置里两条浮层(账本改名 / 共享给谁) */
|
||||||
|
@Local renameOpen: boolean = false;
|
||||||
|
@Local shareOpen: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 页面出现:**两级加载**。
|
* 页面出现:**两级加载**。
|
||||||
@@ -95,6 +110,14 @@ export struct FbLedgerPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private backOnce(): boolean {
|
private backOnce(): boolean {
|
||||||
|
if (this.shareOpen) {
|
||||||
|
this.shareOpen = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.renameOpen) {
|
||||||
|
this.renameOpen = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (this.entryOpen) {
|
if (this.entryOpen) {
|
||||||
this.entryOpen = false;
|
this.entryOpen = false;
|
||||||
return true;
|
return true;
|
||||||
@@ -164,7 +187,24 @@ export struct FbLedgerPage {
|
|||||||
/** 「记一笔」浮层开着 ⇒ 底部悬浮快捷栏让位(与设置浮层不会同时开) */
|
/** 「记一笔」浮层开着 ⇒ 底部悬浮快捷栏让位(与设置浮层不会同时开) */
|
||||||
@Monitor('entryOpen')
|
@Monitor('entryOpen')
|
||||||
onEntrySheetChanged(): void {
|
onEntrySheetChanged(): void {
|
||||||
fbAppState().sheetCount = this.entryOpen ? 1 : 0;
|
this.recountSheets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* v68:改名 / 共享两个浮层同样要让底部悬浮快捷栏让位 */
|
||||||
|
@Monitor('renameOpen')
|
||||||
|
onRenameChanged(): void {
|
||||||
|
this.recountSheets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Monitor('shareOpen')
|
||||||
|
onShareChanged(): void {
|
||||||
|
this.recountSheets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 任一浮层开着 ⇒ 悬浮栏让位(三者互斥,最多一个) */
|
||||||
|
private recountSheets(): void {
|
||||||
|
const n: number = (this.entryOpen || this.renameOpen || this.shareOpen) ? 1 : 0;
|
||||||
|
fbAppState().sheetCount = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
@@ -434,9 +474,18 @@ export struct FbLedgerPage {
|
|||||||
this.settingsOpen = false;
|
this.settingsOpen = false;
|
||||||
},
|
},
|
||||||
onPick: (action: string): void => {
|
onPick: (action: string): void => {
|
||||||
/* 「共享给谁」「账本名字」**都没有 API**(服务端只开了
|
/* v68:这两条原来都是灰的(服务端没有路由)。
|
||||||
建账本 / 记一笔 / 删账本),所以这里保持"暂未开放"的灰态,
|
现在服务端补齐了 `PATCH /ledger/<id>` 与 `/ledger/<id>/shares`。 */
|
||||||
不接一个点了报错的入口。 */
|
if (action === 'ledgershare') {
|
||||||
|
this.settingsOpen = false;
|
||||||
|
this.shareOpen = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === 'ledgername') {
|
||||||
|
this.settingsOpen = false;
|
||||||
|
this.renameOpen = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.transition(fbSheetTransition())
|
.transition(fbSheetTransition())
|
||||||
@@ -448,17 +497,59 @@ export struct FbLedgerPage {
|
|||||||
accent: this.accent,
|
accent: this.accent,
|
||||||
ledgerId: this.currentId(),
|
ledgerId: this.currentId(),
|
||||||
ledgerName: this.currentBook().name,
|
ledgerName: this.currentBook().name,
|
||||||
|
editId: this.editId,
|
||||||
|
editKind: this.editKind,
|
||||||
|
editAmount: this.editAmount,
|
||||||
|
editTitle: this.editTitle,
|
||||||
|
editNote: this.editNote,
|
||||||
onClose: (): void => {
|
onClose: (): void => {
|
||||||
this.entryOpen = false;
|
this.entryOpen = false;
|
||||||
|
this.editId = 0;
|
||||||
},
|
},
|
||||||
onSaved: (): void => {
|
onSaved: (): void => {
|
||||||
this.entryOpen = false;
|
this.entryOpen = false;
|
||||||
|
this.editId = 0;
|
||||||
// 重拉明细:金额与汇总都在里面,端上拼不出来
|
// 重拉明细:金额与汇总都在里面,端上拼不出来
|
||||||
this.loadEntries(this.currentId());
|
this.loadEntries(this.currentId());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.transition(fbSheetTransition())
|
.transition(fbSheetTransition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 账本改名(v68:设置里那条原来是灰的)----
|
||||||
|
if (this.renameOpen) {
|
||||||
|
FbLedgerRenameSheet({
|
||||||
|
accent: this.accent,
|
||||||
|
ledgerId: this.currentId(),
|
||||||
|
currentName: this.currentBook().name,
|
||||||
|
onClose: (): void => {
|
||||||
|
this.renameOpen = false;
|
||||||
|
},
|
||||||
|
onSaved: (): void => {
|
||||||
|
this.renameOpen = false;
|
||||||
|
this.load(); // 名字在 /ledger 列表里,重拉一次
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.transition(fbSheetTransition())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 共享给谁(v68)----
|
||||||
|
if (this.shareOpen) {
|
||||||
|
FbLedgerShareSheet({
|
||||||
|
accent: this.accent,
|
||||||
|
ledgerId: this.currentId(),
|
||||||
|
ledgerName: this.currentBook().name,
|
||||||
|
// 主人才能改共享:`myPerm` 是服务端 `Ledger::find` 给的
|
||||||
|
isOwner: this.currentBook().isOwner,
|
||||||
|
onClose: (): void => {
|
||||||
|
this.shareOpen = false;
|
||||||
|
},
|
||||||
|
onChanged: (): void => {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.transition(fbSheetTransition())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
@@ -506,6 +597,31 @@ export struct FbLedgerPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
.onClick((): void => {
|
||||||
|
/* v68:点一行 = 改这一笔(金额 / 名称 / 备注 / 类别),按住不动的"删除"
|
||||||
|
放在浮层底部(二次确认)—— 与观影/旅程同一套交互习惯。 */
|
||||||
|
this.editId = e.id;
|
||||||
|
this.editKind = e.kind;
|
||||||
|
this.editAmount = this.amountYuanText(e.amountCent);
|
||||||
|
this.editTitle = e.name;
|
||||||
|
this.editNote = e.note;
|
||||||
|
this.entryOpen = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分 → 「元」输入框文本(`1250050` → `12500.50`)。
|
||||||
|
*
|
||||||
|
* ⚠️ 不能复用 `fbMoney()`:那个带千分位和「¥」,塞回 `TextInput` 会被
|
||||||
|
* `Ledger::toCents()` 当成非法数字(它只吃数字与小数点)。
|
||||||
|
*/
|
||||||
|
private amountYuanText(cent: number): string {
|
||||||
|
const neg: boolean = cent < 0;
|
||||||
|
const c: number = Math.abs(cent);
|
||||||
|
const y: number = Math.floor(c / 100);
|
||||||
|
const f: number = c % 100;
|
||||||
|
const fs: string = f < 10 ? ('0' + f.toString()) : f.toString();
|
||||||
|
return (neg ? '-' : '') + y.toString() + '.' + fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isCurrent(id: number): boolean {
|
private isCurrent(id: number): boolean {
|
||||||
@@ -524,7 +640,7 @@ export struct FbLedgerPage {
|
|||||||
if (hit !== undefined) {
|
if (hit !== undefined) {
|
||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
return { id: 0, name: '账本', netCent: 0, myPerm: 'owner', entryCount: 0 };
|
return { id: 0, name: '账本', netCent: 0, myPerm: 'read', isOwner: false, entryCount: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
private netCent(): number {
|
private netCent(): number {
|
||||||
|
|||||||
@@ -1482,7 +1482,14 @@ export struct FbPeoplePage {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 家谱的说明条("以我为中心" + 图谱/列表切换 + 补关系按钮)。
|
* 家谱的说明条("以我为中心" + 图谱/列表切换)。
|
||||||
|
*
|
||||||
|
* 🔴 v68:这里原来还有一颗「补关系」按钮,**从来没有 `onTap`** ——
|
||||||
|
* 点了一点反应都没有(全仓库扫过,只剩这一颗)。
|
||||||
|
* 它要的是"选一个还没有亲属关系的人 → 给他挂上父/母/配偶",
|
||||||
|
* 而服务端 `relations` 表**只有读**(没有写接口),网页版也没有这个功能
|
||||||
|
* ⇒ 与其留一个永远点不动的按钮,先撤掉(免得被当成"坏了")。
|
||||||
|
* 要做得先加 `POST /api/v1/persons/<id>/relations`,那是独立一批活。
|
||||||
*
|
*
|
||||||
* ⚠️ 从 `kinView` 里抽出来的原因:**列表模式**要把它**一起放进滚动的 Scroll**
|
* ⚠️ 从 `kinView` 里抽出来的原因:**列表模式**要把它**一起放进滚动的 Scroll**
|
||||||
* (这样它才跟着内容滚走),而**图谱模式**它是固定的一层。
|
* (这样它才跟着内容滚走),而**图谱模式**它是固定的一层。
|
||||||
@@ -1511,12 +1518,6 @@ export struct FbPeoplePage {
|
|||||||
.padding(2)
|
.padding(2)
|
||||||
.backgroundColor(FbColors.inputBg)
|
.backgroundColor(FbColors.inputBg)
|
||||||
.borderRadius(FbMetric.radiusPill)
|
.borderRadius(FbMetric.radiusPill)
|
||||||
|
|
||||||
FbPrimaryButton({
|
|
||||||
label: '补关系',
|
|
||||||
bg: '#8A6B4B',
|
|
||||||
icon: $r('app.media.fb_ic_plus')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||||
|
|||||||
Reference in New Issue
Block a user