Merge pull request 'phongdt:page video' (#4) from phongdt into main
Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ events: any[] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="events?.length" class="mt-6">
|
||||
<h3 class="text-xl font-semibold after:content-[':']">Sự kiện</h3>
|
||||
<ul class="flex flex-col gap-2 list-disc ml-4 my-2 pl-3 flex-wrap">
|
||||
<li v-for="(event, index) in events" :key="index" class="font-semibold text-primary-100">
|
||||
<nuxt-link :to="{ name: 'event-eventSlug', params: { eventSlug: event.code } }">{{ event.title }}</nuxt-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ tags?: any[] }>();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="tags?.length"
|
||||
class="flex flex-col items-center justify-between gap-6 sm:flex-row mt-6">
|
||||
<div id="article-tags" class="flex order-2 gap-4 xl:order-1">
|
||||
<h3 class="text-xl font-semibold after:content-[':']">Tag</h3>
|
||||
<div class="flex items-center gap-4 flex-wrap">
|
||||
<template v-for="(item, index) in tags" :key="index">
|
||||
<div v-if="item.code">
|
||||
<nuxt-link :to="{ name: 'tag-tagSlug', params:{ tagSlug: item.code } }" class="text-blue-500 hover:text-blue-600">
|
||||
{{ item?.title }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{topics?: any[]}>()
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="topics?.length" class="flex flex-col items-center justify-between gap-6 sm:flex-row mt-6">
|
||||
<div class="flex order-2 gap-4 xl:order-1">
|
||||
<h3 class="text-xl font-semibold after:content-[':'] whitespace-nowrap">Chủ đề</h3>
|
||||
<div class="flex items-center gap-4 flex-wrap">
|
||||
<NuxtLink v-for="(item, index) in topics" :key="index" :to="{ name:'topic-topicSlug', params:{ topicSlug: item.code } }" class="text-blue-500 hover:text-blue-600">
|
||||
{{ item?.title }},
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,156 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{}>();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="player">
|
||||
<div class="player__track">
|
||||
<input class="player__track-range" type="range" disabled />
|
||||
<div class="player__time">
|
||||
<span class="player__time-current">00:00</span>
|
||||
<span class="player__time-duration">00:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="player__controls">
|
||||
<div class="player__speed">
|
||||
<button class="player__speed-button">
|
||||
<span class="player__speed-label">Tốc độ phát</span>
|
||||
<span class="player__speed-value">1.0x</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="player__actions">
|
||||
<button class="player__actions-button player__actions-button--replay">
|
||||
<Icon name="ri:replay-5-fill" class="player__icon player__icon--replay" />
|
||||
</button>
|
||||
<button class="player__actions-button player__actions-button--pause">
|
||||
<Icon name="ri:play-circle-fill" class="player__icon player__icon--pause" />
|
||||
</button>
|
||||
<button class="player__actions-button player__actions-button--forward">
|
||||
<Icon name="ri:forward-5-line" class="player__icon player__icon--forward" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="player__volume">
|
||||
<button class="player__volume-button">
|
||||
<div class="player__volume-control">
|
||||
<Icon name="ri:volume-up-fill" class="player__icon player__icon--volume" />
|
||||
<input class="player__volume-range" type="range" disabled />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.player {
|
||||
&__track {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__track-range {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
accent-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__time {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 1rem;
|
||||
|
||||
&-current,
|
||||
&-duration {
|
||||
font-size: 10px;
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-weight: normal;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
width: 100%;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
& > div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__speed {
|
||||
&-button {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
font-size: 0.75rem;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
|
||||
&-value {
|
||||
font-weight: bold;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__actions {
|
||||
&-button {
|
||||
background-color: transparent;
|
||||
padding: 0.5rem;
|
||||
border-radius: 100%;
|
||||
color: white;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
&--replay:hover,
|
||||
&--forward:hover {
|
||||
background-color: #d6d3d1;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__icon {
|
||||
&--replay,
|
||||
&--forward,
|
||||
&--pause {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&--pause {
|
||||
font-size: 44px;
|
||||
}
|
||||
}
|
||||
&__volume {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-button {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
& .player__icon--volume {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
& .player__volume-range {
|
||||
accent-color: #fff;
|
||||
width: 3rem;
|
||||
height: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
button{
|
||||
border: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,191 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import AudioPlayer from './AudioPlayer.vue'
|
||||
</script>
|
||||
<template>
|
||||
<div class="banner">
|
||||
<div class="banner__background" style="background-image: url('https://acp-api.vpress.vn/Resources/%E1%BA%A2nh/0bf02739-de1e-4899-9a2e-287c5d949250.jpg')">
|
||||
<div class="banner__overlay"></div>
|
||||
<Wrap class="banner__content">
|
||||
<div class="banner__inner">
|
||||
<div class="article">
|
||||
<div class="article__image-container">
|
||||
<div class="article__image-wrapper" style="background-image: url('https://acp-api.vpress.vn/Resources/%E1%BA%A2nh/0bf02739-de1e-4899-9a2e-287c5d949250.jpg')">
|
||||
<img src="https://acp-api.vpress.vn/Resources/%E1%BA%A2nh/0bf02739-de1e-4899-9a2e-287c5d949250.jpg" alt="" class="article__image" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="article__content">
|
||||
<div class="article__header">
|
||||
<div class="article__header-text">
|
||||
<h1 class="article__title">Podcast Truyện ngắn: Như cơi đựng trầu</h1>
|
||||
<time class="article__date">T2, 29 Th01 2024 16:57</time>
|
||||
</div>
|
||||
</div>
|
||||
<div class="article__intro">
|
||||
<div class="article__intro-text">Tình cảm vợ chồng êm ấm 12 năm, tối nay được định đoạt bằng tờ giấy vô hồn, vốn là người dễ xúc động nên trong lúc viết, Ngân Thương để mấy giọt nước mắt rơi xuống làm đôi chỗ bị nhòe đi.</div>
|
||||
</div>
|
||||
<div class="article__audio">
|
||||
<AudioPlayer />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Wrap>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.banner {
|
||||
&__background {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-size: cover;
|
||||
@media (min-width: 768px) {
|
||||
height: 25rem;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: black;
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__inner {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
.article {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, 1fr);
|
||||
width: 100%;
|
||||
|
||||
&__image-container {
|
||||
grid-column: span 3;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 15rem;
|
||||
min-width: 100px;
|
||||
@media (min-width: 768px) {
|
||||
height: 20rem;
|
||||
margin: 0 2rem;
|
||||
}
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
&__image-wrapper {
|
||||
height: 10rem;
|
||||
@media (min-width: 768px) {
|
||||
height: 15rem;
|
||||
}
|
||||
width: 100%;
|
||||
border-radius: 1.5rem 0 0 1.5rem;
|
||||
padding: 0.5rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
z-index: 1;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #000;
|
||||
opacity: 0.3;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__image {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
height: 10rem;
|
||||
@media (min-width: 768px) {
|
||||
height: 15rem;
|
||||
}
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&__content {
|
||||
grid-column: span 7;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__header {
|
||||
grid-column: span 12;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
margin-top: 2rem;
|
||||
|
||||
&-text {
|
||||
grid-column: span 11;
|
||||
}
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 19px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-family: "SFD";
|
||||
}
|
||||
|
||||
&__date {
|
||||
margin-top: 0.125rem;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&__intro {
|
||||
grid-column: span 12;
|
||||
margin-bottom: 1rem;
|
||||
display: none;
|
||||
@media (min-width: 768px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&__intro-text {
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-family: "SFD";
|
||||
}
|
||||
|
||||
&__audio {
|
||||
grid-column: span 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<article class="article">
|
||||
<div id="article-detail" class="article__detail">
|
||||
<div>
|
||||
<video controls="controls" width="100%" height="auto" 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="https://acp-api.vpress.vn/Resources/Video/983d2f57-7743-472f-b22d-fc73085af6d5.mp4" type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
<div class="article__sidebar">
|
||||
<div class="article__sidebar-content">
|
||||
<h1 class="article__title">Tranh cãi chuyện 'quán không nhận chuyển khoản'</h1>
|
||||
<div class="article__author-info">
|
||||
<div class="article__author">
|
||||
<p class="article__author-name">Thanh Huệ</p>
|
||||
</div>
|
||||
<span class="article__separator">-</span>
|
||||
<p class="article__date">T4, 15 Th05 2024 10:55</p>
|
||||
</div>
|
||||
<div id="article-brief" class="article__brief">
|
||||
<div class="article__intro-text">Những ngày cận Tết tại Hà Nội, các hội thi hoa đào, quất cảnh với đa dạng các sản phẩm độc đáo, bắt mắt đ đuợc các nghệ nhân đem đến cho khách tham quan chiêm ngưỡng.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.article {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
/* flex-direction: column; */
|
||||
gap: 1rem; // Equivalent to gap-4
|
||||
margin-top: 1rem; // Equivalent to mt-4
|
||||
background-color: #f7f7f7;
|
||||
|
||||
&__detail {
|
||||
flex: 1;
|
||||
|
||||
iframe,
|
||||
video {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
&.iframe {
|
||||
max-height: 13rem; // Equivalent to max-h-52
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
width: 50%;
|
||||
&-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 17px; // Equivalent to text-2xl
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&__author-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
video {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,91 +1,101 @@
|
||||
<template>
|
||||
<div class="comment">
|
||||
<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 outline-0 outline-none outline-offset-0" 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>
|
||||
<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">
|
||||
<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">
|
||||
|
||||
.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;
|
||||
background: #f5f5f5;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
border-top: 1px solid #dedede;
|
||||
border-right: 1px solid #dedede;
|
||||
border-bottom: 1px solid #dedede;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box-area-input {
|
||||
/* background: #f3f6f9; */
|
||||
background: #f7f7f7;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
padding: 5px 10px;
|
||||
padding: 10px 0 10px 0;
|
||||
border-left: 2px solid rgba(59, 130, 246, 1);
|
||||
}
|
||||
|
||||
|
||||
.input_comment textarea.block_input {
|
||||
height: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
textarea::placeholder {
|
||||
color: #878a99;
|
||||
-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: 58px;
|
||||
height: 76px;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/* .box-area-input .block_input {
|
||||
.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,6 +1,9 @@
|
||||
<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="([^"]+)"/;
|
||||
@@ -19,6 +22,33 @@ const getSrc = (htmlString: string) => {
|
||||
// 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 = {
|
||||
@@ -70,6 +100,9 @@ const defaultClass = {
|
||||
</div>
|
||||
<div class="col-span-11">
|
||||
<AudioPlayer :src="getSrc(currentArticle?.detail)?.[1]" />
|
||||
<!-- <Topic :topics="listTopic" />
|
||||
<Event :events="listEvent" />
|
||||
<Tag :tags="listTag" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<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>
|
||||
@@ -3,6 +3,7 @@ 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 Default_Breadcrumb} from './breadcrumb/default.vue'
|
||||
export { default as ADS_Default } from './ads/default.vue';
|
||||
export { default as Comment } from './comments/default.vue'
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { enumPageComponentTemplates } from "@/definitions/enum";
|
||||
import { Article_Button, Article_Detail_Emagazine, Article_Detail_Default, Article_Detail_Infographics, Default_Breadcrumb, ADS_Default, Comment, Article_Detail_Podcast} from "./index";
|
||||
import { Article_Button, Article_Detail_Emagazine, Article_Detail_Default, Article_Detail_Infographics,
|
||||
Default_Breadcrumb, ADS_Default, Comment, Article_Detail_Podcast, Article_Detail_Video
|
||||
} from "./index";
|
||||
const _props = defineProps<{
|
||||
settings: any;
|
||||
component?: any;
|
||||
@@ -15,7 +17,7 @@ const definedDynamicComponent: Record<string, any> = {
|
||||
'ARTICLE_BUTTON': Article_Button,
|
||||
COMMENT: Comment,
|
||||
PODCAST: Article_Detail_Podcast,
|
||||
// VIDEO: Video
|
||||
VIDEO: Article_Detail_Video
|
||||
};
|
||||
|
||||
const getCurrentComponent = computed(() => `${_props.settings.layout}`);
|
||||
|
||||
@@ -51,7 +51,7 @@ watch(currentArticle, async () => {
|
||||
break;
|
||||
|
||||
case 4:
|
||||
isContentType = 'ArticleLayoutVideo'
|
||||
isContentType = 'trang-chi-tiet-video-clip'
|
||||
break;
|
||||
|
||||
case 5:
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { createRouter, defineEventHandler, useBase } from 'h3'
|
||||
import * as navigationCtrl from '~/server/models/navigation'
|
||||
import * as eventCtrl from '~/server/models/event'
|
||||
import * as tagCtrl from '~/server/models/tag'
|
||||
import * as topicCtrl from '~/server/models/topic'
|
||||
|
||||
const router = createRouter()
|
||||
|
||||
router.get('/navigation', defineEventHandler(navigationCtrl.get))
|
||||
router.get('/tag', defineEventHandler(tagCtrl.fetchById))
|
||||
router.get('/topic', defineEventHandler(topicCtrl.fetchById))
|
||||
router.get('/event', defineEventHandler(eventCtrl.fetchById))
|
||||
|
||||
export default useBase('/api/services', router.handler)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { utils } from "~/utils/utilities";
|
||||
import Base from "./base";
|
||||
import { H3Event } from "h3";
|
||||
|
||||
export const listPaging = async(event: H3Event) => {
|
||||
try {
|
||||
const { apiUrl } = useRuntimeConfig().public
|
||||
const { siteId, page, fetch } = getQuery(event)
|
||||
|
||||
const { items, total }: any = await $fetch(`${apiUrl}/cms/event/condition/paging:${page}-${fetch}`, {
|
||||
method: 'POST',
|
||||
body: {siteIds: [siteId]}
|
||||
})
|
||||
return {items, total}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchByCode = async(event: H3Event) => {
|
||||
try {
|
||||
const { apiUrl } = useRuntimeConfig().public
|
||||
const { eventCode }: any = getQuery(event)
|
||||
const { item }: any = await $fetch(`${apiUrl}/cms/event/code:${eventCode}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Site: 1
|
||||
}
|
||||
})
|
||||
return item
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchById = async(event: H3Event) => {
|
||||
try {
|
||||
const { apiUrl } = useRuntimeConfig().public
|
||||
const { eventId }: any = getQuery(event)
|
||||
const { item }: any = await $fetch(`${apiUrl}/cms/event/${eventId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Site: 1
|
||||
}
|
||||
})
|
||||
return item
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { utils } from "~/utils/utilities";
|
||||
import { Author } from "./author";
|
||||
import Base from "./base";
|
||||
import { H3Event } from "h3";
|
||||
|
||||
export const get = async(event: H3Event) => {
|
||||
try {
|
||||
const { apiUrl } = useRuntimeConfig().public
|
||||
const { code } = getQuery(event)
|
||||
|
||||
const { items }: any = await $fetch(`${apiUrl}/cms/tag/code:${code}`, {
|
||||
headers: {
|
||||
site: 1
|
||||
}
|
||||
})
|
||||
return items
|
||||
} catch(error) {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchById = async(event: H3Event) => {
|
||||
try {
|
||||
const { apiUrl } = useRuntimeConfig().public
|
||||
const { tagId }: any = getQuery(event)
|
||||
const { item }: any = await $fetch(`${apiUrl}/cms/tag/${tagId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Site: 1
|
||||
}
|
||||
})
|
||||
return item
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { utils } from "~/utils/utilities";
|
||||
import { Author } from "./author";
|
||||
import Base from "./base";
|
||||
import { ref } from "vue"
|
||||
import { H3Event } from "h3";
|
||||
|
||||
export const listPaging = async (event: H3Event) => {
|
||||
try {
|
||||
const { apiUrl } = useRuntimeConfig().public
|
||||
const { categoryId, page, limit, sort } = getQuery(event)
|
||||
const query = ref({})
|
||||
if(categoryId) {
|
||||
query.value = { categoryId }
|
||||
}
|
||||
|
||||
const { items, total }: any = await $fetch(`${apiUrl}/cms/topic/condition/paging:${page}-${limit}/sorting:${sort}`,{
|
||||
method: 'POST',
|
||||
headers: {site: 1},
|
||||
body:{ ...query.value }
|
||||
})
|
||||
|
||||
return { items, total };
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchByCode = async(event: H3Event) => {
|
||||
try {
|
||||
const { apiUrl } = useRuntimeConfig().public
|
||||
const { topicCode }: any = getQuery(event)
|
||||
const { item }: any = await $fetch(`${apiUrl}/cms/topic/code:${topicCode}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
site: 1
|
||||
}
|
||||
})
|
||||
|
||||
return item
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchById = async(event: H3Event) => {
|
||||
try {
|
||||
const { apiUrl } = useRuntimeConfig().public
|
||||
const { topicId }: any = getQuery(event)
|
||||
const { item }: any = await $fetch(`${apiUrl}/cms/topic/${topicId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
site: 1
|
||||
}
|
||||
})
|
||||
|
||||
return item
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { LocationQuery } from "vue-router";
|
||||
import { defineStore, acceptHMRUpdate } from "pinia";
|
||||
|
||||
export const useEventStore = defineStore('event', () => {
|
||||
|
||||
const pagination = ref({
|
||||
page: utils.toNumber(useRuntimeConfig().public.pagingDefault),
|
||||
limit: utils.toNumber(useRuntimeConfig().public.pagingLimit),
|
||||
total: 0,
|
||||
});
|
||||
|
||||
function setStateFromRoute(query: LocationQuery) {
|
||||
if (query.page) pagination.value.page = utils.toNumber(query.page)
|
||||
else pagination.value.page = utils.toNumber(useRuntimeConfig().public.pagingDefault);
|
||||
if (query.limit) pagination.value.limit = utils.toNumber(query.limit)
|
||||
else pagination.value.limit = utils.toNumber(useRuntimeConfig().public.pagingLimit);
|
||||
}
|
||||
|
||||
async function listPaging(siteId: number, page: number, fetch: number) {
|
||||
const { data, error } = await useFetch<any>(`/api/services/events-paging?siteId=${siteId}&page=${page}&fetch=${fetch}`)
|
||||
if (error.value) {
|
||||
return null
|
||||
}
|
||||
return data.value
|
||||
}
|
||||
|
||||
async function fetchById(id: string) {
|
||||
const { data, error } = await useFetch<any>(`/api/services/event`, {
|
||||
query: {
|
||||
eventId: id
|
||||
}
|
||||
})
|
||||
if(error.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return data.value
|
||||
}
|
||||
|
||||
return { listPaging, fetchById, setStateFromRoute, pagination }
|
||||
})
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useEventStore, import.meta.hot))
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export const useTagStore = defineStore('tag', () => {
|
||||
// async function fetchByCode(code: string) {
|
||||
// const { data, error } = await useFetch<any>(`/api/services/tag`, { query: {code: code}})
|
||||
// if(error.value) {
|
||||
// return null
|
||||
// }
|
||||
// return data.value[0]
|
||||
// }
|
||||
|
||||
async function fetchById(id: string) {
|
||||
const { data, error } = await useFetch<any>(`/api/services/tag`, {
|
||||
query: {
|
||||
tagId: id
|
||||
}
|
||||
})
|
||||
if(error.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return data.value
|
||||
}
|
||||
|
||||
return { fetchById }
|
||||
})
|
||||
|
||||
if(import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useTagStore, import.meta.hot))
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { LocationQuery } from "vue-router";
|
||||
import { defineStore, acceptHMRUpdate } from "pinia";
|
||||
|
||||
export const useTopicStore = defineStore('topic', () => {
|
||||
const pagination = ref({
|
||||
page: utils.toNumber(useRuntimeConfig().public.pagingDefault),
|
||||
limit: utils.toNumber(useRuntimeConfig().public.pagingLimit),
|
||||
total: 0,
|
||||
});
|
||||
|
||||
function setStateFromRoute(query: LocationQuery) {
|
||||
if (query.page) pagination.value.page = utils.toNumber(query.page)
|
||||
else pagination.value.page = utils.toNumber(useRuntimeConfig().public.pagingDefault);
|
||||
if (query.limit) pagination.value.limit = utils.toNumber(query.limit)
|
||||
else pagination.value.limit = utils.toNumber(useRuntimeConfig().public.pagingLimit);
|
||||
}
|
||||
|
||||
async function fetchById(topicId: string) {
|
||||
const { data, error } = await useFetch<any>(`/api/services/topic`, {
|
||||
query: {
|
||||
topicId: topicId
|
||||
}
|
||||
})
|
||||
if(error.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return data.value
|
||||
}
|
||||
|
||||
async function fetchByCategoryId(categoryId: number, limit?: number) {
|
||||
if(limit) {
|
||||
pagination.value.limit = limit
|
||||
}
|
||||
const { data, error } = await useFetch<any>(`/api/services/topics-paging`, {
|
||||
query: {
|
||||
categoryId: categoryId,
|
||||
limit: pagination.value.limit,
|
||||
page: pagination.value.page,
|
||||
sort: 'createdon desc'
|
||||
}
|
||||
})
|
||||
if(error.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return data.value.items
|
||||
}
|
||||
|
||||
return { pagination, setStateFromRoute, fetchById, fetchByCategoryId }
|
||||
})
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useTopicStore, import.meta.hot))
|
||||
}
|
||||
Reference in New Issue
Block a user