محرر الجدول الأسبوعي

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

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

سأكتب عن ما يتعثرون فيه عادة وكيفية حله ، وسأحل مشكلة رسم الشريط ، وبعد ذلك سأقدم أمثلة على خلفية بسيطة على العقدة / تتمة وننتهي بواجهة أمامية بسيطة على vue / vuex / vuetify / nuxt ، حيث يمكنك سحبها كلها باستخدام الماوس ورؤية كيف يعمل.

يتم نشر الرموز على جيثب ، نشر هنا .



التغييرات الحبيبية


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


عند الحفظ ، يتم تحديث جميع بيانات الفتحة ، يتم فقد السجل. دعونا نحاول إضافة مثل هذا العنصر:


مرة أخرى من قبل. على سبيل المثال ، في الرابع من يونيو ، يوم الاثنين ، تم تسجيل نقل الطبقات لمدة يوم واحد من المكتب الأول إلى الثاني. ثم يأتي طلب جديد - بدءًا من 28 مايو ، سيبدأ الدرس دائمًا الساعة 20:00 بدلاً من 19:00. نفتح النموذج ، ونغير الوقت ، ونشير إلى التاريخ من 28 وإلى الأبد و ... جميع الحقول ، إلى جانب رقم الخزانة ، تذهب إلى الخادم. تم تجاوز التغيير المؤقت في 4 يونيو. باستخدام هذا النموذج ، من المستحيل تحديد الحقول التي يرغب المستخدم في تغييرها ، لأنه يتم إرسال جميع الحقول على الإطلاق.

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


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


مهمة رسم الشرائط


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


النتيجة: [{delete، id: 2}، {update، id: 1، data: {to: 5}}، {update، id: 3، data: {from: 16}}، {insert، data: {from : من 6 إلى: 15 ، القيمة: الأربعاء}}]

هذه مهمة بسيطة ، ولكن من السهل تجاهل شيء ما هنا. هنا مستودع منفصل مع حل واختبارات. http://timeblock-rules.rag.lt - هنا يمكنك التحقق من كيفية عملها واللعب مع التظليل.

الخلفية


لا تتداخل القواعد ، لذا فإن أبسط `select * من القواعد حيث من <=: week و (to null or to> =: week)` كافية لتحديد القواعد الضرورية بالضبط للأسبوع المحدد. هنا مثال بسيط للخلفية على العقدة / تتمة. يستخدم النمط المشترك للوعود c وغير المتزامن / الانتظار ، والتي يمكنك أن تقرأ عنها في مقال آخر خاص بي .

فيما يلي الإجراء الذي يحدد قواعد الأسبوع المحدد:
routes.get('/timeblocks', async (req, res) => { try { ... validation ... await Rule .findAll({ where: { from: {$or: [{$lte: req.query.week}, null]}, to: {$or: [{$gte: req.query.week}, null]} } }) .then( sendSuccess(res, 'Calendar data extracted.'), throwError(500, 'sequelize error') ) } catch (error) { catchError(res, error) } }) 


وهنا PATCH لتغيير مجموعة القواعد:
 routes.patch('/timeblocks/:id(\\d+)', async (req, res) => { try { ... validation ... const initialRules = await Rule .findAll({ where: { timeblock_id: req.params.id, type: {$in: req.params.rules.map(rule => rule.type)} } }).catch(throwError(500, 'sequelize error')) const promises = [] req.params.rules.forEach(rule => { // This function defined in stripe coloring repo, https://github.com/Kasheftin/timeblock-rules/blob/master/src/fn/rules.js; const actions = processNewRule(rule, initialRules[rule.type] || []) actions.forEach(action => { if (action.type === 'delete') { promises.push(Rule.destroy({where: {id: action.id}})) } else if (action.type === 'update') { promises.push(Rule.update(action.data, {where: {id: action.id}})) } else if (action.type === 'insert') { promises.push(Rule.build({...action.data, timeblock_id: rule.timeblock_id, type: rule.type}).save()) } }) }) Promise.all(promises).then( result => sendSuccess(res, 'Timeblock rules updated.')() ) } catch (error) { catchError(res, error) } }) 


هذا هو الجزء الأكثر صعوبة من الواجهة الخلفية ، والباقي أبسط.

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

الواجهة الأمامية


الرمز موجود في هذا المستودع ، وهو موقع بسيط من صفحة واحدة على nuxt. في الواقع ، هناك بعض المشاكل مع ssr (على سبيل المثال ، أحلل بالتفصيل كيفية كتابة المصادقة على nuxt) ، ولكن تتم كتابة التطبيقات البسيطة عليها بسرعة كبيرة.

هذا هو الرمز لصفحة واحدة:
 export default { components: {...}, fetch ({app, route, redirect, store}) { if (!route.query.week) { const newRoute = app.router.resolve({query: {...route.query, week: moment().format('YYYYWW')}}, route) return redirect(newRoute.href) } return Promise.resolve() .then(() => store.dispatch('calendar/set', {week: route.query.week})) .then(() => store.dispatch('calendar/fetch')) }, computed: { week () { return this.$store.state.calendar.week } }, watch: { week (week) { this.$router.push({ query: { ...this.$route.query, week } }) this.$store.dispatch('calendar/fetch') } } } 


تعمل طريقة الجلب على الخادم والعميل ، وتعيد التوجيه إلى الأسبوع الحالي وتطلب تقويمًا. عندما يتغير الأسبوع ، يتم إعادة طلب البيانات.

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



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

http://calendar.rag.lt - هنا يمكنك التحقق من كيفية عمل كل شيء.

روابط للمقال


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


All Articles