张洋 преди 1 ден
родител
ревизия
b357c16fc0

+ 29 - 15
bin/Win32/Debug/zhipuzi_pos_windows/skin/aixuexi_page.xml

@@ -10,14 +10,12 @@
 		<HorizontalLayout>
 			<Control width="1" bkcolor="#FFD1D1D1"></Control>
 			
-            <VerticalLayout name="aixuexi_page_paishe_layout" width="500" padding="0,0,0,0" bordercorner="10,10" bkcolor="#FFFFFFFF" autocalcwidth="true">
-                <Label name="aixuexi_page_xuanzhong_food_name" text="未选中商品" heiht="40" padding="10,10,10,10" font="aixuexi_1"></Label>
+            <VerticalLayout name="aixuexi_page_paishe_layout" width="420" padding="0,0,0,0" bordercorner="10,10" bkcolor="#FFFFFFFF" autocalcwidth="true">
+                <Label name="aixuexi_page_xuanzhong_food_name" text="当前选中商品:无" height="30" padding="10,10,10,10" font="aixuexi_1"></Label>
 
-                <Label name="aixuexi_page_xuanzhong_food_name" wordbreak="true" height="80" text="智铺子AI识别默认就能识别几百种常见的水果蔬菜,这些常见的水果蔬菜无需学习,只有智铺子AI无法正常识别的冷门商品或非水果蔬菜类商品才需要学习。" heiht="60" padding="10,0,10,10" font="3"></Label>
+                <Label name="aixuexi_page_xuanzhong_food_name" wordbreak="true" height="50" padding="10,0,10,10" font="3" text="智铺子AI识别无需学习默认就能识别几百种常见的水果蔬菜,只需要对未正常识别的冷门商品或非水果蔬菜类商品进行学习。"></Label>
 
-                <Control autocalcwidth="true" height="1" bkcolor="#FFEBE8E8" padding="10,0,10,0"></Control>
-
-                <Control name="image" width="480" height="360" bkimage="file='food_image_default.png'" padding="10,0,10,0"></Control>
+                <Control name="image" width="400" height="300" padding="10,0,10,0"></Control>
 
                 <HorizontalLayout height="50" padding="0,20,0,0">
                     <Control></Control>
@@ -25,16 +23,32 @@
                     <Control></Control>
                 </HorizontalLayout>
 
-                <Control></Control>
-
-                
-            </VerticalLayout>
-
-            <Control width="1" bkcolor="#FFD1D1D1"></Control>
+                <Control></Control>                
+            </VerticalLayout> 
 
-            <VerticalLayout name="aixuexi_page_food_layout" padding="15,0,0,0" bordercorner="10,10" bkcolor="#FFFFFFFF">
-
-            </VerticalLayout>    
+            <Control width="1" bkcolor="#FFEBE8E8"></Control>
+			
+			<VerticalLayout name="aixuexi_page_food_layout">
+				<VerticalLayout name="aixuexi_page_fenlei_layout_scrolllayout" vscrollbar="true">
+					<HorizontalLayout name="aixuexi_page_fenlei_layout" padding="0,0,0,0" bkcolor="#FFFFFFFF">
+
+					</HorizontalLayout>
+				</VerticalLayout>				
+				
+				<TileLayout name="aixuexi_page_foodlist" padding="0,0,0,0" inset="10,10,0,10" childpadding="10" vscrollbar="true" hscrollbar="false">
+				
+				</TileLayout>
+				
+				<HorizontalLayout height="70" padding="0,0,0,0" bkcolor="#FFFFFFFF">
+					<Edit name="aixuexi_page_food_search_edit" tooltip="请输入商品名字搜索" width="400" height="50" padding="30,10,0,10" textpadding="50,0,50,0" borderround="45,45" bkcolor="#FFECEFED" nativebkcolor="#FFECEFED" />
+					
+					<Label name="aixuexi_page_food_search_tishi" text="请输入商品名字搜索" width="120" height="35" font="16" float="true" pos="90,18,425,50" mouse="false"></Label>
+					
+					<Control bkimage="search_icon.png" width="25" height="26" pos="45,23,70,49" float="true"></Control>
+					
+					<Button name="aixuexi_page_food_search_clear" visible="false" normalimage="search_clear.png" hotimage="search_clear.png" pushedimage="search_clear.png" bkimage="search_clear.png" width="23" height="23" pos="385,23,408,46" float="true"></Button>
+				</HorizontalLayout>
+			</VerticalLayout>
 		</HorizontalLayout>
 	</AIxuexiPage>
 </Window>

