Router navigation

This commit is contained in:
Joan Sanchez 2022-04-01 12:58:41 +02:00
parent cee5605c63
commit 51015c72f6
11 changed files with 20245 additions and 92 deletions

20135
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,16 +28,17 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.13.14", "@babel/eslint-parser": "^7.13.14",
"@intlify/vue-i18n-loader": "^4.1.0",
"@quasar/app-webpack": "^3.0.0", "@quasar/app-webpack": "^3.0.0",
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.0.1", "@quasar/quasar-app-extension-testing-e2e-cypress": "^4.0.1",
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.9", "@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.9",
"eslint": "^8.10.0", "eslint": "^8.10.0",
"eslint-config-prettier": "^8.1.0", "eslint-config-prettier": "^8.1.0",
"eslint-plugin-cypress": "^2.11.3",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-vue": "^8.5.0", "eslint-plugin-vue": "^8.5.0",
"eslint-webpack-plugin": "^3.1.1", "eslint-webpack-plugin": "^3.1.1",
"prettier": "^2.5.1", "prettier": "^2.5.1"
"eslint-plugin-cypress": "^2.11.3"
}, },
"browserslist": [ "browserslist": [
"last 10 Chrome versions", "last 10 Chrome versions",

View File

@ -67,7 +67,16 @@ module.exports = configure(function (ctx) {
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(chain) { chainWebpack(chain) {
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js', 'vue'] }]); chain.module
.rule("i18n")
.resourceQuery(/blockType=i18n/)
.type('javascript/auto')
.use("i18n")
.loader("@intlify/vue-i18n-loader")
.end();
chain.plugin('eslint-webpack-plugin')
.use(ESLintPlugin, [{ extensions: ['js', 'vue'] }]);
}, },
}, },

View File

@ -5,6 +5,7 @@ import messages from 'src/i18n';
const i18n = createI18n({ const i18n = createI18n({
locale: 'en', locale: 'en',
messages, messages,
legacy: false
}); });
export default boot(({ app }) => { export default boot(({ app }) => {

View File

@ -8,41 +8,38 @@ const { t } = useI18n();
const { hasAny } = useRole(); const { hasAny } = useRole();
const mainRoute = routes.find(route => route.path === '/'); const mainRoute = routes.find(route => route.path === '/');
const modules = mainRoute && mainRoute.children || [] const moduleRoutes = mainRoute && mainRoute.children || []
const items = ref([]); const modules = ref([]);
for (const module of modules) { for (const route of moduleRoutes) {
const item = { const module = {
path: module.path, path: route.path,
name: route.name.toLowerCase(),
roles: [] roles: []
}; };
if (module.meta) { if (route.meta) {
const meta = module.meta; Object.assign(module, route.meta);
item.title = meta.title;
item.icon = meta.icon;
item.roles = meta.roles || [];
} }
items.value.push(item); modules.value.push(module);
} }
</script> </script>
<template> <template>
<q-list padding> <q-list padding>
<template v-for="route in items" :key="route.title"> <template v-for="module in modules" :key="module.title">
<q-item <q-item
clickable clickable
v-ripple v-ripple
:to="{ path: route.path }" :to="{ path: module.path }"
v-if="!route.roles.length || hasAny(route.roles)" v-if="!module.roles.length || hasAny(module.roles)"
active-class="text-orange" active-class="text-orange"
> >
<q-item-section avatar :if="route.icon"> <q-item-section avatar :if="module.icon">
<q-icon :name="route.icon" /> <q-icon :name="module.icon" />
</q-item-section> </q-item-section>
<q-item-section>{{ t(`pages.${route.title}`) }}</q-item-section> <q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
</q-item> </q-item>
</template> </template>
</q-list> </q-list>

View File

@ -16,9 +16,25 @@ export default {
submit: 'Log in', submit: 'Log in',
keepLogin: 'Keep me logged in', keepLogin: 'Keep me logged in',
loginSuccess: 'You have successfully logged in', loginSuccess: 'You have successfully logged in',
loginError: 'Invalid username or password', loginError: 'Invalid username or password'
},
dashboard: {
pageTitles: {
dashboard: 'Dashboard',
}
},
customer: {
pageTitles: {
customers: 'Customers',
list: 'List',
basicData: 'Basic Data'
}
},
ticket: {
pageTitles: {
tickets: 'Tickets'
}
}, },
customer: {},
components: { components: {
topbar: {}, topbar: {},
userPanel: { userPanel: {
@ -26,12 +42,4 @@ export default {
logOut: 'Log Out', logOut: 'Log Out',
}, },
}, },
pages: {
logIn: 'Log In',
dashboard: 'Dashboard',
customers: 'Customers',
list: 'List',
basicData: 'Basic Data',
tickets: 'Tickets',
},
}; };

View File

@ -10,7 +10,7 @@ export default {
statusInternalServerError: 'Ha ocurrido un error interno del servidor', statusInternalServerError: 'Ha ocurrido un error interno del servidor',
}, },
login: { login: {
title: 'Iniciar sesión', title: 'Inicio de sesión',
username: 'Nombre de usuario', username: 'Nombre de usuario',
password: 'Contraseña', password: 'Contraseña',
submit: 'Iniciar sesión', submit: 'Iniciar sesión',
@ -18,7 +18,23 @@ export default {
loginSuccess: 'Inicio de sesión correcto', loginSuccess: 'Inicio de sesión correcto',
loginError: 'Nombre de usuario o contraseña incorrectos', loginError: 'Nombre de usuario o contraseña incorrectos',
}, },
customer: {}, dashboard: {
pageTitles: {
dashboard: 'Tablón',
}
},
customer: {
pageTitles: {
customers: 'Clientes',
list: 'Listado',
basicData: 'Datos básicos'
}
},
ticket: {
pageTitles: {
tickets: 'Tickets'
}
},
components: { components: {
topbar: {}, topbar: {},
userPanel: { userPanel: {

View File

@ -0,0 +1,52 @@
<script setup>
import { reactive, watch } from 'vue'
const customer = reactive({
name: '',
});
watch(() => customer.name, () => {
console.log('customer.name changed');
});
</script>
<template>
<q-page class="q-pa-md">
<q-card class="q-pa-md">
<q-form @submit="onSubmit" @reset="onReset" class="q-gutter-md">
<q-input
filled
v-model="customer.name"
label="Your name *"
hint="Name and surname"
lazy-rules
:rules="[val => val && val.length > 0 || 'Please type something']"
/>
<q-input
filled
type="number"
v-model="age"
label="Your age *"
lazy-rules
:rules="[
val => val !== null && val !== '' || 'Please type your age',
val => val > 0 && val < 100 || 'Please type a real age'
]"
/>
<div>
<q-btn label="Submit" type="submit" color="primary" />
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</q-card>
</q-page>
</template>
<style lang="scss" scoped>
.card {
width: 100%;
max-width: 60em;
}
</style>

View File

@ -12,9 +12,9 @@ const session = useSession();
const router = useRouter(); const router = useRouter();
const { t, locale } = useI18n(); const { t, locale } = useI18n();
let username = ref(''); const username = ref('');
let password = ref(''); const password = ref('');
let keepLogin = ref(true); const keepLogin = ref(true);
const darkMode = computed({ const darkMode = computed({
get() { get() {

View File

@ -39,7 +39,6 @@ export default route(function (/* { store, ssrContext } */) {
if (!isLoggedIn && to.name !== 'Login') { if (!isLoggedIn && to.name !== 'Login') {
next({ path: '/login', query: { redirect: to.fullPath } }); next({ path: '/login', query: { redirect: to.fullPath } });
} else { } else {
const pathRoutes = to.matched; const pathRoutes = to.matched;
const droles = pathRoutes.every(route => { const droles = pathRoutes.every(route => {
const meta = route.meta; const meta = route.meta;
@ -58,33 +57,30 @@ export default route(function (/* { store, ssrContext } */) {
Router.afterEach((to) => { Router.afterEach((to) => {
const { t } = i18n.global; const { t } = i18n.global;
let title = ''; let title = t(`login.title`);
if (to.matched && to.matched.length > 2) { const matches = to.matched;
let moduleName;
const parent = to.matched[1]; if (matches && matches.length > 1) {
const parentMeta = parent.meta; const module = matches[1];
if (parentMeta && parentMeta.title) { const moduleTitle = module.meta && module.meta.title;
title += t(`pages.${parentMeta.title}`); moduleName = module.name.toLowerCase();
if (moduleTitle) {
title = t(`${moduleName}.pageTitles.${moduleTitle}`);
} }
} }
const childMeta = to.meta; const childPage = to.meta;
if (childMeta && childMeta.title) { const childPageTitle = childPage && childPage.title;
if (childPageTitle && matches.length > 2) {
if (title != '') title += ': '; if (title != '') title += ': ';
const childTitle = t(`pages.${childMeta.title}`); const pageTitle = t(`${moduleName}.pageTitles.${childPageTitle}`);
const idParam = to.params && to.params.id;
const idPageTitle = `${idParam} - ${pageTitle}`;
const builtTitle = idParam ? idPageTitle : pageTitle;
if (to.params && to.params.id) { title += builtTitle;
const idParam = to.params.id;
if (idParam instanceof Array) {
title += `${idParam[0]} - ${childTitle}`
} else {
title += `${idParam} - ${childTitle}`;
}
} else {
title += t(`pages.${childMeta.title}`);
}
} }
document.title = title; document.title = title;

View File

@ -17,6 +17,14 @@ export default {
}, },
component: () => import('src/pages/Customer/CustomerList.vue'), component: () => import('src/pages/Customer/CustomerList.vue'),
}, },
{
path: 'create',
name: 'CustomerCreate',
meta: {
title: 'create'
},
component: () => import('src/pages/Customer/CustomerCreate.vue'),
},
{ {
path: ':id', path: ':id',
component: () => import('src/pages/Customer/Card/CustomerCard.vue'), component: () => import('src/pages/Customer/Card/CustomerCard.vue'),