Files
FamilyBook_harmonyos/entry/src/main/ets/data/FbTripSettingsRepo.ets
T

220 lines
7.5 KiB
Plaintext
Raw Normal View History

/**
* 家书 FamilyBook · 旅程三项设置的**写操作**(鸿蒙端)
*
* 🔴 v66(用户 2026-10-20):「旅程的……车辆管理、出行方式、节点设置,
* 这些东西都还没完成。」
*
* 三件事的实际情况并不一样,这里按真实情况办:
* · **车辆管理** → `vehicles` 表 + `Vehicle::save/destroy`,端上补增删改;
* · **节点类型** → `node_types` 表 + `NodeType::save/destroy`,端上补增删改;
* · **出行方式** → **枚举常量**(`Trip::TRAVEL_MODES`),根本没有"管理"这回事
* ⇒ 只做一份只读说明 + 提示"在行程里选",**不摆假入口**。
*
* 服务端契约(v66 新增,与网页版 `trips/vehicles.php` / `trips/types.php` 同一套模型):
* GET /api/v1/vehicles 车辆列表(含 used_count)
* POST /api/v1/vehicles 新增
* PATCH /api/v1/vehicles/<id> 改
* DELETE /api/v1/vehicles/<id> 删
* GET /api/v1/nodetypes 节点类型列表
* POST /api/v1/nodetypes 新增
* PATCH /api/v1/nodetypes/<id> 改(**改名会级联到已有行程**,见服务端注释)
* DELETE /api/v1/nodetypes/<id> 删(内置的 / 还在用的会被拒)
*
* ⚠️ 删不掉的两种情况**不是错误**,是服务端的保护:
* · 车辆:"还有 N 段行程在用这辆车"
* · 类型:内置类型 / 还有 N 段行程在用
* ⇒ 端上把 `error` 原文显示出来即可(服务端那句话已经是人话)。
*/
import { fbApi, FbApiResult } from './FbHttp';
import { FbNodeTypeItem, FbVehicleItem } from '../model/Types';
/** 新建/修改车辆时提交的字段(与网页版 `vehicles.php` 的表单对齐) */
export interface FbVehicleDraft {
plate: string;
name: string;
/** `own` / `rental` / `borrowed` */
vtype: string;
vendor: string;
orderNo: string;
isDefault: boolean;
note: string;
}
/** 新建/修改节点类型时提交的字段 */
export interface FbNodeTypeDraft {
name: string;
icon: string;
}
/** 字符串读值(服务端可能给 null) */
function s(v: Object | null | undefined): string {
return typeof v === 'string' ? v : '';
}
/** 数字读值 */
function n(v: Object | null | undefined): number {
return typeof v === 'number' ? v : 0;
}
/** 车辆列表 → 端上类型 */
export function parseVehicles(json: string): FbVehicleItem[] {
let rows: Object[] = [];
try {
const v: Object = JSON.parse(json) as Object;
if (Array.isArray(v)) {
rows = v as Object[];
}
} catch (err) {
return [];
}
const out: FbVehicleItem[] = [];
for (const r of rows) {
const o: Record<string, Object> = r as Record<string, Object>;
const it: FbVehicleItem = {
id: n(o['id'] as number),
plate: s(o['plate'] as string),
name: s(o['name'] as string),
vtype: s(o['vtype'] as string) !== '' ? s(o['vtype'] as string) : 'own',
vendor: s(o['vendor'] as string),
orderNo: s(o['order_no'] as string),
isDefault: n(o['is_default'] as number) > 0,
note: s(o['note'] as string),
// ⚠️ 服务端不一定给 `used_count`(老服务端没有这条接口时会 404,
// 这里拿不到就按 0 处理;删的时候仍由服务端把关)
usedCount: n(o['used_count'] as number)
};
out.push(it);
}
return out;
}
/** 节点类型列表 → 端上类型 */
export function parseNodeTypes(json: string): FbNodeTypeItem[] {
let rows: Object[] = [];
try {
const v: Object = JSON.parse(json) as Object;
if (Array.isArray(v)) {
rows = v as Object[];
}
} catch (err) {
return [];
}
const out: FbNodeTypeItem[] = [];
for (const r of rows) {
const o: Record<string, Object> = r as Record<string, Object>;
const it: FbNodeTypeItem = {
id: n(o['id'] as number),
name: s(o['name'] as string),
icon: s(o['icon'] as string),
isSystem: n(o['is_system'] as number) > 0,
usedCount: n(o['used_count'] as number)
};
if (it.name !== '') {
out.push(it);
}
}
return out;
}
/** 取车辆列表 */
export async function fetchVehicles(token: string): Promise<FbVehicleItem[]> {
const r: FbApiResult = await fbApi('/api/v1/vehicles', { token: token });
if (!r.ok) {
return [];
}
return parseVehicles(r.data);
}
/** 新增 / 修改一辆车(`id > 0` = 改) */
export async function saveVehicle(token: string, d: FbVehicleDraft,
id: number = 0): Promise<FbApiResult> {
/* ⚠️ 请求体一律 `Record<string, Object> = {}` 再逐键赋值 ——
ArkTS 的 `arkts-no-untyped-obj-literals` 不认 Record 字面量
(与 `FbTripRepo.createTrip` 同一写法)。 */
const body: Record<string, Object> = {};
body['plate'] = d.plate;
body['name'] = d.name;
body['vtype'] = d.vtype;
body['vendor'] = d.vendor;
body['order_no'] = d.orderNo;
body['is_default'] = d.isDefault ? 1 : 0;
body['note'] = d.note;
const path: string = id > 0 ? '/api/v1/vehicles/' + id.toString() : '/api/v1/vehicles';
return await fbApi(path, {
method: id > 0 ? 'PATCH' : 'POST',
token: token,
body: body
});
}
/** 删一辆车(服务端会拦"还有行程在用") */
export async function deleteVehicle(token: string, id: number): Promise<FbApiResult> {
return await fbApi('/api/v1/vehicles/' + id.toString(), { method: 'DELETE', token: token });
}
/** 取节点类型列表 */
export async function fetchNodeTypes(token: string): Promise<FbNodeTypeItem[]> {
const r: FbApiResult = await fbApi('/api/v1/nodetypes', { token: token });
if (!r.ok) {
return [];
}
return parseNodeTypes(r.data);
}
/** 新增 / 修改一种节点类型(`id > 0` = 改;改名会级联到已有行程) */
export async function saveNodeType(token: string, d: FbNodeTypeDraft,
id: number = 0): Promise<FbApiResult> {
const body: Record<string, Object> = {};
body['name'] = d.name;
body['icon'] = d.icon;
const path: string = id > 0 ? '/api/v1/nodetypes/' + id.toString() : '/api/v1/nodetypes';
return await fbApi(path, {
method: id > 0 ? 'PATCH' : 'POST',
token: token,
body: body
});
}
/** 删一种节点类型(内置 / 还在用的会被拒) */
export async function deleteNodeType(token: string, id: number): Promise<FbApiResult> {
return await fbApi('/api/v1/nodetypes/' + id.toString(), { method: 'DELETE', token: token });
}
/**
* 出行方式(**只读枚举**,与 `app/Trip.php` 的 `TRAVEL_MODES` **同一套 key**)。
*
* ⚠️ 它不是一张表,没有增删改 —— 「出行方式设置」这项在端上就是一份说明:
* 列出这十种、并告诉用户"在加行程时选"。
* 🔴 不要照"车辆管理"的样子给它做一个能新增的表单:服务端根本没这回事,
* 做出来只是又一个"点了没用"的入口。
*/
export interface FbTravelModeDef {
key: string;
name: string;
}
export const FB_TRAVEL_MODES: FbTravelModeDef[] = [
{ key: 'walk', name: '步行' },
{ key: 'drive', name: '自驾' },
{ key: 'bus', name: '公交' },
{ key: 'subway', name: '地铁' },
{ key: 'train', name: '火车' },
{ key: 'plane', name: '飞机' },
{ key: 'ship', name: '轮船' },
{ key: 'taxi', name: '打车' },
{ key: 'bike', name: '骑行' },
{ key: 'other', name: '其他' }
];
/** 车辆来源的中文名(`Vehicle::TYPES` 的镜像) */
export function fbVehicleTypeName(vtype: string): string {
if (vtype === 'rental') {
return '租的';
}
if (vtype === 'borrowed') {
return '借的';
}
return '自家的';
}