+ 1 - 1
bin/Win32/Debug/zhipuzi_pos_windows/skin/shangpin_page.xml

@@ -43,7 +43,7 @@
 					
 					<Control bkimage="search_icon.png" width="25" height="26" pos="45,23,70,49" float="true"></Control>
 					
-					<Button name="shangpin_food_search_clear" visible="false" normalimage="search_clear.png" hotimage="search_clear.png" pushedimage="search_clear.png" bkimage="search_clear.png" width="23" height="23" pos="385,23,68,46" float="true"></Button>
+					<Button name="shangpin_food_search_clear" visible="false" normalimage="search_clear.png" hotimage="search_clear.png" pushedimage="search_clear.png" bkimage="search_clear.png" width="23" height="23" pos="385,23,408,46" float="true"></Button>
 				</HorizontalLayout>
 			</VerticalLayout>
 		</HorizontalLayout>

+ 68 - 7
zhipuzi_pos_windows/helper/CLewaimaiString.h

@@ -1,10 +1,10 @@
 #pragma once
 
+#include "../pch/pch.h"
+
 #include <codecvt>
 #include <iostream>
 #include <sstream>
-#include <regex>
-#include <string>
 
 class CLewaimaiString
 {
@@ -18,15 +18,12 @@ public:
 	static void trim(std::string &s);
 
 	static std::string UnicodeToUTF8(const std::wstring wstr);
-
 	static std::wstring UTF8ToUnicode(const std::string str);
 
 	static std::string UnicodeToANSI(const std::wstring wstr);
-
 	static std::wstring ANSIToUnicode(const std::string str);
 
 	static std::string UTF8ToANSI(const std::string str);
-
 	static std::string ANSIToUTF8(const std::string str);
 
 	static std::string UnicodeToGB2312(const std::wstring& wstr);
@@ -52,7 +49,7 @@ public:
 	 */
 	static int Replace(std::wstring& strContent, std::wstring strReplace, std::wstring strDest, int nNum = 1);
 	
-	//整个替换所有的字符串,把strBig的strsrc全部替换成strdst
+	//整个替换所有的字符串,把strBig的strsrc全部替换成strdst
 	static void string_replace(std::string &strBig, const std::string &strsrc, const std::string &strdst);
 
 	//从文件路径或者url中获取文件名
@@ -75,4 +72,68 @@ public:
 
 	//判断一个字符串是否是纯数字组成的
 	static bool is_only_number(std::string str);
-};
+
+	//生成随机字符串,长度为len,字符集是大小写字母和数字,生成的字符串在程序运行期间保证唯一
+	static std::string generateRandomString(int len)
+	{
+		// 线程安全的随机数生成器
+		std::lock_guard<std::mutex> lock(mtx);
+
+		// 字符集:大小写字母 + 数字
+		const std::string charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+		std::string result;
+		result.reserve(len);
+
+		// 生成随机字符串
+		for (int i = 0; i < len; ++i)
+		{
+			result += charset[dis(gen) % charset.length()];
+		}
+
+		// 确保字符串唯一性
+		while (used_strings.find(result) != used_strings.end())
+		{
+			// 如果生成的字符串已存在,则重新生成
+			result.clear();
+			for (int i = 0; i < len; ++i)
+			{
+				result += charset[dis(gen) % charset.length()];
+			}
+		}
+
+		// 将新字符串加入已使用集合
+		used_strings.insert(result);
+
+		return result;
+	}
+
+	// 清除已使用的字符串集合(可选)
+	static void clearUsedStrings()
+	{
+		std::lock_guard<std::mutex> lock(mtx);
+		used_strings.clear();
+	}
+
+private:
+	static std::random_device rd;
+	static std::mt19937 gen;
+	static std::uniform_int_distribution<> dis;
+	static std::unordered_set<std::string> used_strings;
+	static std::mutex mtx;
+	static std::atomic<int> counter;
+};
+
+// 静态成员定义
+std::random_device CLewaimaiString::rd;
+std::mt19937 CLewaimaiString::gen(CLewaimaiString::rd());
+std::uniform_int_distribution<> CLewaimaiString::dis(0, 61); // 26+26+10 = 62个字符
+std::unordered_set<std::string> CLewaimaiString::used_strings;
+std::mutex CLewaimaiString::mtx;
+std::atomic<int> CLewaimaiString::counter(0);
+
+// 便捷函数
+std::string generateUniqueRandomString(int len)
+{
+	return CLewaimaiString::generateRandomString(len);
+}

