Skip to content
Snippets Groups Projects
Commit 270ed768 authored by Laurent Lecluse's avatar Laurent Lecluse
Browse files

Plus de Bootstrap-Vue qui était trop buggé

Mise en place de resolvers simples à utiliser
Bibliothèque de composants partagés en place
Doc à jour
parent 88310804
Branches
Tags
No related merge requests found
Pipeline #20213 passed
......@@ -41,13 +41,6 @@ statiques hautement optimisées pour la production.
Vite est extensible via son API de plugin et son API JavaScript avec un support de typage complet.
### [BootstrapVue](https://bootstrap-vue.org/)
Version Next (l'officielle n'est pas encore compatible Vue3/BS5)
Avec BootstrapVue, vous pouvez construire des projets web réactifs, axés sur les mobiles et accessibles ARIA
en utilisant des composants Vue.js pour générer du HTML compatible Bootstrap
### [Axios](https://axios-http.com/)
Version 1
......
<template>
<div class="calendar">
<div class="recherche">
<div class="recherche btn-group">
<button class="btn btn-light" id="prevMois" @click="prevMois" title="Mois précédant">
<u-icon name="chevron-left"/>
</button>
<select class="form-select btn btn-light" id="otherMois" v-model="mois">
<option v-for="m in listeMois()" :value="m.id">{{ m.libelle }}</option>
</select>
<select class="form-select btn btn-light" id="otherAnnee" v-model="annee">
<option v-for="a in listeAnnees()" :value="a">{{ a }}</option>
</select>
<button class="btn btn-light" id="nextMois" @click="nextMois" title="Mois suivant">
<u-icon name="chevron-right"/>
</button>
</div>
</div>
<table class="table table-bordered table-hover table-sm">
<tr v-for="jour in listeJours" :data-jour="jour">
<th class="nom-jour">
{{ nomJour(jour) }}
</th>
<th class="numero-jour">
<div class="num-jour badge bg-secondary rounded-circle">{{ jour < 10 ? '0' + jour.toString() : jour }}</div>
</th>
<td>
<div class="event" :style="'border-color:'+event.color+';background-color:'+event.bgcolor" v-for="(event, index) in eventsByJour(jour)" :key="index">
<component :is="event.component" :event="event"></component>
</div>
<div v-if="canAddEvent">
<button @click="addEvent" :data-jour="jour" class="btn btn-light btn-sm">
<u-icon name="plus"/>
Nouvel événement
</button>
</div>
</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: 'UCalendar',
props: {
date: {type: Date, required: true},
events: {type: Array, required: true},
canAddEvent: {type: Boolean, required: true, default: true},
},
data()
{
const dateObj = new Date(this.date);
return {
mois: dateObj.getMonth() + 1,
annee: dateObj.getFullYear(),
};
},
computed: {
listeJours()
{
const dateObj = new Date(this.date);
dateObj.setDate(1);
dateObj.setMonth(dateObj.getMonth() + 1);
dateObj.setDate(dateObj.getDate() - 1);
let nombreJours = dateObj.getDate();
return Array.from({length: nombreJours}, (v, k) => k + 1)
},
},
watch: {
date: function (newVal, oldVal) { // watch it
const dateObj = new Date(this.date);
this.mois = dateObj.getMonth() + 1;
this.annee = dateObj.getFullYear();
},
mois: function (newVal, oldVal) { // watch it
const dateObj = new Date(this.date);
dateObj.setMonth(newVal - 1);
this.$emit('changeDate', dateObj);
},
annee: function (newVal, oldVal) { // watch it
const dateObj = new Date(this.date);
dateObj.setFullYear(newVal);
this.$emit('changeDate', dateObj);
}
},
methods: {
nomJour(numJour)
{
const dateObj = new Date(this.date);
dateObj.setDate(numJour);
return dateObj.toLocaleString("fr-FR", {weekday: "short"});
},
listeMois()
{
let mois = [];
const dateObj = new Date();
for (let i = 1; i <= 12; i++) {
dateObj.setMonth(i - 1);
let nomMois = dateObj.toLocaleString("fr-FR", {month: "long"});
mois.push({id: i, libelle: nomMois});
}
return mois;
},
listeAnnees()
{
const dateObj = new Date();
const annee = dateObj.getFullYear();
const range = 1;
let annees = [];
for (let a = annee - range; a <= annee + range; a++) {
annees.push(a);
}
return annees;
},
addEvent(event)
{
const dateObj = new Date(this.date);
dateObj.setDate(event.currentTarget.dataset.jour);
this.$emit('addEvent', dateObj, event);
},
prevMois()
{
const dateObj = new Date(this.date);
dateObj.setMonth(dateObj.getMonth() - 1);
this.$emit('changeDate', dateObj);
},
nextMois()
{
const dateObj = new Date(this.date);
dateObj.setMonth(dateObj.getMonth() + 1);
this.$emit('changeDate', dateObj);
},
eventsByJour(jour)
{
const dateObj = new Date(this.date);
let res = {};
for (let e in this.events) {
let event = this.events[e];
if (event.date.getFullYear() === dateObj.getFullYear()
&& event.date.getMonth() + 1 === dateObj.getMonth() + 1
&& event.date.getDate() === jour
) {
res[e] = event;
}
}
return res;
},
}
}
</script>
<style scoped>
.table tr {
background-color: #f4f4f4;
border-left: 1px #ddd solid;
border-right: 1px #ddd solid;
}
.table-hover tr:hover {
background-color: #f7f7f7;
}
.recherche {
text-align: center;
}
.recherche .btn-group {
box-shadow: none;
margin: auto;
}
.recherche select.btn {
padding-right: 3em;
}
th.nom-jour {
width: 1%;
padding-left: 3px;
}
th.numero-jour {
width: 1%;
padding-right: .5em;
}
.recherche {
justify-content: center;
padding-bottom: 5px;
}
.event {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 3px;
border-left: 10px #bbb solid;
border-right: 10px #bbb solid;
}
.event:hover {
background-color: white;
}
</style>
\ No newline at end of file
<template>
{{ formatted }}
</template>
<script>
export default {
name: "UDate",
props: {
'value': {required: false, type: [String,Date]},
'format': {required: false, type: String},
},
mounted(){
this.formatted = this.formatage(this.value);
},
data(){
return {
formatted: undefined,
};
},
watch: {
'value': function(val){
this.formatted = this.formatage(val);
},
},
methods: {
formatage(val){
if (val === undefined) {
return undefined;
}
let date = new Date(val);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hour = date.getHours().toString().padStart(2, '0');
const min = date.getMinutes().toString().padStart(2, '0');
const sec = date.getSeconds().toString().padStart(2, '0');
switch(this.format){
case 'datetime':
return `${day}/${month}/${year} à ${hour}:${min}`;
case 'time':
return `${hour}:${min}:${sec}`;
}
// format 0 : DATE par défaut
return `${day}/${month}/${year}`;
}
},
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<i :class="`fas fa-${name} text-${variant}`"></i>
</template>
<script>
export default {
name: "UIcon",
props: {
name: {required: true, type: String},
variant: {required: false, type: String},
},
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div class="modal fade" :id="id" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<slot name="body"></slot>
</div>
<div class="modal-footer">
<slot name="footer"></slot>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "UModal",
props: {
id: {required: true, type: String},
title: {required: true, type: String},
},
}
</script>
<style scoped>
</style>
\ No newline at end of file
......@@ -85,3 +85,44 @@ unicaenVue.axios.get(
Lorsqu'il reçoit une réponse du serveur, si cette dernière comporte des messages issus du flashMessenger, il va les afficher sous formes de toasts.
Les toasts sont personnalisées selon qu'il s'agit de success, warning, info ou error. Ils sont affichés pendant 3 secondes, sauf les erreurs qu'il faut fermer
manuellement afin d'avoir le temps de lire le message et de le traiter.
## Bibliothèque de composants personnalisés
UnicaenVue embarque quelques composants que vous pourrez utiliser directement, car il embarque un système d'autoloading de composants basé sur unplugin-vue-component.
Voici les composants directement utilisables :
- UCalendar : calendrier responsive utilisable sur smartphone avec possibilité de définir ses propres composants pour les événements
- UDate : Affiche de manière lisible une date fournie en String au format ISO ou en objet Date JS.
- UIcon : affiche une icône de font-awesome en ne lui donnant que le nom, sans avoir à écrire "fas fa-".
- UModal : dessine une fenêtre modale Bootstrap, vous n'avez plus qu'à écrire le contenu et les entêtes/pied de page.
En attendant une doc plus détaillée, vous pouvez voir les composants [ici](../components).
## Créer votre propre resolver
Si vous avez des composants génériques à utiliser un peu partout et que vous ne voulez pas faire d'import à tout bout de champ, il vous faut mettre en place votre propre résolveur.
C'est faisable facilement, il suffit de les mettre dans un répertoire, et de déclarer le tout dans la config de Vite.
```javascript
import unicaenVue from 'unicaen-vue';
...
const components = unicaenVue.findComponents('/var/www/mon/repertoire/absolu');
...
// bout de config Vite à mettre dans vite.config.js
export default unicaenVue.defineConfig({
...
resolvers: [
(componentName) => {
if (components[componentName] !== undefined) {
return components[componentName];
}
},
]
});
```
\ No newline at end of file
......@@ -16,5 +16,4 @@ Sites officiels :
- [Vue.js](https://vuejs.org/)
- [Vite](https://vitejs.dev)
- [BootstrapVue](https://bootstrap-vue.org/)
- [Axios](https://axios-http.com/)
\ No newline at end of file
const path = require("path");
/**
* Simple object check.
* @param item
* @returns {boolean}
*/
function isObject(item)
{
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
/**
* Permet de fusionner des objets JSON comprenant des sous-objets ou des tableaux
* @param target
* @param ...sources
* @returns Object
*/
function deepMerge(target, ...sources)
{
function deepMerge(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
......@@ -43,14 +42,12 @@ function deepMerge(target, ...sources)
}
/**
* Retourne le répertoire racine d'UnicaenVue
*
* @returns String
*/
function unicaenVueDir()
{
function unicaenVueDir() {
const path = require('path');
// répertoire racine d'UnicaenVue', en partant du répertoire actuel, donc src/Server
......@@ -58,34 +55,80 @@ function unicaenVueDir()
}
/**
* Retourne le répertoire racine de l'application.
*
* @returns String
*/
function projectDir()
{
function projectDir() {
// répertoire racine de l'application
// Attention : le script doit être lancé depuis le bon répertoire...
return process.cwd();
}
/**
* Retourne la liste des composants Vue dans un répertoire donné par nom de composant et fichiers en chemin absolu
*
* Exemple de retour pour les fichiers suivants (chemin absolu) :
* - /var/www/html/prod/monAppli/components/MonComposant.vue
* - /var/www/html/prod/monAppli/components/MonComposant2.vue
* - /var/www/html/prod/monAppli/components/Autres/MonAutre3.vue
*
* findComponents('/var/www/html/prod/monAppli/components') => {
* MonComposant: /var/www/html/prod/monAppli/components/MonComposant.vue,
* MonComposant2: /var/www/html/prod/monAppli/components/MonComposant2.vue,
* AutresMonAutre3: /var/www/html/prod/monAppli/components/Autres/MonAutre3.vue
* }
*
* @param string componentsDir
* @returns {{}}
*/
function findComponents(componentsDir) {
const path = require("path");
const FS = require("fs");
let componentsFiles = [];
function parseComponents(Directory) {
FS.readdirSync(Directory).forEach(File => {
const absolute = path.join(Directory, File);
if (FS.statSync(absolute).isDirectory()) return parseComponents(absolute);
else if (absolute.endsWith('.vue')) {
return componentsFiles.push(absolute.slice(componentsDir.length + 1, -4));
}
});
}
parseComponents(componentsDir);
let components = {};
for (const c in componentsFiles) {
components[componentsFiles[c].replaceAll('/', '')] = path.resolve(componentsDir, componentsFiles[c]) + '.vue';
}
return components;
}
/**
*
* @param Object projectConfig
* @returns {*}
*/
function defineConfig(projectConfig)
{
function defineConfig(projectConfig) {
const path = require('path');
const vue = require('@vitejs/plugin-vue');
const vite = require('vite');
const liveReload = require('vite-plugin-live-reload');
const Components = require('unplugin-vue-components/vite');
const resolvers = require('unplugin-vue-components/resolvers');
const componentsDir = path.resolve(__dirname, '../../components');
const components = findComponents(componentsDir);
//const resolvers = require('unplugin-vue-components/resolvers');
const root = projectConfig.root ? projectConfig.root : 'root';
......@@ -129,8 +172,11 @@ function defineConfig(projectConfig)
}
},
resolvers: [
// Resolver pour que les composants bootstrapVue soient chargés automatiquement
resolvers.BootstrapVueNextResolver(),
(componentName) => {
if (components[componentName] !== undefined) {
return components[componentName];
}
},
],
};
......@@ -145,17 +191,15 @@ function defineConfig(projectConfig)
}
function start()
{
function start() {
}
module.exports = {
defineConfig,
projectDir,
findComponents,
unicaenVueDir,
start
};
\ No newline at end of file
......@@ -10,7 +10,6 @@
"license": "ISC",
"private": true,
"dependencies": {
"bootstrap-vue-next": "^0.7.3",
"vue": "^3.2.45",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/compiler-sfc": "^3.2.45",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment