两份代码哪里不一样?在鸿蒙电脑上搭建差异工作台
·
逐行来回切换两个文件很容易漏掉修改,差异工具需要让文件选择、差异导航和修改摘要保持一致。
界面可以先稳定下来,但真正决定功能是否成立的是文件条目、差异块、搜索索引、选中ID和保存状态。使用稳定差异ID组织左右文本和当前选中项,并让窗口变化只影响排列方式。
这次实现先收住三条主线:
- 读取并规范化文本,明确数据从哪里开始;
- 建立稳定ID,让主要操作真正改变状态;
- 按窗口宽度重排,补齐设备形态或退出路径。
两份文本先统一换行规则
页面重建不可怕,无法恢复对象才麻烦。围绕文件条目、差异块、搜索索引、选中ID和保存状态建立模型,就是为了避开筛选差异后保留旧索引,会让右侧跳到不相关的代码块。
export interface WorkspaceItem {
id: string
title: string
detail: string
keywords: string[]
updatedAt: number
}
export interface WorkspaceState {
query: string
selectedId: string
draft: string
saved: boolean
}
export class FeatureController {
private items: WorkspaceItem[] = [
{ id: 'a', title: '双文件差异工作台', detail: '当前工作区', keywords: ['workspace', 'current'], updatedAt: 3 },
{ id: 'b', title: '交互说明', detail: '实现记录', keywords: ['design', 'arkui'], updatedAt: 2 },
{ id: 'c', title: '导出结果', detail: '最近生成', keywords: ['output', 'result'], updatedAt: 1 }
差异块生成稳定锚点
交互层只发送动作意图,建立稳定ID所需的边界与返回值由控制器给出。
]
private state: WorkspaceState = { query: '', selectedId: 'a', draft: '', saved: true }
search(query: string): WorkspaceItem[] {
this.state.query = query
const keyword: string = query.trim().toLowerCase()
if (keyword.length === 0) {
return this.items.map((item: WorkspaceItem) => ({ id: item.id, title: item.title, detail: item.detail, keywords: item.keywords, updatedAt: item.updatedAt } as WorkspaceItem))
}
return this.items.filter((item: WorkspaceItem) => {
const indexText: string = (item.title + ' ' + item.detail + ' ' + item.keywords.join(' ')).toLowerCase()
return indexText.includes(keyword)
})
}
select(id: string): WorkspaceItem | undefined {
const target: WorkspaceItem | undefined = this.items.find((item: WorkspaceItem) => item.id === id)
if (target) {
this.state.selectedId = target.id
}
建立稳定ID完成后,控制器把结果写回文件条目、差异块、搜索索引、选中ID和保存状态,视图只接收本次动作产生的新状态。
搜索只过滤导航不改结果
页面把模型转换成清楚的层级:左侧文件导航,中间双文件内容,右侧显示差异属性与统计。
import { FeatureController } from '../features/FeatureController'
interface NavItem { icon: string;
label: string;
count: string }
@Entry
@Component
struct Index {
private featureController: FeatureController = new FeatureController()
private nav: NavItem[] = [
{ icon: '◫', label: '工作区', count: '08' }, { icon: '⌘', label: '最近项目', count: '12' },
{ icon: '◇', label: '资源文件', count: '24' }, { icon: '✓', label: '已完成', count: '06' }
]
build() {
Column() {
Row({ space: 12 }) {
Stack() { Rect().width(34).height(34).radius(11).fill('#4669E8');
Text('⌘').fontSize(16).fontColor(Color.White) }
Text('Window Diff').fontSize(19).fontWeight(FontWeight.Bold).fontColor('#EAF0FF')
Text('DESKTOP TOOL').fontSize(12).fontColor('#8D99B3').margin({ left: 8 })
Blank()
Row({ space: 8 }) { Text('⌕').fontSize(18).fontColor('#A9B4CA');
Text('搜索命令或文件').fontSize(14).fontColor('#7F8AA2') }.width(230).padding({ left: 14, right: 14, top: 9, bottom: 9 }).backgroundColor('#222B3F').borderRadius(12)
Text('● 已保存').fontSize(13).fontColor('#65D6B0').margin({ left: 10 })
}.width('100%').height(62).padding({ left: 20, right: 20 }).backgroundColor('#151B2A')
Row() {
Column({ space: 8 }) {
Text('WORKSPACE').fontSize(12).fontWeight(FontWeight.Bold).fontColor('#6F7A91').width('100%').margin({ bottom: 8 })
ForEach(this.nav, (item: NavItem, index: number) => {
Row({ space: 11 }) { Text(item.icon).fontSize(16).fontColor(index === 0 ? Color.White : '#8390A8');
Text(item.label).fontSize(15).fontColor(index === 0 ? Color.White : '#A7B0C1').layoutWeight(1);
Text(item.count).fontSize(12).fontColor('#6F7A91') }.width('100%').padding(11).borderRadius(11).backgroundColor(index === 0 ? '#4669E8' : Color.Transparent)
}, (item: NavItem) => item.label)
Blank()
Column({ space: 5 }) { Text('当前任务').fontSize(12).fontColor('#778298');
Text('Index.ets').fontSize(15).fontWeight(FontWeight.Medium).fontColor('#DDE5F5');
Text('8 处修改').fontSize(13).fontColor('#63C5FF') }.width('100%').padding(12).borderRadius(12).backgroundColor('#20283A')
}.width(188).height('100%').padding(16).backgroundColor('#1A2131')
Column({ space: 14 }) {
Row() { Column({ space: 3 }) { Text('Index.ets').fontSize(26).fontWeight(FontWeight.Bold).fontColor('#20283A');
Text('左右文件、差异导航和修改摘要保持同步').fontSize(14).fontColor('#7D8798') }.alignItems(HorizontalAlign.Start);
Blank();
Button('预览').backgroundColor('#EDF0F6').fontColor('#475269');
Button('导出').backgroundColor('#4669E8').margin({ left: 8 }) }.width('100%')
Row({ space: 14 }) {
Column({ space: 12 }) {
Row() { Text('01').fontSize(13).fontColor('#4669E8');
Text(' INPUT').fontSize(13).fontWeight(FontWeight.Bold).fontColor('#7D8798');
Blank();
Text('8 处修改').fontSize(13).fontColor('#7D8798') }.width('100%')
Column({ space: 9 }) { Rect().width('72%').height(9).radius(5).fill('#CBD4E5');
Rect().width('88%').height(9).radius(5).fill('#D9E0EC');
Rect().width('58%').height(9).radius(5).fill('#4669E8').opacity(0.72);
Rect().width('80%').height(9).radius(5).fill('#D9E0EC') }.width('100%').alignItems(HorizontalAlign.Start)
Divider().color('#E6E9EF')
Row({ space: 8 }) { Text('+').fontSize(16).fontColor('#4669E8');
Text('添加一个工作节点').fontSize(14).fontColor('#69758A') }.width('100%')
Blank()
Row({ space: 8 }) { Text('●').fontSize(12).fontColor('#64CDA8');
Text('结构检查通过').fontSize(13).fontColor('#657086') }.width('100%')
}.layoutWeight(1).height('100%').padding(18).borderRadius(20).backgroundColor(Color.White).border({ width: 1, color: '#E6EAF0' })
Column({ space: 12 }) {
Row() { Text('实时预览').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#273044');
Blank();

窗口变窄时改排列不改选中项
这一部分处理完成之外的结果:取消、过期、不可用与可以重试。
return target
}
updateDraft(value: string): void {
this.state.draft = value
this.state.saved = false
}
save(): boolean {
if (this.state.draft.trim().length === 0) {
return false
}
this.state.saved = true
return true
}
getState(): WorkspaceState {
return { query: this.state.query, selectedId: this.state.selectedId, draft: this.state.draft, saved: this.state.saved }
}
}
离开WindowDiff页面时,当前文件条目、差异块、搜索索引、选中ID和保存状态按会话归属收尾;再次进入后不会叠加旧任务。

WindowDiff的工作区结构
下面是WindowDiff当前使用的目录。pages负责左侧文件导航,中间双文件内容,右侧显示差异属性与统计,features保存本文展示的业务链路。
WindowDiff/
├─ entry/src/main/ets/
│ ├─ pages/Index.ets # 首屏与响应式布局
│ ├─ features/FeatureController.ets # 模型、动作和边界
│ ├─ entryability/EntryAbility.ets # 页面入口与生命周期
│ └─ adapters/ # 真机能力或数据源接口
├─ entry/src/main/resources/ # 颜色、文字和媒体资源
└─ build-profile.json5 # 工程构建配置
Index.ets只组合界面,FeatureController.ets处理文件条目、差异块、搜索索引、选中ID和保存状态。如果后面接入真实设备或网络服务,只需增加适配器并把结果转换成控制器认识的状态。
WindowDiff最终把读取并规范化文本、建立稳定ID和按窗口宽度重排连在同一条状态路径中。
更多推荐




所有评论(0)