كيف قمنا بتحسين ترتيب الغداء في المكتب (دون الوصول إلى الخادم)

مرحبا بالجميع.

أنا أعمل في مكتب. مطور برامج. وأحيانا آكل. نعم كل يوم صاحب العمل يزودنا بغداء - العمال يطلبون الغداء ليوم غد ، وغدًا يقوم مورد وجبات الغداء بإحضار ما طلبه الموظفون. ما تم طلبه وما تم إحضاره لا يتطابق دائمًا ، ولكن هذا ليس هو الحال. يتم طلب الغداء في صفحة طلب الغداء. لكن ...

لكن أولاً ، حول كيفية تكوين صفحة طلب الغداء: يرسل المورد ملف XLS مع قائمة أسعار لمدة أسبوع.

صورة
مثال قائمة الأسعار المرسلة من قبل المورد

الشخص المسؤول عن العشاء يوزع من خلال الأداة المساعدة التي طورها شخص ما في أحشاء شركتنا ، وترجمتها إلى نموذج يمكن أن تعرضه بوابة الشركة الخاصة بنا. ويعرضها ...

صورة
لقطة شاشة مع وجبات العشاء المطلوبة

صورة
لقطة شاشة لصفحة طلب الغداء

يتم تقسيم الوظائف بشكل غير مريح إلى فئات. المعلومات حول الاسم والتكوين في نص كامل ومن الصعب التنقل.

أريد أن أفهم أنه من الأفضل عدم الطلب ، وما يمكنك تجربته ، لأن الآخرين يحبونه. وهذا هو ، أريد تصنيف. أرغب أيضًا في تلقي طلبي على Telegram حتى لا أتذكر ما طلبته في غرفة الطعام.

لذلك الأهداف واضحة. يجب أن أقول على الفور: إن الطريق الذي سلكه أنا وزميلي أبعد ما يكون عن الصواب والعقلانية. ومع ذلك: إنها لعبة كاملة من حيث الهندسة المعمارية / الأمن / الدعم / التسامح مع الخطأ. ولكن ما نمت نمت.

ليس لدينا حق الوصول إلى الخادم ، لذلك يمكنك فقط تغيير مظهر الصفحة باستخدام البرامج النصية للمستخدم. ولكن ماذا عن التصنيف؟ لا يوجد الوصول إلى قاعدة البيانات سواء. حسنًا ، نحتاج إلى خادم لمعالجة الطلبات وتقييمها والتفاعل مع Telegram. تم أخذ هذا الدور بواسطة خادم NodeJS.

جانب الخادم


سأعتني بالخادم ، وسيتولى زميل البرنامج النصي للمستخدم الذي يضيف وظائف إلى الصفحة. نأخذ خادم nodejs ، الاتصال السريع ، إضافة الخلية. وضع تسلسل على القمة . وسوف نتفاعل مع Telegram من خلال node-telegram-bot-api :

//    const app = express(); // ... //   //    app.get("/dinners/user_menu", dinner.getUserMenu); //      app.get("/dinners/r/:id", dinner.getPersonalRatings); //   app.post("/dinners/r/:id", dinner.setRating); //      Telegram app.post("/dinners/resend/:id", dinner.resendMessage); //      app.post("/dinners/order", dinner.order); //  ,      app.post("/dinners/days", dinner.setDinnerDays); 

باختصار حول الوظيفة:
المسار / العشاء / user_menu بإرجاع برنامج نصي للمستخدم:

 res.sendFile(__dirname + '/public_html/user_script.js'); 

يتم ذلك من أجل عدم صرف انتباه الزملاء الذين يستخدمونه عن طريق تثبيت إصدار جديد من البرنامج النصي. تصحيح - رمى على الخادم - تحديث الجميع.

نعم ، أعلم أن هذا أمر سيئ ، من وجهة نظر الأمان ، ولكن الوظيفة نفسها ليست حرجة وسنعتبر الخادم الذي تم تخزين البرنامج النصي عليه آمنًا تمامًا.

