thainv-dev: sửa lại cấu trúc folder

This commit is contained in:
nguyen van thai
2024-06-13 17:24:46 +07:00
parent 8818c73cec
commit ecf4512cd3
22 changed files with 1056 additions and 55 deletions
@@ -17,6 +17,7 @@ const definedDynamicComponent: Record<string, any> = {
'TYPE:Detail-LAYOUT:image': Article_Detail_Image, 'TYPE:Detail-LAYOUT:image': Article_Detail_Image,
'TYPE:Detail-LAYOUT:video': Article_Detail_Video, 'TYPE:Detail-LAYOUT:video': Article_Detail_Video,
'TYPE:Detail-LAYOUT:podcast': Article_Detail_Podcast, 'TYPE:Detail-LAYOUT:podcast': Article_Detail_Podcast,
'TYPE:Card': Article_Card
}; };
const getCurrentComponent = computed(() => `${_props.settings.layout}`); const getCurrentComponent = computed(() => `${_props.settings.layout}`);
@@ -37,5 +38,5 @@ const GET_PROPS = computed(() => {
</script> </script>
<template> <template>
<component :is="definedDynamicComponent[getCurrentComponent]" v-bind="GET_PROPS()" /> <component :is="definedDynamicComponent[getCurrentComponent]" v-bind="GET_PROPS()" class="h-full"/>
</template> </template>
@@ -1,24 +1,94 @@
<script lang="ts" setup> <script lang="ts" setup>
import { enumPageComponentTemplates } from "@/definitions/enum";
import { DEFAULT_QUERY_DROP } from "@/utils/parseSQL";
import { getInputValue } from "@/utils/parseSQL";
const props = defineProps<{ const props = defineProps<{
dataResult?: any; dataResult?: any;
dataType?: any; dataType?: any;
dataQuery?: any; dataQuery?: any;
layout?: string; layout?: string;
design?: 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> </script>
<template> <template>
<div> <article
vào r</div> 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>
<span v-else class="empty-block" style="width: 100%; height: 100%; min-height: 50px"></span>
</div>
<div class="basic-article_content !py-0" :class="[!parseData && 'no-data']">
<div>
<nuxt-link :to="`bai-viet/${parseData?.slug}`" v-if="!LAYOUT_PARSE['HIDE'] || !LAYOUT_PARSE['HIDE'].includes('title')" class="mb-1 text-truncate-two-lines font-bold line-clamp-2 hover:text-primary-600">
<template v-if="parseData">
{{ parseData.title?.replace(/<[^>]+>/g, "") }}
</template>
<span v-else class="empty-block" style="height: 8px"></span>
</nuxt-link>
<p v-if="!LAYOUT_PARSE['HIDE'] || !LAYOUT_PARSE['HIDE'].includes('paragraph')" class="mb-0 line-clamp-3">
<template v-if="parseData">
{{ parseData.intro ? parseData.intro?.replace(/<[^>]+>/g, "") : parseData.detail?.replace(/<[^>]+>/g, "") }}
</template>
<span v-else class="empty-block" style="height: 5px"></span>
</p>
</div>
</div>
</article>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.basic-article { .basic-article {
display: grid; display: flex;
gap: 10px; gap: 10px;
height: 100%; height: 100%;
@@ -27,44 +97,45 @@ const props = defineProps<{
} }
&.vertical { &.vertical {
grid-template-columns: repeat(1, minmax(0, 1fr)); flex-direction: column;
.basic-article_thumbnail {
width: 100%;
}
&.reverse {
flex-direction: column-reverse;
height: 100%;
}
} }
&.border-custom { &.border-custom {
border-color: #e5e5e5 !important; border-color: #e5e5e5 !important;
} }
&.borderLeft { &.borderLeft {
border-left: 2px solid; border-left: 1px solid;
padding-left: 10px; padding-left: 10px;
} }
&.borderRight { &.borderRight {
border-right: 2px solid; border-right: 1px solid;
padding-right: 10px; padding-right: 10px;
} }
&.borderTop { &.borderTop {
border-top: 2px solid; border-top: 1px solid;
padding-top: 10px; padding-top: 10px;
} }
&.borderBottom { &.borderBottom {
border-bottom: 2px solid; border-bottom: 1px solid;
padding-bottom: 10px; padding-bottom: 10px;
} }
&.horizontal { &.horizontal {
grid-template-columns: 1fr 1fr; flex-direction: row;
.basic-article_thumbnail {
width: 40%;
}
&.reverse { &.reverse {
.basic-article_thumbnail { flex-direction: row-reverse;
grid-column: 2;
}
.basic-article_content {
grid-row: 1;
}
} }
} }
&_thumbnail { &_thumbnail {
flex: 1;
img { img {
width: 100%; width: 100%;
border-radius: 6px; border-radius: 6px;
@@ -74,7 +145,7 @@ const props = defineProps<{
&_content { &_content {
padding: 10px 0px; padding: 10px 0px;
flex: 1;
&.no-data { &.no-data {
padding: 0px; padding: 0px;
} }
@@ -89,10 +160,10 @@ const props = defineProps<{
} }
} }
.empty-block { // .empty-block {
background-color: #409eff; // background-color: #409eff;
height: 100px; // height: 100px;
display: block; // display: block;
} // }
} }
</style> </style>
@@ -1,10 +1,478 @@
<script setup lang="ts"></script> <script setup lang="ts">
<template> import { useArticleStore } from "~/stores/articles";
<div> import Poll from "~/components/article/immerse/Poll.vue";
postcart</div> import Quiz from "~/components/article/immerse/Quiz.vue";
</template> import Survey from "~/components/article/immerse/Survey.vue";
<style lang="scss" scoped> import Document from "~/components/article/immerse/Document.vue";
div { import Attachment from "@/components/article/immerse/Attachment.vue";
padding: 0; 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;
} }
</style>
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" 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>
@@ -16,8 +16,6 @@ const store = reactive({
article: useArticleStore(), article: useArticleStore(),
category: useCategoryStore(), category: useCategoryStore(),
}); });
console.log(currentArticle.value ,'curenta')
</script> </script>
<template> <template>
<div class="grid grid-cols-1 md:grid-cols-3"> <div class="grid grid-cols-1 md:grid-cols-3">
@@ -0,0 +1 @@
export { default as Category_Default } from './layouts/Default.vue'
@@ -0,0 +1,34 @@
<script lang="ts" setup>
import { enumPageComponentTemplates } from "@/definitions/enum";
import {
Category_Default
} from "./index";
const _props = defineProps<{
settings: any;
component?: any;
}>();
const definedDynamicComponent: Record<string, any> = {
'TYPE:Category-MAX:5': Category_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()" />
</template>
@@ -0,0 +1,38 @@
<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-[22px] first:border-b first:border-primary-600 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>
@@ -1,6 +1,127 @@
<script setup lang="ts"></script> <script setup lang="ts">
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import { COLLECTION_QUERY_DROP, getValueStringWithKeyAndColon, getInputValue } from "@/utils/parseSQL";
import { isEmpty } from "lodash";
const emit = defineEmits(["dropComponent", "dropData", "selectComponent"]);
const _props = defineProps<{
dataResult?: any[];
dataQuery?: string;
layout?: string;
label?: string;
}>();
const SETTING_OPTIONS = {
MAX_ELEMENT: 5,
TEMPLATE: "Article",
LAYOUT: "TYPE:Card",
};
const layoutParse = 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(layoutParse.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");
};
</script>
<template> <template>
<div> <div>
collection <div
class="collection-container p-2 border-custom"
:class="[layoutParse['LAYOUT_WRAP'] || 'vertical', ...(layoutParse['borderWrap']?.length > 0 ? layoutParse['borderWrap'] : [])]"
@click="selectComponent"
:style="[`grid-template-columns: repeat(${currentScreenMode === 'smartphone' ? 1 : layoutParse['COLUMN']}, minmax(0, 1fr))`, layoutParse['background'] && `background: ${layoutParse['background']}`]"
>
<DynamicComponent
class="h-full"
v-for="(component, index) in _dataResult"
:key="index"
:settings="{
template: SETTING_OPTIONS.TEMPLATE,
layout: SETTING_OPTIONS.LAYOUT,
label,
dataResult: !isEmpty(component) ? { ...component } : null,
}"
@drop-data="dropData"
/>
</div> </div>
</template> </div>
</template>
<style lang="scss" scoped>
.collection-container {
display: grid;
gap: 10px;
&.border-custom {
border-color: #e5e5e5 !important;
}
&.borderLeft {
border-left: 1px solid;
padding-left: 10px;
}
&.borderRight {
border-right: 1px solid;
padding-right: 10px;
}
&.borderTop {
border-top: 1px solid;
padding-top: 10px;
}
&.borderBottom {
border-bottom: 1px solid;
padding-bottom: 10px;
}
&.vertical {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
&.horizontal {
grid-template-rows: auto;
grid-auto-flow: column;
}
.empty {
min-height: 100px;
border-radius: 6px;
background: #409eff;
}
&.noData {
border-radius: 6px;
}
}
</style>
@@ -5,7 +5,8 @@ export { default as CollectionPaging } from './pageCategories/collection_page.vu
export { default as Dynamic_Other } from './other/index.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_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_Article } from './articles/index.vue'
export { default as Dynamic_Collection } from './collections/index.vue' export { default as Dynamic_Collection } from './collections/index.vue'
@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { enumPageComponentTemplates } from "@/definitions/enum"; import { enumPageComponentTemplates } from "@/definitions/enum";
import { BasicCategories, Dynamic_Collection, CollectionPaging, Dynamic_Other, Dynamic_Advertising, Dynamic_Article } from "./index"; import { Dynamic_Section, Dynamic_Category, Dynamic_Collection, CollectionPaging, Dynamic_Other, Dynamic_Advertising, Dynamic_Article } from "./index";
const _props = defineProps<{ const _props = defineProps<{
settings: any; settings: any;
component?: any; component?: any;
@@ -8,9 +8,9 @@ const _props = defineProps<{
const definedDynamicComponent: Record<string, any> = { const definedDynamicComponent: Record<string, any> = {
[enumPageComponentTemplates.ARTICLE]: Dynamic_Article, [enumPageComponentTemplates.ARTICLE]: Dynamic_Article,
[enumPageComponentTemplates.CATEGORY]: BasicCategories, [enumPageComponentTemplates.CATEGORY]: Dynamic_Category,
[enumPageComponentTemplates.COLLECTION]: Dynamic_Collection, [enumPageComponentTemplates.COLLECTION]: Dynamic_Collection,
[enumPageComponentTemplates.SECTION]: CollectionPaging, [enumPageComponentTemplates.SECTION]: Dynamic_Section,
[enumPageComponentTemplates.OTHER]: Dynamic_Other, [enumPageComponentTemplates.OTHER]: Dynamic_Other,
[enumPageComponentTemplates.ADVERTISING]: Dynamic_Advertising [enumPageComponentTemplates.ADVERTISING]: Dynamic_Advertising
}; };
@@ -0,0 +1 @@
export { default as Article_Pagination } from './layouts/Article.vue'
@@ -0,0 +1,34 @@
<script lang="ts" setup>
import { enumPageComponentTemplates, enumPageComponentLayouts } from "@/definitions/enum";
import { Article_Pagination } from "./index";
const _props = defineProps<{
settings: any;
component?: any;
}>();
const definedDynamicComponent: Record<string, any> = {
'TYPE:Article-LAYOUT:horizontal-DATA:HORIZONTAL': Article_Pagination
};
console.log('đã vào')
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
console.log(getCurrentComponent.value, 'getcomponent')
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,225 @@
<script setup lang="ts">
import { isEmpty } from "lodash";
import DynamicComponent from "~/components/dynamic-page/page-component/templates/index.vue";
import { COLLECTION_PAGING_QUERY_DROP, getInputValue } from "@/utils/parseSQL";
const router = useRouter();
const route = useRoute();
const emit = defineEmits(["dropData", "selectComponent"]);
const _props = defineProps<{
dataResult?: any[];
dataQuery?: string;
component?: any;
label?: string;
}>();
const SETTING_OPTIONS = {
MAX_ELEMENT: 5,
TEMPLATE: "Article",
LAYOUT: "TYPE:Card",
};
// const page = ref(1);
const limit = ref(1);
const totals = ref(1);
const type = "Article";
const listArticleByCategory = computed(() => {
return getInputValue(_props.dataResult, "ARRAY");
});
const designObject = computed(() => {
return _props.label ? getInputValue(_props.label, "OBJECT") : {};
});
const dropData = (event: any) => {
const queryBy = {
Category: "Categories",
};
const { dataResult, dataType } = JSON.parse(event.dataTransfer.getData("category"));
// const getDataQuery = `Get[${type}] Top[20] With[${queryBy[dataType]}:${dataResult.id}]`;
const getDataQuery = COLLECTION_PAGING_QUERY_DROP(type, { key: queryBy[dataType], value: dataResult.id });
emit("dropData", {
dataResult: [],
dataType,
dataQuery: getDataQuery,
});
};
//?cpn_1=page:2&cpn_2=page:1
// Get[Article] Top[5] With[Categories:1]
const select = (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 =
};
const selectComponent = () => {
emit("selectComponent");
};
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"
:class="[designObject['LAYOUT_WRAP'] || 'vertical', ...(designObject['borderWrap']?.length > 0 ? designObject['borderWrap'] : [])]"
:style="[designObject['background'] && `background: ${designObject['background']}`]"
>
<template v-if="listArticleByCategory?.length > 0">
<template v-for="(component, index) in listArticleByCategory">
<DynamicComponent
:key="index"
v-if="!isEmpty(component)"
:settings="{
template: SETTING_OPTIONS.TEMPLATE,
layout: SETTING_OPTIONS.LAYOUT,
dataResult: { ...component },
label,
}"
/>
</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">
<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>
</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">
<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>
</a>
</div>
</div>
</div>
</section>
</template>
<style lang="scss" scoped>
.section-container {
.collection-container {
display: grid;
gap: 10px;
&.border-custom {
border-color: #e5e5e5 !important;
}
&.borderLeft {
border-left: 2px solid;
padding-left: 10px;
}
&.borderRight {
border-right: 2px solid;
padding-right: 10px;
}
&.borderTop {
border-top: 2px solid;
padding-top: 10px;
}
&.borderBottom {
border-bottom: 2px solid;
padding-bottom: 10px;
}
&.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;
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;
}
}
.el-empty {
padding: 12px 0;
}
</style>
@@ -147,15 +147,24 @@ const CLASS_FOR_SECTION = computed(() => {
:key="index" :key="index"
:class="[CLASS_FOR_SECTION[index]]" :class="[CLASS_FOR_SECTION[index]]"
> >
<RecusiveSection :type="position.type" :id="position.data" :section="props.section" />
<RecusiveSection :type="position.type" :id="position.data" :section="props.section" class="h-full"/>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss">
.section_layout { .section_layout {
&.basic_column { &.basic_column {
grid-template-columns: repeat(1, minmax(0, 1fr)); grid-template-columns: repeat(1, minmax(0, 1fr));
// & > div:first-child {
// .basic-article {
// h3 {
// @apply text-24px;
// }
// }
// }
} }
&.two_col_layout { &.two_col_layout {
@@ -30,7 +30,6 @@ const definedDynamicSection: Record<string, any> = {
}; };
const getCurrentSection = computed(() => _props?.layout || ""); const getCurrentSection = computed(() => _props?.layout || "");
const GET_PROPS = computed(() => { const GET_PROPS = computed(() => {
return () => { return () => {
let props: any = {}; let props: any = {};
+1
View File
@@ -28,6 +28,7 @@ watch(currentPage, () => {
useHead({ useHead({
title: () => currentPage.value.title || '' title: () => currentPage.value.title || ''
}) })
</script> </script>
<template> <template>
+1 -1
View File
@@ -47,7 +47,7 @@ watch(currentArticle, async () => {
break; break;
case 3: case 3:
isContentType = 'trang-chi-tiet-bai-viet-postcart' isContentType = 'trang-chi-tiet-podcast'
break; break;
case 4: case 4:
-1
View File
@@ -20,7 +20,6 @@ const store = reactive({
})(); })();
watch(currentPage, () => { watch(currentPage, () => {
console.log(currentPage.value)
store.dynamicPage.setSectionPublished(); store.dynamicPage.setSectionPublished();
store.dynamicPage.setComponentPublished() store.dynamicPage.setComponentPublished()
}, { deep: true }) }, { deep: true })
-1
View File
@@ -29,7 +29,6 @@ export type CategoryTree = Category & {
} }
export const list = async () => { export const list = async () => {
console.log('vào category service')
try { try {
const { site, apiUrl } = useRuntimeConfig().public; const { site, apiUrl } = useRuntimeConfig().public;
+1 -1
View File
@@ -22,7 +22,7 @@ export const create = async (event: H3Event) => {
}, },
body: payload body: payload
}) })
console.log(payload, 'payload')
return item return item
} catch (error) { } catch (error) {
-1
View File
@@ -28,7 +28,6 @@ export const useArticleStore = defineStore("article", () => {
method: 'POST', method: 'POST',
body: condition body: condition
}) })
console.log(articles, 'data')
} catch (error: any) { } catch (error: any) {
} }
+2
View File
@@ -232,6 +232,8 @@ const getInputValue = (inputValue: any, typeGet: 'OBJECT' | 'ARRAY') => {
} }
} }
export { export {
parseDataQueryFormString, parseDataQueryFormString,
parseDataQueryFormObject, parseDataQueryFormObject,