68 Commits

Author SHA1 Message Date
nguyen van thai b4aa3e45d1 thainv-dev: Fix emagazine 2024-07-18 17:11:42 +07:00
nguyen van thai a63155a782 thainv-dev: Fix 2024-07-17 14:27:21 +07:00
nguyen van thai 45f21ba187 Fix Footer 2024-07-17 11:00:01 +07:00
nguyen van thai 795cd47e41 thainv-dev: Fix 2024-07-17 10:35:35 +07:00
nguyen van thai 5f9525371d Poll 2024-07-16 22:54:15 +07:00
nguyen van thai 6f571d9549 fix 2024-07-16 13:06:34 +07:00
nguyen van thai 174a596db9 Fix 2024-07-16 11:37:38 +07:00
Duong Truong Phong 5f72a107ce phongdt:add id component 2024-07-16 11:19:46 +07:00
nguyen van thai 5a041acd54 Fix 2024-07-16 09:58:54 +07:00
nguyen van thai 9cc998e0bf Fix 2024-07-16 09:23:44 +07:00
MoreStrive 7565a37d60 feat: create by site 2024-07-15 21:02:22 +07:00
MoreStrive 043f97743c Merge branch 'main' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 2024-07-12 19:14:35 +07:00
MoreStrive dffbe39fa6 minhnt-dev: fix page template 2024-07-12 19:13:34 +07:00
nguyen van thai 7151e7d311 . 2024-07-11 08:38:56 +07:00
nguyen van thai 7da82c9101 . 2024-07-10 17:39:38 +07:00
nguyen van thai 212e6d357c . 2024-07-09 16:17:48 +07:00
nguyen van thai 7cb6199610 fix 2024-07-09 15:27:08 +07:00
nguyen van thai 0dba7790b1 Fix 2024-07-09 15:15:22 +07:00
nguyen van thai 776a3cf2c7 fix 2024-07-09 09:41:47 +07:00
nguyen van thai 2e49529934 Fix Responsive 2024-07-09 09:23:17 +07:00
nguyen van thai 28ce3d42a0 Fix Responsive 2024-07-09 08:33:33 +07:00
nguyen van thai 08ad924483 Responsive 2024-07-08 17:38:54 +07:00
MoreStrive 76b2fa4771 minhnt-dev: scroll smooth 2024-07-08 17:03:33 +07:00
MoreStrive 81bfa351e8 Merge branch 'main' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 2024-07-06 18:29:42 +07:00
MoreStrive a3e20c9445 feat: select tag 2024-07-06 18:29:38 +07:00
nguyen van thai 6806201085 thainv-dev: Fix 2024-07-06 18:19:38 +07:00
MoreStrive 4ec2e425df Merge branch 'main' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 2024-07-06 16:34:43 +07:00
MoreStrive 31175ade27 minhnt-dev: fix lodash 2024-07-06 16:34:25 +07:00
nguyen van thai ccd92c86ee thainv-dev: Nhúng 2024-07-06 16:28:45 +07:00
phongdt a47229f44e Merge pull request 'phongdt:fix paging' (#9) from phongdt into main
Reviewed-on: http://work.gct.com.vn/minhnt/NSG_PORTAL_V2/pulls/9
2024-07-05 15:32:49 +07:00
Duong Truong Phong 0ad19bbcfd phongdt:fix paging 2024-07-05 15:30:58 +07:00
phongdt 11ea05de83 Merge pull request 'phongdt' (#8) from phongdt into main
Reviewed-on: http://work.gct.com.vn/minhnt/NSG_PORTAL_V2/pulls/8
2024-07-05 15:13:26 +07:00
Duong Truong Phong e738cca263 Merge branch 'phongdt' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 into phongdt 2024-07-05 15:13:00 +07:00
Duong Truong Phong b93f0218a5 phongdt:paging 2024-07-05 15:12:49 +07:00
Duong Truong Phong 3fe4da0ecb phongdt: paging component 2024-07-05 15:12:49 +07:00
Duong Truong Phong a9d6bea337 phongdt:paging 2024-07-05 15:12:16 +07:00
nguyen van thai df31b7bdef . 2024-07-05 15:03:50 +07:00
MoreStrive a1c6e2872f feat: full size 2024-07-05 15:01:01 +07:00
Duong Truong Phong 46b808cf9c phongdt: paging component 2024-07-05 14:51:25 +07:00
MoreStrive 367374863e Merge branch 'main' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 2024-07-05 14:29:53 +07:00
MoreStrive 66b5a8ce6a feat: widget 2024-07-05 14:29:49 +07:00
nguyen van thai adecec9041 Layout Page Component 2024-07-05 14:17:41 +07:00
MoreStrive 984ec50a39 Merge branch 'main' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 2024-07-05 13:05:28 +07:00
MoreStrive a756c91bd0 feat: widget 2024-07-05 13:05:21 +07:00
nguyen van thai cf64f11e72 thainv-dev 2024-07-05 11:57:41 +07:00
nguyen van thai 780474bcb3 Merge branch 'main' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 2024-07-05 11:41:49 +07:00
nguyen van thai be1393b7da thainv-dev: Layout Page Section 2024-07-05 11:41:38 +07:00
MoreStrive a5f9ff7bac fix: null component 2024-07-05 11:02:04 +07:00
nguyen van thai 5889e9af0e thainv-dev 2024-07-05 10:39:07 +07:00
MoreStrive 17036b77af feat: layout none bugs 2024-07-05 10:29:08 +07:00
MoreStrive ac218aeac5 fea: navigation 2024-07-05 10:03:28 +07:00
MoreStrive 815ce88d95 Merge branch 'main' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 2024-07-05 09:48:47 +07:00
MoreStrive 76d4628100 feat: fix type 2024-07-05 09:48:34 +07:00
nguyen van thai 7bf902041e thainv-dev 2024-07-05 09:45:00 +07:00
nguyen van thai 554ceab3c6 thainv-dev: Fix 2024-07-03 15:33:51 +07:00
MoreStrive ee5c6f40f1 deploy 2024-07-02 16:03:24 +07:00
MoreStrive 4bf217c207 Merge branch 'main' of http://work.gct.com.vn/minhnt/NSG_PORTAL_V2 2024-07-01 16:06:28 +07:00
MoreStrive 0adb6fca36 minhnt-dev: Video Card 2024-07-01 16:05:16 +07:00
nguyen van thai 35f069a776 thainv-dev: 2024-07-01 16:04:16 +07:00
MoreStrive a01eedc2bc minhnt-dev: fix bugs 2024-07-01 15:14:17 +07:00
MoreStrive 03ca9c6603 minhnt-dev: fix bugs 2024-07-01 14:51:57 +07:00
MoreStrive 5a207435ce feat: bugs 2024-06-28 17:25:58 +07:00
MoreStrive 40622caf61 feat: fix lodash import 2024-06-28 16:41:36 +07:00
nguyen van thai 26e4a289d7 a 2024-06-28 16:27:39 +07:00
phongdt cbc0f8b7c0 Merge pull request 'phongdt:fix ui' (#7) from phongdt into main
Reviewed-on: http://work.gct.com.vn/minhnt/NSG_PORTAL_V2/pulls/7
2024-06-28 16:15:35 +07:00
Duong Truong Phong 80a5aae4e6 phongdt:fix ui 2024-06-28 16:14:06 +07:00
MoreStrive a46ef56e07 feat: scss 2024-06-28 16:04:13 +07:00
MoreStrive 1f60621995 feat: scss 2024-06-28 15:44:12 +07:00
139 changed files with 4532 additions and 1790 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

-2
View File
@@ -1,6 +1,4 @@
@import custom.css
body
@apply font-beVietnam text-black-500
// video
// max-width: 100% !important
+2 -2
View File
@@ -1,8 +1,8 @@
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
/* @import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,100..900;1,100..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,100..900;1,100..900&family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700;1,900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,100..900;1,100..900&family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700;1,900&display=swap'); */
.custom_scrollbar {
white-space: nowrap;
+186 -24
View File
@@ -1,14 +1,127 @@
// .style_layout {
// > .section-container {
// > .layout_define {
// > .section_layout {
// @apply gap-x-10 #{!important};
// }
// }
@import url("https://fonts.googleapis.com/css2?family=Gelasio:ital,wght@0,400..700;1,400..700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap");
// Variables
$font-gelasio: "Gelasio", serif;
$font-raleway: "Raleway", sans-serif;
$color-primary: #ED1C24;
$color-brown: #9E1E0F;
$color-text: #151411;
$color-paragraph: #AFADB5;
$color-placeholder: #F9F9F9;
$color-line: #EDEDED;
// Mixins
// extends
body {
font-size: 14px;
font-family: $font-raleway;
scroll-behavior: smooth;
}
// %headings {
// color: $color-text;
// font-family: $font-gelasio;
// line-height: 130%;
// font-weight: 700;
// }
// %label {
// color: $color-text;
// font-family: $font-raleway;
// line-height: 130%;
// font-weight: 700;
// }
// %paragraph {
// color: $color-paragraph;
// font-family: $font-raleway;
// line-height: 180%;
// font-weight: 500;
// }
// h1, h2, h3, h4, h5, h6 {
// font-family: $font-gelasio;
// }
// h1 a {
// @extend %headings;
// font-size: 64px;
// }
// h2 a{
// @extend %headings;
// font-size: 44px;
// }
// h3 a{
// @extend %headings;
// font-size: 24px;
// }
// h4 a{
// @extend %headings;
// font-size: 20px;
// }
// h5 a{
// @extend %headings;
// font-size: 18px;
// }
// h6 a{
// @extend %headings;
// font-size: 16px;
// }
// h6.h6-plus a {
// @extend %headings;
// font-size: 14px;
// font-weight: 500;
// }
// label {
// &.label-l1 {
// @extend %label;
// font-size: 18px;
// }
// &.label-l2 {
// @extend %label;
// font-size: 16px;
// }
// &.label-l3 {
// @extend %label;
// font-size: 14px;
// }
// }
// p {
// &.paragraph-p1 {
// font-size: 18px;
// }
// &.paragraph-p2 {
// font-size: 16px;
// }
// &.paragraph-p2 {
// font-size: 14px;
// }
// }
a {
@apply hover:text-primary font-gelasio font-700 leading-130%;
}
figure {
margin: auto !important;
}
@@ -17,27 +130,27 @@ img {
object-fit: cover!important;
}
.content {
& p {
@apply mb-2 font-arial leading-160%;
}
// .content {
// & p {
// @apply mb-2 font-arial leading-160%;
// }
& #title {
@apply font-merriweather font-bold leading-150%;
}
// & #title {
// @apply font-merriweather font-bold leading-150%;
// }
& #intro, & #sub {
@apply font-arial font-medium leading-160%;
}
// & #intro, & #sub {
// @apply font-arial font-medium leading-160%;
// }
& audio {
@apply w-full;
}
// & audio {
// @apply w-full;
// }
& document, & a, & custom-figure, & author {
@apply cursor-pointer text-primary-600;
}
}
// & document, & a, & custom-figure, & author {
// @apply cursor-pointer text-primary;
// }
// }
div[layout="TYPE:Detail-LAYOUT:image"] {
& p,& figure.align-center-image, & #sub, & #title, & #intro, & #breadcrumb, & #navigation__bottom {
@@ -56,3 +169,52 @@ div[layout="ARTICLE_PAGE"] {
@apply md:gap-20px
}
}
.container {
max-width: 1385px;
}
.layout_container {
& > .section_layout {
@apply mt-12 first:mt-0;
}
}
@media (max-width: 640px) {
.collection-container {
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
& > article.basic-article {
flex-direction: row!important;
& > .basic-article_thumbnail {
width: 50%!important;
}
}
}
}
.emagazine, .infographic, .image {
p {
@apply w-full max-w-660px mx-auto text-18px font-raleway leading-180%;
}
figure {
@apply w-full;
}
img {
@apply mx-auto;
}
}
.emagazine {
h1,h2,h3,h4,h5,h6,span,em {
@apply w-full max-w-660px mx-auto;
}
}
.detail-default {
p {
@apply text-18px font-raleway leading-180% my-10px;
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ const props = defineProps<{ events: any[] }>()
<div v-if="events?.length" class="mt-6">
<h3 class="text-xl font-semibold after:content-[':']">Sự kiện</h3>
<ul class="flex flex-col gap-2 list-disc ml-4 my-2 pl-3 flex-wrap">
<li v-for="(event, index) in events" :key="index" class="font-semibold text-primary-100">
<li v-for="(event, index) in events" :key="index" class="font-semibold text-primary">
<nuxt-link :to="{ name: 'event-eventSlug', params: { eventSlug: event.code } }">{{ event.title }}</nuxt-link>
</li>
</ul>
+74 -24
View File
@@ -15,13 +15,14 @@ const store = reactive({
});
const { currentPoll } = storeToRefs(store.poll);
const { currentPollOptions } = storeToRefs(store.pollOptions);
const { currentPollResponses } = storeToRefs(store.pollResponse);
// const { currentPollResponses } = storeToRefs(store.pollResponse);
const poll = reactive<Poll | any>({});
const options = ref<PollOption[] | any[]>([]);
async function loadData() {
await store.poll.fetchById(String(props.dataId));
await store.pollOptions.fetchByPollId(String(props.dataId));
await store.pollResponse.fetchByPollId(String(props.dataId));
// await store.pollResponse.fetchByPollId(String(props.dataId));
assignData();
}
@@ -30,13 +31,14 @@ function assignData() {
options.value = currentPollOptions.value;
}
onBeforeMount(async () => {
await loadData();
});
await loadData();
// onBeforeMount(async () => {
// });
const selectedOption = ref<any>(-1);
const showResult = ref(false);
// const selectedOption = ref<any>(-1);
// const showResult = ref(false);
const alreadyVoted = ref(false);
const showResult = ref(false)
const canShowResult = computed(() => {
switch (poll.settings?.resultPublication) {
case 0:
@@ -49,29 +51,77 @@ const canShowResult = computed(() => {
return alreadyVoted.value && (!poll.endTime || new Date() > new Date(poll.endTime));
}
});
// const toggleResults = () => {
// if (canShowResult.value) showResult.value = !showResult.value;
// };
const singleSelect = ref<number>(-1)
const multipleSelect = ref<number []>([])
const totalResponses = ref(0);
async function submitVote() {
if (selectedOption.value >= 0 && !alreadyVoted.value) {
const result = await store.pollResponse.create({ optionId: selectedOption.value });
if (result?.id) {
totalResponses.value = options.value?.reduce((sum, option) => sum + Number(option.responseCount ?? 0), 1);
options?.value?.forEach((option: PollOption | any) => {
if (option.id === selectedOption.value) {
option.responseCount = (option.responseCount ?? 0) + 1;
// if (selectedOption.value >= 0 && !alreadyVoted.value) {
// const result = await store.pollResponse.create({ optionId: selectedOption.value });
// if (result?.id) {
// totalResponses.value = options.value?.reduce((sum, option) => sum + Number(option.responseCount ?? 0), 1);
// options?.value?.forEach((option: PollOption | any) => {
// if (option.id === selectedOption.value) {
// option.responseCount = (option.responseCount ?? 0) + 1;
// }
// option.percentage = Number(((option.responseCount / totalResponses.value) * 100).toFixed(1));
// });
// alreadyVoted.value = true;
// }
// }
switch (poll.type) {
case 1:
if(singleSelect.value >= 0) {
const result = await store.pollResponse.create({ optionId: singleSelect.value })
if(result?.id) {
totalResponses.value = options.value?.reduce((sum, option) => sum + Number(option.responseCount ?? 0), 1);
options?.value?.forEach((option: PollOption | any) => {
if (option.id === singleSelect.value) {
option.responseCount = (option.responseCount ?? 0) + 1;
}
option.percentage = Number(((option.responseCount / totalResponses.value) * 100).toFixed(1));
alreadyVoted.value = true
});
}
option.percentage = Number(((option.responseCount / totalResponses.value) * 100).toFixed(1));
});
alreadyVoted.value = true;
}
}
}
}
const toggleResults = () => {
if (canShowResult.value) showResult.value = !showResult.value;
};
</script>
<template>
<span class="inline-block px-4 py-2 shadow-xl rounded-lg bg-[#f5f5f5]">
<div class="inline-block px-4 py-2 mb-5">
<h3 class="text-#000 font-raleway font-italic text-16px font-500 leading-140% mb-2">{{ poll?.title }}</h3>
<ul class="flex flex-col gap-3">
<template v-if="poll.type === 1">
<li v-for="(option, index) in options" :key="index">
<input :id="`option-${index}`" :disabled="singleSelect >= 0 && alreadyVoted ? true : false" type="radio" class="peer opacity-0" :value="option.id" v-model="singleSelect">
<label :for="`option-${index}`" class="font-raleway text-16px relative text-#000 font-400 leading-140% pl-16px before:content-[''] before:inline-block before:w-14px before:h-14px before:rounded-50% before:border-1 before:absolute before:left--3 before:top-10px before:border-1px before:border-#000 before:translate-y--50% after:absolute after:left--10px after:top-10px after:translate-y--50% after:border-#000 after:content-[''] after:inline-block after:w-10px after:h-10px after:rounded-full peer-checked:after:bg-primary peer-checked:before:border-primary ">{{ option?.title }}</label>
</li>
</template>
</ul>
<div class="flex items-center justify-center mt-3">
<button @click="submitVote()" v-if="!alreadyVoted" :disabled="alreadyVoted && singleSelect >= 0 ? true : false" class="px-5 py-10px rounded-6px bg-primary text-#fff font-raleway text-14px font-400">Gửi kết quả</button>
<button @click="showResult = true" v-if="alreadyVoted" class="px-5 py-10px rounded-6px bg-primary text-#fff font-raleway text-14px font-400">Xem kết quả</button>
</div>
<div class="mt-5" v-if="showResult && canShowResult">
<h3 class="text-#000 font-raleway font-italic text-16px font-500 leading-140% mb-2">{{ poll?.title }}</h3>
<ul>
<li v-for="(answer, index) in options" :key="index" class="flex gap-4 items-center text-#000 font-raleway text-12px leading-140%">
{{ answer.percentage}}%
<div :style="{ width: `${answer.percentage}%` }" :class="answer.id === singleSelect ? 'bg-primary' : 'bg-#AEAEAE'" class="h-1.5 rounded-full"></div>
</li>
</ul>
</div>
</div>
<!-- <span class="inline-block px-4 py-2 shadow-xl rounded-lg bg-[#f5f5f5]">
<span class="block">
<span class="underline decoration-gray-500 font-bold">
{{ poll?.title }}
@@ -100,5 +150,5 @@ const toggleResults = () => {
</span>
<b>Tổng số lượt binh chọn: {{ totalResponses }}</b>
</span>
</span>
</span> -->
</template>
+17 -7
View File
@@ -246,23 +246,33 @@ async function submitSend() {}
</script>
<template>
<div class="inline-block px-4 py-2 shadow-xl rounded-lg bg-[#f5f5f5] !text-black-500">
<div class="inline-block px-4 py-2 !text-black-500">
<h5 class="underline decoration-gray-500 font-bold mb-2">Câu đố: {{ data?.title }}</h5>
<ul class="px-3">
<ul class="px-3 flex flex-col gap-3">
<li v-for="(question, questionIndex) in data.questionGeneral" :key="questionIndex" class="mb-2">
<h5 class="mb-1 font-700 text-black-500">{{ `${questionIndex + 1}. ${question.title}` }}</h5>
<h5 class="text-#000 font-raleway font-italic text-16px font-500 leading-140% mb-2">{{ `${questionIndex + 1}. ${question.title}` }}</h5>
<ul>
<template v-if="question.type === 1">
<li v-for="(answer, answerIndex) in question.answers" :key="answerIndex" class="flex items-center gap-1 py-1">
<input :id="`answer-${questionIndex}-${answerIndex}`" :type="question.type === 1 ? 'radio' : 'checkbox'" :value="answerIndex" v-model="selectQuizAnswer[questionIndex]" />
<label :for="`answer-${questionIndex}-${answerIndex}`" class="font-semibold">{{ answer.title }}</label>
<input :id="`answer-${questionIndex}-${answerIndex}`" type="radio" :value="answerIndex" class="peer opacity-0" v-model="selectQuizAnswer[questionIndex]" />
<label :for="`answer-${questionIndex}-${answerIndex}`" class="font-raleway text-16px relative text-#000 font-400 leading-140% pl-16px before:content-[''] before:inline-block before:w-14px before:h-14px before:rounded-50% before:border-1 before:absolute before:left--3 before:top-10px before:border-1px before:border-#000 before:translate-y--50% after:absolute after:left--10px after:top-10px after:translate-y--50% after:border-#000 after:content-[''] after:inline-block after:w-10px after:h-10px after:rounded-full peer-checked:after:bg-primary peer-checked:before:border-primary ">{{ answer.title }}</label>
</li>
</template>
<template v-else>
<li v-for="(answer, answerIndex) in question.answers" :key="answerIndex" class="flex items-center gap-1 py-1">
<input :id="`answer-${questionIndex}-${answerIndex}`" type="checkbox" :value="answerIndex" class="peer opacity-0" v-model="selectQuizAnswer[questionIndex]" />
<label :for="`answer-${questionIndex}-${answerIndex}`" class="font-raleway text-16px relative text-#000 font-400 leading-140% pl-16px before:content-[''] before:inline-block before:w-14px before:h-14px before:rounded-2px before:border-1 before:absolute before:left--3 before:top-10px before:border-1px before:border-#000 before:translate-y--50% after:absolute after:left--10px after:top-6px after:translate-y--50% after:border-#000 after:content-['✔'] after:text-12px after:text-transparent after:inline-block after:w-14px after:h-14px peer-checked:after:text-white peer-checked:before:border-primary peer-checked:before:bg-primary ">{{ answer.title }}</label>
</li>
</template>
</ul>
</li>
</ul>
<button @click="submitSend" class="bg-primary-500 text-white py-1 px-3 rounded-4px cursor-pointer hover:bg-primary-600 float-right">Gửi câu trả lời</button>
<div class="flex justify-center">
<button @click="submitSend" class="mx-auto px-5 py-10px rounded-6px bg-primary text-#fff font-raleway text-14px font-400">Gửi câu trả lời</button>
</div>
</div>
<!-- <div>
<h5 class="text-black text-18px font-700">{{ data?.title }}</h5>
@@ -274,7 +284,7 @@ async function submitSend() {}
<li
v-for="(index, item) in data.questionGeneral.length"
:key="index"
:class="step >= index - 1 ? 'bg-primary-500 text-white transition-all delay-300' : 'bg-white text-primary-500'"
:class="step >= index - 1 ? 'bg-primary-500 text-white transition-all delay-300' : 'bg-white text-primary'"
class="relative z-3 w-7 h-7 rounded-full flex items-center justify-center border-2 border-solid border-primary-500"
>
<template template v-if="step > index - 1"><Icon name="material-symbols:check-rounded" class="text-22px" /></template>
+23 -6
View File
@@ -270,24 +270,41 @@ async function submitSend() {
<template>
<div class="inline-block px-4 py-2 shadow-xl rounded-lg bg-[#f5f5f5] !text-black-500">
<div class="inline-block px-4 py-2 !text-black-500">
<h5 class="underline decoration-gray-500 font-bold mb-2">Khảo sát: {{ dataSurvey?.title }}</h5>
<ul class="px-3">
<li v-for="(question, questionIndex) in dataSurvey.questionGeneral" :key="questionIndex" class="mb-2">
<h5 class="mb-1 font-700 text-black-500">{{ `${questionIndex + 1}. ${question.title}` }}</h5>
<h5 class="text-#000 font-raleway font-italic text-16px font-500 leading-140% mb-2">{{ `${questionIndex + 1}. ${question.title}` }}</h5>
<ul>
<!-- <ul>
<li v-for="(answer, answerIndex) in question.answers" :key="answerIndex" class="flex items-center gap-1 py-1">
<input :id="`answer-survey-${questionIndex}-${answerIndex}`" :type="question.type === 1 ? 'radio' : 'checkbox'" :value="answerIndex" v-model="selectSurveyAnswer[questionIndex]">
<label :for="`answer-survey-${questionIndex}-${answerIndex}`" class="font-semibold">{{ answer.title }}</label>
</li>
</ul>
</ul> -->
<ul>
<template v-if="question.type === 1">
<li v-for="(answer, answerIndex) in question.answers" :key="answerIndex" class="flex items-center gap-1 py-1">
<input :id="`answer-survey-${questionIndex}-${answerIndex}`" type="radio" :value="answerIndex" class="peer opacity-0" v-model="selectSurveyAnswer[questionIndex]" />
<label :for="`answer-survey-${questionIndex}-${answerIndex}`" class="font-raleway text-16px relative text-#000 font-400 leading-140% pl-16px before:content-[''] before:inline-block before:w-14px before:h-14px before:rounded-50% before:border-1 before:absolute before:left--3 before:top-10px before:border-1px before:border-#000 before:translate-y--50% after:absolute after:left--10px after:top-10px after:translate-y--50% after:border-#000 after:content-[''] after:inline-block after:w-10px after:h-10px after:rounded-full peer-checked:after:bg-primary peer-checked:before:border-primary ">{{ answer.title }}</label>
</li>
</template>
<template v-else>
<li v-for="(answer, answerIndex) in question.answers" :key="answerIndex" class="flex items-center gap-1 py-1">
<input :id="`answer-survey-${questionIndex}-${answerIndex}`" type="checkbox" :value="answerIndex" class="peer opacity-0" v-model="selectSurveyAnswer[questionIndex]" />
<label :for="`answer-survey-${questionIndex}-${answerIndex}`" class="font-raleway text-16px relative text-#000 font-400 leading-140% pl-16px before:content-[''] before:inline-block before:w-14px before:h-14px before:rounded-2px before:border-1 before:absolute before:left--3 before:top-10px before:border-1px before:border-#000 before:translate-y--50% after:absolute after:left--10px after:top-6px after:translate-y--50% after:border-#000 after:content-['✔'] after:text-12px after:text-transparent after:inline-block after:w-14px after:h-14px peer-checked:after:text-white peer-checked:before:border-primary peer-checked:before:bg-primary ">{{ answer.title }}</label>
</li>
</template>
</ul>
</li>
</ul>
<button @click="submitSend" class="bg-primary-500 text-white py-1 px-3 rounded-4px cursor-pointer hover:bg-primary-600 float-right">Gửi câu trả lời</button>
<div class="flex justify-center">
<button @click="submitSend" class="mx-auto px-5 py-10px rounded-6px bg-primary text-#fff font-raleway text-14px font-400">Gửi câu trả lời</button>
</div>
<!-- <button @click="submitSend" class="bg-primary-500 text-white py-1 px-3 rounded-4px cursor-pointer hover:bg-primary-600 float-right">Gửi câu trả lời</button> -->
</div>
</template>
@@ -4,26 +4,35 @@ import DynamicComponent from "~/components/dynamic-page/page-component/templates
import { useDynamicPageStore } from '~/stores/dynamic-page';
const { currentPage } = storeToRefs(useDynamicPageStore());
const props = defineProps<{
type: string; // [TOP_NAVIGATION, BOTTOM_NAVIGATION]
type?: any; // [TOP_NAVIGATION, BOTTOM_NAVIGATION]
}>();
const contentParse = computed(() => (currentPage.value.content ? JSON.parse(currentPage.value.content) : {}));
const defineTypeRecusive = {
TOP_NAVIGATION: enumPageComponentLayouts[[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']}`]]['NAVIGATION_TOP_DEFAULT'],
BOTTOM_NAVIGATION: enumPageComponentLayouts[[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['BOTTOM']}`]]['NAVIGATION_BOTTOM_DEFAULT'],
TOP_NAVIGATION: enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']}`]['NAVIGATION_TOP_DEFAULT'],
BOTTOM_NAVIGATION: enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['BOTTOM']}`]['NAVIGATION_BOTTOM_DEFAULT'],
};
const findDataPosition = computed(() => {
const findDataPosition = computed<any>(() => {
let result = {};
switch (props.type) {
case defineTypeRecusive.TOP_NAVIGATION:
result = currentPage.value.components && currentPage.value.components.find((component: any) => {
return component.taxonomy === enumPageComponentKey.NAVIGATION && component.settings?.layout === defineTypeRecusive.TOP_NAVIGATION
});
if (contentParse.value.navigationTop) {
result =
currentPage.value.components &&
currentPage.value.components.find((component: any) => {
return component.id === contentParse.value.navigationTop;
});
}
break;
case defineTypeRecusive.BOTTOM_NAVIGATION:
result = currentPage.value.components && currentPage.value.components.find((component: any) => {
return component.taxonomy === enumPageComponentKey.NAVIGATION && component.settings?.layout === defineTypeRecusive.BOTTOM_NAVIGATION
});
if (contentParse.value.navigationBottom) {
result =
currentPage.value.components &&
currentPage.value.components.find((component: any) => {
return component.id === contentParse.value.navigationBottom;
});
}
break;
default:
result = {};
@@ -1,16 +1,15 @@
<script setup lang="ts"></script>
<template>
<div class="content">
<span>Quảng cáo đây</span>
<div class="pt-5">
<div class="content p-3">
<span class="text-12px text-[#AFADB5] text-end block">Quảng cáo</span>
<img class="block w-full h-full" src="/assets/images/tienphong/main-ads-2.jpg" alt="">
</div>
</div>
</template>
<style scoped lang="scss">
.content {
font-size: 18px;
background-color: #eeeeee;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
@@ -0,0 +1,13 @@
<script setup lang="ts"></script>
<template>
<div class="pt-5">
<div class="content p-3 border-y-1px border-solid border-#000">
<img class="mx-auto max-h-[300px] object-cover" src="/assets/images/tienphong/ads_full.png" alt="">
</div>
</div>
</template>
<style scoped lang="scss">
.content {
font-size: 18px;
}
</style>
@@ -1 +1,2 @@
export { default as Default_Ads } from './Default.vue'
export { default as Main_Ads } from './Main.vue'
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
import { Default_Ads } from "./index";
import { Default_Ads, Main_Ads } from "./index";
const _props = defineProps<{
settings: any;
@@ -9,10 +9,10 @@ const _props = defineProps<{
}>();
const definedDynamicComponent: Record<string, any> = {
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.ADVERTISING]['ADVERTISING']}`]['DEFAULT']]: Default_Ads,
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.ADVERTISING]['ADVERTISING']}`]['MAIN']]: Main_Ads,
};
const getCurrentComponent = computed(() => _props.settings.layout);
const GET_PROPS = computed(() => {
return () => {
let props: any = {};
@@ -31,6 +31,7 @@ const GET_PROPS = computed(() => {
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
@@ -11,8 +11,7 @@ const definedDynamicComponent: Record<string, any> = {
[enumPageComponentTemplate[enumPageComponentKey.ADVERTISING]["ADVERTISING"]]: Advertisings,
};
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
console.log(definedDynamicComponent, "vào rrassd", getCurrentComponent.value);
const getCurrentComponent = computed(() => `${_props.settings.template}`);
const GET_PROPS = computed(() => {
return () => {
let props: any = {};
@@ -27,8 +26,13 @@ const GET_PROPS = computed(() => {
}
};
});
// console.log(getCurrentComponent.value, 'quảng caosd ád')
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
</template>
@@ -1,15 +1,15 @@
<script setup lang="ts">
const type = ref("");
import { enumPageComponentTemplates } from "@/definitions/enum";
import { DEFAULT_QUERY_DROP } from "@/utils/parseSQL";
import { getInputValue } from "@/utils/parseSQL";
import { DEFAULT_QUERY_DROP, getInputValue } from "@/utils/parseSQL";
const props = defineProps<{
dataResult?: any;
dataType?: any;
dataQuery?: any;
layout?: string;
label?: string;
label?: any;
component?: any;
}>();
const LAYOUT_PARSE = computed(() => {
@@ -17,12 +17,6 @@ const LAYOUT_PARSE = computed(() => {
return Object.assign({}, designObject);
});
const emit = defineEmits(["selectComponent", "dropData"]);
const selectComponent = () => {
emit("selectComponent");
};
const parseData = computed(() => {
if (!props.dataResult) return;
const result = getInputValue(props.dataResult, "OBJECT");
@@ -55,26 +49,16 @@ const parseData = computed(() => {
}
return result;
});
const drop = (e: any) => {
if (e.dataTransfer.getData(`${enumPageComponentTemplates.ARTICLE}`)) {
const data = e.dataTransfer.getData(`${enumPageComponentTemplates.ARTICLE}`);
const { dataType, dataResult } = JSON.parse(data);
const dataQuery = DEFAULT_QUERY_DROP(dataType, dataResult.id);
emit("dropData", {
dataType,
dataResult,
dataQuery: dataQuery,
});
}
};
</script>
<template>
<article class="card-audio" :class="LAYOUT_PARSE['article_Class']" @click="selectComponent" @dragover.prevent @drop.stop.prevent="drop" :style="LAYOUT_PARSE['article']">
<img :src="parseData?.thumbnail ? parseData?.thumbnail : 'https://indiaeducationdiary.in/wp-content/uploads/2021/02/SD-default-image.png'" :alt="parseData?.title?.replace(/<[^>]+>/g, '')" />
<article :id="`cpn_${props.component.id}`" class="card-audio" :class="LAYOUT_PARSE['article_Class']" :style="LAYOUT_PARSE['article']">
<nuxt-link :to="`/bai-viet/${parseData?.slug}`" class="article-thumbnail">
<img :src="parseData?.thumbnail ? parseData?.thumbnail : 'https://indiaeducationdiary.in/wp-content/uploads/2021/02/SD-default-image.png'" :alt="parseData?.title?.replace(/<[^>]+>/g, '')" />
</nuxt-link>
<div class="card-audio__content">
<span>
<template v-if="type === 'Image'">
<span class="flex justify-center">
<template v-if="['Image', 'Infographics', 'Emagazine'].includes(type)">
<svg width="28" height="22" viewBox="0 0 28 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M25.25 0.5H7.25C5.98438 0.5 5 1.53125 5 2.75V14.75C5 16.0156 5.98438 17 7.25 17H25.25C26.4688 17 27.5 16.0156 27.5 14.75V2.75C27.5 1.53125 26.4688 0.5 25.25 0.5ZM10.9531 3.5C11.75 3.5 12.4531 4.20312 12.4531 5C12.4531 5.84375 11.7969 6.5 10.9531 6.5C10.1094 6.5 9.45312 5.84375 9.45312 5C9.45312 4.20312 10.1562 3.5 10.9531 3.5ZM23.6562 13.625C23.5156 13.8594 23.2344 14 23 14H9.5C9.17188 14 8.9375 13.8594 8.79688 13.625C8.70312 13.3438 8.70312 13.0625 8.89062 12.8281L12.1719 8.32812C12.3125 8.14062 12.5 8 12.7812 8C13.0156 8 13.2031 8.14062 13.3438 8.32812L14.4219 9.78125L17.375 5.375C17.4688 5.14062 17.7031 5 17.9844 5C18.2188 5 18.4531 5.14062 18.5938 5.375L23.6094 12.875C23.75 13.0625 23.75 13.3906 23.6562 13.625ZM21.875 19.25H6.125C4.25 19.25 2.75 17.75 2.75 15.875V4.625C2.75 4.01562 2.23438 3.5 1.625 3.5C0.96875 3.5 0.5 4.01562 0.5 4.625V15.875C0.5 19.0156 2.98438 21.5 6.125 21.5H21.875C22.4844 21.5 23 21.0312 23 20.375C23 19.7656 22.4844 19.25 21.875 19.25Z"
@@ -82,42 +66,53 @@ const drop = (e: any) => {
/>
</svg>
</template>
</span>
<template v-if="['Postcard', 'Video'].includes(type)">
<svg width="28" height="18" viewBox="0 0 28 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.5 2.25V15.75C18.5 17.0156 17.4688 18 16.25 18H2.75C1.48438 18 0.5 17.0156 0.5 15.75V2.25C0.5 1.03125 1.48438 0 2.75 0H16.25C17.4688 0 18.5 1.03125 18.5 2.25ZM27.5 3V15.0469C27.5 16.2188 26.0938 16.9219 25.1094 16.2656L20 12.7031V5.34375L25.1094 1.78125C26.0938 1.125 27.5 1.82812 27.5 3Z" fill="white"/>
</svg>
<div class="card-audio__type-category">
</template>
</span>
<div class="card-audio__type-category" >
<div class="card-audio__type" v-if="type">{{ type }}</div>
<nuxt-link v-if="parseData" class="card-audio__category" :style="LAYOUT_PARSE['category-article']" :class="LAYOUT_PARSE['category-article_Class']">{{
<nuxt-link v-if="parseData?.category" to="#" class="card-audio__category" :style="LAYOUT_PARSE['category-article']" :class="LAYOUT_PARSE['category-article_Class']">{{
parseData?.category?.title
}}</nuxt-link>
<span v-else class="empty-block" style="height: 8px; width: 30px"></span>
</div>
<nuxt-link>
<h2 v-html="parseData.title" v-if="parseData" :class="LAYOUT_PARSE['title_Class']" :style="LAYOUT_PARSE['h3.title']"></h2>
<span v-else class="empty-block" style="height: 8px"></span>
</nuxt-link>
<h2 :class="LAYOUT_PARSE['title_Class']" :style="LAYOUT_PARSE['h3.title']">
<nuxt-link :to="`/bai-viet/${parseData?.code}`"><span v-html="parseData?.title"></span> </nuxt-link>
</h2>
</div>
<div v-html="LAYOUT_PARSE.styleClasses"></div>
</article>
<div v-if="LAYOUT_PARSE.styleClasses" v-html="LAYOUT_PARSE.styleClasses" style="display:none;"></div>
</template>
<style lang="scss">
.card-audio {
position: relative;
width: 100%;
padding-bottom: 56.25%; //tỷ lệ 9 /16;;
padding-bottom: calc((16 / 9) * 100%);
overflow: hidden;
img {
.article-thumbnail {
position: absolute;
height: 100%;
width: 100%;
object-fit: cover;
z-index: 1;
& img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.card-audio__content {
position: absolute;
width: 100%;
max-height: 100%;
bottom: 0;
padding: 20px 30px;
z-index: 3;
@@ -9,7 +9,8 @@ const props = defineProps<{
dataType?: any;
dataQuery?: any;
layout?: string;
label?: string;
label?: any;
component?: any;
}>();
const LAYOUT_PARSE = computed(() => {
@@ -17,107 +18,65 @@ const LAYOUT_PARSE = computed(() => {
return Object.assign({}, designObject);
});
const emit = defineEmits(["selectComponent", "dropData"]);
const selectComponent = () => {
emit("selectComponent");
};
const parseData = computed(() => {
if (!props.dataResult) return;
const result = getInputValue(props.dataResult, "OBJECT");
return result;
});
const drop = (e: any) => {
if (e.dataTransfer.getData(`${enumPageComponentTemplates.ARTICLE}`)) {
const data = e.dataTransfer.getData(`${enumPageComponentTemplates.ARTICLE}`);
const { dataType, dataResult } = JSON.parse(data);
const dataQuery = DEFAULT_QUERY_DROP(dataType, dataResult.id);
emit("dropData", {
dataType,
dataResult,
dataQuery: dataQuery,
});
}
};
</script>
<template>
<!-- <article class="article-card-default">
<div class="article-card-default__content">
<template v-if="currentArticle.tags && currentArticle?.tags.length > 0">
<nuxt-link class="article-card-default__tag" :to="`/${currentArticle?.tags[0].code}`">
<h5>{{ currentArticle?.tags[0].title }}</h5>
</nuxt-link>
</template>
<nuxt-link class="article-card-default__title" :to="`/bai-viet/${currentArticle.code}`">
<h2 v-html="currentArticle.title"></h2>
<p v-html="currentArticle.intro"></p>
</nuxt-link>
<div class="article-card-default__bottom">
<span>{{ formatDate(String(currentArticle.createdOn), "DD/MM/YYYY | HH:mm") }}</span> /
<nuxt-link :to="`/${currentArticle.category?.code}`"> {{ currentArticle.category?.title }}</nuxt-link>
</div>
</div>
<div class="article-card-default__thumbnail">
<figure>
<nuxt-link :to="`bai-viet${currentArticle.code}`">
<img :src="currentArticle?.thumbnail ? currentArticle?.thumbnail : 'http://picsum.photos/1024/600?random=1'" :alt="currentArticle?.title" />
</nuxt-link>
</figure>
</div>
</article> -->
<article
v-if="parseData"
:id="`cpn_${props.component.id}`"
class="basic-article border-custom"
:class="LAYOUT_PARSE['article_Class']"
@click="selectComponent"
@dragover.prevent
@drop.stop.prevent="drop"
:style="LAYOUT_PARSE['article']"
>
<div class="basic-article_thumbnail" :class="LAYOUT_PARSE['thumbnail_Class']" :style="LAYOUT_PARSE['div.basic-article_thumbnail']">
<div class="basic-article_thumbnail article-thumbnail" :class="LAYOUT_PARSE['thumbnail_Class']" :style="LAYOUT_PARSE['div.basic-article_thumbnail']">
<template v-if="parseData">
<img class="object-fit-cover" :src="parseData.thumbnail ? parseData.thumbnail : '/images/default-thumbnail.jpg'" :alt="parseData.title?.replace(/<[^>]+>/g, '')" />
<nuxt-link :to="`${parseData.code}`">
<img class="object-fit-cover" :src="parseData.thumbnail ? parseData.thumbnail : '/images/default-thumbnail.jpg'" :alt="parseData.title?.replace(/<[^>]+>/g, '')" />
</nuxt-link>
</template>
<span v-else class="empty-block" style="width: 100%; height: 100%; min-height: 50px"></span>
</div>
<div class="basic-article_content" :class="[!parseData && 'no-data']">
<template v-if="parseData?.topics && parseData?.topics.length > 0">
<nuxt-link class="article-card-default__topic" :to="`/${parseData?.topics[0].code}`" :style="LAYOUT_PARSE['topic']">
<h5>{{ parseData?.topics[0].title }}</h5>
<h5><nuxt-link :to="`/topic/${parseData?.topics[0].code}`">
{{ parseData?.topics[0].title }}</nuxt-link></h5>
</nuxt-link>
</template>
<h3 class="line-clamp" :class="LAYOUT_PARSE['title_Class']" :style="LAYOUT_PARSE['h3.title']">
<template v-if="parseData">
{{ parseData.title?.replace(/<[^>]+>/g, "") }}
<nuxt-link :to="`/bai-viet/${parseData.code}`">
{{ parseData.title?.replace(/<[^>]+>/g, "") }}
</nuxt-link>
</template>
<span v-else class="empty-block" style="height: 8px"></span>
</h3>
<div class="article-card-default__bottom" v-if="LAYOUT_PARSE.layout === 'row'">
<span :style="LAYOUT_PARSE['time']" style="margin-right: 5px" :class="LAYOUT_PARSE['time_Class']">{{
<span :style="LAYOUT_PARSE['time']" style="margin-right: 5px" :class="[LAYOUT_PARSE['time_Class'], 'article-time']">{{
formatDate(String(parseData?.createdOn), "DD/MM/YYYY | HH:mm")
}}</span>
<nuxt-link :style="LAYOUT_PARSE['category-article']" :class="LAYOUT_PARSE['category-article_Class']">{{ parseData?.category?.title }}</nuxt-link>
</div>
<p class="mb-0 line-clamp" :class="LAYOUT_PARSE['paragraph_Class']" :style="LAYOUT_PARSE['p.paragraph']">
<p class="mb-0 line-clamp-5 article-intro" :class="LAYOUT_PARSE['paragraph_Class']" :style="LAYOUT_PARSE['p.paragraph']">
<template v-if="parseData">
{{ parseData.intro?.replace(/<[^>]+>/g, "") }}
</template>
<span v-else class="empty-block" style="height: 5px"></span>
</p>
<div class="article-card-default__bottom" v-if="LAYOUT_PARSE?.layout !== 'row'" :style="LAYOUT_PARSE['metadata']">
<span :style="LAYOUT_PARSE['time']" style="margin-right: 5px" :class="LAYOUT_PARSE['time_Class']">{{
<span :style="LAYOUT_PARSE['time']" style="margin-right: 5px" :class="[LAYOUT_PARSE['time_Class'], 'article-time']">{{
formatDate(String(parseData?.createdOn), "DD/MM/YYYY | HH:mm")
}}</span>
<nuxt-link :style="LAYOUT_PARSE['category-article']" :class="LAYOUT_PARSE['category-article_Class']">{{ parseData?.category?.title }}</nuxt-link>
</div>
</div>
<div v-html="LAYOUT_PARSE.styleClasses"></div>
</article>
<div v-html="LAYOUT_PARSE.styleClasses" style="display:none;"></div>
</template>
<style lang="scss" scoped>
@@ -282,5 +241,16 @@ p {
height: 100px;
display: block;
}
@media (max-width: 640px) {
padding-left: 0px!important;
padding-right: 0px!important;
border: 0px solid transparent!important;
flex-direction: column !important;
& .basic-article_thumbnail {
width: 100% !important;
}
}
}
</style>
@@ -0,0 +1,114 @@
<script lang="ts" setup>
import { enumPageComponentTemplates } from "@/definitions/enum";
import { DEFAULT_QUERY_DROP } from "@/utils/parseSQL";
import { getInputValue } from "@/utils/parseSQL";
import { formatDate } from "@/utils/filters";
const props = defineProps<{
dataResult?: any;
dataType?: any;
dataQuery?: any;
layout?: string;
label?: string;
component?: any;
}>();
const LAYOUT_PARSE = computed(() => {
const designObject = props.label ? getInputValue(props.label, "OBJECT") : {};
return Object.assign({}, designObject);
});
const emit = defineEmits(["selectComponent", "dropData"]);
const selectComponent = () => {
emit("selectComponent");
};
const parseData = computed(() => {
if (!props.dataResult) return;
const result = getInputValue(props.dataResult, "OBJECT");
return result;
});
const drop = (e: any) => {
if (e.dataTransfer.getData(`${enumPageComponentTemplates.ARTICLE}`)) {
const data = e.dataTransfer.getData(`${enumPageComponentTemplates.ARTICLE}`);
const { dataType, dataResult } = JSON.parse(data);
const dataQuery = DEFAULT_QUERY_DROP(dataType, dataResult.id);
emit("dropData", {
dataType,
dataResult,
dataQuery: dataQuery,
});
}
};
</script>
<template>
<article :id="`cpn_${props.component.id}`" class="basic-article border-custom" :class="LAYOUT_PARSE['article_Class']" :style="LAYOUT_PARSE['article']">
<div class="article_miss">
<template v-if="parseData">
<nuxt-link :to="`/bai-viet/${parseData.slug}`">
<div class="article_miss_thumb custom-thumb" :style="{ backgroundImage: `url('${parseData.thumbnail ? parseData.thumbnail : '/images/default-thumbnail.jpg'}')` }"></div>
</nuxt-link>
<div class="article_miss_content" :style="LAYOUT_PARSE['content']">
<nuxt-link :to="`/bai-viet/${parseData.slug}`">
<h3 class="line-clamp text-white" :class="LAYOUT_PARSE['title_Class']" :style="LAYOUT_PARSE['h3.title']">
{{ parseData.title?.replace(/<[^>]+>/g, "") }}
</h3>
</nuxt-link>
</div>
</template>
<div v-else class="empty-box"></div>
</div>
<div v-html="LAYOUT_PARSE.styleClasses" v-if="LAYOUT_PARSE.styles" style="display: none"></div>
</article>
</template>
<style lang="scss" scoped>
.article_miss {
height: 100%;
position: relative;
.article_miss_thumb {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
position: relative;
border-radius: 12px;
cursor: pointer;
height: 100%;
}
.article_miss_content {
position: absolute;
z-index: 2;
bottom: -30px;
background-color: rgba(255, 93, 2, 0.7);
backdrop-filter: blur(2px);
width: 80%;
left: 10%;
padding: 16px 10px;
border-radius: 8px;
h3 {
font-size: 16px;
font-weight: 700;
line-height: 130%;
text-align: center;
// margin-bottom: 12px;
margin-bottom: 0;
}
}
.empty-box {
background-color: #409eff;
min-height: 60px;
height: 100%;
i {
font-size: 60px;
}
}
}
</style>
@@ -0,0 +1,166 @@
<script lang="ts" setup>
import { enumPageComponentTemplates } from "@/definitions/enum";
import { DEFAULT_QUERY_DROP, getInputValue } from "@/utils/parseSQL";
const props = defineProps<{
dataResult?: any;
dataType?: any;
dataQuery?: any;
layout?: string;
label?: any;
component?: any;
}>();
const LAYOUT_PARSE = computed(() => {
const designObject = props.label ? getInputValue(props.label, "OBJECT") : {};
return Object.assign({}, designObject);
});
const parseData = computed(() => {
if (!props.dataResult) return;
const result = getInputValue(props.dataResult, "OBJECT");
return result;
});
</script>
<template>
<article
:id="`cpn_${props.component.id}`"
class="basic-article border-custom"
:class="LAYOUT_PARSE['article_Class']"
:style="LAYOUT_PARSE['article']"
>
<!-- <div class="basic-article_thumbnail" :class="LAYOUT_PARSE['thumbnail_Class']" :style="LAYOUT_PARSE['div.basic-article_thumbnail']">
<template v-if="parseData">
<img class="object-fit-cover" :src="parseData.thumbnail ? parseData.thumbnail : '/images/default-thumbnail.jpg'" :alt="parseData.title?.replace(/<[^>]+>/g, '')" />
</template>
<span v-else class="empty-block" style="width: 100%; height: 100%; min-height: 50px"></span>
</div>
<div class="basic-article_content" :class="[!parseData && 'no-data']">
<template v-if="parseData?.topics && parseData?.topics.length > 0">
<nuxt-link class="article-card-default__topic" :to="`/${parseData?.topics[0].code}`" :style="LAYOUT_PARSE['topic']">
<h5>{{ parseData?.topics[0].title }}</h5>
</nuxt-link>
</template>
<h3 class="line-clamp" :class="LAYOUT_PARSE['title_Class']" :style="LAYOUT_PARSE['h3.title']">
<template v-if="parseData">
{{ parseData.title?.replace(/<[^>]+>/g, "") }}
</template>
<span v-else class="empty-block" style="height: 8px"></span>
</h3>
<div class="article-card-default__bottom" v-if="LAYOUT_PARSE.layout === 'row'">
<span :style="LAYOUT_PARSE['time']" style="margin-right: 5px" :class="LAYOUT_PARSE['time_Class']">{{
formatDate(String(parseData?.createdOn), "DD/MM/YYYY | HH:mm")
}}</span>
<nuxt-link :style="LAYOUT_PARSE['category-article']" :class="LAYOUT_PARSE['category-article_Class']">{{ parseData?.category?.title }}</nuxt-link>
</div>
<p class="mb-0 line-clamp" :class="LAYOUT_PARSE['paragraph_Class']" :style="LAYOUT_PARSE['p.paragraph']">
<template v-if="parseData">
{{ parseData.intro?.replace(/<[^>]+>/g, "") }}
</template>
<span v-else class="empty-block" style="height: 5px"></span>
</p>
<div class="article-card-default__bottom" v-if="LAYOUT_PARSE?.layout !== 'row'" :style="LAYOUT_PARSE['metadata']">
<span :style="LAYOUT_PARSE['time']" style="margin-right: 5px" :class="LAYOUT_PARSE['time_Class']">{{
formatDate(String(parseData?.createdOn), "DD/MM/YYYY | HH:mm")
}}</span>
<nuxt-link :style="LAYOUT_PARSE['category-article']" :class="LAYOUT_PARSE['category-article_Class']">{{ parseData?.category?.title }}</nuxt-link>
</div>
</div>
-->
<div class="article_video">
<template v-if="parseData">
<div class="article_video_container">
<div
class="article_video_thumb"
:style="{ backgroundImage: `url('${parseData.thumbnail ? parseData.thumbnail : '/images/default-thumbnail.jpg'}')` }"
>
<Icon name="ri:play-circle-line" />
</div>
<div class="article_video_content">
<div>
<h3 class="line-clamp" :class="LAYOUT_PARSE['title_Class']" :style="LAYOUT_PARSE['h3.title']">
{{ parseData.title?.replace(/<[^>]+>/g, "") }}
</h3>
</div>
</div>
</div>
</template>
</div>
<div v-if="LAYOUT_PARSE.styleClasses" v-html="LAYOUT_PARSE.styleClasses"></div>
</article>
</template>
<style lang="scss" scoped>
.article_video {
.article_video_container {
display: flex;
align-items: center;
.article_video_thumb {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
position: relative;
z-index: 0;
width: 250px;
height: 140px;
border-radius: 2px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
&::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
z-index: 1;
top: 0px;
left: 0px;
}
svg {
font-size: 40px;
color: white;
position: relative;
z-index: 2;
}
}
.article_video_content {
position: relative;
z-index: 2;
padding: 10px;
> div {
background: #ffffff;
}
h3 {
font-size: 16px;
font-weight: 500;
line-height: 24px;
text-align: left;
margin-bottom: 12px;
}
}
}
}
.empty-box {
margin: 10px;
display: flex;
align-items: center;
gap: 10px;
.empty-block {
width: 50%;
> div {
background-color: #409eff;
margin-bottom: 5px;
}
}
i {
font-size: 60px;
}
}
</style>
@@ -0,0 +1,138 @@
<script lang="ts" setup>
import { enumPageComponentTemplates } from "@/definitions/enum";
import { DEFAULT_QUERY_DROP, getInputValue } from "@/utils/parseSQL";
import { getResource } from "@/utils/resourceHandler";
const props = defineProps<{
dataResult?: any;
dataType?: any;
dataQuery?: any;
layout?: string;
label?: string;
component?: any;
}>();
const LAYOUT_PARSE = computed(() => {
const designObject = props.label ? getInputValue(props.label, "OBJECT") : {};
return Object.assign({}, designObject);
});
const emit = defineEmits(["selectComponent", "dropData"]);
const selectComponent = () => {
emit("selectComponent");
};
const parseData = computed(() => {
if (!props.dataResult) return;
const result = getInputValue(props.dataResult, "OBJECT");
return result;
});
const drop = (e: any) => {
if (e.dataTransfer.getData(`${enumPageComponentTemplates.ARTICLE}`)) {
const data = e.dataTransfer.getData(`${enumPageComponentTemplates.ARTICLE}`);
const { dataType, dataResult } = JSON.parse(data);
const dataQuery = DEFAULT_QUERY_DROP(dataType, dataResult.id);
emit("dropData", {
dataType,
dataResult,
dataQuery: dataQuery,
});
}
};
</script>
<template>
<article :id="`cpn_${props.component?.id}`" class="basic-article border-custom" :class="LAYOUT_PARSE['article_Class']" @click="selectComponent" @dragover.prevent @drop.stop.prevent="drop" :style="LAYOUT_PARSE['article']">
<div class="article_video">
<template v-if="parseData">
<div class="article_video_thumb article-thumbnail" :style="{ backgroundImage: `url('${parseData.thumbnail ? parseData.thumbnail : '/images/default-thumbnail.jpg'}')` }">
<div></div>
<div class="article_video_content">
<span>
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M23.5 18.5C24.375 17.875 25.625 17.875 26.5 18.5L44.5 29.5C45.375 30 46 31 46 32C46 33.125 45.375 34.125 44.5 34.625L26.5 45.625C25.625 46.125 24.375 46.25 23.5 45.625C22.5 45.125 22 44.125 22 43V21C22 20 22.5 19 23.5 18.5ZM64 32C64 49.75 49.625 64 32 64C14.25 64 0 49.75 0 32C0 14.375 14.25 0 32 0C49.625 0 64 14.375 64 32ZM32 6C17.625 6 6 17.75 6 32C6 46.375 17.625 58 32 58C46.25 58 58 46.375 58 32C58 17.75 46.25 6 32 6Z"
fill="#ED1C24"
/>
</svg>
</span>
<h3 class="line-clamp article-title" :class="LAYOUT_PARSE['title_Class']" :style="LAYOUT_PARSE['h3.title']">
{{ parseData.title?.replace(/<[^>]+>/g, "") }}
</h3>
<p class="mb-0 line-clamp article-intro" :class="LAYOUT_PARSE['paragraph_Class']" :style="LAYOUT_PARSE['p.paragraph']">
{{ parseData.intro?.replace(/<[^>]+>/g, "") }}
</p>
</div>
</div>
</template>
<div v-else class="empty-box">
<div class="d-flex justify-content-center align-items-center flex-column">
<i class="ri-play-circle-line"></i>
</div>
</div>
</div>
<div v-html="LAYOUT_PARSE.styleClasses" v-if="LAYOUT_PARSE.styles"></div>
</article>
</template>
<style lang="scss" scoped>
.article_video {
.article_video_thumb {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
position: relative;
z-index: 0;
padding: 120px 85px 60px 85px;
border-radius: 2px;
margin: 10px;
cursor: pointer;
&::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
z-index: 1;
top: 0px;
left: 0px;
}
.article_video_content {
position: relative;
z-index: 2;
h3 {
font-size: 44px;
font-weight: 700;
line-height: 57.2px;
text-align: left;
margin-bottom: 12px;
color: white;
}
p {
font-size: 14px;
font-weight: 400;
color: white;
}
i {
font-size: 80px;
color: #ed1c24;
}
}
}
.empty-box {
background-color: #409eff;
margin: 10px;
min-height: 60px;
i {
font-size: 60px;
}
}
}
</style>
@@ -1,2 +1,5 @@
export { default as Article_Card_Default } from './Card.vue'
export { default as Article_Card_Audio } from './Audio.vue'
export { default as Article_Card_Audio } from './Audio.vue'
export { default as Article_Card_Video } from './Video.vue'
export { default as Article_Card_Video_Hightlight } from './VideoBackground.vue'
export { default as Article_Card_Miss_Hightlight } from './MissBackground.vue'
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
import { Article_Card_Default, Article_Card_Audio } from "./index";
import { Article_Card_Default, Article_Card_Audio, Article_Card_Video, Article_Card_Video_Hightlight,Article_Card_Miss_Hightlight } from "./index";
const _props = defineProps<{
settings: any;
@@ -11,10 +11,12 @@ const _props = defineProps<{
const definedDynamicComponent: Record<string, any> = {
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_CARD"]]["CARD_DEFAULT"]]: Article_Card_Default,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_CARD"]]["CARD_AUDIO"]]: Article_Card_Audio,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_CARD"]]["CARD_VIDEO"]]: Article_Card_Video,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_CARD"]]["CARD_VIDEO_HIGHLIGHT"]]: Article_Card_Video_Hightlight,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_CARD"]]["CARD_MISS_HIGHLIGHT"]]: Article_Card_Miss_Hightlight,
};
const getCurrentComponent = computed(() => {
console.log(_props.settings.layout, enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_CARD"]]["CARD_DEFAULT"]);
return _props.settings.layout;
});
@@ -35,5 +37,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
</template>
@@ -0,0 +1,95 @@
<script setup lang="ts">
const emit = defineEmits(["dropData", "selectComponent"]);
const _props = defineProps<{
dataResult?: any;
dataType?: any;
dataQuery?: any;
layout?: string;
label?: string;
}>();
const { currentArticle } = storeToRefs(useArticleStore())
console.log(currentArticle.value, 'currentArticle')
</script>
<template>
<div class="overflow-hidden emagazine">
<h2 class="font-gelasio text-center text-44px font-bold leading-130%" v-if="currentArticle?.title" v-html="currentArticle?.title"></h2>
<div class="article-detail" v-html="currentArticle.detail"></div>
</div>
</template>
<style scoped lang="scss">
.breadcrumb {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
&__list {
margin: 0;
padding: 0px;
display: flex;
overflow-x: auto;
gap: 1.5rem;
align-items: center;
font-size: 0.875rem;
line-height: 1.25rem;
&__item {
display: inline-block;
position: relative;
&__title {
margin: 0;
font-size: 18px;
color: #000;
font-weight: 500;
text-transform: uppercase;
line-height: 180%;
}
// &:first-child {
// color: blue;
// }
&:not(:first-child):before {
content: "\\";
position: absolute;
left: -18px;
}
}
}
.article-card-default__topic {
position: relative;
// background-color: #151411;
display: inline-block;
h5 {
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
// color: #fff;
padding: 0 12px;
height: 100%;
margin: 0;
border: 1px solid #000;
line-height: 180%;
font-weight: 300;
}
&::after {
position: absolute;
content: "";
display: block;
width: 12px;
height: 100%;
background-color: #ed1c24;
left: -12px;
top: 0;
}
}
}
.content {
width: auto;
}
</style>
@@ -1,9 +1,7 @@
<script setup lang="ts">
import { enumPageComponentTemplates } from "@/definitions/enum";
import { DEFAULT_QUERY_DROP } from "@/utils/parseSQL";
import { isEmpty } from "lodash";
import { DEFAULT_QUERY_DROP, getInputValue } from "@/utils/parseSQL";
const emit = defineEmits(["dropData", "selectComponent"]);
import { getInputValue } from "@/utils/parseSQL";
const _props = defineProps<{
dataResult?: any;
@@ -41,9 +39,6 @@ const drop = (e: any) => {
});
}
};
const articleStore = useArticleStore();
console.log(articleStore.currentArticleGeneral, "cas");
const currentArticle = computed(() => articleStore.currentArticleGeneral);
</script>
<template>
<div @click="selectComponent" class="overflow-hidden" @dragover.prevent @drop.stop.prevent="drop">
@@ -53,9 +48,8 @@ const currentArticle = computed(() => articleStore.currentArticleGeneral);
class="breadcrumb__list__item"
v-for="(item, index) in _props.dataResult && _props.dataResult?.length > 0 ? _props.dataResult : Array(SETTING_OPTIONS.BREADCRUMB_MAX_ELEMENT).fill(null)"
:key="index"
:class="isEmpty(item) && 'empty'"
>
<p v-if="!isEmpty(item)" class="breadcrumb__list__item__title">
<p class="breadcrumb__list__item__title">
{{ item?.title }}
</p>
</li>
@@ -1,56 +1,76 @@
<script setup lang="ts">
import { isEmpty } from "lodash";
const emit = defineEmits(["dropData", "selectComponent"]);
const { categoryTree } = storeToRefs(useCategoryStore());
const { currentArticle } = storeToRefs(useArticleStore());
if (categoryTree.value) {
await useCategoryStore().fetchBySiteId();
}
const _props = defineProps<{
dataResult?: any[];
}>();
const SETTING_OPTIONS = {
BREADCRUMB_MAX_ELEMENT: 3,
};
const currentCategoryTree = findElementPathById(categoryTree.value, currentArticle.value.categoryId);
function findElementPathById(categories: any[], targetId: number, path: any[] = []) {
for (const category of categories) {
const currentPath = [...path, { title: category.title, code: category.code }];
if (category.id === targetId) {
return currentPath;
}
if (category.children) {
const result: any = findElementPathById(category.children, targetId, currentPath);
if (result) {
return result;
}
}
}
return null;
}
console.log(currentArticle.value, "currentArticle");
</script>
<template>
<div class="overflow-hidden">
<div class="breadcrumb">
<ul class="breadcrumb__list">
<li
class="breadcrumb__list__item"
v-for="(item, index) in _props.dataResult && _props.dataResult?.length > 0 ? _props.dataResult : Array(SETTING_OPTIONS.BREADCRUMB_MAX_ELEMENT).fill(null)"
:key="index"
:class="isEmpty(item) && 'empty'"
>
<p v-if="!isEmpty(item)" class="breadcrumb__list__item__title">
{{ item?.title }}
</p>
</li>
</ul>
<div class="overflow-hidden w-full max-w-1385px mx-auto px-30px image">
<div class="">
<div class="category flex justify-between flex-wrap items-center mb-10px">
<ul class="flex gap-32px">
<li v-for="(category, index) in currentCategoryTree" :key="index" class="first:text-#000 text-#929292 last:after:content-[''] relative after:absolute after:content-['/'] after:text-20px after:right--20px">
<nuxt-link class="font-raleway text-18px font-500 leading-180% uppercase" :to="`/${category.code}`">{{ category.title }}</nuxt-link>
</li>
</ul>
<p class="breakcrumb__time">Ngày tạo image</p>
</div>
<div class="content">Nội dung bài viết sẽ đây</div>
<div class="btn-wrap w-100 max-w">
<div class="center-y">
<p title="Quay trở lại" class="button--back">
<Icon name="fa6-solid:arrow-left" />
</p>
<button class="button--bookmark">
<Icon name="fa6-regular:bookmark" />
</button>
<div v-if="currentArticle.topics" class="pl-20px relative bg-primary inline-block">
<nuxt-link class="h-30px block py-4px px-16px border-1 border-#000 bg-white text-12px leading-180% font-raleway font-400" :to="`/topic/${currentArticle.topics[0].code}`">{{ currentArticle.topics[0].title }}</nuxt-link>
</div>
</div>
<h2 class="font-gelasio text-44px font-bold leading-130%" v-if="currentArticle.title" v-html="currentArticle.title"></h2>
<!-- <div class="grid grid-cols-12 gap-20px">
<div class="col-span-3"></div>
</div> -->
<div class="center-y">
<button title="Copy link" class="button--back copy-link">
<Icon name="mdi:link-variant" />
</button>
<div class="author flex gap-12px my-20px" v-if="currentArticle.authors">
<ul class="flex">
<li :style="{ 'z-index': index + 1 }" class="relative ml--12px first:ml-0" v-for="(author, index) in currentArticle.authors" :key="index">
<nuxt-link :to="`/tac-gia/${author.code}`">
<img :src="author.thumbnail || `http://picsum.photos/1024/600?random=1`" alt="" class="w-64px p-1px border-1px border-white h-64px object-cover rounded-full" />
</nuxt-link>
</li>
</ul>
<div>
<div class="mt-10px">
<nuxt-link class="font-raleway text-#000" v-for="(author, index) in currentArticle.authors" :key="index" :to="`/tac-gia/${author.code}`">{{ author.title + (index < currentArticle.authors.length - 1 ? ", " : "") }}</nuxt-link>
</div>
<div class="text-12px">Xuất bản vào {{ formatDate(currentArticle.publishedOn, "DD/MM/YYYY | hh:mm") }}</div>
</div>
</div>
<div v-html="currentArticle.detail"></div>
</div>
</div>
</template>
<style scoped lang="scss">
$max-width: 680px;
$max-width: 1276px;
.breadcrumb {
display: flex;
@@ -0,0 +1,101 @@
<script setup lang="ts">
const emit = defineEmits(["dropData", "selectComponent"]);
const _props = defineProps<{
dataResult?: any;
dataType?: any;
dataQuery?: any;
layout?: string;
label?: string;
}>();
const { currentArticle } = storeToRefs(useArticleStore())
console.log(currentArticle.value, 'currentArticle')
</script>
<template>
<div class="overflow-hidden infographic">
<!-- bổ sung sau -->
<!-- <img :src="currentArticle.thumbnail" alt="" class="w-full object-cover">
<div class="px-44px pb-30px my-30px max-w-660px mx-auto border-b-1px border-#000">
</div> -->
<h2 class="font-gelasio text-center text-44px font-bold leading-130%" v-if="currentArticle?.title" v-html="currentArticle?.title"></h2>
<div v-html="currentArticle.detail"></div>
</div>
</template>
<style scoped lang="scss">
.breadcrumb {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
&__list {
margin: 0;
padding: 0px;
display: flex;
overflow-x: auto;
gap: 1.5rem;
align-items: center;
font-size: 0.875rem;
line-height: 1.25rem;
&__item {
display: inline-block;
position: relative;
&__title {
margin: 0;
font-size: 18px;
color: #000;
font-weight: 500;
text-transform: uppercase;
line-height: 180%;
}
// &:first-child {
// color: blue;
// }
&:not(:first-child):before {
content: "\\";
position: absolute;
left: -18px;
}
}
}
.article-card-default__topic {
position: relative;
// background-color: #151411;
display: inline-block;
h5 {
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
// color: #fff;
padding: 0 12px;
height: 100%;
margin: 0;
border: 1px solid #000;
line-height: 180%;
font-weight: 300;
}
&::after {
position: absolute;
content: "";
display: block;
width: 12px;
height: 100%;
background-color: #ed1c24;
left: -12px;
top: 0;
}
}
}
.center-y {
width: auto;
}
</style>
@@ -1,93 +1,254 @@
<script setup lang="ts">
import { useArticleStore } from "~/stores/articles";
import Poll from "~/components/article/immerse/Poll.vue";
import Quiz from "~/components/article/immerse/Quiz.vue";
import Survey from "~/components/article/immerse/Survey.vue";
import Document from "~/components/article/immerse/Document.vue";
import Attachment from "@/components/article/immerse/Attachment.vue";
import Tag from "@/components/article/immerse/Tag.vue";
const { currentArticle } = storeToRefs(useArticleStore());
import { useDynamicPageStore } from "~/stores/dynamic-page";
import { useCategoryStore } from "~/stores/category";
const store = reactive({
dynamicPage: useDynamicPageStore(),
article: useArticleStore(),
category: useCategoryStore(),
});
const { categoryTree } = storeToRefs(store.category);
await store.category.fetchBySiteId();
const currentCategoryTree = (store.category.currentCategoryTree = findElementPathById(categoryTree.value, currentArticle.value.categoryId));
function findElementPathById(categories: any[], targetId: number, path: any[] = []) {
for (const category of categories) {
const currentPath = [...path, { title: category.title, code: category.code }];
if (category.id === targetId) {
return currentPath;
}
if (category.children) {
const result: any = findElementPathById(category.children, targetId, currentPath);
if (result) {
return result;
}
}
}
return null;
}
onMounted(async () => {
clickElement("figure", "custom-figure", "data-code");
clickElement("author", "author", "data-code");
let detailEmagazine = document.querySelector('div[layout="ARTICLE_DETAIL_EMAGAZINE"]');
let breadcrumb = document.querySelector('div[layout="BREADCRUM_DEFAULT"]');
if (detailEmagazine && breadcrumb) {
breadcrumb.classList.add("lg:max-w-640px", "mx-auto");
}
});
function clickElement(type: string, selector: string, attribute: string) {
const elements = document.querySelectorAll(selector);
elements.forEach((element) => {
element.addEventListener("click", (event) => {
event.preventDefault();
const url = `${window.location.protocol}//${window.location.host}/${type}/${element.getAttribute(attribute)}`;
const a = document.createElement("a");
a.href = url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
});
}
const isBookmark = ref(false);
const onClickBookmark = () => {
isBookmark.value = !isBookmark.value;
};
async function copyLink() {
try {
const url = window.location.href;
await navigator.clipboard.writeText(url);
alert("copy link thành công");
} catch (error) {
alert(error);
}
}
const getSrc = (htmlString: string) => {
const srcRegex = /src="([^"]+)"/;
return htmlString?.match(srcRegex);
};
const isMoreControl = ref(false);
const isPlayed = ref(true);
const isVolume = ref(true);
const speedList = ref<{ [key: number]: string }>({
1: "0.5x",
2: "0.75x",
3: "1.0x",
4: "1.25x",
5: "1.50x",
});
const speedIndexDefault = ref(3);
const speedDefault = ref(speedList.value[speedIndexDefault.value]);
const volume = ref(1.0);
const audioPlayer = ref<HTMLAudioElement | null>(null);
const currentTime = ref(0);
const duration = ref(0);
function setUpVolums() {
isVolume.value = !isVolume.value;
if (audioPlayer.value) {
if (isVolume.value) {
audioPlayer.value.volume = 1;
} else {
audioPlayer.value.volume = 0;
}
}
}
const updateVolume = (num?: number) => {
if (audioPlayer.value) {
if(num) {
volume.value += num
}
// console.log('1231321')
audioPlayer.value.volume = volume.value;
}
};
function chanageSpeed() {
if (speedIndexDefault.value < 5) {
speedIndexDefault.value += 1;
if (audioPlayer.value) {
audioPlayer.value.playbackRate += 0.25;
}
speedDefault.value = speedList.value[speedIndexDefault.value];
} else {
if (audioPlayer.value) {
audioPlayer.value.playbackRate = 0.5;
}
speedIndexDefault.value = 1;
speedDefault.value = speedList.value[1];
}
}
function togglePlayer() {
isPlayed.value = !isPlayed.value;
if (audioPlayer.value) {
if (isPlayed.value) {
audioPlayer.value.pause();
} else {
audioPlayer.value.play();
}
}
}
function replayAndForward(time: number) {
if (audioPlayer.value) {
if (audioPlayer.value.currentTime == audioPlayer.value.duration) {
isPlayed.value = true;
} else {
audioPlayer.value.currentTime = audioPlayer.value.currentTime + time;
}
}
}
const seekToTime = () => {
if (audioPlayer.value) {
audioPlayer.value.currentTime = currentTime.value;
}
};
const updateCurrentTime = () => {
if (audioPlayer.value) {
currentTime.value = audioPlayer.value.currentTime;
}
};
const updateDuration = () => {
if (audioPlayer.value) {
duration.value = audioPlayer.value.duration;
}
};
const currrentTimeComputed = computed(() => {
return utils.formattedTime(currentTime.value);
});
const durationComputed = computed(() => {
return utils.formattedTime(duration.value);
});
</script>
<template>
<div
class="podcast__wrapper overflow-hidden"
>
<div
class="podcast"
>
<p
class="podcast__content__time"
>
Ngày tạo podcast
<div class="lg:p-40px md:p-30px p-5 border-1px border-solid border-black/10 rounded-8px">
<div class="flex md:flex-row flex-col md:gap-6 gap-2 justify-between mb-10px">
<p class="text-#9f9f9f text-14px mb-2 md:hidden block text-center">
{{ utils.dateFormat(currentArticle?.publishedOn, "dddd, DD/MM/YYYY - HH:mm") }}
</p>
<figure><img src="http://picsum.photos/1024/600?random=1'" alt="Ảnh podcast" title="Ảnh podcast" /></figure>
<div
class="podcast__content"
>
<p
class="podcast__content__time"
>
Ngày tạo podcast
</p>
<h1 class="podcast__content__title">Tiêu đề podcast</h1>
<p
class="podcast__content__text"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
<figure class="!w-auto"><img class="w-150px h-150px rounded-8px shadow-md cursor-pointer" :src="currentArticle?.thumbnail" alt="Ảnh podcast" title="Ảnh podcast" /></figure>
<div class="flex-1 text-#222 m-0 md:text-left text-center">
<p class="text-#9f9f9f text-14px mb-2 md:block hidden">
{{ utils.dateFormat(currentArticle?.publishedOn, "dddd, DD/MM/YYYY - HH:mm") }}
</p>
<h1 class="text-24px md:mb-4 mb-2 font-bold" v-html="currentArticle?.title"></h1>
<p class="hidden md:line-clamp-3" v-html="currentArticle?.intro"></p>
</div>
<ul
class="buttons"
>
<li><Icon name="mdi:bookmark-outline" /></li>
<li><Icon name="material-symbols:mode-comment-outline" /></li>
<ul class="items-start gap-2 m-0 p-0 md:flex hidden">
<li class="w-9 h-9 bg-white border-1 border-solid border-[rgb(229, 231, 235)] cursor-pointer shadow-md rounded-50px relative hover:bg-primary-100 hover:text-primary-600">
<Icon class="text-18px absolute top-50% left-50% translate-x--50% translate-y--50%" name="mdi:bookmark-outline" />
</li>
<li class="w-9 h-9 bg-white border-1 border-solid border-[rgb(229, 231, 235)] cursor-pointer shadow-md rounded-50px relative hover:bg-primary-100 hover:text-primary-600">
<Icon class="text-18px absolute top-50% left-50% translate-x--50% translate-y--50%" name="material-symbols:mode-comment-outline" />
</li>
</ul>
</div>
<div class="playlist">
<div class="playlist__time">
<span>5:00</span>
<span>10:00</span>
<audio :src="getSrc(currentArticle?.detail)?.[1]" preload="auto" ref="audioPlayer" @timeupdate="updateCurrentTime" @loadedmetadata="updateDuration" />
<div class="p-2">
<input class="w-full accent-primary-600 cursor-pointer" type="range" v-model="currentTime" @input="seekToTime" :max="duration" />
<div class="flex justify-between">
<span>{{ currrentTimeComputed }}</span>
<span>{{ durationComputed }}</span>
</div>
<div class="playlist__buttons">
<div class="playlist__buttons__left">
<div
class="button__prev"
>
<div class="flex justify-between items-center">
<div class="md:w-150px text-left">
<div class="text-28px text-primary-600 md:hidden block">
<Icon name="material-symbols:skip-previous" />
</div>
<div
class="sound"
>
<Icon name="material-symbols:volume-mute"></Icon>
<div></div>
<Icon name="material-symbols:volume-up"></Icon>
<div class="md:inline-flex hidden items-center gap-2 ml--10px h9 text-primary-600 rounded-8px text-28px cursor-pointer hover:bg-primary-100">
<Icon @click="updateVolume(-0.1)" name="material-symbols:volume-mute"></Icon>
<input v-if="isVolume" class="accent-primary-600 h-1 w-12 lg:w-20 cursor-pointer" type="range" v-model="volume" @input="updateVolume()" min="0.1" max="1" step="0.1" />
<Icon @click="updateVolume(0.1)" name="material-symbols:volume-up"></Icon>
</div>
</div>
<div class="play">
<Icon name="fluent:skip-back-10-48-filled" />
<Icon name="material-symbols:play-arrow" class="button" />
<Icon name="fluent:skip-forward-10-48-filled" />
<div class="flex items-center justify-center gap-4 flex-1 text-28px text-primary-600">
<Icon @click="replayAndForward(-10)" name="fluent:skip-back-10-48-filled" />
<button @click="togglePlayer" class="bg-transparent">
<Icon v-if="isPlayed" name="material-symbols:play-arrow-rounded" class="text-64px" />
<Icon v-if="!isPlayed" name="material-symbols:pause" class="text-64px" />
</button>
<Icon @click="replayAndForward(10)" name="fluent:skip-forward-10-48-filled" />
</div>
<div class="playlist__buttons__right">
<div
class="button__next"
>
<div class="md:w-150px text-right">
<div class="text-28px text-primary-600 md:hidden block">
<Icon name="material-symbols:skip-next" />
</div>
<div
class="speed"
>
<span>Tốc độ phát: </span>
<strong>1x</strong>
<div class="text-14px text-primary-600 md:block hidden cursor-pointer" @click="chanageSpeed">
<span class="font-300">Tốc độ phát: </span>
<strong class="font-bold text-20px ml-1">{{ speedDefault }}</strong>
</div>
</div>
</div>
</div>
<p
class="podcast__content__text"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p class="md:hidden block" v-html="currentArticle?.intro"></p>
</div>
</template>
<style lang="scss">
@@ -1,42 +1,93 @@
<script setup lang="ts">
import Comment from "@/components/dynamic-page/page-component/templates/others/comments/Default.vue";
const { categoryTree } = storeToRefs(useCategoryStore());
const { currentArticle } = storeToRefs(useArticleStore());
if (categoryTree.value) {
await useCategoryStore().fetchBySiteId();
}
const currentCategoryTree = findElementPathById(categoryTree.value, currentArticle.value.categoryId);
function findElementPathById(categories: any[], targetId: number, path: any[] = []) {
for (const category of categories) {
const currentPath = [...path, { title: category.title, code: category.code }];
if (category.id === targetId) {
return currentPath;
}
if (category.children) {
const result: any = findElementPathById(category.children, targetId, currentPath);
if (result) {
return result;
}
}
}
return null;
}
</script>
<template>
<div class="container overflow-hidden">
<div class="video row">
<div
class="video__left"
>
<video controls="controls" width="100%" height="100%" data-file-id="149" data-resource="https://acp-api.vpress.vn/Resources/Video/983d2f57-7743-472f-b22d-fc73085af6d5.mp4" data-title="Download.mp4">
<source src="" type="video/mp4" />
</video>
</div>
<div
class="video__right bg-body-tertiary"
>
<h1
class=""
>
Tiêu đề video
</h1>
<p class="line-clamp-3 fs-5 fw-light">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<h5 class="text-end fs-4 opacity-75">Tác giả</h5>
<p><b class="text-primary fw-semibold">Danh mục</b> <span class="ms-2 opacity-25 fw-semibold">Ngày đăng video</span></p>
<Comment />
</div>
</div>
<div class="video-container">
<ul class="flex gap-32px lg:ml-80px md:ml-40px sm:ml-20px">
<li v-for="(category, index) in currentCategoryTree" :key="index" class="first:text-#000 text-#929292 last:after:content-[''] relative after:absolute after:content-['/'] after:text-20px after:right--20px">
<nuxt-link class="font-raleway text-18px font-500 leading-180% uppercase" :to="`/${category.code}`">{{ category.title }}</nuxt-link>
</li>
</ul>
<h2 class="font-gelasio text-center text-44px font-bold leading-130%" v-if="currentArticle?.title" v-html="currentArticle?.title"></h2>
<div class="video-content" v-html="currentArticle.detail"></div>
</div>
</template>
<style scoped lang="scss">
.line-clamp-3 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
.video-container {
width: 100%;
max-width: 1080px;
margin: auto;
.category-list {
display: flex;
gap: 32px;
margin-bottom: 26px;
list-style: none;
padding: 0;
margin: 0 0 0 80px;
.category-item {
color: #929292;
position: relative;
font-size: 18px;
& > span {
font-size: 18px;
font-weight: 500;
line-height: 180%;
text-transform: uppercase;
}
&::after {
position: absolute;
content: "/";
font-size: 20px;
right: -20px;
}
&:first-child {
color: #000;
}
&:last-child {
&::after {
content: "";
}
}
}
}
.video-content {
width: 100%;
// max-width: 1080px;
margin: 26px 0 26px 0px;
// background-color: #eee;
// height: 500px;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
@@ -3,4 +3,6 @@
export { default as Article_Detail_General } from './General.vue'
export { default as Article_Detail_Podcast } from './Podcast.vue'
export { default as Article_Detail_Video } from './Video.vue'
export { default as Article_Detail_Image } from './Image.vue'
export { default as Article_Detail_Image } from './Image.vue'
export { default as Article_Detail_Emagazine } from './Emagazine.vue'
export { default as Article_Detail_Infographic } from './Infographic.vue'
@@ -2,7 +2,7 @@
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
// import { Article_Card, Article_Detail_Video, Article_Detail_Podcast, Article_Detail_General, Article_Detail_Image } from "./index";
import { Article_Detail_General, Article_Detail_Podcast, Article_Detail_Video, Article_Detail_Image } from "./index";
import { Article_Detail_General, Article_Detail_Podcast, Article_Detail_Video, Article_Detail_Image, Article_Detail_Emagazine, Article_Detail_Infographic } from "./index";
const _props = defineProps<{
settings: any;
component?: any;
@@ -13,6 +13,8 @@ const definedDynamicComponent: Record<string, any> = {
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]["DETAIL_PODCAST"]]: Article_Detail_Podcast,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]["DETAIL_VIDEO"]]: Article_Detail_Video,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]["DETAIL_IMAGE"]]: Article_Detail_Image,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]["DETAIL_EMAGAZINE"]]: Article_Detail_Emagazine,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]["DETAIL_INFOGRAPHIC"]]: Article_Detail_Infographic,
};
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
@@ -33,5 +35,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
</template>
@@ -32,5 +32,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
</template>
@@ -1,13 +1,30 @@
<script setup lang="ts">
import { isEmpty } from "lodash";
import { isEmpty } from "@/utils/lodash";
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
const _props = defineProps<{
dataResult?: any[];
dataResult?: any;
dataQuery?: string;
label?: string;
label?: any;
component?: any;
}>();
const SETTING_OPTIONS = {
MAX_ELEMENT: 3,
};
const _dataResult = computed(() => {
const designObject = _props.label ? getInputValue(_props.label, "OBJECT") : {};
let _components = Array(Number(designObject.MAX) || SETTING_OPTIONS.MAX_ELEMENT).fill(null);
const result = getInputValue(_props.dataResult, "ARRAY");
result &&
result.length > 0 &&
_components.map((_: any, index: any) => {
_components[index] = result[index] || null;
});
return Object.assign({}, _components);
});
const designObject = computed(() => {
return _props.label ? getInputValue(_props.label, "OBJECT") : {};
});
@@ -21,12 +38,12 @@ const mapActivesToItems = (index: number) => {
</script>
<template>
<div class="categories-container border-custom" :class="designObject['categories_Class']" @click="selectComponent" :style="designObject['div.categories-container']">
<div :id="`cpn_${_props.component.id}`" class="categories-container border-custom" :class="designObject['categories_Class']" :style="designObject['div.categories-container']">
<div v-for="(component, index) in _dataResult" :key="index" :class="['border-custom', isEmpty(component) ? 'empty' : 'category', designObject['category_Class']]" :style="mapActivesToItems(index)['category']">
<template v-if="!isEmpty(component)">
<div>
<h3 :style="mapActivesToItems(index)['h3.categories']">
{{ component.title }}
<nuxt-link :to="`/${component.code}`">{{ component.title }}</nuxt-link>
</h3>
</div>
<div v-html="designObject.styleClasses"></div>
@@ -1,11 +1,12 @@
<script setup lang="ts">
import { isEmpty } from "lodash";
import { isEmpty } from "@/utils/lodash";
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
const _props = defineProps<{
dataResult?: any[];
dataResult?: any;
dataQuery?: string;
label?: string;
label?: any;
component?: any;
}>();
const SETTING_OPTIONS = {
@@ -37,7 +38,7 @@ const mapActivesToItems = (index: number) => {
</script>
<template>
<div class="categories-container border-custom" :class="designObject['categories_Class']" @click="selectComponent" :style="designObject['div.categories-container']">
<div :id="`cpn_${_props.component.id}`" class="categories-container border-custom flex-wrap" :class="designObject['categories_Class']" :style="designObject['div.categories-container']">
<div v-for="(component, index) in _dataResult" :key="index" :class="['border-custom', isEmpty(component) ? 'empty' : 'category', designObject['category_Class']]" :style="mapActivesToItems(index)['category']">
<template v-if="!isEmpty(component)">
<div class="category-content">
@@ -45,24 +46,12 @@ const mapActivesToItems = (index: number) => {
<path d="M5.984 2.456V4.184H4.336V5.992H2.4V4.184H0.752V2.456H2.4V0.648H4.336V2.456H5.984Z" fill="black" />
</svg>
<h3
:class="[
index !== 0
? store.page.classifyScreenByWidth({
tablet: '',
smartphone: 'd-none',
})
: '',
]"
:style="mapActivesToItems(index)['h3.categories']"
>
{{ component.title }}
<h3 :style="mapActivesToItems(index)['h3.categories']" class="whitespace-nowrap">
<nuxt-link :to="`/${component.code}`">{{ component.title }}</nuxt-link>
</h3>
</div>
<div v-html="designObject.styleClasses"></div>
<div v-if="designObject.styleClasses" v-html="designObject.styleClasses"></div>
</template>
<div v-else @dragover.prevent @drop.stop.prevent="dropData($event)"></div>
</div>
</div>
</template>
@@ -31,5 +31,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
</template>
@@ -31,6 +31,7 @@ const GET_PROPS = computed(() => {
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
@@ -1,15 +1,16 @@
<script setup lang="ts">
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
import { isEmpty, groupBy } from "lodash";
import { isEmpty } from "@/utils/lodash";
import { enumPageComponentTemplates } from "@/definitions/enum";
const _props = defineProps<{
dataResult?: any[];
dataResult?: any;
dataQuery?: string;
layout?: string;
label?: string;
label?: any;
content?: any;
component?: any;
}>();
const SETTING_OPTIONS = {
@@ -46,7 +47,7 @@ const mapActivesToItems = (index: number) => {
</script>
<template>
<div class="collection-container border-custom" :class="[LAYOUT_PARSE['div.collection-container_Class'], LAYOUT_PARSE['collection_Class']]" @click="selectComponent" :style="LAYOUT_PARSE['div.collection-container']">
<div :id="`cpn_${_props.component.id}`" class="collection-container border-custom" :class="[LAYOUT_PARSE['div.collection-container_Class'], LAYOUT_PARSE['collection_Class']]" :style="LAYOUT_PARSE['div.collection-container']">
<DynamicComponent
v-for="(component, index) in _dataResult"
:key="index"
@@ -57,10 +58,9 @@ const mapActivesToItems = (index: number) => {
dataResult: !isEmpty(component) ? { ...component } : null,
}"
:component="COMPONENT"
@drop-data="dropData"
/>
<div v-html="LAYOUT_PARSE.styleClasses"></div>
</div>
<div v-html="LAYOUT_PARSE.styleClasses"></div>
</template>
<style lang="scss" scoped>
@@ -79,18 +79,6 @@ const mapActivesToItems = (index: number) => {
&.border-custom {
border-color: #e5e5e5 !important;
}
&.borderLeft {
border-left: 1px solid;
}
&.borderRight {
border-right: 1px solid;
}
&.borderTop {
border-top: 1px solid;
}
&.borderBottom {
border-bottom: 1px solid;
}
.empty {
min-height: 100px;
border-radius: 6px;
@@ -1,15 +1,16 @@
<script setup lang="ts">
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
import { isEmpty, groupBy } from "lodash";
import { isEmpty } from "@/utils/lodash";
import { enumPageComponentTemplates } from "@/definitions/enum";
const _props = defineProps<{
dataResult?: any[];
dataResult?: any;
dataQuery?: string;
layout?: string;
label?: string;
label?: any;
content?: any;
component?: any;
}>();
const SETTING_OPTIONS = {
@@ -46,7 +47,7 @@ const mapActivesToItems = (index: number) => {
</script>
<template>
<div class="collection-container border-custom overflow-hidden" :class="[LAYOUT_PARSE['div.collection-container_Class'], LAYOUT_PARSE['collection_Class']]" @click="selectComponent" :style="LAYOUT_PARSE['div.collection-container']">
<div :id="`cpn_${_props.component.id}`" class="collection-container border-custom overflow-hidden" :class="[LAYOUT_PARSE['div.collection-container_Class'], LAYOUT_PARSE['collection_Class']]" :style="LAYOUT_PARSE['div.collection-container']">
<DynamicComponent
v-for="(component, index) in _dataResult"
:key="index"
@@ -57,10 +58,9 @@ const mapActivesToItems = (index: number) => {
dataResult: !isEmpty(component) ? { ...component } : null,
}"
:component="COMPONENT"
@drop-data="dropData"
/>
<div v-html="LAYOUT_PARSE.styleClasses"></div>
</div>
<div v-html="LAYOUT_PARSE.styleClasses"></div>
</template>
<style lang="scss" scoped>
@@ -79,18 +79,6 @@ const mapActivesToItems = (index: number) => {
&.border-custom {
border-color: #e5e5e5 !important;
}
&.borderLeft {
border-left: 1px solid;
}
&.borderRight {
border-right: 1px solid;
}
&.borderTop {
border-top: 1px solid;
}
&.borderBottom {
border-bottom: 1px solid;
}
.empty {
min-height: 100px;
border-radius: 6px;
@@ -0,0 +1,247 @@
<script setup lang="ts">
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
import { isEmpty } from "@/utils/lodash";
import { enumPageComponentTemplates } from "@/definitions/enum";
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
// const store = reactive({
// page: useCmsPageStore(),
// section: usePageSectionStore(),
// });
// const { currentScreenMode } = storeToRefs(useCmsPageStore());
const _props = defineProps<{
dataResult?: any[];
dataQuery?: string;
layout?: string;
label?: string;
content?: any;
component?: any;
}>();
const SETTING_OPTIONS = {
MAX_ELEMENT: 9,
TEMPLATE: "TYPE:Card",
LAYOUT: "TYPE:Card_VideoHightLight",
};
const COMPONENT = {
taxonomy: enumPageComponentTemplates.ARTICLE,
};
const LAYOUT_PARSE = computed(() => {
return _props.label ? getInputValue(_props.label, "OBJECT") : {};
});
const _dataResult = computed(() => {
let _components = Array(Number(LAYOUT_PARSE.value.MAX) || SETTING_OPTIONS.MAX_ELEMENT).fill(null);
const result = getInputValue(_props.dataResult, "ARRAY");
result &&
result.length > 0 &&
_components.map((_: any, index: any) => {
_components[index] = result[index] || null;
});
return _components;
});
async function dropData(data: any) {
if (data) {
const { dataResult, dataType } = data;
const checkDataResult = getInputValue(_props.dataResult, "ARRAY");
const result = _props.dataResult ? [...checkDataResult, { ...dataResult }] : [{ ...dataResult }];
const getDataQuery = _props.dataQuery ? COLLECTION_QUERY_DROP(dataType, getValueStringWithKeyAndColon(_props.dataQuery) + "," + dataResult.id) : COLLECTION_QUERY_DROP(dataType, dataResult.id);
emit("dropData", {
dataResult: result,
dataType,
dataQuery: getDataQuery,
});
}
}
const selectComponent = () => {
emit("selectComponent");
};
const mapActivesToItems = (index: number) => {
if (LAYOUT_PARSE.value && LAYOUT_PARSE.value.listCss) {
return LAYOUT_PARSE.value.listCss[index] || {};
}
return {};
};
</script>
<template>
<div
:id="`cpn_${_props.component?.id}`"
class="collection-video-container border-custom"
:class="[LAYOUT_PARSE['div.collection-container_Class'], LAYOUT_PARSE['collection_Class']]"
@click="selectComponent"
:style="LAYOUT_PARSE['div.collection-container']"
>
<div v-for="(component, index) in _dataResult" :key="index">
<div class="wrap">
<!-- {{ index }} -->
<DynamicComponent
:settings="{
template: SETTING_OPTIONS.TEMPLATE,
layout: SETTING_OPTIONS.LAYOUT,
label: mapActivesToItems(Number(index)),
dataResult: !isEmpty(component) ? { ...component } : null,
}"
:component="COMPONENT"
@drop-data="dropData"
/>
</div>
</div>
</div>
<div v-html="LAYOUT_PARSE.styleClasses" style="display: none" v-if="LAYOUT_PARSE.styles"></div>
</template>
<style lang="scss">
.collection-video-container {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-rows: repeat(3, minmax(0, 1fr));
gap: 20px;
& > div {
background-color: #eee;
position: relative;
width: 100%;
padding-top: calc((9 / 16) * 100%);
& > .wrap {
position: absolute;
top: 0;
width: 100%;
height: 100%;
& > .basic-article {
height: 100%;
& > .article_video {
height: 100%;
& > .article_video_thumb {
height: 100%;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
overflow: hidden;
& > .article_video_content {
padding: 0 24px 8px 24px;
display: flex;
flex-direction: column;
align-items: center;
& > span {
margin-bottom: 10px;
}
& > .article-title {
text-align: center;
font-size: 18px;
font-weight: 700;
line-height: 130%;
margin: 0;
}
& > .article-intro {
display: none;
}
}
}
& > .empty-box {
width: 100%;
height: 100%;
box-sizing: border-box;
margin: 0px;
& > div {
width: 100%;
height: 100%;
}
}
}
}
}
&:nth-child(1) {
grid-column: span 2 / span 2;
grid-row: span 2 / span 2;
order: 6;
background-color: aqua;
& > .wrap {
& > .basic-article {
& > .article_video {
& > .article_video_thumb {
& > .article_video_content {
padding: 0 120px 24px 120px;
}
}
}
}
}
}
&:nth-child(2) {
order: 2;
background-color: red;
}
&:nth-child(3) {
order: 3;
background-color: green;
}
&:nth-child(4) {
order: 4;
background-color: orange;
}
&:nth-child(5) {
order: 5;
background-color: orangered;
}
&:nth-child(6) {
order: 6;
background-color: brown;
}
&:nth-child(7) {
order: 7;
background-color: blueviolet;
}
&:nth-child(8) {
order: 8;
background-color: darkred;
}
&:nth-child(9) {
order: 9;
background-color: darkcyan;
}
}
// &.column-phone {
// grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
// }
// &.column {
// grid-template-columns: repeat(1, minmax(0, 1fr));
// }
// &.row {
// grid-template-rows: auto;
// grid-auto-flow: column;
// }
// &.border-pri {
// gap: 5px;
// }
// &.border-custom {
// border-color: #e5e5e5 !important;
// }
// .empty {
// min-height: 100px;
// border-radius: 6px;
// background: #409eff;
// }
// &.noData {
// border-radius: 6px;
// }
}
</style>
@@ -1,2 +1,3 @@
export { default as Default_Collection } from './Default.vue'
export { default as Audio_Collection } from './Audio.vue'
export { default as Video_Collection } from './Video.vue'
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
import { Default_Collection, Audio_Collection } from "./index";
import { Default_Collection, Audio_Collection, Video_Collection } from "./index";
const _props = defineProps<{
settings: any;
@@ -10,6 +10,7 @@ const _props = defineProps<{
const definedDynamicComponent: Record<string, any> = {
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.COLLECTION]["ARTICLE"]}`]["ARTICLE_COLLECTION_DEFAULT"]]: Default_Collection,
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.COLLECTION]["ARTICLE"]}`]["ARTICLE_COLLECTION_AUDIO"]]: Audio_Collection,
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.COLLECTION]["ARTICLE"]}`]["ARTICLE_COLLECTION_VIDEO"]]: Video_Collection,
};
const getCurrentComponent = computed(() => _props.settings.layout);
@@ -30,5 +31,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
</template>
@@ -0,0 +1 @@
export { default as Misses_Default } from './misses/Default.vue'
@@ -0,0 +1,37 @@
<script lang="ts" setup>
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
import { Misses_Default } from "./index";
const _props = defineProps<{
settings: any;
component?: any;
content?: any;
}>();
const definedDynamicComponent: Record<string, any> = {
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.COLLECTION]["CATEGORY"]}`]["MISSES_COLLECTION_DEFAULT"]]: Misses_Default,
};
const getCurrentComponent = computed(() => _props.settings.layout);
const GET_PROPS = computed(() => {
return () => {
let props: any = {};
if (_props.settings) {
for (const [key, value] of Object.entries(_props.settings)) {
props = {
...props,
[key]: value,
};
}
return props;
}
};
});
</script>
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
</template>
@@ -0,0 +1,195 @@
<script setup lang="ts">
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
import { isEmpty } from "@/utils/lodash";
import { enumPageComponentTemplates } from "@/definitions/enum";
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
const _props = defineProps<{
dataResult?: any;
dataQuery?: string;
layout?: string;
label?: string;
content?: any;
component?: any;
}>();
const SETTING_OPTIONS = {
MAX_ELEMENT: 6,
TEMPLATE: "TYPE:Card",
LAYOUT: "TYPE:Card_MissHightLight",
};
const COMPONENT = {
taxonomy: enumPageComponentTemplates.ARTICLE,
};
const LAYOUT_PARSE = computed(() => {
// console.log(_props.label);
return _props.label ? getInputValue(_props.label, "OBJECT") : {};
});
const _dataResult = computed(() => {
let _components = Array(Number(LAYOUT_PARSE.value.MAX) || SETTING_OPTIONS.MAX_ELEMENT).fill(null);
const result = getInputValue(_props.dataResult, "ARRAY");
result &&
result.length > 0 &&
_components.map((_: any, index: any) => {
_components[index] = result[index] || null;
});
return _components;
});
async function dropData(data: any) {
if (data) {
const { dataResult, dataType } = data;
const checkDataResult = getInputValue(_props.dataResult, "ARRAY");
const result = _props.dataResult ? [...checkDataResult, { ...dataResult }] : [{ ...dataResult }];
const getDataQuery = _props.dataQuery ? COLLECTION_QUERY_DROP(dataType, getValueStringWithKeyAndColon(_props.dataQuery) + "," + dataResult.id) : COLLECTION_QUERY_DROP(dataType, dataResult.id);
emit("dropData", {
dataResult: result,
dataType,
dataQuery: getDataQuery,
});
}
}
const selectComponent = () => {
emit("selectComponent");
};
const mapActivesToItems = (index: number) => {
if (LAYOUT_PARSE.value && LAYOUT_PARSE.value.listCss) {
return LAYOUT_PARSE.value.listCss[index] || {};
}
return {};
};
</script>
<template>
<section :id="`cpn_${_props.component.id}`" class="gallery" :class="[LAYOUT_PARSE['div.collection-container_Class'], LAYOUT_PARSE['collection_Class']]" @click="selectComponent" :style="LAYOUT_PARSE['div.collection-container']">
<div class="wrap" v-for="(component, index) in _dataResult" :key="index">
<DynamicComponent
class="box"
:settings="{
template: SETTING_OPTIONS.TEMPLATE,
layout: SETTING_OPTIONS.LAYOUT,
label: { ...mapActivesToItems(Number(index)) },
dataResult: !isEmpty(component) ? { ...component } : null,
}"
:component="COMPONENT"
@drop-data="dropData"
/>
</div>
</section>
<!-- <conllection
class="collection-container border-custom overflow-hidden"
:class="[LAYOUT_PARSE['div.collection-container_Class'], LAYOUT_PARSE['collection_Class']]"
@click="selectComponent"
:style="LAYOUT_PARSE['div.collection-container']"
>
<DynamicComponent
v-for="(component, index) in _dataResult"
:key="index"
:class="[index === 0 || index === 1 ? 'row-span-3' : index === 2 || index === 3 ? 'row-span-2' : 'row-span-1']"
:settings="{
template: SETTING_OPTIONS.TEMPLATE,
layout: SETTING_OPTIONS.LAYOUT,
label: { ...mapActivesToItems(Number(index)) },
dataResult: !isEmpty(component) ? { ...component } : null,
}"
:component="COMPONENT"
@drop-data="dropData"
/>
</conllection> -->
<div v-if="LAYOUT_PARSE.styleClasses" v-html="LAYOUT_PARSE.styleClasses" style="display: none"></div>
</template>
<style lang="scss" scoped>
.gallery {
column-count: 4;
-webkit-column-count: 4;
-moz-column-count: 4;
gap: 16px;
@media (min-width: 640px) and (max-width: 1024px) {
column-count: 2;
-webkit-column-count: 2;
-moz-column-count: 2;
}
@media (max-width: 640px) {
column-count: 1;
-webkit-column-count: 1;
-moz-column-count: 1;
}
.wrap {
position: relative;
width: 100%;
&:nth-child(1),
&:nth-child(2) {
padding-top: 615px;
}
&:nth-child(3),
&:nth-child(5) {
padding-top: 358px;
}
&:nth-child(4),
&:nth-child(6) {
margin-top: 16px;
padding-top: 241px;
}
& > .box {
position: absolute;
top: 0;
width: 100%;
height: 100%;
padding-bottom: 30px;
// margin: 10px 0;
}
}
.row-span-3 {
// grid-row: span 3 / span 3;
// height: 585px;
// margin: 10px 0;
// &:nth-child(1) {
// background-color: red;
// }
// &:nth-child(2) {
// background-color: yellow;
// }
}
.row-span-2 {
// margin: 10px 0;
// grid-row: span 2 / span 2;
// height: 328px;
// background-color: aqua;
// .basic-article {
// }
}
.row-span-1 {
// grid-row: span 1 / span 1;
// height: 211px;
// background-color: green;
// .basic-article {
// }
}
}
.image img {
height: auto;
width: 100%;
}
.collection-container {
// display: grid;
// grid-template-columns: repeat(4, 1fr);
// grid-template-rows: repeat(3, 1fr);
gap: 20px;
column-count: 4;
}
</style>
@@ -1 +1,2 @@
export { default as Article_Collection } from './articles/index.vue'
export { default as Category_Collection } from './categories/index.vue'
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
import { Article_Collection } from "./index";
import { Article_Collection, Category_Collection } from "./index";
const _props = defineProps<{
settings: any;
@@ -9,6 +9,7 @@ const _props = defineProps<{
}>();
const definedDynamicComponent: Record<string, any> = {
[enumPageComponentTemplate[enumPageComponentKey.COLLECTION]["ARTICLE"]]: Article_Collection,
[enumPageComponentTemplate[enumPageComponentKey.COLLECTION]["CATEGORY"]]: Category_Collection,
};
const getCurrentComponent = computed(() => _props.settings.template);
@@ -30,5 +31,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
</template>
@@ -36,5 +36,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
</template>
@@ -1,12 +1,12 @@
<script setup lang="ts">
import { isEmpty } from "lodash";
import { nanoid } from "nanoid"
import { isEmpty } from "@/utils/lodash";
import { nanoid } from "nanoid";
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import RecusiveNavItem from "@/components/dynamic-page/page-component/templates/navigations/components/RecusiveNavItem.vue";
import { buildTree } from "@/utils/recusive";
const _props = defineProps<{
content?: any[];
content?: any;
component?: any;
}>();
</script>
@@ -15,16 +15,17 @@ const _props = defineProps<{
<div class="px-4 mt-4">
<div class="nav-container">
<template v-if="_props.content">
<div v-for="item, index in buildTree(_props.content)" :key="index" class="nav-items-box">
<div v-for="(item, index) in buildTree(_props.content)" :key="index" class="nav-items-box">
<div class="submenu-container">
<h4 class="" >{{ item.title }}</h4>
<nuxt-link :to="`/${item.slug}`"
><h4 class="font-raleway">{{ item.title }}</h4></nuxt-link
>
<div class="ml-2">
<h5
v-for="_item, _index in item.childs ? item.childs : []"
:key="_index"
<nuxt-link v-for="(_item, _index) in item.childs ? item.childs : []" :key="_index" :to="`/${_item.slug}`"
><h5 class="font-raleway">
{{ _item.title }}
</h5></nuxt-link
>
{{ _item.title }}
</h5>
</div>
</div>
</div>
@@ -32,6 +32,7 @@ const GET_PROPS = computed(() => {
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
@@ -1,24 +1,27 @@
<script lang="ts" setup>
import RecusiveNavItem from "@/components/dynamic-page/page-component/templates/navigations/components/RecusiveNavItem.vue";
import RecusiveSection from "@/components/dynamic-page/page-section/RecusiveSection.vue";
import { enumPageComponentStaticChild } from "@/definitions/enum";
const props = defineProps<{
records?: any[]
component: any;
component?: any;
}>();
const globalState = ref({})
const globalState = ref<any>({})
const setGlobalState = (id: any) => {
globalState.value[id] = !globalState.value[id];
}
</script>
<template>
<div class="navigation-container d-flex gap-4 justify-content-center align-items-center">
<div class="navigation-container flex gap-4 justify-center items-center">
<div v-for="(record) in props.records" :key="record.id" class="navigation-branch cursor-pointer">
<template v-if="record && record.childs && record.childs.length > 0 && record.typeChild === enumPageComponentStaticChild.DEFAULT">
<div class="navigation-submenu">
<div class="navigation_title " @click="selectNavigationComponent">{{ record?.title }}</div>
<div class="navigation_title">
<nuxt-link :to="`/${record?.slug}`" class="!font-arial !font-400">{{ record?.title }}</nuxt-link>
</div>
<div class="navigation-item submenu-container dropdown-container">
<RecusiveNavItem :records="record.childs" />
</div>
@@ -27,7 +30,9 @@ const setGlobalState = (id: any) => {
<template v-else-if="record.typeChild === enumPageComponentStaticChild.LAYOUT">
<div class="navigation-submenu">
<div class="position-relative ps-3">
<div class="navigation_title " @click="selectNavigationComponent">{{ record?.title }}</div>
<div class="navigation_title ">
<nuxt-link :to="`/${record?.slug}`" class="!font-arial !font-400">{{ record?.title }}</nuxt-link>
</div>
</div>
<div class="full-layout dropdown-container">
<template v-if="record.data">
@@ -39,7 +44,9 @@ const setGlobalState = (id: any) => {
</div>
</template>
<template v-else>
<div class="navigation_title navigation-item" @click="selectNavigationComponent">{{ record?.title }}</div>
<div class="navigation_title navigation-item" >
<nuxt-link :to="`/${record?.slug}`" class="!font-arial !font-400">{{ record?.title }}</nuxt-link>
</div>
</template>
</div>
</div>
@@ -1,12 +1,10 @@
<script setup lang="ts">
import { isEmpty } from "lodash";
import { isEmpty } from "@/utils/lodash";
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import { getInputValue } from "@/utils/parseSQL";
const emit = defineEmits(["selectComponent"]);
const _props = defineProps<{
dataResult?: any[];
dataResult?: any;
dataQuery?: string;
component?: any;
}>();
@@ -32,6 +32,7 @@ const GET_PROPS = computed(() => {
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
@@ -33,7 +33,8 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
@@ -3,7 +3,7 @@ import { buildTree } from "@/utils/recusive";
import RecusiveNavItem from "@/components/dynamic-page/page-component/templates/navigations/components/RecusiveNavItem.vue";
const _props = defineProps<{
content?: any[];
content?: any;
component?: any;
}>();
@@ -14,7 +14,7 @@ const SETTING_OPTIONS = {
<template>
<nav>
<div class="d-flex gap-3 justify-content-end align-items-center">
<div class="flex gap-3 justify-end items-center">
<RecusiveNavItem :records="content && buildTree(content)" :component="_props.component" />
</div>
</nav>
@@ -32,6 +32,7 @@ const GET_PROPS = computed(() => {
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
@@ -1,4 +1,4 @@
// export { default as Weather_Day } from './weathers/WeatherDay.vue'
// export { default as Comment_Default } from './comments/Default.vue'
export { default as Other_Weather } from './weathers/index.vue'
export { default as Other_Secutities } from './securities/index.vue'
export { default as Other_Stock } from './stocks/index.vue'
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
import { Other_Weather, Other_Secutities } from "./index";
import { Other_Weather, Other_Stock } from "./index";
const _props = defineProps<{
settings: any;
@@ -9,7 +9,7 @@ const _props = defineProps<{
}>();
const definedDynamicComponent: Record<string, any> = {
[enumPageComponentTemplate[enumPageComponentKey.OTHER]["WEATHER"]]: Other_Weather,
[enumPageComponentTemplate[enumPageComponentKey.OTHER]["SECURITIES"]]: Other_Secutities,
[enumPageComponentTemplate[enumPageComponentKey.OTHER]['STOCK']]: Other_Stock,
// [enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]: Article_Detail,
};
const getCurrentComponent = computed(() => _props.settings.template);
@@ -30,5 +30,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
</template>
@@ -1,15 +0,0 @@
<script setup lang="ts"></script>
<template>
<div>chứng khoán</div>
</template>
<style lang="scss" scoped>
div {
width: 100%;
height: 200px;
background-color: #ededed;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
@@ -1 +0,0 @@
export { default as Securities_Default } from './Securities.vue'
@@ -0,0 +1,32 @@
<script setup lang="ts">
import { nanoid } from 'nanoid';
import JSWidget from '@/components/widget/JSwidget.vue';
const widgetOptions = {
"locale": "vi",
"width": "334px",
"height": "250px",
"price_line_color": "#71BDDF",
"grid_color": "#999999",
"label_color": "#999999",
}
</script>
<template>
<JSWidget
:CONTAINER_ID="nanoid(10)"
:SCRIPT_ID="nanoid(10)"
SCRIPT_SRC="https://www.fireant.vn/Scripts/web/widgets.js"
:options="widgetOptions"
:inside="false"
widgetKey="FireAnt"
/>
</template>
<style lang="scss" scoped>
div {
width: 100%;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { nanoid } from 'nanoid';
import JSWidget from '@/components/widget/JSwidget.vue';
const widgetOptions = {
"symbols": [
{
"proName": "FOREXCOM:SPXUSD",
"title": "S&P 500 Index"
},
{
"proName": "FOREXCOM:NSXUSD",
"title": "US 100 Cash CFD"
},
{
"proName": "FX_IDC:EURUSD",
"title": "EUR to USD"
},
{
"proName": "BITSTAMP:BTCUSD",
"title": "Bitcoin"
},
{
"proName": "BITSTAMP:ETHUSD",
"title": "Ethereum"
}
],
"isTransparent": false,
"showSymbolLogo": true,
"colorTheme": "dark",
"locale": "en"
}
</script>
<template>
<div>
<JSWidget
:CONTAINER_ID="nanoid(10)"
:SCRIPT_ID="nanoid(10)"
SCRIPT_SRC="https://s3.tradingview.com/external-embedding/embed-widget-tickers.js"
:options="widgetOptions"
:inside="true"
widgetKey="TradingView"
/>
</div>
</template>
<style lang="scss" scoped>
div {
width: 100%;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
@@ -0,0 +1,2 @@
export { default as Stock_Default } from './334x641.vue'
export { default as Stock_FullSize } from './Full.vue'
@@ -1,15 +1,15 @@
<script lang="ts" setup>
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
import { Stock_Default, Stock_FullSize } from "./index";
// import { Article_Card, Article_Detail_Video, Article_Detail_Podcast, Article_Detail_General, Article_Detail_Image } from "./index";
import { Securities_Default } from "./index";
const _props = defineProps<{
settings: any;
component?: any;
content?: any;
}>();
const definedDynamicComponent: Record<string, any> = {
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.OTHER]["SECURITIES"]]["SECURITIES_DEFAULT"]]: Securities_Default,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.OTHER]['STOCK']]['STOCK_DEFAULT']]: Stock_Default,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.OTHER]["STOCK"]]["STOCK_FULLSIZE"]]: Stock_FullSize,
};
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
@@ -30,5 +30,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
</template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
</template>
@@ -21,7 +21,7 @@
<div class="big-temp">28°</div>
<div class="name">Mưa nhẹ</div>
</div>
<div class="color-gray-2">
<!-- <div class="color-gray-2">
<p>Cao: 28° Thấp: 24°</p>
<div>
<span>Không khí:</span>
@@ -52,9 +52,9 @@
</div>
</div>
</div>
</div>
</div> -->
</div>
<div class="box-info-weather__right text-right color-gray-2">
<!-- <div class="box-info-weather__right text-right color-gray-2">
<div class="weather-tooltip mb40">
<span class="weather-tooltip-group">
<span>Cảm giác như 33° </span>
@@ -134,7 +134,7 @@
</div>
</div>
</div>
</div>
</div> -->
</div>
</template>
<style scoped lang="scss">
@@ -145,9 +145,9 @@
margin-bottom: 24px;
font-size: 16px;
line-height: 1.5;
min-height: 240px;
// min-height: 240px;
&.flexbox {
align-items: end;
align-items: flex-end;
display: flex;
}
&__left {
@@ -9,7 +9,7 @@ const _props = defineProps<{
}>();
const definedDynamicComponent: Record<string, any> = {
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']]['NAVIGATION_TOP_DEFAULT']]: WeatherDay,
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.OTHER]["WEATHER"]]["WEATHER_DEFAULT"]]: WeatherDay,
};
const getCurrentComponent = computed(() => _props.settings.layout);
@@ -32,6 +32,7 @@ const GET_PROPS = computed(() => {
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
/>
@@ -1,16 +1,13 @@
<script setup lang="ts">
import { isEmpty } from "lodash";
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import { COLLECTION_PAGING_QUERY_DROP, getInputValue } from "@/utils/parseSQL";
import { getInputValue } from "@/utils/parseSQL";
import { enumPageComponentTemplates } from "@/definitions/enum";
const router = useRouter();
const route = useRoute();
const emit = defineEmits(["dropData", "selectComponent"]);
const store = reactive({
component: usePageComponentStore(),
import { useScroll } from '@vueuse/core';
const store = reactive({
component: useDynamicPageStore(),
});
const _props = defineProps<{
dataResult?: any[];
dataQuery?: string;
@@ -20,8 +17,12 @@ const _props = defineProps<{
const SETTING_OPTIONS = {
MAX_ELEMENT: 5,
TEMPLATE: "Article",
LAYOUT: "TYPE:Card",
TEMPLATE: "TYPE:Card",
LAYOUT: "TYPE:Card_Default",
};
const COMPONENT = {
taxonomy: enumPageComponentTemplates.ARTICLE,
};
const page = ref(1);
@@ -43,20 +44,6 @@ const designObject = computed(() => {
return _props.label ? getInputValue(_props.label, "OBJECT") : {};
});
const dropData = (event: any) => {
const queryBy = {
Category: "Categories",
};
const { dataResult, dataType } = JSON.parse(event.dataTransfer.getData("category"));
// const getDataQuery = `Get[${type}] Top[20] With[${queryBy[dataType]}:${dataResult.id}]`;
const getDataQuery = COLLECTION_PAGING_QUERY_DROP(type, { key: queryBy[dataType], value: dataResult.id });
emit("dropData", {
dataResult: [],
dataType,
dataQuery: getDataQuery,
});
};
//?cpn_1=page:2&cpn_2=page:1
// Get[Article] Top[5] With[Categories:1]
const select = async (pageActive: number) => {
@@ -76,14 +63,11 @@ const handleRouteChange = async (query: any) => {
const param = query[`cpn_${_props.component?.id}`];
if (param) {
const [_, value] = param.split(":") || [];
page.value = value;
page.value = Number(value);
await loadPage(value);
}
};
onBeforeMount(() => {
if (route.query[`cpn_${_props.component?.id}`]) handleRouteChange(route.query);
});
const loadPage = async (page: number) => {
let newDataQuery = "";
@@ -95,14 +79,17 @@ const loadPage = async (page: number) => {
} else {
newDataQuery = _props.component?.settings?.dataQuery + ` Page[${page}]`;
}
const res = await store.component.getOverviewPageComponentById(Number(_props.component?.id), newDataQuery);
const data = getInputValue(res?.settings?.dataResult, "OBJECT");
const {item} = await store.component.getOverviewPageComponentById(Number(_props.component?.id), newDataQuery);
const data = getInputValue(item?.settings?.dataResult, "OBJECT");
if (Object.keys(data).length > 0) {
totals.value = data.Total;
listArticlePaging.value = data?.Data || [];
}
};
if (route.query[`cpn_${_props.component?.id}`]) handleRouteChange(route.query);
const handleNextPrev = (type: "+" | "-") => {
if (listArticleByCategory.value?.length > 0) {
if (type === "+") {
@@ -114,63 +101,42 @@ const handleNextPrev = (type: "+" | "-") => {
}
};
const selectComponent = () => {
emit("selectComponent");
};
const mapActivesToItems = (index: number) => {
if (designObject.value && designObject.value.listCss) {
return designObject.value.listCss[index] || {};
}
return {};
};
</script>
<template>
<section>
<div class="section-container" @click="selectComponent" @dragover.prevent @drop.stop.prevent="dropData($event)" :class="[listArticleByCategory && listArticleByCategory?.length > 0 ? '' : 'noData']">
<section :id="`cpn_[${_props.component.id}]`" v-if="listArticleByCategory?.length > 0">
<div class="section-container">
<div class="section-layout" :style="designObject['div.section']">
<template v-if="listArticleByCategory?.length > 0">
<template v-for="(component, index) in listArticlePaging?.length > 0 ? listArticlePaging : listArticleByCategory">
<DynamicComponent
:key="index"
v-if="!isEmpty(component)"
:settings="{
template: SETTING_OPTIONS.TEMPLATE,
layout: SETTING_OPTIONS.LAYOUT,
dataResult: { ...component },
label: mapActivesToItems(Number(index)),
}"
/>
</template>
</template>
<template v-else>
<div class="empty"><h6 class="px-2 text-center">Nội dung danh sách bài viết của danh mục sẽ đây</h6></div>
<template v-for="(component, index) in listArticlePaging?.length > 0 ? listArticlePaging : listArticleByCategory" :key="index">
<DynamicComponent
:settings="{
template: SETTING_OPTIONS.TEMPLATE,
layout: SETTING_OPTIONS.LAYOUT,
dataResult: { ...component },
label: mapActivesToItems(Number(index)),
}"
:component="COMPONENT"
/>
</template>
<div class="button-page flex">
<a class="btn-page prev-page" @click.stop="() => handleNextPrev('-')" v-if="page > 1">
<i class="el-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path
fill="currentColor"
d="M609.408 149.376 277.76 489.6a32 32 0 0 0 0 44.672l331.648 340.352a29.12 29.12 0 0 0 41.728 0 30.592 30.592 0 0 0 0-42.752L339.264 511.936l311.872-319.872a30.592 30.592 0 0 0 0-42.688 29.12 29.12 0 0 0-41.728 0z"
></path>
</svg>
</i>
<a :href="`#cpn_[${_props.component.id}]`" class="btn-page prev-page" @click.stop="() => handleNextPrev('-')" v-if="page > 1">
<Icon name="ooui:previous-ltr"></Icon>
</a>
<a v-if="listArticleByCategory?.length > 0" :class="['btn-page', page === index + 1 && 'active']" @click.stop="() => select(index + 1)" v-for="(_, index) in Math.ceil(totals / limit)">{{ index + 1 }}</a>
<a class="btn-page next-page" @click.stop="() => handleNextPrev('+')" v-if="page < Math.ceil(totals / limit)">
<i class="el-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path
fill="currentColor"
d="M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z"
></path>
</svg>
</i>
<a :href="`#cpn_[${_props.component.id}]`" :class="['btn-page', page === index + 1 && 'active']" @click.stop="() => select(index + 1)" v-for="(_, index) in Math.ceil(totals / limit)" :key="index">{{ index + 1 }}</a>
<a :href="`#cpn_[${_props.component.id}]`" class="btn-page next-page" @click.stop="() => handleNextPrev('+')" v-if="page < Math.ceil(totals / limit)">
<Icon name="ooui:previous-rtl"></Icon>
</a>
</div>
</div>
</div>
<div v-html="designObject.styleClasses" style="display: none" v-if="designObject.styles"></div>
</section>
</template>
@@ -180,35 +146,9 @@ const mapActivesToItems = (index: number) => {
display: flex;
flex-direction: column;
/* gap: 10px; */
overflow-x: scroll;
&.border-custom {
border-color: #e5e5e5 !important;
}
&.borderLeft {
border-left: 1px solid;
}
&.borderRight {
border-right: 1px solid;
}
&.borderTop {
border-top: 1px solid;
}
&.borderBottom {
border-bottom: 1px solid;
}
}
.empty {
width: 100%;
height: 100%;
min-height: 50px;
background-color: #409eff;
display: flex;
white-space: normal;
justify-content: center;
align-items: center;
h6 {
color: #fff;
}
}
.basic-article {
&.article {
@@ -217,9 +157,6 @@ const mapActivesToItems = (index: number) => {
pointer-events: none;
}
}
&.noData {
border-radius: 6px;
}
.flex {
display: flex;
@@ -237,17 +174,21 @@ const mapActivesToItems = (index: number) => {
.btn-page {
width: 40px;
height: 40px;
padding: 9px 16px;
text-align: center;
line-height: 36px;
border: 1px solid #409eff;
border-radius: 3px;
border-radius: 4px;
margin-left: 10px;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
flex-shrink: 0;
background: #f2f2f2;
cursor: pointer;
color: #222222;
&.active {
background: #409eff;
background: #ed1c24;
color: white;
}
}
@@ -31,6 +31,7 @@ const GET_PROPS = computed(() => {
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
@@ -31,6 +31,7 @@ const GET_PROPS = computed(() => {
<template>
<component
v-if="definedDynamicComponent[getCurrentComponent]"
:is="definedDynamicComponent[getCurrentComponent]"
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
/>
@@ -0,0 +1,595 @@
<script setup lang="ts">
import Poll from "~/components/article/immerse/Poll.vue";
import Quiz from "~/components/article/immerse/Quiz.vue";
import Survey from "~/components/article/immerse/Survey.vue";
import Document from "~/components/article/immerse/Document.vue";
import Attachment from "@/components/article/immerse/Attachment.vue";
import Tag from "@/components/article/immerse/Tag.vue";
// import articlerelation from "@/components/article/immerse/ArticleRelation.vue";
import RecusiveSection from "@/components/dynamic-page/page-section/RecusiveSection.vue";
import { getInputValue } from "@/utils/parseSQL";
import { enumPageSectionLayouts, enumPageSectionTemplate, enumPageSectionKey } from "~/definitions/enum";
import type { PageSection } from "@/server/models/dynamic-page/index";
import { formatDate } from '@/utils/filters'
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
const { categoryTree } = storeToRefs(useCategoryStore());
const { currentArticle } = storeToRefs(useArticleStore())
if(categoryTree.value) {
await useCategoryStore().fetchBySiteId()
}
const props = defineProps<{
layout?: string;
content?: any;
settings: any;
section: PageSection;
}>();
const defineTypeRecusive = {
COMPONENT: "component",
SECTION: "section",
};
const SETTING_OPTIONS = computed(() => {
let _setting_options = {};
switch (props.layout) {
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.ARTICLE]["DETAIL"]]["DEFAULT"]:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
default:
_setting_options = {
MAX_ELEMENT: 1,
};
break;
}
return _setting_options;
});
const designObject = computed(() => {
// không truyn lable là ch lên s li
return props?.settings?.label ? getInputValue(props.settings.label, "OBJECT") : {};
});
const CLASS_FOR_SECTION = computed(() => {
let _classForSection = {};
switch (props.layout) {
case enumPageSectionLayouts[`${enumPageSectionTemplate[enumPageSectionKey.ARTICLE]["DETAIL"]}`]["DEFAULT"]:
_classForSection = {
section_layout: "section_layout two_col_layout tow_row_layout",
section_layout_value: enumPageSectionLayouts[`${enumPageSectionTemplate[enumPageSectionKey.ARTICLE]["DETAIL"]}`]["DEFAULT"],
};
break;
default:
_classForSection = {
section_layout: "section_layout basic_column",
};
break;
}
return _classForSection;
});
const LAYOUT_PARSE = computed(() => {
return props.settings?.label ? getInputValue(props.settings.label, "OBJECT") : {};
});
const mapActivesToItems = (index: number) => {
if (LAYOUT_PARSE.value && LAYOUT_PARSE.value.listCss) {
return LAYOUT_PARSE.value.listCss[index] || {};
}
return {};
};
const currentCategoryTree = ref<any []>([]);
if(currentArticle.value?.categoryId) {
console.log('urrentArticle.value?.categoryId', categoryTree.value)
currentCategoryTree.value = findElementPathById(categoryTree.value, currentArticle.value.categoryId)
}
function findElementPathById(categories: any[], targetId: number, path: any[] = []) {
for (const category of categories) {
const currentPath = [...path, { title: category.title, code: category.code }];
if (category.id === targetId) {
return currentPath;
}
if (category.children) {
const result: any = findElementPathById(category.children, targetId, currentPath);
if (result) {
return result;
}
}
}
return null;
}
</script>
<template>
<div class="section_layout border-custom four_col_layout" :style="LAYOUT_PARSE['div.section_layout']">
<div class="left">
<div>
<!-- <div class="audio">
<div class="play">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M24 12C24 18.6562 18.6094 24 12 24C5.34375 24 0 18.6562 0 12C0 5.39062 5.34375 0 12 0C18.6094 0 24 5.39062 24 12ZM8.25 7.875V16.125C8.25 16.5469 8.4375 16.9219 8.8125 17.1094C9.14062 17.3438 9.60938 17.2969 9.9375 17.1094L16.6875 12.9844C17.0156 12.7969 17.25 12.4219 17.25 12C17.25 11.625 17.0156 11.25 16.6875 11.0625L9.9375 6.9375C9.60938 6.70312 9.14062 6.70312 8.8125 6.9375C8.4375 7.125 8.25 7.5 8.25 7.875Z"
fill="#8E8E8E"
/>
</svg>
</div>
<div class="time">
<span>20:42</span>
</div>
<div class="timeline">
<input type="range" name="" id="" />
</div>
</div> -->
<div class="buttons">
<div class="actions">
<span class="copy">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 3V0H8.5C7.65625 0 7 0.6875 7 1.5V10.5C7 11.3438 7.65625 12 8.5 12H14.5C15.3125 12 16 11.3438 16 10.5V4H13C12.4375 4 12 3.5625 12 3ZM13 0V3H16L13 0ZM6 11V4H1.5C0.65625 4 0 4.6875 0 5.5V14.5C0 15.3438 0.65625 16 1.5 16H7.5C8.3125 16 9 15.3438 9 14.5V13H8C6.875 13 6 12.125 6 11Z"
fill="currentColor"
/>
</svg>
</span>
<span class="facebook">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15.75 8C15.75 3.71875 12.2812 0.25 8 0.25C3.71875 0.25 0.25 3.71875 0.25 8C0.25 11.875 3.0625 15.0938 6.78125 15.6562V10.25H4.8125V8H6.78125V6.3125C6.78125 4.375 7.9375 3.28125 9.6875 3.28125C10.5625 3.28125 11.4375 3.4375 11.4375 3.4375V5.34375H10.4688C9.5 5.34375 9.1875 5.9375 9.1875 6.5625V8H11.3438L11 10.25H9.1875V15.6562C12.9062 15.0938 15.75 11.875 15.75 8Z"
fill="currentColor"
/>
</svg>
</span>
<span class="envelope">
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8 9C7.46875 9 6.9375 8.84375 6.5 8.5L0 3.4375V10.5C0 11.3438 0.65625 12 1.5 12H14.5C15.3125 12 16 11.3438 16 10.5V3.4375L9.46875 8.5C9.03125 8.84375 8.5 9 8 9ZM0.5 2.5625L7.125 7.71875C7.625 8.09375 8.34375 8.09375 8.84375 7.71875L15.4688 2.5625C15.7812 2.3125 16 1.90625 16 1.5C16 0.6875 15.3125 0 14.5 0H1.5C0.65625 0 0 0.6875 0 1.5C0 1.90625 0.1875 2.3125 0.5 2.5625Z"
fill="currentColor"
/>
</svg>
</span>
<span class="print">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M14 6H2C0.875 6 0 6.90625 0 8V11C0 11.5625 0.4375 12 1 12H2V15C2 15.5625 2.4375 16 3 16H13C13.5312 16 14 15.5625 14 15V12H15C15.5312 12 16 11.5625 16 11V8C16 6.90625 15.0938 6 14 6ZM12 14H4V11H12V14ZM13.5 9.25C13.0625 9.25 12.75 8.9375 12.75 8.5C12.75 8.09375 13.0625 7.75 13.5 7.75C13.9062 7.75 14.25 8.09375 14.25 8.5C14.25 8.9375 13.9062 9.25 13.5 9.25ZM4 2H11.1562L12 2.84375V5H14V2.4375C14 2.15625 13.875 1.90625 13.6875 1.71875L12.2812 0.3125C12.0938 0.125 11.8438 0 11.5625 0H3C2.4375 0 2 0.46875 2 1V5H4V2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div class="fontSize">
<span>
<svg width="13" height="15" viewBox="0 0 13 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.05469 12C0.971354 12 0.90625 11.9609 0.859375 11.8828C0.8125 11.7995 0.789062 11.7057 0.789062 11.6016C0.789062 11.4818 0.809896 11.4062 0.851562 11.375C0.888021 11.3438 0.960938 11.3229 1.07031 11.3125C1.20052 11.2969 1.34115 11.2891 1.49219 11.2891C1.64844 11.2839 1.79427 11.2448 1.92969 11.1719C2.07031 11.0938 2.18229 10.9401 2.26562 10.7109L5.84375 0.742188H7.09375L10.5703 10.7109C10.6536 10.9401 10.763 11.0938 10.8984 11.1719C11.0339 11.2448 11.1771 11.2839 11.3281 11.2891C11.4844 11.2891 11.625 11.2969 11.75 11.3125C11.8594 11.3229 11.9323 11.3438 11.9688 11.375C12.0104 11.4062 12.0312 11.4818 12.0312 11.6016C12.0312 11.6589 12.0078 11.7396 11.9609 11.8438C11.9193 11.9479 11.8542 12 11.7656 12H7.85156C7.76302 12 7.69531 11.9479 7.64844 11.8438C7.60677 11.7396 7.58594 11.6589 7.58594 11.6016C7.58594 11.4297 7.67188 11.3333 7.84375 11.3125C8.16146 11.2969 8.41927 11.2839 8.61719 11.2734C8.8151 11.263 8.91406 11.1979 8.91406 11.0781C8.91406 11.0417 8.90625 11.0052 8.89062 10.9688L7.94531 8.14062H4.19531L3.24219 10.9688C3.23177 10.9948 3.22656 11.026 3.22656 11.0625C3.22656 11.1927 3.33594 11.263 3.55469 11.2734C3.77865 11.2786 4.04948 11.2917 4.36719 11.3125C4.53906 11.3333 4.625 11.4297 4.625 11.6016C4.625 11.6589 4.60156 11.7396 4.55469 11.8438C4.51302 11.9479 4.44792 12 4.35938 12H1.05469ZM6.14844 2.72656L4.4375 7.46875H7.72656L6.14844 2.72656Z"
fill="currentColor"
/>
<path d="M1 13.8984H11.7344V14.7266H1V13.8984Z" fill="black" />
</svg>
</span>
<span>
<svg width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.08203 17C0.957031 17 0.859375 16.9414 0.789062 16.8242C0.71875 16.6992 0.683594 16.5586 0.683594 16.4023C0.683594 16.2227 0.714844 16.1094 0.777344 16.0625C0.832031 16.0156 0.941406 15.9844 1.10547 15.9688C1.30078 15.9453 1.51172 15.9336 1.73828 15.9336C1.97266 15.9258 2.19141 15.8672 2.39453 15.7578C2.60547 15.6406 2.77344 15.4102 2.89844 15.0664L8.26562 0.113281H10.1406L15.3555 15.0664C15.4805 15.4102 15.6445 15.6406 15.8477 15.7578C16.0508 15.8672 16.2656 15.9258 16.4922 15.9336C16.7266 15.9336 16.9375 15.9453 17.125 15.9688C17.2891 15.9844 17.3984 16.0156 17.4531 16.0625C17.5156 16.1094 17.5469 16.2227 17.5469 16.4023C17.5469 16.4883 17.5117 16.6094 17.4414 16.7656C17.3789 16.9219 17.2812 17 17.1484 17H11.2773C11.1445 17 11.043 16.9219 10.9727 16.7656C10.9102 16.6094 10.8789 16.4883 10.8789 16.4023C10.8789 16.1445 11.0078 16 11.2656 15.9688C11.7422 15.9453 12.1289 15.9258 12.4258 15.9102C12.7227 15.8945 12.8711 15.7969 12.8711 15.6172C12.8711 15.5625 12.8594 15.5078 12.8359 15.4531L11.418 11.2109H5.79297L4.36328 15.4531C4.34766 15.4922 4.33984 15.5391 4.33984 15.5938C4.33984 15.7891 4.50391 15.8945 4.83203 15.9102C5.16797 15.918 5.57422 15.9375 6.05078 15.9688C6.30859 16 6.4375 16.1445 6.4375 16.4023C6.4375 16.4883 6.40234 16.6094 6.33203 16.7656C6.26953 16.9219 6.17188 17 6.03906 17H1.08203ZM8.72266 3.08984L6.15625 10.2031H11.0898L8.72266 3.08984Z"
fill="currentColor"
/>
</svg>
</span>
<span>
<svg width="24" height="23" viewBox="0 0 24 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.10938 23C0.942708 23 0.8125 22.9219 0.71875 22.7656C0.625 22.599 0.578125 22.4115 0.578125 22.2031C0.578125 21.9635 0.619792 21.8125 0.703125 21.75C0.776042 21.6875 0.921875 21.6458 1.14062 21.625C1.40104 21.5938 1.68229 21.5781 1.98438 21.5781C2.29688 21.5677 2.58854 21.4896 2.85938 21.3438C3.14062 21.1875 3.36458 20.8802 3.53125 20.4219L10.6875 0.484375H13.1875L20.1406 20.4219C20.3073 20.8802 20.526 21.1875 20.7969 21.3438C21.0677 21.4896 21.3542 21.5677 21.6562 21.5781C21.9688 21.5781 22.25 21.5938 22.5 21.625C22.7188 21.6458 22.8646 21.6875 22.9375 21.75C23.0208 21.8125 23.0625 21.9635 23.0625 22.2031C23.0625 22.3177 23.0156 22.4792 22.9219 22.6875C22.8385 22.8958 22.7083 23 22.5312 23H14.7031C14.526 23 14.3906 22.8958 14.2969 22.6875C14.2135 22.4792 14.1719 22.3177 14.1719 22.2031C14.1719 21.8594 14.3438 21.6667 14.6875 21.625C15.3229 21.5938 15.8385 21.5677 16.2344 21.5469C16.6302 21.526 16.8281 21.3958 16.8281 21.1562C16.8281 21.0833 16.8125 21.0104 16.7812 20.9375L14.8906 15.2812H7.39062L5.48438 20.9375C5.46354 20.9896 5.45312 21.0521 5.45312 21.125C5.45312 21.3854 5.67188 21.526 6.10938 21.5469C6.55729 21.5573 7.09896 21.5833 7.73438 21.625C8.07812 21.6667 8.25 21.8594 8.25 22.2031C8.25 22.3177 8.20312 22.4792 8.10938 22.6875C8.02604 22.8958 7.89583 23 7.71875 23H1.10938ZM11.2969 4.45312L7.875 13.9375H14.4531L11.2969 4.45312Z"
fill="currentColor"
/>
</svg>
</span>
</div>
</div>
<div class="tags" v-if="currentArticle && currentArticle?.tags">
<span>
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.875 0.75H7.69531C8.35938 0.75 8.98438 1.02344 9.45312 1.49219L16.3281 8.36719C17.3047 9.34375 17.3047 10.9453 16.3281 11.9219L11.1328 17.1172C10.1562 18.0938 8.55469 18.0938 7.57812 17.1172L0.703125 10.2422C0.234375 9.77344 0 9.14844 0 8.48438V2.625C0 1.60938 0.820312 0.75 1.875 0.75ZM4.375 6.375C5.03906 6.375 5.625 5.82812 5.625 5.125C5.625 4.46094 5.03906 3.875 4.375 3.875C3.67188 3.875 3.125 4.46094 3.125 5.125C3.125 5.82812 3.67188 6.375 4.375 6.375Z"
fill="#ED1C24"
/>
</svg>
</span>
<ul >
<li v-for="(tag, index) in currentArticle?.tags">
<nuxt-link class="font-raleway font-500" :to="`/tag/${tag.code}`">{{ tag.title }}</nuxt-link>
</li>
</ul>
</div>
</div>
<div class="section_child" :class="['border-custom']" :style="mapActivesToItems(0)['div.section_child']" @dragover.prevent @drop.stop.prevent="dropPlacementInSection($event, 0, props.content ? props?.content[0].data : '')">
<template v-if="props.content">
<RecusiveSection :type="props?.content[0].type" :id="props?.content[0].data" :section="props.section" />
</template>
<template v-else>
<RecusiveSection :type="''" :id="''" :section="props.section" />
</template>
</div>
</div>
<div class="content detail-default">
<div class="content__top">
<div class="flex justify-between flex-wrap items-center mb-10px">
<ul class="flex gap-32px" v-if="currentCategoryTree?.length">
<li v-for="( category, index ) in currentCategoryTree" :key="index" class="first:text-#000 text-#929292 last:after:content-[''] relative after:absolute after:content-['/'] after:text-20px after:right--20px" >
<nuxt-link class=" font-raleway text-18px font-500 leading-180% uppercase" :to="`/${category.code}`">{{ category.title }}</nuxt-link>
</li>
</ul>
<div v-if="currentArticle?.topics" class="pl-20px relative bg-primary inline-block">
<nuxt-link class="h-30px block py-4px px-16px border-1 border-#000 bg-white text-12px leading-180% font-raleway font-400" :to="`/topic/${currentArticle?.topics[0].slug}`">{{ currentArticle?.topics[0].title }}</nuxt-link>
</div>
</div>
<h2 class="font-gelasio text-44px font-bold leading-130%" v-if="currentArticle?.title" v-html="currentArticle?.title"></h2>
<div class="author flex gap-12px my-20px" v-if="currentArticle?.authors">
<ul class="flex">
<li :style="{'z-index': index + 1}" class="relative ml--12px first:ml-0" v-for="(author, index) in currentArticle?.authors" :key="index">
<nuxt-link :to="`/tac-gia/${author.code}`">
<img :src="author?.thumbnail || `http://picsum.photos/1024/600?random=1`" alt="" class="w-64px p-1px border-1px border-white h-64px object-cover rounded-full">
</nuxt-link>
</li>
</ul>
<div>
<div class="mt-10px">
<nuxt-link class="font-raleway text-#000" v-for="(author, index) in currentArticle?.authors" :key="index" :to="`/tac-gia/${author.code}`">{{ author.title + (index < currentArticle.authors.length - 1 ? ', ' : '') }}</nuxt-link>
</div>
<div class="text-12px">
Xuất bản vào {{ formatDate(currentArticle?.publishedOn, 'DD/MM/YYYY | hh:mm') }}
</div>
</div>
</div>
<figure v-if="currentArticle?.thumbnail">
<img :src="currentArticle?.thumbnail" class="w-full " alt="">
</figure>
</div>
<div class="content__bottom" >
<div class="content__bottom__left">
<div class="content__bottom__left__control">
<div class="content__bottom__left__control__sticky [&_span:hover]:text-primary">
<span class="flex justify-center ">
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M20 10.5C20 16.0469 15.5078 20.5 10 20.5C4.45312 20.5 0 16.0469 0 10.5C0 4.99219 4.45312 0.5 10 0.5C15.5078 0.5 20 4.99219 20 10.5ZM6.875 7.0625V13.9375C6.875 14.2891 7.03125 14.6016 7.34375 14.7578C7.61719 14.9531 8.00781 14.9141 8.28125 14.7578L13.9062 11.3203C14.1797 11.1641 14.375 10.8516 14.375 10.5C14.375 10.1875 14.1797 9.875 13.9062 9.71875L8.28125 6.28125C8.00781 6.08594 7.61719 6.08594 7.34375 6.28125C7.03125 6.4375 6.875 6.75 6.875 7.0625Z"
fill="currentColor"
/>
</svg>
</span>
<span class="flex justify-center">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 3V0H8.5C7.65625 0 7 0.6875 7 1.5V10.5C7 11.3438 7.65625 12 8.5 12H14.5C15.3125 12 16 11.3438 16 10.5V4H13C12.4375 4 12 3.5625 12 3ZM13 0V3H16L13 0ZM6 11V4H1.5C0.65625 4 0 4.6875 0 5.5V14.5C0 15.3438 0.65625 16 1.5 16H7.5C8.3125 16 9 15.3438 9 14.5V13H8C6.875 13 6 12.125 6 11Z"
fill="currentColor"
/>
</svg>
</span>
<span class="flex justify-center">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15.75 8C15.75 3.71875 12.2812 0.25 8 0.25C3.71875 0.25 0.25 3.71875 0.25 8C0.25 11.875 3.0625 15.0938 6.78125 15.6562V10.25H4.8125V8H6.78125V6.3125C6.78125 4.375 7.9375 3.28125 9.6875 3.28125C10.5625 3.28125 11.4375 3.4375 11.4375 3.4375V5.34375H10.4688C9.5 5.34375 9.1875 5.9375 9.1875 6.5625V8H11.3438L11 10.25H9.1875V15.6562C12.9062 15.0938 15.75 11.875 15.75 8Z"
fill="currentColor"
/>
</svg>
</span>
<span class="flex justify-center">
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8 9C7.46875 9 6.9375 8.84375 6.5 8.5L0 3.4375V10.5C0 11.3438 0.65625 12 1.5 12H14.5C15.3125 12 16 11.3438 16 10.5V3.4375L9.46875 8.5C9.03125 8.84375 8.5 9 8 9ZM0.5 2.5625L7.125 7.71875C7.625 8.09375 8.34375 8.09375 8.84375 7.71875L15.4688 2.5625C15.7812 2.3125 16 1.90625 16 1.5C16 0.6875 15.3125 0 14.5 0H1.5C0.65625 0 0 0.6875 0 1.5C0 1.90625 0.1875 2.3125 0.5 2.5625Z"
fill="currentColor"
/>
</svg>
</span>
<span class="flex justify-center">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M14 6H2C0.875 6 0 6.90625 0 8V11C0 11.5625 0.4375 12 1 12H2V15C2 15.5625 2.4375 16 3 16H13C13.5312 16 14 15.5625 14 15V12H15C15.5312 12 16 11.5625 16 11V8C16 6.90625 15.0938 6 14 6ZM12 14H4V11H12V14ZM13.5 9.25C13.0625 9.25 12.75 8.9375 12.75 8.5C12.75 8.09375 13.0625 7.75 13.5 7.75C13.9062 7.75 14.25 8.09375 14.25 8.5C14.25 8.9375 13.9062 9.25 13.5 9.25ZM4 2H11.1562L12 2.84375V5H14V2.4375C14 2.15625 13.875 1.90625 13.6875 1.71875L12.2812 0.3125C12.0938 0.125 11.8438 0 11.5625 0H3C2.4375 0 2 0.46875 2 1V5H4V2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
</div>
<div>
<p class="my-10px" v-if="currentArticle?.intro" v-html="currentArticle.intro">
</p>
<!-- <div v-html="currentArticle.detail" class="[&_p_>_span]:!font-raleway [&_p]:mb-10px"></div> -->
<component :is="{ template: currentArticle?.detail, components: { Poll, Quiz, Survey, Document, Attachment, Tag } }" />
</div>
</div>
<div class="content__bottom__right">
<div class="section_child" :class="['border-custom']" :style="mapActivesToItems(1)['div.section_child']">
<template v-if="props.content">
<RecusiveSection :type="props?.content[1].type" :id="props?.content[1].data" :section="props.section" />
</template>
<template v-else>
<RecusiveSection :type="''" :id="''" :section="props.section" />
</template>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
$control-width: 80px;
.border-pri {
&.section_layout {
gap: 5px;
}
}
.section_layout {
display: flex;
.left {
width: 305px;
.tags {
display: flex;
align-items: center;
gap: 20px;
padding: 24px 0 20px 0;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
& > ul {
padding: 0;
margin: 0;
display: flex;
gap: 12px;
flex-wrap: wrap;
& > li {
list-style: none;
& > a {
display: block;
font-size: 12px;
line-height: 180%;
padding: 0 10px;
background-color: #f5efef;
border-radius: 6px;
}
}
}
}
.buttons {
margin: 20px 0;
display: flex;
justify-content: space-between;
align-items: center;
.actions,
.fontSize {
display: flex;
align-items: flex-end;
gap: 1.5rem;
}
}
.audio {
display: flex;
width: 100%;
height: 28px;
padding: 2px 12px 2px 2px;
border-radius: 999px;
background-color: #eee;
align-items: center;
.play {
margin-right: 16px;
}
.time {
line-height: 180%;
font-size: 12px;
color: #000;
margin-right: 6px;
}
.timeline {
flex: 1;
display: flex;
align-items: center;
}
}
input[type="range"] {
width: 100%;
height: 4px;
color: #4a4a4a;
--thumb-height: 1.125em;
--track-height: 0.125em;
--track-color: rgba(0, 0, 0, 0.2);
--brightness-hover: 180%;
--brightness-down: 80%;
--clip-edges: 0.125em;
}
/* === range commons === */
input[type="range"] {
position: relative;
background: #fff0;
overflow: hidden;
}
input[type="range"]:active {
cursor: grabbing;
}
input[type="range"]:disabled {
filter: grayscale(1);
opacity: 0.3;
cursor: not-allowed;
}
/* === WebKit specific styles === */
input[type="range"],
input[type="range"]::-webkit-slider-runnable-track,
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
transition: all ease 100ms;
height: var(--thumb-height);
}
input[type="range"]::-webkit-slider-runnable-track,
input[type="range"]::-webkit-slider-thumb {
position: relative;
}
input[type="range"]::-webkit-slider-thumb {
--thumb-radius: calc((var(--thumb-height) * 0.5) - 1px);
--clip-top: calc((var(--thumb-height) - var(--track-height)) * 0.5 - 0.5px);
--clip-bottom: calc(var(--thumb-height) - var(--clip-top));
--clip-further: calc(100% + 1px);
--box-fill: calc(-100vmax - var(--thumb-width, var(--thumb-height))) 0 0 100vmax currentColor;
width: var(--thumb-width, var(--thumb-height));
background: linear-gradient(#e50e0e 0 0) scroll no-repeat left center / 50% calc(var(--track-height) + 1px);
background-color: #e50e0e;
box-shadow: var(--box-fill);
border-radius: var(--thumb-width, var(--thumb-height));
filter: brightness(100%);
clip-path: polygon(100% -1px, var(--clip-edges) -1px, 0 var(--clip-top), -100vmax var(--clip-top), -100vmax var(--clip-bottom), 0 var(--clip-bottom), var(--clip-edges) 100%, var(--clip-further) var(--clip-further));
}
input[type="range"]:hover::-webkit-slider-thumb {
filter: brightness(var(--brightness-hover));
cursor: grab;
}
input[type="range"]:active::-webkit-slider-thumb {
filter: brightness(var(--brightness-down));
cursor: grabbing;
}
input[type="range"]::-webkit-slider-runnable-track {
background: linear-gradient(var(--track-color) 0 0) scroll no-repeat center / 100% calc(var(--track-height) + 1px);
}
input[type="range"]:disabled::-webkit-slider-thumb {
cursor: not-allowed;
}
/* === Firefox specific styles === */
// input[type="range"],
// input[type="range"]::-moz-range-track,
// input[type="range"]::-moz-range-thumb {
// appearance: none;
// transition: all ease 100ms;
// height: var(--thumb-height);
// }
// input[type="range"]::-moz-range-track,
// input[type="range"]::-moz-range-thumb,
// input[type="range"]::-moz-range-progress {
// background: #fff0;
// }
// input[type="range"]::-moz-range-thumb {
// background: currentColor;
// border: 0;
// width: var(--thumb-width, var(--thumb-height));
// border-radius: var(--thumb-width, var(--thumb-height));
// cursor: grab;
// }
// input[type="range"]:active::-moz-range-thumb {
// cursor: grabbing;
// }
// input[type="range"]::-moz-range-track {
// width: 100%;
// background: var(--track-color);
// }
// input[type="range"]::-moz-range-progress {
// appearance: none;
// background: currentColor;
// transition-delay: 30ms;
// }
// input[type="range"]::-moz-range-track,
// input[type="range"]::-moz-range-progress {
// height: calc(var(--track-height) + 1px);
// border-radius: var(--track-height);
// }
// input[type="range"]::-moz-range-thumb,
// input[type="range"]::-moz-range-progress {
// filter: brightness(100%);
// }
// input[type="range"]:hover::-moz-range-thumb,
// input[type="range"]:hover::-moz-range-progress {
// filter: brightness(var(--brightness-hover));
// }
// input[type="range"]:active::-moz-range-thumb,
// input[type="range"]:active::-moz-range-progress {
// filter: brightness(var(--brightness-down));
// }
// input[type="range"]:disabled::-moz-range-thumb {
// cursor: not-allowed;
// }
}
.content {
margin-left: $control-width;
flex: 1;
display: flex;
flex-direction: column;
&__top {
width: 100%;
}
&__bottom {
width: 100%;
min-height: 300px;
display: flex;
justify-content: space-between;
&__left {
flex: 1;
max-width: 660px;
display: block;
height: 100%;
position: relative;
&__control {
width: $control-width;
position: absolute;
height: 100%;
left: -$control-width;
&__sticky {
position: sticky;
top: 10px;
display: flex;
flex-direction: column;
gap: 16px;
& > span {
text-align: center;
}
}
}
}
&__right {
width: 300px;
}
}
}
}
</style>
@@ -0,0 +1,135 @@
<script setup lang="ts">
import RecusiveSection from "@/components/dynamic-page/page-section/RecusiveSection.vue";
import { getInputValue } from "@/utils/parseSQL";
import { enumPageSectionLayouts, enumPageSectionTemplate, enumPageSectionKey } from "~/definitions/enum";
import type { PageSection } from "@/server/models/dynamic-page/index";
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
const props = defineProps<{
layout?: string;
content?: any;
settings: any;
section: PageSection;
}>();
const defineTypeRecusive = {
COMPONENT: "component",
SECTION: "section",
};
const SETTING_OPTIONS = computed(() => {
let _setting_options = {
MAX_ELEMENT: 1,
};
return _setting_options;
});
const designObject = computed(() => {
// không truyn lable là ch lên s li
return props?.settings?.label ? getInputValue(props.settings.label, "OBJECT") : {};
});
const CLASS_FOR_SECTION = computed(() => {
let _classForSection = {
section_layout: "section_layout basic_column",
};
return _classForSection;
});
const handleActiveItem = (key: string, keyActive: string, index: number, defaultValue: any) => {
const designObject = props?.section?.settings?.label ? getInputValue(props.section.settings.label, "OBJECT") : {};
const updatedDesignObject = { ...designObject };
if (Array.isArray(designObject[keyActive])) {
const isActive = designObject[keyActive].includes(Number(index) + 1);
return {
...updatedDesignObject,
[key]: isActive ? designObject[key] : defaultValue,
};
}
delete updatedDesignObject[key];
return updatedDesignObject;
};
const LAYOUT_PARSE = computed(() => {
return props.settings?.label ? getInputValue(props.settings.label, "OBJECT") : {};
});
const mapActivesToItems = (index: number) => {
if (LAYOUT_PARSE.value && LAYOUT_PARSE.value.listCss) {
return LAYOUT_PARSE.value.listCss[index] || {};
}
return {};
};
</script>
<template>
<div
class="section_layout border-custom"
:class="[CLASS_FOR_SECTION.section_layout]"
:style="LAYOUT_PARSE['div.section_layout']"
>
<div
class="section_child"
v-for="(position, index) in props.content || Array(SETTING_OPTIONS.MAX_ELEMENT).fill({})"
:key="index"
:class="['border-custom', CLASS_FOR_SECTION[index]]"
:style="mapActivesToItems(index)['div.section_child']"
>
<RecusiveSection :type="position.type" :id="position.data" :section="props.section" />
</div>
</div>
</template>
<style lang="scss" scoped>
.border-pri {
&.section_layout {
gap: 5px;
}
}
.section_layout {
display: grid;
&.smartphone_layout {
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
}
&.basic_column {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
&.two_col_layout {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
&.three_col_layout {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
&.four_col_layout {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
&.five_col_layout {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
&.six_col_layout {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.col-span-2 {
grid-column: span 2 / span 2;
}
.col-span-3 {
grid-column: span 3 / span 3;
}
.col-span-4 {
grid-column: span 4 / span 4;
}
.col-span-5 {
grid-column: span 5 / span 5;
}
.border-custom {
border-color: #e5e5e5 !important;
}
}
</style>
@@ -1 +1,5 @@
export { default as NONE_DEFAULT_LAYOUT } from './none/Default.vue'
export { default as MISS_DEFAULT_LAYOUT } from './category/misses/Default.vue'
export { default as ARTICLE_DETAIL_DEFAULT } from './articles/details/Default.vue'
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { PageSection } from "@/models/cms";
import type { PageSection } from "@/server/models/dynamic-page/index";
import { enumPageSectionLayouts, enumPageSectionTemplate, enumPageSectionKey } from "@/definitions/enum";
import { NONE_DEFAULT_LAYOUT } from "./index";
import { NONE_DEFAULT_LAYOUT, MISS_DEFAULT_LAYOUT, ARTICLE_DETAIL_DEFAULT } from "./index";
const _props = defineProps<{
settings?: any;
@@ -11,30 +11,42 @@ const _props = defineProps<{
}>();
const definedDynamicSection: Record<string, any> = {
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_TWO']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_LEFT_TWO']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_RIGHT_TWO']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_THREE']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_FOUR']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_TWO']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_THREE']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_FOUR']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_ONE']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_TWO']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_THREE']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_FOUR']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_FIVE']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_SIX']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_SEVEN']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_EIGHT']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_NINE']]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_TEN']]: NONE_DEFAULT_LAYOUT,
/* NONE */
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_TWO"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_TWO_THREE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FIVE_THREE_TWO_TWO"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FIVE_TWO_TWO_THREE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_TWO_FIVE_THREE_TWO"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_FIVE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_FOUR"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_LEFT_TWO"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_RIGHT_TWO"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_THREE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FOUR"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_TWO"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_THREE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_FOUR"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_ONE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_TWO"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_THREE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_FOUR"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_FIVE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_SIX"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_SEVEN"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_EIGHT"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_NINE"]]: NONE_DEFAULT_LAYOUT,
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_TEN"]]: NONE_DEFAULT_LAYOUT,
/* SECTION */
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.CATEGORY]["MISSES"]]["DEFAULT"]]: MISS_DEFAULT_LAYOUT,
/** ARTICLE */
[enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.ARTICLE]["DETAIL"]]["DEFAULT"]]: ARTICLE_DETAIL_DEFAULT,
};
const getCurrentSection = computed(() => {
return _props.settings.layout
});
const GET_PROPS = computed(() => {
return () => {
let props: any = {};
@@ -52,7 +64,9 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component
v-if="definedDynamicSection[getCurrentSection]"
:is="definedDynamicSection[getCurrentSection]"
v-bind="{
...GET_PROPS(),
@@ -2,7 +2,9 @@
import RecusiveSection from "@/components/dynamic-page/page-section/RecusiveSection.vue";
import { getInputValue } from "@/utils/parseSQL";
import { enumPageSectionLayouts, enumPageSectionTemplate, enumPageSectionKey } from "~/definitions/enum";
import type { PageSection } from "@/models/cms";
import type { PageSection } from "@/server/models/dynamic-page/index";
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
const props = defineProps<{
layout?: string;
@@ -16,95 +18,130 @@ const defineTypeRecusive = {
SECTION: "section",
};
const designObject = computed(() => {
// không truyn lable là ch lên s li
return props?.settings?.label ? getInputValue(props.settings.label, "OBJECT") : {};
});
const SETTING_OPTIONS = computed(() => {
let _setting_options = {};
switch (props.layout) {
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_TWO']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_TWO"]:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_LEFT_TWO']:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_RIGHT_TWO']:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_THREE']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_TWO_THREE"]:
_setting_options = {
MAX_ELEMENT: 3,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_FOUR']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_FIVE"]:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FIVE_THREE_TWO_TWO"]:
_setting_options = {
MAX_ELEMENT: 4,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_TWO']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FIVE_TWO_TWO_THREE"]:
_setting_options = {
MAX_ELEMENT: 4,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_TWO_FIVE_THREE_TWO"]:
_setting_options = {
MAX_ELEMENT: 4,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_FOUR"]:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_LEFT_TWO"]:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_RIGHT_TWO"]:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_THREE"]:
_setting_options = {
MAX_ELEMENT: 3,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_THREE']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FOUR"]:
_setting_options = {
MAX_ELEMENT: 4,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_TWO"]:
_setting_options = {
MAX_ELEMENT: 3,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_FOUR']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_THREE"]:
_setting_options = {
MAX_ELEMENT: 3,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_ONE']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_FOUR"]:
_setting_options = {
MAX_ELEMENT: 3,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_ONE"]:
_setting_options = {
MAX_ELEMENT: 1,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_TWO']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_TWO"]:
_setting_options = {
MAX_ELEMENT: 2,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_THREE']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_THREE"]:
_setting_options = {
MAX_ELEMENT: 3,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_FOUR']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_FOUR"]:
_setting_options = {
MAX_ELEMENT: 4,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_FIVE']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_FIVE"]:
_setting_options = {
MAX_ELEMENT: 5,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_SIX']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_SIX"]:
_setting_options = {
MAX_ELEMENT: 6,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_SEVEN']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_SEVEN"]:
_setting_options = {
MAX_ELEMENT: 7,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_EIGHT']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_EIGHT"]:
_setting_options = {
MAX_ELEMENT: 8,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_NINE']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_NINE"]:
_setting_options = {
MAX_ELEMENT: 9,
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_TEN']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["HORIZONTAL_TEN"]:
_setting_options = {
MAX_ELEMENT: 10,
};
@@ -118,56 +155,109 @@ const SETTING_OPTIONS = computed(() => {
return _setting_options;
});
const designObject = computed(() => {
return props?.settings?.label ? getInputValue(props.settings.label, "OBJECT") : {};
});
const CLASS_FOR_SECTION = computed(() => {
let _classForSection = {};
switch (props.layout) {
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_TWO']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_TWO"]:
_classForSection = {
section_layout: "section_layout two_col_layout",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_LEFT_TWO']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_LEFT_TWO"]:
_classForSection = {
section_layout: "section_layout three_col_layout",
0: "col-span-2",
section_layout: "section_layout twelve_col_layout",
0: "md:col-span-8 col-span-12",
1: "md:col-span-4 col-span-12",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_RIGHT_TWO']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_RIGHT_TWO"]:
_classForSection = {
section_layout: "section_layout three_col_layout",
1: "col-span-2",
section_layout: "section_layout grid-cols-12",
0: "md:col-span-4 col-span-12",
1: "md:col-span-8 col-span-12",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_THREE']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_FIVE"]:
_classForSection = {
section_layout: "section_layout three_col_layout",
section_layout: "section_layout grid-cols-12",
0: "lg:col-span-2 lg:block hidden",
1: "lg:col-span-10 col-span-12",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_FOUR']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FIVE_THREE_TWO_TWO"]:
_classForSection = {
section_layout: "section_layout twelve_col_layout",
0: "col-span-5",
1: "col-span-3",
2: "col-span-2",
3: "col-span-2",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FIVE_TWO_TWO_THREE"]:
_classForSection = {
section_layout: "section_layout grid-cols-12",
0: "md:col-span-5 col-span-12",
1: "md:col-span-2 sm:col-span-6 col-span-12",
2: "md:col-span-2 sm:col-span-6 col-span-12",
3: "md:col-span-3 col-span-12",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_TWO_FIVE_THREE_TWO"]:
_classForSection = {
section_layout: "section_layout grid-cols-12",
0: "col-span-2 md:block hidden",
1: "md:col-span-5 sm:col-span-7 col-span-12",
2: "md:col-span-3 sm:col-span-5 col-span-12",
3: "col-span-2 md:block hidden",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_FOUR"]:
_classForSection = {
section_layout: "section_layout five_col_layout",
1: "col-span-4",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_THREE"]:
_classForSection = {
section_layout: "section_layout grid-cols-12",
0: "sm:col-span-4 col-span-12",
1: "sm:col-span-4 col-span-12",
2: "sm:col-span-4 col-span-12"
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_FOUR"]:
_classForSection = {
section_layout: "section_layout four_col_layout",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_TWO']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_TWO"]:
_classForSection = {
section_layout: "section_layout four_col_layout",
1: "col-span-2",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_THREE']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_THREE"]:
_classForSection = {
section_layout: "section_layout five_col_layout",
1: "col-span-3",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_CENTER_FOUR']:
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_CENTER_FOUR"]:
_classForSection = {
section_layout: "section_layout six_col_layout",
1: "col-span-4",
section_layout: "section_layout grid-cols-12",
0: "lg:col-span-2 md:col-span-6 col-span-12 lg:order-1 md:order-2 order-2",
1: "lg:col-span-8 md:col-span-12 col-span-12 lg:order-2 md:order-1 order-1",
2: "lg:col-span-2 md:col-span-6 col-span-12 lg:order-3 md:order-3 order-3",
};
break;
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]["NONE"]]["VERTICAL_ONE_TWO_THREE"]:
_classForSection = {
section_layout: "section_layout grid-cols-12",
0: "col-span-2 lg:block hidden",
1: "lg:col-span-4 md:col-span-5 col-span-12",
2: "lg:col-span-6 md:col-span-7 col-span-12",
};
break;
default:
@@ -179,6 +269,22 @@ const CLASS_FOR_SECTION = computed(() => {
return _classForSection;
});
const handleActiveItem = (key: string, keyActive: string, index: number, defaultValue: any) => {
const designObject = props?.section?.settings?.label ? getInputValue(props.section.settings.label, "OBJECT") : {};
const updatedDesignObject = { ...designObject };
if (Array.isArray(designObject[keyActive])) {
const isActive = designObject[keyActive].includes(Number(index) + 1);
return {
...updatedDesignObject,
[key]: isActive ? designObject[key] : defaultValue,
};
}
delete updatedDesignObject[key];
return updatedDesignObject;
};
const LAYOUT_PARSE = computed(() => {
return props.settings?.label ? getInputValue(props.settings.label, "OBJECT") : {};
});
@@ -186,7 +292,7 @@ const LAYOUT_PARSE = computed(() => {
const mapActivesToItems = (index: number) => {
if (LAYOUT_PARSE.value && LAYOUT_PARSE.value.listCss) {
return LAYOUT_PARSE.value.listCss[index] || {};
}
}
return {};
};
</script>
@@ -195,22 +301,48 @@ const mapActivesToItems = (index: number) => {
<div class="section_layout border-custom" :class="[CLASS_FOR_SECTION.section_layout]" :style="LAYOUT_PARSE['div.section_layout']">
<div
class="section_child"
v-for="(position, index) in props.content || Array(SETTING_OPTIONS.MAX_ELEMENT).fill({})"
v-for="(position, index) in props.content || Array(SETTING_OPTIONS?.MAX_ELEMENT).fill({})"
:key="index"
:class="['border-custom', CLASS_FOR_SECTION[index]]"
:style="mapActivesToItems(index)['div.section_child']"
@dragover.prevent
@drop.stop.prevent="dropPlacementInSection($event, index, position.data)"
>
<RecusiveSection :type="position.type" :id="position.data" :section="props.section" />
</div>
<div v-if="LAYOUT_PARSE['div.background']?.includes('display:block;')" class="attributeBackground" :style="LAYOUT_PARSE['div.background']"></div>
</div>
</template>
<style lang="scss" scoped>
.border-pri {
&.section_layout {
gap: 5px;
}
}
.section_layout {
display: grid;
gap: 5px;
position: relative;
.attributeBackground {
position: absolute;
height: 100%;
z-index: 0;
display: none;
left: -50vw;
width: 150vw;
}
.section_child {
z-index: 1;
}
.attributeBackground {
position: absolute;
height: 100%;
z-index: 0;
display: none;
left: -50vw;
width: 150vw;
}
&.smartphone_layout {
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
}
@@ -234,15 +366,57 @@ const mapActivesToItems = (index: number) => {
&.six_col_layout {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.col-span-2 {
grid-column: span 2 / span 2;
&.seven_col_layout {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.col-span-3 {
grid-column: span 3 / span 3;
&.eight_col_layout {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.col-span-4 {
grid-column: span 4 / span 4;
&.nine_col_layout {
grid-template-columns: repeat(9, minmax(0, 1fr));
}
&.ten_col_layout {
grid-template-columns: repeat(10, minmax(0, 1fr));
}
&.eleven_col_layout {
grid-template-columns: repeat(11, minmax(0, 1fr));
}
&.twelve_col_layout {
grid-template-columns: repeat(12, minmax(0, 1fr));
}
// .col-span-2 {
// grid-column: span 2 / span 2;
// }
// .col-span-3 {
// grid-column: span 3 / span 3;
// }
// .col-span-4 {
// grid-column: span 4 / span 4;
// }
// .col-span-5 {
// grid-column: span 5 / span 5;
// }
// .col-span-6 {
// grid-column: span 6 / span 6;
// }
// .col-span-7 {
// grid-column: span 7 / span 7;
// }
// .col-span-8 {
// grid-column: span 8 / span 8;
// }
// .col-span-9 {
// grid-column: span 9 / span 9;
// }
// .col-span-10 {
// grid-column: span 10 / span 10;
// }
// .col-span-11 {
// grid-column: span 11 / span 11;
// }
// .col-span-12 {
// grid-column: span 12 / span 12;
// }
.border-custom {
border-color: #e5e5e5 !important;
}
@@ -1,8 +1,6 @@
<script setup lang="ts">
import DynamicLayout from '@/components/dynamic-page/page-section/layouts/index.vue';
import type { PageSection } from "@/models/cms";
const emit = defineEmits(['dropComponent', 'dropData', 'selectComponent']);
import type { PageSection } from "@/server/models/dynamic-page/index";
const props = defineProps<{
label?: any
@@ -12,10 +10,6 @@ const props = defineProps<{
section: PageSection
}>()
const store = reactive({
section: usePageSectionStore(),
});
</script>
<template>
@@ -0,0 +1,25 @@
<script setup lang="ts">
import DynamicLayout from '@/components/dynamic-page/page-section/layouts/index.vue';
import type { PageSection } from "@/server/models/dynamic-page/index";
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
const props = defineProps<{
label?: any;
layout?: string;
settings?: any;
content?: any;
section: PageSection;
}>();
</script>
<template>
<DynamicLayout :layout="props.layout" :content="props.content" :settings="props.settings" :section="props.section" />
</template>
<style lang="scss" scoped>
.border-custom {
border-color: #e5e5e5 !important;
}
</style>
@@ -1 +1,2 @@
export { default as Article_Section_Default } from './Default.vue'
export { default as Article_Detail } from './Detail.vue'
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { Article_Section_Default } from "./index";
import type { PageSection } from "@/models/cms";
import { Article_Section_Default, Article_Detail } from "./index";
import type { PageSection } from "@/server/models/dynamic-page/index";
import { enumPageSectionKey, enumPageSectionTemplate } from "@/definitions/enum";
const _props = defineProps<{
@@ -10,7 +10,8 @@ const _props = defineProps<{
}>();
const definedDynamicSection: Record<string, any> = {
[enumPageSectionTemplate[enumPageSectionKey.ARTICLE]['DEFAULT']]: Article_Section_Default,
[`${enumPageSectionTemplate[enumPageSectionKey.ARTICLE]["DEFAULT"]}`]: Article_Section_Default,
[`${enumPageSectionTemplate[enumPageSectionKey.ARTICLE]["DETAIL"]}`]: Article_Detail,
};
const getCurrentSection = computed(() => _props.settings.template);
@@ -32,7 +33,11 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicSection[getCurrentSection]" v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }">
<component
v-if="definedDynamicSection[getCurrentSection]"
:is="definedDynamicSection[getCurrentSection]"
v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }"
>
<slot />
</component>
</template>
@@ -0,0 +1 @@
export { default as Misses_Section } from './misses/index.vue'
@@ -0,0 +1,43 @@
<script lang="ts" setup>
import type { PageSection } from "@/server/models/dynamic-page/index";
import { enumPageSectionKey, enumPageSectionTemplate } from "@/definitions/enum";
import { Misses_Section } from "./index";
const _props = defineProps<{
settings?: any;
content?: any;
section: PageSection;
}>();
const definedDynamicSection: Record<string, any> = {
[enumPageSectionTemplate[enumPageSectionKey.CATEGORY]['MISSES']]: Misses_Section,
};
const getCurrentSection = computed(() => _props.settings.template);
const GET_PROPS = computed(() => {
return () => {
let props: any = {};
if (_props.settings) {
for (const [key, value] of Object.entries(_props.settings)) {
props = {
...props,
[key]: value,
};
}
}
return props;
};
});
</script>
<template>
<component
v-if="definedDynamicSection[getCurrentSection]"
:is="definedDynamicSection[getCurrentSection]"
v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }"
>
<slot />
</component>
</template>
@@ -0,0 +1,43 @@
<script setup lang="ts">
import DynamicLayout from '@/components/dynamic-page/page-section/layouts/index.vue';
import type { PageSection } from "@/server/models/dynamic-page/index";
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
const props = defineProps<{
label?: any;
layout?: string;
settings?: any;
content?: any;
section: PageSection;
}>();
</script>
<template>
<section>
<div>
<div>
<div>
<img class="w-full " src="~/assets/images/tienphong/MissCategory.png" alt="">
</div>
<div class="mt-3">
<template v-if="props.layout">
<DynamicLayout
:layout="props.layout"
:content="props.content"
:settings="props.settings"
:section="props.section"
/>
</template>
</div>
</div>
</div>
</section>
</template>
<style lang="scss" scoped>
.border-custom {
border-color: #e5e5e5 !important;
}
</style>
@@ -0,0 +1 @@
export { default as Default } from './Default.vue'
@@ -0,0 +1,43 @@
<script lang="ts" setup>
import type { PageSection } from "@/server/models/dynamic-page/index";
import { enumPageSectionKey, enumPageSectionTemplate } from "@/definitions/enum";
import { Default } from "./index";
const _props = defineProps<{
settings?: any;
content?: any;
section: PageSection;
}>();
const definedDynamicSection: Record<string, any> = {
[enumPageSectionTemplate[enumPageSectionKey.CATEGORY]['MISSES']]: Default,
};
const getCurrentSection = computed(() => _props.settings.template);
const GET_PROPS = computed(() => {
return () => {
let props: any = {};
if (_props.settings) {
for (const [key, value] of Object.entries(_props.settings)) {
props = {
...props,
[key]: value,
};
}
}
return props;
};
});
</script>
<template>
<component
v-if="definedDynamicSection[getCurrentSection]"
:is="definedDynamicSection[getCurrentSection]"
v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }"
>
<slot />
</component>
</template>
@@ -1 +1,3 @@
export { default as None_Section } from './none/index.vue'
export { default as Category_Section } from './category/index.vue'
export { default as Article_Section } from './articles/index.vue'
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { PageSection } from "@/models/cms";
import type { PageSection } from "@/server/models/dynamic-page/index";
import { enumPageSectionKey } from "@/definitions/enum";
import { None_Section } from "./index";
import { None_Section,Category_Section, Article_Section } from "./index";
const _props = defineProps<{
settings?: any;
@@ -11,6 +11,8 @@ const _props = defineProps<{
const definedDynamicSection: Record<string, any> = {
[enumPageSectionKey.NONE]: None_Section,
[enumPageSectionKey.CATEGORY]: Category_Section,
[enumPageSectionKey.ARTICLE]: Article_Section,
};
const getCurrentSection = computed(() => {
@@ -34,7 +36,7 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicSection[getCurrentSection]" v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }">
<component v-if="definedDynamicSection[getCurrentSection]" :is="definedDynamicSection[getCurrentSection]" v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }">
<slot />
</component>
</template>
@@ -1,8 +1,6 @@
<script setup lang="ts">
import DynamicLayout from "@/components/dynamic-page/page-section/layouts/index.vue";
import type { PageSection } from "@/models/cms";
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
import type { PageSection } from "@/server/models/dynamic-page/index";
const props = defineProps<{
label?: any;
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { PageSection } from "@/models/cms";
import type { PageSection } from "@/server/models/dynamic-page/index";
import { enumPageSectionKey, enumPageSectionTemplate } from "@/definitions/enum";
import { None_Section_Default } from "./index";
@@ -32,7 +32,11 @@ const GET_PROPS = computed(() => {
</script>
<template>
<component :is="definedDynamicSection[getCurrentSection]" v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }">
<component
v-if="definedDynamicSection[getCurrentSection]"
:is="definedDynamicSection[getCurrentSection]"
v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }"
>
<slot />
</component>
</template>
@@ -1,78 +0,0 @@
<script setup lang="ts">
import { getInputValue } from "@/utils/parseSQL";
const props = defineProps<{
layout?: any,
label?:any
}>()
const CLASS_FOR_LAYOUT = computed(() => {
let _classForLayout = {};
switch (props.layout) {
case 'Full_Page':
_classForLayout = {
page_container: 'page_container full-size-page',
layout_container: 'layout_container full-size-layout',
};
break;
case 'Center_Page':
_classForLayout = {
page_container: 'page_container full-size-page',
layout_container: 'layout_container center-layout',
};
break;
case 'Background_Page':
_classForLayout = {
page_container: 'page_container full-size-page background-container',
layout_container: 'layout_container center-layout',
};
break;
default:
_classForLayout = {
page_container: 'page_container',
layout_container: 'layout_container',
};
break;
}
return _classForLayout;
})
const LAYOUT_PARSE = computed(() => {
return props?.label ? getInputValue(props.label, "OBJECT") : {};
});
</script>
<template>
<div :class="[CLASS_FOR_LAYOUT.page_container]" :style="LAYOUT_PARSE['div.page_container']">
<div :class="[CLASS_FOR_LAYOUT.layout_container]" class="grid-container">
<slot />
</div>
</div>
</template>
<style lang="scss" scoped>
.page_container {
&.full-size-page {
width: 100%;
}
.full-size-layout {
padding-left: 20px;
padding-right: 20px;
}
}
.layout_container {
padding-top: 40px;
padding-bottom: 40px;
&.center-layout {
max-width: 1300px;
margin: auto;
}
}
.grid-container {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
/* gap: 40px; */
}
</style>
@@ -0,0 +1,76 @@
<script setup lang="ts">
import { getInputValue } from "@/utils/parseSQL";
import { enumPageLayouts, enumPageTemplate, enumPageKey } from "@/definitions/enum";
const props = defineProps<{
layout?: any,
label?:any
}>()
const CLASS_FOR_LAYOUT = computed(() => {
let _classForLayout = {};
switch (props.layout) {
case enumPageLayouts[enumPageTemplate[enumPageKey.HOME]['DEFAULT']]['DEFAULT']:
_classForLayout = {
page_container: "page_container full-size-page",
layout_container: "layout_container center-layout",
};
break;
case enumPageLayouts[enumPageTemplate[enumPageKey.HOME]['DEFAULT']]['FULL']:
_classForLayout = {
page_container: "page_container full-size-page",
layout_container: "layout_container full-size-layout",
};
break;
case enumPageLayouts[enumPageTemplate[enumPageKey.HOME]['DEFAULT']]['BACKGROUND_PAGE']:
_classForLayout = {
page_container: "page_container full-size-page background-container",
layout_container: "layout_container center-layout",
};
break;
default:
_classForLayout = {
page_container: "page_container",
layout_container: "layout_container",
};
break;
}
return _classForLayout;
});
</script>
<template>
<div :class="[CLASS_FOR_LAYOUT.page_container]">
<div :class="[CLASS_FOR_LAYOUT.layout_container]" class="grid-container">
<slot />
</div>
</div>
</template>
<style lang="scss" scoped>
.page_container {
// padding: 20px 0;
&.full-size-page {
width: 100%;
}
// .full-size-layout {
// padding-left: 20px;
// padding-right: 20px;
// }
}
.layout_container {
padding-top: 20px;
&.center-layout {
max-width: 1440px;
padding: 0 27.5px;
margin: auto;
}
}
.grid-container {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
}
</style>
@@ -1,4 +1,4 @@
export { default as BASE_LAYOUT } from './Default.vue'
export { default as Home_Default } from './homes/Default.vue'
// Article
export { default as ARTICLE_LONG_LAYOUT } from './articles/Long.vue'
+30 -35
View File
@@ -1,50 +1,45 @@
<script lang="ts" setup>
import { layouts } from "@/definitions/enum";
import {
BASE_LAYOUT,
ARTICLE_SHORT_LAYOUT,
ARTICLE_PAGE_LAYOUT,
ARTICLE_NORMAL_LAYOUT,
ARTICLE_NONE_LAYOUT,
ARTICLE_LONG_LAYOUT,
} from './index';
import { enumPageKey, enumPageTemplate, enumPageLayouts } from "@/definitions/enum";
import { Home_Default, ARTICLE_LONG_LAYOUT, ARTICLE_NONE_LAYOUT, ARTICLE_NORMAL_LAYOUT, ARTICLE_PAGE_LAYOUT, ARTICLE_SHORT_LAYOUT } from "./index";
const _props = defineProps<{
settings?: any,
}>()
settings?: any;
}>();
const definedDynamicPageLayout: Record<string, any> = {
'Default': BASE_LAYOUT,
[layouts.FULL_PAGE]: BASE_LAYOUT,
[layouts.CENTER_PAGE]: BASE_LAYOUT,
[layouts.BACKGROUND_PAGE]: BASE_LAYOUT,
[enumPageLayouts[enumPageTemplate[enumPageKey.HOME]["DEFAULT"]]["DEFAULT"]]: Home_Default,
[enumPageLayouts[enumPageTemplate[enumPageKey.HOME]["DEFAULT"]]["FULL"]]: Home_Default,
[enumPageLayouts[enumPageTemplate[enumPageKey.HOME]["DEFAULT"]]["BACKGROUND_PAGE"]]: Home_Default,
'ARTICLE_SHORT': ARTICLE_SHORT_LAYOUT,
'ARTICLE_PAGE': ARTICLE_PAGE_LAYOUT,
'ARTICLE_NORMAL': ARTICLE_NORMAL_LAYOUT,
'ARTICLE_NONE': ARTICLE_NONE_LAYOUT,
'ARTICLE_LONG': ARTICLE_LONG_LAYOUT,
}
[enumPageLayouts[enumPageTemplate[enumPageKey.ARTICLE]["DETAIL"]]["ARTICLE_SHORT"]]: ARTICLE_SHORT_LAYOUT,
[enumPageLayouts[enumPageTemplate[enumPageKey.ARTICLE]["DETAIL"]]["ARTICLE_PAGE"]]: ARTICLE_PAGE_LAYOUT,
[enumPageLayouts[enumPageTemplate[enumPageKey.ARTICLE]["DETAIL"]]["ARTICLE_NORMAL"]]: ARTICLE_NORMAL_LAYOUT,
[enumPageLayouts[enumPageTemplate[enumPageKey.ARTICLE]["DETAIL"]]["ARTICLE_NONE"]]: ARTICLE_NONE_LAYOUT,
[enumPageLayouts[enumPageTemplate[enumPageKey.ARTICLE]["DETAIL"]]["ARTICLE_LONG"]]: ARTICLE_LONG_LAYOUT,
};
const getCurrentLayout = computed(() => _props.settings.layout);
const getCurrentLayout = computed(() => _props.settings?.layout);
const GET_PROPS = computed(() => {
return () => {
let props: any = {};
for (const [key, value] of Object.entries(_props.settings)) {
props = {
...props,
[key]: value
}
}
return props;
};
})
return () => {
let props: any = {};
for (const [key, value] of Object.entries(_props.settings)) {
props = {
...props,
[key]: value,
};
}
return props;
};
});
</script>
<template>
<component :is="definedDynamicPageLayout[getCurrentLayout]" v-bind="GET_PROPS()">
<component
v-if="definedDynamicPageLayout[getCurrentLayout]"
:is="definedDynamicPageLayout[getCurrentLayout]"
v-bind="GET_PROPS()"
>
<slot />
</component>
</template>
@@ -9,7 +9,7 @@ const props = defineProps<{
</script>
<template>
<div class="h-100 overflow-y-auto">
<div class="h-full overflow-y-auto">
<HeaderHomeTemplate />
<DynamicLayout :settings="props.settings">
<slot />
@@ -0,0 +1 @@
export { default as DetailDefault } from './DetailDefault.vue';
@@ -0,0 +1,37 @@
<script lang="ts" setup>
import { DetailDefault } from './index';
import { enumPageKey, enumPageTemplate } from "@/definitions/enum";
const _props = defineProps<{
settings: any
}>()
const definedDynamicPage: Record<string, any> = {
[enumPageTemplate[enumPageKey.ARTICLE]['DETAIL']]: DetailDefault,
}
const getCurrentTemplate = computed(() => {
return _props.settings?.template || '';
});
const GET_PROPS = computed(() => {
return () => {
let props : any = {};
if (_props.settings) {
for (const [key, value] of Object.entries(_props.settings)) {
props = {
...props,
[key]: value
}
}
}
return props;
};
})
</script>
<template>
<component v-if="definedDynamicPage[getCurrentTemplate]" :is="definedDynamicPage[getCurrentTemplate]" v-bind="{...(GET_PROPS()), settings: _props.settings}">
<slot />
</component>
</template>
@@ -4,57 +4,56 @@ import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentK
</script>
<template>
<footer id="footer" class="main-footer">
<footer id="footer" class="main-footer mt-20">
<div class="main-footer-container">
<div class="footer-centertab">
<div>
<div class="footer-navigation-container">
<div class="footer-navigation-container md:block hidden">
<div>
<AssignComponent :type="enumPageComponentLayouts[[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['BOTTOM']}`]]['NAVIGATION_BOTTOM_DEFAULT']" />
<AssignComponent :type="enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['BOTTOM']}`]['NAVIGATION_BOTTOM_DEFAULT']" />
</div>
</div>
<div class="footer-contact-container">
<div>
<h3>BÁO GIẤY</h3>
<div class="footer-contact-container md:grid-cols-2 grid-cols-1">
<div class="md:order-1 order-2">
<h3 class="md:text-left text-center">BÁO GIẤY</h3>
<div class="footer-contact">
<div>
<div class="md:justify-start justify-center">
<div>
<span><Icon name="material-symbols:call-outline" /></span>
<span>024.39434341</span>
<span>0123456789</span>
</div>
<div>
<span><Icon name="material-symbols:mail-outline" /></span>
<span>toasoan@baotienphong.vn</span>
<span>toasoan@vpress.vn</span>
</div>
</div>
<div class="footer-contact-buynow">
<div class="footer-contact-buynow md:justify-start justify-center">
<img src="~/assets/images/tienphong/muabaogiay-footer.png" alt="" />
</div>
</div>
</div>
<div>
<h3>THEO DÕI TRÊN MẠNG HỘI</h3>
<div class="md:order-2 order-1">
<h3 class="md:text-left text-center md:!border-#3d3d3d !border-transparent">THEO DÕI TRÊN MẠNG HỘI</h3>
<div class="footer-socials">
<div>
<div>
<div class="md:grid lg:grid-cols-2 md:grid-cols-1 flex md:gap-50px gap-32px md:justify-start justify-center">
<div class="xl:gap-22px gap-6px">
<span><Icon name="ic:baseline-facebook" /></span>
<span>facebook.com/baotienphong</span>
<span class="md:inline hidden whitespace-normal">facebook.com/vpress</span>
</div>
<div>
<div class="xl:gap-22px gap-6px">
<span><Icon name="uil:youtube" /></span>
<span>youtube.com/baotienphong</span>
<span class="md:inline hidden whitespace-normal">youtube.com/vpress</span>
</div>
<div>
<div class="xl:gap-22px gap-6px">
<span><Icon name="ic:baseline-tiktok" /></span>
<span>tiktok.com/baotienphong</span>
<span class="md:inline hidden whitespace-normal">tiktok.com/vpress</span>
</div>
<div>
<div class="xl:gap-22px gap-6px">
<span><Icon name="arcticons:zalo" /></span>
<span>zalo.vn/baotienphong</span>
<span class="md:inline hidden whitespace-normal">zalo.vn/vpress</span>
</div>
</div>
</div>
@@ -62,19 +61,19 @@ import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentK
</div>
<div class="footer-ads">
<h3>LIÊN HỆ QUẢNG CÁO</h3>
<h3 class="md:text-left text-center">LIÊN HỆ QUẢNG CÁO</h3>
<div class="footer-ads-container">
<div class="footer-ads-container flex-wrap lg:gap-60px gap-20px md:flex-row flex-col">
<div class="align-items-center">
<span><Icon name="material-symbols:call-outline" /></span>
<span class="flex flex-column">
<span>024.39434340</span>
<span>0909559988</span>
<span>0123456789 - </span>
<span>0123456789</span>
</span>
</div>
<div>
<span><Icon name="material-symbols:mail-outline" /></span>
<span>kinhdoanh@baotienphong.vn</span>
<span>kinhdoanh@vpress.vn</span>
</div>
<div class="button-ads-price">
<span><Icon name="ic:outline-format-list-bulleted" /></span>
@@ -82,41 +81,36 @@ import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentK
</div>
</div>
</div>
</div>
</div>
</div>
<div class="footer-bottomtab">
<div>
<div>
<div class="flex justify-between items-end">
<div class="w-1/2">
<div class="flex justify-between items-end">
<div class="flex-1 flex md:justify-start justify-center">
<div>
<img src="~/assets/images/tienphong/logo-footer.png" alt="" />
<img class="w-200px" src="~/assets/images/tienphong/logo.png" alt="" />
</div>
</div>
<div class="footer-bottomtab-relations w-1/2">
<img src="~/assets/images/tienphong/sinhvien-logo.png" alt="" />
<div class="footer-bottomtab-relations">
<!-- <img src="~/assets/images/tienphong/sinhvien-logo.png" alt="" />
<img src="~/assets/images/tienphong/hht-online-logo.png" alt="" />
<img src="~/assets/images/tienphong/tamviet-logo.png" alt="" />
<img src="~/assets/images/tienphong/tamviet-logo.png" alt="" /> -->
</div>
</div>
<div class="flex justify-between items-end footer-bottomtab-description">
<div class="w-1/2">
<p>© Bản quyền thuộc báo điện tử <span class="fw-bold">Tiền Phong</span></p>
<p><span class="fw-bold">Tổng Biên tập</span>: XUÂN SƠN</p>
<p>Tòa soạn: 15 Hồ Xuân Hương, Nội - Điện thoại: 024.39431250</p>
<p>Email: <span class="fw-bold">online.baotienphong@gmail.com</span> - Hotline: <span class="fw-bold">0865.015.015 - 0977.456.112</span></p>
<div class="flex justify-between md:flex-row flex-col gap-20px footer-bottomtab-description md:text-left text-center">
<div class="flex-1 space-y-10px">
<p>© Bản quyền thuộc báo điện tử <span class="fw-bold">....</span></p>
<p><span class="fw-bold">Tổng Biên tập</span>: ...</p>
<p>Tòa soạn: Nội - Điện thoại: 0123456789</p>
<p>Email: <span class="fw-bold">online.vpress@gmail.com</span> - Hotline: <span class="fw-bold">0123456789</span></p>
</div>
<div class="w-1/2">
<p>Giấy phép số <span class="fw-bold">76/GP-BTTTT</span>, cấp ngày 26/02/2020.</p>
<p> quan chủ quản: <span class="fw-bold">Trung ương Đoàn TNCS Hồ Chí Minh</span></p>
<div class="flex-1 space-y-10px">
<p>Giấy phép số <span class="fw-bold">...</span>, cấp ngày ....</p>
<p> quan chủ quản: <span class="fw-bold">....</span></p>
<p>Cấm sao chép dưới mọi hình thức nếu không sự chấp thuận bằng văn bản</p>
<p>Powered by Hemera Media</p>
<p>Powered by VPress</p>
</div>
</div>
</div>
</div>
</div>
</footer>
</template>
@@ -125,16 +119,17 @@ import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentK
.main-footer {
.main-footer-container {
background: #2b2b2b;
padding: 54px 15px 20px 15px;
padding: 54px 0 32px 0;
}
.footer-centertab {
margin: 0 auto;
max-width: 1385px;
max-width: 1440px;
padding: 0 27.5px;
> div {
max-width: 1145px;
margin-left: auto;
padding: 0 33px;
// max-width: 1145px;
// margin-left: auto;
// padding: 0 33px;
}
.footer-navigation-container {
@@ -147,7 +142,7 @@ import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentK
.footer-contact-container {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
// grid-template-columns: repeat(2, minmax(0, 1fr));
margin-top: 22px;
gap: 60px;
border-bottom: 1px solid #ed1c24;
@@ -189,13 +184,13 @@ import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentK
.footer-socials {
margin-top: 20px;
> div {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 55px;
// display: grid;
// grid-template-columns: repeat(2, minmax(0, 1fr));
// gap: 55px;
> div {
display: flex;
align-items: flex-end;
gap: 22px;
// gap: 22px;
}
span {
color: #ffffff;
@@ -213,7 +208,7 @@ import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentK
> div {
display: flex;
align-items: center;
gap: 60px;
// gap: 60px;
> div {
display: flex;
align-items: flex-end;
@@ -262,18 +257,19 @@ import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentK
.footer-bottomtab {
width: 100%;
padding: 5px 15px;
// padding: 5px 15px;
> div {
margin: 0 auto;
max-width: 1385px;
max-width: 1440px;
padding: 0 27.5px;
> div {
max-width: 1145px;
margin-left: auto;
padding: 0 33px;
// max-width: 1145px;
// margin-left: auto;
// padding: 0 33px;
}
}
padding: 0 33px;
// padding: 0 33px;
padding-bottom: 80px;
padding-top: 24px;
.footer-bottomtab-relations {
@@ -8,61 +8,61 @@ const currentDateTime = ref<string>("");
onMounted(() => {
currentDateTime.value = dayjs().format("dddd, DD/MM/YYYY, HH:mm");
});
const showMenuMobile = ref(false)
</script>
<template>
<header id="header" class="main-header">
<div class="main-header-topbar ">
<div class="flex items-center">
<time class="text-capitalize text-white" :datetime="currentDateTime">{{ currentDateTime }} GMT+7</time>
<div class="main-header-topbar-listag">
</div>
<div class="main-header-topbar lg:block hidden">
<div class="flex items-center !py-3px">
<time class="text-capitalize text-white text-12px" :datetime="currentDateTime">{{ currentDateTime }} GMT+7</time>
<div class="main-header-topbar-listag"></div>
<div class="main-header-topbar-infor flex items-center">
<div>
<div class="text-white">
<div class="text-white text-12px">
<span>HOTLINE: </span>
<a href="tel:0865015015"> 0865.015.015</a>
<a href="tel:0865015015"> 0123456789</a>
<span> - </span>
<a href="tel:0977456112">0977.456.112</a>
<a href="tel:0977456112">0123456789</a>
</div>
</div>
<div>
<a href=""><Icon class="" name="ic:baseline-facebook" /></a>
<a href=""><Icon class="" name="ion:logo-youtube" /></a>
<div class="flex items-center">
<a class="flex" href=""><Icon class="text-12px" name="ic:baseline-facebook" /></a>
<a class="flex" href=""><Icon class="" name="ion:logo-youtube" /></a>
</div>
</div>
</div>
</div>
<div class="main-header-centerbar">
<div class="main-header-centerbar lg:block hidden">
<div class="flex items-center">
<div class="main-header-logo">
<img src="~/assets/images/tienphong/logo.png" alt="logo">
<nuxt-link to="/"><img class="w-200px" src="~/assets/images/tienphong/logo.png" alt="logo" /></nuxt-link>
</div>
<div class="main-header-control">
<div class="main-header-relations">
<img src="~/assets/images/tienphong/sinhvien-logo.png" alt="">
<!-- <img src="~/assets/images/tienphong/sinhvien-logo.png" alt="">
<img src="~/assets/images/tienphong/hht-online-logo.png" alt="">
<img src="~/assets/images/tienphong/tamviet-logo.png" alt="">
<img src="~/assets/images/tienphong/tamviet-logo.png" alt=""> -->
</div>
<div class="main-header-navbar">
<AssignComponent :type="enumPageComponentLayouts[[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']}`]]['NAVIGATION_TOP_DEFAULT']" />
<AssignComponent :type="enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']}`]['NAVIGATION_TOP_DEFAULT']" />
</div>
</div>
</div>
</div>
<div class="main-ads">
<div class="main-ads-container">
<div class="main-ads lg:block hidden">
<div class="main-ads-container py-20px border-y-1px border-#000">
<div class="main-ads-content">
<img src="~/assets/images/tienphong/main-ads.png" alt="">
<img src="~/assets/images/tienphong/main-ads.png" alt="" />
</div>
</div>
</div>
<div class="main-header-control">
<div class="main-header-control lg:block hidden">
<div class="header-control-container">
<div>
<div class="border-b-1px border-#000 py-2">
<div class="header-control_tag">
<h5>Xu hướng</h5>
<label>Xu hướng</label>
</div>
<div class="header-control_article">
<h4>Thư du học sinh: Hoa Học Trò cánh cửa thần kỳ dẫn tớ đến thế giới rộng lớn hơn</h4>
@@ -72,22 +72,67 @@ onMounted(() => {
<Icon name="material-symbols-light:search" />
</div>
<div>
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSwdIVSqaMsmZyDbr9mDPk06Nss404fosHjLg&s" width="40" height="40" class="rounded-circle" alt="">
<img src="http://picsum.photos/1024/600?random=1" width="40" height="40" class="rounded-circle" alt="" />
</div>
</div>
</div>
</div>
</div>
<div class="main-header-mobile px-3 lg:hidden block">
<div class="flex items-center justify-between py-4 border-b-1px border-solid border-#000">
<div class="main-header-mobile-logo">
<nuxt-link to="/"><img class="w-128px" src="~/assets/images/tienphong/logo.png" alt="logo" /></nuxt-link>
</div>
<div class="main-header-mobile-control flex gap-20px">
<button class="bg-transparent" @click="showMenuMobile = true">
<svg width="21" height="17" viewBox="0 0 21 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M0 0.75C0 0.375 0.328125 0 0.75 0H20.25C20.625 0 21 0.375 21 0.75C21 1.17188 20.625 1.5 20.25 1.5H0.75C0.328125 1.5 0 1.17188 0 0.75ZM0 8.25C0 7.875 0.328125 7.5 0.75 7.5H20.25C20.625 7.5 21 7.875 21 8.25C21 8.67188 20.625 9 20.25 9H0.75C0.328125 9 0 8.67188 0 8.25ZM20.25 16.5H0.75C0.328125 16.5 0 16.1719 0 15.75C0 15.375 0.328125 15 0.75 15H20.25C20.625 15 21 15.375 21 15.75C21 16.1719 20.625 16.5 20.25 16.5Z"
fill="black"
/>
</svg>
</button>
<button class="bg-transparent">
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.7656 22.7344L17.1094 16.125C18.6094 14.3906 19.4531 12.1875 19.4531 9.75C19.4531 4.40625 15.0469 0 9.70312 0C4.3125 0 0 4.40625 0 9.75C0 15.1406 4.35938 19.5 9.70312 19.5C12.0938 19.5 14.2969 18.6562 16.0312 17.1562L22.6406 23.8125C22.8281 23.9531 23.0156 24 23.25 24C23.4375 24 23.625 23.9531 23.7656 23.8125C24.0469 23.5312 24.0469 23.0156 23.7656 22.7344ZM9.75 18C5.15625 18 1.5 14.2969 1.5 9.75C1.5 5.20312 5.15625 1.5 9.75 1.5C14.2969 1.5 18 5.20312 18 9.75C18 14.3438 14.2969 18 9.75 18Z" fill="black"/>
</svg>
</button>
</div>
</div>
</div>
<!-- navigation header -->
<div class="main-header-mobile-navigation fixed inset-0 bg-white z-999" v-if="showMenuMobile">
<div class="main-header-mobile px-3">
<div class="flex items-center justify-between py-4">
<div class="main-header-mobile-logo" @click="showMenuMobile = false">
<nuxt-link to="/"><img class="w-128px" src="~/assets/images/tienphong/logo.png" alt="logo" /></nuxt-link>
</div>
<div class="main-header-mobile-control flex gap-20px">
<button class="bg-transparent" @click="showMenuMobile = false">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275t.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275z"/></svg>
</button>
</div>
</div>
</div>
<div class="p-3">
<AssignComponent :type="enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']}`]['NAVIGATION_TOP_DEFAULT']" />
</div>
</div>
</header>
</template>
<style lang="scss" scoped>
<style lang="scss">
.main-header {
.main-header-topbar {
background: #ED1C24;
>div {
background: #ed1c24;
> div {
max-width: 1385px;
padding: 5px 15px;
padding: 5px 20px;
margin: 0 auto;
justify-content: space-between;
}
@@ -110,9 +155,9 @@ onMounted(() => {
.main-header-centerbar {
padding: 20px 0px;
> div {
max-width: 1385px;
max-width: 1440px;
margin: 0 auto;
padding: 0px 15px;
padding: 0px 27.5px;
}
.main-header-control {
flex: 1;
@@ -128,15 +173,17 @@ onMounted(() => {
}
}
.main-ads {
max-width: 1385px;
max-width: 1440px;
margin: 0px auto;
padding: 20px 0px;
border-top: 1px solid #000000;
border-bottom: 1px solid #000000;
padding: 0px 27.5px;
// border-top: 1px solid #000000;
// border-bottom: 1px solid #000000;
.main-ads-content {
width: 970px;
height: 250px;
width: 100%;
max-width: 970px;
// height: 250px;
text-align: center;
display: flex;
flex-direction: column;
@@ -147,10 +194,10 @@ onMounted(() => {
}
.main-header-control {
.header-control-container {
max-width: 1385px;
max-width: 1440px;
margin: 0px auto 10px auto;
padding: 10px 0px;
border-bottom: 1px solid #000000;
padding: 0 27.5px;
// border-bottom: 1px solid #000000;
> div {
display: flex;
align-items: center;
@@ -162,26 +209,29 @@ onMounted(() => {
.header-control {
&_tag {
position: relative;
h5 {
font-size: 12px;
height: 30px;
label {
font-size: 12px;
text-transform: uppercase;
padding: 4px 12px 0 12px;
height: 100%;
margin: 0;
border: 1px solid #000;
line-height: 1.8;
font-weight: 300;
padding: 0 16px 0 12px;
display: flex;
align-items: center;
height: 100%;
margin: 0;
border: 1px solid #000;
line-height: 1.8;
font-weight: 300;
}
&::after {
position: absolute;
content: "";
display: block;
width: 12px;
height: 100%;
background-color: #ed1c24;
left: -12px;
top: 0;
}
&::after {
position: absolute;
content: "";
display: block;
width: 12px;
height: 100%;
background-color: #ed1c24;
left: -12px;
top: 0;
}
}
&_article {
flex: 1;
@@ -194,12 +244,59 @@ onMounted(() => {
&_control {
display: flex;
align-items: center;
gap: 10px;
gap: 20px;
svg {
font-size: 30px;
font-size: 36px;
}
img {
width: 32px;
height: 32px;
object-fit: cover;
border-radius: 999px;
}
}
}
}
.main-header-mobile-navigation {
& > div > nav > div {
justify-content: flex-start;
}
& .navigation-container {
flex-direction: column;
gap: 0px;
width: 100%;
align-items: start;
& > .navigation-branch {
width: 100%;
border-top: 1px solid #dee2e6;
&:first-child {
border: transparent;
}
& > .navigation-item {
width: 100%;
height: 100%;
& > a {
font-family: "Gelasio", serif !important;
text-transform: uppercase;
font-weight: 800!important;
display: block;
font-size: 16px;
padding: 8px 0;
width: 100%;
height: 100%;
}
}
}
}
}
}
</style>
</style>
@@ -2,22 +2,19 @@
import DynamicLayout from "~/components/dynamic-page/page/layouts/index.vue";
import HeaderHomeTemplate from "~/components/dynamic-page/page/templates/components/headers/HeaderHomeTemplate.vue";
import FooterHomeTemplate from "~/components/dynamic-page/page/templates/components/footers/FooterHomeTemplate.vue";
const props = defineProps<{
settings?: any;
}>();
const props = defineProps<{
settings?: any
}>()
</script>
<template>
<div>
<HeaderHomeTemplate>
<DynamicLayout :settings="props.settings">
<div class="h-full overflow-y-auto">
<HeaderHomeTemplate />
<DynamicLayout :settings="props.settings">
<slot />
</DynamicLayout>
</HeaderHomeTemplate>
<DynamicLayout :settings="props.settings">
<slot />
</DynamicLayout>
<FooterHomeTemplate />
</div>
<FooterHomeTemplate />
</div>
</template>
@@ -0,0 +1 @@
export { default as Home } from './Home.vue';
@@ -0,0 +1,37 @@
<script lang="ts" setup>
import { Home } from './index';
import { enumPageKey, enumPageTemplate } from "@/definitions/enum";
const _props = defineProps<{
settings: any
}>()
const definedDynamicPage: Record<string, any> = {
[enumPageTemplate[enumPageKey.HOME]['DEFAULT']]: Home,
}
const getCurrentTemplate = computed(() => {
return _props.settings?.template || '';
});
const GET_PROPS = computed(() => {
return () => {
let props : any = {};
if (_props.settings) {
for (const [key, value] of Object.entries(_props.settings)) {
props = {
...props,
[key]: value
}
}
}
return props;
};
})
</script>
<template>
<component v-if="definedDynamicPage[getCurrentTemplate]" :is="definedDynamicPage[getCurrentTemplate]" v-bind="{...(GET_PROPS()), settings: _props.settings}">
<slot />
</component>
</template>

Some files were not shown because too many files have changed in this diff Show More