+ 7 - 0
zhipuzi_pos_windows/helper/CSystem.cpp

@@ -57,6 +57,13 @@ std::wstring CSystem::GetProgramDir()
 	return strPath.substr(0, pos);  // Return the directory without the file name
 }
 
+std::wstring CSystem::GetTmpDir()
+{
+	std::wstring ProgramDir = GetProgramDir();
+
+	return ProgramDir + L"\\tmp";
+}
+
 // 程序开机自动启动
 void CSystem::autostart()
 {

+ 2 - 0
zhipuzi_pos_windows/helper/CSystem.h

@@ -17,6 +17,8 @@ public:
 
     static std::wstring GetProgramDir();
 
+	static std::wstring GetTmpDir();
+
     // 程序开机自动启动
     static void autostart();
 

+ 456 - 3
zhipuzi_pos_windows/page/CAIxuexiPageUI.cpp

@@ -1,9 +1,17 @@
 #include "CAIxuexiPageUI.h"
 
+#include "../control/ControlEx.h"
+
 #include "../wnd/CMainWnd.h"
 #include "../worker/CVideoCaptureWorker.h"
 
-#define WM_TIMER_VIDEO_UPDATE 200002
+#include "../tool/CSqlite3.h"
+
+#include "../wnd/CMainWnd.h"
+
+#include "../ai/YoloFeatureManager.h"
+#include "../ai/ImageProcessor.h"
+#include "../ai/SQLiteVecManager.h"
 
 CAIxuexiPageUI::CAIxuexiPageUI()
 {
@@ -25,7 +33,6 @@ CAIxuexiPageUI::~CAIxuexiPageUI()
 	}
 }
 
-
 void CAIxuexiPageUI::InitShow()
 {
 	if (m_isFirstInit)
@@ -34,18 +41,305 @@ void CAIxuexiPageUI::InitShow()
 		
 		std::thread(&CAIxuexiPageUI::HandleUpdateVideo, this).detach();
 	}	
