当前位置: 首页 > news >正文

【读书笔记-《30天自制操作系统》-23】Day24

本篇内容依然比较简单,主要是优化窗口功能以及开发定时器应用程序。首先是优化窗口的切换功能,实现通过键盘和鼠标切换窗口,然后是实现通过鼠标关闭窗口。接着实现不同窗口输入状态的切换,最后是实现定时器的API与应用程序。
在这里插入图片描述

1. 窗口优化

前面已经完成了窗口的基本功能,现在需要对窗口功能进行进一步优化。首先是增加窗口的切换功能。

1.1 通过按键实现窗口切换

先从简单的开始,实现按下F11后将最下面的窗口放在最上面。F11的按键编码为0x57,只需要在主程序中添加如下代码:

……
if (i == 256 + 0x57 && shtctl->top > 2) 
{	/* F11 */sheet_updown(shtctl->sheets[1], shtctl->top - 1);
}
……

代码很简单,sheets[0]表示背景图层,sheets[1]即为最下层的窗口。top图层为鼠标图层,按下F11键后将最下层的窗口调整到鼠标图层下一层即可(因为不能覆盖鼠标)。
在这里插入图片描述
1.2 通过鼠标实现窗口切换

接下来实现通过鼠标点击实现的窗口切换。鼠标点击画面上的某个地方时,我们需要按照从上到下的顺序判断鼠标的位置落在哪个图层的范围内,并且还需要确保该位置不是透明色区域:

else if (512 <= i && i <= 767) 
{ /* 鼠标数据 */if (mouse_decode(&mdec, i - 512) != 0) {/* 鼠标指针移动 */mx += mdec.x;my += mdec.y;if (mx < 0) {mx = 0;}if (my < 0) {my = 0;}if (mx > binfo->scrnx - 1) {mx = binfo->scrnx - 1;}if (my > binfo->scrny - 1) {my = binfo->scrny - 1;}sheet_slide(sht_mouse, mx, my);if ((mdec.btn & 0x01) != 0) {/* 按下左键 *//* 按照从上到下的顺序寻找鼠标所指向的图层 */for (j = shtctl->top - 1; j > 0; j--) {sht = shtctl->sheets[j];x = mx - sht->vx0;y = my - sht->vy0;if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {sheet_updown(sht, shtctl->top - 1);break;}}}}}

这一部分的逻辑也比较清楚。按照从上到下的顺序寻找鼠标点击的图层,如果鼠标点击的位置处于该图层的范围之内,就将该图层移动到最上面。如果将命令行窗口移动到最上面,则会遮挡其他窗口。不过这时又可以通过F11将下面的窗口切换上来。
在这里插入图片描述

1.3 窗口移动

窗口的切换功能基本完成了,接下来实现窗口的移动功能。之前单独实现了任务A窗口的实现功能,现在有了多个任务窗口,需要重新进行实现。

当鼠标左键点击窗口时,如果点击窗口的标题栏区域,则进入窗口移动模式,使窗口位置随着鼠标指针移动;而放开鼠标左键时,退出窗口移动模式,返回普通模式。实现窗口的移动需要记录鼠标移动的距离,这里添加了两个变量mmx和mmy,用于记录移动之前的坐标,并且规定mmx=-1时不处于窗口移动模式。

……
int j, x, y, mmx = -1, mmy = -1;
struct SHEET *sht = 0;else if (512 <= i && i <= 767) 
{ /* 鼠标数据 */if (mouse_decode(&mdec, i - 512) != 0) {/*按下左键 */mx += mdec.x;my += mdec.y;if (mx < 0) {mx = 0;}if (my < 0) {my = 0;}if (mx > binfo->scrnx - 1) {mx = binfo->scrnx - 1;}if (my > binfo->scrny - 1) {my = binfo->scrny - 1;}sheet_slide(sht_mouse, mx, my);if ((mdec.btn & 0x01) != 0) {/* 按下左键 */if (mmx < 0) {/* 处于通常模式,不处于窗口移动模式 *//* 按照从上到下的顺序寻找鼠标指针所在的图层 */for (j = shtctl->top - 1; j > 0; j--) {sht = shtctl->sheets[j];x = mx - sht->vx0;y = my - sht->vy0;if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {sheet_updown(sht, shtctl->top - 1);if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {mmx = mx;	/* 进入窗口移动模式 */mmy = my;}break;}}}} else {/* 如果处于窗口移动模式 */x = mx - mmx;	/* 计算鼠标移动距离 */y = my - mmy;sheet_slide(sht, sht->vx0 + x, sht->vy0 + y);mmx = mx;	/* 更新移动后窗口坐标*/mmy = my;}} else {/* 没有按下左键 */mmx = -1;	/* 返回通常模式 */}}
}

