feat: build UI
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
@use "sass:color";
|
||||
@use "bulma/sass/utilities/initial-variables.scss" as *;
|
||||
@use "bulma/sass/utilities/derived-variables.scss" as *;
|
||||
@use "bulma/sass/utilities/mixins.scss" as *;
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900');
|
||||
@use "bulma/sass/components/card.scss" as *;
|
||||
@use "utils.scss";
|
||||
@use "overrides-components.scss";
|
||||
|
||||
:root {
|
||||
--bulma-family-primary: 'Inter', 'SF Pro', 'Helvetica', 'Arial', sans-serif;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
@@ -124,81 +126,15 @@ $size: (
|
||||
// 4. CUSTOM CLASSES
|
||||
// ==========================================
|
||||
|
||||
.fullheight { height: 100vh; }
|
||||
|
||||
.textsize {
|
||||
@include mobile { font-size: 18px; }
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
background: url('/logo_dev.png') no-repeat center center;
|
||||
background-size: 40px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.border-bottom { border-bottom: 1px solid color.change($black-pure, $alpha: 0.15) }
|
||||
|
||||
.carousel-height {
|
||||
width: 100%;
|
||||
height: 80vh;
|
||||
@include mobile { height: 110vh; }
|
||||
}
|
||||
|
||||
// Mobile Spacing Utilities
|
||||
.mobile-mt20 { @include mobile { margin-top: 20px; } }
|
||||
.mobile-px10 { @include mobile { padding-left: 10px; padding-right: 10px; } }
|
||||
.mobile-pt10 { @include mobile { padding-top: 10px; } }
|
||||
.mobile-pt80 { padding-top: 120px; @include mobile { padding-top: 80px; } }
|
||||
|
||||
.fullhd-pt30 {
|
||||
padding-top: 30px;
|
||||
@include until($fullhd) { padding-top: 0px; }
|
||||
}
|
||||
|
||||
.media-width {
|
||||
width: 120px !important;
|
||||
@include mobile { width: 112px !important; }
|
||||
}
|
||||
|
||||
.hideon-mobile { @include mobile { display: none; } }
|
||||
|
||||
// Typography Classes
|
||||
.maintext {
|
||||
margin-top: 20px;
|
||||
font-size: 40px;
|
||||
line-height: 3rem;
|
||||
font-weight: 600;
|
||||
color: $blue-dianne;
|
||||
@include mobile { font-size: 34px; }
|
||||
}
|
||||
|
||||
.subtext {
|
||||
margin-top: 30px;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2rem;
|
||||
color: $cutty-sark;
|
||||
@include mobile { line-height: 1.8rem; }
|
||||
}
|
||||
|
||||
.dotslide {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.activetab {
|
||||
border-radius: 8px;
|
||||
color: $white-pure;
|
||||
background-color: $blue-dianne;
|
||||
}
|
||||
|
||||
// Block Layout
|
||||
.blockdiv {
|
||||
max-width: 1900px !important;
|
||||
background-color: $white-pure;
|
||||
padding: 60px 15px 40px 15px;
|
||||
|
||||
@include until($desktop) { padding: 65px 20px 30px 20px; }
|
||||
@@ -213,31 +149,6 @@ $size: (
|
||||
}
|
||||
}
|
||||
|
||||
.padding-text {
|
||||
padding-left: 15%; padding-right: 5%;
|
||||
@include until($desktop) { padding-left: 0; padding-right: 0; }
|
||||
@include until($fullhd) { padding-left: 0; padding-right: 0; }
|
||||
}
|
||||
|
||||
.padding-image {
|
||||
padding-left: 5%; padding-right: 15%;
|
||||
@include until($fullhd) { padding-left: 0; padding-right: 0; }
|
||||
@include until($desktop) { padding-left: 15%; padding-right: 15%; }
|
||||
@include mobile { padding-left: 0; padding-right: 0; }
|
||||
}
|
||||
|
||||
.imgcontainer {
|
||||
position: relative;
|
||||
width: 100% !important;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.centered {
|
||||
position: absolute;
|
||||
top: 80%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// Tooltip Styles
|
||||
.tooltip {
|
||||
position: relative;
|
||||
@@ -262,8 +173,6 @@ $size: (
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.to-left { right: 30px; }
|
||||
|
||||
@mixin tooltipshow() {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
@@ -277,166 +186,3 @@ $size: (
|
||||
|
||||
.tooltip:hover .tooltiptext { @include tooltipshow() }
|
||||
.tooltip:hover .tooltiptext .to-left { @include tooltipshow() }
|
||||
|
||||
// Dot Indicators
|
||||
@mixin dot($background) {
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
color: $white-pure;
|
||||
font-weight: bold;
|
||||
background-color: $background;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@each $name, $hex in $color {
|
||||
.dot-#{$name} {
|
||||
@include dot($hex);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 5. HELPER CLASSES GENERATOR
|
||||
// ==========================================
|
||||
@each $name, $hex in $color {
|
||||
.bg-#{$name} { background-color: $hex !important; }
|
||||
.text-#{$name} { color: $hex !important; }
|
||||
.border-#{$name} { border-color: $hex !important; }
|
||||
.icon-#{$name} { color: $hex !important; }
|
||||
|
||||
.icon-bg-#{$name} {
|
||||
background-color: $hex !important;
|
||||
color: if(color.channel($hex, "lightness", $space: hsl) > 70, $blue-dianne, $white-pure) !important;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 6. BULMA OVERRIDES
|
||||
// ==========================================
|
||||
|
||||
// Backgrounds
|
||||
.has-background-primary { background-color: $primary-color !important; }
|
||||
.has-background-secondary { background-color: $secondary-color !important; }
|
||||
.has-background-info { background-color: $info-color !important; }
|
||||
.has-background-success { background-color: $success-color !important; }
|
||||
.has-background-warning { background-color: $warning-color !important; }
|
||||
.has-background-danger { background-color: $danger-color !important; }
|
||||
.has-background-light { background-color: $light-color !important; }
|
||||
.has-background-dark { background-color: $dark-color !important; }
|
||||
.has-background-white { background-color: $white-pure !important; }
|
||||
|
||||
// Text Colors
|
||||
.has-text-primary { color: #086e71 !important; }
|
||||
.has-text-secondary { color: $secondary-color !important; }
|
||||
.has-text-info { color: $info-color !important; }
|
||||
.has-text-success { color: $success-color !important; }
|
||||
.has-text-warning { color: $warning-color !important; }
|
||||
.has-text-danger { color: $danger-color !important; }
|
||||
.has-text-light { color: $accent-color !important; }
|
||||
.has-text-dark { color: $dark-color !important; }
|
||||
|
||||
// Button/Element States (is-*)
|
||||
.is-primary {
|
||||
background-color: $primary-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $white-pure !important;
|
||||
}
|
||||
|
||||
.is-secondary {
|
||||
background-color: $secondary-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $white-pure !important;
|
||||
}
|
||||
|
||||
.is-link {
|
||||
background-color: $link-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $white-pure !important;
|
||||
}
|
||||
|
||||
.is-info {
|
||||
background-color: $info-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $white-pure !important;
|
||||
}
|
||||
|
||||
.is-success {
|
||||
background-color: $success-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $white-pure !important;
|
||||
}
|
||||
|
||||
.is-warning {
|
||||
background-color: $warning-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $white-pure !important;
|
||||
}
|
||||
|
||||
.is-danger {
|
||||
background-color: $danger-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $white-pure !important;
|
||||
}
|
||||
|
||||
.is-light {
|
||||
background-color: $light-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $blue-dianne !important;
|
||||
}
|
||||
|
||||
.is-dark {
|
||||
background-color: $dark-color !important;
|
||||
border-color: transparent !important;
|
||||
color: $white-pure !important;
|
||||
}
|
||||
|
||||
// Outlined Variants
|
||||
.is-primary.is-outlined {
|
||||
background-color: transparent !important;
|
||||
border-color: $primary-color !important;
|
||||
color: $primary-color !important;
|
||||
}
|
||||
|
||||
.is-link.is-outlined {
|
||||
background-color: transparent !important;
|
||||
border-color: $link-color !important;
|
||||
color: $link-color !important;
|
||||
}
|
||||
|
||||
.is-info.is-outlined {
|
||||
background-color: transparent !important;
|
||||
border-color: $info-color !important;
|
||||
color: $info-color !important;
|
||||
}
|
||||
|
||||
.is-success.is-outlined {
|
||||
background-color: transparent !important;
|
||||
border-color: $success-color !important;
|
||||
color: $success-color !important;
|
||||
}
|
||||
|
||||
.is-warning.is-outlined {
|
||||
background-color: transparent !important;
|
||||
border-color: $warning-color !important;
|
||||
color: $warning-color !important;
|
||||
}
|
||||
|
||||
.is-primary.is-light {
|
||||
background-color: rgba($primary-color, 0.1) !important;
|
||||
color: $primary-color !important;
|
||||
}
|
||||
|
||||
.is-info.is-light {
|
||||
background-color: rgba($info-color, 0.2) !important;
|
||||
color: color.adjust($info-color, $lightness: -10%) !important;
|
||||
}
|
||||
|
||||
.is-success.is-light {
|
||||
background-color: rgba($success-color, 0.1) !important;
|
||||
color: $success-color !important;
|
||||
}
|
||||
6
app/assets/styles/overrides-components.scss
Normal file
6
app/assets/styles/overrides-components.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
@use "bulma/sass/utilities/initial-variables.scss" as *;
|
||||
|
||||
.card {
|
||||
--bulma-card-shadow: none;
|
||||
border: 1px solid $grey-lighter;
|
||||
}
|
||||
237
app/assets/styles/utils.scss
Normal file
237
app/assets/styles/utils.scss
Normal file
@@ -0,0 +1,237 @@
|
||||
@use "sass:list";
|
||||
|
||||
.font-thin {
|
||||
font-weight: 100;
|
||||
}
|
||||
.font-extralight {
|
||||
font-weight: 200;
|
||||
}
|
||||
.font-light {
|
||||
font-weight: 300;
|
||||
}
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
.font-semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-extrabold {
|
||||
font-weight: 800;
|
||||
}
|
||||
.font-black {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.rounded-xs {
|
||||
border-radius: 0.125rem;
|
||||
}
|
||||
.rounded-sm {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.rounded-md {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.rounded-xl {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
.rounded-2xl {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.rounded-3xl {
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
.rounded-4xl {
|
||||
border-radius: 2rem;
|
||||
}
|
||||
.rounded-none {
|
||||
border-radius: 0;
|
||||
}
|
||||
.rounded-full {
|
||||
border-radius: calc(infinity * 1px);
|
||||
}
|
||||
|
||||
$base: 0.25rem;
|
||||
|
||||
@function spacing($step) {
|
||||
@return $step * $base;
|
||||
}
|
||||
|
||||
$spacing-types: (
|
||||
"p": "padding",
|
||||
"m": "margin",
|
||||
);
|
||||
|
||||
$spacing-variants: (
|
||||
"": (""),
|
||||
"x": ("-left", "-right"),
|
||||
"y": ("-top", "-bottom"),
|
||||
"t": ("-top"),
|
||||
"b": ("-bottom"),
|
||||
"l": ("-left"),
|
||||
"r": ("-right"),
|
||||
);
|
||||
|
||||
@each $type-prefix, $type in $spacing-types {
|
||||
@each $dir-suffix, $sides in $spacing-variants {
|
||||
@for $i from 0 through 48 {
|
||||
// Whole step: p-0, p-1, p-2 ...
|
||||
.#{$type-prefix}#{$dir-suffix}-#{$i} {
|
||||
@each $side in $sides {
|
||||
#{$type}#{$side}: spacing($i);
|
||||
}
|
||||
}
|
||||
|
||||
// Half step: p-0\.5, p-1\.5, p-2\.5 ...
|
||||
// Stop at 47.5 — 48.5 would exceed the scale
|
||||
@if $i < 48 {
|
||||
.#{$type-prefix}#{$dir-suffix}-#{$i}\.5 {
|
||||
@each $side in $sides {
|
||||
#{$type}#{$side}: spacing($i + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── CSS custom properties ─────────────────────────────────────────────────
|
||||
:root {
|
||||
--spacing: 0.25rem;
|
||||
--container-3xs: 16rem;
|
||||
--container-2xs: 18rem;
|
||||
--container-xs: 20rem;
|
||||
--container-sm: 24rem;
|
||||
--container-md: 28rem;
|
||||
--container-lg: 32rem;
|
||||
--container-xl: 36rem;
|
||||
--container-2xl: 42rem;
|
||||
--container-3xl: 48rem;
|
||||
--container-4xl: 56rem;
|
||||
--container-5xl: 64rem;
|
||||
--container-6xl: 72rem;
|
||||
--container-7xl: 80rem;
|
||||
}
|
||||
|
||||
// ─── Shared mixin ──────────────────────────────────────────────────────────
|
||||
@mixin set-props($props, $value) {
|
||||
@each $prop in $props {
|
||||
#{$prop}: $value;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Class types ───────────────────────────────────────────────────────────
|
||||
$class-types: (
|
||||
"w": (width),
|
||||
"h": (height),
|
||||
"size": (width, height),
|
||||
);
|
||||
|
||||
// ─── Numeric: w-0 → w-48, h-0 → h-48, size-0 → size-48 ───────────────────
|
||||
@each $prefix, $props in $class-types {
|
||||
@for $i from 0 through 48 {
|
||||
.#{$prefix}-#{$i} {
|
||||
@include set-props($props, calc(var(--spacing) * #{$i}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Fractions ─────────────────────────────────────────────────────────────
|
||||
$fractions: (
|
||||
"1\\/2": (1, 2),
|
||||
"1\\/3": (1, 3),
|
||||
"2\\/3": (2, 3),
|
||||
"1\\/4": (1, 4),
|
||||
"2\\/4": (2, 4),
|
||||
"3\\/4": (3, 4),
|
||||
"1\\/5": (1, 5),
|
||||
"2\\/5": (2, 5),
|
||||
"3\\/5": (3, 5),
|
||||
"4\\/5": (4, 5),
|
||||
"1\\/6": (1, 6),
|
||||
"2\\/6": (2, 6),
|
||||
"3\\/6": (3, 6),
|
||||
"4\\/6": (4, 6),
|
||||
"5\\/6": (5, 6),
|
||||
"1\\/12": (1, 12),
|
||||
"2\\/12": (2, 12),
|
||||
"3\\/12": (3, 12),
|
||||
"4\\/12": (4, 12),
|
||||
"5\\/12": (5, 12),
|
||||
"6\\/12": (6, 12),
|
||||
"7\\/12": (7, 12),
|
||||
"8\\/12": (8, 12),
|
||||
"9\\/12": (9, 12),
|
||||
"10\\/12": (10, 12),
|
||||
"11\\/12": (11, 12),
|
||||
);
|
||||
|
||||
@each $prefix, $props in $class-types {
|
||||
@each $name, $pair in $fractions {
|
||||
$num: list.nth($pair, 1);
|
||||
$den: list.nth($pair, 2);
|
||||
.#{$prefix}-#{$name} {
|
||||
@include set-props($props, calc(#{$num} / #{$den} * 100%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Container sizes (w- only) ─────────────────────────────────────────────
|
||||
$containers: (
|
||||
"3xs", "2xs", "xs", "sm", "md", "lg", "xl",
|
||||
"2xl", "3xl", "4xl", "5xl", "6xl", "7xl"
|
||||
);
|
||||
|
||||
@each $name in $containers {
|
||||
.w-#{$name} { width: var(--container-#{$name}); }
|
||||
}
|
||||
|
||||
// ─── Shared keywords (auto, px, full, min, max, fit) ───────────────────────
|
||||
// These are identical in value across w-, h-, and size-
|
||||
$shared-keywords: (
|
||||
"auto": auto,
|
||||
"px": 1px,
|
||||
"full": 100%,
|
||||
"min": min-content,
|
||||
"max": max-content,
|
||||
"fit": fit-content,
|
||||
);
|
||||
|
||||
@each $prefix, $props in $class-types {
|
||||
@each $name, $value in $shared-keywords {
|
||||
.#{$prefix}-#{$name} {
|
||||
@include set-props($props, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Viewport keywords ─────────────────────────────────────────────────────
|
||||
// dvw/dvh/lvw/lvh/svw/svh apply equally to w-, h-, and size-
|
||||
$viewport-keywords: (
|
||||
"dvw": 100dvw,
|
||||
"dvh": 100dvh,
|
||||
"lvw": 100lvw,
|
||||
"lvh": 100lvh,
|
||||
"svw": 100svw,
|
||||
"svh": 100svh,
|
||||
);
|
||||
|
||||
@each $prefix, $props in $class-types {
|
||||
@each $name, $value in $viewport-keywords {
|
||||
.#{$prefix}-#{$name} {
|
||||
@include set-props($props, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.w-screen { width: 100vw; }
|
||||
.h-screen { height: 100vh; }
|
||||
@@ -1,11 +1,20 @@
|
||||
<template>
|
||||
<nav class="navbar is-fixed-top has-shadow px-3" role="navigation">
|
||||
<div class="navbar-brand mr-5">
|
||||
<span class="navbar-item">
|
||||
<SvgIcon v-bind="{ name: 'dot.svg', size: 18, type: 'primary' }"</SvgIcon>
|
||||
<span class="fsb-20 has-text-primary">{{$dayjs().format('DD/MM')}}</span>
|
||||
<span class="navbar-item is-gap-1">
|
||||
<div style="width: 16px; height: 16px" class="has-background-primary rounded-full"></div>
|
||||
<span class="fs-17 font-semibold has-text-primary">{{$dayjs().format('DD/MM')}}</span>
|
||||
</span>
|
||||
<a class="navbar-item header-logo" @click="changeTab(leftmenu[0])"></a>
|
||||
<a
|
||||
class="navbar-item p-0 has-text-primary"
|
||||
@click="changeTab(leftmenu[0])"
|
||||
>
|
||||
<svg
|
||||
style="max-height: none; width: 44px"
|
||||
width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor" d="M40 8C57.6731 8 72 22.3269 72 40C72 57.6731 57.6731 72 40 72C22.3269 72 8 57.6731 8 40C8 22.3269 22.3269 8 40 8ZM15.7285 31.5762V49.2266H21.9854C23.778 49.2266 25.3185 48.8727 26.6055 48.166C27.898 47.4593 28.8887 46.4453 29.5781 45.124C30.2733 43.8025 30.6211 42.2224 30.6211 40.3838C30.6211 38.5511 30.2733 36.9768 29.5781 35.6611C28.8887 34.3455 27.9033 33.3367 26.6221 32.6357C25.3409 31.9292 23.8123 31.5762 22.0371 31.5762H15.7285ZM33.3857 49.2266H45.3135V46.1494H37.1172V41.9355H44.667V38.8584H37.1172V34.6533H45.2793V31.5762H33.3857V49.2266ZM53.3818 49.2266H58.1914L64.2754 31.5762H60.1387L55.8643 44.9863H55.7002L51.4346 31.5762H47.2891L53.3818 49.2266ZM21.8389 34.7734C22.942 34.7734 23.8704 34.9687 24.623 35.3594C25.3756 35.75 25.9411 36.3593 26.3203 37.1865C26.7052 38.0138 26.8984 39.0797 26.8984 40.3838C26.8984 41.6995 26.7053 42.7743 26.3203 43.6074C25.9411 44.4346 25.3726 45.047 24.6143 45.4434C23.8617 45.8339 22.9339 46.0292 21.8311 46.0293H19.4609V34.7734H21.8389Z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
role="button"
|
||||
class="navbar-burger"
|
||||
@@ -22,23 +31,23 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu" id="navMenu">
|
||||
<div class="navbar-start" style="min-width: 650px;">
|
||||
<div class="navbar-start is-gap-1 is-align-items-center" style="min-width: 650px;">
|
||||
<template v-for="(v, i) in leftmenu" :key="i" :id="v.code">
|
||||
<a class="navbar-item px-2" v-if="!v.submenu" @click="changeTab(v)">
|
||||
<span
|
||||
:class="`fsb-17 ${currentTab.code === v.code ? 'activetab' : ''}`"
|
||||
style="padding: 3px 4px"
|
||||
>
|
||||
<a class="navbar-item rounded-lg is-clipped p-0" v-if="!v.submenu" @click="changeTab(v)">
|
||||
<span :class="[
|
||||
'px-3 py-1.5 fs-14 font-medium',
|
||||
currentTab.code === v.code ? 'has-text-primary-50 has-background-primary-95' : 'has-text-grey-30'
|
||||
]">
|
||||
{{ v[lang] }}
|
||||
</span>
|
||||
</a>
|
||||
<div class="navbar-item has-dropdown is-hoverable" v-else>
|
||||
<a class="navbar-item px-2" @click="changeTab(v)">
|
||||
<span
|
||||
:class="`icon-text ${currentTab.code === v.code ? 'activetab' : ''}`"
|
||||
style="padding: 3px 4px"
|
||||
>
|
||||
<span class="fsb-16">{{ v[lang] }}</span>
|
||||
<span :class="[
|
||||
'px-3 py-1 font-medium',
|
||||
currentTab.code === v.code ? 'has-text-primary-bold has-background-primary-soft' : 'has-text-grey-30'
|
||||
]">
|
||||
<span>{{ v[lang] }}</span>
|
||||
<SvgIcon
|
||||
style="padding-top: 5px"
|
||||
v-bind="{ name: 'down2.svg', type: currentTab.code === v.code ? 'white' : 'dark', size: 15 }"
|
||||
@@ -48,7 +57,7 @@
|
||||
</a>
|
||||
<div class="navbar-dropdown has-background-light">
|
||||
<a
|
||||
class="navbar-item has-background-light fs-15 has-text-black py-1 border-bottom"
|
||||
class="navbar-item has-background-light py-1 border-bottom"
|
||||
v-for="x in v.submenu"
|
||||
@click="changeTab(v, x)"
|
||||
>
|
||||
|
||||
27
app/components/dashboard/AvatarBox.vue
Normal file
27
app/components/dashboard/AvatarBox.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div
|
||||
class="fs-13 font-semibold mx-0 px-0 is-flex is-justify-content-center is-align-items-center is-flex-shrink-0 rounded-full size-10"
|
||||
style="border: 1px solid var(--bulma-grey-80)"
|
||||
:style="image && 'border: none'">
|
||||
<div>
|
||||
<span>{{text}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['text', 'image', 'type', 'size']
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.cbox {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--grey-100);
|
||||
border: 1px solid hsl(0, 0%, 85%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,90 @@
|
||||
<script setup>
|
||||
import DashboardHighlightCard from '@/components/dashboard/DashboardHighlightCard.vue';
|
||||
import Delivery from '@/components/dashboard/Delivery.vue';
|
||||
import OrderStatus from '@/components/dashboard/OrderStatus.vue';
|
||||
import RevenueChart from '@/components/dashboard/RevenueChart.vue';
|
||||
import TopCustomers from '@/components/dashboard/TopCustomers.vue';
|
||||
import TopProducts from '@/components/dashboard/TopProducts.vue';
|
||||
import Warnings from '@/components/dashboard/Warnings.vue';
|
||||
|
||||
const highlights = [
|
||||
{
|
||||
name: 'Doanh thu hôm nay',
|
||||
value: '72.5M',
|
||||
color: 'blue',
|
||||
icon: 'material-symbols:attach-money-rounded',
|
||||
subheader: {
|
||||
value: '+12.5%',
|
||||
fluctuation: 'up',
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Số đơn hàng',
|
||||
value: '73',
|
||||
color: 'purple',
|
||||
icon: 'material-symbols:shopping-cart-outline-rounded',
|
||||
subheader: {
|
||||
value: '+8 đơn',
|
||||
fluctuation: 'up',
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Đơn đang giao',
|
||||
value: '8',
|
||||
color: 'orange',
|
||||
icon: 'material-symbols:delivery-truck-speed-outline-rounded',
|
||||
},
|
||||
{
|
||||
name: 'Đơn hoàn thành',
|
||||
value: '38',
|
||||
color: 'green',
|
||||
icon: 'material-symbols:check-circle-outline-rounded',
|
||||
},
|
||||
{
|
||||
name: 'Công nợ phải thu',
|
||||
value: '245M',
|
||||
color: 'red',
|
||||
icon: 'material-symbols:universal-currency-alt-outline-rounded',
|
||||
subheader: {
|
||||
value: '+15M',
|
||||
fluctuation: 'down',
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Tồn kho',
|
||||
value: '1,250',
|
||||
color: 'cyan',
|
||||
icon: 'material-symbols:box-outline-rounded',
|
||||
subheader: {
|
||||
value: '-45 SP',
|
||||
fluctuation: 'down',
|
||||
}
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
Dashboard
|
||||
<div>
|
||||
<div class="fixed-grid has-2-cols has-3-cols-tablet has-6-cols-desktop">
|
||||
<div class="grid">
|
||||
<DashboardHighlightCard
|
||||
v-for="highlight in highlights"
|
||||
v-bind="highlight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-grid has-3-cols">
|
||||
<div class="grid">
|
||||
<div class="cell is-col-span-2">
|
||||
<RevenueChart />
|
||||
</div>
|
||||
<div class="cell">
|
||||
<TopCustomers />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TopProducts />
|
||||
<OrderStatus />
|
||||
<Delivery />
|
||||
<Warnings />
|
||||
</div>
|
||||
</template>
|
||||
41
app/components/dashboard/DashboardHighlightCard.vue
Normal file
41
app/components/dashboard/DashboardHighlightCard.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
value: String,
|
||||
color: String,
|
||||
icon: String,
|
||||
subheader: Object
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="cell">
|
||||
<div class="card h-full">
|
||||
<div class="card-content is-flex is-gap-0.5 is-justify-content-space-between">
|
||||
<div>
|
||||
<p class="fs-14 has-text-grey mb-1">{{ name }}</p>
|
||||
<p class="fsb-26 mb-1 has-text-black">{{ value }}</p>
|
||||
<div v-if="subheader"
|
||||
:class="[
|
||||
'is-flex is-gap-0.5 is-align-items-center',
|
||||
subheader.fluctuation === 'up' ? 'has-text-green-40' : 'has-text-red-40'
|
||||
]"
|
||||
>
|
||||
<Icon
|
||||
:name="subheader.fluctuation === 'up' ? 'material-symbols:arrow-upward-rounded' :'material-symbols:arrow-downward-rounded'"
|
||||
color="inherit"
|
||||
/>
|
||||
<p class="fs-14">{{ subheader.value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
'rounded-lg size-12 is-flex-shrink-0 is-flex is-justify-content-center is-align-items-center',
|
||||
`has-background-${color}-soft has-text-${color}-40`
|
||||
]"
|
||||
">
|
||||
<Icon :name="icon" :size="28" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
53
app/components/dashboard/Delivery.vue
Normal file
53
app/components/dashboard/Delivery.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup>
|
||||
import Driver from '@/components/dashboard/Driver.vue';
|
||||
|
||||
const drivers = [
|
||||
{
|
||||
name: 'Nguyễn Văn A',
|
||||
status: 'Đang giao',
|
||||
deliveries: 3,
|
||||
deliveries_completed: 2,
|
||||
},
|
||||
{
|
||||
name: 'Trần Văn B',
|
||||
status: 'Đang giao',
|
||||
deliveries: 2,
|
||||
deliveries_completed: 1,
|
||||
},
|
||||
{
|
||||
name: 'Lê Thị C',
|
||||
status: 'Hoàn thành',
|
||||
deliveries: 4,
|
||||
deliveries_completed: 4,
|
||||
},
|
||||
{
|
||||
name: 'Phạm Văn D',
|
||||
status: 'Đang giao',
|
||||
deliveries: 1,
|
||||
deliveries_completed: 0,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fs-17 font-semibold mb-4">Giao nhận & Tài xế</p>
|
||||
<div class="fixed-grid has-3-cols">
|
||||
<div class="grid">
|
||||
<div class="cell is-col-span-2">
|
||||
<div style="border-radius: 0.5rem; overflow: hidden">
|
||||
<NuxtImg src="/map-demo.png" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell is-flex is-flex-direction-column is-gap-2">
|
||||
<Driver
|
||||
v-for="driver in drivers"
|
||||
:key="driver.name"
|
||||
v-bind="driver"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
35
app/components/dashboard/Driver.vue
Normal file
35
app/components/dashboard/Driver.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import AvatarBox from '@/components/dashboard/AvatarBox.vue';
|
||||
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
status: String,
|
||||
deliveries: Number,
|
||||
deliveries_completed: Number,
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="is-flex is-gap-2 fs-14 p-3 rounded-lg"
|
||||
:style="{
|
||||
border: '1px solid var(--bulma-grey-80)',
|
||||
}"
|
||||
>
|
||||
<AvatarBox :text="name.slice(0, 2)" />
|
||||
<div class="is-flex-grow-1">
|
||||
<div class="is-flex is-gap-1 is-align-items-center">
|
||||
<p>{{ name }}</p>
|
||||
<span :class="['tag', status === 'Đang giao' ? 'is-warning' : 'is-success']">{{ status }}</span>
|
||||
</div>
|
||||
<p class="fs-13 has-text-grey">Đơn: {{ deliveries_completed }}/{{ deliveries }}</p>
|
||||
<progress
|
||||
v-if="deliveries !== deliveries_completed"
|
||||
class="progress is-small is-primary mt-2"
|
||||
style="--bulma-size-small: 0.4rem;"
|
||||
:value="deliveries_completed"
|
||||
:max="deliveries"
|
||||
>
|
||||
</progress>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
54
app/components/dashboard/OrderStatus.vue
Normal file
54
app/components/dashboard/OrderStatus.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
import OrderStatusCard from '@/components/dashboard/OrderStatusCard.vue';
|
||||
|
||||
const statuses = [
|
||||
{
|
||||
id: 1,
|
||||
code: 'pending',
|
||||
name: 'Chờ xử lý',
|
||||
value: 12,
|
||||
color: 'orange',
|
||||
icon: 'material-symbols:clock-loader-40'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
code: 'delivering',
|
||||
name: 'Đang giao',
|
||||
value: 8,
|
||||
color: 'blue',
|
||||
icon: 'material-symbols:delivery-truck-speed-outline-rounded'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
code: 'delivered',
|
||||
name: 'Đã giao',
|
||||
value: 15,
|
||||
color: 'purple',
|
||||
icon: 'material-symbols:bucket-check-outline-rounded'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
code: 'completed',
|
||||
name: 'Hoàn thành',
|
||||
value: 38,
|
||||
color: 'green',
|
||||
icon: 'material-symbols:check-circle-outline-rounded'
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-full">
|
||||
<div class="card-content">
|
||||
<p class="fs-17 font-semibold mb-4">Trạng thái đơn hàng</p>
|
||||
<div class="fixed-grid has-2-cols-mobile has-4-cols">
|
||||
<div class="grid">
|
||||
<OrderStatusCard
|
||||
v-for="status in statuses"
|
||||
:key="status.name"
|
||||
v-bind="status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
33
app/components/dashboard/OrderStatusCard.vue
Normal file
33
app/components/dashboard/OrderStatusCard.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
id: Number,
|
||||
code: String,
|
||||
name: String,
|
||||
value: Number,
|
||||
icon: String,
|
||||
color: String,
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="cell">
|
||||
<div class="card" :style="{ border: `1px solid var(--bulma-${color}-70)` }">
|
||||
<div class="card-content is-flex is-flex-direction-column is-align-items-center is-gap-1">
|
||||
<div
|
||||
:class="[
|
||||
'p-3 is-flex rounded-full',
|
||||
`has-background-${color}-90`,
|
||||
`has-text-${color}-40`,
|
||||
]" >
|
||||
<Icon
|
||||
:name="icon"
|
||||
:size="24"
|
||||
/>
|
||||
</div>
|
||||
<div class="is-flex is-flex-direction-column is-align-items-center">
|
||||
<p class="fs-24 font-bold">{{ value }}</p>
|
||||
<p class="has-text-grey">{{ name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
61
app/components/dashboard/RevenueChart.vue
Normal file
61
app/components/dashboard/RevenueChart.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
const { $shortenCurrency } = useNuxtApp();
|
||||
const revenueChartOptions = {
|
||||
chart: {
|
||||
type: 'spline'
|
||||
},
|
||||
credits: {
|
||||
enabled: false,
|
||||
},
|
||||
title: {
|
||||
text: null,
|
||||
},
|
||||
xAxis: {
|
||||
categories: [
|
||||
'10/4', '11/4', '12/4', '13/4', '14/4', '15/4', '16/4', '17/4', '18/4'
|
||||
],
|
||||
accessibility: {
|
||||
description: 'Dates'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: 'Doanh thu'
|
||||
},
|
||||
labels: {
|
||||
format: '{value}'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
crosshairs: true,
|
||||
shared: true,
|
||||
valueSuffix: ' VNĐ',
|
||||
},
|
||||
plotOptions: {
|
||||
spline: {
|
||||
marker: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'Doanh thu',
|
||||
data: [45000000, 52000000, 48000000, 51000000, 58000000, 61000000, 67500000, 72000000, 69000000],
|
||||
showInLegend: false,
|
||||
}]
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="is-flex is-justify-content-space-between mb-2">
|
||||
<p style="font-weight: 600; font-size: 18px">Doanh thu theo ngày</p>
|
||||
<div class="buttons">
|
||||
<button class="button is-primary fs-14">7 ngày</button>
|
||||
<button class="button is-white fs-14">30 ngày</button>
|
||||
</div>
|
||||
</div>
|
||||
<highcharts :options="revenueChartOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
23
app/components/dashboard/TopCustomer.vue
Normal file
23
app/components/dashboard/TopCustomer.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
import AvatarBox from '@/components/dashboard/AvatarBox.vue';
|
||||
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
order_count: Number,
|
||||
paid: Number,
|
||||
})
|
||||
|
||||
const { $shortenCurrency } = useNuxtApp();
|
||||
</script>
|
||||
<template>
|
||||
<div class="is-flex is-gap-1 is-justify-content-space-between">
|
||||
<div class="is-flex is-gap-1">
|
||||
<AvatarBox :text="name.slice(0, 2)" />
|
||||
<div>
|
||||
<p>{{ name }}</p>
|
||||
<p class="fs-13 has-text-grey">{{ order_count }} đơn hàng</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="font-semibold">{{ $shortenCurrency(paid) }}</p>
|
||||
</div>
|
||||
</template>
|
||||
47
app/components/dashboard/TopCustomers.vue
Normal file
47
app/components/dashboard/TopCustomers.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
import TopCustomer from '@/components/dashboard/TopCustomer.vue';
|
||||
|
||||
|
||||
const customers = [
|
||||
{
|
||||
name: 'Công ty TNHH ABC',
|
||||
order_count: 45,
|
||||
paid: 125000000,
|
||||
},
|
||||
{
|
||||
name: 'Siêu thị XYZ',
|
||||
order_count: 38,
|
||||
paid: 98000000,
|
||||
},
|
||||
{
|
||||
name: 'Nhà hàng Đông Dương',
|
||||
order_count: 32,
|
||||
paid: 87000000,
|
||||
},
|
||||
{
|
||||
name: 'Khách sạn Mường Thanh',
|
||||
order_count: 28,
|
||||
paid: 76000000,
|
||||
},
|
||||
{
|
||||
name: 'Cửa hàng Bách Hoá',
|
||||
order_count: 24,
|
||||
paid: 64000000,
|
||||
},
|
||||
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fs-17 font-semibold mb-4">Top khách hàng</p>
|
||||
<div class="is-flex is-flex-direction-column is-gap-1.5">
|
||||
<TopCustomer
|
||||
v-for="cus in customers"
|
||||
:key="cus.name"
|
||||
v-bind="cus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
29
app/components/dashboard/TopProduct.vue
Normal file
29
app/components/dashboard/TopProduct.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
sold_count: Number,
|
||||
revenue: Number,
|
||||
topRevenue: Number,
|
||||
});
|
||||
|
||||
const { $shortenCurrency } = useNuxtApp();
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="is-flex is-gap-1 is-justify-content-space-between mb-2">
|
||||
<div>
|
||||
<p>{{ name }}</p>
|
||||
<p class="fs-13 has-text-grey-60">Đã bán {{ sold_count }} sản phẩm</p>
|
||||
</div>
|
||||
<p class="font-semibold">{{ $shortenCurrency(revenue) }}</p>
|
||||
</div>
|
||||
<progress class="progress is-small is-primary" :value="revenue" :max="topRevenue">
|
||||
15%
|
||||
</progress>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.progress {
|
||||
--bulma-size-small: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
48
app/components/dashboard/TopProducts.vue
Normal file
48
app/components/dashboard/TopProducts.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import TopProduct from '@/components/dashboard/TopProduct.vue';
|
||||
|
||||
const products = [
|
||||
{
|
||||
name: 'Gạo ST25 - Bao 5kg',
|
||||
sold_count: 1250,
|
||||
revenue: 156000000
|
||||
},
|
||||
{
|
||||
name: 'Nước mắm Phú Quốc - Chai 500ml',
|
||||
sold_count: 980,
|
||||
revenue: 132000000
|
||||
},
|
||||
{
|
||||
name: 'Đường tinh luyện - Bao 1kg',
|
||||
sold_count: 856,
|
||||
revenue: 98000000
|
||||
},
|
||||
{
|
||||
name: 'Dầu ăn Neptune - Chai 1L',
|
||||
sold_count: 742,
|
||||
revenue: 87000000
|
||||
},
|
||||
{
|
||||
name: 'Bột mì đa dụng - Bao 1kg',
|
||||
sold_count: 623,
|
||||
revenue: 72000000
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fs-17 font-semibold mb-4">Top sản phẩm</p>
|
||||
<div class="is-flex is-flex-direction-column is-gap-2">
|
||||
<TopProduct
|
||||
v-for="product in products"
|
||||
:key="product.name"
|
||||
v-bind="{
|
||||
...product,
|
||||
topRevenue: products[0].revenue
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
36
app/components/dashboard/Warning.vue
Normal file
36
app/components/dashboard/Warning.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
details: String,
|
||||
level: Number,
|
||||
type: String
|
||||
})
|
||||
|
||||
const color = computed(() => {
|
||||
if (props.level === 1) return 'yellow';
|
||||
if (props.level === 2) return 'orange';
|
||||
if (props.level === 3) return 'red';
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="['card m-0', `has-background-${color}-95`]"
|
||||
:style="{
|
||||
borderColor: `var(--bulma-${color}-70)`,
|
||||
}"
|
||||
>
|
||||
<div class="card-content p-4 is-flex is-align-items-center is-gap-2">
|
||||
<Icon
|
||||
:name="type === 'time' ? 'material-symbols:clock-loader-40' : 'material-symbols:box-outline-rounded'"
|
||||
:size="20"
|
||||
:class="`has-text-${color}-45`"
|
||||
/>
|
||||
<div>
|
||||
<p :class="`has-text-${color}-40`">{{ name }}</p>
|
||||
<p class="fs-13 has-text-grey">{{ details }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
38
app/components/dashboard/Warnings.vue
Normal file
38
app/components/dashboard/Warnings.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import Warning from '@/components/dashboard/Warning.vue';
|
||||
|
||||
const warnings = [
|
||||
{
|
||||
name: 'Công nợ sắp đến hạn',
|
||||
details: 'Công ty TNHH ABC - 35.000.000đ - Hạn: 25/03/2026',
|
||||
level: 1,
|
||||
type: 'time',
|
||||
},
|
||||
{
|
||||
name: 'Đơn giao trễ',
|
||||
details: 'Đơn hàng #DH-2156 - Đã trễ 2 giờ - Khách: Siêu thị XYZ',
|
||||
level: 3,
|
||||
type: 'time',
|
||||
},
|
||||
{
|
||||
name: 'Tồn kho thấp',
|
||||
details: 'Gạo ST25 - Chỉ còn 45 bao - Cần nhập thêm',
|
||||
level: 2,
|
||||
type: 'inventory'
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fs-17 font-semibold mb-4">Cảnh báo và thông báo</p>
|
||||
<div class="is-flex is-flex-direction-column is-gap-1">
|
||||
<Warning
|
||||
v-for="warning in warnings"
|
||||
:key="warning.details"
|
||||
v-bind="warning"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +1,43 @@
|
||||
<script setup>
|
||||
import InventoryHighlightCard from '@/components/inventory/InventoryHighlightCard.vue';
|
||||
|
||||
const inventoryHighlights = [
|
||||
{
|
||||
name: 'Tổng số SKU',
|
||||
value: 12,
|
||||
icon: 'material-symbols:deployed-code-outline',
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
name: 'Tổng tồn kho',
|
||||
value: '1,120',
|
||||
icon: 'material-symbols:box-outline-rounded',
|
||||
color: 'green',
|
||||
unit: 'đơn vị'
|
||||
},
|
||||
{
|
||||
name: 'Giá trị tồn kho',
|
||||
value: '294.5M',
|
||||
icon: 'material-symbols:attach-money-rounded',
|
||||
color: 'purple',
|
||||
},
|
||||
{
|
||||
name: 'Sắp hết hàng',
|
||||
value: 3,
|
||||
icon: 'material-symbols:warning-outline-rounded',
|
||||
color: 'red',
|
||||
unit: 'sản phẩm'
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<template>
|
||||
Inventory
|
||||
<div class="fixed-grid has-2-cols-mobile has-4-cols">
|
||||
<div class="grid">
|
||||
<InventoryHighlightCard
|
||||
v-for="highlight in inventoryHighlights"
|
||||
:key="highlight.name"
|
||||
v-bind="highlight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
28
app/components/inventory/InventoryHighlightCard.vue
Normal file
28
app/components/inventory/InventoryHighlightCard.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
value: String,
|
||||
unit: String,
|
||||
icon: String,
|
||||
color: String
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="cell">
|
||||
<div class="card h-full">
|
||||
<div class="card-content is-flex is-justify-content-space-between is-align-items-start is-gap-1">
|
||||
<div>
|
||||
<p class="fs-14 has-text-grey">{{ name }}</p>
|
||||
<p class="fs-22 font-bold">{{ value }}</p>
|
||||
</div>
|
||||
<div :class="['p-3 rounded-lg is-flex is-justify-content-center is-align-items-center', `has-background-${color}-soft`]">
|
||||
<Icon
|
||||
:name="icon"
|
||||
:class="`has-text-${color}-40`"
|
||||
:size="24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
34
app/components/orders/OrderHighlightCard.vue
Normal file
34
app/components/orders/OrderHighlightCard.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
value: Number,
|
||||
icon: String,
|
||||
color: String,
|
||||
unit: String
|
||||
})
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="cell">
|
||||
<div class="card h-full">
|
||||
<div class="card-content is-flex is-gap-1 is-justify-content-space-between is-align-items-center">
|
||||
<div>
|
||||
<p class="has-text-grey">{{ name }}</p>
|
||||
<div class="is-flex is-gap-1 is-align-items-center">
|
||||
<p class="fs-26 font-bold">{{ value }}</p>
|
||||
<span class="fs-13 has-text-grey">{{ unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:class="['p-3 is-flex', `has-background-${color}-soft`]" style="border-radius: 8px"
|
||||
>
|
||||
<Icon
|
||||
:name="icon"
|
||||
:size="26"
|
||||
:class="`has-text-${color}-40`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
53
app/components/orders/OrderPipeline.vue
Normal file
53
app/components/orders/OrderPipeline.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup>
|
||||
import PipelinePhase from '@/components/orders/PipelinePhase.vue';
|
||||
|
||||
const phases = [
|
||||
{
|
||||
name: 'Nháp',
|
||||
value: 2,
|
||||
icon: 'material-symbols:assignment-outline-rounded',
|
||||
color: 'yellow',
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
name: 'Đã xác nhận',
|
||||
value: 3,
|
||||
icon: 'material-symbols:check-circle-outline-rounded',
|
||||
color: 'blue',
|
||||
index: 2,
|
||||
},
|
||||
{
|
||||
name: 'Đang giao',
|
||||
value: 2,
|
||||
icon: 'material-symbols:delivery-truck-speed-outline-rounded',
|
||||
color: 'orange',
|
||||
index: 3,
|
||||
},
|
||||
{
|
||||
name: 'Hoàn thành',
|
||||
value: 1,
|
||||
icon: 'material-symbols:box-outline-rounded',
|
||||
color: 'green',
|
||||
index: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<p class="fs-17 font-semibold mb-4">Pipeline đơn hàng</p>
|
||||
<div class="is-flex is-justify-content-space-evenly">
|
||||
<PipelinePhase
|
||||
v-for="phase in phases"
|
||||
:key="phases.name"
|
||||
v-bind="phase"
|
||||
/>
|
||||
</div>
|
||||
<hr class="has-background-grey-lighter" />
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<p>Tổng đơn hàng</p>
|
||||
<p class="fs-18 font-semibold">{{ phases.reduce((prev, curr) => prev + curr.value, 0) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
85
app/components/orders/OrderRow.vue
Normal file
85
app/components/orders/OrderRow.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
id: Number,
|
||||
code: String,
|
||||
employee: Number,
|
||||
employee__name: String,
|
||||
customer: Number,
|
||||
customer__name: String,
|
||||
customer__phone: String,
|
||||
total: String,
|
||||
status: Number,
|
||||
status__name: String,
|
||||
status__color: String,
|
||||
payment_status: Number,
|
||||
payment_status__name: String,
|
||||
payment_status__color: String,
|
||||
delivery_status: Number,
|
||||
delivery_status__name: String,
|
||||
delivery_status__color: String,
|
||||
order__products: Array,
|
||||
create_time: String
|
||||
});
|
||||
|
||||
const { $dayjs, $shortenCurrency } = useNuxtApp();
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<p class="fs-15 font-semibold">{{ code }}</p>
|
||||
<p class="fs-13 has-text-grey">{{ employee__name }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<p>{{ customer__name }}</p>
|
||||
<div class="is-flex is-gap-0.5 is-align-items-center mt-1 fs-13 has-text-grey">
|
||||
<Icon name="material-symbols:call-outline-rounded"
|
||||
:size="15"
|
||||
/>
|
||||
<span class="fs-12">{{ customer__phone }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<div>
|
||||
<p class="font-semibold">{{ $shortenCurrency(total) }}</p>
|
||||
<p class="fs-13 has-text-grey">{{ order__products.length }} SP</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
<span :class="[
|
||||
'tag rounded-full',
|
||||
`has-background-${status__color}-80 has-text-${status__color}-25`
|
||||
]">
|
||||
{{ status__name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<p :class="`has-text-${payment_status__color}-40`">{{ payment_status__name }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p :class="`has-text-${delivery_status__color}-40`">{{ delivery_status__name }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<div class="is-flex is-gap-0.5">
|
||||
<Icon :size="18" name="material-symbols:calendar-month-outline-rounded" />
|
||||
<span class="fs-13">{{ $dayjs(create_time).format('L') }}</span>
|
||||
</div>
|
||||
<p class="has-text-grey fs-12">{{ $dayjs(create_time).format('HH:mm') }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button class="button is-dark fs-13 rounded-full">Xác nhận</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<style scoped>
|
||||
td {
|
||||
vertical-align: middle;
|
||||
--bulma-table-cell-padding: 0.75em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,54 @@
|
||||
<script setup>
|
||||
import OrderHighlightCard from '@/components/orders/OrderHighlightCard.vue';
|
||||
import OrderPipeline from '@/components/orders/OrderPipeline.vue';
|
||||
import OrdersTable from '@/components/orders/OrdersTable.vue';
|
||||
|
||||
const highlights = [
|
||||
{
|
||||
name: 'Nháp',
|
||||
value: 2,
|
||||
icon: 'material-symbols:assignment-outline-rounded',
|
||||
color: 'yellow',
|
||||
},
|
||||
{
|
||||
name: 'Đã xác nhận',
|
||||
value: 3,
|
||||
icon: 'material-symbols:check-circle-outline-rounded',
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
name: 'Đang giao',
|
||||
value: 2,
|
||||
icon: 'material-symbols:delivery-truck-speed-outline-rounded',
|
||||
color: 'orange',
|
||||
},
|
||||
{
|
||||
name: 'Hoàn thành',
|
||||
value: 1,
|
||||
icon: 'material-symbols:box-outline-rounded',
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
name: 'Doanh thu',
|
||||
value: '6.8M',
|
||||
icon: 'material-symbols:attach-money-rounded',
|
||||
color: 'purple',
|
||||
unit: 'VNĐ'
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
Orders
|
||||
<div>
|
||||
<div class="fixed-grid has-2-cols-mobile has-5-cols">
|
||||
<div class="grid">
|
||||
<OrderHighlightCard
|
||||
v-for="highlight in highlights"
|
||||
:key="highlight.name"
|
||||
v-bind="highlight"
|
||||
/>
|
||||
</div>
|
||||
<OrderPipeline />
|
||||
<OrdersTable />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
231
app/components/orders/OrdersTable.vue
Normal file
231
app/components/orders/OrdersTable.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<script setup>
|
||||
import OrderRow from '@/components/orders/OrderRow.vue';
|
||||
|
||||
const orders = [
|
||||
{
|
||||
id: 1,
|
||||
code: 'SO001',
|
||||
employee: 1,
|
||||
employee__name: 'Trần Thị B',
|
||||
customer: 1,
|
||||
customer__name: 'Nguyễn Văn A',
|
||||
customer__phone: '0901234567',
|
||||
total: '5200000.00',
|
||||
status: 1,
|
||||
status__name: 'Nháp',
|
||||
status__color: 'yellow',
|
||||
payment_status: 1,
|
||||
payment_status__name: 'Chưa thanh toán',
|
||||
payment_status__color: 'red',
|
||||
delivery_status: 1,
|
||||
delivery_status__name: 'Chờ xử lý',
|
||||
delivery_status__color: 'grey',
|
||||
order__products: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Kem chống nắng SPF 50+',
|
||||
unit_price: '280000.00',
|
||||
quantity: 10,
|
||||
discount: null,
|
||||
total: '2800000.00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Serum Vitamin C 30ml',
|
||||
unit_price: '450000.00',
|
||||
quantity: 5,
|
||||
discount: 0.1,
|
||||
total: '2025000.00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Son dưỡng môi vitamin E',
|
||||
unit_price: '75000.00',
|
||||
quantity: 5,
|
||||
discount: null,
|
||||
total: '375000.00'
|
||||
},
|
||||
],
|
||||
create_time: '2026-02-11T08:51:04.587660+07:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
code: 'SO002',
|
||||
employee: 1,
|
||||
employee__name: 'Trần Thị B',
|
||||
customer: 3,
|
||||
customer__name: 'Lê Thị C',
|
||||
customer__phone: '0912345678',
|
||||
total: '8500000.00',
|
||||
status: 2,
|
||||
status__name: 'Đã xác nhận',
|
||||
status__color: 'blue',
|
||||
payment_status: 2,
|
||||
payment_status__name: 'Một phần',
|
||||
payment_status__color: 'orange',
|
||||
delivery_status: 2,
|
||||
delivery_status__name: 'Sẵn sàng',
|
||||
delivery_status__color: 'blue',
|
||||
order__products: [
|
||||
{
|
||||
id: 3,
|
||||
name: 'Mặt nạ collagen 10 miếng',
|
||||
unit_price: '320000.00',
|
||||
quantity: 15,
|
||||
discount: 0.05,
|
||||
total: '4560000.00'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Toner cân bằng da 200ml',
|
||||
unit_price: '180000.00',
|
||||
quantity: 20,
|
||||
discount: 0.1,
|
||||
total: '3240000.00'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Gel rửa mặt làm sạch sâu',
|
||||
unit_price: '140000.00',
|
||||
quantity: 5,
|
||||
discount: null,
|
||||
total: '700000.00'
|
||||
},
|
||||
],
|
||||
create_time: '2026-04-06T09:10:09.587660+07:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
code: 'SO003',
|
||||
employee: 5,
|
||||
employee__name: 'Hoàng Văn E',
|
||||
customer: 4,
|
||||
customer__name: 'Phạm Văn D',
|
||||
customer__phone: '0923456703',
|
||||
total: '12300000.00',
|
||||
status: 3,
|
||||
status__name: 'Đang giao',
|
||||
status__color: 'orange',
|
||||
payment_status: 3,
|
||||
payment_status__name: 'Đã thanh toán',
|
||||
payment_status__color: 'green',
|
||||
delivery_status: 3,
|
||||
delivery_status__name: 'Hoàn thành',
|
||||
delivery_status__color: 'green',
|
||||
order__products: [
|
||||
{
|
||||
id: 6,
|
||||
name: 'Kem dưỡng ẩm ban đêm',
|
||||
unit_price: '380000.00',
|
||||
quantity: 20,
|
||||
discount: 0.05,
|
||||
total: '7220000.00'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'Phấn nền BB cream',
|
||||
unit_price: '220000.00',
|
||||
quantity: 15,
|
||||
discount: null,
|
||||
total: '3300000.00'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'Nước tẩy trang 3 trong 1',
|
||||
unit_price: '195000.00',
|
||||
quantity: 10,
|
||||
discount: 0.1,
|
||||
total: '1755000.00'
|
||||
},
|
||||
],
|
||||
create_time: '2026-04-05T02:33:24.587660+07:00'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
code: 'SO004',
|
||||
employee: 1,
|
||||
employee__name: 'Trần Thị B',
|
||||
customer: 6,
|
||||
customer__name: 'Vũ Thị F',
|
||||
customer__phone: '0934835222',
|
||||
total: '6800000.00',
|
||||
status: 4,
|
||||
status__name: 'Hoàn thành',
|
||||
status__color: 'green',
|
||||
payment_status: 3,
|
||||
payment_status__name: 'Đã thanh toán',
|
||||
payment_status__color: 'green',
|
||||
delivery_status: 3,
|
||||
delivery_status__name: 'Hoàn thành',
|
||||
delivery_status__color: 'green',
|
||||
order__products: [
|
||||
{
|
||||
id: 9,
|
||||
name: 'Dầu gội thảo mộc 500ml',
|
||||
unit_price: '120000.00',
|
||||
quantity: 30,
|
||||
discount: 0.1,
|
||||
total: '3240000.00'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Sữa rửa mặt trà xanh 150ml',
|
||||
unit_price: '150000.00',
|
||||
quantity: 20,
|
||||
discount: 0.05,
|
||||
total: '2850000.00'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'Son dưỡng môi vitamin E',
|
||||
unit_price: '75000.00',
|
||||
quantity: 10,
|
||||
discount: 0.05,
|
||||
total: '712500.00'
|
||||
},
|
||||
],
|
||||
create_time: '2026-04-04T23:21:11.587660+07:00'
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-content"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-content p-0">
|
||||
<p class="p-5 fs-17 font-semibold is-flex is-align-items-center is-gap-1">
|
||||
<Icon name="material-symbols:list-alt-outline-rounded" :size="22" />
|
||||
<span>Danh sách đơn hàng ({{ orders.length }})</span>
|
||||
</p>
|
||||
<table class="table is-fullwidth is-hoverable fs-14">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Đơn hàng</th>
|
||||
<th>Khách hàng</th>
|
||||
<th class="has-text-right">Tổng tiền</th>
|
||||
<th class="has-text-centered">Trạng thái</th>
|
||||
<th>Thanh toán</th>
|
||||
<th>Giao hàng</th>
|
||||
<th>Ngày tạo</th>
|
||||
<th>Thao tác</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<OrderRow
|
||||
v-for="order in orders"
|
||||
:key="order.id"
|
||||
v-bind="order"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
th {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
44
app/components/orders/PipelinePhase.vue
Normal file
44
app/components/orders/PipelinePhase.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
value: Number,
|
||||
icon: String,
|
||||
color: String,
|
||||
index: Number,
|
||||
});
|
||||
|
||||
const progressUnfilled = computed(() => `var(--bulma-${props.color}-85)`);
|
||||
</script>
|
||||
<template>
|
||||
<div class="is-flex-grow-1">
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<p :class="`has-text-${color}-40`">{{ name }}</p>
|
||||
<p :class="['fs-18 font-bold', `has-text-${color}-30`]">{{ value }}</p>
|
||||
</div>
|
||||
<progress
|
||||
:class="['progress is-small mt-2', `is-${color}`]"
|
||||
value="20"
|
||||
max="100"
|
||||
>
|
||||
</progress>
|
||||
</div>
|
||||
<div
|
||||
v-if="index < 4"
|
||||
class="is-flex is-justify-content-center is-align-items-center"
|
||||
style="width: 60px; height: 48px"
|
||||
>
|
||||
<Icon
|
||||
name="material-symbols:arrow-forward-rounded"
|
||||
:size="24"
|
||||
class="has-text-grey-70"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
$progress-value-background-color: 'red';
|
||||
|
||||
.progress {
|
||||
--bulma-size-small: 0.5rem;
|
||||
background-color: v-bind(progressUnfilled);
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
style="min-height: 100vh"
|
||||
class="has-background-white"
|
||||
style="min-height: 100vh; background-color: #f9fafb;"
|
||||
data-theme="light"
|
||||
lang="vi"
|
||||
>
|
||||
@@ -48,7 +47,7 @@ async function checkRedirect() {
|
||||
$store.commit("login", row);
|
||||
}
|
||||
} else if (!$store.login) return /* $requestLogin(); */
|
||||
await checkLogin();
|
||||
// await checkLogin();
|
||||
}
|
||||
async function checkLogin() {
|
||||
if ($store.login ? $store.login.token : false) {
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
<TopMenu @changetab="changeTab" />
|
||||
</ClientOnly>
|
||||
<ClientOnly>
|
||||
<div class="container blockdiv" style="padding-bottom: 0">
|
||||
<div class="fsb-18 mb-2 has-text-black" v-if="tab">
|
||||
<div class="container blockdiv has-text-text-20">
|
||||
<div class="fs-17 font-semibold mb-2 is-flex is-align-items-center is-gap-1" v-if="tab">
|
||||
<template v-if="subtab">
|
||||
<span>{{ tab[$store.lang] }}</span>
|
||||
<SvgIcon class="mx-2" v-bind="{ name: 'right.svg', size: 17, type: 'has-text-black' }"></SvgIcon>
|
||||
<span>{{ subtab[$store.lang] }}</span>
|
||||
</template>
|
||||
<span v-else>{{ tab[$store.lang] }}</span>
|
||||
<a class="ml-3" @click="refresh()">
|
||||
<SvgIcon v-bind="{name: 'refresh.svg', type: 'primary', size: 20}"></SvgIcon>
|
||||
</a>
|
||||
<button class="button is-primary is-light rounded-full p-1" @click="refresh()">
|
||||
<Icon name="material-symbols:refresh-rounded" :size="24" />
|
||||
</button>
|
||||
</div>
|
||||
<KeepAlive>
|
||||
<component :is="componentMap[vbind.component]" v-bind="vbind" :key="componentKey" v-if="componentKey" />
|
||||
|
||||
@@ -5,11 +5,12 @@ import dayjs from 'dayjs';
|
||||
import weekday from 'dayjs/plugin/weekday';
|
||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat'
|
||||
import 'dayjs/locale/vi';
|
||||
dayjs.extend(weekday);
|
||||
dayjs.extend(weekOfYear);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.locale('vi');
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
@@ -608,6 +609,12 @@ export default defineNuxtPlugin(() => {
|
||||
return `https://img.vietqr.io/image/${bankInfo.bank.code}-${bankInfo.account.number}-print.png?${params.toString()}`;
|
||||
};
|
||||
|
||||
function shortenCurrency(amount) {
|
||||
return new Intl.NumberFormat('en', { notation: 'compact' }).format(
|
||||
Number(amount),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
provide: {
|
||||
dialog,
|
||||
@@ -636,6 +643,7 @@ export default defineNuxtPlugin(() => {
|
||||
formatDateVN,
|
||||
getFirstAndLastName,
|
||||
paymentQR,
|
||||
shortenCurrency
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
10293
my-bulma-project.css
10293
my-bulma-project.css
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,53 +1,32 @@
|
||||
$blue-dianne: #204853;
|
||||
$parchment: #fefefe;
|
||||
$sirocco: #758385;
|
||||
$delta: #b0afaa;
|
||||
$cutty-sark: #566c72;
|
||||
$silver-rust: #ccc5bc;
|
||||
$fiord: #3c5b63;
|
||||
$pewter: #959b99;
|
||||
$pearl-bush: #ffffff;
|
||||
$red: #ff2e4e;
|
||||
$orange: #fd8206;
|
||||
$yellow: #eec704;
|
||||
$green: #16d164;
|
||||
$cyan: #03c2c2;
|
||||
$blue: #1678ff;
|
||||
$purple: #833bff;
|
||||
$pink: #ff2bdd;
|
||||
$grey: #767676;
|
||||
|
||||
// Set your brand colors
|
||||
$primary: $blue-dianne;
|
||||
$secondary: $fiord;
|
||||
$accent: $parchment;
|
||||
$findata: $sirocco;
|
||||
$danger: #f14668;
|
||||
$dark: $cutty-sark;
|
||||
$light: $pearl-bush;
|
||||
$twitter: $pewter;
|
||||
$warning: $cutty-sark;
|
||||
$info: $sirocco;
|
||||
$success: $fiord;
|
||||
|
||||
// Path to Bulma's sass folder
|
||||
@use "bulma/sass" with (
|
||||
$family-primary: 'Arial, sans-serif',
|
||||
$primary: $primary,
|
||||
$info: $info,
|
||||
$success: $success,
|
||||
$warning: $warning,
|
||||
$danger: $danger,
|
||||
$body-color: $sitecolor,
|
||||
$text: $sitecolor,
|
||||
$link: $blue-dianne,
|
||||
$link-hover: $fiord
|
||||
$family-primary: "'Inter', 'SF Pro', 'Helvetica', 'Arial', sans-serif",
|
||||
$primary: $blue,
|
||||
$link: $blue,
|
||||
$info: $cyan,
|
||||
$success: $green,
|
||||
$warning: $yellow,
|
||||
$danger: $red,
|
||||
$custom-colors: (
|
||||
"red": $red,
|
||||
"orange": $orange,
|
||||
"yellow": $yellow,
|
||||
"green": $green,
|
||||
"cyan": $cyan,
|
||||
"blue": $blue,
|
||||
"purple": $purple,
|
||||
"pink": $pink,
|
||||
"grey": $grey
|
||||
)
|
||||
);
|
||||
|
||||
a {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $primary !important;
|
||||
}
|
||||
|
||||
strong, label {
|
||||
color: $sitecolor !important;
|
||||
}
|
||||
|
||||
.hyperlink:hover {
|
||||
cursor: pointer;
|
||||
color: $primary !important;
|
||||
}
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900');
|
||||
@@ -10,8 +10,7 @@ export default defineNuxtConfig({
|
||||
script: [{ src: "/js/html2pdf.bundle.min.js" }, { src: "/js/html2canvas.min.js" }],
|
||||
},
|
||||
},
|
||||
modules: ["@pinia/nuxt", "pinia-plugin-persistedstate/nuxt", "@nuxt/image","nuxt-qrcode"],
|
||||
|
||||
modules: ["@pinia/nuxt", "pinia-plugin-persistedstate/nuxt", "@nuxt/image","nuxt-qrcode", "@nuxt/icon"],
|
||||
compatibilityDate: "2025-10-01",
|
||||
devtools: { enabled: true },
|
||||
});
|
||||
|
||||
295
package-lock.json
generated
295
package-lock.json
generated
@@ -18,7 +18,7 @@
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"axios": "^1.9.0",
|
||||
"bowser": "^2.11.0",
|
||||
"bulma": "^1.0.2",
|
||||
"bulma": "^1.0.4",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"docx-preview": "^0.3.6",
|
||||
@@ -44,10 +44,24 @@
|
||||
"vue3-quill": "^0.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"nuxt": "^4.2.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@antfu/install-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
|
||||
"integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"package-manager-detector": "^1.3.0",
|
||||
"tinyexec": "^1.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@aps_sdk/authentication": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@aps_sdk/authentication/-/authentication-1.0.0.tgz",
|
||||
@@ -1113,6 +1127,47 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@iconify/collections": {
|
||||
"version": "1.0.670",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/collections/-/collections-1.0.670.tgz",
|
||||
"integrity": "sha512-32kW4oJ+QV1HCndcjwAprDyJJ0+T9ahP4y2lmLfekdGWNMwG1pVZah4biqT+HrZdEiBm8InJrbCvaitIusTE3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@iconify/types": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/types": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@iconify/utils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz",
|
||||
"integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@antfu/install-pkg": "^1.1.0",
|
||||
"@iconify/types": "^2.0.0",
|
||||
"mlly": "^1.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/vue": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-5.0.0.tgz",
|
||||
"integrity": "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@iconify/types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/cyberalien"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
|
||||
@@ -2208,6 +2263,94 @@
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/icon": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/icon/-/icon-2.2.1.tgz",
|
||||
"integrity": "sha512-GI840yYGuvHI0BGDQ63d6rAxGzG96jQcWrnaWIQKlyQo/7sx9PjXkSHckXUXyX1MCr9zY6U25Td6OatfY6Hklw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@iconify/collections": "^1.0.641",
|
||||
"@iconify/types": "^2.0.0",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@nuxt/devtools-kit": "^3.1.1",
|
||||
"@nuxt/kit": "^4.2.2",
|
||||
"consola": "^3.4.2",
|
||||
"local-pkg": "^1.1.2",
|
||||
"mlly": "^1.8.0",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"std-env": "^3.10.0",
|
||||
"tinyglobby": "^0.2.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/icon/node_modules/@nuxt/devtools-kit": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-3.2.4.tgz",
|
||||
"integrity": "sha512-Yxy2Xgmq5hf3dQy983V0xh0OJV2mYwRZz9eVIGc3EaribdFGPDNGMMbYqX9qCty3Pbxn/bCF3J0UyPaNlHVayQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^4.4.2",
|
||||
"execa": "^8.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/icon/node_modules/@nuxt/kit": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.4.2.tgz",
|
||||
"integrity": "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"c12": "^3.3.3",
|
||||
"consola": "^3.4.2",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.5",
|
||||
"errx": "^0.1.0",
|
||||
"exsolve": "^1.0.8",
|
||||
"ignore": "^7.0.5",
|
||||
"jiti": "^2.6.1",
|
||||
"klona": "^2.0.6",
|
||||
"mlly": "^1.8.1",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.3.0",
|
||||
"rc9": "^3.0.0",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.7.4",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ufo": "^1.6.3",
|
||||
"unctx": "^2.5.0",
|
||||
"untyped": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/icon/node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/icon/node_modules/rc9": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
|
||||
"integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"defu": "^6.1.6",
|
||||
"destr": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/image": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/image/-/image-1.11.0.tgz",
|
||||
@@ -5715,10 +5858,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -6877,8 +7019,7 @@
|
||||
"node_modules/bulma": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.4.tgz",
|
||||
"integrity": "sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g==",
|
||||
"license": "MIT"
|
||||
"integrity": "sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g=="
|
||||
},
|
||||
"node_modules/bundle-name": {
|
||||
"version": "4.1.0",
|
||||
@@ -6897,23 +7038,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/c12": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-3.3.2.tgz",
|
||||
"integrity": "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A==",
|
||||
"license": "MIT",
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
|
||||
"integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
"confbox": "^0.2.2",
|
||||
"defu": "^6.1.4",
|
||||
"dotenv": "^17.2.3",
|
||||
"chokidar": "^5.0.0",
|
||||
"confbox": "^0.2.4",
|
||||
"defu": "^6.1.6",
|
||||
"dotenv": "^17.3.1",
|
||||
"exsolve": "^1.0.8",
|
||||
"giget": "^2.0.0",
|
||||
"giget": "^3.2.0",
|
||||
"jiti": "^2.6.1",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"perfect-debounce": "^2.0.0",
|
||||
"perfect-debounce": "^2.1.0",
|
||||
"pkg-types": "^2.3.0",
|
||||
"rc9": "^2.1.2"
|
||||
"rc9": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"magicast": "*"
|
||||
@@ -6924,6 +7064,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/giget": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz",
|
||||
"integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==",
|
||||
"bin": {
|
||||
"giget": "dist/cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/rc9": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
|
||||
"integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
|
||||
"dependencies": {
|
||||
"defu": "^6.1.6",
|
||||
"destr": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
@@ -7914,10 +8097,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
|
||||
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
|
||||
"license": "MIT"
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
|
||||
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="
|
||||
},
|
||||
"node_modules/config-chain": {
|
||||
"version": "1.1.13",
|
||||
@@ -8679,10 +8861,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"license": "MIT"
|
||||
"version": "6.1.7",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
|
||||
"integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
@@ -8905,10 +9086,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"version": "17.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
|
||||
"integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -10076,6 +10256,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
@@ -12554,15 +12735,14 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
|
||||
"integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
|
||||
"license": "MIT",
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
|
||||
"integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
|
||||
"dependencies": {
|
||||
"acorn": "^8.15.0",
|
||||
"acorn": "^8.16.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^1.3.1",
|
||||
"ufo": "^1.6.1"
|
||||
"ufo": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly/node_modules/confbox": {
|
||||
@@ -13318,6 +13498,7 @@
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
||||
"integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
@@ -14042,10 +14223,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz",
|
||||
"integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==",
|
||||
"license": "MIT"
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
|
||||
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
@@ -15786,10 +15966,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"license": "ISC",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
@@ -17197,6 +17376,7 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
|
||||
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -17538,10 +17718,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
||||
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
|
||||
"license": "MIT"
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
|
||||
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="
|
||||
},
|
||||
"node_modules/ultrahtml": {
|
||||
"version": "1.6.0",
|
||||
@@ -17557,15 +17736,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unctx": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/unctx/-/unctx-2.4.1.tgz",
|
||||
"integrity": "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==",
|
||||
"license": "MIT",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/unctx/-/unctx-2.5.0.tgz",
|
||||
"integrity": "sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==",
|
||||
"dependencies": {
|
||||
"acorn": "^8.14.0",
|
||||
"acorn": "^8.15.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.17",
|
||||
"unplugin": "^2.1.0"
|
||||
"magic-string": "^0.30.21",
|
||||
"unplugin": "^2.3.11"
|
||||
}
|
||||
},
|
||||
"node_modules/unctx/node_modules/estree-walker": {
|
||||
@@ -17796,10 +17974,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",
|
||||
"integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==",
|
||||
"license": "MIT",
|
||||
"version": "2.3.11",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
|
||||
"integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"acorn": "^8.15.0",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"axios": "^1.9.0",
|
||||
"bowser": "^2.11.0",
|
||||
"bulma": "^1.0.2",
|
||||
"bulma": "^1.0.4",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"docx-preview": "^0.3.6",
|
||||
@@ -51,6 +51,7 @@
|
||||
"vue": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"nuxt": "^4.2.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
BIN
public/map-demo.png
Normal file
BIN
public/map-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
Reference in New Issue
Block a user