+
+	//先初始化商品分类
+	this->InitFoodtypeShow();
+
+	//再初始化商品显示
+	this->InitFoodShow();
+}
+
+void CAIxuexiPageUI::InitFoodtypeShow()
+{
+	//初始化商品渲染相关的信息
+	m_foodtype_mutex.lock();
+
+	m_foodtypeLayout = static_cast<CHorizontalLayoutUI*>(this->FindSubControl(_T("aixuexi_page_fenlei_layout")));
+	m_foodtypeLayout->RemoveAll();
+
+	CSqlite3 sqlite;
+	m_types = sqlite.GetFoodtypes(); //只包含收银机显示的分类
+
+	if (m_types.size() > 0)
+	{
+		m_cur_type_id = m_types[0].id;
+	}
+
+	m_curFoodtypeOption = NULL;
+
+	//接下来开始处理商品分类
+	for (std::vector<CFoodType>::iterator it = m_types.begin(); it != m_types.end(); it++)
+	{
+		CFoodType type = *it;
+
+		CDialogBuilder builder;
+		CDialogBuilderCallbackEx cb;
+
+		CFoodtypeOptionUI* pItem = static_cast<CFoodtypeOptionUI*>(builder.Create(_T("foodtype_option.xml"), (UINT)0, &cb, m_pManager));
+
+		pItem->SetName(CLewaimaiString::UTF8ToUnicode(type.name));
+		pItem->SetTypeId(type.id);
+		pItem->SetGroup(L"aixuexi_page_foodtype");
+
+		m_foodtypeLayout->Add(pItem);
+
+		if (m_cur_type_id == type.id)
+		{
+			m_curFoodtypeOption = pItem;
+		}
+	}
+
+	m_foodtype_mutex.unlock();
+
+	if (m_curFoodtypeOption != NULL)
+	{
+		m_curFoodtypeOption->SetBkColor(0xFF3CB371);
+	}
+
+	//如果不是一个分类都没有,选中第一个分类
+	if (m_cur_type_id != "")
+	{
+		CFoodtypeOptionUI* curTypeUI = static_cast<CFoodtypeOptionUI*>(m_foodtypeLayout->GetItemAt(0));
+
+		curTypeUI->Selected(true, false);
+	}
+
+	UpdateFoodtypePos();
+}
+
+void CAIxuexiPageUI::UpdateFoodtypePos()
+{
+	int nFoodtypeNum = m_types.size();
+
+	//添加支付方式
+	int nWidth = m_nPageWidth;
+	if (nWidth == 0)
+	{
+		return;
+	}
+
+	//根据宽度计算每行显示的数量
+	int nMeihangNum = (nWidth - 321) / 140;
+
+	int num = 0;
+
+	m_foodtype_mutex.lock();
+
+	CHorizontalLayoutUI* pFenleiLayout = static_cast<CHorizontalLayoutUI*>(this->FindSubControl(_T("aixuexi_page_fenlei_layout")));
+	for (int i = 0; i < nFoodtypeNum; i++)
+	{
+		CButtonUI* curItem = static_cast<CButtonUI*>(pFenleiLayout->GetItemAt(i));
+
+		int curRow = num / nMeihangNum + 1;
+		int curCol = num % nMeihangNum + 1;
+
+		RECT rect;
+		rect.left = (curCol - 1) * 140 + 10;
+		rect.right = rect.left + 130;
+		rect.top = (curRow - 1) * 52 + 10;
+		rect.bottom = rect.top + 42;
+
+		// 强制设置固定大小和位置
+		SIZE size;
+		size.cx = rect.left;
+		size.cy = rect.top;
+		curItem->SetFixedXY(size);
+
+		curItem->SetFixedWidth(rect.right - rect.left);
+		curItem->SetFixedHeight(rect.bottom - rect.top);
+
+		num++;
+	}
+
+	m_foodtype_mutex.unlock();
+
+	//调整区域高度
+	int lastRow = (num - 1) / nMeihangNum + 1;
+	pFenleiLayout->SetFixedHeight(lastRow * 52 + 10);
+
+	//处理滚动条问题
+	CVerticalLayoutUI* pFenleiScrollLayout = static_cast<CVerticalLayoutUI*>(this->FindSubControl(_T("aixuexi_page_fenlei_layout_scrolllayout")));
+	if (lastRow > 2)
+	{
+		lastRow = 2;
+	}
+
+	pFenleiScrollLayout->SetFixedHeight(lastRow * 52 + 10);
+
+	SIZE size;
+	size.cx = 0;
+	size.cy = 0;
+	pFenleiScrollLayout->SetScrollPos(size);
+}
+
+void CAIxuexiPageUI::InitFoodShow()
+{
+	bool is_youtu;
+	if (CSetting::GetInstance()->GetParam("setting_xianshi_is_youtu") == "1")
+	{
+		is_youtu = true;
+	}
+	else
+	{
+		is_youtu = false;
+	}
+
+	m_foodLayout = static_cast<CTileLayoutUI*>(this->FindSubControl(_T("aixuexi_page_foodlist")));
+	m_foodLayout->RemoveAll();
+
+	std::wstring xml_name;
+
+	if (is_youtu)
+	{
+		SIZE itemsize;
+		itemsize.cx = 130;
+		itemsize.cy = 220;
+		m_foodLayout->SetItemSize(itemsize);
+
+		xml_name = _T("shangpin_fooditem.xml");
+	}
+	else
+	{
+		SIZE itemsize;
+		itemsize.cx = 130;
+		itemsize.cy = 90;
+		m_foodLayout->SetItemSize(itemsize);
+
+		xml_name = _T("shangpin_fooditem_wutu.xml");
+	}
+
+	//如果当前一个分类都没有,那么就不处理了
+	if (m_cur_type_id == "")
+	{
+		return;
+	}
+	else if (m_cur_type_id == "sousuo")
+	{
+		if (CLewaimaiString::is_only_number(m_sousuo_foodname))
+		{
+			//说明是纯数字,按商品条码来搜索
+			CSqlite3 sqlite;
+			CFood food;
+
+			bool ret = sqlite.GetFoodByBarcode(m_sousuo_foodname, food);
+
+			if (ret)
+			{
+				CDialogBuilder builder;
+				CDialogBuilderCallbackEx cb;
+
+				CShangpinFoodItemUI* pItem = static_cast<CShangpinFoodItemUI*>(builder.Create(xml_name.c_str(), (UINT)0, &cb, m_pManager));
+
+				pItem->SetYoutuModel(is_youtu);
+				pItem->SetFoodInfo(food);
+				pItem->UpdateShow();
+
+				m_foodLayout->Add(pItem);
+			}
+		}
+		else
+		{
+			//当商品名字来搜索
+			CSqlite3 sqlite;
+			std::vector<CFood> foodlist = sqlite.GetFoodByFoodname(m_sousuo_foodname);
+
+			for (std::vector<CFood>::iterator it = foodlist.begin(); it != foodlist.end(); it++)
+			{
+				CFood food = *it;
+
+				CDialogBuilder builder;
+				CDialogBuilderCallbackEx cb;
+
+				CShangpinFoodItemUI* pItem = static_cast<CShangpinFoodItemUI*>(builder.Create(xml_name.c_str(), (UINT)0, &cb, m_pManager));
+
+				pItem->SetYoutuModel(is_youtu);
+				pItem->SetFoodInfo(food);
+				pItem->UpdateShow();
+
+				m_foodLayout->Add(pItem);
+			}
+		}
+	}
+	else
+	{
+		//选择的是普通商品分类
+		CSqlite3 sqlite;
+		std::vector<CFood> foodlist = sqlite.GetFoodByTypeid(m_cur_type_id);
+
+		for (std::vector<CFood>::iterator it = foodlist.begin(); it != foodlist.end(); it++)
+		{
+			CFood food = *it;
+
+			CDialogBuilder builder;
+			CDialogBuilderCallbackEx cb;
+
+			CShangpinFoodItemUI* pItem = static_cast<CShangpinFoodItemUI*>(builder.Create(xml_name.c_str(), (UINT)0, &cb, m_pManager));
+
+			pItem->SetYoutuModel(is_youtu);
+			pItem->SetFoodInfo(food);
+			pItem->UpdateShow();
+
+			m_foodLayout->Add(pItem);
+		}
+	}
 }
 
 //处理按钮点击类事件
 void CAIxuexiPageUI::HandleClickMsg(TNotifyUI& msg)
 {
+	CDuiString name = msg.pSender->GetName();
+
+	if (name == L"shangpin_fooditem")
+	{
+		CShangpinFoodItemUI* fooditemUI = static_cast<CShangpinFoodItemUI*>(msg.pSender);
+
+		m_cur_click_food_item = fooditemUI;
 
+		this->ClickFoodAction();
+	}
+	else if (name == L"aixuexi_page_food_search_clear")
+	{
+		CEditUI* m_pEdit = static_cast<CEditUI*>(m_pManager->FindControl(_T("aixuexi_page_food_search_edit")));
+		m_pEdit->SetText(L"");
+
+		StopSerachFood();
+	}
+	else if (name == L"aixuexi_page_paishe_btn")
+	{
+		this->DoXuexi();
+	}
 }
 
 
 void CAIxuexiPageUI::HandleSelectChangeMsg(TNotifyUI& msg)
 {
+	CDuiString name = msg.pSender->GetName();
+
+	COptionUI* curOption = static_cast<COptionUI*>(msg.pSender);
+	std::wstring groupname = curOption->GetGroup();
+
+	if (groupname == L"aixuexi_page_foodtype")
+	{
+		//商品分类切换
+		CFoodtypeOptionUI* typeUI = static_cast<CFoodtypeOptionUI*>(curOption);
+		std::string id = typeUI->GetTypeId();
+
+		if (m_cur_type_id != id)
+		{
+			//切换了商品分类
+			m_curFoodtypeOption->SetBkColor(0xFFECECEC);
+
+			msg.pSender->SetBkColor(0xFF3CB371);
 
+			m_curFoodtypeOption = static_cast<CControlUI*>(msg.pSender);
+
+			m_cur_type_id = id;
+
+			this->InitFoodShow();
+
+			return;
+		}
+	}
 }
 
 void CAIxuexiPageUI::HandleItemSelectMsg(TNotifyUI& msg)
