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