|
- <template>
- <Layout>
- <BRow>
- <BCol sm="12">
- <BCard no-body>
- <BCardHeader>
- <h5>ویرایش بلاگ</h5>
- </BCardHeader>
- <BCardBody>
- <BTabs>
- <BTab title="عمومی">
- <BRow class="mt-4">
- <BCol md="6">
- <div class="form-group">
- <label class="form-label">تصویر بلاگ</label>
-
- <input
- type="file"
- accept="image/*"
- @change="handleImageChange"
- class="form-control"
- :class="{ 'is-invalid': errors.image }"
- />
-
- <div v-if="imagePreview" class="mt-2">
- <img
- :src="imagePreview"
- alt="Image Preview"
- class="img-fluid rounded shadow-sm Image-Preview"
- />
- </div>
-
- <small v-if="errors.image" class="text-danger">
- {{ errors.image }}
- </small>
- </div>
- </BCol>
-
- <BCol md="6">
- <div class="form-group">
- <label class="form-label">دسته</label>
- <VueSelect
- style="--vs-min-height: 48px; --vs-border-radius: 8px"
- :isLoading="categorySelectorLoader"
- v-model="blogCat"
- :options="formattedCategories"
- :reduce="(option) => option.value"
- placeholder="دسته ای را انتخاب نمایید"
- @search="handleSearch"
- />
- </div>
- <small v-if="errors.blogCat" class="text-danger">
- {{ errors.blogCat }}
- </small>
- </BCol>
-
- <button
- :disabled="loadingFirstTab"
- @click="handlerSubmitCategory"
- class="btn rounded btn-primary w-auto mt-5 d-flex justify-content-center align-items-center mx-auto"
- >
- <span
- v-if="loadingFirstTab"
- class="spinner-border spinner-border-sm "
- role="status"
- />
- ذخیره
- </button>
- </BRow>
- </BTab>
-
- <BTab title="ترجمه ها">
- <BButton
- :disabled="!findLocaleTranslation"
- :loading="loadingDelete"
- @click="handlerRemoveTranslation"
- class="btn btn-sm rounded btn-danger d-block mt-5"
- style="margin-right: auto"
- >
- حذف ترجمه {{ findLocaleTranslation }}
- </BButton>
- <BRow class="g-3 mt-4">
- <BCol lg="6">
- <div class="form-group">
- <label class="form-label">انتخاب زبان</label>
- <select
- v-model="locale"
- class="form-control"
- placeholder="انتخاب کنید"
- @change="handlerChangeLocale"
- >
- <option
- key="fa"
- value="fa"
- >
- فارسی
- </option>
- <option
- key="en"
- value="en"
- >
- انگلیسی
- </option>
- <option
- key="ar"
- value="ar"
- >
- عربی
- </option>
- </select>
- </div>
- </BCol>
-
- <BCol md="6">
- <div class="form-group">
- <label class="form-label">عنوان بلاگ</label>
- <input
- type="text"
- v-model="title"
- class="form-control"
- placeholder="عنوان بلاگ"
- :class="{ 'is-invalid': errors.title }"
- @input="clearError('title')"
- />
- </div>
- <small v-if="errors.title" class="text-danger">
- {{ errors.title }}
- </small>
- </BCol>
-
- <BCol md="6">
- <div class="form-group">
- <label class="form-label">کلمه کلیدی</label>
- <input
- type="text"
- v-model="slug"
- class="form-control"
- placeholder="کلمه کلیدی بلاگ"
- :class="{ 'is-invalid': errors.slug }"
- @input="clearError('slug')"
- />
- </div>
- <small v-if="errors.slug" class="text-danger">
- {{ errors.slug }}
- </small>
- </BCol>
-
- <BCol md="6">
- <div class="form-group">
- <label class="form-label">خلاصه</label>
- <textarea
- v-model="summary"
- class="form-control"
- placeholder="خلاصه ای از بلاگ"
- :class="{ 'is-invalid': errors.summary }"
- @input="clearError('summary')"
- />
- </div>
- <small v-if="errors.summary" class="text-danger">
- {{ errors.summary }}
- </small>
- </BCol>
-
- <BCol md="6">
- <div class="form-group">
- <label class="form-label">نویسنده</label>
- <input
- type="text"
- v-model="author"
- class="form-control"
- placeholder="نویسنده"
- :class="{ 'is-invalid': errors.author }"
- @input="clearError('author')"
- />
- </div>
- <small v-if="errors.author" class="text-danger">
- {{ errors.author }}
- </small>
- </BCol>
-
- <BCol md="12">
- <div class="form-group">
- <label class="form-label">محتوا</label>
- <div
- @input="clearError('editorContent')"
- ref="editor"
- class="quill-editor"
- ></div>
- </div>
- <small v-if="errors.editorContent" class="text-danger">
- {{ errors.editorContent }}
- </small>
- </BCol>
- </BRow>
- <div class="d-flex justify-content-center">
- <button
- type="submit"
- class="btn btn-primary mt-5"
- @click.prevent="submitForm"
- :disabled="loading"
- >
- <span v-if="loading">
- <i class="fa fa-spinner fa-spin"></i> بارگذاری...
- </span>
- <span v-else>ویرایش</span>
- </button>
- </div>
- </BTab>
- </BTabs>
- </BCardBody>
- </BCard>
- </BCol>
- </BRow>
- </Layout>
- </template>
-
- <script>
- import VueSelect from "vue3-select-component";
- import { toast } from "vue3-toastify";
- import "vue3-toastify/dist/index.css";
- import ApiServiece from "@/services/ApiService";
- import { ref, onMounted, computed } from "vue";
- import Layout from "@/layout/custom.vue";
- import Quill from "quill";
- import "quill/dist/quill.snow.css";
- import { useRoute } from "vue-router";
- import {BTabs} from "bootstrap-vue-next";
- export default {
- name: "SAMPLE-PAGE",
- components: {
- BTabs,
- Layout,
- VueSelect,
- },
- setup() {
- const quillInstance = ref(null);
- const blog = ref();
- const route = useRoute();
- const loading = ref(false);
- const image = ref();
- const imagePreview = ref();
- const errors = ref({});
- const title = ref("");
- const slug = ref("");
- const summary = ref("");
- const blogCat = ref();
- const author = ref("");
- const editor = ref(null);
- const categorySelectorLoader = ref(false);
- const categories = ref([{ value: null, label: null }]);
- const editorContent = ref("");
- const locale = ref("fa");
- const blogTranslationId = ref();
- const loadingFirstTab = ref(false);
- const loadingDelete = ref(false);
-
- const formattedCategories = computed(() =>
- Array.isArray(categories.value)
- ? categories.value.map((category) => ({
- value: category?.translation?.id,
- label: category?.translation?.title,
- }))
- : []
- );
-
- const findLocaleTranslation = computed(() => {
- const foundTranslation = blog.value?.translations?.find(
- item => item?.locale === locale.value
- );
-
- if (foundTranslation) {
- switch (foundTranslation?.locale) {
- case "en":
- return "انگلیسی";
- case "fa":
- return "فارسی";
- case "ar":
- return "عربی";
- default:
- return null;
- }
- }
-
- return null;
- });
-
- const handleSearch = async (searchTerm) => {
- if (searchTerm?.length < 3) return;
-
- categorySelectorLoader.value = true;
-
- try {
- const response = await ApiServiece.get(
- `admin/blog-categories?title=${searchTerm ?? ''}`
- );
- categories.value = response.data.data;
- categorySelectorLoader.value = false;
- } catch (error) {
- categorySelectorLoader.value = false;
- categories.value = [];
- }
- };
-
- const handleImageChange = (event) => {
- const file = event.target.files[0];
-
- if (file) {
- errors.value.image = null;
-
- image.value = file;
-
- const reader = new FileReader();
- reader.onload = () => {
- imagePreview.value = reader.result;
- };
- reader.readAsDataURL(file);
- }
- };
-
- const validateForm = () => {
- errors.value = {};
- if (!title.value) errors.value.title = "وارد کردن عنوان بلاگ الزامی است";
- if (!slug.value)
- errors.value.slug = "وارد کردن کلمه کلیدی بلاگ ضروری می باشد";
- if (!summary.value)
- errors.value.summary = "وارد کردن خلاصه بلاگ ضروری می باشد";
- if (!blogCat.value)
- errors.value.blogCat = "انتخاب دسته برای بلاگ ضروری می باشد";
- if (!author.value)
- errors.value.author = "وارد کردن نویسنده بلاگ ضروری می باشد";
- if (!editorContent.value)
- errors.value.editorContent = "وارد کردن محتوای بلاگ ضروری می باشد";
- if (!imagePreview.value)
- errors.value.image = "وارد کردن عکس بلاگ ضروری می باشد";
- return Object.keys(errors.value)?.length === 0;
- };
-
- const clearError = (field) => {
- errors.value[field] = "";
- };
-
- const getBlog = () => {
- ApiServiece.get(`admin/blogs/${route.params.id}`)
- .then((resp) => {
- blog.value = resp.data.data;
-
- categories.value[0].value = blog.value?.blog_category_id;
-
- categories.value[0].lable = blog.value?.blog_category?.title;
-
- title.value = blog.value?.translation?.title;
-
- slug.value = blog.value?.translation?.slug;
-
- summary.value = blog.value?.translation?.summary;
-
- imagePreview.value = blog.value?.image;
-
- blogCat.value = blog.value?.blog_category_id;
-
- author.value = blog.value?.translation?.author;
-
- locale.value = blog.value?.translation?.locale;
-
- if (editor.value) {
- quillInstance.value = new Quill(editor.value, {
- theme: "snow",
- modules: {
- toolbar: [
- [{ header: "1" }, { header: "2" }, { font: [] }],
- [{ list: "ordered" }, { list: "bullet" }],
- [{ align: [] }],
- ["bold", "italic", "underline"],
- ["link", "image"],
- [{ script: "sub" }, { script: "super" }],
- [{ direction: "rtl" }],
- ],
- },
- });
-
- quillInstance.value.root.innerHTML = blog.value?.translation?.content;
-
- editorContent.value = blog.value?.translation?.content;
-
- // ✨ Update content on change
- quillInstance.value.on("text-change", () => {
- editorContent.value = quillInstance.value.root.innerHTML;
- });
- }
- })
- .catch((err) => {
- console.log(err);
- });
- };
-
- onMounted( () => {
- getBlog();
-
- handleSearch()
- });
-
- const submitForm = () => {
- if (!validateForm()) {
- toast.error("لطفا فیلد های لازم را وارد نمایید", {
- position: "top-right",
- autoClose: 1000,
- });
- return;
- }
-
- loading.value = true;
-
- const params = {
- title: title.value,
- slug: slug.value,
- content: editorContent.value,
- author: author.value,
- summary: summary.value,
- locale: locale.value,
- };
-
- const existingTranslation = blog.value?.translations?.find(
- t => t.locale === locale.value
- );
-
- const url = existingTranslation ? `admin/blogs/${route.params.id}/translations/${existingTranslation?.id}` : `admin/blogs/${route.params.id}/translations`
-
- ApiServiece[existingTranslation ? 'put' : 'post'](url, params, {
- headers: {
- Authorization: `Bearer ${localStorage.getItem("token")}`,
- },
- })
- .then((resp) => {
- const updatedCategory = blog.value;
-
- updatedCategory.translations = resp?.data?.data?.translations
-
- blog.value = updatedCategory;
-
- toast.success(resp?.data?.message, {
- position: "top-right",
- autoClose: 1000,
- });
- })
-
- .catch((error) => {
- toast.error(error?.response?.data?.message, {
- position: "top-right",
- autoClose: 1000,
- })
- })
- .finally(() => {
- loading.value = false;
- });
- };
-
- const handlerChangeLocale = (e) => {
- const findLocale = blog.value?.translations?.find(item => item?.locale === e.target.value);
-
- if (findLocale) {
- title.value = findLocale?.title;
-
- slug.value = findLocale?.slug;
-
- summary.value = findLocale?.summary;
-
- author.value = findLocale?.author;
-
- locale.value = findLocale?.locale;
-
- blogTranslationId.value = findLocale?.id
-
- quillInstance.value.root.innerHTML = findLocale?.content
- } else {
- title.value = undefined;
-
- slug.value = undefined;
-
- summary.value = undefined;
-
- author.value = undefined;
-
- quillInstance.value.root.innerHTML = undefined
- }
- }
-
- const handlerSubmitCategory = async () => {
- try {
- loadingFirstTab.value = true;
-
- const formData = new FormData();
-
- if (image.value) {
- formData.append("image", image.value);
- }
-
- formData.append("blog_categories", blogCat.value);
-
- formData.append("_method", 'put');
-
- const { data: { success, message } } = await ApiServiece.post(
- `admin/blogs/${blog.value?.id}`, formData, {
- headers: {
- "content-type": "multipart",
- Authorization: `Bearer ${localStorage.getItem("token")}`,
- },
- });
-
- if(success) {
- /* categoryId.value = data?.id
-
- emit("cat-updated")*/
-
- toast.success(message)
- }
- } catch (e) {
- toast.error(e?.response?.data?.message)
- } finally {
- loadingFirstTab.value = false;
- }
- };
-
- const handlerRemoveTranslation = async () => {
- const findLocale = blog.value?.translations?.find(item => item?.locale === locale.value);
-
- if (findLocale) {
- try {
- loadingDelete.value = true;
-
- const { data: { success, message, data } } = await ApiServiece.delete(
- `admin/blogs/${blog.value?.id}/translations/${findLocale?.id}`
- )
-
- if (success) {
- const updatedCategory = blog.value
-
- updatedCategory.translations = data?.translations
-
- blog.value = updatedCategory
-
- toast.success(message)
- }
- } catch (e) {
- console.log(e)
- }finally {
- loadingDelete.value = false
- }
- }
- }
-
- return {
- title,
- slug,
- summary,
- editor,
- categories,
- errors,
- image,
- imagePreview,
- handleImageChange,
- formattedCategories,
- submitForm,
- clearError,
- editorContent,
- author,
- blogCat,
- loading,
- loadingFirstTab,
- handleSearch,
- categorySelectorLoader,
- locale,
- handlerChangeLocale,
- blogTranslationId,
- handlerSubmitCategory,
- findLocaleTranslation,
- handlerRemoveTranslation,
- loadingDelete,
- };
- },
- };
- </script>
-
- <style scoped>
- .quill-editor {
- height: 300px;
- border: 1px solid #ccc;
- border-radius: 5px;
- }
-
- .quill-editor .ql-container {
- font-family: "Vazir", "Arial", sans-serif;
- font-size: 14px;
- }
-
- .quill-editor .ql-editor {
- padding: 10px;
- text-align: right;
- }
-
- .ql-editor {
- direction: rtl;
- text-align: right;
- }
-
- .ql-editor::before {
- content: attr(placeholder);
- direction: rtl !important;
- text-align: right;
- }
- .Image-Preview {
- min-width: 200px;
- max-height: 200px;
- max-width: 200px;
- object-fit: cover;
- border-radius: 8px;
- border: 1px solid #ddd;
- }
- .delete-btn {
- display: flex;
- align-items: center;
- padding: 3px;
- font-size: 10px;
- background-color: #e74c3c;
- color: white;
- border: none;
- border-radius: 5px;
- cursor: pointer;
- transition: background-color 0.3s ease, transform 0.2s ease;
- gap: 8px;
- margin-right: 100px;
- }
-
- .delete-btn:hover {
- background-color: #c0392b;
- transform: scale(1.05);
- }
-
- .delete-btn:active {
- background-color: #a93226;
- }
-
- .delete-btn:focus {
- outline: none;
- }
- </style>
|