@@ -56,7 +350,26 @@ void CAIxuexiPageUI::HandleItemSelectMsg(TNotifyUI& msg)
 
 void CAIxuexiPageUI::HandleTextChangedMsg(TNotifyUI& msg)
 {
+	CDuiString name = msg.pSender->GetName();
+
+	if (name == L"aixuexi_page_food_search_edit")
+	{
+		//商品搜索框的输入事件
+		CEditUI* m_pEdit = static_cast<CEditUI*>(m_pManager->FindControl(_T("aixuexi_page_food_search_edit")));
+		std::wstring ws_Value = m_pEdit->GetText();
+		std::string strValue = CLewaimaiString::UnicodeToUTF8(ws_Value);
 
+		if (strValue.length() == 0)
+		{
+			//搜索词被清空了,退出搜索
+			this->StopSerachFood();
+		}
+		else
+		{
+			//搜索词没清空,进入搜索
+			this->StartSearchFood(strValue);
+		}
+	}
 }
 
 
@@ -102,7 +415,7 @@ void CAIxuexiPageUI::HandleUpdateVideo()
 		}
 
 		//缩放尺寸,适合在界面上展示,过大过小都不好看,这个尺寸是根据界面上image控件的大小来定的,可以根据实际情况调整
-		cv::resize(img, img, cv::Size(480, 360), 0, 0, cv::INTER_LINEAR);
+		cv::resize(img, img, cv::Size(400, 300), 0, 0, cv::INTER_LINEAR);
 
 		// 创建或更新位图
 		HDC hDC = GetDC(NULL);