虽然代码长了一些,但基本没有什么新东西,主要还是利用之前已经实现的内容与熟悉的方法。这样就可以通过鼠标移动窗口,更有操作系统的样子了。

在这里插入图片描述

1.4 用鼠标关闭窗口

有了前面用鼠标切换窗口的基础,用鼠标关闭窗口的实现也就顺理成章了。原理基本一致,只是需要增加判断鼠标点击的位置范围是否在窗口右上角的"X"号,并根据点击结束程序就可以了。

……
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) 
{if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {sheet_updown(sht, shtctl->top - 1);if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {mmx = mx;	mmy = my;}if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {/* 点击"X"按钮 */if (sht->task != 0) {	/* 该窗口是否为应用程序窗口 */cons = (struct CONSOLE *) *((int *) 0x0fec);cons_putstr0(cons, "\nBreak(mouse) :\n");io_cli();	/* 强制结束处理时禁止切换任务 */task_cons->tss.eax = (int) &(task_cons->tss.esp0);task_cons->tss.eip = (int) asm_end_app;io_sti();}}break;}
}
……

在这里插入图片描述
在这里插入图片描述

1.5 切换输入到应用窗口

现在有了多个应用程序窗口,并且像walk这样的应用程序已经可以接受键盘输入了。应该使应用程序的窗口能够切换到输入状态,然后通过键盘进行输入。这里仍然通过tab键来切换多个应用程序窗口,使用key_win变量保存当前处于输入状态的窗口地址。另外如果应用程序的窗口处于输入状态时被关闭,这是就让操作系统自动切换到最上层的窗口。

if (256 <= i && i <= 511) 
{ /* 键盘数据 */
……if (s[0] != 0) { /* 一般字符 */if (key_win == sht_win) {	/* 发送给任务A */if (cursor_x < 128) {/* 显示一个字符,光标后移一位 */s[1] = 0;putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);cursor_x += 8;}} else {	/* 发送至命令行窗口 */fifo32_put(&key_win->task->fifo, s[0] + 256);}}if (i == 256 + 0x0e) {	/* BackSpace键 */if (key_win == sht_win) {	/*  发送给任务A */if (cursor_x > 8) {/* 空格擦除光标后后移一位 */putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);cursor_x -= 8;}} else {	/* 发送至命令行窗口 */fifo32_put(&key_win->task->fifo, 8 + 256);}}if (i == 256 + 0x1c) {	/* Enter */if (key_win != sht_win) {	/* 发送至任务A */fifo32_put(&key_win->task->fifo, 10 + 256);}}if (i == 256 + 0x0f) {	/* Tab */cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);j = key_win->height - 1;if (j == 0) {j = shtctl->top - 1;}key_win = shtctl->sheets[j];cursor_c = keywin_on(key_win, sht_win, cursor_c);}
……
}

key_winon与key_winoff两个函数用于控制串口标题栏的颜色与任务A窗口的光标:

int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x)
{change_wtitle8(key_win, 0);if (key_win == sht_win) {cur_c = -1; /* 删除光标 */boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cur_x, 28, cur_x + 7, 43);} else {if ((key_win->flags & 0x20) != 0) {fifo32_put(&key_win->task->fifo, 3); /* 命令行窗口光标OFF */}}return cur_c;
}int keywin_on(struct SHEET *key_win, struct SHEET *sht_win, int cur_c)
{change_wtitle8(key_win, 1);if (key_win == sht_win) {cur_c = COL8_000000; /* 显示光标 */} else {if ((key_win->flags & 0x20) != 0) {fifo32_put(&key_win->task->fifo, 2); /* 命令行窗口光标ON */}}return cur_c;
}

通过tab键可以实现窗口输入的切换:
在这里插入图片描述

1.6 鼠标切换输入窗口

实现鼠标的窗口输入切换,只需要再增加一点改动:

