Estou escrevendo porque, pela terceira vez em um ano, me deparei com essa tarefa. Cada vez, tudo começa com uma solução incrivelmente criativa, mais fácil e, no final, chega ao sistema sobre o qual falarei.
O objetivo é criar e manter um horário semanal, como um horário de aula escolar ou um horário de médicos e funcionários. Há um conjunto de slots, cada slot é um local da programação semanal com vários parâmetros adicionais, como número do gabinete, nome do funcionário. É necessário criar um sistema flexível com um histórico completo que possa resolver problemas como: criar um horário diferente desde o início do verão, substituir o professor pelas próximas 3 semanas, mudar o horário de sexta para sábado por causa do feriado.
Vou escrever sobre o que eles geralmente tropeçam e como resolvê-lo, vou resolver o problema de pintar a tira e, em seguida, darei exemplos de um back-end simples no nó / sequenciar e terminar com um front-end simples no vue / vuex / vuetify / nuxt, onde você pode arrastar tudo com o mouse e ver como isso funciona
Os códigos são publicados no
github , implantados
aqui .

Alterações granulares
Há um slot, de alguma forma apresentado no banco de dados. Precisa de edição. Então você precisa desenhar algum formulário com os campos, e abaixo do botão "salvar". Afinal, tudo geralmente é organizado assim. No entanto, não neste caso. Considere o formulário:
Ao salvar, todos os dados do slot são atualizados, o histórico é perdido. Vamos tentar adicionar esse elemento:
Mais uma vez por. Por exemplo, em 4 de junho, na segunda-feira, foi registrada uma transferência de aulas de um dia do primeiro escritório para o segundo. Então vem uma nova demanda - a partir de 28 de maio, a lição sempre começará às 20:00, em vez de 19:00. Abrimos o formulário, alteramos a hora, indicamos a data do dia 28 e para sempre e ... todos os campos, juntamente com o número do gabinete, vão para o servidor. As alterações temporárias em 4 de junho são substituídas. Usando este formulário, é impossível determinar quais campos em quais intervalos o usuário deseja alterar, porque todos os campos são enviados.
A idéia é que cada regra mude independentemente das outras com seu próprio intervalo. O slot é definido por um conjunto de parâmetros unidimensionais, cada parâmetro tem um histórico de alterações definido por um conjunto de regras. Cada regra contém um valor, uma data de início e fim. Como esse é um calendário semanal, as datas são suficientes para indicar até uma semana, AAAAAW.

Parece que a edição do slot agora é muito complicada - para alterar vários campos, é necessário selecionar cada campo, abrir o formulário, colocar um valor e um intervalo. No entanto, na prática, mudar vários campos provou ser uma situação rara. Muito mais frequente é a atualização em massa de vários slots por vez. Por exemplo, para anular a ausência de um professor devido a uma doença, você precisa selecionar todos os seus blocos, colocar o status de atribuição de equipe em licença médica e selecionar um professor substituto para os mesmos blocos. Apenas 2 ações em vez de n ações para n slots no caso, como se elas fossem especificadas no formulário tradicional. No sistema
StarBright.com em que estou trabalhando, fica assim:
A tarefa de pintar tiras
Considere uma faixa composta por células pintadas em cores diferentes. Cada célula é uma semana, cada cor é um significado. Chega uma nova cor e, no intervalo para aplicá-la, eles precisam repintar sobre o que é. No nível dos dados, isso significa que você precisa remover intervalos completamente sobrepostos, alterar os intervalos parcialmente sobrepostos, adicionar um novo intervalo, mesclar intervalos adjacentes de uma cor em um. O resultado final deve consistir em intervalos que não se sobrepõem.
Resultado: [{delete, id: 2}, {update, id: 1, dados: {to: 5}}, {update, id: 3, dados: {from: 16}}, {insert, data: {from : 6, a: 15, valor: quarta-feira}}]Essa é uma tarefa simples, mas é fácil ignorar algo aqui.
Aqui está um repositório separado com uma solução e testes.
http://timeblock-rules.rag.lt - aqui você pode verificar como funciona e brincar com sombreamento.
Backend
As regras não se sobrepõem; portanto, a regra `select * from mais simples, de <=: week e (to é nula ou> =: week)` é suficiente para selecionar exatamente as regras necessárias para a semana especificada.
Aqui está um exemplo simples de back-end no nó / sequenciar. Ele usa o estilo combinado de promessas de c e assíncrono / espera, sobre o qual você pode ler
em outro artigo meu .
Aqui está a ação que seleciona as regras para a semana especificada:
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) } })
E aqui está PATCH para alterar o conjunto de regras:
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 => {
Esta é a parte ideológica mais difícil do back-end, o resto é ainda mais simples.
A questão é como remover slots. Nesse caso, o histórico completo é armazenado, nada é excluído. Há um campo de status que pode ser aberto, fechado e fechado temporariamente. Os visitantes veem slots ativos e temporariamente inativos; neste último, o administrador geralmente escreve um comentário sobre o motivo de não haver atividade. Com o tempo, existem muitos slots fechados e, para simplificar a situação, é útil introduzir outra propriedade, como um ano letivo, e mostrar apenas o ano letivo atual ao editar os slots.
Frontend
O código está
neste repositório , é um site de uma página simples no nuxt. Na verdade, existem alguns problemas com o ssr (por exemplo, analiso detalhadamente como escrever autenticação no nuxt), mas aplicativos simples são escritos nele muito rapidamente.
Aqui está o código para uma única página:
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') } } }
O método de busca funciona no servidor e no cliente, redireciona para a semana atual e solicita um calendário. Quando a semana muda, os dados são solicitados novamente.
O que fazer com os slots sobrepostos? A resposta depende da lógica comercial, por exemplo, você pode precisar de validação do servidor que proíba sobreposições. Nesse caso, as sobreposições são permitidas e, para obter uma imagem bonita, esses slots são desenhados com metade da largura um do outro. Adicione o layout e obtenha esta aparência:
Tudo o resto é javascript simples, sem idéias especiais. Ao descer o bloco, o arraste e solte começa. Os eventos de remoção de mouse e mouse estão pendurados em toda a janela. Arrastar e soltar começa com um atraso de 200ms para distinguir o arrasto do clique. Os parâmetros dos contêineres nos quais a queda é rastreada são calculados com antecedência, porque getBoundingClientRect é uma operação muito pesada para fazer em cada remoção de rato. Eu tive que fazer dois formulários - um para criar (definindo todas as regras de uma vez a partir da semana atual), o outro para alterações granulares no slot.
http://calendar.rag.lt - aqui você pode verificar como tudo funciona.
Links para o artigo