@@ -156,4 +469,144 @@ void CAIxuexiPageUI::UpdateVideoShow()
 	SelectObject(m_hMemDC, hOldBitmap);
 
 	return;
+}
+
+void CAIxuexiPageUI::ClickFoodAction()
+{
+	//点击普通商品之后的处理逻辑
+	CLabelUI* pLabel = static_cast<CLabelUI*>(this->FindSubControl(_T("aixuexi_page_xuanzhong_food_name")));
+
+	std::string sName = m_cur_click_food_item->GetFoodInfo().name;
+
+	std::wstring wsShow = L"当前选中商品:" + CLewaimaiString::UTF8ToUnicode(sName);
+
+	pLabel->SetText(wsShow.c_str());
+
+	//加载这个商品当前已经学习过的图片,并显示出来
+
+}
+
+//开始搜索某个商品名字
+void CAIxuexiPageUI::StartSearchFood(std::string foodname)
+{
+	//展示删除按钮
+	CButtonUI* pClear = static_cast<CButtonUI*>(this->FindSubControl(_T("aixuexi_page_food_search_clear")));
+	pClear->SetVisible(true);
+
+	//隐藏商品分类展示
+	CVerticalLayoutUI* pFoodtype = static_cast<CVerticalLayoutUI*>(this->FindSubControl(_T("aixuexi_page_fenlei_layout_scrolllayout")));
+	pFoodtype->SetVisible(false);
+
+	if (m_cur_type_id != "sousuo")
+	{
+		m_type_id_before_sousuo = m_cur_type_id;
+	}
+
+	m_cur_type_id = "sousuo";
+
+	m_sousuo_foodname = foodname;
+
+	CLabelUI* pTishi = static_cast<CLabelUI*>(this->FindSubControl(_T("aixuexi_page_food_search_tishi")));
+	pTishi->SetVisible(false);
+
+	this->InitFoodShow();
+}
+
+//停止搜索商品
+void CAIxuexiPageUI::StopSerachFood()
+{
+	//隐藏删除按钮
+	CButtonUI* pClear = static_cast<CButtonUI*>(this->FindSubControl(_T("aixuexi_page_food_search_clear")));
+	pClear->SetVisible(false);
+
+	//展示商品分类展示
+	CVerticalLayoutUI* pFoodtype = static_cast<CVerticalLayoutUI*>(this->FindSubControl(_T("aixuexi_page_fenlei_layout_scrolllayout")));
+	pFoodtype->SetVisible(true);
+
+	m_cur_type_id = m_type_id_before_sousuo;
+
+	CLabelUI* pTishi = static_cast<CLabelUI*>(this->FindSubControl(_T("aixuexi_page_food_search_tishi")));
+	pTishi->SetVisible(true);
+
+	this->InitFoodShow();
+}
+
+void CAIxuexiPageUI::SetPos(RECT rc, bool bNeedInvalidate)
+{
+	m_nPageWidth = rc.right - rc.left;
+
+	//更新分类位置
+	UpdateFoodtypePos();
+
+	CContainerUI::SetPos(rc, bNeedInvalidate);
+}
+
+void CAIxuexiPageUI::DoXuexi()
+{
+	//第一步先把当前帧保存为图片
+	std::wstring save_path = CSystem::GetTmpDir() + L"\\ai_xuexi_image";
+
+	if (!CSystem::IsPathExist(save_path))
+	{
+		CSystem::CreateMultiLevel(CLewaimaiString::UnicodeToUTF8(save_path));
+	}
+
+	std::string filename = CLewaimaiString::generateRandomString(32);
+	std::wstring file_save_path = save_path + L"\\" + CLewaimaiString::UTF8ToUnicode(filename) + L".jpg";
+	std::string s_file_save_path = CLewaimaiString::UnicodeToUTF8(file_save_path);
+
+	cv::Mat img;
+	CVideoCaptureWorker::GetInstance()->GetFrame(img);
+
+	if (img.empty())
+	{
+		return;
+	}
+
+	if (img.type() != CV_8UC3)
+	{
+		// 仅支持 3 通道彩色图像
+		cvtColor(img, img, cv::COLOR_GRAY2BGR);
+	}
+
+	cv::imwrite(CLewaimaiString::UnicodeToUTF8(file_save_path), img);
+
+	//第二步把这个图片的特征向量提取出来,保存到数据库里,关联到当前选中的商品上
+	std::string sName = m_cur_click_food_item->GetFoodInfo().name;
+	std::string sFoodId = m_cur_click_food_item->GetFoodInfo().id;
+
+	//初始化数据库
+	std::wstring wsExePath = CSystem::getExePath();
+	std::wstring wsProgramDir = CSystem::GetProgramDir();
+	std::filesystem::path mainDir = wsProgramDir;
+	std::string sMainDir = mainDir.string();
+
+	std::string databasePath = sMainDir + "/image_features.db";     // SQLite数据库路径
+
+	// 初始化SQLite-Vec数据库
+	std::cout << "正在初始化SQLite-Vec数据库..." << std::endl;
+	SQLiteVecManager vecManager(databasePath);
+
+	// 检查数据库是否已存在数据
+	bool databaseExists = vecManager.loadDatabase();
+	std::cout << "数据库加载结果:" << std::boolalpha << databaseExists << std::endl;
+	std::cout << "数据集数量:" << vecManager.getFeatureCount() << std::endl;
+	if (databaseExists && vecManager.getFeatureCount() > 0)
+	{
+		std::cout << "数据库已存在 " << vecManager.getFeatureCount() << " 条特征记录" << std::endl;
+	}
+
+	//初始化向量数据库,特征向量是1280维度
+	vecManager.initializeDatabase(1280);
+
+	std::string modelPath = sMainDir + "/ai/best_448.onnx";           // YOLO2026模型路径
+	YoloFeatureManager extractor;
+	extractor.loadModel(modelPath);
+
+	std::cout << "开始提取图库图片特征..." << std::endl;
+	std::vector<float> features = extractor.extractFeatures(s_file_save_path);
+	if (!features.empty())
+	{
+		vecManager.addFeatureVector(features, s_file_save_path);
+	}
 }

