conflict-nuxt-4/app/components/lazy-load/global/lineContent.vue
Baghi330 7892a7cefb 1
2026-02-14 10:41:53 +03:30

543 lines
15 KiB
Vue
Executable File

<template>
<div class="flex flex-col sm:flex-row items-center mb-3 line-content">
<template v-for="(itemSchema, j) in props.lineSchema?.items || []" :key="j">
<div
v-if="isExistDataItems(props.itemData, itemSchema)"
class="flex items-center ms-2"
:class="itemSchema.style"
>
<label
v-if="itemSchema.label"
class="whitespace-nowrap ms-1 text-black"
:style="{ fontFamily: 'sahel-semi-bold' }"
for=""
>
{{ itemSchema.label }}
</label>
<template v-if="item_schema_link_route">
<!-- ⚠️ توجه: itemSchema.link_route نیاز به دسترسی دارد -->
<a
@click.prevent="openNewPage(props.itemData, itemSchema)"
@mousedown.middle.prevent="openNewPage(props.itemData, itemSchema)"
class="text-[15px] text-content"
v-html="getDataItems(props.itemData, itemSchema)"
></a>
</template>
<template v-else-if="itemSchema.isArray">
<div class="text-wrap">
<span
v-for="(item, index) in getDataItems(props.itemData, itemSchema)"
:key="index"
class="me-2"
:class="itemSchema.style"
>
<span v-if="itemSchema.isArray === 2">{{ item }} ,</span>
<span v-else>{{ item.title }} ,</span>
</span>
</div>
</template>
<template v-else-if="itemSchema.style === 'search-voice'">
<div class="w-11/12">
<div
v-for="(item, index) in getHtmlSound(props.itemData, itemSchema)"
:key="index"
class="text-[14px] text-content"
>
<div class="flex items-center mb-1">
<span
@click="playVoice(props.itemData, index, $event)"
class="search-tag me-2"
>
پخش صوت
</span>
<span v-html="item"></span>
</div>
</div>
<audio
:key="
audioSource ?? getApiLink(props.itemData._source.sound_link)
"
ref="audioPlayer"
controls
crossorigin
playsinline
>
<source :src="audioSource" type="audio/mp3" />
</audio>
</div>
</template>
<div
v-else
class="text-[14px] text-content"
v-html="getDataItems(props.itemData, itemSchema)"
></div>
</div>
</template>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useNuxtApp, useRouter } from "#app";
import { persianDateAndTime } from "@/manuals/utilities.js";
// Define props
const props = defineProps({
lineSchema: {
type: Object,
default: () => ({}),
},
arrayItemData: {
type: Array,
default: () => [],
},
itemData: {
type: Object,
default: () => ({}),
},
textSearch: {
type: String,
default: "",
},
});
// Define emits
const emit = defineEmits(["openModalHandler", "click-item"]);
// Use composables and stores
const { $http: httpService } = useNuxtApp();
const router = useRouter();
// Template ref for audio element
const audioPlayer = ref(null);
// Reactive state (replaces `data()`)
const test = ref([
{ id: 3262, title: "جدول تجزیه اصطلاحات موضوع" },
{ id: 3260, title: "جدول تطبیق جدول جامعه" },
{ id: 3263, title: "جدول تجزیه اصطلاحات مقیاس" },
]);
const vuePlyrOptions = ref({
controls: ["play", "progress", "current-time", "mute", "volume", "settings"],
speed: { selected: 1, options: [0.75, 1, 1.5, 2] },
iconUrl: "/img/plyr.svg",
});
const audioSource = ref("");
// Methods (replaces `methods` object)
const openModalHandler = (item, schema) => {
emit("openModalHandler", { item, schema });
};
const getHtmlSound = (itemData, itemSchema) => {
let value = highlightKey2(itemData, itemSchema.source_key, "\n");
let items = value.split("\n");
return items;
};
const playVoice = (itemData, index, event) => {
let voice_times = itemData.voice_times;
if (!voice_times || !voice_times[index]) return;
audioSource.value = getApiLink(itemData._source.sound_link);
const selectedVoice = voice_times[index];
const start = selectedVoice.start || 0;
// استفاده از template ref به جای جستجو در DOM
const player = audioPlayer.value;
if (!player) {
console.warn("Audio element not found!");
return;
}
const initPlay = () => {
player.muted = true;
return player
.play()
.then(() => {
player.pause();
player.muted = false;
})
.catch((err) => console.warn("Init play failed:", err));
};
const playFromStart = () => {
player.currentTime = start;
return player.play().catch((err) => {
console.warn("Playback failed:", err);
});
};
if (player.readyState === 0) {
player.addEventListener(
"loadedmetadata",
() => {
initPlay().then(() => playFromStart());
},
{ once: true },
);
} else {
initPlay().then(() => playFromStart());
}
};
const getApiLink = (link) => {
if (!link || typeof link !== "string") return "";
// نکته: در Nuxt 3 متغیرهای محیطی معمولا با NUXT_PUBLIC_ شروع می‌شوند
let url = process.env.VUE_APP_BASE_URL + process.env.VUE_APP_API_NAME;
if (link.startsWith("/Quick/")) url += "media2" + link;
else if (link.startsWith("meidaex/")) url += link;
else url += link;
return url;
};
const isExistDataItems = (item, schema) => {
if (schema.style == "search-voice") return true;
let source_key = schema.source_key;
let hilight_key = schema.hilight_key;
if (hilight_key && item?.highlight && item?.highlight[hilight_key]?.length)
return true;
const keys = source_key?.split(",");
if (keys.length >= 2) {
for (let key of keys) {
if (item?._source[key]?.trim()) {
return true;
}
}
return false;
} else {
let value;
if (keys[0] == "voice_times") {
value = getSourceData(item?._source, "sound_link");
} else value = getSourceData(item?._source, keys[0]);
if (value) {
if (Array.isArray(value) && value.length == 0) return false;
else return true;
} else return false;
}
};
const getDataItems = (item, schema) => {
let source_key = schema.source_key;
let trancate_word = schema?.trancate_word;
let hilight_delimiter = schema?.hilight_delimiter ?? "... ";
const keys = source_key?.split(",");
let value = "";
let key = keys[0];
if (keys.length > 1) {
for (let key1 of keys) {
if (item?._source[key1]?.trim()) {
key = key1;
break;
}
}
}
value = highlightKey2(item, key, hilight_delimiter);
if (value != "") return value;
if (schema.key == "tags") {
value = textTags(item, schema.key);
} else if (schema.key == "convert_time") {
value = getSourceData(item?._source, key);
if (typeof value === "string" && value.includes(":")) {
let parts = value.split(":");
if (parts.length >= 2) {
return `${parts[0]}:${parts[1]}`;
}
}
return value;
} else {
value = getSourceData(item?._source, key);
if (trancate_word && trancate_word > 0) {
let words = value.split(" ");
if (words.length > trancate_word) {
value = words.slice(0, trancate_word).join(" ");
value += "...";
}
}
if (typeof value === "string") {
value = value.replaceAll("\n", " ");
value = value.replaceAll("\t", "");
}
if (schema?.process) {
if (schema.process == "convert_date") {
return formatDateToPersian(value);
} else if (schema.process == "convert_gdate") {
return convertTo3DateLang(value);
}
} else if (schema.key == "date_create") {
return formatDateToPersian(value);
}
}
if (value == "") {
value = "--";
}
return value;
};
const convertTo3DateLang = (date) => {
const resFa = persianDateAndTime(date, "fa-IR");
const resAr = persianDateAndTime(date, "fa-u-ca-islamic-umalqura");
const parsedDate =
`${resFa.weekday.short} ${resFa.year.numeric}/${resFa.month.numeric}/${resFa.day.numeric}` +
` - ${resAr.day.numeric} ${resAr.month.short} ${resAr.year.numeric}`;
return parsedDate;
};
const formatDateToPersian = (item) => {
let date;
let num = Number(item);
if (!isNaN(num)) {
if (item.toString().length == 10) {
date = new Date(item * 1000);
}
} else if (!item.includes("T00:00:00")) item = item + "T00:00:00";
date = new Date(item);
let date_f = date.toLocaleDateString("fa-IR");
return date_f;
};
const textTags = (item, key) => {
let text = "";
if (Array.isArray(item._source[key])) text = item._source[key].join("، ");
else text = item._source[key];
return text;
};
const highlightKey2 = (
item,
key,
hilight_delimiter = "...",
posfix = ["fa", "ph"],
) => {
var text = "";
if (item.highlight) {
let key_highlight = key;
let i = 0;
while (i < posfix.length) {
if (key_highlight in item.highlight) break;
key_highlight = key + "." + posfix[i];
i++;
}
if (key_highlight in item.highlight) {
let value1 = "";
value1 = item.highlight[key_highlight];
if (Array.isArray(value1)) text = value1.join(hilight_delimiter);
else text = value1;
}
}
return text;
};
const highlightKey = (item, key1, key2 = "", key3 = "") => {
var text = "";
if (item.highlight) {
if (item.highlight[key1]) text = item.highlight[key1].join("... ");
else if (key2 && item.highlight[key2])
text = item.highlight[key2].join("... ");
else if (key3 && item.highlight[key3])
text = item.highlight[key3].join("... ");
}
if (text == "") {
if (item._source[key1]) text = item._source[key1];
else if (key2 && item._source[key2]) text = item._source[key2];
else if (key3 && item._source[key3]) text = item._source[key3];
if (text.length > 500) text = text.substring(0, 500);
}
return text;
};
const openNewPage = (item, schema) => {
let cloneItem = { ...item };
if (schema.link_route.length == 0) return;
if (schema.link_route.url_key) {
window.open(item._source.url, "_blank");
} else {
let cloneList = [];
props.arrayItemData.forEach((item) => {
cloneList.push({
_id: item._id,
_source: {
id: item._id,
title: item?._source?.title ?? item?._source?.book_title,
qanon_title: item?._source?.qanon_title,
qanon_id: item?._source?.qanon_id,
},
});
});
localStorage.setItem("myList", JSON.stringify(cloneList));
localStorage.setItem("myItem", JSON.stringify(cloneItem));
let key = "";
let query_key = "";
let query_value = "";
let keys = "";
let id_route = "";
let id_route2 = "";
let keyName = schema?.link_route.id;
let query = {};
if (process.env.VUE_APP_BUILD_NAME != "majles") {
query = { searchtext: props.textSearch ?? undefined };
}
if (schema.link_route.id2) {
id_route2 = getSourceData(item, schema.link_route.id2);
}
id_route = getSourceData(item, schema.link_route.id);
if (schema.link_route?.query) {
let querys = schema.link_route.query;
if (typeof querys === "string") {
keys = schema.link_route?.query.split("=");
if (keys.length >= 2) {
query_key = keys[0];
query_value = keys[1];
}
if (query_key && query_value) {
query[query_key] = query_value;
}
} else {
let newObject = {};
for (const [key, value] of Object.entries(querys)) {
if (value in item) {
newObject[key] = item[value];
} else {
newObject[key] = item?._source[value];
}
}
Object.assign(query, newObject);
}
}
if (schema.link_route?.key_item || schema.link_route.key) {
if (schema.link_route?.key_item)
key = getSourceData(item, schema.link_route.key_item);
else key = schema.link_route.key;
}
const routeData = router.resolve({
name: schema.link_route.name,
params: {
id: id_route,
...(id_route2 && { id2: id_route2 }),
key: key,
},
query: query,
});
emit("click-item", { id: id_route });
window.open(routeData.href, "_blank");
}
};
const getArrayData = (itemData, collapseItem) => {
if (!collapseItem.array_key) return [itemData];
let sourceData = getSourceData(itemData, collapseItem.array_key);
if (!Array.isArray(sourceData)) return [itemData];
return sourceData;
};
const getSourceData = (itemData, key) => {
let sourceData = itemData;
key?.split(".").forEach((k) => {
let keyName = k;
if (keyName in sourceData) {
sourceData = sourceData[keyName];
} else if (sourceData?._source && keyName in sourceData?._source) {
sourceData = sourceData?._source[keyName];
} else {
sourceData = "";
}
});
return sourceData;
};
</script>
<style lang="scss">
.search-tag {
cursor: pointer;
color: #96a5b5;
white-space: nowrap;
padding: 0 5px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: #fff;
border: 1px solid #e4dfd8;
height: 20px;
margin-left: 8px;
&:hover {
color: black;
border-color: black;
}
}
</style>
<style lang="scss" scoped>
.search-tag {
cursor: pointer;
color: #96a5b5;
white-space: nowrap;
padding: 0 5px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: #fff;
border: 1px solid #e4dfd8;
height: 20px;
margin-left: 8px;
&:hover {
color: black;
border-color: black;
}
}
.search-title {
cursor: pointer;
color: rgb(59, 130, 246) !important;
&:hover {
color: rgb(59, 130, 246) !important;
text-decoration: underline !important;
}
}
.search-label {
a {
color: #00b6e3;
}
span {
color: #00b6e3;
}
}
.search-body {
display: -webkit-box !important;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 25px;
color: #000;
}
.text-content {
text-align: justify;
}
</style>