……
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) 
{if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {sheet_updown(sht, shtctl->top - 1);/*鼠标点击的图层不是当前输入的图层,切换*/if (sht != key_win) {cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);key_win = sht;cursor_c = keywin_on(key_win, sht_win, cursor_c);}if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {mmx = mx;	mmy = my;}
……}
}

2. 定时器API与应用程序

2.1 定时器应用程序

为了编写定时器的应用程序,先编写了定时器的API:

_api_alloctimer:	; int api_alloctimer(void);MOV		EDX,16INT		0x40RET_api_inittimer:		; void api_inittimer(int timer, int data);PUSH	EBXMOV		EDX,17MOV		EBX,[ESP+ 8]		; timerMOV		EAX,[ESP+12]		; dataINT		0x40POP		EBXRET_api_settimer:		; void api_settimer(int timer, int time);PUSH	EBXMOV		EDX,18MOV		EBX,[ESP+ 8]		; timerMOV		EAX,[ESP+12]		; timeINT		0x40POP		EBXRET_api_freetimer:		; void api_freetimer(int timer);PUSH	EBXMOV		EDX,19MOV		EBX,[ESP+ 8]		; timerINT		0x40POP		EBXRET
……
else if (edx == 16) 
{reg[7] = (int) timer_alloc();
} 
else if (edx == 17) 
{timer_init((struct TIMER *) ebx, &task->fifo, eax + 256);
} 
else if (edx == 18) 
{timer_settime((struct TIMER *) ebx, eax);
} 
else if (edx == 19) 
{timer_free((struct TIMER *) ebx);
}
……

分别实现了获取定时器、设置定时器的发送数据、设置定时器时间、释放定时器的API。应用程序如下:

#include <stdio.h>int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
int api_getkey(int mode);
int api_alloctimer(void);
void api_inittimer(int timer, int data);
void api_settimer(int timer, int time);
void api_end(void);void HariMain(void)
{char *buf, s[12];int win, timer, sec = 0, min = 0, hou = 0;api_initmalloc();buf = api_malloc(150 * 50);win = api_openwin(buf, 150, 50, -1, "noodle");timer = api_alloctimer();api_inittimer(timer, 128);for (;;) {sprintf(s, "%5d:%02d:%02d", hou, min, sec);api_boxfilwin(win, 28, 27, 115, 41, 7 /* 白色*/);api_putstrwin(win, 28, 27, 0 /* 黑色 */, 11, s);api_settimer(timer, 100);	/* 1秒 */if (api_getkey(1) != 128) {break;}sec++;if (sec == 60) {sec = 0;min++;if (min == 60) {min = 0;hou++;}}}api_end();
}

实现的效果就是显示时间:
在这里插入图片描述

2.2 取消定时器

定时器超时时,会发送设置好的数据。但如果定时器超时之前应用程序已经退出了,定时器超时时会产生什么效果呢?
运行定时器程序,关闭应用程序。过1秒钟后(产生超时),命令行窗口中会出现一个异常字符。

在这里插入图片描述

为了解决这种异常的情况,我们需要再应用程序关闭后取消定时器。

首先编写用于取消指定定时器的函数:

int timer_cancel(struct TIMER *timer)
{int e;struct TIMER *t;e = io_load_eflags();io_cli();	/*设置过程中禁止改变定时器状态 */if (timer->flags == TIMER_FLAGS_USING) {	/* 是否需要取消? */if (timer == timerctl.t0) {/* 取消第一个定时器的处理 */t = timer->next;timerctl.t0 = t;timerctl.next = t->timeout;} else {/* 非第一个定时器的取消处理 *//* 找到timer前的一个定时器 */t = timerctl.t0;for (;;) {if (t->next == timer) {break;}t = t->next;}t->next = timer->next; }timer->flags = TIMER_FLAGS_ALLOC;io_store_eflags(e);return 1;	/* 取消处理成功 */}io_store_eflags(e);return 0; /* 不需要取消处理 */
}

在此基础上,再来编写应用结束时取消定时器的函数。同时需要在定时器上增加一个标记,防止命令行窗口中的光标定时器也被取消:

struct TIMER {struct TIMER *next;unsigned int timeout;char flags, flags2;// 增加标志位struct FIFO32 *fifo;int data;
};