علاوة على ذلك ، على طول المسار / dinners / r /: id ، يمكنك الحصول على تصنيف لجميع المواقف وحفظ التصنيف ، أي التصويت للأطباق.

يخدم المسار / العشاء / إعادة إرسال /: id إرسال رسالة إلى Telegram. يتم إنشاء نص الرسالة على العميل ، فقط التفاعل مع Telegram يحدث على الخادم:

 const parseMode: TelegramBot.SendMessageOptions = {parse_mode: "HTML"}; await this.bot.sendMessage(telegramId, htmlMessage, {...options, ...parseMode}); 

بعد ذلك ، يرسل بوت رسالة بالترتيب.

صورة

بعد ذلك ، على طول المسار / العشاء / الترتيب ، يتم حفظ الطلب. نظرًا لأنه يصعب تحديد طلب الطلب الأصلي (بعد النقر فوق الزر "حفظ" ، يظهر تنبيه مع زر تأكيد الطلب) ، يتم إرسال طلب الخادم مع الطلبات عند تحميل صفحة الطلب (وينقسم نظام الطلب بالكامل على الموقع إلى صفحتين - صفحة الطلب وصفحة القائمة - اختيار الأطباق ليوم معين - أي تشكيل طلبية) من غير المنطقي إرسال الطلبات في كل مرة تدخل فيها صفحة الطلب ، ولكن لم يكن هناك خيار أفضل للمفاجئة.

أخيرًا ، يحدد مسار / العشاء / الأيام الأيام التي يتم فيها طلب الغداء. ظهر هذا الجزء من الوظيفة من أجل التشغيل الصحيح للتذكيرات بشأن أمر غير مكتمل - تحتاج إلى معرفة ما هو اليوم التالي من الطلب (بعد كل شيء ، هناك عطلات نهاية الأسبوع والأعياد في منتصف الأسبوع). بدلاً من أخذ تطبيق تقويم الإنتاج ، أقوم ببساطة بتحليل التواريخ في صفحة الطلب ، حيث تم تحديد أيام العمل وغير أيام العمل بالفعل (لا يمكنك إصدار أمر ليوم غير العمل). يتم وضع علامة على أيام غير العمل على البوابة مع فئة "عطلة":

 //     const trToday = $(".dinner_today")[0]; const tbodyAllDays = $(trToday).parent(); const dinnerDays = []; $(tbodyAllDays).children().each(async function() { if ($(this).hasClass("isHoliday")) { return; } const itemMenuDate = $(this).find("> td:first-child").text().substring(0, 10); dinnerDays.push(itemMenuDate); // ... }); await sendRequest("POST", `https://****/dinners/days/`, {days: dinnerDays}); 

أوه نعم ، استخدم مسج لاختيار. أنها مريحة للغاية الخوض في شجرة الصفحة.

البرقية بوت


جزء آخر من الإضافة بأكملها هو بوت التلغراف.

صورة
مع هذه الوظيفة

الحصول على معرف - وهذا هو مثل نظام تحديد الهوية. لربط البرنامج النصي للمستخدم على متصفح معين مع userId في برقية.

عرض ترتيب اليوم ، راجع قائمة الطلبات (آخر 5) ، قم بتعيين تذكير.
يتم إرسال الغداء تلقائيًا إلى المورد في نفس الوقت كل يوم ، لذلك من المهم تقديم طلب قبل وقت معين ، الساعة 13:00.

بعد ذلك ، يتم حظر القدرة على تقديم الطلب.

تذكير:

صورة
يوفر الروبوت فرصة لاختيار وقت التذكير: 9 أو 10 أو 11 ساعة.

علاوة على ذلك ، إذا لم تصدر طلبًا بعد تذكير ، فسيذكرك الروبوت كل 10 دقائق بعد ذلك حتى تقوم بطلبه ، أو حتى يتم حظر الطلب.

