phongdt #2

Merged
Ghost merged 3 commits from phongdt into main 2024-05-31 09:39:07 +00:00
11 changed files with 317 additions and 23 deletions
@@ -1,6 +1,6 @@
<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 { getInputValue } from '@/utils/parseSQL';
import { isEmpty } from "lodash";
const _props = defineProps<{
@@ -37,7 +37,8 @@ const _dataResult = computed(() => {
<template>
<div>
<div class="collection-container grid gap-5" :class="LAYOUT_PARSE['LAYOUT'] || 'horizontal'">
<div class="collection-container grid gap-5" :class="LAYOUT_PARSE['LAYOUT'] || 'horizontal'"
:style="`grid-template-columns: repeat(${Number(LAYOUT_PARSE['COLUMN'])}}, minmax(0, 1fr))`">
<div v-for="(component, index) in _dataResult" :key="index">
<template v-if="!isEmpty(component)">
<DynamicComponent
@@ -46,16 +47,6 @@ const _dataResult = computed(() => {
layout: `LAYOUT:${LAYOUT_PARSE.DATA.toLowerCase()}` || SETTING_OPTIONS.LAYOUT,
dataResult: { ...component },
}"
@drop-data="dropData"
/>
</template>
<template v-else>
<DynamicComponent
:settings="{
template: LAYOUT_PARSE.TYPE || SETTING_OPTIONS.TEMPLATE,
layout: `LAYOUT:${LAYOUT_PARSE.DATA.toLowerCase()}` || SETTING_OPTIONS.LAYOUT,
}"
@drop-data="dropData"
/>
</template>
</div>
@@ -1,8 +1,6 @@
<script setup lang="ts">
import { useArticleStore } from '~/stores/articles';
const emit = defineEmits(['dropData', 'selectComponent'])
const { currentArticle } = storeToRefs(useArticleStore());
console.log(currentArticle.value ,'12')
</script>
<template>
<div class="content" v-if="currentArticle">
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { useArticleStore } from '~/stores/articles';
const emit = defineEmits(['dropData', 'selectComponent'])
const { currentArticle } = storeToRefs(useArticleStore());
</script>
@@ -1,6 +1,4 @@
<script setup lang="ts">
import { isEmpty } from 'lodash';
const emit = defineEmits(['dropData', 'selectComponent'])
const _props = defineProps<{
dataResult?: any[]
@@ -0,0 +1,116 @@
<script setup lang="ts">
import AudioPlayer from "~/organisms/audioPlayer/AudioPlayer.vue";
const { currentArticle } = storeToRefs(useArticleStore());
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 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]" />
</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>
@@ -2,6 +2,7 @@ export { default as Article_Button } from './copyLinks/ArticleButton.vue'
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 Default_Breadcrumb} from './breadcrumb/default.vue'
export { default as ADS_Default } from './ads/default.vue';
export { default as Comment } from './comments/default.vue'
@@ -1,7 +1,6 @@
<script lang="ts" setup>
import { enumPageComponentTemplates } from "@/definitions/enum";
// import { Default_Breadcrumb, Comment, Podcast, Video, Article_Detail_Default, ADS_Default, Article_Button, Article_Detail_Infographics, Article_Detail_Emagazine} from "./index";
import { Article_Button, Article_Detail_Emagazine, Article_Detail_Default, Article_Detail_Infographics, Default_Breadcrumb, ADS_Default, Comment} from "./index";
import { Article_Button, Article_Detail_Emagazine, Article_Detail_Default, Article_Detail_Infographics, Default_Breadcrumb, ADS_Default, Comment, Article_Detail_Podcast} from "./index";
const _props = defineProps<{
settings: any;
component?: any;
@@ -15,7 +14,7 @@ const definedDynamicComponent: Record<string, any> = {
'ADS_DEFAULT': ADS_Default,
'ARTICLE_BUTTON': Article_Button,
COMMENT: Comment,
// POCAST: Podcast,
PODCAST: Article_Detail_Podcast,
// VIDEO: Video
};
+162
View File
@@ -0,0 +1,162 @@
<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps<{
src?: string
}>()
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 = () => {
if (audioPlayer.value) {
audioPlayer.value.volume = volume.value;
if (volume.value == 0) {
isVolume.value = false
} else {
isVolume.value = true
}
}
};
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>
<audio :src="src" preload="auto" ref="audioPlayer" @timeupdate="updateCurrentTime" @loadedmetadata="updateDuration" />
<div class="relative">
<div class="w-full">
<input class="w-full accent-[#fff] cursor-pointer" type="range" v-model="currentTime" @input="seekToTime" :max="duration" />
<div class="flex items-center justify-between px-4 ">
<span class="text-[10px] sm:text-lg font-normal font-[Raleway] text-[#fff]">{{ currrentTimeComputed }}</span>
<span class="text-[10px] sm:text-lg font-normal font-[Raleway] text-[#fff]">{{ durationComputed }}</span>
</div>
</div>
<div class="w-full px-4 ">
<div class="flex items-center justify-between">
<div>
<button @click="chanageSpeed"
class="text-[#fff] bg-transparent text-sm sm:text-sm lg:text-lg flex gap-1"><span
class="hidden lg:block">Tốc độ phát </span> <span
class="font-bold text-[12px] sm:text-sm lg:text-lg">{{
speedDefault
}}</span> </button>
</div>
<div>
<button @click="replayAndForward(-5)" class="hover:bg-stone-200 bg-transparent p-2 rounded-full">
<Icon name="ri:replay-5-fill" color="fff" class="text-lg sm:text-2xl md:text-4xl" style="" />
</button>
<button @click="togglePlayer" class="bg-transparent">
<Icon v-if="isPlayed" name="ri:play-circle-fill" color="fff"
class="text-lg sm:text-3xl md:text-6xl" />
<Icon v-if="!isPlayed" name="ri:pause-circle-fill" color="fff"
class="text-lg sm:text-3xl md:text-6xl" />
</button>
<button @click="replayAndForward(5)" class="hover:bg-stone-200 bg-transparent p-2 rounded-full">
<Icon name="ri:forward-5-line" color="fff" class="text-lg sm:text-2xl md:text-4xl" />
</button>
</div>
<div class="flex items-center">
<button class="bg-transparent">
<div class="flex gap-2 ">
<Icon v-if="isVolume" @click="setUpVolums()" name="ri:volume-up-fill" color="fff"
class="text-lg md:text-2xl lg:text-3xl" />
<Icon v-if="!isVolume" @click="setUpVolums()" name="ri:volume-mute-fill" color="fff"
class="text-lg md:text-2xl lg:text-3xl" />
<input v-if="isVolume" class="accent-[#fff] w-12 lg:w-20 cursor-pointer" type="range" v-model="volume"
@input="updateVolume" min="0" max="1" step="0.1" />
</div>
</button>
</div>
</div>
</div>
</div></template>
+2 -2
View File
@@ -37,7 +37,7 @@ const loadPage = async (contentType: string | number) => {
watch(currentArticle, async () => {
let isContentType : string = '';
console.log(currentArticle.value.contentType, 'type')
console.log(currentArticle.value, 'type')
switch (currentArticle.value?.contentType) {
case 1:
isContentType = 'trang-doi-song'
@@ -47,7 +47,7 @@ watch(currentArticle, async () => {
break;
case 3:
isContentType = 'ArticleLayoutPodcast'
isContentType = 'trang-chi-tiet-podcast'
break;
case 4:
-1
View File
@@ -3,7 +3,6 @@ import _cloneDeep from 'lodash/cloneDeep';
import DynamicTemplate from "~/components/dynamic-page/page/templates/index.vue";
import DynamicSection from "~/components/dynamic-page/page-section/templates/index.vue";
import { useDynamicPageStore } from '~/stores/dynamic-page';
const { currentPage, sectionPublished, componentPublished } = storeToRefs(useDynamicPageStore());
+31
View File
@@ -0,0 +1,31 @@
import { utils } from "~/utils/utilities";
import Base from "./base";
import { H3Event } from "h3";
export type Author = {
id:number;
siteId:number;
userId:number;
title:string
code:string;
description?:string;
thumbnail?:string;
feature?:string;
type?:number;
}
export const fetchByCode = async (event: H3Event) => {
try {
const { apiUrl } = useRuntimeConfig().public
const { authorCode }: any = getQuery(event)
const { items }: any = await $fetch(`${apiUrl}/cms/author/code:${authorCode}`, {
method: 'GET',
headers: {
site: 1
}
})
return items[0]
} catch (error) {
handleError(error)
}
}