feat: new layout
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { enumPageComponentTemplates, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { enumPageComponentLayouts, enumPageComponentTemplate, enumPageComponentKey } from "@/definitions/enum";
|
||||
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
|
||||
import { useDynamicPageStore } from '~/stores/dynamic-page';
|
||||
const { currentPage } = storeToRefs(useDynamicPageStore());
|
||||
@@ -7,17 +7,9 @@ const props = defineProps<{
|
||||
type: string; // [TOP_NAVIGATION, BOTTOM_NAVIGATION]
|
||||
}>();
|
||||
|
||||
// const store = reactive({
|
||||
// page: useCmsPageStore(),
|
||||
// section: usePageSectionStore(),
|
||||
// component: usePageComponentStore(),
|
||||
// });
|
||||
|
||||
// const { currentPage } = storeToRefs(useCmsPageStore());
|
||||
|
||||
const defineTypeRecusive = {
|
||||
TOP_NAVIGATION: enumPageComponentLayouts[enumPageComponentTemplates.NAVIGATION]['NAVIGATION-TOP'],
|
||||
BOTTOM_NAVIGATION: enumPageComponentLayouts[enumPageComponentTemplates.NAVIGATION]['NAVIGATION-BOTTOM'],
|
||||
TOP_NAVIGATION: enumPageComponentLayouts[[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']}`]]['NAVIGATION_TOP_DEFAULT'],
|
||||
BOTTOM_NAVIGATION: enumPageComponentLayouts[[`${enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['BOTTOM']}`]]['NAVIGATION_BOTTOM_DEFAULT'],
|
||||
};
|
||||
|
||||
const findDataPosition = computed(() => {
|
||||
@@ -25,12 +17,12 @@ const findDataPosition = computed(() => {
|
||||
switch (props.type) {
|
||||
case defineTypeRecusive.TOP_NAVIGATION:
|
||||
result = currentPage.value.components && currentPage.value.components.find((component: any) => {
|
||||
return component.settings?.template === enumPageComponentTemplates.NAVIGATION && component.settings?.layout === defineTypeRecusive.TOP_NAVIGATION
|
||||
return component.taxonomy === enumPageComponentKey.NAVIGATION && component.settings?.layout === defineTypeRecusive.TOP_NAVIGATION
|
||||
});
|
||||
break;
|
||||
case defineTypeRecusive.BOTTOM_NAVIGATION:
|
||||
result = currentPage.value.components && currentPage.value.components.find((component: any) => {
|
||||
return component.settings && component.settings?.template === enumPageComponentTemplates.NAVIGATION && component.settings?.layout === defineTypeRecusive.BOTTOM_NAVIGATION
|
||||
return component.taxonomy === enumPageComponentKey.NAVIGATION && component.settings?.layout === defineTypeRecusive.BOTTOM_NAVIGATION
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default as ADS_Default } from './layouts/Default.vue';
|
||||
@@ -1,12 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import ADSDefault from '@/assets/images/ads.jpg'
|
||||
</script>
|
||||
<template>
|
||||
<div class="shadow sticky top-10px">
|
||||
<img :src="ADSDefault" alt="quảng cáo" class=" object-cover">
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts"></script>
|
||||
<template>
|
||||
<div class="content">
|
||||
<span>Quảng cáo ở đây</span>
|
||||
</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 @@
|
||||
export { default as Default_Ads } from './Default.vue'
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Default_Ads } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.ADVERTISING]['ADVERTISING']}`]['DEFAULT']]: Default_Ads,
|
||||
};
|
||||
|
||||
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
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Advertisings } from './advertisings/index.vue'
|
||||
@@ -1,17 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
import { ADS_Default
|
||||
} from "./index";
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Advertisings } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
'DEFAULT': ADS_Default,
|
||||
[enumPageComponentTemplate[enumPageComponentKey.ADVERTISING]["ADVERTISING"]]: Advertisings,
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
|
||||
console.log(definedDynamicComponent, "vào rrassd", getCurrentComponent.value);
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
let props: any = {};
|
||||
@@ -29,5 +30,5 @@ const GET_PROPS = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="GET_PROPS()" />
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
|
||||
</template>
|
||||
@@ -0,0 +1,178 @@
|
||||
<script setup lang="ts">
|
||||
const type = ref("");
|
||||
import { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
import { DEFAULT_QUERY_DROP } from "@/utils/parseSQL";
|
||||
import { getInputValue } from "@/utils/parseSQL";
|
||||
|
||||
const props = defineProps<{
|
||||
dataResult?: any;
|
||||
dataType?: any;
|
||||
dataQuery?: any;
|
||||
layout?: string;
|
||||
label?: string;
|
||||
}>();
|
||||
|
||||
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");
|
||||
switch (result?.contentType) {
|
||||
case 1:
|
||||
type.value = "";
|
||||
// type.value = "Image";
|
||||
break;
|
||||
case 2:
|
||||
type.value = "Image";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
type.value = "Postcard";
|
||||
break;
|
||||
|
||||
case 4:
|
||||
type.value = "Video";
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if (result?.layoutType === 4) {
|
||||
type.value = "Emagazine";
|
||||
break;
|
||||
}
|
||||
if (result?.layoutType === 3) {
|
||||
type.value = "Infographics";
|
||||
break;
|
||||
}
|
||||
}
|
||||
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, '')" />
|
||||
<div class="card-audio__content">
|
||||
<span>
|
||||
<template v-if="type === 'Image'">
|
||||
<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"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</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']">{{
|
||||
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>
|
||||
</div>
|
||||
<div v-html="LAYOUT_PARSE.styleClasses"></div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.card-audio {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 56.25%; //tỷ lệ 9 /16;;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card-audio__content {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
bottom: 0;
|
||||
padding: 20px 30px;
|
||||
z-index: 3;
|
||||
text-align: center;
|
||||
h2 {
|
||||
color: #fff;
|
||||
margin: 12px 0 20px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 130%;
|
||||
|
||||
&:hover {
|
||||
color: #9e1e0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-audio__type-category {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
justify-content: center;
|
||||
margin-top: 12px;
|
||||
|
||||
.card-audio__type,
|
||||
.card-audio__category {
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
line-height: 180%;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.card-audio__type {
|
||||
background-color: #ed1c24;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-audio__category {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
z-index: 2;
|
||||
}
|
||||
.empty-block {
|
||||
background-color: #409eff;
|
||||
height: 100px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,286 @@
|
||||
<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;
|
||||
}>();
|
||||
|
||||
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 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
|
||||
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']">
|
||||
<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 v-html="LAYOUT_PARSE.styleClasses"></div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.article-card-default {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
|
||||
.article-card-default__title {
|
||||
color: #000;
|
||||
h2 {
|
||||
display: inline-block;
|
||||
/* margin: 12px 0 20px 0; */
|
||||
font-size: 24px;
|
||||
|
||||
font-weight: 700;
|
||||
line-height: 130%;
|
||||
|
||||
&:hover {
|
||||
color: #9e1e0f;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 150%;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__thumbnail {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.article-card-default__topic {
|
||||
position: relative;
|
||||
/* margin: 0 0 12px 12px; */
|
||||
|
||||
// background-color: #151411;
|
||||
display: inline-block;
|
||||
|
||||
h5 {
|
||||
font-size: 12px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.article-card-default__bottom {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
/* margin-top: 10px; */
|
||||
a {
|
||||
color: #ed1c24;
|
||||
}
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
h3,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.basic-article {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
background-size: cover;
|
||||
flex-direction: column;
|
||||
&.no-data {
|
||||
gap: 5px !important;
|
||||
padding: 0;
|
||||
}
|
||||
.line-clamp {
|
||||
display: -webkit-box;
|
||||
/* -webkit-line-clamp: 3; */
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.basic-article_thumbnail {
|
||||
width: 100%;
|
||||
}
|
||||
&.border-custom {
|
||||
border-color: #e5e5e5 !important;
|
||||
}
|
||||
/* &.horizontal {
|
||||
flex-direction: row;
|
||||
.basic-article_thumbnail {
|
||||
width: 40%;
|
||||
}
|
||||
&.reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
} */
|
||||
|
||||
&_thumbnail {
|
||||
img {
|
||||
width: 100%;
|
||||
border-radius: 2px;
|
||||
aspect-ratio: 16/10;
|
||||
}
|
||||
}
|
||||
|
||||
&_content {
|
||||
/* padding: 10px 0px; */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
&.no-data {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
/* margin-top: 10px; */
|
||||
opacity: 85%;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-block {
|
||||
background-color: #409eff;
|
||||
height: 100px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Article_Card_Default } from './Card.vue'
|
||||
export { default as Article_Card_Audio } from './Audio.vue'
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Article_Card_Default, Article_Card_Audio } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => {
|
||||
console.log(_props.settings.layout, enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_CARD"]]["CARD_DEFAULT"]);
|
||||
return _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 :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
|
||||
</template>
|
||||
@@ -0,0 +1,242 @@
|
||||
<script setup lang="ts">
|
||||
import { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
import { DEFAULT_QUERY_DROP } from "@/utils/parseSQL";
|
||||
import { isEmpty } from "lodash";
|
||||
const emit = defineEmits(["dropData", "selectComponent"]);
|
||||
import { getInputValue } from "@/utils/parseSQL";
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any;
|
||||
dataType?: any;
|
||||
dataQuery?: any;
|
||||
layout?: string;
|
||||
label?: string;
|
||||
}>();
|
||||
const SETTING_OPTIONS = {
|
||||
BREADCRUMB_MAX_ELEMENT: 3,
|
||||
};
|
||||
const LAYOUT_PARSE = computed(() => {
|
||||
const designObject = _props.label ? getInputValue(_props.label, "OBJECT") : {};
|
||||
return Object.assign({}, designObject);
|
||||
});
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
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">
|
||||
<div class="breadcrumb" v-if="!LAYOUT_PARSE['HideBreadcrumb']">
|
||||
<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>
|
||||
|
||||
<nuxt-link class="article-card-default__topic" :to="`#`">
|
||||
<h5>Topic</h5>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
|
||||
<div class="content">Nội dung bài viết sẽ ở đây</div>
|
||||
<!-- <div class="btn-wrap" v-if="!LAYOUT_PARSE['HideCopylink']">
|
||||
<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>
|
||||
|
||||
<div class="center-y">
|
||||
<button title="Copy link" class="button--back">
|
||||
<Icon name="mdi:link-variant" />
|
||||
</button>
|
||||
</div>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
border-radius: 6px;
|
||||
background: #409eff;
|
||||
width: 40px;
|
||||
min-height: 20px;
|
||||
}
|
||||
.content {
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
background: #eeeeee;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.intro {
|
||||
white-space: normal;
|
||||
padding-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.detail {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
// .btn-wrap {
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
// justify-content: space-between;
|
||||
// align-items: center;
|
||||
|
||||
// @media (min-width: 768px) {
|
||||
// flex-direction: row;
|
||||
// }
|
||||
// .class-default,
|
||||
// .button--back {
|
||||
// border-radius: 9999px;
|
||||
// border-width: 1px;
|
||||
// width: 3rem;
|
||||
// height: 3rem;
|
||||
// margin: 0;
|
||||
// font-size: 17px;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// background-color: #ffffff;
|
||||
// border: 1px solid rgb(229, 231, 235);
|
||||
// box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
// &:hover {
|
||||
// background-color: #e6f4ff;
|
||||
// color: #3c7abc;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .button--bookmark {
|
||||
// width: 32px;
|
||||
// height: 32px;
|
||||
// border-radius: 200px;
|
||||
// background-color: white;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// border: none;
|
||||
// &:hover {
|
||||
// background-color: #e6f4ff;
|
||||
// color: #3c7abc;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
.center-y {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,176 @@
|
||||
<script setup lang="ts">
|
||||
import { isEmpty } from "lodash";
|
||||
const emit = defineEmits(["dropData", "selectComponent"]);
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any[];
|
||||
}>();
|
||||
const SETTING_OPTIONS = {
|
||||
BREADCRUMB_MAX_ELEMENT: 3,
|
||||
};
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="center-y">
|
||||
<button title="Copy link" class="button--back copy-link">
|
||||
<Icon name="mdi:link-variant" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
$max-width: 680px;
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__list {
|
||||
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;
|
||||
&:first-child {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
&:not(:first-child):before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-top: 1px solid #bdbdbd;
|
||||
border-right: 1px solid #bdbdbd;
|
||||
transform: rotate(45deg);
|
||||
position: absolute;
|
||||
left: -18px;
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__time {
|
||||
color: #9f9f9f;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
border-radius: 6px;
|
||||
background: #409eff;
|
||||
width: 40px;
|
||||
min-height: 20px;
|
||||
}
|
||||
.content {
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
background: #eeeeee;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.intro {
|
||||
white-space: normal;
|
||||
padding-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.detail {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.btn-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
.class-default,
|
||||
.button--back,
|
||||
.button--bookmark {
|
||||
border-radius: 8px;
|
||||
border-width: 1px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid rgb(229, 231, 235);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
&:hover {
|
||||
background-color: #e6f4ff;
|
||||
color: #3c7abc;
|
||||
}
|
||||
|
||||
&.copy-link {
|
||||
border-radius: 999px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb,
|
||||
.btn-wrap {
|
||||
max-width: $max-width;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.center-y {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,318 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="podcast__wrapper overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="podcast"
|
||||
>
|
||||
<p
|
||||
class="podcast__content__time"
|
||||
>
|
||||
Ngày tạo podcast
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
class="buttons"
|
||||
>
|
||||
<li><Icon name="mdi:bookmark-outline" /></li>
|
||||
<li><Icon name="material-symbols:mode-comment-outline" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="playlist">
|
||||
<div class="playlist__time">
|
||||
<span>5:00</span>
|
||||
<span>10:00</span>
|
||||
</div>
|
||||
<div class="playlist__buttons">
|
||||
<div class="playlist__buttons__left">
|
||||
<div
|
||||
class="button__prev"
|
||||
>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<div class="playlist__buttons__right">
|
||||
<div
|
||||
class="button__next"
|
||||
>
|
||||
<Icon name="material-symbols:skip-next" />
|
||||
</div>
|
||||
<div
|
||||
class="speed"
|
||||
>
|
||||
<span>Tốc độ phát: </span>
|
||||
<strong>1x</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>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
:root {
|
||||
--podcast-wrapper-padding: 40;
|
||||
}
|
||||
|
||||
.podcast-padding-tablet {
|
||||
--podcast-wrapper-padding: 30;
|
||||
}
|
||||
|
||||
.podcast-padding-smartphone {
|
||||
--podcast-wrapper-padding: 20;
|
||||
}
|
||||
|
||||
.podcast__wrapper {
|
||||
padding: calc(var(--podcast-wrapper-padding) * 1px);
|
||||
border: 1px solid #eeeeee;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
|
||||
.podcast {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
& > figure > img {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__content__text {
|
||||
font-size: 18px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
color: #222222;
|
||||
margin: 0;
|
||||
|
||||
&__time {
|
||||
color: #9f9f9f;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
text-align: center;
|
||||
&__time {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .buttons {
|
||||
display: flex;
|
||||
align-self: start;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
& li {
|
||||
list-style: none;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background-color: white;
|
||||
border: 1px solid rgb(229, 231, 235);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
border-radius: 50px;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background-color: #e6f4ff;
|
||||
color: #3c7abc;
|
||||
}
|
||||
|
||||
& svg {
|
||||
font-size: 18px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateY(-50%) translateX(-55%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
|
||||
& .buttons {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.playlist {
|
||||
padding: 8px;
|
||||
|
||||
&__time {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 12px;
|
||||
position: relative;
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 4px;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
background-color: #e6f4ff;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&::before {
|
||||
width: 50%;
|
||||
background-color: #3c7abc;
|
||||
z-index: 2;
|
||||
}
|
||||
& span {
|
||||
font-size: 16px;
|
||||
color: #3c7abc;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
& .button__prev,
|
||||
& .button__next {
|
||||
font-size: 28px;
|
||||
color: #3c7abc;
|
||||
}
|
||||
& .sound {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-left: -10px;
|
||||
padding: 0 10px;
|
||||
height: 36px;
|
||||
color: #3c7abc;
|
||||
border-radius: 8px;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #e6f4ff;
|
||||
}
|
||||
|
||||
& > div {
|
||||
width: 50px;
|
||||
height: 2px;
|
||||
position: relative;
|
||||
background-color: #dcf0ff;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: 0;
|
||||
height: 2px;
|
||||
width: 50%;
|
||||
background-color: #3c7abc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .play {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
|
||||
& svg {
|
||||
font-size: 28px;
|
||||
color: #3c7abc;
|
||||
|
||||
&.button {
|
||||
font-size: 64px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .speed {
|
||||
font-size: 14px;
|
||||
color: #3c7abc;
|
||||
|
||||
& span {
|
||||
font-weight: 200;
|
||||
}
|
||||
& strong {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import Comment from "@/components/dynamic-page/page-component/templates/others/comments/Default.vue";
|
||||
</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>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.line-clamp-3 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,6 @@
|
||||
// export { default as Article_Card } from './cards/Card.vue'
|
||||
|
||||
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'
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
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";
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]["DETAIL_GENERAL"]]: Article_Detail_General,
|
||||
[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,
|
||||
};
|
||||
|
||||
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 :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
|
||||
</template>
|
||||
@@ -1,5 +1,2 @@
|
||||
export { default as Article_Card } from './layouts/Card.vue'
|
||||
export { default as Article_Detail_General } from './layouts/details/General.vue'
|
||||
export { default as Article_Detail_Podcast } from './layouts/details/Podcast.vue'
|
||||
export { default as Article_Detail_Video } from './layouts/details/Video.vue'
|
||||
export { default as Article_Detail_Image } from './layouts/details/Image.vue'
|
||||
export { default as Article_Card } from './cards/index.vue'
|
||||
export { default as Article_Detail } from './details/index.vue'
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
import {
|
||||
Article_Card,
|
||||
Article_Detail_General,
|
||||
Article_Detail_Podcast,
|
||||
Article_Detail_Video,
|
||||
Article_Detail_Image
|
||||
} from "./index";
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Article_Card, Article_Detail } from "./index";
|
||||
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
'TYPE:Detail-LAYOUT:default': Article_Detail_General,
|
||||
'TYPE:Detail-LAYOUT:image': Article_Detail_Image,
|
||||
'TYPE:Detail-LAYOUT:video': Article_Detail_Video,
|
||||
'TYPE:Detail-LAYOUT:podcast': Article_Detail_Podcast,
|
||||
'TYPE:Card': Article_Card
|
||||
[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_CARD"]]: Article_Card,
|
||||
[enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]: Article_Detail,
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
|
||||
const getCurrentComponent = computed(() => _props.settings.template);
|
||||
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
let props: any = {};
|
||||
@@ -38,5 +32,5 @@ const GET_PROPS = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="GET_PROPS()" class="h-full"/>
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
|
||||
</template>
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
<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?: string;
|
||||
}>();
|
||||
|
||||
const LAYOUT_PARSE = computed(() => {
|
||||
const parseLayout =
|
||||
props.layout?.split("-")?.map((_layout: any) => {
|
||||
const parseItem = _layout.split(":");
|
||||
return {
|
||||
[parseItem[0]]: parseItem[0] === "HIDE" ? parseItem[1].split(",") : parseItem[1],
|
||||
};
|
||||
}) || [];
|
||||
const designObject = props.label ? getInputValue(props.label, "OBJECT") : {};
|
||||
return Object.assign({}, ...parseLayout, 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="basic-article border-custom"
|
||||
@click="selectComponent"
|
||||
@dragover.prevent
|
||||
@drop.stop.prevent="drop"
|
||||
:class="[LAYOUT_PARSE['LAYOUT'] || 'horizontal', !parseData && 'no-data', LAYOUT_PARSE['REVERSE'] ? 'reverse' : '', ...(LAYOUT_PARSE['border']?.length > 0 ? LAYOUT_PARSE['border'] : [])]"
|
||||
:style="[LAYOUT_PARSE['background'] && `background: ${LAYOUT_PARSE['background']}`]"
|
||||
>
|
||||
<div v-if="!LAYOUT_PARSE['HIDE'] || !LAYOUT_PARSE['HIDE'].includes('thumbnail')" class="basic-article_thumbnail" :style="[LAYOUT_PARSE['LAYOUT'] === 'horizontal' && LAYOUT_PARSE['WidthImg'] && `width: ${LAYOUT_PARSE['WidthImg']}%`]">
|
||||
<template v-if="parseData">
|
||||
<nuxt-link :to="`bai-viet/${parseData?.slug}`">
|
||||
<img class="object-fit-cover" :src="parseData.thumbnail ? parseData.thumbnail : '/images/default-thumbnail.jpg'" :alt="parseData.title?.replace(/<[^>]+>/g, '')" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</div>
|
||||
<div class="basic-article_content" :class="[!parseData && 'no-data']">
|
||||
<div>
|
||||
<nuxt-link :to="`bai-viet/${parseData?.slug}`"
|
||||
v-if="!LAYOUT_PARSE['HIDE'] || !LAYOUT_PARSE['HIDE'].includes('title')"
|
||||
class="line-clamp hover:text-primary-600"
|
||||
:style="[
|
||||
LAYOUT_PARSE['fontSizeTitle'] && `font-size: ${LAYOUT_PARSE['fontSizeTitle']}px`,
|
||||
LAYOUT_PARSE['fontWeightTitle'] && `font-weight: ${LAYOUT_PARSE['fontWeightTitle']}`,
|
||||
LAYOUT_PARSE['color'] && `color: ${LAYOUT_PARSE['color']}`,
|
||||
LAYOUT_PARSE['lineClampTitle'] && `-webkit-line-clamp: ${LAYOUT_PARSE['lineClampTitle']}`
|
||||
]"
|
||||
>
|
||||
<template v-if="parseData">
|
||||
{{ parseData.title?.replace(/<[^>]+>/g, "") }}
|
||||
</template>
|
||||
|
||||
</nuxt-link>
|
||||
<p
|
||||
v-if="!LAYOUT_PARSE['HIDE'] || !LAYOUT_PARSE['HIDE'].includes('paragraph')"
|
||||
class="mb-0 line-clamp font-arial"
|
||||
:style="[
|
||||
LAYOUT_PARSE['fontSizeIntro'] && `font-size: ${LAYOUT_PARSE['fontSizeIntro']}px`,
|
||||
LAYOUT_PARSE['fontWeightIntro'] && `font-weight: ${LAYOUT_PARSE['fontWeightIntro']}`,
|
||||
LAYOUT_PARSE['color'] && `color: ${LAYOUT_PARSE['color']}`,
|
||||
LAYOUT_PARSE['lineClampIntro'] && `-webkit-line-clamp: ${LAYOUT_PARSE['lineClampIntro']}`
|
||||
]"
|
||||
>
|
||||
<template v-if="parseData">
|
||||
{{ parseData.intro?.replace(/<[^>]+>/g, "") }}
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.basic-article {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
&.no-data {
|
||||
gap: 5px !important;
|
||||
}
|
||||
.line-clamp {
|
||||
display: -webkit-box;
|
||||
/* -webkit-line-clamp: 3; */
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
flex-direction: column;
|
||||
.basic-article_thumbnail {
|
||||
width: 100%;
|
||||
}
|
||||
&.reverse {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
}
|
||||
&.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;
|
||||
}
|
||||
&.horizontal {
|
||||
flex-direction: row;
|
||||
.basic-article_thumbnail {
|
||||
width: 40%;
|
||||
}
|
||||
&.reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
&_thumbnail {
|
||||
img {
|
||||
width: 100%;
|
||||
border-radius: 2px;
|
||||
aspect-ratio: 16/10;
|
||||
}
|
||||
}
|
||||
|
||||
&_content {
|
||||
/* padding: 10px 0px; */
|
||||
flex: 1;
|
||||
&.no-data {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
opacity: 85%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,214 +0,0 @@
|
||||
<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);
|
||||
|
||||
|
||||
|
||||
function increase() {
|
||||
const step = ref(Number(getComputedStyle(document.documentElement).getPropertyValue("--step").trim()));
|
||||
step.value += 2;
|
||||
document.documentElement.style.setProperty("--step", step.value.toString());
|
||||
}
|
||||
|
||||
function decrease() {
|
||||
const step = ref(Number(getComputedStyle(document.documentElement).getPropertyValue("--step").trim()));
|
||||
step.value -= 2;
|
||||
document.documentElement.style.setProperty("--step", step.value.toString());
|
||||
}
|
||||
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 breakcrumb = document.querySelector('div[layout="BREADCRUM_DEFAULT"]');
|
||||
if (detailEmagazine && breakcrumb) {
|
||||
breakcrumb.classList.add("lg:max-w-640px", "mx-auto");
|
||||
}
|
||||
|
||||
document.documentElement.style.setProperty("--step", '0');
|
||||
});
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div id="breakcrumb" class="flex justify-between items-center my-3 font-arial">
|
||||
<ul class="flex gap-6 items-center text-md">
|
||||
<template v-for="(category, index) in currentCategoryTree" :key="index">
|
||||
<li class="first:text-primary-600 hover:text-primary-600 font-medium relative after:absolute after:content-['\003E'] last:after:content-[''] after:right--4 after:text-gray-3">
|
||||
<nuxt-link :to="{ name: 'categories', params: { categories: category.code ?? '/' } }">{{ category.title }}</nuxt-link>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div @click="increase()" class="w-10 h-10 border-1px border-solid shadow rounded-full relative cursor-pointer hover:bg-primary-100 hover:text-primary-600">
|
||||
<svg class="absolute top-50% left-50% translate-y--50% translate-x--55%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
|
||||
<path fill="none" d="M0 0h24v24H0z"></path>
|
||||
<path
|
||||
d="M11.246 15H4.75416L2.75416 20H0.600098L7.0001 4H9.0001L15.4001 20H13.246L11.246 15ZM10.446 13L8.0001 6.88516L5.55416 13H10.446ZM21.0001 12.5351V12H23.0001V20H21.0001V19.4649C20.4118 19.8052 19.7287 20 19.0001 20C16.791 20 15.0001 18.2091 15.0001 16C15.0001 13.7909 16.791 12 19.0001 12C19.7287 12 20.4118 12.1948 21.0001 12.5351ZM19.0001 18C20.1047 18 21.0001 17.1046 21.0001 16C21.0001 14.8954 20.1047 14 19.0001 14C17.8955 14 17.0001 14.8954 17.0001 16C17.0001 17.1046 17.8955 18 19.0001 18Z"
|
||||
></path>
|
||||
</svg>
|
||||
<svg class="absolute right-1.5 top-2 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M11 11V5H13V11H19V13H13V19H11V13H5V11H11Z"></path></svg>
|
||||
</div>
|
||||
<div @click="decrease()" class="w-10 h-10 border-1px border-solid shadow rounded-full relative cursor-pointer hover:bg-primary-100 hover:text-primary-600">
|
||||
<svg class="absolute top-50% left-50% translate-y--50% translate-x--55%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
|
||||
<path fill="none" d="M0 0h24v24H0z"></path>
|
||||
<path
|
||||
d="M11.246 15H4.75416L2.75416 20H0.600098L7.0001 4H9.0001L15.4001 20H13.246L11.246 15ZM10.446 13L8.0001 6.88516L5.55416 13H10.446ZM21.0001 12.5351V12H23.0001V20H21.0001V19.4649C20.4118 19.8052 19.7287 20 19.0001 20C16.791 20 15.0001 18.2091 15.0001 16C15.0001 13.7909 16.791 12 19.0001 12C19.7287 12 20.4118 12.1948 21.0001 12.5351ZM19.0001 18C20.1047 18 21.0001 17.1046 21.0001 16C21.0001 14.8954 20.1047 14 19.0001 14C17.8955 14 17.0001 14.8954 17.0001 16C17.0001 17.1046 17.8955 18 19.0001 18Z"
|
||||
></path>
|
||||
</svg>
|
||||
<svg class="absolute right-1.5 top-2 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5 11V13H19V11H5Z"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content" v-if="currentArticle">
|
||||
<h1 id="sub" v-html="currentArticle?.sub" class="font-bold opacity-60 pb-1"></h1>
|
||||
<h3 id="title" class="font-bold pb-1" v-html="currentArticle?.title"></h3>
|
||||
<p id="published-on" class="text-gray-600 mb-3">{{ utils.dateFormat(currentArticle?.publishedOn, "dddd, DD/MM/YYYY - HH:mm") }}</p>
|
||||
<div id="intro" v-if="currentArticle?.intro" v-html="currentArticle?.intro" class="font-semibold pb-1 mb-3"></div>
|
||||
<component :is="{ template: currentArticle.detail, components: { Poll, Quiz, Survey, Document, Attachment, Tag } }" />
|
||||
</div>
|
||||
|
||||
<div class="py-5 flex flex-wrap justify-between items-center">
|
||||
<div class="flex gap-4 items-center">
|
||||
<nuxt-link :to="{ name: 'categories', params: { categories: `${currentCategoryTree[currentCategoryTree.length - 1]?.code}` } }" title="Quay trở lại" class="w-12 h-3rem rounded-full flex items-center justify-center bg-white border-1px shadow hover:bg-primary-100 hover:text-primary-600 cursor-pointer">
|
||||
<Icon name="fa6-solid:arrow-left" />
|
||||
</nuxt-link>
|
||||
<button @click="onClickBookmark()" class="w-8 h-8 rounded-full bg-white hover:bg-primary-100 hover:text-primary-600">
|
||||
<Icon v-show="isBookmark === false" name="fa6-regular:bookmark" />
|
||||
<Icon v-show="isBookmark === true" name="fa6-solid:bookmark" class="text-primary-600" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<button @click="copyLink()" title="Copy link" class="w-12 h-3rem rounded-full flex items-center justify-center bg-white border-1px shadow hover:bg-primary-100 hover:text-primary-600 cursor-pointer text-2xl">
|
||||
<Icon name="bi:link-45deg" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex gap-2 items-start mb-2 text-15px" v-if="currentArticle?.topics">
|
||||
<div>
|
||||
<Icon name="material-symbols:trending-up" class="w-5 h-5"/>
|
||||
</div>
|
||||
<span>Chủ đề: </span>
|
||||
<ul>
|
||||
<li v-for="(topic, index) in currentArticle?.topics" :key="index" class="mb-1">
|
||||
<nuxt-link to="#" class="text-primary-600 font-bold font-merriweather hover:underline">{{ topic.title }}</nuxt-link>
|
||||
<!-- <nuxt-link :to="`topic/${topic.slug}`" class="text-primary-600 font-bold font-merriweather hover:underline">{{ topic.title }}</nuxt-link> -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 items-start mb-2 text-15px" v-if="currentArticle?.events">
|
||||
<div>
|
||||
<Icon name="ic:baseline-event" class="w-5 h-5"/>
|
||||
</div>
|
||||
<span>Sự kiện: </span>
|
||||
<ul>
|
||||
<li v-for="(event, index) in currentArticle?.events" :key="index" class="mb-1">
|
||||
<nuxt-link to="#" class="text-primary-600 font-bold font-merriweather hover:underline">{{ event.title }}</nuxt-link>
|
||||
<!-- <nuxt-link :to="`event/${event.slug}`" class="text-primary-600 font-bold font-merriweather hover:underline">{{ event.title }}</nuxt-link> -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 items-start mb-2 text-15px" v-if="currentArticle?.tags">
|
||||
<div>
|
||||
<Icon name="mdi:tag" class="w-5 h-5"/>
|
||||
</div>
|
||||
<span>Tags: </span>
|
||||
<ul class="flex gap-6 flex-wrap">
|
||||
<li v-for="(tag, index) in currentArticle?.tags" :key="index" class="mb-1 font-normal text-black-500 relative after:absolute after:content-['/'] after:right--4 last:after:content-[''] after:text-[#eee]">
|
||||
<nuxt-link to="#" class="hover:text-primary-600">{{ tag.title }}</nuxt-link>
|
||||
<!-- <nuxt-link :to="`topic/${tag.slug}`" class="text-primary-600 font-bold font-merriweather hover:underline">{{ tag.title }}</nuxt-link> -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:root {
|
||||
--step: 1;
|
||||
}
|
||||
#sub,
|
||||
#intro,
|
||||
#intro + div {
|
||||
font-size: calc(16px + var(--step) * 1px);
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: calc(28px + var(--step) * 1px);
|
||||
}
|
||||
|
||||
#published-on {
|
||||
font-size: calc(14px + var(--step) * 1px);
|
||||
}
|
||||
</style>
|
||||
@@ -1,138 +0,0 @@
|
||||
<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 breakcrumb = document.querySelector('div[layout="BREADCRUM_DEFAULT"]');
|
||||
if (detailEmagazine && breakcrumb) {
|
||||
breakcrumb.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);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="content" v-if="currentArticle">
|
||||
<div id="breakcrumb" class="flex justify-between items-center my-3">
|
||||
<ul class="flex gap-6 items-center text-md">
|
||||
<template v-for="(category, index) in currentCategoryTree" :key="index">
|
||||
<li class="font-semibold relative after:absolute after:content-['\003E'] last:after:content-[''] after:right--4 after:text-gray-3">
|
||||
<nuxt-link :class="index !== 0 ? '!text-black-500' : ''" class="hover:!text-primary-600" :to="{ name: 'categories', params: { categories: category.code ?? '/' } }">{{ category.title }}</nuxt-link>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
||||
<span id="published-on" class="text-gray-600 mb-3">{{ utils.dateFormat(currentArticle?.publishedOn, "dddd, DD/MM/YYYY - HH:mm") }}</span>
|
||||
</div>
|
||||
<h1 id="sub" v-html="currentArticle?.sub" class="font-bold opacity-60 pb-1"></h1>
|
||||
<h3 id="title" class="font-bold pb-1" v-html="currentArticle?.title"></h3>
|
||||
|
||||
<div id="intro" v-if="currentArticle?.intro" v-html="currentArticle?.intro" class="font-semibold tracking-widest pb-1 mb-3"></div>
|
||||
<component :is="{ template: currentArticle.detail, components: { Poll, Quiz, Survey, Document, Attachment, Tag } }" />
|
||||
|
||||
<div id="navigation__bottom" class="py-5 flex flex-wrap justify-between items-center">
|
||||
<div class="flex gap-4 items-center">
|
||||
<nuxt-link
|
||||
:to="{ name: 'categories', params: { categories: `${currentCategoryTree[currentCategoryTree.length - 1]?.code}` } }"
|
||||
title="Quay trở lại"
|
||||
class="w-10 h-10 rounded-8px flex items-center justify-center bg-white border-1px shadow hover:bg-primary-100 hover:text-primary-600 cursor-pointer"
|
||||
>
|
||||
<Icon name="fa6-solid:arrow-left" />
|
||||
</nuxt-link>
|
||||
<button @click="onClickBookmark()" class="w-10 h-10 rounded-8px border-1px bg-white hover:bg-primary-100 hover:text-primary-600">
|
||||
<Icon v-show="isBookmark === false" name="fa6-regular:bookmark" />
|
||||
<Icon v-show="isBookmark === true" name="fa6-solid:bookmark" class="text-primary-600" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<button @click="copyLink()" title="Copy link" class="w-10 h-10 rounded-8px flex items-center justify-center bg-white border-1px shadow hover:bg-primary-100 hover:text-primary-600 cursor-pointer text-2xl">
|
||||
<Icon name="bi:link-45deg" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#sub,
|
||||
#intro,
|
||||
#intro + div {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
#published-on {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,478 +0,0 @@
|
||||
<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 breakcrumb = document.querySelector('div[layout="BREADCRUM_DEFAULT"]');
|
||||
if (detailEmagazine && breakcrumb) {
|
||||
breakcrumb.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
|
||||
}
|
||||
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="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 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="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>
|
||||
<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="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="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="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="md:w-150px text-right">
|
||||
<div class="text-28px text-primary-600 md:hidden block">
|
||||
<Icon name="material-symbols:skip-next" />
|
||||
</div>
|
||||
<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="md:hidden block" v-html="currentArticle?.intro"></p>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
:root {
|
||||
--podcast-wrapper-padding: 40;
|
||||
}
|
||||
|
||||
.podcast-padding-tablet {
|
||||
--podcast-wrapper-padding: 30;
|
||||
}
|
||||
|
||||
.podcast-padding-smartphone {
|
||||
--podcast-wrapper-padding: 20;
|
||||
}
|
||||
|
||||
.podcast__wrapper {
|
||||
padding: calc(var(--podcast-wrapper-padding) * 1px);
|
||||
border: 1px solid #eeeeee;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
|
||||
.podcast {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
& > figure > img {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__content__text {
|
||||
font-size: 18px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
color: #222222;
|
||||
margin: 0;
|
||||
|
||||
&__time {
|
||||
color: #9f9f9f;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
text-align: center;
|
||||
&__time {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .buttons {
|
||||
display: flex;
|
||||
align-self: start;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
& li {
|
||||
list-style: none;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background-color: white;
|
||||
border: 1px solid rgb(229, 231, 235);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
border-radius: 50px;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background-color: #e6f4ff;
|
||||
color: #3c7abc;
|
||||
}
|
||||
|
||||
& svg {
|
||||
font-size: 18px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateY(-50%) translateX(-55%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
|
||||
& .buttons {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.playlist {
|
||||
padding: 8px;
|
||||
|
||||
&__time {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 12px;
|
||||
position: relative;
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 4px;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
background-color: #e6f4ff;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&::before {
|
||||
width: 50%;
|
||||
background-color: #3c7abc;
|
||||
z-index: 2;
|
||||
}
|
||||
& span {
|
||||
font-size: 16px;
|
||||
color: #3c7abc;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
& .button__prev,
|
||||
& .button__next {
|
||||
font-size: 28px;
|
||||
color: #3c7abc;
|
||||
}
|
||||
& .sound {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-left: -10px;
|
||||
padding: 0 10px;
|
||||
height: 36px;
|
||||
color: #3c7abc;
|
||||
border-radius: 8px;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #e6f4ff;
|
||||
}
|
||||
|
||||
& > div {
|
||||
width: 50px;
|
||||
height: 2px;
|
||||
position: relative;
|
||||
background-color: #dcf0ff;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: 0;
|
||||
height: 2px;
|
||||
width: 50%;
|
||||
background-color: #3c7abc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .play {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
|
||||
& svg {
|
||||
font-size: 28px;
|
||||
color: #3c7abc;
|
||||
|
||||
&.button {
|
||||
font-size: 64px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .speed {
|
||||
font-size: 14px;
|
||||
color: #3c7abc;
|
||||
|
||||
& span {
|
||||
font-weight: 200;
|
||||
}
|
||||
& strong {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,45 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import Comment from "@/components/dynamic-page/page-component/templates/other/comments/default.vue";
|
||||
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(),
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||
<div class="md:col-span-2">
|
||||
<div id="article-detail" class="flex-1 [&_iframe]:w-full [&_iframe]:max-w-full [&_iframe]:max-h-52 md:[&_iframe]:max-h-full [&_video]:max-w-full [&_video]:w-full">
|
||||
<div v-html="currentArticle?.detail" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pt-2 bg-[rgb(248,249,250)]">
|
||||
<h1 class="text-2rem text-#495057 font-700" v-html="currentArticle.title"></h1>
|
||||
<p class="line-clamp-3 font-400" v-html="currentArticle.intro"></p>
|
||||
<h5 class="text-end font-600 opacity-75">Tác giả</h5>
|
||||
|
||||
<p><b class="text-primary-600 fw-bold">Danh mục</b> <span class="ms-2 opacity-25 fw-semibold">{{ utils.dateFormat(currentArticle?.publishedOn, "dddd, DD/MM/YYYY - HH:mm") }}</span></p>
|
||||
<Comment />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.line-clamp-3 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
</style>
|
||||
@@ -1,38 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from '@/utils/parseSQL';
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any;
|
||||
dataQuery?: string;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 5,
|
||||
};
|
||||
|
||||
const _dataResult = computed(() => {
|
||||
let _components = Array(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;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex gap-4 items-end">
|
||||
<template v-for="(component, index) in _dataResult">
|
||||
<nuxt-link v-if="component" :key="index" :to="`/${component.code}`" class=" py-1 font-400 text-[16px] first:font-600 first:text-[20px] sm:block hidden first:block">
|
||||
<h3 class="m-0 leading-none hover:text-primary-600 transition-all duration-300">{{ component.title }}</h3>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { isEmpty } from "lodash";
|
||||
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any[];
|
||||
dataQuery?: string;
|
||||
label?: string;
|
||||
}>();
|
||||
|
||||
const designObject = computed(() => {
|
||||
return _props.label ? getInputValue(_props.label, "OBJECT") : {};
|
||||
});
|
||||
|
||||
const mapActivesToItems = (index: number) => {
|
||||
if (designObject.value && designObject.value.listCss) {
|
||||
return designObject.value.listCss[index] || {};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="categories-container border-custom" :class="designObject['categories_Class']" @click="selectComponent" :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 }}
|
||||
</h3>
|
||||
</div>
|
||||
<div v-html="designObject.styleClasses"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.border-pri {
|
||||
.categories-container {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.categories-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
|
||||
.category {
|
||||
height: 100%;
|
||||
|
||||
h3 {
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
margin: 0px !important;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
h3 {
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
border-radius: 6px;
|
||||
background: #409eff;
|
||||
width: 50px;
|
||||
|
||||
> div {
|
||||
min-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.border-custom {
|
||||
border-color: #e5e5e5 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import { isEmpty } from "lodash";
|
||||
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any[];
|
||||
dataQuery?: string;
|
||||
label?: string;
|
||||
}>();
|
||||
|
||||
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") : {};
|
||||
});
|
||||
|
||||
const mapActivesToItems = (index: number) => {
|
||||
if (designObject.value && designObject.value.listCss) {
|
||||
return designObject.value.listCss[index] || {};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="categories-container border-custom" :class="designObject['categories_Class']" @click="selectComponent" :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">
|
||||
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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>
|
||||
</div>
|
||||
<div v-html="designObject.styleClasses"></div>
|
||||
</template>
|
||||
|
||||
<div v-else @dragover.prevent @drop.stop.prevent="dropData($event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.border-pri {
|
||||
.categories-container {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.categories-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
|
||||
.category {
|
||||
&-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
line-height: 180%;
|
||||
font-size: 14px;
|
||||
margin: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
border-radius: 6px;
|
||||
background: #409eff;
|
||||
width: 100px;
|
||||
|
||||
> div {
|
||||
min-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.border-custom {
|
||||
border-color: #e5e5e5 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Default_Collection } from './Default.vue'
|
||||
export { default as Vertical_Collection } from './Vertical.vue'
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Default_Collection, Vertical_Collection } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.CATEGORY]["CATEGORY"]}`]["DEFAULT"]]: Default_Collection,
|
||||
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.CATEGORY]["CATEGORY"]}`]["CATEGORY_VERTICAL"]]: Vertical_Collection,
|
||||
};
|
||||
|
||||
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 :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
|
||||
</template>
|
||||
@@ -1 +1 @@
|
||||
export { default as Category_Default } from './layouts/Default.vue'
|
||||
export { default as Categories } from './categories/index.vue'
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
import {
|
||||
Category_Default
|
||||
} from "./index";
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Categories } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
'TYPE:Category-MAX:5': Category_Default,
|
||||
[enumPageComponentTemplate[enumPageComponentKey.CATEGORY]['CATEGORY']]: Categories,
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
|
||||
const getCurrentComponent = computed(() => _props.settings.template);
|
||||
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
let props: any = {};
|
||||
@@ -30,5 +30,9 @@ const GET_PROPS = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="GET_PROPS()" />
|
||||
<component
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
|
||||
import { isEmpty } from "@/utils/lodash";
|
||||
|
||||
const emit = defineEmits(["dropData", "selectComponent"]);
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any;
|
||||
dataQuery?: string;
|
||||
label?: string;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 5,
|
||||
};
|
||||
|
||||
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") : {};
|
||||
});
|
||||
|
||||
async function dropData(event: any) {
|
||||
const { dataResult, dataType } = JSON.parse(event.dataTransfer.getData("category"));
|
||||
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 handleActiveItem = (key: string, keyActive: string, index: number, defaultValue: any) => {
|
||||
const designObject = _props.label ? getInputValue(_props.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;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="categories-container border-custom"
|
||||
@click="selectComponent"
|
||||
:class="[designObject['LAYOUT_WRAP'] || 'horizontal', ...(designObject['borderWrap']?.length > 0 ? designObject['borderWrap'] : [])]"
|
||||
:style="[designObject['background'] && `background: ${designObject['background']}`]"
|
||||
>
|
||||
<div
|
||||
v-for="(component, index) in _dataResult"
|
||||
:key="index"
|
||||
:class="[...(handleActiveItem('border', 'activesBorder', index, [])['border']?.length > 0 ? handleActiveItem('border', 'activesBorder', index, [])['border'] : []), 'border-custom', isEmpty(component) ? 'empty' : 'category']"
|
||||
>
|
||||
<template v-if="!isEmpty(component)">
|
||||
<nuxt-link :to="`/${component.code}`"
|
||||
class="sm:block hidden hover:text-primary-600"
|
||||
:class="index == 0 ? 'border-b-1px border-primary-600' : 'text-black-400 font-normal'"
|
||||
:style="[
|
||||
handleActiveItem('fontSizeTitle', 'activesFontSize', index, designObject.defaultFontSizeTitle)['fontSizeTitle'] &&
|
||||
`font-size: ${handleActiveItem('fontSizeTitle', 'activesFontSize', index, designObject.defaultFontSizeTitle)['fontSizeTitle']}px`,
|
||||
handleActiveItem('fontWeightTitle', 'activesFontWeight', index, designObject.defaultFontWeightTitle)['fontWeightTitle'] &&
|
||||
`font-weight: ${handleActiveItem('fontWeightTitle', 'activesFontWeight', index, designObject.defaultFontWeightTitle)['fontWeightTitle']}`,
|
||||
designObject['color'] && `color: ${designObject['color']}`
|
||||
]"
|
||||
>
|
||||
{{ component.title }}
|
||||
</nuxt-link>
|
||||
</template>
|
||||
|
||||
<div v-else @dragover.prevent @drop.stop.prevent="dropData($event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.categories-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-end;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
|
||||
&.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
&.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
.category {
|
||||
height: 100%;
|
||||
|
||||
a {
|
||||
font-size: 15px;
|
||||
margin: 0px !important;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
a {
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<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 { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any[];
|
||||
dataQuery?: string;
|
||||
layout?: string;
|
||||
label?: string;
|
||||
content?: any;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 5,
|
||||
TEMPLATE: "TYPE:Card",
|
||||
LAYOUT: "TYPE:Card_Audio",
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
const mapActivesToItems = (index: number) => {
|
||||
if (LAYOUT_PARSE.value && LAYOUT_PARSE.value.listCss) {
|
||||
return LAYOUT_PARSE.value.listCss[index] || {};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
</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']">
|
||||
<DynamicComponent
|
||||
v-for="(component, index) in _dataResult"
|
||||
:key="index"
|
||||
:settings="{
|
||||
template: SETTING_OPTIONS.TEMPLATE,
|
||||
layout: SETTING_OPTIONS.LAYOUT,
|
||||
label: mapActivesToItems(Number(index)),
|
||||
dataResult: !isEmpty(component) ? { ...component } : null,
|
||||
}"
|
||||
:component="COMPONENT"
|
||||
@drop-data="dropData"
|
||||
/>
|
||||
<div v-html="LAYOUT_PARSE.styleClasses"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.collection-container {
|
||||
display: grid;
|
||||
&.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;
|
||||
}
|
||||
&.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;
|
||||
background: #409eff;
|
||||
}
|
||||
&.noData {
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<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 { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any[];
|
||||
dataQuery?: string;
|
||||
layout?: string;
|
||||
label?: string;
|
||||
content?: any;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 5,
|
||||
TEMPLATE: "TYPE:Card",
|
||||
LAYOUT: "TYPE:Card_Default",
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
const mapActivesToItems = (index: number) => {
|
||||
if (LAYOUT_PARSE.value && LAYOUT_PARSE.value.listCss) {
|
||||
return LAYOUT_PARSE.value.listCss[index] || {};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
</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']">
|
||||
<DynamicComponent
|
||||
v-for="(component, index) in _dataResult"
|
||||
:key="index"
|
||||
:settings="{
|
||||
template: SETTING_OPTIONS.TEMPLATE,
|
||||
layout: SETTING_OPTIONS.LAYOUT,
|
||||
label: mapActivesToItems(Number(index)),
|
||||
dataResult: !isEmpty(component) ? { ...component } : null,
|
||||
}"
|
||||
:component="COMPONENT"
|
||||
@drop-data="dropData"
|
||||
/>
|
||||
<div v-html="LAYOUT_PARSE.styleClasses"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.collection-container {
|
||||
display: grid;
|
||||
&.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;
|
||||
}
|
||||
&.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;
|
||||
background: #409eff;
|
||||
}
|
||||
&.noData {
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Default_Collection } from './Default.vue'
|
||||
export { default as Audio_Collection } from './Audio.vue'
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Default_Collection, Audio_Collection } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
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,
|
||||
};
|
||||
|
||||
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 :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
|
||||
</template>
|
||||
@@ -1 +1 @@
|
||||
export { default as Collection_Article } from './layouts/Article.vue'
|
||||
export { default as Article_Collection } from './articles/index.vue'
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
Collection_Article
|
||||
} from "./index";
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Article_Collection } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
'TYPE:Article-LAYOUT:vertical-DATA:HORIZONTAL': Collection_Article,
|
||||
[enumPageComponentTemplate[enumPageComponentKey.COLLECTION]["ARTICLE"]]: Article_Collection,
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
|
||||
const getCurrentComponent = computed(() => _props.settings.template);
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
let props: any = {};
|
||||
@@ -29,5 +29,5 @@ const GET_PROPS = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="GET_PROPS()" />
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
|
||||
</template>
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
<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 { breakpoint } from "~/definitions";
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
const { width } = useWindowSize()
|
||||
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
|
||||
|
||||
// const store = reactive({
|
||||
// section: usePageSectionStore(),
|
||||
// });
|
||||
// const { currentScreenMode } = storeToRefs(useCmsPageStore());
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any;
|
||||
dataQuery?: string;
|
||||
layout?: string;
|
||||
label?: string;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 5,
|
||||
TEMPLATE: "Article",
|
||||
LAYOUT: "TYPE:Card",
|
||||
};
|
||||
|
||||
const LAYOUT_PARSE = computed(() => {
|
||||
const parseLayout = _props.layout?.split("-")?.map((_layout: any) => {
|
||||
const parseItem = _layout.split(":");
|
||||
return {
|
||||
[parseItem[0]]: parseItem[1],
|
||||
};
|
||||
});
|
||||
const designObject = _props.label ? getInputValue(_props.label, "OBJECT") : {};
|
||||
return Object.assign({}, ...parseLayout, designObject);
|
||||
});
|
||||
|
||||
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 handleActiveItem = (listKey: string[], listKeyActive: string[], index: number, listDefaultValue: any) => {
|
||||
const designObject = _props.label ? getInputValue(_props.label, "OBJECT") : {};
|
||||
const updatedDesignObject = { ...designObject };
|
||||
|
||||
const dataDefault = {
|
||||
fontSizeTitle: "defaultFontSizeTitle",
|
||||
fontWeightTitle: "defaultFontWeightTitle",
|
||||
};
|
||||
|
||||
for (let i = 0; i < listKeyActive.length; i++) {
|
||||
const keyActive = listKeyActive[i];
|
||||
const key = listKey[i];
|
||||
const defaultValue = listDefaultValue[i] || designObject[dataDefault[key]];
|
||||
|
||||
if (Array.isArray(designObject[keyActive])) {
|
||||
const isActive = designObject[keyActive].includes(index + 1);
|
||||
updatedDesignObject[key] = isActive ? designObject[key] : defaultValue;
|
||||
} else {
|
||||
delete updatedDesignObject[key];
|
||||
}
|
||||
}
|
||||
|
||||
return updatedDesignObject;
|
||||
};
|
||||
|
||||
const mapActivesToItems = (index: number) => {
|
||||
const designObject = _props.label ? getInputValue(_props.label, "OBJECT") : {};
|
||||
const output = {};
|
||||
|
||||
designObject.layoutGrid?.forEach((item: any) => {
|
||||
item.actives.forEach((active: any) => {
|
||||
output[active] = { ...item };
|
||||
});
|
||||
});
|
||||
|
||||
return output[index + 1] || {};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="collection-container border-custom"
|
||||
:class="[LAYOUT_PARSE['LAYOUT_WRAP'] || 'vertical', ...(LAYOUT_PARSE['borderWrap']?.length > 0 ? LAYOUT_PARSE['borderWrap'] : [])]"
|
||||
@click="selectComponent"
|
||||
:style="[`grid-template-columns: repeat(${width < breakpoint.sm ? 1 : LAYOUT_PARSE['COLUMN']}, minmax(0, 1fr))`, LAYOUT_PARSE['background'] && `background: ${LAYOUT_PARSE['background']}`]"
|
||||
>
|
||||
<DynamicComponent
|
||||
v-for="(component, index) in _dataResult"
|
||||
:key="index"
|
||||
:style="[
|
||||
mapActivesToItems(index)['colSpan'] && `grid-column: span ${mapActivesToItems(index)['colSpan']} / span ${mapActivesToItems(index)['colSpan']}`,
|
||||
mapActivesToItems(index)['colStart'] && `grid-column-start: ${mapActivesToItems(index)['colStart']}`,
|
||||
mapActivesToItems(index)['colEnd'] && `grid-column-end: ${mapActivesToItems(index)['colEnd']}`,
|
||||
]"
|
||||
:settings="{
|
||||
template: SETTING_OPTIONS.TEMPLATE,
|
||||
layout: SETTING_OPTIONS.LAYOUT,
|
||||
label: handleActiveItem(['border', 'HIDE', 'fontSizeTitle', 'fontWeightTitle'], ['activesBorder', 'activesHide', 'activesFontSize', 'activesFontWeight'], index, [[], [], null, null]),
|
||||
dataResult: !isEmpty(component) ? { ...component } : null,
|
||||
}"
|
||||
@drop-data="dropData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.collection-container {
|
||||
display: grid;
|
||||
&.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;
|
||||
}
|
||||
&.vertical {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
&.horizontal {
|
||||
grid-template-rows: auto;
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
|
||||
&.noData {
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,7 @@
|
||||
|
||||
// Category
|
||||
export { default as BasicCategories } from './categories/BasicCategories.vue'
|
||||
export { default as CollectionPaging } from './pageCategories/collection_page.vue'
|
||||
|
||||
export { default as Dynamic_Other } from './other/index.vue'
|
||||
export { default as Dynamic_Section } from './sections/index.vue';
|
||||
export { default as Dynamic_Advertising } from './advertising/index.vue'
|
||||
export { default as Dynamic_Category } from './categories/index.vue'
|
||||
export { default as Dynamic_Article } from './articles/index.vue'
|
||||
export { default as Dynamic_Collection } from './collections/index.vue'
|
||||
export { default as Dynamic_Navigation } from './navigations/index.vue'
|
||||
export { default as Articles } from './articles/index.vue'
|
||||
export { default as Navigations } from './navigations/index.vue'
|
||||
export { default as Collections } from './collections/index.vue'
|
||||
export { default as Sections } from './sections/index.vue'
|
||||
export { default as Categories } from './categories/index.vue'
|
||||
export { default as Advertisings } from './advertisings/index.vue'
|
||||
export { default as Others } from './others/index.vue'
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
import { Dynamic_Section, Dynamic_Category, Dynamic_Collection, Dynamic_Navigation, Dynamic_Other, Dynamic_Advertising, Dynamic_Article } from "./index";
|
||||
import { Articles, Navigations, Collections, Sections, Categories, Advertisings, Others } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentTemplates.ARTICLE]: Dynamic_Article,
|
||||
[enumPageComponentTemplates.CATEGORY]: Dynamic_Category,
|
||||
[enumPageComponentTemplates.COLLECTION]: Dynamic_Collection,
|
||||
[enumPageComponentTemplates.SECTION]: Dynamic_Section,
|
||||
[enumPageComponentTemplates.OTHER]: Dynamic_Other,
|
||||
[enumPageComponentTemplates.ADVERTISING]: Dynamic_Advertising,
|
||||
[enumPageComponentTemplates.NAVIGATION]: Dynamic_Navigation,
|
||||
[enumPageComponentTemplates.ARTICLE]: Articles,
|
||||
[enumPageComponentTemplates.NAVIGATION]: Navigations,
|
||||
[enumPageComponentTemplates.COLLECTION]: Collections,
|
||||
[enumPageComponentTemplates.SECTION]: Sections,
|
||||
[enumPageComponentTemplates.CATEGORY]: Categories,
|
||||
[enumPageComponentTemplates.ADVERTISING]: Advertisings,
|
||||
[enumPageComponentTemplates.OTHER]: Others,
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => `${_props.settings.template}`);
|
||||
const getCurrentComponent = computed(() => _props.component?.taxonomy);
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
let props: any = {};
|
||||
if (_props.settings) {
|
||||
for (const [key, value] of _props.settings ? Object.entries(_props.settings) : []) {
|
||||
for (const [key, value] of Object.entries(_props.settings)) {
|
||||
props = {
|
||||
...props,
|
||||
[key]: value,
|
||||
@@ -34,6 +36,5 @@ const GET_PROPS = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...(GET_PROPS()), component: _props.component, settings: _props.settings }" /> -->
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import { isEmpty } from "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[];
|
||||
component?: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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 class="submenu-container">
|
||||
<h4 class="" >{{ item.title }}</h4>
|
||||
<div class="ml-2">
|
||||
<h5
|
||||
v-for="_item, _index in item.childs ? item.childs : []"
|
||||
:key="_index"
|
||||
>
|
||||
{{ _item.title }}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.empty {
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
border-radius: 4px;
|
||||
background: #409eff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
.nav-items-box {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-container {
|
||||
> div {
|
||||
margin-left: 10px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
h5 {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: white;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
// Navigation
|
||||
export { default as Navigation_Default } from './Default.vue'
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Navigation_Default } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['BOTTOM']]['NAVIGATION_BOTTOM_DEFAULT']]: Navigation_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
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,7 +1,6 @@
|
||||
<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, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
|
||||
const props = defineProps<{
|
||||
records?: any[]
|
||||
@@ -15,13 +14,11 @@ const setGlobalState = (id: any) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navigation-container flex gap-4 justify-center items-center">
|
||||
<div class="navigation-container d-flex gap-4 justify-content-center align-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">
|
||||
<nuxt-link class="hover:text-primary-600 transition-all duration-300" :to="`/${record.slug}`">
|
||||
<div class="">{{ record?.title }}</div>
|
||||
</nuxt-link>
|
||||
<div class="navigation_title " @click="selectNavigationComponent">{{ record?.title }}</div>
|
||||
<div class="navigation-item submenu-container dropdown-container">
|
||||
<RecusiveNavItem :records="record.childs" />
|
||||
</div>
|
||||
@@ -29,12 +26,12 @@ const setGlobalState = (id: any) => {
|
||||
</template>
|
||||
<template v-else-if="record.typeChild === enumPageComponentStaticChild.LAYOUT">
|
||||
<div class="navigation-submenu">
|
||||
<nuxt-link class="hover:text-primary-600 transition-all duration-300" :to="`/${record.slug}`">
|
||||
<div class="">{{ record?.title }}</div>
|
||||
</nuxt-link>
|
||||
<div class="position-relative ps-3">
|
||||
<div class="navigation_title " @click="selectNavigationComponent">{{ record?.title }}</div>
|
||||
</div>
|
||||
<div class="full-layout dropdown-container">
|
||||
<template v-if="record.data">
|
||||
<div class="p-2">
|
||||
<div class="p-1">
|
||||
<RecusiveSection type="section" :id="record.data" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -42,9 +39,7 @@ const setGlobalState = (id: any) => {
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<nuxt-link class="hover:text-primary-600 transition-all duration-300" :to="`/${record.slug}`">
|
||||
<div class="navigation-item">{{ record?.title }}</div>
|
||||
</nuxt-link>
|
||||
<div class="navigation_title navigation-item" @click="selectNavigationComponent">{{ record?.title }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,9 +47,14 @@ const setGlobalState = (id: any) => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navigation-branch {
|
||||
.navigation_title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
}
|
||||
.navigation-submenu {
|
||||
position: relative;
|
||||
padding: 12px 5px;
|
||||
padding: 15px 5px;
|
||||
&:hover {
|
||||
> .dropdown-container {
|
||||
opacity: 1;
|
||||
@@ -101,10 +101,8 @@ const setGlobalState = (id: any) => {
|
||||
top: 100%;
|
||||
}
|
||||
.full-layout {
|
||||
width: 800px;
|
||||
z-index: 100;
|
||||
// height: 400px;
|
||||
|
||||
width: 1200px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.show-menu {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
// import DynamicComponent from "~/components/cms/page-component/templates/index.vue";
|
||||
// import { getInputValue } from "@/utils/cms/page/parseSQL";
|
||||
import { isEmpty } from "lodash";
|
||||
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
|
||||
import { getInputValue } from "@/utils/parseSQL";
|
||||
|
||||
// const emit = defineEmits(["selectComponent"]);
|
||||
const emit = defineEmits(["selectComponent"]);
|
||||
|
||||
// const _props = defineProps<{
|
||||
// dataResult?: any[];
|
||||
// dataQuery?: string;
|
||||
// component?: any;
|
||||
// }>();
|
||||
const _props = defineProps<{
|
||||
dataResult?: any[];
|
||||
dataQuery?: string;
|
||||
component?: any;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 10,
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Navigation_Default } from './Default.vue'
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Navigation_Default } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['DIRECTION']]['NAVIGATION_BOTTOM_DEFAULT']]: Navigation_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
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,4 +1,4 @@
|
||||
// Navigation
|
||||
export { default as Top_Navigation } from './layouts/Top.vue'
|
||||
export { default as Bottom_Navigation } from './layouts/Bottom.vue'
|
||||
export { default as Direction_Navigation } from './layouts/Direction.vue'
|
||||
export { default as Top_Navigation } from './tops/index.vue'
|
||||
export { default as Bottom_Navigation } from './bottoms/index.vue'
|
||||
export { default as Direction_Navigation } from './directions/index.vue'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplates, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Direction_Navigation, Bottom_Navigation, Top_Navigation } from "./index";
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Top_Navigation, Bottom_Navigation, Direction_Navigation } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
@@ -9,11 +9,12 @@ const _props = defineProps<{
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[enumPageComponentTemplates.NAVIGATION]['NAVIGATION-DIRECTION']]: Direction_Navigation,
|
||||
[enumPageComponentLayouts[enumPageComponentTemplates.NAVIGATION]['NAVIGATION-BOTTOM']]: Bottom_Navigation,
|
||||
[enumPageComponentLayouts[enumPageComponentTemplates.NAVIGATION]['NAVIGATION-TOP']]: Top_Navigation,
|
||||
[enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['DIRECTION']]: Direction_Navigation,
|
||||
[enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['BOTTOM']]: Bottom_Navigation,
|
||||
[enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']]: Top_Navigation,
|
||||
};
|
||||
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
|
||||
|
||||
const getCurrentComponent = computed(() => _props.settings.template);
|
||||
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
@@ -32,5 +33,8 @@ const GET_PROPS = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }" />
|
||||
<component
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { buildTree } from "@/utils/recusive";
|
||||
|
||||
const _props = defineProps<{
|
||||
content?: any;
|
||||
component?: any;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 4,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-4">
|
||||
<div class="gap-5 grid" :style="`grid-template-columns: repeat(${SETTING_OPTIONS.MAX_ELEMENT}, minmax(0, 1fr));`">
|
||||
<template v-if="_props.content">
|
||||
<div v-for="item, index in buildTree(_props.content)" :key="index">
|
||||
<div class="submenu-container">
|
||||
<h4 class="mb-0" @click="selectNavigationComponent">{{ item.title }}</h4>
|
||||
<h4
|
||||
v-for="_item, _index in item.childs ? item.childs : []"
|
||||
:key="_index"
|
||||
class="mb-0"
|
||||
@click="selectNavigationComponent"
|
||||
>
|
||||
{{ _item.title }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.submenu-container {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
|
||||
import { buildTree } from "@/utils/recusive";
|
||||
import RecusiveNavItem from "@/components/dynamic-page/page-component/templates/navigations/components/RecusiveNavItem.vue";
|
||||
|
||||
const _props = defineProps<{
|
||||
content?: any;
|
||||
component?: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="border-y-1px border-y-[#dddddd] border-solid">
|
||||
<div class="">
|
||||
<RecusiveNavItem :records="_props.content && buildTree(_props.content)" :component="_props.component" />
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { buildTree } from "@/utils/recusive";
|
||||
import RecusiveNavItem from "@/components/dynamic-page/page-component/templates/navigations/components/RecusiveNavItem.vue";
|
||||
|
||||
const _props = defineProps<{
|
||||
content?: any[];
|
||||
component?: any;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 10,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav>
|
||||
<div class="d-flex gap-3 justify-content-end align-items-center">
|
||||
<RecusiveNavItem :records="content && buildTree(content)" :component="_props.component" />
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.empty {
|
||||
width: 100px;
|
||||
min-height: 20px;
|
||||
border-radius: 4px;
|
||||
background: #409eff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
// Navigation
|
||||
export { default as Navigation_Default } from './Default.vue'
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Navigation_Default } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']]['NAVIGATION_TOP_DEFAULT']]: Navigation_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
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,101 +0,0 @@
|
||||
<template>
|
||||
<div class="mt-4">
|
||||
<div class="input_comment width_common mb-2">
|
||||
<div class="box-area-input width_common">
|
||||
<textarea id="txtComment" class="block_input"
|
||||
placeholder="* Bình luận của bạn sẽ được biên tập trước khi đăng. Xin vui lòng gõ tiếng Việt có dấu"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<button type="button" class="mr-2 p-2 bg-blue-500 text-[#fff] rounded text-xs">
|
||||
Gửi bình luận
|
||||
<Icon name="ri:send-plane-2-fill"></Icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
.input_comment {
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
background: #f5f5f5;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-top: 1px solid #dedede;
|
||||
border-right: 1px solid #dedede;
|
||||
border-bottom: 1px solid #dedede;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box-area-input {
|
||||
background: #f7f7f7;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
padding: 10px 0 10px 0;
|
||||
border-left: 2px solid rgba(59, 130, 246, 1);
|
||||
}
|
||||
|
||||
|
||||
.input_comment textarea.block_input {
|
||||
height: 30px;
|
||||
-webkit-transition-duration: 200ms;
|
||||
transition-duration: 200ms;
|
||||
-webkit-transition-property: all;
|
||||
transition-property: all;
|
||||
-webkit-transition-timing-function: cubic-bezier(0.7, 1, 0.7, 1);
|
||||
transition-timing-function: cubic-bezier(0.7, 1, 0.7, 1);
|
||||
}
|
||||
|
||||
.input_comment textarea.block_input {
|
||||
height: 76px;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.box-area-input .block_input {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.input_comment textarea {
|
||||
font: 400 16px/150% arial;
|
||||
background: #fff;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 58px;
|
||||
color: #4f4f4f !important;
|
||||
overflow: hidden;
|
||||
padding: 5px 37px 0 15px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"],
|
||||
input[type="tel"],
|
||||
textarea,
|
||||
select {
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 14px;
|
||||
margin: 3px 0;
|
||||
padding: 0 5px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
font-family: arial;
|
||||
font-size: 11px;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,113 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { breakpoint } from '~/definitions';
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
const { width } = useWindowSize()
|
||||
import { useArticleStore } from '~/stores/articles';
|
||||
import { useDynamicPageStore } from "~/stores/dynamic-page";
|
||||
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'
|
||||
const { currentArticle } = storeToRefs(useArticleStore());
|
||||
const { step } = storeToRefs(useDynamicPageStore());
|
||||
|
||||
const router = useRouter()
|
||||
// import * as cherrio from 'cheerio'
|
||||
|
||||
// const $ = cherrio.load(currentArticle.value.detail)
|
||||
// for(let index = 0; index < $('articlerelation').length, index++) {
|
||||
// $('articlerelation')[index]
|
||||
// }
|
||||
// console.log($('articlerelation').length, 'cherrip')
|
||||
// onBeforeMount(async () => {
|
||||
// await useArticleStore().getArticleCondition({ids: [1, 2, 3]})
|
||||
// })
|
||||
// console.log(router,'route')
|
||||
onMounted(() => {
|
||||
// const elements = document.querySelectorAll('custom-figure')
|
||||
// elements.forEach((element) => {
|
||||
// element.addEventListener('click', (event) => {
|
||||
// event.preventDefault();
|
||||
// console.log(element, 'element')
|
||||
// const url = `figure/${element.getAttribute('data-code')}`;
|
||||
|
||||
// const a = document.createElement('a')
|
||||
// a.href = url;
|
||||
// document.body.appendChild(a)
|
||||
// a.click();
|
||||
// document.body.removeChild(a);
|
||||
// })
|
||||
// })
|
||||
|
||||
clickElement('figure', 'custom-figure', 'data-code')
|
||||
clickElement('author', 'author', 'data-code')
|
||||
})
|
||||
|
||||
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 fileName = ref('')
|
||||
// onMounted(() => {
|
||||
// const documentElements = document.querySelectorAll('document, attachment')
|
||||
|
||||
// console.log(documentElements, 'doc')
|
||||
|
||||
// if(documentElements.length > 0) {
|
||||
// documentElements.forEach((doc) => {
|
||||
// doc.addEventListener('click', (event) => {
|
||||
// event.preventDefault();
|
||||
// const url = doc.getAttribute('data-resource');
|
||||
// const file = doc.getAttribute('data-resource') ? doc.getAttribute('data-resource')?.toString().split('.').pop() : 'docx'
|
||||
// fileName.value = `${doc.getAttribute('data-title')}.${file}` ;
|
||||
// console.log(url , fileName.value, '123')
|
||||
// if(url && fileName.value) {
|
||||
// const a = document.createElement('a');
|
||||
// a.href = url?.toString();
|
||||
// a.download = fileName.value;
|
||||
// document.body.appendChild(a);
|
||||
// a.click();
|
||||
// document.body.removeChild(a);
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
<template>
|
||||
<div class="content" v-if="currentArticle">
|
||||
<h1 id="sub" v-html="currentArticle?.sub" class=" font-bold opacity-60 pb-1"></h1>
|
||||
<h3 id="title" class="font-bold pb-1" v-html="currentArticle?.title"></h3>
|
||||
<p id="published-on" class="text-gray-600 mb-3">{{ utils.dateFormat(currentArticle?.publishedOn, "dddd, DD/MM/YYYY - HH:mm") }}</p>
|
||||
<div id="intro" v-if="currentArticle?.intro" v-html="currentArticle?.intro" class="font-semibold tracking-widest pb-1 mb-3"></div>
|
||||
<component :is="{template: currentArticle.detail, components: { Poll, Quiz, Survey, Document, Attachment, Tag} }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#sub, #intro, #intro + div {
|
||||
font-size: calc(16px + var(--step) * 1px);
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: calc(28px + var(--step) * 1px);
|
||||
}
|
||||
|
||||
#published-on {
|
||||
font-size: calc(14px + var(--step) * 1px);
|
||||
}
|
||||
</style>
|
||||
@@ -1,25 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useArticleStore } from '~/stores/articles';
|
||||
import { breakpoint } from '~/definitions';
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
const { width } = useWindowSize()
|
||||
import { useDynamicPageStore } from "~/stores/dynamic-page";
|
||||
const { currentArticle } = storeToRefs(useArticleStore());
|
||||
const { step } = storeToRefs(useDynamicPageStore());
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="content" v-if="currentArticle">
|
||||
<h1 id="sub" v-html="currentArticle?.sub" class=" font-bold opacity-60 pb-1" :style="{ 'font-size': `${16 + Number(step)}px`}"></h1>
|
||||
<h3 id="title" :style="{ 'font-size': width > breakpoint.lg ? `${32 + Number(step)}px` : `${20 + Number(step)}px`}" class="font-bold pb-1" v-html="currentArticle?.title"></h3>
|
||||
<p id="published-on" class="text-gray-600 mb-3" :style="{ 'font-size': `${14 + Number(step)}px` }">{{ utils.dateFormat(currentArticle?.publishedOn, "dddd, DD/MM/YYYY - HH:mm") }}</p>
|
||||
|
||||
<div id="intro" v-if="currentArticle?.intro" v-html="currentArticle?.intro" class="font-semibold tracking-widest pb-1 mb-3" :style="{'font-size': `${16 + Number(step)}px`}"></div>
|
||||
<div id="article-detail" :class="' tracking-wider'" v-html="currentArticle.detail" class="[&_img]:mx-auto" :style="{ 'font-size': `${16 + Number(step)}px`}"> </div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#title {}
|
||||
</style>
|
||||
@@ -1,29 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useArticleStore } from '~/stores/articles';
|
||||
import { breakpoint } from '~/definitions';
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
const { width } = useWindowSize()
|
||||
import { useDynamicPageStore } from "~/stores/dynamic-page";
|
||||
const { currentArticle } = storeToRefs(useArticleStore());
|
||||
const { step } = storeToRefs(useDynamicPageStore());
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="content" v-if="currentArticle">
|
||||
<h1 id="sub" v-html="currentArticle?.sub" class=" font-bold opacity-60 pb-1" :style="{ 'font-size': `${16 + Number(step)}px`}"></h1>
|
||||
<h3 id="title" :style="{ 'font-size': width > breakpoint.lg ? `${32 + Number(step)}px` : `${20 + Number(step)}px`}" class="font-bold pb-1" v-html="currentArticle?.title"></h3>
|
||||
<p id="published-on" class="text-gray-600 mb-3" :style="{ 'font-size': `${14 + Number(step)}px` }">{{ utils.dateFormat(currentArticle?.publishedOn, "dddd, DD/MM/YYYY - HH:mm") }}</p>
|
||||
|
||||
<!-- <div class="author">Tác giả - Thời gian tạo</div> -->
|
||||
<div id="intro" v-if="currentArticle?.intro" v-html="currentArticle?.intro" class="font-semibold tracking-widest mb-3" :style="{'font-size': `${16 + Number(step)}px`}"></div>
|
||||
<div id="article-detail" :class="'text-[16px] tracking-wider'" v-html="currentArticle.detail" class="[&_img]:mx-auto" :style="{ 'font-size': `${16 + Number(step)}px`}"> </div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,149 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import AudioPlayer from "~/organisms/audioPlayer/AudioPlayer.vue";
|
||||
const { currentArticle } = storeToRefs(useArticleStore());
|
||||
import Topic from "@/components/article/Topic.vue";
|
||||
import Event from "@/components/article/Event.vue";
|
||||
import Tag from "@/components/article/Tag.vue";
|
||||
|
||||
const getSrc = (htmlString: string) => {
|
||||
const srcRegex = /src="([^"]+)"/;
|
||||
return htmlString?.match(srcRegex);
|
||||
};
|
||||
// const getArticleById = async (articleId: number) => {
|
||||
// try {
|
||||
// const { apiUrl } = useRuntimeConfig().public;
|
||||
// const { item }: any = await $fetch(`${apiUrl}/cms/digital-article/${articleId}`, {
|
||||
// headers: {
|
||||
// Site: "1",
|
||||
// },
|
||||
// });
|
||||
// return item;
|
||||
// } catch (error) {
|
||||
// handleError(error);
|
||||
// }
|
||||
// };
|
||||
const store = reactive({
|
||||
tag: useTagStore(),
|
||||
topic: useTopicStore(),
|
||||
event: useEventStore()
|
||||
});
|
||||
|
||||
// const listTag = ref([]);
|
||||
// const listTopic = ref([]);
|
||||
// const listEvent = ref([])
|
||||
|
||||
// const getTagsAndTopicsAndEvents = async () => {
|
||||
// if (!currentArticle) return;
|
||||
|
||||
// const fetchData = async (ids, fetchFn, list) => {
|
||||
// if (!ids) return;
|
||||
// const data = await Promise.all(ids.split(",").map(fetchFn));
|
||||
// if (data.length > 0) list.value = data;
|
||||
// };
|
||||
|
||||
// await Promise.all([
|
||||
// fetchData(currentArticle.tagIds, store.tag.fetchById, listTag),
|
||||
// fetchData(currentArticle.topicIds, store.topic.fetchById, listTopic),
|
||||
// fetchData(currentArticle.eventIds, store.event.fetchById, listEvent)
|
||||
// ]);
|
||||
// };
|
||||
|
||||
// getTagsAndTopicsAndEvents();
|
||||
const listArticle = ref([]);
|
||||
const audioPlay = ref({});
|
||||
const defaultClass = {
|
||||
article: ["group", "max-w-full", "grid", "gap-3", "overflow-hidden", "p-4"],
|
||||
thumbnail: ["rounded-3xl", "shadow-md", "max-w-full", "w-full", "aspect-5/3", "group-hover:scale-[1.05]", "duration-500", "ease-in-out", "object-cover"],
|
||||
title: ["font-bold", "px-4", "md:px-0", "xl:text-xl", "text-base"],
|
||||
brief: ["text-sm", "sm:text-base", "mx-4", "pb-4", "md:pb-0", "md:mx-0", "border-b", "border-stone-400", "md:border-none"],
|
||||
};
|
||||
// const getListArticle = async () => {
|
||||
// if (currentArticle && currentArticle.audioIds) {
|
||||
// const audioIds = currentArticle.audioIds.split(",").map(Number);
|
||||
// const articles = await Promise.all(audioIds.map(async (audioId) => await getArticleById(audioId)));
|
||||
// if (articles.length > 0) {
|
||||
// listArticle.value = articles;
|
||||
// audioPlay.value = articles[0];
|
||||
// }
|
||||
// }
|
||||
// // const test = "8,9";
|
||||
// };
|
||||
// getListArticle();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full grid grid-cols-12 gap-4" v-if="currentArticle">
|
||||
<div class="col-span-12 h-60 md:h-100 relative bg-center" :style="'background-image: url(' + currentArticle?.thumbnail + '); background-size: cover;'">
|
||||
<div class="absolute inset-0 bg-black opacity-80 z-1"></div>
|
||||
<div class="w-full mx-auto px-4 max-w-6xl relative flex items-center justify-center">
|
||||
<div class="w-full h-40 md:h-80 absolute inset-0 z-2">
|
||||
<div class="grid grid-cols-10 w-full">
|
||||
<div class="col-span-3 flex justify-center items-center h-60 md:h-80 mx-2 md:mx-8">
|
||||
<div
|
||||
class="h-40 md:h-60 w-full rounded-tl-3xl rounded-br-3xl border-double px-2 overflow-x-hidden relative overflow-y-hidden bg-cover z-1 after:z-2 after:content-[''] after:w-full after:h-full after:top-0 after:left-0 after:bg-#000 after:opacity-30 after:absolute"
|
||||
:style="{ backgroundImage: `url(${currentArticle?.thumbnail})` }"
|
||||
>
|
||||
<img :src="currentArticle?.thumbnail" alt="" class="relative z-3 h-40 md:h-60 w-full object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-7 grid grid-cols-12 relative">
|
||||
<div class="col-span-12 w-full grid grid-cols-12 mt-8 md:mb-4">
|
||||
<div class="col-span-11">
|
||||
<h1 class="text-md md:text-3xl text-[#fff] font-bold font-['SFD']" v-html="currentArticle?.title"></h1>
|
||||
<time class="xs:mt-0.5 text-[10px] md:text-sm text-[#fff]">
|
||||
{{ utils.dateFormat(currentArticle?.createdOn, "dddd, DD/MM/YYYY - HH:mm") }}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 w-full mb-4 hidden md:block">
|
||||
<div v-html="currentArticle?.intro" class="text-left text-xl text-[#fff] font-['SFD']"></div>
|
||||
</div>
|
||||
<div class="col-span-11">
|
||||
<AudioPlayer :src="getSrc(currentArticle?.detail)?.[1]" />
|
||||
<!-- <Topic :topics="listTopic" />
|
||||
<Event :events="listEvent" />
|
||||
<Tag :tags="listTag" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="md:col-span-3 col-span-12" v-if="listArticle?.length > 1">
|
||||
<div class="flex items-center mb-5">
|
||||
<ul class="bg-red-500 text-white text-sm font-semibold hover:bg-red-400 font-medium inline-block rounded-tl-lg rounded-br-lg">
|
||||
<li class="inline-block uppercase rounded-l-lg border-radius border-red-500 border-r-0 px-2 py-1 text-center block transition-transform duration-300 transform hover:scale-105">Podcast Hôm nay</li>
|
||||
</ul>
|
||||
<div class="border border-slate-7 flex-grow ml-4"></div>
|
||||
</div>
|
||||
<div class="grid w-full">
|
||||
<div class="" v-for="(item, index) in listArticle.filter((item) => item.id !== audioPlay.id)" :key="index">
|
||||
<article mode="basic" :class="defaultClass.article" @click="audioPlay = { ...item }">
|
||||
<div class="rounded-sm overflow-hidden relative">
|
||||
<NuxtImg :src="item?.thumbnail || '/images/default-thumbnail.jpg'" placeholder fit="cover" :class="defaultClass.thumbnail" loading="lazy" />
|
||||
<div class="absolute bottom-2 left-0 bg-stone-200/[.56] h-10 flex justify-center items-center w-20 rounded-full">
|
||||
<span class="icon">
|
||||
<svg width="30" height="30" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.0131 0.5C5.38869 0.5 0 5.88331 0 12.5C0 19.1167 5.38869 24.5 12.0131 24.5C18.6376 24.5 24.0263 19.1167 24.0263 12.5C24.0263 5.88331 18.6376 0.5 12.0131 0.5ZM16.7889 12.9204L9.78122 17.4204C9.6991 17.4736 9.60426 17.5 9.51041 17.5C9.42829 17.5 9.34518 17.4795 9.2709 17.439C9.10957 17.3511 9.00985 17.1831 9.00985 17V8C9.00985 7.81691 9.10957 7.64891 9.2709 7.56102C9.42927 7.47411 9.62773 7.47945 9.78122 7.57958L16.7889 12.0796C16.9316 12.1714 17.0186 12.3301 17.0186 12.5C17.0186 12.6699 16.9316 12.8286 16.7889 12.9204Z"
|
||||
fill="#FF0000"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="xs:mt-0.5 xs:text-sm text-sm">{{ utils.dateFormat(item?.createdOn) }}</p>
|
||||
<h3 :class="defaultClass.title" v-html="item?.title"></h3>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.name {
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,70 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import Comment from "@/components/dynamic-page/page-component/templates/other/comments/default.vue";
|
||||
import Topic from "@/components/article/Topic.vue";
|
||||
import Event from "@/components/article/Event.vue";
|
||||
import Tag from "@/components/article/Tag.vue";
|
||||
|
||||
const { currentArticle } = storeToRefs(useArticleStore());
|
||||
const store = reactive({
|
||||
tag: useTagStore(),
|
||||
topic: useTopicStore(),
|
||||
event: useEventStore()
|
||||
});
|
||||
|
||||
// const listTag = ref([]);
|
||||
// const listTopic = ref([]);
|
||||
// const listEvent = ref([])
|
||||
|
||||
// const getTagsAndTopicsAndEvents = async () => {
|
||||
// if (!currentArticle) return;
|
||||
// const fetchData = async (ids, fetchFn, list) => {
|
||||
// if (!ids) return;
|
||||
// const data = await Promise.all(ids.split(",").map(fetchFn));
|
||||
// if (data.length > 0) list.value = data;
|
||||
// };
|
||||
// await Promise.all([
|
||||
// fetchData(currentArticle.value.tagIds, store.tag.fetchById, listTag),
|
||||
// fetchData(currentArticle.value.topicIds, store.topic.fetchById, listTopic),
|
||||
// fetchData(currentArticle.value.eventIds, store.event.fetchById, listEvent)
|
||||
// ]);
|
||||
// };
|
||||
|
||||
// getTagsAndTopicsAndEvents();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-1500px mx-auto">
|
||||
<article class="w-full flex flex-col lg:flex-row gap-4 overflow-x-hidden mt-4 bg-#f7f7f7">
|
||||
<div id="article-detail" class="flex-1 [&_iframe]:w-full [&_iframe]:max-w-full [&_iframe]:max-h-52 md:[&_iframe]:max-h-full [&_video]:max-w-full [&_video]:w-full">
|
||||
<div v-html="currentArticle?.detail" />
|
||||
</div>
|
||||
<div class="lg:w-[480px] overflow-y-auto lg:max-h-560px">
|
||||
<div class="w-full pt-6 pr-3">
|
||||
<h1 v-html="currentArticle?.sub" class="text-xl font-bold opacity-60"></h1>
|
||||
<h1 v-html="currentArticle?.title" class="text-2xl font-bold text-left sm:text-3xl xl:text-4xl" />
|
||||
<!-- <ArticleMeta class="!justify-start items-center gap-x-2" :authors="article?.authors" :createdOn="article?.createdOn" :createdBy="article?.createdBy" /> -->
|
||||
|
||||
<div id="article-brief" class="mx-auto xl:max-w-6xl text-balance">
|
||||
<div v-html="currentArticle?.intro" class="font-semibold text-left" />
|
||||
</div>
|
||||
<!-- <section>
|
||||
<article class="mb-[1rem] py-[1rem] border-y-[1px] border-solid border-[#e0e0e0] flex items-center">
|
||||
<iframe
|
||||
:src="`https://www.facebook.com/plugins/like.php?href=${ORIGIN}/${category?.code}/${article?.code}&width=160&layout=button&action=like&size=small&share=true&height=65&appId`"
|
||||
width="140" height="20" style="border:none;overflow:hidden" scrolling="no" frameborder="0"
|
||||
allowfullscreen="true"
|
||||
allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"></iframe>
|
||||
</article>
|
||||
</section> -->
|
||||
<!-- <Topic :topics="listTopic" />
|
||||
<Event :events="listEvent" />
|
||||
<Tag :tags="listTag" /> -->
|
||||
<section id="comment-section" class="grid">
|
||||
<Comment :articleId="currentArticle?.articleId" />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<div class="w-full border-t-2 border-dashed mt-4" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +0,0 @@
|
||||
export { default as Article_Detail_Emagazine } from './details/emagazine.vue'
|
||||
export { default as Article_Detail_Default } from './details/default.vue'
|
||||
export { default as Article_Detail_Infographics } from './details/infographics.vue'
|
||||
export { default as Article_Detail_Podcast } from './details/podcast.vue'
|
||||
export { default as Article_Detail_Video } from './details/video.vue'
|
||||
export { default as Comment } from './comments/default.vue'
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="comment">
|
||||
<div class="input_comment width_common mb-2">
|
||||
<div class="box-area-input width_common">
|
||||
<textarea id="txtComment" class="block_input" placeholder="* Bình luận của bạn sẽ được biên tập trước khi đăng. Xin vui lòng gõ tiếng Việt có dấu"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<button type="button" class="send-comment">
|
||||
Gửi bình luận
|
||||
<Icon name="ri:send-plane-2-fill"></Icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.comment {
|
||||
pointer-events: none;
|
||||
}
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.send-comment {
|
||||
padding: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
background-color: #409eff;
|
||||
border: 1px solid;
|
||||
color: #fff;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 80rem;
|
||||
&.h3 {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
.input_comment {
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
border-top: 1px solid #dedede;
|
||||
border-right: 1px solid #dedede;
|
||||
border-bottom: 1px solid #dedede;
|
||||
}
|
||||
|
||||
.box-area-input {
|
||||
/* background: #f3f6f9; */
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
padding: 5px 10px;
|
||||
border-left: 2px solid rgba(59, 130, 246, 1);
|
||||
}
|
||||
|
||||
.input_comment textarea.block_input {
|
||||
height: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
textarea::placeholder {
|
||||
color: #878a99;
|
||||
}
|
||||
.input_comment textarea.block_input {
|
||||
height: 58px;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/* .box-area-input .block_input {
|
||||
background: #f7f7f7;
|
||||
} */
|
||||
|
||||
.input_comment textarea {
|
||||
background: #fff;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 58px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +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'
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Other_Weather, Other_Secutities } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentTemplate[enumPageComponentKey.OTHER]["WEATHER"]]: Other_Weather,
|
||||
[enumPageComponentTemplate[enumPageComponentKey.OTHER]["SECURITIES"]]: Other_Secutities,
|
||||
// [enumPageComponentTemplate[enumPageComponentKey.ARTICLE]["ARTICLE_DETAIL"]]: Article_Detail,
|
||||
};
|
||||
const getCurrentComponent = 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 :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<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>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Securities_Default } from './Securities.vue'
|
||||
@@ -1,23 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
import { Article_Detail_Emagazine, Article_Detail_Default, Article_Detail_Infographics, Comment, Article_Detail_Podcast, Article_Detail_Video
|
||||
} from "./index";
|
||||
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 { Securities_Default } from "./index";
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
'ARTICLE_DETAIL_DEFAULT': Article_Detail_Default,
|
||||
'ARTICLE_DETAIL_INFOGRAPHICS': Article_Detail_Infographics,
|
||||
'ARTICLE_DETAIL_EMAGAZINE': Article_Detail_Emagazine,
|
||||
'COMMENT_DEFAULT': Comment,
|
||||
PODCAST: Article_Detail_Podcast,
|
||||
VIDEO: Article_Detail_Video
|
||||
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.OTHER]["SECURITIES"]]["SECURITIES_DEFAULT"]]: Securities_Default,
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
|
||||
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
let props: any = {};
|
||||
@@ -35,5 +30,5 @@ const GET_PROPS = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="GET_PROPS()" />
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
|
||||
</template>
|
||||
@@ -0,0 +1,316 @@
|
||||
<script setup lang="ts">
|
||||
// import axios from "axios";
|
||||
// async function getAllWeatherCity() {
|
||||
// try {
|
||||
// const res = await axios.get();
|
||||
// console.log('res',res)
|
||||
// } catch (err) {
|
||||
// }
|
||||
// }
|
||||
// onBeforeMount(async()=>{
|
||||
// await getAllWeatherCity()
|
||||
// })
|
||||
</script>
|
||||
<template>
|
||||
<div class="box-info-weather flexbox" id="overview">
|
||||
<div class="box-info-weather__left">
|
||||
<span class="weather-day-current">Hiện tại</span>
|
||||
|
||||
<div class="weather-day">
|
||||
<img data-v-b1c9218b="" src="https://cdn.weatherapi.com/weather/64x64/day/116.png" alt="Weather Icon">
|
||||
<div class="big-temp">28°</div>
|
||||
<div class="name">Mưa nhẹ</div>
|
||||
</div>
|
||||
<div class="color-gray-2">
|
||||
<p>Cao: 28° Thấp: 24°</p>
|
||||
<div>
|
||||
<span>Không khí:</span>
|
||||
<div class="weather-tooltip">
|
||||
<span class="weather-tooltip-group">
|
||||
<span class="quality-2">Trung bình</span>
|
||||
<Icon class="ic ic-help" name="material-symbols:help-outline"></Icon>
|
||||
</span>
|
||||
<div class="box-info-hover" data-left="-71px">
|
||||
<div class="title">
|
||||
<span class="header_tooltip">
|
||||
<Icon class="ic ic-help" name="material-symbols-light:airware"></Icon>
|
||||
<span>Chất lượng không khí</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid__2 chat-luong mb15">
|
||||
<div class="quality-2">
|
||||
<div class="lbl">Trung bình</div>
|
||||
</div>
|
||||
<div class="quality-2">
|
||||
<p>
|
||||
Nồng độ bụi mịn<br />
|
||||
<span class="lbl">PM2.5: 30.42</span> <br />(μg/m3)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p>Chất lượng không khí có thể chấp nhận được, tuy nhiên một số người nhạy cảm vẫn nên lưu ý.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<Icon class="ic ic-help" name="material-symbols:help-outline"></Icon>
|
||||
</span>
|
||||
<div class="box-info-hover">
|
||||
<div class="title">
|
||||
<span class="header_tooltip">
|
||||
<Icon class="ic ic-help" name="fxemoji:thermometer"></Icon>
|
||||
<span>Nhiệt độ cảm nhận</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="scroll-height">
|
||||
<p>Nhiệt độ cảm nhận (heat index) là nhiệt độ cơ thể con người cảm thấy trong thực tế, được tính dựa trên dữ liệu nhiệt độ kết hợp với độ ẩm.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Độ ẩm: 86%</p>
|
||||
|
||||
<div>
|
||||
<span>UV: </span>
|
||||
<div class="weather-tooltip">
|
||||
<span class="weather-tooltip-group">
|
||||
<span> 5 / 11 </span>
|
||||
<Icon class="ic ic-help" name="material-symbols:help-outline"></Icon>
|
||||
</span>
|
||||
|
||||
<div class="box-info-hover">
|
||||
<div class="title">
|
||||
<span class="header_tooltip">
|
||||
<Icon name="material-symbols:sunny-outline"></Icon>
|
||||
<span>Chỉ số UV</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="scroll-height">
|
||||
<ul class="chi-so">
|
||||
<li class="item quality-1">
|
||||
<div class="lbl d-flex justify-content-between">
|
||||
<div>1 → 2</div>
|
||||
<div>Thấp</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="item quality-2">
|
||||
<div class="lbl d-flex justify-content-between">
|
||||
<div>3 → 5</div>
|
||||
<div>Trung bình</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="item quality-3">
|
||||
<div class="lbl d-flex justify-content-between">
|
||||
<div>6 → 7</div>
|
||||
<div>Cao</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="item quality-4">
|
||||
<div class="lbl d-flex justify-content-between">
|
||||
<div>8 → 10</div>
|
||||
<div>Rất cao</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="item quality-5">
|
||||
<div class="lbl d-flex justify-content-between">
|
||||
<div>11+</div>
|
||||
<div>Cực điểm</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Theo Cơ quan Bảo vệ Môi trường Mỹ (EPA), chỉ số UV dao động 0-2 được xem là thấp, chỉ số 8-10 có thời gian tiếp xúc gây bỏng là 25 phút. Chỉ số UV từ 11 trở lên được xem là cực kỳ cao, rất nguy hiểm, nguy cơ làm tổn thương
|
||||
da, mắt bị bỏng nếu tiếp xúc ánh nắng mặt trời trong khoảng 15 phút mà không được bảo vệ.
|
||||
</p>
|
||||
<p>
|
||||
Tiếp xúc quá mức với ánh sáng mặt trời trong thời gian ngắn sẽ gây bỏng nắng, tổn thương mắt như đục thủy tinh thể, da bị bỏng, khô, sạm, tạo nếp nhăn, lão hóa nhanh. Tiếp xúc tia UV lâu dài, tích lũy có thể gây ung thư da.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.box-info-weather {
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
min-height: 240px;
|
||||
&.flexbox {
|
||||
align-items: end;
|
||||
display: flex;
|
||||
}
|
||||
&__left {
|
||||
width: 50%;
|
||||
.weather-day {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 19px;
|
||||
.ic-weather {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
.big-temp {
|
||||
font: 400 48px "Merriweather", serif;
|
||||
font-feature-settings: "pnum" on, "lnum" on;
|
||||
}
|
||||
.name {
|
||||
width: 100%;
|
||||
font: 400 20px "Merriweather", serif;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
.color-gray-2 {
|
||||
color: #757575;
|
||||
}
|
||||
span.weather-day-current {
|
||||
color: #9f9f9f;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&__right {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
.ic {
|
||||
fill: #757575;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ic-help {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
.mb40 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.weather-tooltip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: -15px;
|
||||
display: inline-block;
|
||||
.quality-2 {
|
||||
color: #c6990c;
|
||||
}
|
||||
&:hover .box-info-hover {
|
||||
top: 30px;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.box-info-hover {
|
||||
font-family: Arial;
|
||||
line-height: 1.5;
|
||||
white-space: normal;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
padding: 16px;
|
||||
background: #ffffff;
|
||||
width: 300px;
|
||||
text-align: left;
|
||||
border-radius: 8px;
|
||||
font-weight: 400;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition-duration: 300ms;
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.7, 1, 0.7, 1);
|
||||
filter: drop-shadow(0px 2px 12px rgba(0, 0, 0, 0.2));
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
margin-bottom: 18px;
|
||||
color: #222;
|
||||
.header_tooltip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.lbl {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
.scroll-height {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
scrollbar-color: #e5e5e5 #fff;
|
||||
scrollbar-width: thin;
|
||||
.item {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.quality-1 {
|
||||
background: rgba(36, 161, 72, 0.1);
|
||||
color: #24a148;
|
||||
}
|
||||
.quality-2 {
|
||||
background: rgba(198, 153, 12, 0.1);
|
||||
color: #c6990c;
|
||||
}
|
||||
.quality-3 {
|
||||
background: rgba(224, 120, 47, 0.1);
|
||||
color: #e0782f;
|
||||
}
|
||||
.quality-4 {
|
||||
background: rgba(220, 78, 85, 0.1);
|
||||
color: #dc4e55;
|
||||
}
|
||||
.quality-5 {
|
||||
background: rgba(164, 78, 201, 0.1);
|
||||
color: #a44ec9;
|
||||
}
|
||||
}
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
.mb15 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.grid__2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 8px;
|
||||
row-gap: 8px;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
position: relative;
|
||||
}
|
||||
.chat-luong [class*='quality-'] {
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
background: rgba(198, 153, 12, 0.1);
|
||||
color: #C6990C;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
// Navigation
|
||||
export { default as WeatherDay } from './WeatherDay.vue'
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { WeatherDay } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
content?: any
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[enumPageComponentTemplate[enumPageComponentKey.NAVIGATION]['TOP']]['NAVIGATION_TOP_DEFAULT']]: WeatherDay,
|
||||
};
|
||||
|
||||
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
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings, content: _props.content }"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,159 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { isEmpty } from "@/utils/lodash";
|
||||
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
|
||||
import { COLLECTION_PAGING_QUERY_DROP, getInputValue } from "@/utils/parseSQL";
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any;
|
||||
dataQuery?: string;
|
||||
component?: any;
|
||||
}>();
|
||||
|
||||
const SETTING_OPTIONS = {
|
||||
MAX_ELEMENT: 5,
|
||||
TEMPLATE: "Article",
|
||||
LAYOUT: "LAYOUT:horizontal",
|
||||
};
|
||||
|
||||
// const page = ref(1);
|
||||
const limit = ref(1);
|
||||
const totals = ref(1);
|
||||
const type = "Article";
|
||||
|
||||
const listArticleByCategory = computed(() => {
|
||||
return getInputValue(_props.dataResult, "ARRAY");
|
||||
});
|
||||
|
||||
//?cpn_1=page:2&cpn_2=page:1
|
||||
// Get[Article] Top[5] With[Categories:1]
|
||||
const select = (page: number) => {
|
||||
const componentId = _props.component?.id;
|
||||
if (componentId) {
|
||||
router.push({
|
||||
query: {
|
||||
...route.query,
|
||||
[`cpn_${componentId}`]: `page:${page}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleRouteChange = (query: any) => {
|
||||
const [_, value] = query[`cpn_${_props.component?.id}`]?.split(":");
|
||||
if (value) {
|
||||
loadPage(Number(value));
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (route.query[`cpn_${_props.component?.id}`]) handleRouteChange(route.query);
|
||||
});
|
||||
|
||||
const loadPage = (page: string | number) => {
|
||||
// console.log(`Loading page ${page}`);
|
||||
// listArticleByCategory.value =
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery) => {
|
||||
handleRouteChange(newQuery);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<div class="section-container" @click="selectComponent" @dragover.prevent @drop.stop.prevent="dropData($event)" :class="[listArticleByCategory && listArticleByCategory?.length > 0 ? '' : 'noData']">
|
||||
<div class="collection-container">
|
||||
<template v-if="listArticleByCategory?.length > 0">
|
||||
<template v-for="(component, index) in listArticleByCategory" :key="index">
|
||||
<DynamicComponent
|
||||
v-if="!isEmpty(component)"
|
||||
:settings="{
|
||||
template: SETTING_OPTIONS.TEMPLATE,
|
||||
layout: SETTING_OPTIONS.LAYOUT,
|
||||
dataResult: { ...component },
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="empty"><h6>Nội dung danh sách bài viết của danh mục sẽ ở đây</h6></div>
|
||||
</template>
|
||||
<div class="button-page flex">
|
||||
<a class="btn-page prev-page">
|
||||
<Icon name="material-symbols:arrow-back-ios-new-rounded"/>
|
||||
</a>
|
||||
<a class="btn-page" @click="() => select(index + 1)" v-for="(_, index) in totals" :key="index">{{ index + 1 }}</a>
|
||||
<a class="btn-page next-page">
|
||||
<Icon class="rotate-180deg" name="material-symbols:arrow-back-ios-new-rounded"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.section-container {
|
||||
.collection-container {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.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 {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
&.noData {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
justify-content: center;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.button-page {
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-page {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
border: 1px solid #409eff;
|
||||
border-radius: 3px;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color:#409eff;
|
||||
}
|
||||
}
|
||||
.el-empty {
|
||||
padding: 12px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { isEmpty } from "@/utils/lodash";
|
||||
import { isEmpty } from "lodash";
|
||||
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
|
||||
import { COLLECTION_PAGING_QUERY_DROP, getInputValue } from "@/utils/parseSQL";
|
||||
const router = useRouter();
|
||||
@@ -7,8 +7,12 @@ const route = useRoute();
|
||||
|
||||
const emit = defineEmits(["dropData", "selectComponent"]);
|
||||
|
||||
const store = reactive({
|
||||
component: usePageComponentStore(),
|
||||
});
|
||||
|
||||
const _props = defineProps<{
|
||||
dataResult?: any;
|
||||
dataResult?: any[];
|
||||
dataQuery?: string;
|
||||
component?: any;
|
||||
label?: string;
|
||||
@@ -20,13 +24,19 @@ const SETTING_OPTIONS = {
|
||||
LAYOUT: "TYPE:Card",
|
||||
};
|
||||
|
||||
// const page = ref(1);
|
||||
const limit = ref(1);
|
||||
const totals = ref(1);
|
||||
const page = ref(1);
|
||||
const limit = ref(5);
|
||||
const totals = ref(2);
|
||||
const type = "Article";
|
||||
const listArticlePaging = ref([]);
|
||||
|
||||
const listArticleByCategory = computed(() => {
|
||||
return getInputValue(_props.dataResult, "ARRAY");
|
||||
const data = getInputValue(_props.dataResult, "OBJECT");
|
||||
if (data && Object.keys(data)?.length > 0) {
|
||||
totals.value = data.Total;
|
||||
return data?.Data;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const designObject = computed(() => {
|
||||
@@ -49,21 +59,25 @@ const dropData = (event: any) => {
|
||||
|
||||
//?cpn_1=page:2&cpn_2=page:1
|
||||
// Get[Article] Top[5] With[Categories:1]
|
||||
const select = (page: number) => {
|
||||
const select = async (pageActive: number) => {
|
||||
const componentId = _props.component?.id;
|
||||
if (componentId) {
|
||||
router.push({
|
||||
query: {
|
||||
...route.query,
|
||||
[`cpn_${componentId}`]: `page:${page}`,
|
||||
[`cpn_${componentId}`]: `page:${pageActive}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
page.value = Number(pageActive);
|
||||
await loadPage(Number(pageActive));
|
||||
};
|
||||
const handleRouteChange = (query: any) => {
|
||||
const [_, value] = query[`cpn_${_props.component?.id}`]?.split(":");
|
||||
if (value) {
|
||||
loadPage(Number(value));
|
||||
const handleRouteChange = async (query: any) => {
|
||||
const param = query[`cpn_${_props.component?.id}`];
|
||||
if (param) {
|
||||
const [_, value] = param.split(":") || [];
|
||||
page.value = value;
|
||||
await loadPage(value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,32 +85,52 @@ onBeforeMount(() => {
|
||||
if (route.query[`cpn_${_props.component?.id}`]) handleRouteChange(route.query);
|
||||
});
|
||||
|
||||
const loadPage = (page: string | number) => {
|
||||
// listArticleByCategory.value =
|
||||
const loadPage = async (page: number) => {
|
||||
let newDataQuery = "";
|
||||
const regex = /Page\[(.*?)\]/;
|
||||
const match = _props.component?.settings?.dataQuery?.match(regex);
|
||||
if (match) {
|
||||
const [pageData] = match;
|
||||
newDataQuery = _props.component?.settings?.dataQuery.replace(pageData, `Page[${page}]`);
|
||||
} 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");
|
||||
if (Object.keys(data).length > 0) {
|
||||
totals.value = data.Total;
|
||||
listArticlePaging.value = data?.Data || [];
|
||||
}
|
||||
};
|
||||
|
||||
const handleNextPrev = (type: "+" | "-") => {
|
||||
if (listArticleByCategory.value?.length > 0) {
|
||||
if (type === "+") {
|
||||
page.value += 1;
|
||||
} else if (type === "-") {
|
||||
page.value -= 1;
|
||||
}
|
||||
select(page.value);
|
||||
}
|
||||
};
|
||||
|
||||
const selectComponent = () => {
|
||||
emit("selectComponent");
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery) => {
|
||||
handleRouteChange(newQuery);
|
||||
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']">
|
||||
<div
|
||||
class="collection-container"
|
||||
:class="[designObject['LAYOUT_WRAP'] || 'vertical', ...(designObject['borderWrap']?.length > 0 ? designObject['borderWrap'] : [])]"
|
||||
:style="[designObject['background'] && `background: ${designObject['background']}`]"
|
||||
>
|
||||
<div class="section-layout" :style="designObject['div.section']">
|
||||
<template v-if="listArticleByCategory?.length > 0">
|
||||
<template v-for="(component, index) in listArticleByCategory">
|
||||
<template v-for="(component, index) in listArticlePaging?.length > 0 ? listArticlePaging : listArticleByCategory">
|
||||
<DynamicComponent
|
||||
:key="index"
|
||||
v-if="!isEmpty(component)"
|
||||
@@ -104,28 +138,35 @@ watch(
|
||||
template: SETTING_OPTIONS.TEMPLATE,
|
||||
layout: SETTING_OPTIONS.LAYOUT,
|
||||
dataResult: { ...component },
|
||||
label,
|
||||
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>
|
||||
<div class="button-page flex">
|
||||
<a class="btn-page prev-page cursor-pointer" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" class="w-7 h-6 text-primary-600">
|
||||
<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>
|
||||
<a class="btn-page" @click="() => select(index + 1)" v-for="(_, index) in totals">{{ index + 1 }}</a>
|
||||
<a class="btn-page next-page cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" class="w-7 h-6 text-primary-600">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,8 +176,9 @@ watch(
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.section-container {
|
||||
.collection-container {
|
||||
display: grid;
|
||||
.section-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 10px; */
|
||||
overflow-x: scroll;
|
||||
&.border-custom {
|
||||
@@ -154,15 +196,20 @@ watch(
|
||||
&.borderBottom {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
&.vertical {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
&.horizontal {
|
||||
grid-template-rows: auto;
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
.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 {
|
||||
margin-bottom: 10px;
|
||||
@@ -198,6 +245,14 @@ watch(
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-empty {
|
||||
padding: 12px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Default_Pagination } from './Default.vue'
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Default_Pagination } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
[enumPageComponentLayouts[`${enumPageComponentTemplate[enumPageComponentKey.SECTION]['ARTICLE']}`]['ARTICLE_SECTION_DEFAULT']]: Default_Pagination,
|
||||
};
|
||||
|
||||
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
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
|
||||
/>
|
||||
</template>
|
||||
@@ -1 +1 @@
|
||||
export { default as Article_Pagination } from './layouts/Article.vue'
|
||||
export { default as Article_Pagination } from './articles/index.vue'
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplates, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { enumPageComponentTemplate, enumPageComponentKey, enumPageComponentLayouts } from "@/definitions/enum";
|
||||
import { Article_Pagination } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
@@ -8,10 +8,10 @@ const _props = defineProps<{
|
||||
}>();
|
||||
|
||||
const definedDynamicComponent: Record<string, any> = {
|
||||
'TYPE:Article-LAYOUT:horizontal-DATA:HORIZONTAL': Article_Pagination
|
||||
[enumPageComponentTemplate[enumPageComponentKey.SECTION]['ARTICLE']]: Article_Pagination,
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
|
||||
const getCurrentComponent = computed(() => _props.settings.template);
|
||||
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
@@ -30,5 +30,8 @@ const GET_PROPS = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }" />
|
||||
<component
|
||||
:is="definedDynamicComponent[getCurrentComponent]"
|
||||
v-bind="{ ...GET_PROPS(), component: _props.component, settings: _props.settings }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import RecusiveSection from "~/components/dynamic-page/page-section/RecusiveSection.vue";
|
||||
import { enumPageSectionLayouts } from "~/definitions/enum";
|
||||
|
||||
const props = defineProps<{
|
||||
layout?: string;
|
||||
content?: any;
|
||||
section: any;
|
||||
}>();
|
||||
|
||||
const defineTypeRecusive = {
|
||||
COMPONENT: "component",
|
||||
SECTION: "section",
|
||||
};
|
||||
|
||||
const SETTING_OPTIONS = computed(() => {
|
||||
let _setting_options = {};
|
||||
switch (props.layout) {
|
||||
case enumPageSectionLayouts.VERTICAL_TWO:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 2,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.VERTICAL_LEFT_TWO:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 2,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.VERTICAL_RIGHT_TWO:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 2,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.VERTICAL_THREE:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 3,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.VERTICAL_FOUR:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 4,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_ONE:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 1,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_TWO:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 2,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_THREE:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 3,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_FOUR:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 4,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_FIVE:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 5,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_SIX:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 6,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_SEVEN:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 7,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_EIGHT:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 8,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_NINE:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 9,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.HORIZONTAL_TEN:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 10,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 1,
|
||||
};
|
||||
break;
|
||||
}
|
||||
return _setting_options;
|
||||
});
|
||||
|
||||
const CLASS_FOR_SECTION = computed(() => {
|
||||
let _classForSection = {};
|
||||
switch (props.layout) {
|
||||
case enumPageSectionLayouts.VERTICAL_TWO:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout two_col_layout mb-5 mt-2",
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.VERTICAL_LEFT_TWO:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout three_col_layout",
|
||||
0: "col-span-2",
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.VERTICAL_RIGHT_TWO:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout three_col_layout",
|
||||
1: "col-span-2",
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.VERTICAL_THREE:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout three_col_layout mt-4",
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts.VERTICAL_FOUR:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout four_col_layout",
|
||||
};
|
||||
break;
|
||||
default:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout basic_column",
|
||||
};
|
||||
break;
|
||||
}
|
||||
return _classForSection;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="section_layout grid lg:grid-cols-2" :class="[CLASS_FOR_SECTION.section_layout]">
|
||||
<div
|
||||
v-for="(position, index) in props.content || Array(SETTING_OPTIONS.MAX_ELEMENT).fill({})"
|
||||
:key="index"
|
||||
:class="[CLASS_FOR_SECTION[index]]"
|
||||
>
|
||||
|
||||
<RecusiveSection :type="position.type" :id="position.data" :section="props.section" class="h-full"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.section_layout {
|
||||
&.basic_column {
|
||||
@apply grid-cols-1;
|
||||
}
|
||||
|
||||
&.two_col_layout {
|
||||
@apply md:grid-cols-2 grid-cols-1;
|
||||
}
|
||||
|
||||
&.three_col_layout {
|
||||
@apply md:grid-cols-3 grid-cols-1;
|
||||
// grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
&.four_col_layout {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
.col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1 +1 @@
|
||||
export { default as BASE_LAYOUT } from './Default.vue'
|
||||
export { default as NONE_DEFAULT_LAYOUT } from './none/Default.vue'
|
||||
|
||||
@@ -1,41 +1,45 @@
|
||||
<script lang="ts" setup>
|
||||
import { BASE_LAYOUT } from "./index";
|
||||
import { enumPageSectionLayouts } from "@/definitions/enum";
|
||||
import type { PageSection } from "@/models/cms";
|
||||
import { enumPageSectionLayouts, enumPageSectionTemplate, enumPageSectionKey } from "@/definitions/enum";
|
||||
import { NONE_DEFAULT_LAYOUT } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings?: any;
|
||||
layout?: string;
|
||||
content?: any;
|
||||
|
||||
section: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicSection: Record<string, any> = {
|
||||
'Default': BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.VERTICAL_TWO]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.VERTICAL_LEFT_TWO]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.VERTICAL_RIGHT_TWO]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.VERTICAL_THREE]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.VERTICAL_FOUR]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_ONE]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_TWO]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_THREE]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_FOUR]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_FIVE]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_SIX]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_SEVEN]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_EIGHT]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_NINE]: BASE_LAYOUT,
|
||||
[enumPageSectionLayouts.HORIZONTAL_TEN]: BASE_LAYOUT,
|
||||
[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,
|
||||
};
|
||||
|
||||
const getCurrentSection = computed(() => _props?.layout || "");
|
||||
const getCurrentSection = computed(() => {
|
||||
return _props.settings.layout
|
||||
});
|
||||
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
let props: any = {};
|
||||
if (_props.settings) {
|
||||
for (const [key, value] of _props.settings ? Object.entries(_props.settings) : []) {
|
||||
for (const [key, value] of Object.entries(_props.settings)) {
|
||||
props = {
|
||||
...props,
|
||||
[key]: value,
|
||||
@@ -49,11 +53,12 @@ const GET_PROPS = computed(() => {
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="definedDynamicSection[getCurrentSection] || null"
|
||||
:is="definedDynamicSection[getCurrentSection]"
|
||||
v-bind="{
|
||||
...GET_PROPS(),
|
||||
section: _props.section,
|
||||
content: _props.content,
|
||||
settings: _props.settings,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
<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 "@/models/cms";
|
||||
|
||||
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.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']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 3,
|
||||
};
|
||||
break;
|
||||
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_THREE']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 3,
|
||||
};
|
||||
break;
|
||||
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']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 2,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_THREE']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 3,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_FOUR']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 4,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_FIVE']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 5,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_SIX']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 6,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_SEVEN']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 7,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_EIGHT']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 8,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_NINE']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 9,
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['HORIZONTAL_TEN']:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 10,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
_setting_options = {
|
||||
MAX_ELEMENT: 1,
|
||||
};
|
||||
break;
|
||||
}
|
||||
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']:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout two_col_layout",
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_LEFT_TWO']:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout three_col_layout",
|
||||
0: "col-span-2",
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_RIGHT_TWO']:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout three_col_layout",
|
||||
1: "col-span-2",
|
||||
};
|
||||
break;
|
||||
case enumPageSectionLayouts[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]['VERTICAL_THREE']:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout three_col_layout",
|
||||
};
|
||||
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']:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout four_col_layout",
|
||||
1: "col-span-2",
|
||||
};
|
||||
break;
|
||||
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']:
|
||||
_classForSection = {
|
||||
section_layout: "section_layout six_col_layout",
|
||||
1: "col-span-4",
|
||||
};
|
||||
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 {};
|
||||
};
|
||||
</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']"
|
||||
@dragover.prevent
|
||||
@drop.stop.prevent="dropPlacementInSection($event, index, position.data)"
|
||||
>
|
||||
<RecusiveSection :type="position.type" :id="position.data" :section="props.section" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.section_layout {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
&.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;
|
||||
}
|
||||
.border-custom {
|
||||
border-color: #e5e5e5 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,29 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import DynamicLayout from '~/components/dynamic-page/page-section/layouts/index.vue';
|
||||
import DynamicLayout from '@/components/dynamic-page/page-section/layouts/index.vue';
|
||||
import type { PageSection } from "@/models/cms";
|
||||
|
||||
const emit = defineEmits(['dropComponent', 'dropData', 'selectComponent']);
|
||||
|
||||
const props = defineProps<{
|
||||
label?: any
|
||||
layout?: string
|
||||
settings?: any
|
||||
content?: any
|
||||
|
||||
section: any
|
||||
section: PageSection
|
||||
}>()
|
||||
|
||||
const store = reactive({
|
||||
section: usePageSectionStore(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container">
|
||||
<h2 class="text-4xl mb-3 font-600" v-if="props.label">{{ props.label || '' }}</h2>
|
||||
<div class="layout_define grid grid-cols-1 gap-x-10 gap-y-2.5">
|
||||
<!-- tạm thời đóng vì đang đẩy vào label -->
|
||||
<!-- <h2 class="section__title mb-3" v-if="props.label">{{ props.label || '' }}</h2> -->
|
||||
<div class="section_layout">
|
||||
<template v-if="props.layout">
|
||||
<DynamicLayout
|
||||
:layout="props.layout"
|
||||
:content="props.content"
|
||||
:settings="props.settings"
|
||||
:section= "props.section"
|
||||
:section="props.section"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
Bấm để chọn layout
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.border-pri {
|
||||
.section_layout{
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
.section_layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
|
||||
.empty {
|
||||
min-height: 100px;
|
||||
border-radius: 6px;
|
||||
background: #409eff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Article_Section_Default } from './Default.vue'
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { Article_Section_Default } from "./index";
|
||||
import type { PageSection } from "@/models/cms";
|
||||
import { enumPageSectionKey, enumPageSectionTemplate } from "@/definitions/enum";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings?: any;
|
||||
content?: any;
|
||||
section: PageSection;
|
||||
}>();
|
||||
|
||||
const definedDynamicSection: Record<string, any> = {
|
||||
[enumPageSectionTemplate[enumPageSectionKey.ARTICLE]['DEFAULT']]: Article_Section_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 :is="definedDynamicSection[getCurrentSection]" v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
@@ -1,77 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
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<{
|
||||
layout?: any
|
||||
}>()
|
||||
|
||||
const classForLayout = computed(() => {
|
||||
let _empty = {
|
||||
page_container: 'page_container',
|
||||
layout_container: 'layout_container',
|
||||
};
|
||||
switch (props.layout) {
|
||||
case 'Full_Page':
|
||||
_empty = {
|
||||
page_container: 'page_container full-size-page',
|
||||
layout_container: 'layout_container full-size-layout',
|
||||
};
|
||||
break;
|
||||
case 'Center_Page':
|
||||
_empty = {
|
||||
page_container: 'page_container full-size-page',
|
||||
layout_container: 'layout_container center-layout',
|
||||
};
|
||||
break;
|
||||
case 'Background_Page':
|
||||
_empty = {
|
||||
page_container: 'page_container full-size-page background-container',
|
||||
layout_container: 'layout_container center-layout',
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return _empty;
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-100 overflow-y-auto">
|
||||
<HeaderHomeTemplate />
|
||||
|
||||
<div :class="[classForLayout.page_container]">
|
||||
<div :class="[classForLayout.layout_container]">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FooterHomeTemplate />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.body-center {
|
||||
padding: 40px 0px;
|
||||
}
|
||||
|
||||
.page_container {
|
||||
&.full-size-page {
|
||||
width: 100%;
|
||||
}
|
||||
.full-size-layout {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.layout_container {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
|
||||
&.center-layout {
|
||||
max-width: 1300px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,2 +1 @@
|
||||
export { default as Article_Section_Default } from './articles/Default.vue'
|
||||
export { default as None_Section_Default } from './none/Default.vue'
|
||||
export { default as None_Section } from './none/index.vue'
|
||||
|
||||
@@ -1,69 +1,40 @@
|
||||
<script lang="ts" setup>
|
||||
import { Article_Section_Default, None_Section_Default } from './index';
|
||||
import { enumPageSectionLayouts } from "@/definitions/enum";
|
||||
import type { PageSection } from "@/models/cms";
|
||||
import { enumPageSectionKey } from "@/definitions/enum";
|
||||
import { None_Section } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings?: any,
|
||||
content?: any
|
||||
section: any
|
||||
}>()
|
||||
settings?: any;
|
||||
content?: any;
|
||||
section?: any;
|
||||
}>();
|
||||
|
||||
const definedDynamicSection: Record<string, any> = {
|
||||
'Article_Default': Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.VERTICAL_TWO}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.VERTICAL_LEFT_TWO}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.VERTICAL_RIGHT_TWO}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.VERTICAL_THREE}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.VERTICAL_FOUR}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_ONE}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_TWO}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_THREE}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_FOUR}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_FIVE}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_SIX}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_SEVEN}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_EIGHT}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_NINE}`]: Article_Section_Default,
|
||||
[`Article_${enumPageSectionLayouts.HORIZONTAL_TEN}`]: Article_Section_Default,
|
||||
[enumPageSectionKey.NONE]: None_Section,
|
||||
};
|
||||
|
||||
'None_Default': None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.VERTICAL_TWO}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.VERTICAL_LEFT_TWO}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.VERTICAL_RIGHT_TWO}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.VERTICAL_THREE}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.VERTICAL_FOUR}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_ONE}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_TWO}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_THREE}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_FOUR}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_FIVE}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_SIX}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_SEVEN}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_EIGHT}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_NINE}`]: None_Section_Default,
|
||||
[`None_${enumPageSectionLayouts.HORIZONTAL_TEN}`]: None_Section_Default,
|
||||
const getCurrentSection = computed(() => {
|
||||
return _props.section?.taxonomy;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const getCurrentSection = computed(() => `${_props.settings.template}_${_props.settings.layout}`);
|
||||
const GET_PROPS = computed(() => {
|
||||
return () => {
|
||||
let props: any = {};
|
||||
if (_props.settings) {
|
||||
for (const [key, value] of _props.settings ? Object.entries(_props.settings) : []) {
|
||||
props = {
|
||||
...props,
|
||||
[key]: value
|
||||
}
|
||||
}
|
||||
}
|
||||
return props;
|
||||
};
|
||||
})
|
||||
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 :is="definedDynamicSection[getCurrentSection] || null" v-bind="{ ...(GET_PROPS()), section: _props.section, content: _props.content, settings: _props.settings }">
|
||||
<slot />
|
||||
</component>
|
||||
<component :is="definedDynamicSection[getCurrentSection]" v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import DynamicLayout from '~/components/dynamic-page/page-section/layouts/index.vue';
|
||||
import type { PageSection } from "~/server/models/dynamic-page/index";
|
||||
import DynamicLayout from "@/components/dynamic-page/page-section/layouts/index.vue";
|
||||
import type { PageSection } from "@/models/cms";
|
||||
|
||||
const emit = defineEmits(['dropComponent', 'dropData', 'selectComponent']);
|
||||
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
|
||||
|
||||
const props = defineProps<{
|
||||
label?: any
|
||||
layout?: string
|
||||
settings?: any
|
||||
content?: any
|
||||
section: PageSection
|
||||
}>()
|
||||
|
||||
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"
|
||||
/>
|
||||
<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>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as None_Section_Default } from './Default.vue'
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PageSection } from "@/models/cms";
|
||||
import { enumPageSectionKey, enumPageSectionTemplate } from "@/definitions/enum";
|
||||
import { None_Section_Default } from "./index";
|
||||
|
||||
const _props = defineProps<{
|
||||
settings?: any;
|
||||
content?: any;
|
||||
section: PageSection;
|
||||
}>();
|
||||
|
||||
const definedDynamicSection: Record<string, any> = {
|
||||
[enumPageSectionTemplate[enumPageSectionKey.NONE]['NONE']]: None_Section_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 :is="definedDynamicSection[getCurrentSection]" v-bind="{ ...GET_PROPS(), section: _props.section, content: _props.content, settings: _props.settings }">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { getInputValue } from "@/utils/parseSQL";
|
||||
const props = defineProps<{
|
||||
layout?: any
|
||||
layout?: any,
|
||||
label?:any
|
||||
}>()
|
||||
|
||||
const CLASS_FOR_LAYOUT = computed(() => {
|
||||
@@ -8,41 +10,69 @@ const CLASS_FOR_LAYOUT = computed(() => {
|
||||
switch (props.layout) {
|
||||
case 'Full_Page':
|
||||
_classForLayout = {
|
||||
page_container: 'page_container mx-auto w-full px-2',
|
||||
layout_container: 'layout_container px-5',
|
||||
page_container: 'page_container full-size-page',
|
||||
layout_container: 'layout_container full-size-layout',
|
||||
};
|
||||
break;
|
||||
case 'Center_Page':
|
||||
_classForLayout = {
|
||||
page_container: 'page_container mx-auto w-full px-2 container ',
|
||||
layout_container: 'layout_container container-xxl mx-auto',
|
||||
page_container: 'page_container full-size-page',
|
||||
layout_container: 'layout_container center-layout',
|
||||
};
|
||||
break;
|
||||
case 'Background_Page':
|
||||
_classForLayout = {
|
||||
page_container: 'page_container mx-auto w-full background-container px-2',
|
||||
layout_container: 'layout_container mx-auto',
|
||||
page_container: 'page_container full-size-page background-container',
|
||||
layout_container: 'layout_container center-layout',
|
||||
};
|
||||
break;
|
||||
default:
|
||||
_classForLayout = {
|
||||
page_container: 'page_container mx-auto px-2',
|
||||
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]">
|
||||
<div :class="[CLASS_FOR_LAYOUT.layout_container]" class="grid-container grid grid-cols-1 gap-20 py-10 style_layout">
|
||||
<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>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<template>
|
||||
<div class="page_container full-size-page">
|
||||
<div class="container-long my-5">
|
||||
<div class="layout_container center-layout grid-container">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||