# addr **Repository Path**: guiv/addr ## Basic Information - **Project Name**: addr - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-11 - **Last Updated**: 2026-09-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # V 通讯录(V 0.5.2 + ui2 + SQLite) 一个用 **V 语言 + ui2 桌面 GUI + SQLite** 编写的通讯录程序:支持联系人的增删改查、关键词搜索、**分组筛选 / 排序 / 收藏**、**个人头像(图片)**、**CSV 导入导出**与 **JSON 整库备份恢复**,自带 10 条中文模拟数据,启动后直接可用。所有 UI 状态与业务逻辑都封装在 `ContactApp` 结构体中,**不使用任何全局变量**。 ![界面预览](contact_preview.png) ``` ├── main.v # ContactApp 模型 + 入口:所有状态/逻辑入 struct ├── store.v # 数据层:SQLite ORM + 增量迁移(avatar/favorite)+ 模拟数据 ├── paths.v # ★ 路径统一入口:data/ 数据目录、数据库、头像目录、旧数据自动迁移 ├── csv.v # CSV 导入导出(encoding/csv,UTF-8 BOM,电话去重,含收藏列) ├── backup.v # JSON 整库备份 / 恢复(json2,兼容纯数组格式) ├── avatar.v # 头像模块:图片缩放 + 手写 BI_RGB BMP 编码器 + 文件清理 ├── contact.vml # VML 界面:顶栏 + 左列表(筛选片/排序)+ 右表单 + 消息条 + 统计 ├── app_icon.png # 应用图标(256×256,暖橙 + 白色人形,-icon 编译嵌入) ├── store_test.v # 数据层单元测试(含迁移 / 排重 / 整库替换) ├── csv_test.v # CSV 往返 / BOM / 表头 / 去重测试 ├── backup_test.v # JSON 备份往返 / 数组兼容 / 恢复测试 ├── vml_test.v # ★ 无头 VML 校验:不开窗口即可验证界面文档 ├── bin/ │ └── contact.exe # 已编译程序(GUI 子系统,启动无 cmd 窗口) └── data/ # ★ 数据目录:数据库、头像、CSV/JSON 文件都在这 ├── contacts.db # SQLite 数据库(11 条记录,含模拟数据) └── avatars/ # 头像 BMP 目录(avatar_.bmp) ``` ## 功能 | 功能 | 说明 | |---|---| | 增删改查 | 「添加」「更新」「删除」「清空」,字段:姓名、电话、邮箱、分组、备注、收藏 | | 搜索 | 按**姓名 / 电话 / 邮箱 / 备注**实时过滤(不区分大小写) | | 分组筛选 | 6 个筛选片(全部 / 同事 / 客户 / 朋友 / 家人 / 其他),可与搜索、收藏过滤组合 | | 排序 | 「排序」按钮循环切换:添加顺序 → 按姓名 → 按分组 | | **收藏星标** | 「☆ 设为收藏」一键收藏(立即写库),列表行显示 ★,「只看收藏」勾选过滤,统计面板计数 | | **表单校验** | 电话格式(5~24 位数字,可含 `+ - ( ) . 空格`)、邮箱格式、**电话重复检测**(添加 / 更新分别排重),错误显示在红色消息条 | | **删除确认** | 「删除」弹系统模态确认框;删除后自动选中相邻联系人,方便连续整理 | | **消息条分级** | 信息蓝 / 成功绿 / 错误红,背景、边框、文字色同步切换 | | CSV 导出 | 当前列表(含过滤结果)存为 CSV:UTF-8 带 BOM(Excel 中文不乱码),自动引号转义,末列「收藏」;默认保存到 `data\` | | CSV 导入 | 自动剥离 BOM、跳过表头;电话已存在自动跳过(去重);无效行忽略;支持「收藏」列;默认打开 `data\` | | **JSON 备份/恢复** | 「备份 JSON」导出全部字段(含收藏、头像文件名)为带版本号的 JSON;「恢复 JSON」二次确认后**整库替换**;兼容纯数组格式 | | 复制电话 | 「复制电话」把当前联系人号码写入系统剪贴板 | | 统计面板 | 实时显示各分组数量与收藏 / 总数 | | 个人头像 | 「选择图片」(jpg/jpeg/png/bmp)自动缩放为 112×112 BMP 存入 `avatars/`,可移除;删除 / 替换时自动清理孤儿文件 | | 模拟数据 | 首次启动自动写入 10 条中文模拟数据,之后保留 | | 启动体验 | 启动即选中第一条联系人并显示其头像 | ## 构建与运行 ```powershell # 常规构建 v -o bin/contact.exe . # ★ 推荐:GUI 子系统构建(启动时无 cmd 黑色窗口) v -subsystem windows -o bin/contact.exe . # ★ 带应用图标构建(-icon 支持 .ico / .png,V 自动转 ICO 并链接) v -icon app_icon.png -subsystem windows -o bin/contact.exe . # 运行 .\bin\contact.exe # 测试(4 个测试文件,含无头 VML 界面校验) v test . ``` > 编译时 `warning: tcc compilation failed, falling back to gcc` 属正常现象(tcc 对 ui2 生成的 C 代码编译失败时自动回退 gcc),不影响产物。 > ⚠️ **关键**:去掉 cmd 窗口必须用 `-subsystem windows`(V 0.5.2 实测有效,PE 子系统 = 2/GUI)。 > `-d windows_gui` 在这个版本**不生效**。 ## 数据库与数据目录 - **数据目录**:`data\`(位于项目根)。exe 在 `bin\` 子目录时自动定位到项目根 `data\`,exe 单独放置时退化到 exe 同级 `data\` - 数据库:`data\contacts.db`;头像:`data\avatars\avatar_.bmp`;CSV / JSON 导入导出对话框默认打开 `data\` - 表:`ContactRow`(id 字符串主键、name、phone、email、category、notes、avatar、**favorite**) - 旧库自动迁移:启动时检测缺列自动 `ALTER TABLE ... ADD COLUMN`(`avatar TEXT DEFAULT ''` / `favorite INTEGER DEFAULT 0`) - **旧版数据自动迁移**:`paths.v` 的 `ensure_data_migrated()` 在启动时把旧位置(exe 同级 `contacts.db` / `avatars/`)复制到 `data\`(目标已存在则跳过,旧文件保留) ## 技术要点(实测踩坑记录) 1. **无全局变量**:全部状态在 `ContactApp` 结构体(`pub mut` 字段),VML 双向绑定直接读写 `app.xxx`。 2. **ORM 简洁写法**: ```v sql db { create table ContactRow } or {} sql db { insert row into ContactRow } or { return error('insert: ${err}') } sql db { update ContactRow set name = row.name, favorite = row.favorite where id == row.id } or { ... } sql db { delete from ContactRow where id == cid } or { ... } rows := sql db { select from ContactRow } or { return [] } ``` 层次约定:`store.v` 数据层返回 `!`(`sql ... !` 上抛),界面层用 `or {}` 捕获并把错误写进消息条,程序不崩溃。 批量 `insert` 数组会触发 C 代码生成 bug,**逐条循环插入**最稳。 3. **ORM 结构体字段要 `pub mut`**:`pub:`(只读)字段会让 `app.contacts[i].favorite = 1` 报 *field is immutable*;整结构体赋值(`app.contacts[i] = row`)不受影响。需要就地改单个字段就必须 `pub mut`。 4. **增量迁移**:ORM 无自动迁移,`migrate()` 用 `db.columns(table)` 检查缺列后 `db.exec('ALTER TABLE ... ADD COLUMN ... DEFAULT ...')` 补齐,旧库无缝升级。 5. **JSON 用 `json2` 而不是 `json`**:V 0.5.2 **没有 `encoding.json`**(报 not found);`json` 模块可用但已标记 deprecated。用纯 V 的 `json2`: `json2.encode(val, json2.EncoderOptions{ prettify: true })`、`json2.decode[BackupFile](data, json2.DecoderOptions{})!`(泛型参数写成 `[T]`,不要把类型当第一个实参传)。 6. **CSV**:标准库 `encoding/csv`;导出加 `'\uFEFF'` BOM 前缀 Excel 中文不乱码;导入手动剥离 BOM 三字节、识别表头行、电话去重。 7. **字符串主键**:`id string @[primary]`;`time.now().unix_nano()` 在 Windows 分辨率约 1ms,连续调用会重复 → `'${time.now().unix_nano()}-${rand.u64()}'`。 8. **★ 无头 VML 校验测试(强烈推荐)**:`ui2.element_from_vml_model(source, model, rect)!` 只做模板求值 + 元素树构建 + id/key 去重校验,**不打开窗口**,可以放进 `v test`。界面文档写错(字段名、表达式、类型不符)会在测试阶段失败,而不是用户启动时 `panic(err)`。见 `vml_test.v`。 9. **★ Windows 原生 Label 不换行**:`Label`(STATIC 控件)既不自动换行也**不吃 `\n`**,`lines: N` 不起作用;长文案会被截断。拆成多条短 `Label`(或改用 `TextArea`)。本项目统计面板因此拆成 `stats_line1` / `stats_line2` 两个字段。 10. **★ VML 插值里不要嵌套字符串字面量**:`"${item.favorite == 1 ? "★ " : ""}${item.name}"` 这种在 `${}` 内再写 `"..."` 会解析失败。解法:在 V 层预生成展示字符串(`ContactView.display`),Repeater 只输出 `item.display`。 11. **Repeater + 模型字段**:`model: app.rows` + `key: item.id`(标量即可),行内可用 `item.xxx` 与 `index`;选中态等变化要在模型方法里主动调用 `refresh_visible()` 重建展示数组。 12. **Dropdown 触发动作**:`on_change: app.xxx()` 走 Win32 `CBN_SELCHANGE`,选择即回调;`bind.text` 负责把选中项写回模型字段。声明文本与模型不同时 ui2 会自动 `CB_SELECTSTRING` 同步。筛选场景为避免依赖,改用 6 个分组「筛选片」按钮更稳。 13. **确认框**:`ui2.confirm(title, text) bool` 弹系统模态框并阻塞(Windows `MessageBoxW`),在 `on_tap` 处理器里直接调用即可,返回后 ui2 会 `refresh()`。 14. **数组 `index()`**:本版本对常量数组调用 `.index(x) or {}` 报 *does not return an Option*;用 `for k, v in arr` 循环查找最稳。 15. **排序**:`arr.sort(a.name < b.name)` 适合单字段;多字段比较用 `arr.sort_with_compare(fn (a &T, b &T) int {...})`(顶层函数,勿在表达式里混 `||`)。 16. **ui2 Windows 后端已知限制**(实测):Button 必须 `native: true`,否则静默崩溃;native 按钮 / Dropdown 自定义背景色不生效;`-d ui2_custom_rendering` + 中文崩溃;Rectangle / Label / TextField / TextArea / Scroll 自定义颜色正常。 17. **头像 BMP 兼容性**:ui2 的 Image 控件用 Windows `LoadImageW`,**只认非压缩 BI_RGB BMP**(BITMAPFILEHEADER 14B + BITMAPINFOHEADER 40B、32bpp、compression=0)。`stbi_write_bmp` 输出 V4 头 + BI_BITFIELDS 压缩 BMP,加载失败 → 头像不显示。`avatar.v` 手写 `write_bmp_32` 输出标准 BI_RGB 解决。 18. **VML 内嵌**:`contact.vml` 用 `$embed_file('contact.vml').to_string()` 编译进 exe,运行时不依赖外部文件。 ## 验证 - `v test .` —— 4 个测试文件全绿: - 数据层(建库播种 / 增删改 / 主键唯一 / avatar 与 favorite 读写 / 旧库迁移 / 电话排重 / 整库替换) - CSV(往返含逗号引号多行、BOM+表头+去重、无扩展名导出、收藏列往返) - JSON 备份(对象 / 纯数组 / 恢复整库替换) - **无头 VML 校验**(真实模型渲染 + 错误消息 / 筛选 / 空列表等多种状态)+ 过滤排序统计 + 校验函数 - 真实界面验证:窗口 1060×740,左列表(搜索 / 分组筛选片 / 排序 / 只看收藏 / 导入导出 / 计数条)、右表单(头像 / 双列字段 / 收藏 / 消息条 / 统计面板)无重叠、无截断。 ## 模拟数据 | id | 姓名 | 电话 | 分组 | |---|---|---|---| | seed01 | 张伟 | 13800138001 | 同事(带头像演示) | | seed02 | 李娜 | 13900139002 | 同事 | | seed03 | 王芳 | 13700137003 | 客户 | | seed04 | 刘强 | 13600136004 | 朋友 | | seed05 | 陈静 | 13500135005 | 朋友 | | seed06 | 杨志刚 | 13400134006 | 家人 | | seed07 | 赵敏 | 13300133007 | 家人 | | seed08 | 孙磊 | 13200132008 | 同事 | | seed09 | 周婷 | 13100131009 | 客户 | | seed10 | 吴昊 | 13000130010 | 朋友 |