通常情况下将flags2置为0:

struct TIMER *timer_alloc(void)
{int i;for (i = 0; i < MAX_TIMER; i++) {if (timerctl.timers0[i].flags == 0) {timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;timerctl.timers0[i].flags2 = 0;return &timerctl.timers0[i];}}return 0; 
}

对于应用程序申请的定时器,将flags2置为1:

else if (edx == 16) 
{reg[7] = (int) timer_alloc();((struct TIMER *) reg[7])->flags2 = 1;	
}

这样就可以在程序结束取消不需要的定时器了:

void timer_cancelall(struct FIFO32 *fifo)
{int e, i;struct TIMER *t;e = io_load_eflags();io_cli();	for (i = 0; i < MAX_TIMER; i++) {t = &timerctl.timers0[i];if (t->flags != 0 && t->flags2 != 0 && t->fifo == fifo) {timer_cancel(t);timer_free(t);}}io_store_eflags(e);return;
}

运行定时器程序并关闭,超时也不会再出现异常字符了。
在这里插入图片描述
本篇的内容逻辑清楚,代码简单,但却使操作系统看起来更像样了。下一篇继续优化命令行窗口,敬请期待。

相关文章:

【读书笔记-《30天自制操作系统》-23】Day24

本篇内容依然比较简单&#xff0c;主要是优化窗口功能以及开发定时器应用程序。首先是优化窗口的切换功能&#xff0c;实现通过键盘和鼠标切换窗口&#xff0c;然后是实现通过鼠标关闭窗口。接着实现不同窗口输入状态的切换&#xff0c;最后是实现定时器的API与应用程序。 1.…...

XML:DOM4j解析XML

XML简介&#xff1a; 什么是XML&#xff1a;XML 是独立于软件和硬件的信息传输工具。 XML 的设计宗旨是传输数据&#xff0c;而不是显示数据。XML 标签没有被预定义。您需要自行定义标签。XML不会做任何事情&#xff0c;XML被设计用来结构化、存储以及传输信息。 XML可以发明…...

15.5 创建监控控制平面的service

本节重点介绍 : k8s中service的作用和类型创建k8s控制平面的service 给prometheus采集用&#xff0c; 类型clusterIp kube-schedulerkube-controller-managerkube-etcd service的作用 Kubernetes Service定义了这样一种抽象&#xff1a; Service是一种可以访问 Pod逻辑分组…...

【Docker Nexus3】maven 私库

