吳昊 白俊鴿 四川大學(xué)錦城學(xué)院 計算機與軟件學(xué)院
我國餐飲行業(yè)近年來發(fā)展迅速,今日的餐飲企業(yè)會同時管理多個餐飲品牌,同時單門店客戶基數(shù)倍增。在激烈的市場競爭環(huán)境下,企業(yè)不僅需要創(chuàng)新產(chǎn)品,還需要創(chuàng)新企業(yè)模式理念。數(shù)字化點餐系統(tǒng)解決了傳統(tǒng)模式中傳菜員傳達(dá)信息的時間延誤,以及一旦忙起來傳菜員可能會亂套的效率下降問題。
本系統(tǒng)針對中小型餐飲團隊,以中餐類目為示例,為經(jīng)營中的點餐協(xié)作流程提供數(shù)字化的操作系統(tǒng)支持。具體為:為服務(wù)員提供下單系統(tǒng)、為后廚提供客人點菜流水清單和出菜流水清單、為管理者提供菜品編輯系統(tǒng)。
根據(jù)我們實地考察整理出的商家需求,我們將點餐系統(tǒng)分成三個界面,分別對應(yīng)前臺點餐、后臺數(shù)據(jù)管理、廚房數(shù)據(jù)接收端。
在前臺菜譜下單系統(tǒng),可以看到按照類目整理,能夠直接點擊交互的菜譜,和記錄客人點的菜的點餐清單。并支持修改數(shù)量和口味,以及取消點好的菜品的功能。
廚房數(shù)據(jù)接收系統(tǒng)包含兩個表格顯示區(qū),分別對應(yīng)待完成的顧客點單和已完成的出菜流水。點擊制作完成的菜品項,會彈出一個窗口校對詳細(xì)菜品信息,確認(rèn)無誤后,點擊[已完成,刪除]按鈕,菜品就會更新到“已完成菜品”表里。已完成菜品表也會按出菜順序記錄下菜品流水。
該系統(tǒng)提供菜譜添加和菜譜修改菜單。添加菜品時會看到描述菜品屬性的候選欄和輸入框,對應(yīng)菜品類別、菜品名稱、菜品價格、菜品單位信息填寫,點擊[添加]按鈕即可在總的菜譜中加入新菜品;同時支持清空和放棄選項。菜譜修改界面為菜譜總覽和瀏覽視圖,能在這里執(zhí)行刪除、修改功能。在總菜譜中找到對應(yīng)的菜品,單擊,右側(cè)表格就會顯示菜譜所有信息,輸入框和候選欄中會顯示出原有的菜品信息,提供編輯和刪除功能。
本例使用VC++6.0進(jìn)行開發(fā),VC++6.0是一個代碼編寫和編譯軟件。全稱為Visual C++6.0,是在Windows環(huán)境中工作的。在程序界面方面,我們選擇MFC來實現(xiàn)這一目標(biāo)。MFC是一個Windows應(yīng)用程序開發(fā)模式,對程序的控制主要是由MFC框架完成的。對于信息傳輸,我們采用了TCP/IP協(xié)議簇中的socket套接字作為信息互通的方式。Socket套接字主要是描述IP地址和端口,目的在于實現(xiàn)不同計算機設(shè)備直接的網(wǎng)絡(luò)通信。
為了方便演示,節(jié)省實際投入時的適配成本,本例使用本地文件來記錄菜單數(shù)據(jù),文件交互使用CFile來實現(xiàn)。Cfile類的CreateDirectory()、CopyFile()、DeleteFile()函數(shù)實現(xiàn)對文件的查找、復(fù)制、刪除等操作。
整個系統(tǒng)加載完成后,會將IDD_DIALDG_DISHCHOOSE對話框展示在使用者面前,同時讀取存儲在本地的DishMenuData文件,加載出菜譜。菜譜的實現(xiàn)使用MFC提供的CTreeCtrl類樹形控件結(jié)構(gòu),先將菜品大類呈現(xiàn)給客戶,點擊類目會展開該類目的菜品。為了美觀,此處會在菜品類目和菜品的文字前加載一個小圖標(biāo)以示區(qū)分,一共能展示給使用者三個不同的圖標(biāo)。實現(xiàn)為HICON hIcon創(chuàng)建圖表句柄,imageList.Create(32,32,8,0,32)創(chuàng)建圖像列表,hIcon[0]= AfxGetApp()->LoadIcon(IDI_ICON2)和 m_imageList.Add(hIcon[0])語句將圖標(biāo)加到圖像列表中,m_MenuTree.SetImageList(&m_imageList,TVSIL_NORMAL)語句設(shè)置控件所使用的圖像列表。這里的實現(xiàn)僅以一個圖標(biāo)舉例,其它圖標(biāo)實現(xiàn)方法相同,只是文件名和存儲地址不同,此處不再贅述。
統(tǒng)計表格我們使用CListCtrl來實現(xiàn),通過CRect rect語句聲明點菜清單區(qū)域,使用m_menuListCtl.GetWindowRect(&rect)語句獲取窗體的邊界矩形賦值給rect,實現(xiàn)點菜清單區(qū)域的劃分;表格通過設(shè)置擴展風(fēng)格dystyle|=LVS_EX_GRIDLINES實現(xiàn);選中某行菜品后高亮顯示由dwStyle|=LVS_EX_FULLROWSELECT實現(xiàn);表頭信息使用語句m_menuListCtl.InsertColumn(0,_T("菜名稱"),LVCFMT_LEFT, (int)(rect.Width()*0.31))實現(xiàn),此處以菜品的菜名屬性舉例,菜品的其他屬性譬如單價、數(shù)量、單位、口味、金額等實現(xiàn)方法一致,以此類推。
這一交互是由雙擊后彈出的單獨的對話框IDD_DLG_NUMCHANGE實現(xiàn)的,對話框里包含了口味候選框和數(shù)量輸入框供用戶修改。核心實現(xiàn)是由g_nItem識別出菜品,通 過 CDlgNumChange::strDisNum=m_menuListCtl.GetItemText(g_nItem,2)將口味和數(shù)量提供給用戶進(jìn)行交互。
當(dāng)顧客確認(rèn)點餐,點餐的內(nèi)容我們使用SOCKET套接字來進(jìn)行傳輸。由于本例僅作技術(shù)演示,傳輸?shù)哪康腎P地址我們設(shè)置為本地地址127.0.0.1,用戶能看到我們默認(rèn)設(shè)置的IP,也能修改它,代碼為((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS))->SetAddress(ntohl(inet_addr("127.0.0.1")))。關(guān)于傳輸過程,當(dāng)用戶點擊確認(rèn)發(fā)送按鈕,并且在“您是否確定發(fā)送該顧客菜單?”對話框處點擊確認(rèn)后,會執(zhí)行信息打包發(fā)送的步驟。代碼實現(xiàn)為通過 for(int i=0;i<nCount; i++)實現(xiàn)將所有菜品按照相同方法打包,然后使用CString strName= m_menuListCtl.GetItemText(i,0)語句分別將菜品名稱、菜品數(shù)量、菜品單位、菜品口味信息分別傳入strName、strNum、strUnit、strTaste里。對于單個菜品報文,打包代碼為strTemp+="#name="+strName+";num="+strNum+";unit="+strUnit+";
taste="+strTaste+ "@";這樣的報文打包以#開頭以@結(jié)尾,方便我們在內(nèi)容整理的時候區(qū)分開每個數(shù)據(jù)包里的菜品信息。當(dāng)所有菜品傳輸完成后,我們使用strTemp+="#end@";語句在整個菜品信息報文的末尾添加結(jié)束標(biāo)志,方便在接收到的時候區(qū)分接受到的報文是來自一次點餐還是多次點餐。準(zhǔn)備好了報文信息,還需要做的就是表頭信息了。表頭部分重點在于緩沖區(qū)的設(shè)置,此處操作我們使用strncpy函數(shù)。
同時,此處的SOCKET傳輸帶有發(fā)送檢測,成功與否都會提示用戶。檢測判別的方法是使用string庫中的find()函數(shù),這個函數(shù)會查找指定字符在母串中的位置,并將位置數(shù)據(jù)傳入返回值,找不到目標(biāo)字符的時候會返回標(biāo)記npos。我們定義一個字符串起始標(biāo)志indexBe和結(jié)束標(biāo)志indexEnd,通過find函數(shù)搜索字符串的起始字符和結(jié)束字符,分別賦值給indexBe和indexEnd,再分別將其與傳輸數(shù)據(jù)串的npos比較,可以確定傳輸?shù)某晒εc否,并出對話框提示。判定語句為 while (indexBe!=string::npos)和 if(indexEnd!=string::npos)。
廚房數(shù)據(jù)接收端會先展示系統(tǒng)中的IDD_DIALDG_KITCHEN對話框,這個對話框最重要的兩部分:待完成流水表和已完成流水表也是由CListCtrl的擴展風(fēng)格實現(xiàn)的,流水表的每一項菜品的參數(shù)數(shù)據(jù)使用STL容器中的MAP容器來裝載。顯示的信息是實時的,需要輸入端傳數(shù)據(jù)過來才看得到,實現(xiàn)是接受到數(shù)據(jù)以后使用我自己封裝的StrIntoListCtl顯示菜單函數(shù)。函數(shù)執(zhí)行的內(nèi)容主要為,先用InsertItem在控件中申請一行表格放數(shù)據(jù),然后用SetItemText將內(nèi)容填入,最后執(zhí)行UpdateData刷新函數(shù)。這里我們使用了MFC自帶的UpdateData刷新函數(shù),由于我們需要在窗口中顯示成員變量,也就是菜品的各項數(shù)據(jù),所以我們將函數(shù)參數(shù)設(shè)為FALSE,F(xiàn)ALSE的作用為,將接收到的數(shù)據(jù)展示到該窗口。
在socket接收時,由于發(fā)送方可能一次發(fā)很多包,也可能所有文件都發(fā)在一個包,但接收端接收時的看到的都是全部的包,有時候就會有各個包之間內(nèi)容粘連的情況(俗稱粘包)發(fā)生。對于這樣的粘包問題,我們通過增加起始和結(jié)束字符的方法避免了。拆包時,先用if(retval==nPackLenght)和else if(retval>nPackLenght)語句判別我們收到包的情況。其中retval為我們接收到的字符串,而nPackLenght為數(shù)據(jù)包包長,通過這一組語句我們可以得知我們接收到的數(shù)據(jù)是來自單個包還是多個包。單個包直接導(dǎo)入就行,如果是來自多個包,那我們首先要整理數(shù)據(jù)包。整理數(shù)據(jù)包的具體過程為,首先計算數(shù)據(jù)包的個數(shù),這個過程的核心是使用一個循環(huán)來查找數(shù)據(jù)包的起始標(biāo)志字符#和結(jié)束字符@,并考慮end內(nèi)容的判斷,當(dāng)我們查到一組有效內(nèi)容就讓記數(shù)文件nChNum+1,然后我們設(shè)置一個執(zhí)行條件為(int i=0;i<nChNum;i++)的for循環(huán),每一次執(zhí)行只讀取一個包長的數(shù)據(jù),就成功的實現(xiàn)了socket包的拆解。
此外,由于本界面是一個未完成菜品和已完成菜品的流水頁,對于同一客人點的某道菜品,其內(nèi)容是同一個資源,所以當(dāng)用戶點擊[已完成,刪除]的按鈕時,執(zhí)行的內(nèi)容是在未完成菜品中將菜品刪除并刷新未完成菜品列表,并在已完成菜品中顯示。實現(xiàn)方法為自定義的KitchenDelWait函數(shù),核心功能是在刪除未完成菜品的時候,就用一個類去記載刪除菜品的信息,并使用與StrIntoListCtl函數(shù)類似的方法將菜品信息傳入已完成流水。
菜譜管理系統(tǒng)核心是菜譜添加窗口IDD_DIALDG_BGDATAADD和菜譜編輯窗口IDD_DIALDG_AMEND,用戶點擊此系統(tǒng)時會將兩個對話框展示為同級菜單,同一時刻只能操作其中的一個。編輯窗口的界面和實現(xiàn)都與點菜窗口相似,但主要區(qū)別在于,點菜窗口的菜品主要用于展示,所以刷新函數(shù)UpdateData參數(shù)設(shè)置為FALSE,執(zhí)行后會將菜譜文件中的信息展示到窗口;此處的窗口目的為修改信息,所以將刷新函數(shù)UpdateData參數(shù)設(shè)置為TRUE,執(zhí)行后會將窗口中的信息寫入菜譜文件。
菜譜添加窗口就只有兩個候選欄、兩個輸入框、以及確認(rèn)、清空、放棄按鈕。這里可以看到菜品的類目,由于用戶輸入的數(shù)據(jù)可能會涉及程序的各種邊界值,設(shè)置了各種可能涉及到的報錯預(yù)警,例如if(""==strName)為未輸入菜名,會彈對話框提醒用戶輸入等情況,通過報錯條件判斷結(jié)果的布爾值作為報錯提示框的if函數(shù)參數(shù)值即可實現(xiàn)各類報錯提示框。而當(dāng)新菜品輸入好以后,為新菜建立一個指針,然后將指針鏈接到鏈表的尾部即可,具體代碼為m_dishMenu.AddTail(structDis)。
雖然本次實現(xiàn)的餐飲管理系統(tǒng)已經(jīng)能夠?qū)崿F(xiàn)整個點餐流程,但是站在產(chǎn)品的角度還可以做更多的完善,比如設(shè)立微信掃碼點餐的信息接收接口,將付款流程也加入進(jìn)系統(tǒng)等。在以后的學(xué)習(xí)中,除了代碼本身,還可以多思考如何提升產(chǎn)品交互易用性。