nuxt-layer-core 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/app/app.config.ts +5 -0
  3. package/app/app.vue +11 -0
  4. package/app/assets/css/main.css +113 -0
  5. package/app/components/.gitkeep +0 -0
  6. package/app/components/ui/sonner/Sonner.vue +62 -0
  7. package/app/components/ui/sonner/index.ts +1 -0
  8. package/app/composables/.gitkeep +0 -0
  9. package/app/layers/solution/app/assets/proxmox-css/main.css +89 -0
  10. package/app/layers/solution/app/assets/proxmox-css/proxmox-utils.css +68 -0
  11. package/app/layers/solution/app/components/ClusterDiagram.vue +72 -0
  12. package/app/layers/solution/app/components/CoursesSection.vue +246 -0
  13. package/app/layers/solution/app/components/CtaSection.vue +329 -0
  14. package/app/layers/solution/app/components/FeaturesSection.vue +67 -0
  15. package/app/layers/solution/app/components/HeroSection.vue +144 -0
  16. package/app/layers/solution/app/components/PricingPlansSection.vue +434 -0
  17. package/app/layers/solution/app/components/RegistrationForm.vue +256 -0
  18. package/app/layers/solution/app/components/TestimonialsSection.vue +58 -0
  19. package/app/layers/solution/app/components/TheFooter.vue +61 -0
  20. package/app/layers/solution/app/components/TheHeader.vue +189 -0
  21. package/app/layers/solution/app/components/proxmox/AppIcon.vue +45 -0
  22. package/app/layers/solution/app/components/proxmox/ComparisonSection.vue +54 -0
  23. package/app/layers/solution/app/components/proxmox/FaqSection.vue +58 -0
  24. package/app/layers/solution/app/components/proxmox/HciOverviewSection.vue +37 -0
  25. package/app/layers/solution/app/components/proxmox/HeroSection.vue +74 -0
  26. package/app/layers/solution/app/components/proxmox/TestimonialsSection.vue +44 -0
  27. package/app/layers/solution/app/components/proxmox/TrainingBenefitsSection.vue +37 -0
  28. package/app/layers/solution/app/components/proxmox/VeFeaturesSection.vue +55 -0
  29. package/app/layers/solution/app/composables/useActiveSection.ts +47 -0
  30. package/app/layers/solution/app/composables/useAuthApi.ts +244 -0
  31. package/app/layers/solution/app/composables/useAuthState.ts +16 -0
  32. package/app/layers/solution/app/composables/useCookie.ts +79 -0
  33. package/app/layers/solution/app/composables/useCookieHelper.ts +81 -0
  34. package/app/layers/solution/app/composables/useCrmApi.ts +32 -0
  35. package/app/layers/solution/app/composables/useDynamicCollection.ts +23 -0
  36. package/app/layers/solution/app/composables/useScrollAnimate.ts +40 -0
  37. package/app/layers/solution/app/composables/useScrollToSection.ts +13 -0
  38. package/app/layers/solution/app/composables/useSolutionApi.ts +55 -0
  39. package/app/layers/solution/app/composables/useToast.ts +14 -0
  40. package/app/layers/solution/app/data/proxmox-content.ts +158 -0
  41. package/app/layers/solution/app/images/Proxmox HCI.png +0 -0
  42. package/app/layers/solution/app/images/image_from_https_hercules_cdn.com_file_6dztkvkfnip0ud23tzzvu57c.png +0 -0
  43. package/app/layers/solution/app/layouts/default.vue +5 -0
  44. package/app/layers/solution/app/nuxt.config.ts +9 -0
  45. package/app/layers/solution/app/pages/cap-nhat-thong-tin/index.vue +302 -0
  46. package/app/layers/solution/app/pages/dang-ky/index.vue +627 -0
  47. package/app/layers/solution/app/pages/dang-nhap/index.vue +208 -0
  48. package/app/layers/solution/app/pages/doi-mat-khau/index.vue +237 -0
  49. package/app/layers/solution/app/pages/giai-phap-proxmox/[slug]/index.vue +37 -0
  50. package/app/layers/solution/app/pages/giai-phap-proxmox/[slug]/proxmox-hci/index.vue +45 -0
  51. package/app/layers/solution/app/pages/giai-phap-proxmox/dang-ky-khoa-hoc/[slug].vue +131 -0
  52. package/app/layers/solution/app/pages/quen-mat-khau/index.vue +136 -0
  53. package/app/layers/solution/app/pages/xac-thuc/index.vue +320 -0
  54. package/app/layers/solution/app/plugins/reveal.ts +56 -0
  55. package/app/layers/solution/app/public/images/image_from_https_hercules_cdn.com_file_6dztkvkfnip0ud23tzzvu57c.png +0 -0
  56. package/app/layers/solution/app/public/images/logo-lvs-1300x330.png +0 -0
  57. package/app/layouts/auth.vue +71 -0
  58. package/app/lib/utils.ts +7 -0
  59. package/app/middleware/.gitkeep +0 -0
  60. package/app/modules/layer-route-priority.ts +60 -0
  61. package/app/pages/index.vue +9 -0
  62. package/app/pages/san-pham-proxmox.vue +8 -0
  63. package/app/plugins/.gitkeep +0 -0
  64. package/app/plugins/sdk.ts +29 -0
  65. package/app/stores/.gitkeep +0 -0
  66. package/app/types/sdk.d.ts +15 -0
  67. package/components.json +24 -0
  68. package/nuxt.config.ts +104 -0
  69. package/package.json +52 -0
  70. package/public/.gitkeep +0 -0
  71. package/server/api/.gitkeep +0 -0
  72. package/server/utils/.gitkeep +0 -0
  73. package/tailwind.config.js +96 -0
  74. package/tsconfig.base.json +18 -0
  75. package/tsconfig.json +18 -0
  76. package/website-longvan-1.1.6.tgz +0 -0
  77. package/website-longvan-1.1.7.tgz +0 -0
  78. package/website-longvan-1.1.8.tgz +0 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Extract root domain from hostname.
