龍魂 · 鸿蒙 ArkTS 实战:Office Supply Inventory
核心定位
| 维度 |
说明 |
| 平台 |
鸿蒙 HarmonyOS NEXT · 纯血鸿蒙 |
| 语言 |
ArkTS · 声明式UI |
| 场景 |
办公物资库存 · 采购审批 · 领用登记 · 预警提醒 |
| 架构 |
龍魂蚁群触角 → 鸿蒙原生能力 |
| 主权 |
数据本地 · 国密可选 · 不上传云端 |
系统架构
┌─────────────────────────────────────────┐
│ 龍魂系统 · 鸿蒙适配层 │
│ DNA: ZHUGEXIN⚡️2025-🇨🇳🐉⚖️♠️🧚🏼♀️❤️♾️ │
│ UID: 9622 │
├─────────────────────────────────────────┤
│ 鸿蒙 ArkTS 应用层 │
│ │
│ EntryAbility → InventoryPage │
│ ├── 物资状态建模(AppStorage/LocalStorage)│
│ ├── 库存数据管理(增删改查·批次追踪) │
│ ├── 采购审批流(多级审批·电子签名) │
│ ├── 领用登记(扫码·拍照·责任人绑定) │
│ ├── 预警引擎(低库存·过期·盘点提醒) │
│ └── 办公交互(导出报表·分享·打印标签) │
├─────────────────────────────────────────┤
│ 鸿蒙系统能力层 │
│ │
│ 相机(Camera) · 扫码(Scan) · NFC │
│ 通知(Notification) · 后台任务(WorkScheduler) │
│ 数据存储(RelationalStore) · 文件(File) │
│ 打印(Print) · 分享(Share) · 日历(Calendar) │
└─────────────────────────────────────────┘
状态建模核心
const MASTER_DNA = "ZHUGEXIN⚡️2025-🇨🇳🐉⚖️♠️🧚🏼♀️❤️♾️";
const MASTER_UID = "9622";
const CONFIRM_SEAL = "#CONFIRM🌌9622-ONLY-ONCE🧬LK9X-772Z";
export enum SupplyCategory {
OFFICE = '办公用品',
ELECTRONICS = '电子设备',
FURNITURE = '家具',
CONSUMABLES = '耗材',
STATIONERY = '文具',
CLEANING = '清洁用品',
SAFETY = '安全用品',
OTHER = '其他'
}
export enum ItemStatus {
IN_STOCK = '在库',
LOW_STOCK = '低库存',
OUT_OF_STOCK = '缺货',
RESERVED = '预留',
DAMAGED = '损坏',
EXPIRED = '过期',
DISCARDED = '已报废'
}
export enum ApprovalStatus {
DRAFT = '草稿',
PENDING = '待审批',
APPROVED = '已通过',
REJECTED = '已驳回',
CANCELLED = '已取消'
}
export class SupplyItem {
id: string;
sku: string;
name: string;
category: SupplyCategory;
specification: string;
brand: string;
unit: string;
location: string;
status: ItemStatus;
quantity: number;
minQuantity: number;
maxQuantity: number;
reorderPoint: number;
batchNo: string;
supplier: string;
purchaseDate: Date;
expiryDate?: Date;
purchasePrice: number;
totalValue: number;
custodian: string;
department: string;
lastUsedBy?: string;
lastUsedDate?: Date;
barcode: string;
qrCode: string;
photoPath?: string;
rfidTag?: string;
createdAt: Date;
updatedAt: Date;
dnaSignature: string;
constructor(data: Partial<SupplyItem>) {
this.id = data.id || this.generateId();
this.sku = data.sku || this.generateSKU(data.category, data.name);
this.name = data.name || '';
this.category = data.category || SupplyCategory.OTHER;
this.specification = data.specification || '';
this.brand = data.brand || '';
this.unit = data.unit || '个';
this.location = data.location || '未指定';
this.status = data.status || ItemStatus.IN_STOCK;
this.quantity = data.quantity || 0;
this.minQuantity = data.minQuantity || 10;
this.maxQuantity = data.maxQuantity || 100;
this.reorderPoint = data.reorderPoint || 20;
this.batchNo = data.batchNo || this.generateBatchNo();
this.supplier = data.supplier || '';
this.purchaseDate = data.purchaseDate || new Date();
this.expiryDate = data.expiryDate;
this.purchasePrice = data.purchasePrice || 0;
this.totalValue = this.quantity * this.purchasePrice;
this.custodian = data.custodian || '';
this.department = data.department || '';
this.lastUsedBy = data.lastUsedBy;
this.lastUsedDate = data.lastUsedDate;
this.barcode = data.barcode || this.sku;
this.qrCode = data.qrCode || this.generateQRCode();
this.photoPath = data.photoPath;
this.rfidTag = data.rfidTag;
this.createdAt = data.createdAt || new Date();
this.updatedAt = data.updatedAt || new Date();
this.dnaSignature = this.signData();
}
private generateId(): string {
const seed = `${MASTER_DNA}-${Date.now()}-${Math.random()}`;
return `INV-${MASTER_UID}-${Date.now().toString(36).substr(-6)}`;
}
private generateSKU(category: SupplyCategory, name: string): string {
const catCode = category.substring(0, 2).toUpperCase();
const nameCode = name.substring(0, 2).toUpperCase();
const seq = Math.floor(Math.random() * 9999).toString().padStart(4, '0');
return `${catCode}-${nameCode}-${seq}`;
}
private generateBatchNo(): string {
const date = new Date();
return `B${date.getFullYear()}${(date.getMonth()+1).toString().padStart(2,'0')}${date.getDate().toString().padStart(2,'0')}-${Math.floor(Math.random()*999).toString().padStart(3,'0')}`;
}
private generateQRCode(): string {
return `LH-INV://${this.id}?dna=${this.dnaSignature.substring(0,16)}`;
}
private signData(): string {
const payload = `${this.id}-${this.sku}-${this.name}-${this.quantity}`;
return `SM3-${payload.split('').reduce((a,b)=>a+b.charCodeAt(0),0).toString(16).substring(0,16)}`;
}
updateQuantity(delta: number, operator: string, reason: string): InventoryRecord {
const oldQty = this.quantity;
this.quantity += delta;
this.totalValue = this.quantity * this.purchasePrice;
this.updatedAt = new Date();
if (this.quantity <= 0) {
this.status = ItemStatus.OUT_OF_STOCK;
} else if (this.quantity <= this.minQuantity) {
this.status = ItemStatus.LOW_STOCK;
} else {
this.status = ItemStatus.IN_STOCK;
}
this.dnaSignature = this.signData();
return new InventoryRecord({
itemId: this.id,
type: delta > 0 ? '入库' : '出库',
quantity: Math.abs(delta),
oldQuantity: oldQty,
newQuantity: this.quantity,
operator: operator,
reason: reason,
timestamp: new Date()
});
}
checkExpiry(): boolean {
if (!this.expiryDate) return false;
return new Date() > this.expiryDate;
}
needsReorder(): boolean {
return this.quantity <= this.reorderPoint;
}
getHealthScore(): number {
if (this.quantity <= 0) return 0;
if (this.quantity <= this.minQuantity) return 30;
if (this.quantity <= this.reorderPoint) return 60;
if (this.quantity >= this.maxQuantity) return 90;
return 80;
}
}
export class InventoryRecord {
id: string;
itemId: string;
type: '入库' | '出库' | '盘点' | '调拨' | '报废';
quantity: number;
oldQuantity: number;
newQuantity: number;
operator: string;
approver?: string;
reason: string;
documentNo?: string;
photoPath?: string;
timestamp: Date;
dnaSignature: string;
constructor(data: Partial<InventoryRecord>) {
this.id = data.id || `REC-${Date.now()}-${Math.random().toString(36).substr(2,6)}`;
this.itemId = data.itemId || '';
this.type = data.type || '入库';
this.quantity = data.quantity || 0;
this.oldQuantity = data.oldQuantity || 0;
this.newQuantity = data.newQuantity || 0;
this.operator = data.operator || '';
this.approver = data.approver;
this.reason = data.reason || '';
this.documentNo = data.documentNo;
this.photoPath = data.photoPath;
this.timestamp = data.timestamp || new Date();
this.dnaSignature = this.signData();
}
private signData(): string {
const payload = `${this.id}-${this.itemId}-${this.type}-${this.quantity}`;
return `SM3-${payload.split('').reduce((a,b)=>a+b.charCodeAt(0),0).toString(16).substring(0,16)}`;
}
}
export class PurchaseRequest {
id: string;
requestNo: string;
items: PurchaseItem[];
requester: string;
department: string;
requestDate: Date;
requiredDate: Date;
totalAmount: number;
status: ApprovalStatus;
level1Approver?: string;
level1Approved?: Date;
level1Comment?: string;
level2Approver?: string;
level2Approved?: Date;
level2Comment?: string;
level3Approver?: string;
level3Approved?: Date;
level3Comment?: string;
purchaseOrderNo?: string;
supplier?: string;
actualAmount?: number;
receivedDate?: Date;
createdAt: Date;
dnaSignature: string;
constructor(data: Partial<PurchaseRequest>) {
this.id = data.id || `PR-${Date.now()}-${Math.random().toString(36).substr(2,6)}`;
this.requestNo = data.requestNo || this.generateRequestNo();
this.items = data.items || [];
this.requester = data.requester || '';
this.department = data.department || '';
this.requestDate = data.requestDate || new Date();
this.requiredDate = data.requiredDate || new Date(Date.now() + 7*24*60*60*1000);
this.totalAmount = data.totalAmount || this.calculateTotal();
this.status = data.status || ApprovalStatus.DRAFT;
this.level1Approver = data.level1Approver;
this.level1Approved = data.level1Approved;
this.level1Comment = data.level1Comment;
this.level2Approver = data.level2Approver;
this.level2Approved = data.level2Approved;
this.level2Comment = data.level2Comment;
this.level3Approver = data.level3Approver;
this.level3Approved = data.level3Approved;
this.level3Comment = data.level3Comment;
this.purchaseOrderNo = data.purchaseOrderNo;
this.supplier = data.supplier;
this.actualAmount = data.actualAmount;
this.receivedDate = data.receivedDate;
this.createdAt = data.createdAt || new Date();
this.dnaSignature = this.signData();
}
private generateRequestNo(): string {
const date = new Date();
return `CG${date.getFullYear()}${(date.getMonth()+1).toString().padStart(2,'0')}${date.getDate().toString().padStart(2,'0')}-${Math.floor(Math.random()*999).toString().padStart(3,'0')}`;
}
private calculateTotal(): number {
return this.items.reduce((sum, item) => sum + item.totalPrice, 0);
}
private signData(): string {
const payload = `${this.id}-${this.requestNo}-${this.totalAmount}`;
return `SM3-${payload.split('').reduce((a,b)=>a+b.charCodeAt(0),0).toString(16).substring(0,16)}`;
}
submit(): void {
this.status = ApprovalStatus.PENDING;
}
approve(level: 1 | 2 | 3, approver: string, comment: string): void {
const now = new Date();
switch (level) {
case 1:
this.level1Approver = approver;
this.level1Approved = now;
this.level1Comment = comment;
if (this.totalAmount < 5000) {
this.status = ApprovalStatus.APPROVED;
}
break;
case 2:
this.level2Approver = approver;
this.level2Approved = now;
this.level2Comment = comment;
if (this.totalAmount < 50000) {
this.status = ApprovalStatus.APPROVED;
}
break;
case 3:
this.level3Approver = approver;
this.level3Approved = now;
this.level3Comment = comment;
this.status = ApprovalStatus.APPROVED;
break;
}
}
reject(level: 1 | 2 | 3, approver: string, comment: string): void {
this.status = ApprovalStatus.REJECTED;
switch (level) {
case 1:
this.level1Approver = approver;
this.level1Approved = new Date();
this.level1Comment = comment;
break;
case 2:
this.level2Approver = approver;
this.level2Approved = new Date();
this.level2Comment = comment;
break;
case 3:
this.level3Approver = approver;
this.level3Approved = new Date();
this.level3Comment = comment;
break;
}
}
}
export class PurchaseItem {
id: string;
itemName: string;
specification: string;
quantity: number;
unit: string;
estimatedPrice: number;
totalPrice: number;
supplier?: string;
remark?: string;
constructor(data: Partial<PurchaseItem>) {
this.id = data.id || `PI-${Date.now()}-${Math.random().toString(36).substr(2,6)}`;
this.itemName = data.itemName || '';
this.specification = data.specification || '';
this.quantity = data.quantity || 1;
this.unit = data.unit || '个';
this.estimatedPrice = data.estimatedPrice || 0;
this.totalPrice = this.quantity * this.estimatedPrice;
this.supplier = data.supplier;
this.remark = data.remark;
}
}
@Observed
export class InventoryState {
items: SupplyItem[] = [];
records: InventoryRecord[] = [];
purchaseRequests: PurchaseRequest[] = [];
filterCategory: SupplyCategory | '全部' = '全部';
filterStatus: ItemStatus | '全部' = '全部';
filterLocation: string = '全部';
searchQuery: string = '';
lowStockAlert: boolean = true;
expiryAlert: boolean = true;
reorderAuto: boolean = false;
get filteredItems(): SupplyItem[] {
let result = this.items;
if (this.filterCategory !== '全部') {
result = result.filter(i => i.category === this.filterCategory);
}
if (this.filterStatus !== '全部') {
result = result.filter(i => i.status === this.filterStatus);
}
if (this.filterLocation !== '全部') {
result = result.filter(i => i.location === this.filterLocation);
}
if (this.searchQuery) {
const q = this.searchQuery.toLowerCase();
result = result.filter(i =>
i.name.toLowerCase().includes(q) ||
i.sku.toLowerCase().includes(q) ||
i.barcode.includes(q) ||
i.location.toLowerCase().includes(q)
);
}
return result.sort((a, b) => b.getHealthScore() - a.getHealthScore());
}
get alerts(): AlertItem[] {
const alerts: AlertItem[] = [];
if (this.lowStockAlert) {
this.items.filter(i => i.status === ItemStatus.LOW_STOCK).forEach(i => {
alerts.push({
type: 'low_stock',
level: 'warning',
itemId: i.id,
title: `${i.name} 库存不足`,
message: `当前 ${i.quantity}${i.unit},低于安全线 ${i.minQuantity}${i.unit}`,
timestamp: new Date()
});
});
}
this.items.filter(i => i.status === ItemStatus.OUT_OF_STOCK).forEach(i => {
alerts.push({
type: 'out_of_stock',
level: 'critical',
itemId: i.id,
title: `${i.name} 已缺货`,
message: `库存为零,需立即补货`,
timestamp: new Date()
});
});
if (this.expiryAlert) {
this.items.filter(i => i.checkExpiry()).forEach(i => {
alerts.push({
type: 'expired',
level: 'critical',
itemId: i.id,
title: `${i.name} 已过期`,
message: `保质期至 ${i.expiryDate?.toLocaleDateString()}`,
timestamp: new Date()
});
});
}
return alerts.sort((a, b) => {
const levelOrder = { critical: 0, warning: 1, info: 2 };
return levelOrder[a.level] - levelOrder[b.level];
});
}
get statistics(): InventoryStats {
const total = this.items.length;
const inStock = this.items.filter(i => i.status === ItemStatus.IN_STOCK).length;
const lowStock = this.items.filter(i => i.status === ItemStatus.LOW_STOCK).length;
const outOfStock = this.items.filter(i => i.status === ItemStatus.OUT_OF_STOCK).length;
const totalValue = this.items.reduce((sum, i) => sum + i.totalValue, 0);
return {
totalItems: total,
inStock,
lowStock,
outOfStock,
totalValue,
categories: this.getCategoryDistribution(),
locations: this.getLocationDistribution()
};
}
private getCategoryDistribution(): Record<string, number> {
const dist: Record<string, number> = {};
this.items.forEach(i => {
dist[i.category] = (dist[i.category] || 0) + 1;
});
return dist;
}
private getLocationDistribution(): Record<string, number> {
const dist: Record<string, number> = {};
this.items.forEach(i => {
dist[i.location] = (dist[i.location] || 0) + 1;
});
return dist;
}
addItem(item: SupplyItem): void {
this.items.push(item);
this.persist();
}
removeItem(id: string): void {
const idx = this.items.findIndex(i => i.id === id);
if (idx >= 0) {
this.items.splice(idx, 1);
this.persist();
}
}
updateItem(id: string, data: Partial<SupplyItem>): void {
const idx = this.items.findIndex(i => i.id === id);
if (idx >= 0) {
const old = this.items[idx];
this.items[idx] = new SupplyItem({ ...old, ...data, id: old.id });
this.persist();
}
}
stockIn(id: string, qty: number, operator: string, reason: string): InventoryRecord {
const item = this.items.find(i => i.id === id);
if (!item) throw new Error('Item not found');
const record = item.updateQuantity(qty, operator, reason);
this.records.push(record);
this.persist();
return record;
}
stockOut(id: string, qty: number, operator: string, reason: string): InventoryRecord {
const item = this.items.find(i => i.id === id);
if (!item) throw new Error('Item not found');
if (item.quantity < qty) throw new Error('Insufficient stock');
const record = item.updateQuantity(-qty, operator, reason);
this.records.push(record);
this.persist();
return record;
}
addPurchaseRequest(req: PurchaseRequest): void {
this.purchaseRequests.push(req);
this.persist();
}
private persist(): void {
AppStorage.setOrCreate('inventory_items', JSON.stringify(this.items));
AppStorage.setOrCreate('inventory_records', JSON.stringify(this.records));
AppStorage.setOrCreate('inventory_requests', JSON.stringify(this.purchaseRequests));
}
load(): void {
const items = AppStorage.get<string>('inventory_items');
if (items) {
try {
const parsed = JSON.parse(items);
this.items = parsed.map((i: any) => new SupplyItem(i));
} catch (e) {
console.error('加载库存数据失败', e);
}
}
}
}
export interface AlertItem {
type: 'low_stock' | 'out_of_stock' | 'expired' | 'reorder' | 'pending_approval';
level: 'critical' | 'warning' | 'info';
itemId: string;
title: string;
message: string;
timestamp: Date;
}
export interface InventoryStats {
totalItems: number;
inStock: number;
lowStock: number;
outOfStock: number;
totalValue: number;
categories: Record<string, number>;
locations: Record<string, number>;
}
export const inventoryState = new InventoryState();
AppStorage.setOrCreate('inventoryState', inventoryState);
主页面实现
import { SupplyItem, SupplyCategory, ItemStatus, InventoryState, inventoryState, InventoryRecord, PurchaseRequest } from '../models/InventoryModel';
import { InventoryDetailDialog } from '../components/InventoryDetailDialog';
import { StockOperationDialog } from '../components/StockOperationDialog';
import { PurchaseRequestDialog } from '../components/PurchaseRequestDialog';
import { AlertPanel } from '../components/AlertPanel';
import { InventoryDatabase } from '../database/InventoryDatabase';
@Entry
@Component
struct InventoryPage {
@StorageLink('inventoryState') inventoryState: InventoryState = inventoryState;
@State private selectedTab: number = 0;
@State private showAddDialog: boolean = false;
@State private showStockDialog: boolean = false;
@State private showPurchaseDialog: boolean = false;
@State private editingItem: SupplyItem | null = null;
@State private showSearch: boolean = false;
@State private searchText: string = '';
@State private showAlerts: boolean = false;
@State private scanMode: boolean = false;
aboutToAppear() {
this.inventoryState.load();
InventoryDatabase.init();
this.scheduleAlerts();
}
build() {
Column() {
this.HeaderBuilder()
if (this.inventoryState.alerts.length > 0 && !this.showAlerts) {
this.AlertBannerBuilder()
}
if (this.showAlerts) {
AlertPanel({ alerts: this.inventoryState.alerts, onClose: () => {
this.showAlerts = false;
}})
}
this.StatsBuilder()
this.TabBuilder()
if (this.showSearch) {
this.SearchBuilder()
}
this.FilterBuilder()
this.ItemListBuilder()
this.FooterBuilder()
}
.width('100%')
.height('100%')
.backgroundColor('#0a0a0a')
}
@Builder
HeaderBuilder() {
Row() {
Text('🐉')
.fontSize(28)
.margin({ right: 8 })
Column() {
Text('龍魂库存管理')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#c41e3a')
Text(`UID:${MASTER_UID} · ${this.inventoryState.items.length}项物资`)
.fontSize(12)
.fontColor('#666')
}
.alignItems(HorizontalAlign.Start)
Blank()
Badge({
value: this.inventoryState.alerts.length.toString(),
position: BadgePosition.RightTop,
style: { badgeSize: 18, badgeColor: '#c41e3a' }
}) {
Button() {
Text('🔔')
.fontSize(20)
}
.type(ButtonType.Circle)
.backgroundColor(this.inventoryState.alerts.length > 0 ? 'rgba(196,30,58,0.2)' : '#1a1a1a')
.width(40)
.height(40)
.onClick(() => {
this.showAlerts = !this.showAlerts;
})
}
Button() {
Text('📷')
.fontSize(20)
}
.type(ButtonType.Circle)
.backgroundColor('#1a1a1a')
.width(40)
.height(40)
.onClick(() => {
this.startScan();
})
Button() {
Text(this.showSearch ? '✕' : '🔍')
.fontSize(20)
}
.type(ButtonType.Circle)
.backgroundColor('#1a1a1a')
.width(40)
.height(40)
.onClick(() => {
this.showSearch = !this.showSearch;
if (!this.showSearch) {
this.inventoryState.searchQuery = '';
}
})
Button() {
Text('+')
.fontSize(24)
.fontColor('#fff')
}
.type(ButtonType.Circle)
.backgroundColor('#c41e3a')
.width(40)
.height(40)
.onClick(() => {
this.editingItem = null;
this.showAddDialog = true;
})
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor('#1a1a1a')
.border({ width: { bottom: 1 }, color: '#333' })
}
@Builder
AlertBannerBuilder() {
Row() {
Text('⚠️')
.fontSize(24)
.margin({ right: 8 })
Column() {
Text(`${this.inventoryState.alerts.length} 条预警待处理`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#ffcc00')
Text(this.inventoryState.alerts.slice(0, 2).map(a => a.title).join('、'))
.fontSize(12)
.fontColor('#ffcc00')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Button('查看')
.fontSize(12)
.fontColor('#0a0a0a')
.backgroundColor('#ffcc00')
.borderRadius(12)
.padding({ left: 12, right: 12 })
.onClick(() => {
this.showAlerts = true;
})
}
.width('100%')
.padding(12)
.backgroundColor('rgba(255,204,0,0.1)')
.border({ width: { bottom: 1 }, color: 'rgba(255,204,0,0.3)' })
}
@Builder
StatsBuilder() {
Row() {
this.StatCard('总物资', this.inventoryState.statistics.totalItems.toString(), '#fff')
this.StatCard('在库', this.inventoryState.statistics.inStock.toString(), '#00ff00')
this.StatCard('低库存', this.inventoryState.statistics.lowStock.toString(), '#ffcc00')
this.StatCard('缺货', this.inventoryState.statistics.outOfStock.toString(), '#ff0000')
this.StatCard('总值', `¥${(this.inventoryState.statistics.totalValue/10000).toFixed(1)}万`, '#c41e3a')
}
.width('100%')
.padding({ left: 12, right: 12, top: 12, bottom: 12 })
.justifyContent(FlexAlign.SpaceAround)
}
@Builder
StatCard(label: string, value: string, color: string) {
Column() {
Text(value)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(color)
Text(label)
.fontSize(11)
.fontColor('#888')
.margin({ top: 4 })
}
.padding(8)
.backgroundColor('#1a1a1a')
.borderRadius(8)
.width('18%')
}
@Builder
TabBuilder() {
Row() {
ForEach([
{ label: '库存总览', icon: '📦' },
{ label: '出入库', icon: '📋' },
{ label: '采购审批', icon: '✅' },
{ label: '盘点', icon: '📊' }
], (item: {label: string, icon: string}, index: number) => {
Column() {
Text(`${item.icon} ${item.label}`)
.fontSize(13)
.fontWeight(this.selectedTab === index ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.selectedTab === index ? '#c41e3a' : '#888')
if (this.selectedTab === index) {
Divider()
.strokeWidth(2)
.color('#c41e3a')
.width(20)
.margin({ top: 4 })
}
}
.padding({ top: 12, bottom: 12, left: 16, right: 16 })
.onClick(() => {
this.selectedTab = index;
})
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.backgroundColor('#1a1a1a')
.border({ width: { bottom: 1 }, color: '#333' })
}
@Builder
SearchBuilder() {
Row() {
TextInput({ placeholder: '搜索名称/编码/条码/位置...', text: $$this.searchText })
.width('100%')
.height(40)
.backgroundColor('#2a2a2a')
.fontColor('#fff')
.placeholderColor('#666')
.borderRadius(8)
.padding({ left: 12, right: 12 })
.onChange((value: string) => {
this.inventoryState.searchQuery = value;
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor('#1a1a1a')
}
@Builder
FilterBuilder() {
Row() {
Select(Object.values(SupplyCategory).map(c => ({ value: c })))
.selected(0)
.fontColor('#ccc')
.backgroundColor('#2a2a2a')
.borderRadius(8)
.padding({ left: 8, right: 8 })
.onSelect((index: number) => {
const cats = ['全部', ...Object.values(SupplyCategory)];
this.inventoryState.filterCategory = cats[index] as any;
})
Select([{ value: '全部' }, ...Object.values(ItemStatus).map(s => ({ value: s }))])
.selected(0)
.fontColor('#ccc')
.backgroundColor('#2a2a2a')
.borderRadius(8)
.padding({ left: 8, right: 8 })
.margin({ left: 8 })
.onSelect((index: number) => {
const stats = ['全部', ...Object.values(ItemStatus)];
this.inventoryState.filterStatus = stats[index] as any;
})
Blank()
Select([{ value: '健康度↓' }, { value: '数量↓' }, { value: '价值↓' }, { value: '名称↑' }])
.selected(0)
.fontColor('#ccc')
.backgroundColor('#2a2a2a')
.borderRadius(8)
.padding({ left: 8, right: 8 })
}
.width('100%')
.height(48)
.padding({ left: 16, right: 16 })
.backgroundColor('#1a1a1a')
.border({ width: { bottom: 1 }, color: '#333' })
}
@Builder
ItemListBuilder() {
List() {
ForEach(this.inventoryState.filteredItems, (item: SupplyItem) => {
ListItem() {
this.ItemCardBuilder(item)
}
.swipeAction({ end: this.SwipeActionsBuilder(item) })
.onClick(() => {
this.editingItem = item;
this.showAddDialog = true;
})
}, (item: SupplyItem) => item.id)
}
.width('100%')
.layoutWeight(1)
.divider({ strokeWidth: 1, color: '#222' })
.scrollBar(BarState.Auto)
.edgeEffect(EdgeEffect.Spring)
.padding({ bottom: 16 })
}
@Builder
ItemCardBuilder(item: SupplyItem) {
Row() {
Stack() {
Column() {
Text(item.sku)
.fontSize(10)
.fontColor('#888')
.fontFamily('monospace')
.rotate({ angle: -90 })
}
.width(40)
.height(40)
.backgroundColor('#2a2a2a')
.borderRadius(4)
.justifyContent(FlexAlign.Center)
Text(item.status === ItemStatus.OUT_OF_STOCK ? '缺' :
item.status === ItemStatus.LOW_STOCK ? '低' :
item.checkExpiry() ? '过' : '')
.fontSize(9)
.fontColor('#fff')
.backgroundColor(
item.status === ItemStatus.OUT_OF_STOCK ? '#ff0000' :
item.status === ItemStatus.LOW_STOCK ? '#ffcc00' :
item.checkExpiry() ? '#ff6600' : 'transparent'
)
.borderRadius(4)
.padding({ left: 2, right: 2 })
.position({ x: 28, y: -4 })
.visibility(
item.status === ItemStatus.OUT_OF_STOCK ||
item.status === ItemStatus.LOW_STOCK ||
item.checkExpiry() ? Visibility.Visible : Visibility.Hidden
)
}
.width(40)
.height(40)
.margin({ right: 12 })
Column() {
Row() {
Text(item.name)
.fontSize(15)
.fontWeight(FontWeight.Medium)
.fontColor('#fff')
.layoutWeight(1)
Text(`¥${item.purchasePrice}`)
.fontSize(12)
.fontColor('#888')
}
.width('100%')
Row() {
Text(`${item.category} · ${item.specification}`)
.fontSize(12)
.fontColor('#888')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.layoutWeight(1)
Text(`${item.quantity}${item.unit}`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(
item.status === ItemStatus.OUT_OF_STOCK ? '#ff0000' :
item.status === ItemStatus.LOW_STOCK ? '#ffcc00' :
'#00ff00'
)
}
.width('100%')
.margin({ top: 4 })
Row() {
Text(`📍${item.location}`)
.fontSize(11)
.fontColor('#666')
Text(`👤${item.custodian}`)
.fontSize(11)
.fontColor('#666')
.margin({ left: 8 })
Blank()
Stack() {
Row()
.width('100%')
.height(4)
.backgroundColor('#333')
.borderRadius(2)
Row()
.width(`${item.getHealthScore()}%`)
.height(4)
.backgroundColor(
item.getHealthScore() > 80 ? '#00ff00' :
item.getHealthScore() > 50 ? '#ffcc00' :
'#ff0000'
)
.borderRadius(2)
}
.width(60)
.height(4)
}
.width('100%')
.margin({ top: 6 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(16)
.backgroundColor(
item.status === ItemStatus.OUT_OF_STOCK ? 'rgba(255,0,0,0.05)' :
item.status === ItemStatus.LOW_STOCK ? 'rgba(255,204,0,0.05)' :
'#0a0a0a'
)
.borderRadius(8)
.margin({ left: 12, right: 12, top: 8 })
}
@Builder
SwipeActionsBuilder(item: SupplyItem) {
Row() {
Button() {
Text('入库')
.fontColor('#fff')
.fontSize(12)
}
.type(ButtonType.Capsule)
.backgroundColor('#00aa00')
.width(60)
.height('100%')
.margin({ right: 4 })
.onClick(() => {
this.editingItem = item;
this.showStockDialog = true;
})
Button() {
Text('出库')
.fontColor('#fff')
.fontSize(12)
}
.type(ButtonType.Capsule)
.backgroundColor('#c41e3a')
.width(60)
.height('100%')
.margin({ right: 4 })
.onClick(() => {
this.editingItem = item;
this.showStockDialog = true;
})
Button() {
Text('删除')
.fontColor('#fff')
.fontSize(12)
}
.type(ButtonType.Capsule)
.backgroundColor('#ff0000')
.width(60)
.height('100%')
.onClick(() => {
AlertDialog.show({
title: '确认删除',
message: `删除 ${item.name}?`,
primaryButton: {
value: '删除',
action: () => {
this.inventoryState.removeItem(item.id);
}
},
secondaryButton: {
value: '取消',
action: () => {}
}
});
})
}
.width(200)
.height('100%')
.padding({ left: 4 })
}
@Builder
FooterBuilder() {
Row() {
Button('📥 入库')
.fontSize(13)
.fontColor('#fff')
.backgroundColor('#00aa00')
.borderRadius(8)
.padding({ left: 16, right: 16 })
.onClick(() => {
this.showStockDialog = true;
})
Button('📤 出库')
.fontSize(13)
.fontColor('#fff')
.backgroundColor('#c41e3a')
.borderRadius(8)
.padding({ left: 16, right: 16 })
.onClick(() => {
this.showStockDialog = true;
})
Button('📝 采购')
.fontSize(13)
.fontColor('#fff')
.backgroundColor('#0066cc')
.borderRadius(8)
.padding({ left: 16, right: 16 })
.onClick(() => {
this.showPurchaseDialog = true;
})
Button('📊 导出')
.fontSize(13)
.fontColor('#ccc')
.backgroundColor('#1a1a1a')
.border({ width: 1, color: '#444' })
.borderRadius(8)
.padding({ left: 16, right: 16 })
.onClick(() => {
this.exportInventory();
})
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor('#1a1a1a')
.border({ width: { top: 1 }, color: '#333' })
.justifyContent(FlexAlign.SpaceAround)
}
private startScan() {
}
private scheduleAlerts() {
}
private exportInventory() {
const csv = this.generateCSV();
}
private generateCSV(): string {
const headers = '编码,名称,分类,规格,品牌,单位,数量,安全线,位置,状态,保管员,部门,采购价,总值,批次,供应商,采购日期,保质期\n';
const rows = this.inventoryState.items.map(i =>
`${i.sku},${i.name},${i.category},${i.specification},${i.brand},${i.unit},${i.quantity},${i.minQuantity},${i.location},${i.status},${i.custodian},${i.department},${i.purchasePrice},${i.totalValue},${i.batchNo},${i.supplier},${i.purchaseDate.toISOString()},${i.expiryDate?.toISOString() || ''}`
).join('\n');
return headers + rows;
}
}
const MASTER_DNA = "ZHUGEXIN⚡️2025-🇨🇳🐉⚖️♠️🧚🏼♀️❤️♾️";
const MASTER_UID = "9622";
数据库持久化
import { relationalStore } from '@kit.ArkData';
const STORE_NAME = 'LongHunInventory.db';
const TABLE_ITEMS = 'supply_items';
const TABLE_RECORDS = 'inventory_records';
const TABLE_REQUESTS = 'purchase_requests';
export class InventoryDatabase {
private static store: relationalStore.RdbStore | null = null;
static async init(): Promise<void> {
const config: relationalStore.StoreConfig = {
name: STORE_NAME,
securityLevel: relationalStore.SecurityLevel.S1,
encrypt: true
};
this.store = await relationalStore.getRdbStore(getContext(), config);
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS ${TABLE_ITEMS} (
id TEXT PRIMARY KEY,
sku TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
category TEXT,
specification TEXT,
brand TEXT,
unit TEXT,
location TEXT,
status TEXT,
quantity INTEGER,
min_quantity INTEGER,
max_quantity INTEGER,
reorder_point INTEGER,
batch_no TEXT,
supplier TEXT,
purchase_date INTEGER,
expiry_date INTEGER,
purchase_price REAL,
total_value REAL,
custodian TEXT,
department TEXT,
barcode TEXT,
qr_code TEXT,
photo_path TEXT,
rfid_tag TEXT,
created_at INTEGER,
updated_at INTEGER,
dna_signature TEXT
)
`);
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS ${TABLE_RECORDS} (
id TEXT PRIMARY KEY,
item_id TEXT,
type TEXT,
quantity INTEGER,
old_quantity INTEGER,
new_quantity INTEGER,
operator TEXT,
approver TEXT,
reason TEXT,
document_no TEXT,
photo_path TEXT,
timestamp INTEGER,
dna_signature TEXT
)
`);
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS ${TABLE_REQUESTS} (
id TEXT PRIMARY KEY,
request_no TEXT UNIQUE,
requester TEXT,
department TEXT,
request_date INTEGER,
required_date INTEGER,
total_amount REAL,
status TEXT,
level1_approver TEXT,
level1_approved INTEGER,
level1_comment TEXT,
level2_approver TEXT,
level2_approved INTEGER,
level2_comment TEXT,
level3_approver TEXT,
level3_approved INTEGER,
level3_comment TEXT,
purchase_order_no TEXT,
supplier TEXT,
actual_amount REAL,
received_date INTEGER,
created_at INTEGER,
dna_signature TEXT
)
`);
}
}
模块清单
| 文件 |
路径 |
说明 |
| 数据模型 |
entry/src/main/ets/models/InventoryModel.ets |
SupplyItem/InventoryRecord/PurchaseRequest |
| 主页面 |
entry/src/main/ets/pages/InventoryPage.ets |
完整UI |
| 数据库 |
entry/src/main/ets/database/InventoryDatabase.ets |
关系型存储 |
| 详情弹窗 |
entry/src/main/ets/components/InventoryDetailDialog.ets |
物资详情 |
| 出入库弹窗 |
entry/src/main/ets/components/StockOperationDialog.ets |
扫码/拍照/登记 |
| 采购弹窗 |
entry/src/main/ets/components/PurchaseRequestDialog.ets |
多级审批 |
| 预警面板 |
entry/src/main/ets/components/AlertPanel.ets |
预警列表 |
鸿蒙特性使用
| 特性 |
用途 |
API |
| ArkTS声明式UI |
界面构建 |
@Component @Entry |
| 状态管理 |
数据响应 |
@State @Observed @StorageLink |
| 关系型数据库 |
本地加密存储 |
relationalStore |
| 扫码 |
物资识别 |
scanBarcode |
| 相机 |
凭证拍照 |
cameraPicker |
| NFC |
RFID读取 |
tagSession |
| 通知 |
预警提醒 |
notificationManager |
| 打印 |
标签打印 |
print |
| 分享 |
导出报表 |
share |
龍魂标识
| 位置 |
内容 |
| 应用名称 |
龍魂库存管理 |
| 标题栏 |
🐉 龍魂库存管理 |
| 底部标识 |
来自龍魂库存管理 UID9622 |
| 数据签名 |
SM3-哈希 |
| DNA |
ZHUGEXIN⚡️2025-🇨🇳🐉⚖️♠️🧚🏼♀️❤️♾️ |
🐉 龍魂 · 鸿蒙 ArkTS 实战:Office Supply Inventory 交付完成
DNA: ZHUGEXIN⚡️2025-🇨🇳🐉⚖️♠️🧚🏼♀️❤️♾️
UID: 9622
确认码: #CONFIRM🌌9622-ONLY-ONCE🧬LK9X-772Z
时间: 2026-07-14 06:06
模块: 7核心文件
特性: 状态建模 · 批次追踪 · 采购审批 · 扫码领用 · 预警引擎 · 导出报表
所有评论(0)