1.部署环境 window 11 x64Docker Desktop 4.34.1 (166053) Docker Engine v27.2.0 1.1.Docker 镜像源 1.1.1.Docker Engine 配置 {"builder": {"features": {"buildkit": true},"gc": {"defaultKeepStorage": "32…...

Docker本地部署Chatbot Ollama搭建AI聊天机器人并实现远程交互

文章目录 前言1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 前言 本文主要分享如何在群晖NAS本地部署并运行一个基于大语言模型Llama 2的个人本地聊天机器人并结合内网穿透工具…...

MySQL:用户管理

添加用户 create user usernamelocalhost identified by user_password;删除用户 drop user usernamelocalhost;查看所有用户 输入格式 select user,host from mysql.user; 输出 mysql> select user,host from mysql.user; ----------------------------- | user …...

论文《Mixture of Weak Strong Experts on Graphs》笔记

【Mowst 2024 ICLR】论文提出了一种新的图神经网络架构&#xff0c;称为Mixture of weak and strong experts&#xff08;Mowst&#xff09;&#xff0c;通过将轻量级的多层感知机&#xff08;MLP&#xff09;作为弱专家和现成的GNN作为强专家相结合&#xff0c;以处理图中的节…...

【诉讼流程-健身房-违约-私教课-诉讼书提交流程-民事诉讼-自我学习-铺平通往法律的阶梯-讲解(3)】

【诉讼流程-健身房-违约-私教课-诉讼书提交流程-民事诉讼-自我学习-铺平通往法律的阶梯-讲解&#xff08;3&#xff09;】 1、前言说明2、流程说明3、现场提交&#xff08;线下&#xff09;4、网上提交1-起诉书样例2-起诉书编写&#xff08;1&#xff09;原告信息&#xff1a;&…...

数据结构(Day14)

一、学习内容 结构体 概念 引入&#xff1a;定义整数赋值为10 int a10; 定义小数赋值为3.14 float b3.14; 定义5个整数并赋值 int arr[5] {1 , 2 , 3 , 4 ,5}; 定义一个学生并赋值学号姓名成绩 定义一个雪糕并赋值名称产地单价 问题&#xff1a;没有学生、雪糕 数据类型 解决&…...

Paragon NTFS for Mac和Tuxera NTFS for Mac,那么两种工具有什么区别呢?

我们在使用Mac系统读取U盘的过程中往往会遇到一个问题&#xff0c;那就是U盘插进电脑无法显示&#xff0c;或者只能读取不能编辑。出现这种情况的原因就一般是格式错误。 很多小伙伴在解决这种问题的时候会选择使用U盘读写工具&#xff0c;那么哪一种读写工具比较好呢&#xf…...

HashTable结构体数组实现

写了个哈希表&#xff0c;底层逻辑基于结构体数组&#xff0c;核心结构:HashNode,结构外壳:HashTable_R,冲突处理以后会加的(QwQ)~ 目前代码&#xff1a; #ifndef PYIC_X #define PYIC_Xunsigned int PYIC_Hash(unsigned int Val, unsigned int Mov) {unsigned int Ht[4] { …...

Python 管理 AWS ElastiCache 告警

在 AWS 环境中,监控和管理 ElastiCache 集群的性能是至关重要的。本文将介绍如何使用 Python 和 AWS SDK (boto3) 来自动创建和删除 ElastiCache 集群的 CloudWatch 告警。我们将分两部分来讨论:创建告警和删除告警。 第一部分:创建 ElastiCache 告警 首先,让我们看看如何…...

【无人机设计与控制】四旋翼无人机俯仰姿态保持模糊PID控制(带说明报告)

摘要 为了克服常规PID控制方法在无人机俯仰姿态控制中的不足&#xff0c;本研究设计了一种基于模糊自适应PID控制的控制律。通过引入模糊控制器&#xff0c;实现了对输入输出论域的优化选择&#xff0c;同时解决了模糊规则数量与控制精度之间的矛盾。仿真结果表明&#xff0c;…...

[数据集][目标检测]不同颜色的安全帽检测数据集VOC+YOLO格式7574张5类别

重要说明&#xff1a;数据集里面有2/3是增强数据集&#xff0c;请仔细查看图片预览&#xff0c;确认符合要求在下载&#xff0c;分辨率均为640x640 数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件…...

确保 App 跟踪透明度权限:Flutter 中的实践

确保 App 跟踪透明度权限&#xff1a;Flutter 中的实践 在数字广告领域&#xff0c;用户隐私保护已成为一个重要议题。随着 iOS 14 的发布&#xff0c;Apple 引入了 App Tracking Transparency (ATT) 框架&#xff0c;要求开发者在跟踪用户行为以提供个性化广告之前必须获得用…...

李沐 过拟合和欠拟合【动手学深度学习v2】

模型容量 模型容量的影响 估计模型容量 难以在不同的种类算法之间比较,例如树模型和神经网络 给定一个模型种类,将有两个主要因素: 参数的个数参数值的选择范围 VC维...

Go Testify学习与使用

文章目录 1. **安装 Testify**2. **Testify 的主要功能**3. **Testify 使用示例**3.1 断言 (Assertions)3.2 使用 require3.3 使用 Mock3.4 测试套件 (Test Suites) 4. **Testify 的其他功能**5. **总结**常用资源 Testify 是 Go 语言中一个常用的单元测试库&#xff0c;它为标…...

Word中引用参考文献和公式编号的方法

文章目录 应用参考文献对于单个文献引用多于多个文献同时引用 公式编号手动编号自动编号 参考&#xff1a; 应用参考文献 对于单个文献引用 word中的参考文献用交叉应用实现。 首先&#xff0c;将参考文献编号&#xff1a; 然后&#xff0c;在需要引用的地方用交叉引用插入…...

[数据集][目标检测]智慧养殖场肉鸡目标检测数据集VOC+YOLO格式3548张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3548 标注数量(xml文件个数)&#xff1a;3548 标注数量(txt文件个数)&#xff1a;3548 标注…...

Vision Transform—用于大规模图像分类的Transformers架构

VIT — 用于大规模图像识别的 Transformer 论文题目&#xff1a;AN IMAGE IS WORTH 16X16 WORDS:TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE。 官方代码&#xff1a;https://github.com/google-research/vision_transformer 引言与概述 Vision Transformer&#xff08;ViT&…...

Rydberg原子量子门实现原理与优化技术

1. Rydberg原子平台中的量子门实现基础1.1 Rydberg原子特性与量子计算优势Rydberg原子是指外层电子被激发到高主量子数能级的原子态&#xff0c;这类原子具有三个关键特性使其成为量子计算的理想平台&#xff1a;强偶极-偶极相互作用&#xff1a;当两个原子同时处于Rydberg态时…...

别再只用Service了!ROS1 Action通信保姆级教程:从导航进度条到任务取消,手把手教你实现带反馈的机器人任务

别再只用Service了&#xff01;ROS1 Action通信保姆级教程&#xff1a;从导航进度条到任务取消&#xff0c;手把手教你实现带反馈的机器人任务当你的机器人正在执行一个长达10分钟的导航任务时&#xff0c;突然发现目标点设置错误&#xff0c;这时候如果只能干等着任务完成或者…...

网易云音乐NCM转MP3终极指南:ncmdump工具完整使用教程

网易云音乐NCM转MP3终极指南&#xff1a;ncmdump工具完整使用教程 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你是否曾经从网易云音乐下载了心爱的歌曲&#xff0c;却发现只能在特定播放器上收听&#xff1f;NCM格式的限制让音乐…...

iPaaS 应用场景深度解析:从系统孤岛到数据自由流动的六大实战路径

写在前面 一个企业的数字化程度越高&#xff0c;系统就越多。系统越多&#xff0c;集成问题就越严重。 这不是假设&#xff0c;而是我们在服务客户过程中反复验证的结论——企业数字化转型的瓶颈&#xff0c;往往不在于"造新系统"&#xff0c;而在于"连老系统&q…...

【UniApp小程序开发】解决无法使用Vue自定义指令的完美替代方案:权限组件封装

在 UniApp 开发中&#xff0c;你是否遇到过这样的困惑&#xff1a;明明在 Vue Web 项目中用得顺手的 v-permission 自定义指令&#xff0c;一到小程序端就完全失效&#xff1f;本文将深入剖析其原因&#xff0c;并提供一套可直接复用的组件化解决方案&#xff0c;让你在小程序中…...

Windows Cleaner深度解析:5大核心模块彻底解决系统空间不足问题

Windows Cleaner深度解析&#xff1a;5大核心模块彻底解决系统空间不足问题 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服&#xff01; 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner Windows Cleaner是一款完全免费开源的…...

AI圈内火热的Agent、MCP、Skill、CLI是啥?用装修房子讲透,看完秒懂

本文用装修房子的比喻&#xff0c;详细解释了AI领域的四个核心概念&#xff1a;Agent如同会自主规划任务的私人助理&#xff1b;MCP是AI与外部工具数据的统一接口&#xff0c;类似USB-C&#xff1b;Skill是指导AI按标准操作执行的手册&#xff1b;CLI则是不依赖图形界面的命令行…...

Gazebo Sim多旋翼控制:四轴飞行器动力学建模与PID调参

Gazebo Sim多旋翼控制&#xff1a;四轴飞行器动力学建模与PID调参 【免费下载链接】gz-sim Open source robotics simulator. The latest version of Gazebo. 项目地址: https://gitcode.com/gh_mirrors/gz/gz-sim Gazebo Sim是一款功能强大的开源机器人模拟器&#xff…...

告别漫长等待:UE5.2.1 Windows打包效率优化与插件问题排查指南

告别漫长等待&#xff1a;UE5.2.1 Windows打包效率优化与插件问题排查指南第一次点击"打包项目"按钮时&#xff0c;进度条仿佛被冻结的场景&#xff0c;每个UE5开发者都经历过。尤其当项目规模达到数十GB时&#xff0c;等待时间可能超过一小时——这背后隐藏着引擎底…...

对比按量计费与Token Plan套餐的实际成本差异

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 对比按量计费与Token Plan套餐的实际成本差异 在构建和运营基于大模型的应用时&#xff0c;成本控制是一个核心的工程考量。Taotok…...