+ 49 - 3
zhipuzi_pos_windows/page/CAIxuexiPageUI.h

@@ -5,6 +5,12 @@
 
 #include "../wnd/CModalWnd.h"
 
+#include "../zhipuzi/CFood.h"
+#include "../zhipuzi/CFoodtype.h"
+#include "../zhipuzi/CFoodpackage.h"
+
+#include "../control/CShangpinFoodItemUI.h"
+
 class CAIxuexiPageUI : public CBasePageUI
 {
 public:
@@ -15,6 +21,14 @@ public:
 	//初始化当前页面的展示,处理默认展示效果,在页面每次被选中加载(注意不是页面创建构造)的时候调用,如果多次选中会多次调用,这里要避免数据重复处理
 	void InitShow();
 
+	//初始化商品分类的显示
+	void InitFoodtypeShow();
+
+	void UpdateFoodtypePos();
+
+	//根据当前选择的分类,刷新商品展示
+	void InitFoodShow();
+
 	//处理按钮点击类事件
 	void HandleClickMsg(TNotifyUI& msg);
 
@@ -37,13 +51,45 @@ public:
 
 	void UpdateVideoShow();
 
+	//点击某一个商品的item之后的处理逻辑
+	void ClickFoodAction();
 
-private:
-	std::wstring m_newImage;
+	//开始搜索某个商品名字
+	void StartSearchFood(std::string foodname);
+
+	//停止搜索商品
+	void StopSerachFood();
+
+	void SetPos(RECT rc, bool bNeedInvalidate = true);
 
+	//点击拍摄并学习按钮之后的处理逻辑
+	void DoXuexi();
+
+private:
 	bool m_isFirstInit = true;
 
 	HBITMAP m_hBitmap;
 	HDC m_hMemDC;
-};
 
+	CTileLayoutUI* m_foodLayout;
+	CHorizontalLayoutUI* m_foodtypeLayout;
+
+	//所有商品分类(只包含收银机显示的分类)
+	std::vector<CFoodType> m_types;
+
+	//当前选中的商品分类ID,为空表示没选中任何分类,为taocan表示选中套餐分类,如果选中普通商品分类就是分类ID
+	std::string m_cur_type_id = "";
+	CControlUI* m_curFoodtypeOption;
+
+	//商品搜索相关
+	std::string m_type_id_before_sousuo;
+	std::string m_sousuo_foodname;
+
+	//当前点击的哪个商品
+	CShangpinFoodItemUI* m_cur_click_food_item;
+
+	//当前控件的实际宽度
+	int m_nPageWidth;
+
+	std::mutex m_foodtype_mutex;
+};

+ 3 - 0
zhipuzi_pos_windows/pch/pch.h

@@ -22,6 +22,9 @@
 #include <iomanip>
 #include <regex>
 #include <filesystem>
+#include <random>
+#include <unordered_set>
+#include <atomic>
 
 //windows库
 #include <WinSock2.h>