يتم ذلك بواسطة مهمة cron (باستخدام جدول العقدة ):

 schedule.scheduleJob('*/10 9-13 * * 1-5', async function() { // ... }); 

جزء العميل. قائمة الطعام


أكرر أن الواجهة الحالية بالاقتران مع نص عناصر القائمة التي يرسلها المورد سيئة للغاية (انظر الشاشة 2). وعند نقطة واحدة ، تتوقف عن رؤية أي شيء بالأطنان من النصوص الرتيبة والمفيدة قليلاً.

بعد البحث عبر الإنترنت يمكن أن يساعدنا ، صادفنا مكوّنًا جيدًا جيدًا لنصوص Greasemonkey المخصصة ، وقرروا استخدامه.

بادئ ذي بدء ، نقوم بإنشاء برنامج نصي للمستخدم ونمنح الحق في التواصل مع بوابة وخادم الشركة ، حيث يتم تثبيته على التصنيف والقدرة على إرسال الطلبات

 // @include http://****.int/* // @include http://****/* // @grant GM.xmlHttpRequest 

أيضًا ، لتعديل صفحة الغداء نفسها ، استخدمنا jQuery ، وربطها باستخدام //require

الآن دعونا نبدأ في تجريف صفحة الغداء. بعد الاطلاع على كود html للصفحة ، نجد معرف جدول الغداء ، ونحصل على الجدول ونعدله.

 const table = $(".dinner__innerData"); const categoryList = []; //      $(table).find(“tbody tr td:nth-child(2})”).each(function () { const text = $(this).text(); //           if (!categoryList.find(name => name === text)) { $(this).parent().before("<tr><th colspan='6'>" + text + "</th><th style='display:none'></th><th style='display:none'></th><th style='display:none'>0</th><th style='display:none'><span class='dish__amount'>0</span></th></tr>"); categoryList.push(text); } }); //      $(table).find(“thead th:nth-child(2)”).remove(); $(table).find("tbody tr td:nth-child(2)”).remove(); //     $(table).find(“tbody tr td:nth-child(2)”).after("<td></td>"); $(table).find(“thead th:nth-child(2)”).after("<th class='ui-state-default'></th>"); 

أريد أن أشير إلى أنه في صفحة تكوين الغداء ، عند حساب مبلغ الطلب ، يتم النظر في جميع صفوف الجدول ، مع تلقي رقم العنصر المطلوب مضروبًا بالسعر. لهذه الأسباب ، إذا أضفت صفًا يحمل اسم الفئة ، فسيتم كسر كل شيء ... اضطررت إلى إدخال أعمدة مخفية بكمية صفر وكمية لهذا الصف.

الآن دعنا ننتقل لتنظيف النص وإضافة معلومات عن تصنيف الطبق. أولا ، بعض وظائف المساعد. يتم تحديد الطبق في التصنيف بالاسم دون أي نفايات في شكل غرام ، أي علامات الترقيم والمسافات. وهذا هو ، طبق يسمى "مرق الدجاج مع البيض (مرق الدجاج والجزر والبصل والبيض والخضر). في 100 غرام: بروتينات 3.43 ؛ الدهون، 2.86؛ الكربوهيدرات 1،0. en.value-43.39kcal (200gr) "تم تعريفها على أنها" bouillon curd ". ويرجع ذلك إلى حقيقة أن المورد قد يتسلل إلى مسافات إضافية ، علامات ، وشيء آخر. كما أظهرت الممارسة ، كان هذا كافياً لتحديد الطبق بدقة في 90٪ من الحالات ، وقررنا ألا نضايق وندخل في بحث النص الكامل.

 /** *         * @param items   * @param tdText    * @return   */ function findByName(items, tdText) { tdText = clearTrash(tdText, true, true, true); return items.find(({clear_name}) => { return clear_name.trim().toLowerCase() === tdText; }); } /** *     * @param text  * @param clearDescr       * @param clearGrams    * @return    */ function clearTrash(text, clearDescr, clearGrams, clearSymbols) { //   ,       } 

وهذا هو تشكيل تصنيف:

 const table = $(".dinner__innerData"); const nameTd = $(table).find(“tr td:nth-child(2)”); for (let index = 0; index <= nameTd.length; index++) { const tdText = $(nameTd[index]).text(); //     const item = findByName(items, tdText); if (item) { let ratingTd = $(nameTd[index]).parent().find(“td:nth-child(2)”)[0]; //           let ratingText = "<i></i> " + parseFloat(item.avgrating).toFixed(1) + " (: " + item.orders + ", : " + item.ratingsCount + ")"; ratingText = item.persrating ? `<b><i></i> ${parseFloat(item.persrating).toFixed(1)} (: ${item.perscount})</b><br>` + ratingText : ratingText; //   $(ratingTd).css({ // getColorRating       background: getColorRating(item.avgrating) }).html(ratingText); } //           //   ,      const grams = getGrams(tdText); //     $(nameTd[index]).html(clearTrash(tdText, false, true, false)); //      ,       $(nameTd[index]).append("<br/><span></span>") .find("span") .append(grams) .css({"font-size": 10}); } 

وهذا ما حدث.

صورة
توافق ، أجمل بكثير وأكثر ملاءمة؟

جزء العميل. تصويت


بعد ذلك ، سننتقل إلى إضافة القدرة على التصويت للأطباق المطلوبة ، بالإضافة إلى إرسال رسالة بترتيب التلغراف.

صورة
الصفحة مع أوامر دون برنامج نصي

في صفحة الأطباق المطلوبة ، أضف التصنيف:

 async function addRatingForm() { const table = $(".dinner__innerData"); const nameTd = $(table).find("tr td:nth-child(1)"); //   for (let index = 0; index <= nameTd.length; index++) { const tdText = $(nameTd[index]).text(); $(nameTd[index]).html(clearTrash(tdText, false, true, false)); } //       Telegram $(table).append("<tfoot><tr><th colspan='6' class='rating-buttons btn-group margT0' style='display: table-cell;'></tr></tfoot>"); $(".rating-buttons").prepend(`<input type="submit" value="" class="btn_primary rating-button">`); $(".rating-buttons").prepend(`<input type="submit" value=" Telegram" class="btn_primary send-button">`); //      await diableButtonByDate(); //      for (let index = 0; index <= table.length; index++) { $(table[index]).find("tbody tr td:nth-child(4)").after("<td class='ratingInputTd'><input id='horizontal-spinner' class='ui-spinner-input' style='width:20px;'></td>"); $(table[index]).find("thead th:nth-child(4)").after("<th class='ui-state-default'></th>"); } $(".ui-spinner-input").spinner({ max: 10, min: 1 }); //   $(".rating-button").click(sendRating); $(".send-button").click(sendTelegram); } /** *      ,    */ async function diableButtonByDate() { //             . //              const buttons = $(".rating-button"); for (let index = 0; index <= buttons.length; index++) { const button = $(buttons[index]); const date = button.parent().parent().parent().parent().parent().parent().find("> td:nth-child(1)").text().substring(0, 10); if (await GM.getValue(date)) { button.attr({disabled: "disabled"}); } } } /** *  */ async function sendRating(event) { event.preventDefault(); const items = []; //         $(this).parent().parent().parent().parent().find("tr").each(function () { const tdList = $(this).find("td"); const ratingInput = $(tdList[4]).find("input"); if (!ratingInput.length) { return; } items.push({ count: $(tdList[2]).text(), price: $(tdList[1]).text(), name: $(tdList[0]).text(), rating: ratingInput.val(), }); }); await sendRequest("POST", `https://****/dinners/r/${telegramId}`, items); const menuDate = $(this).parent().parent().parent().parent().parent().parent().find("> td:nth-child(1)").text().substring(0, 10); await GM.setValue(menuDate, true); location.reload(); } 

وهنا ما حصلنا عليه في الإخراج:

صورة

نعم - الكود فظيع. نعم - ليس الأمثل. ونعم - في بعض الأماكن غير منطقي. ولكن الوقت الذي تم إنفاقه كان بحد أدنى ، وزادت الوظائف والراحة بشكل كبير.

كان الهدف هو جعل طلب العشاء أكثر متعة لنفسي ولرفاقي ، وقد تحقق هذا الهدف في رأيي.

Source: https://habr.com/ru/post/ar475892/


All Articles