3
+ * e.g. "dev.longvan.net" → ".longvan.net"
4
+ * "longvan.net" → ".longvan.net"
5
+ * "localhost" → "localhost"
6
+ */
7
+ function getCookieDomain(): string {
8
+ if (typeof window === 'undefined') return ''
9
+ const hostname = window.location.hostname
10
+
11
+ // localhost / IP → không set domain (browser tự xử lý)
12
+ if (hostname === 'localhost' || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) {
13
+ return hostname
14
+ }
15
+
16
+ const parts = hostname.split('.')
17
+ // Lấy 2 phần cuối (root domain): longvan.net, example.com, ...
18
+ if (parts.length >= 2) {
19
+ return '.' + parts.slice(-2).join('.')
20
+ }
21
+ return hostname
22
+ }
23
+
24
+ /**
25
+ * Cookie helper composable — set, get, delete cookies theo domain hiện tại.
26
+ *
27
+ * Usage:
28
+ * const { setCookie, getCookie, deleteCookie } = useCookie()
29
+ * setCookie('token', 'abc123', 7) // expires in 7 days
30
+ * getCookie('token') // → 'abc123'
31
+ * deleteCookie('token')
32
+ */
33
+ export function useCookie() {
34
+ /**
35
+ * Lưu cookie.
36
+ * @param key - Tên cookie
37
+ * @param value - Giá trị cookie
38
+ * @param days - Số ngày hết hạn (mặc định 365)
39
+ */
40
+ function setCookie(key: string, value: string, days = 365): void {
41
+ const date = new Date()
42
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000)
43
+ const expires = 'expires=' + date.toUTCString()
44
+ const domain = getCookieDomain()
45
+ const domainPart = domain ? `; domain=${domain}` : ''
46
+ document.cookie = `${key}=${encodeURIComponent(value)}; ${expires}; path=/${domainPart}`
47
+ }
48
+
49
+ /**
50
+ * Lấy giá trị cookie theo key.
51
+ * @param key - Tên cookie
52
+ * @returns Giá trị cookie hoặc null nếu không tìm thấy
53
+ */
54
+ function getCookie(key: string): string | null {
55
+ const name = key + '='
56
+ const decodedCookie = decodeURIComponent(document.cookie)
57
+ const cookies = decodedCookie.split(';')
58
+
59
+ for (let cookie of cookies) {
60
+ cookie = cookie.trim()
61
+ if (cookie.startsWith(name)) {
62
+ return cookie.substring(name.length)
63
+ }
64
+ }
65
+ return null
66
+ }
67
+
68
+ /**
69
+ * Xóa cookie theo key.
70
+ * @param key - Tên cookie cần xóa
71
+ */
72
+ function deleteCookie(key: string): void {
73
+ const domain = getCookieDomain()
74
+ const domainPart = domain ? `; domain=${domain}` : ''
75
+ document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/${domainPart}`
76
+ }
77
+
78
+ return { setCookie, getCookie, deleteCookie }
79
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Extract root domain from hostname.
3
+ * e.g. "dev.longvan.net" → ".longvan.net"
4
+ * "longvan.net" → ".longvan.net"
5
+ * "localhost" → "localhost"
6
+ */
7
+ function getCookieDomain(): string {
8
+ if (typeof window === 'undefined') return ''
9
+ const hostname = window.location.hostname
10
+
11
+ // localhost / IP → không set domain (browser tự xử lý)
12
+ if (hostname === 'localhost' || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) {
13
+ return hostname
14
+ }
15
+
16
+ const parts = hostname.split('.')
17
+ // Lấy 2 phần cuối (root domain): longvan.net, example.com, ...
18
+ if (parts.length >= 2) {
19
+ return '.' + parts.slice(-2).join('.')
20
+ }
21
+ return hostname
22
+ }
23
+
24
+ /**
25
+ * Cookie helper composable — set, get, delete cookies theo domain hiện tại.
26
+ *
27
+ * Renamed from `useCookie` to avoid collision with Nuxt's built-in auto-import.
28
+ *
29
+ * Usage:
30
+ * const { setCookie, getCookie, deleteCookie } = useCookieHelper()
31
+ * setCookie('token', 'abc123', 7) // expires in 7 days
32
+ * getCookie('token') // → 'abc123'
33
+ * deleteCookie('token')
34
+ */
35
+ export function useCookieHelper() {
36
+ /**
37
+ * Lưu cookie.
38
+ * @param key - Tên cookie
39
+ * @param value - Giá trị cookie
40
+ * @param days - Số ngày hết hạn (mặc định 365)
41
+ */
42
+ function setCookie(key: string, value: string, days = 365): void {
43
+ const date = new Date()
44
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000)
45
+ const expires = 'expires=' + date.toUTCString()
46
+ const domain = getCookieDomain()
47
+ const domainPart = domain ? `; domain=${domain}` : ''
48
+ document.cookie = `${key}=${encodeURIComponent(value)}; ${expires}; path=/${domainPart}`
49
+ }
50
+
51
+ /**
52
+ * Lấy giá trị cookie theo key.
53
+ * @param key - Tên cookie
54
+ * @returns Giá trị cookie hoặc null nếu không tìm thấy
55
+ */
56
+ function getCookie(key: string): string | null {
57
+ const name = key + '='
58
+ const decodedCookie = decodeURIComponent(document.cookie)
59
+ const cookies = decodedCookie.split(';')
60
+
61
+ for (let cookie of cookies) {
62
+ cookie = cookie.trim()
63
+ if (cookie.startsWith(name)) {
64
+ return cookie.substring(name.length)
65
+ }
66
+ }
67
+ return null
68
+ }
69
+
70
+ /**
71
+ * Xóa cookie theo key.
72
+ * @param key - Tên cookie cần xóa
73
+ */
74
+ function deleteCookie(key: string): void {
75
+ const domain = getCookieDomain()
76
+ const domainPart = domain ? `; domain=${domain}` : ''
77
+ document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/${domainPart}`
78
+ }
79
+
80
+ return { setCookie, getCookie, deleteCookie }
81
+ }
@@ -0,0 +1,32 @@
1
+ export const useCrmApi = () => {
2
+ const { $sdk } = useNuxtApp()
3
+
4
+ const addOpportunity = async (payload: {
5
+ name: string
6
+ description: string
7
+ referName: string
8
+ referPhone: string
9
+ referEmail: string
10
+ priorityName?: string
11
+ targetUrl?: string
12
+ extSource?: string
13
+ ownerId?: string
14
+ }) => {
15
+ const addOpportunityRequest = {
16
+ name: payload.name,
17
+ description: payload.description,
18
+ referName: payload.referName,
19
+ referPhone: payload.referPhone,
20
+ referEmail: payload.referEmail,
21
+ priorityName: payload.priorityName || 'MEDIUM',
22
+ targetId: '',
23
+ targetUrl: payload.targetUrl || '',
24
+ extSource: payload.extSource || '',
25
+ ownerId: payload.ownerId || '',
26
+ }
27
+
28
+ return $sdk.crm.addOpportunity(addOpportunityRequest, 'ANONYMOUS')
29
+ }
30
+
31
+ return { addOpportunity }
32
+ }
@@ -0,0 +1,23 @@
1
+ export const useDynamicCollection = () => {
2
+ const { $sdk } = useNuxtApp()
3
+
4
+ const handleGetSlugPathView = (slug: string, type: string) => {
5
+ try {
6
+ const response = $sdk.dynamic_collection.getSlugPathView(slug, type);
7
+
8
+ if (response) {
9
+ return response
10
+ } else {
11
+ return null;
12
+ }
13
+
14
+ } catch (error) {
15
+ console.log("🚀 ~ handleGetSlugPathView ~ error:", error);
16
+ return null;
17
+ }
18
+ }
19
+
20
+ return {
21
+ handleGetSlugPathView
22
+ }
23
+ }
@@ -0,0 +1,40 @@
1
+ const presets: Record<string, string> = {
2
+ 'fade-up': 'opacity-0 translate-y-8',
3
+ 'fade-in': 'opacity-0',
4
+ 'slide-left': 'opacity-0 -translate-x-8',
5
+ 'slide-right': 'opacity-0 translate-x-8',
6
+ 'scale-up': 'opacity-0 scale-95',
7
+ }
8
+
9
+ export function useScrollAnimate(selector: string, animation = 'fade-up') {
10
+ onMounted(() => {
11
+ const els = document.querySelectorAll<HTMLElement>(selector)
12
+ if (!els.length) return
13
+
14
+ const base = 'transition-all duration-700 ease-out'
15
+ const initial = presets[animation] || presets['fade-up']
16
+
17
+ const observer = new IntersectionObserver(
18
+ (entries) => {
19
+ for (const entry of entries) {
20
+ if (entry.isIntersecting) {
21
+ const el = entry.target as HTMLElement
22
+ const delay = el.dataset.delay || '0'
23
+ el.style.transitionDelay = `${delay}ms`
24
+ el.classList.remove(...initial.split(' '))
25
+ el.classList.add('opacity-100', 'translate-y-0', 'translate-x-0', 'scale-100')
26
+ observer.unobserve(el)
27
+ }
28
+ }
29
+ },
30
+ { threshold: 0.1 }
31
+ )
32
+
33
+ els.forEach((el) => {
34
+ el.classList.add(...base.split(' '), ...initial.split(' '))
35
+ observer.observe(el)
36
+ })
37
+
38
+ onUnmounted(() => observer.disconnect())
39
+ })
40
+ }
@@ -0,0 +1,13 @@
1
+ export const useScrollToSection = () => {
2
+ const scrollTo = (id: string, behavior: ScrollBehavior = 'smooth') => {
3
+ if (!id) return
4
+ const el = document.getElementById(id)
5
+ if (el) {
6
+ el.scrollIntoView({ behavior })
7
+ }
8
+ }
9
+
10
+ return {
11
+ scrollTo,
12
+ }
13
+ }
@@ -0,0 +1,55 @@
1
+ export const useSolutionApi = () => {
2
+ const { $sdk } = useNuxtApp()
3
+ const { handleGetSlugPathView } = useDynamicCollection()
4
+
5
+ const getPublicProductDynamic = (categoryId: string) => {
6
+ return $sdk.marketplace.getPublicProductDynamic(categoryId, '', [
7
+ "supplierId",
8
+ "categoryId",
9
+ "storeChannel",
10
+ "productContent { resumeId resumeName resumeDesc }",
11
+ "product { partyId id title status }",
12
+ "slugPath { id slug basePath domain publicStatus type templateId contentId platform providerDomain profileId fullUrl url targetCTA }"
13
+ ])
14
+ }
15
+
16
+ const getResumeByIdData = (resumeId: string) => {
17
+ console.log("🚀 ~ getSolutionDetail ~ resumeId:", resumeId)
18
+ const data = $sdk.crm.getResumeById(resumeId, [
19
+ "resumeId",
20
+ "resumeName",
21
+ "status",
22
+ "statusCode",
23
+ "level",
24
+ "fieldResumes { fieldName value fieldType type children { fieldName value fieldType type children { fieldName value fieldType type children { fieldName value fieldType type } } } }"
25
+ ])
26
+ console.log("🚀 ~ getResumeByIdData ~ data:", data)
27
+
28
+ return data
29
+ }
30
+
31
+ const getSolutionDetail = async (slug: string, type: string) => {
32
+ const productData = await handleGetSlugPathView(slug, type)
33
+
34
+ if(productData.length > 0){
35
+ const getData = productData?.find((c:any)=> c.type == type) ?? productData?.[0];
36
+
37
+ // console.log("🚀 ~ getSolutionDetail ~ productData:", productData)
38
+ const resumeId = getData?.profileId
39
+ // console.log("🚀 ~ getSolutionDetail ~ resumeId:", resumeId)
40
+ if (!resumeId) return { productData, resumeData: null }
41
+ const resumeData = await getResumeByIdData(resumeId)
42
+ // console.log("🚀 ~ getSolutionDetail ~ resumeData:", resumeData)
43
+ return { productData, resumeData }
44
+ }else{
45
+ return { productData: null, resumeData: null }
46
+ }
47
+
48
+ }
49
+
50
+ return {
51
+ getPublicProductDynamic,
52
+ getResumeByIdData,
53
+ getSolutionDetail,
54
+ }
55
+ }
@@ -0,0 +1,14 @@
1
+ import { toast } from 'vue-sonner'
2
+
3
+ export function useToast() {
4
+ return {
5
+ success: (message: string) => toast.success(message),
6
+ error: (message: string) => toast.error(message),
7
+ info: (message: string) => toast.info(message),
8
+ warning: (message: string) => toast.warning(message),
9
+ promise: <T>(
10
+ promise: Promise<T>,
11
+ msgs: { loading: string; success: string | ((data: T) => string); error: string | ((err: unknown) => string) }
12
+ ) => toast.promise(promise, msgs),
13
+ }
14
+ }
@@ -0,0 +1,158 @@
1
+ // Toàn bộ nội dung trong file này là dữ liệu giả (mock data) dùng để dựng giao diện demo.
2
+
3
+ export const heroStats = [
4
+ { value: "1.000+", label: "Doanh nghiệp tin dùng" },
5
+ { value: "50%", label: "Tiết kiệm chi phí bản quyền" },
6
+ { value: "99.99%", label: "Uptime đảm bảo (HA)" },
7
+ { value: "24/7", label: "Hỗ trợ kỹ thuật chuyên gia" }
8
+ ]
9
+
10
+ export const veFeatureList = [
11
+ { icon: "cpu", text: "Quản lý VM (KVM) & Container (LXC)" },
12
+ { icon: "globe", text: "Giao diện web trực quan, dễ vận hành" },
13
+ { icon: "layers", text: "Snapshot, Backup & Restore tích hợp" },
14
+ { icon: "activity", text: "Monitoring tài nguyên theo thời gian thực" },
15
+ { icon: "share", text: "Software Defined Network (SDN)" },
16
+ { icon: "cluster", text: "Hỗ trợ cluster HA tự động failover" }
17
+ ]
18
+
19
+ export const veFeatureCards = [
20
+ { icon: "cpu", title: "KVM Virtualization", desc: "Hiệu năng gần native" },
21
+ { icon: "database", title: "Ceph Storage", desc: "Lưu trữ phân tán HA" },
22
+ { icon: "share", title: "SDN / Networking", desc: "VLAN, VXLAN tích hợp" },
23
+ { icon: "shield", title: "Proxmox Backup", desc: "Dedup & incremental" },
24
+ { icon: "bolt", title: "HA Cluster", desc: "Failover tự động" },
25
+ { icon: "chart", title: "Monitoring", desc: "Real-time dashboard" }
26
+ ]
27
+
28
+ export const hciOverviewCards = [
29
+ {
30
+ icon: "layers",
31
+ title: "Hợp nhất Compute – Storage – Network",
32
+ desc: "Tích hợp toàn bộ thành phần hạ tầng vào một platform duy nhất, giảm thiểu độ phức tạp vận hành."
33
+ },
34
+ {
35
+ icon: "database",
36
+ title: "Ceph Distributed Storage",
37
+ desc: "Lưu trữ phân tán hiệu năng cao với deduplication, không cần SAN/NAS riêng biệt."
38
+ },
39
+ {
40
+ icon: "share",
41
+ title: "Software Defined Network",
42
+ desc: "Quản lý VLAN, VXLAN, SDN ngay trong giao diện web Proxmox, không cần thiết bị mạng phức tạp."
43
+ },
44
+ {
45
+ icon: "shield",
46
+ title: "Backup & Disaster Recovery",
47
+ desc: "Proxmox Backup Server tích hợp incremental backup, khôi phục nhanh, tiết kiệm dung lượng."
48
+ },
49
+ {
50
+ icon: "bolt",
51
+ title: "High Availability Cluster",
52
+ desc: "Tự động failover khi node gặp sự cố, duy trì uptime 99.99% không gián đoạn dịch vụ."
53
+ },
54
+ {
55
+ icon: "trending",
56
+ title: "Mở rộng không giới hạn",
57
+ desc: "Thêm node mới vào cluster không gián đoạn hệ thống, scale linh hoạt theo nhu cầu doanh nghiệp."
58
+ }
59
+ ]
60
+
61
+ export const trainingBenefits = [
62
+ {
63
+ icon: "book",
64
+ title: "Làm chủ toàn diện Proxmox VE",
65
+ desc: "Nắm vững trọn vẹn các khái niệm cốt lõi, từ triển khai ảo hóa cơ bản đến quản trị các tính năng cao cấp."
66
+ },
67
+ {
68
+ icon: "users",
69
+ title: "Học từ chuyên gia thực chiến",
70
+ desc: "Tiếp thu kinh nghiệm từ đội ngũ giảng viên được chứng nhận, đã xử lý hàng ngàn bài toán hệ thống."
71
+ },
72
+ {
73
+ icon: "layers",
74
+ title: "Thực hành Lab môi trường thật",
75
+ desc: "Trực tiếp thao tác và giải quyết tình huống giả lập sát với thực tế doanh nghiệp, không chỉ lý thuyết."
76
+ },
77
+ {
78
+ icon: "bolt",
79
+ title: "Troubleshooting thần tốc",
80
+ desc: "Trang bị kỹ năng nhận diện 'bệnh' hệ thống nhạy bén, xử lý sự cố chính xác và dứt điểm."
81
+ },
82
+ {
83
+ icon: "trending",
84
+ title: "Thăng tiến & Khẳng định vị thế",
85
+ desc: "Nâng tầm giá trị bản thân, tạo dựng sự tín nhiệm với cấp trên nhờ năng lực chuyên môn vượt trội."
86
+ },
87
+ {
88
+ icon: "award",
89
+ title: "Chứng nhận chính quy",
90
+ desc: "Nhận chứng chỉ hoàn thành khóa học danh giá, minh chứng rõ nét cho năng lực và sự đầu tư nghiêm túc."
91
+ }
92
+ ]
93
+
94
+ export const comparisonRows = [
95
+ { label: "Hiệu suất", hci: "Vượt trội", legacy: "Đạt", legacyOk: true },
96
+ { label: "Chi phí ban đầu", hci: "Vượt trội", legacy: "Hạn chế" },
97
+ { label: "Triển khai ban đầu", hci: "Vượt trội", legacy: "Hạn chế" },
98
+ { label: "Mở rộng tài nguyên", hci: "Vượt trội", legacy: "Hạn chế" },
99
+ { label: "Quản lý & vận hành", hci: "Vượt trội", legacy: "Hạn chế" },
100
+ { label: "Quản lý ngân sách", hci: "Vượt trội", legacy: "Hạn chế" },
101
+ { label: "Uptime", hci: "Vượt trội", legacy: "Hạn chế" },
102
+ { label: "Sao lưu dự phòng", hci: "Vượt trội", legacy: "Hạn chế" },
103
+ { label: "Chuyển đổi mô hình", hci: "Vượt trội", legacy: "Hạn chế" }
104
+ ]
105
+
106
+ export const testimonials = [
107
+ {
108
+ quote:
109
+ "Ban đầu định đăng ký gói cao nhất, nhưng buổi tư vấn cho thấy đội em chỉ cần gói Nền tảng. Tiết kiệm được kha khá chi phí mà vẫn đạt mục tiêu.",
110
+ name: "Học viên",
111
+ role: "System Admin"
112
+ },
113
+ {
114
+ quote:
115
+ "Người tư vấn hỏi rất kỹ về hạ tầng hiện tại trước khi gợi ý khóa học, nên nội dung học sát với công việc thật, không lan man.",
116
+ name: "Học viên",
117
+ role: "DevOps Engineer"
118
+ },
119
+ {
120
+ quote:
121
+ "Nhờ được tư vấn đúng lộ trình, công ty chuyển từ VMware sang cluster HCI trên Proxmox và cắt gần 60% chi phí license hàng năm.",
122
+ name: "Học viên",
123
+ role: "IT Manager"
124
+ }
125
+ ]
126
+
127
+ export const faqItems = [
128
+ {
129
+ question: "Khóa học phù hợp với đối tượng nào?",
130
+ answer:
131
+ "Khóa học phù hợp với System Admin, DevOps Engineer, IT Manager và bất kỳ ai đang vận hành hạ tầng máy chủ, muốn chuyển đổi sang nền tảng ảo hóa mã nguồn mở tiết kiệm chi phí."
132
+ },
133
+ {
134
+ question: "Học xong có thể làm được gì ngay?",
135
+ answer:
136
+ "Bạn có thể tự triển khai cluster Proxmox HCI hoàn chỉnh với Ceph Storage, SDN và HA, đồng thời xử lý các sự cố vận hành thường gặp trong môi trường doanh nghiệp."
137
+ },
138
+ {
139
+ question: "Môi trường lab thực hành như thế nào?",
140
+ answer:
141
+ "Học viên được cấp quyền truy cập lab thật, mô phỏng sát hạ tầng doanh nghiệp thực tế, thao tác trực tiếp trên cluster nhiều node thay vì chỉ xem demo."
142
+ },
143
+ {
144
+ question: "Thời gian học và hình thức học?",
145
+ answer:
146
+ "Khóa học tổ chức linh hoạt cả online và offline, với lịch học buổi tối hoặc cuối tuần để phù hợp với người đang đi làm."
147
+ },
148
+ {
149
+ question: "Có hỗ trợ sau khi hoàn thành khóa học không?",
150
+ answer:
151
+ "Học viên được tham gia nhóm hỗ trợ kỹ thuật riêng, tư vấn thêm khi triển khai thực tế tại doanh nghiệp sau khi kết thúc khóa học."
152
+ },
153
+ {
154
+ question: "Tôi chưa biết nên chọn khóa nào?",
155
+ answer:
156
+ "Đặt lịch tư vấn miễn phí 15 phút, đội ngũ chuyên gia sẽ khảo sát hạ tầng hiện tại và đề xuất lộ trình học phù hợp nhất với bạn."
157
+ }
158
+ ]
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="layer-theme pt-16">
3
+ <slot />
4
+ </div>
5
+ </template>
@@ -0,0 +1,9 @@
1
+ export default defineNuxtConfig({
2
+ imports: {
3
+ dirs: [
4
+ "composables/**",
5
+ "services",
6
+ "types",
7
+ ],
8
+ },
9
+ })