You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

587 line
16 KiB

  1. <script>
  2. import Layout from "@/layout/custom.vue";
  3. import ApiServiece from "@/services/ApiService";
  4. import moment from "jalali-moment";
  5. import { onMounted, ref, watch, computed } from "vue";
  6. import { toast } from "vue3-toastify";
  7. import "vue3-toastify/dist/index.css";
  8. import Swal from "sweetalert2";
  9. import showDescription from "@/components/modals/commonModals/showDescription.vue";
  10. export default {
  11. name: "BORDER",
  12. components: {
  13. Layout,
  14. showDescription,
  15. },
  16. setup() {
  17. const selectedPageType = ref("");
  18. let searchTimeout = null;
  19. const searchPage = ref();
  20. const currentPage = ref(1);
  21. const totalPages = ref(1);
  22. const paginate = ref(20);
  23. const page = ref(1);
  24. const catDescription = ref();
  25. const filterLoading = ref(false);
  26. const searchQuery = ref("");
  27. const banners = ref();
  28. const catTitle = ref();
  29. const catId = ref();
  30. const catParent = ref();
  31. const catImage = ref();
  32. const convertToJalali = (date) => {
  33. return moment(date, "YYYY-MM-DD HH:mm:ss")
  34. .locale("fa")
  35. .format("YYYY/MM/DD");
  36. };
  37. const handleSearchChange = () => {
  38. clearTimeout(searchTimeout);
  39. searchTimeout = setTimeout(() => {
  40. getBanners();
  41. page.value = 1;
  42. }, 500);
  43. };
  44. watch(searchQuery, () => {
  45. handleSearchChange();
  46. });
  47. const getBanners = () => {
  48. filterLoading.value = true;
  49. ApiServiece.get(
  50. `admin/banners?paginate=${paginate.value || 10}&page=${
  51. page.value || 1
  52. }&title=${searchQuery.value || ""}&page_type=${
  53. selectedPageType.value || ""
  54. }`
  55. )
  56. .then((resp) => {
  57. filterLoading.value = false;
  58. banners.value = resp.data.data.data;
  59. currentPage.value = resp.data.data.current_page;
  60. totalPages.value = resp.data.data.last_page;
  61. })
  62. .catch(() => {
  63. filterLoading.value = false;
  64. });
  65. };
  66. const visiblePages = computed(() => {
  67. const pages = [];
  68. if (totalPages.value <= 5) {
  69. for (let i = 1; i <= totalPages.value; i++) {
  70. pages.push(i);
  71. }
  72. } else {
  73. let start = currentPage.value - 2;
  74. let end = currentPage.value + 2;
  75. if (start < 1) {
  76. end += 1 - start;
  77. start = 1;
  78. }
  79. if (end > totalPages.value) {
  80. start -= end - totalPages.value;
  81. end = totalPages.value;
  82. }
  83. start = Math.max(start, 1);
  84. for (let i = start; i <= end; i++) {
  85. pages.push(i);
  86. }
  87. }
  88. return pages;
  89. });
  90. function handlePageInput() {
  91. if (searchPage.value < 1) {
  92. searchPage.value = 1;
  93. } else if (searchPage.value > totalPages.value) {
  94. searchPage.value = totalPages.value;
  95. }
  96. if (searchPage.value >= 1 && searchPage.value <= totalPages.value) {
  97. page.value = searchPage.value;
  98. }
  99. }
  100. const nextPage = () => {
  101. if (currentPage.value < totalPages.value) {
  102. page.value++;
  103. getBanners();
  104. }
  105. };
  106. const prevPage = () => {
  107. if (currentPage.value > 1) {
  108. page.value--;
  109. getBanners();
  110. }
  111. };
  112. const handleCatUpdated = () => {
  113. getBanners();
  114. };
  115. const deleteBanner = (id, title) => {
  116. Swal.fire({
  117. text: `می خواهید بنر ${title} را حذف کنید؟`,
  118. icon: "warning",
  119. showCancelButton: true,
  120. confirmButtonColor: "#3085d6",
  121. cancelButtonColor: "#d33",
  122. confirmButtonText: "بله!",
  123. cancelButtonText: "خیر",
  124. }).then((result) => {
  125. if (result.isConfirmed) {
  126. ApiServiece.delete(`admin/banners/${id}`)
  127. .then(() => {
  128. toast.success("!بنر با موفقیت حذف شد", {
  129. position: "top-right",
  130. autoClose: 3000,
  131. });
  132. getBanners();
  133. })
  134. .catch((err) => {
  135. console.log(err);
  136. toast.error("!مشکلی در حذف کردن بنر پیش آمد", {
  137. position: "top-right",
  138. autoClose: 3000,
  139. });
  140. });
  141. }
  142. });
  143. };
  144. const descriptionModal = (desc) => {
  145. catDescription.value = desc;
  146. };
  147. watch(page, () => {
  148. getBanners();
  149. });
  150. watch(selectedPageType, () => {
  151. getBanners();
  152. });
  153. const restoreBanner = (id, title) => {
  154. Swal.fire({
  155. text: `می خواهید بنر ${title} را بازیابی کنید؟`,
  156. icon: "warning",
  157. showCancelButton: true,
  158. confirmButtonColor: "#3085d6",
  159. cancelButtonColor: "#d33",
  160. confirmButtonText: "بله!",
  161. cancelButtonText: "خیر",
  162. }).then((result) => {
  163. if (result.isConfirmed) {
  164. ApiServiece.put(`admin/banners/${id}/restore`)
  165. .then(() => {
  166. toast.success("!بنر با موفقیت بازیابی شد", {
  167. position: "top-right",
  168. autoClose: 3000,
  169. });
  170. })
  171. .then(() => {
  172. getBanners();
  173. })
  174. .catch((err) => {
  175. console.log(err);
  176. toast.error("!مشکلی در بازیابی بنر پیش آمد", {
  177. position: "top-right",
  178. autoClose: 3000,
  179. });
  180. });
  181. }
  182. });
  183. };
  184. const setPageType = (banner)=> {
  185. switch (banner.page_type) {
  186. case 'category':
  187. return 'دسته ' + banner?.category_page?.title;
  188. case 'brand':
  189. return 'برند ' + banner?.brand_page?.translation?.title
  190. case 'main_page':
  191. return 'صفحه اصلی'
  192. case 'special_page':
  193. return 'شگفت انگیزها'
  194. case 'blog_page':
  195. return 'مقالات'
  196. }
  197. }
  198. const setProductOrCategory = (banner) => {
  199. if (banner?.category_id) {
  200. return `دسته<br>${banner?.category?.translation?.title ?? 'ندارد'}`;
  201. }
  202. if (banner?.product_id) {
  203. return `محصول<br>${banner?.product?.translation?.title ?? 'ندارد'}`;
  204. }
  205. return "";
  206. };
  207. onMounted(() => {
  208. getBanners();
  209. });
  210. return {
  211. banners,
  212. convertToJalali,
  213. handleCatUpdated,
  214. restoreBanner,
  215. deleteBanner,
  216. searchQuery,
  217. filterLoading,
  218. descriptionModal,
  219. catDescription,
  220. catTitle,
  221. catParent,
  222. catImage,
  223. catId,
  224. currentPage,
  225. totalPages,
  226. nextPage,
  227. prevPage,
  228. page,
  229. handlePageInput,
  230. searchPage,
  231. visiblePages,
  232. selectedPageType,
  233. setPageType,
  234. setProductOrCategory,
  235. };
  236. },
  237. };
  238. </script>
  239. <template>
  240. <Layout>
  241. <BRow>
  242. <div class="col-md-12">
  243. <div class="card shadow-sm border-0 rounded">
  244. <div
  245. class="card-header d-flex justify-content-between align-items-center p-3 shadow-sm rounded"
  246. dir="rtl"
  247. >
  248. <div class="d-flex align-items-center gap-2">
  249. <input
  250. v-model="searchQuery"
  251. type="text"
  252. placeholder="جستجو..."
  253. class="form-control form-control-sm"
  254. style="
  255. width: 250px;
  256. border-radius: 15px;
  257. border: 1px solid #ddd;
  258. "
  259. />
  260. <select
  261. class="form-select form-select-sm"
  262. v-model="selectedPageType"
  263. style="width: 120px; border-radius: 15px"
  264. >
  265. <option value="" disabled selected>نوع صفحه</option>
  266. <option value="">همه</option>
  267. <option value="special_page">صفحه ویژه</option>
  268. <option value="category">دسته بندی</option>
  269. <option value="brand">برند</option>
  270. </select>
  271. </div>
  272. <router-link to="/addBanner">
  273. <button class="btn btn-light text-primary btn-sm px-3">
  274. افزودن بنر
  275. </button>
  276. </router-link>
  277. </div>
  278. <div v-if="!filterLoading" class="card-body table-border-style p-0">
  279. <div class="table-responsive">
  280. <table class="table table-hover table-bordered m-0" dir="rtl">
  281. <thead class="table">
  282. <tr>
  283. <th>عکس</th>
  284. <th>عنوان</th>
  285. <th>حالت صفحه</th>
  286. <th>شناسه صفحه</th>
  287. <th>موقعیت</th>
  288. <th>نوع</th>
  289. <th>تاریخ ایجاد</th>
  290. <th>پنل</th>
  291. <th>نمایش در</th>
  292. <th>صفحه فرود</th>
  293. <th>عملیات</th>
  294. </tr>
  295. </thead>
  296. <tbody>
  297. <tr v-for="banner in banners" :key="banner.id">
  298. <td v-if="banner?.image">
  299. <img
  300. :src="banner?.image"
  301. alt="Brand Image"
  302. class="Brand-Image"
  303. />
  304. </td>
  305. <td v-if="!banner.image">ندارد</td>
  306. <td>
  307. <div
  308. type="button"
  309. data-bs-toggle="modal"
  310. data-bs-target="#showDescription"
  311. @click="descriptionModal(banner.title)"
  312. class="subject-box"
  313. aria-haspopup="dialog"
  314. aria-controls="showDescription"
  315. >
  316. <i class="fas fa-comments subject-icon"></i>
  317. <span class="subject-text">
  318. {{ banner.title?.slice(0, 20) }}
  319. {{ banner.title?.length > 20 ? "..." : "" }}
  320. </span>
  321. </div>
  322. </td>
  323. <td>
  324. {{
  325. {
  326. category: "دسته‌بندی",
  327. brand: "برند",
  328. special_page: "صفحه ویژه",
  329. main_page: "صفحه اصلی",
  330. blog_page: "صفحه بلاگ",
  331. }[banner.page_type] || banner.page_type
  332. }}
  333. </td>
  334. <td>{{ banner.page_id || "ندارد" }}</td>
  335. <td>{{ banner.location }}</td>
  336. <td v-if="banner.type === 'slider'">اسلایدر</td>
  337. <td v-if="banner.type === 'banner'">بنر</td>
  338. <td>{{ convertToJalali(banner.created_at) }}</td>
  339. <td>{{ banner?.panel === 'wholesale' ? 'عمده': 'وب و اپلیکیشن' }}</td>
  340. <td>{{ setPageType(banner) }}</td>
  341. <td>
  342. <div v-html="setProductOrCategory(banner)"></div>
  343. </td>
  344. <td>
  345. <router-link
  346. :to="`/editBanner/${banner?.id}`"
  347. class="btn btn-sm btn-outline-warning me-1"
  348. >
  349. ویرایش
  350. </router-link>
  351. <button
  352. v-if="!banner.deleted_at"
  353. @click="deleteBanner(banner.id, banner.title)"
  354. class="btn btn-sm btn-outline-danger"
  355. >
  356. حذف
  357. </button>
  358. <button
  359. v-else
  360. @click="restoreBanner(banner?.id, banner?.title)"
  361. class="btn btn-sm btn-outline-success"
  362. >
  363. بازیابی
  364. </button>
  365. </td>
  366. </tr>
  367. </tbody>
  368. </table>
  369. </div>
  370. </div>
  371. <div
  372. v-else
  373. class="filter-loader card table-card user-profile-list"
  374. ></div>
  375. </div>
  376. </div>
  377. <showDescription :desc="catDescription" />
  378. </BRow>
  379. <BRow>
  380. <BCol sm="12">
  381. <div class="d-flex justify-content-center">
  382. <nav aria-label="Page navigation">
  383. <ul class="pagination">
  384. <!-- Previous page -->
  385. <li class="page-item" :class="{ disabled: currentPage === 1 }">
  386. <span class="page-link" @click="prevPage">قبلی</span>
  387. </li>
  388. <!-- First page and leading dots -->
  389. <li
  390. v-if="visiblePages[0] > 1"
  391. class="page-item"
  392. @click="page = 1"
  393. >
  394. <a class="page-link" href="javascript:void(0)">1</a>
  395. </li>
  396. <li v-if="visiblePages[0] > 2" class="page-item disabled">
  397. <span class="page-link">...</span>
  398. </li>
  399. <!-- Visible pages -->
  400. <li
  401. v-for="n in visiblePages"
  402. :key="n"
  403. class="page-item"
  404. :class="{ active: currentPage === n }"
  405. >
  406. <a
  407. class="page-link"
  408. href="javascript:void(0)"
  409. @click="page = n"
  410. >
  411. {{ n }}
  412. </a>
  413. </li>
  414. <!-- Trailing dots and last page -->
  415. <li
  416. v-if="visiblePages[visiblePages.length - 1] < totalPages - 1"
  417. class="page-item disabled"
  418. >
  419. <span class="page-link">...</span>
  420. </li>
  421. <li
  422. v-if="visiblePages[visiblePages.length - 1] < totalPages"
  423. class="page-item"
  424. @click="page = totalPages"
  425. >
  426. <a class="page-link" href="javascript:void(0)">
  427. {{ totalPages }}
  428. </a>
  429. </li>
  430. <!-- Next page -->
  431. <li
  432. class="page-item"
  433. :class="{ disabled: currentPage === totalPages }"
  434. >
  435. <span class="page-link" @click="nextPage">بعدی</span>
  436. </li>
  437. </ul>
  438. </nav>
  439. </div>
  440. </BCol>
  441. <BCol sm="4">
  442. <div class="ms-0 search-number">
  443. <input
  444. v-model="searchPage"
  445. type="text"
  446. class="form-control"
  447. placeholder="برو به صفحه"
  448. :max="totalPages"
  449. min="1"
  450. @input="handlePageInput"
  451. />
  452. </div>
  453. </BCol>
  454. </BRow>
  455. </Layout>
  456. </template>
  457. <style scoped>
  458. .card {
  459. transition: transform 0.3s ease;
  460. }
  461. .card:hover {
  462. transform: translateY(-3px);
  463. }
  464. .table th,
  465. .table td {
  466. vertical-align: middle;
  467. text-align: center;
  468. }
  469. .filter-loader {
  470. border: 4px solid rgba(0, 123, 255, 0.3);
  471. border-top: 4px solid #007bff;
  472. border-radius: 50%;
  473. width: 40px;
  474. height: 40px;
  475. animation: spin 1s linear infinite;
  476. margin: 20px auto;
  477. }
  478. .Brand-Image {
  479. width: 100px;
  480. height: 100px;
  481. object-fit: cover;
  482. border-radius: 8px;
  483. border: 1px solid #ddd;
  484. }
  485. .subject-box {
  486. padding: 8px 14px;
  487. background: linear-gradient(135deg, #fff3e0, #ffe0b2);
  488. color: #ef6c00;
  489. font-weight: 600;
  490. border-radius: 10px;
  491. box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
  492. display: inline-flex;
  493. align-items: center;
  494. gap: 8px;
  495. transition: transform 0.2s ease, box-shadow 0.2s ease;
  496. }
  497. .subject-box i {
  498. color: #ef6c00;
  499. font-size: 1rem;
  500. }
  501. .subject-box:hover {
  502. transform: translateY(-2px);
  503. box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);
  504. }
  505. .search-number {
  506. display: flex;
  507. align-items: center;
  508. }
  509. .search-number input {
  510. width: 150px;
  511. padding: 0.5rem;
  512. font-size: 1rem;
  513. border-radius: 0.375rem;
  514. margin-bottom: 7px;
  515. border: 1px solid #ced4da;
  516. transition: border-color 0.3s ease, box-shadow 0.3s ease;
  517. }
  518. .search-number input:focus {
  519. border-color: #007bff;
  520. box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.25);
  521. }
  522. .search-number input::placeholder {
  523. color: #6c757d;
  524. }
  525. .search-number input:disabled {
  526. background-color: #f8f9fa;
  527. cursor: not-allowed;
  528. }
  529. .pagination {
  530. display: flex;
  531. flex-wrap: wrap;
  532. gap: 5px;
  533. }
  534. .page-item {
  535. flex: 0 0 auto;
  536. }
  537. .page-link {
  538. cursor: pointer;
  539. user-select: none;
  540. }
  541. .filter-input {
  542. padding: 10px 16px;
  543. font-size: 14px;
  544. border: 1px solid #ccc;
  545. border-radius: 8px;
  546. margin-right: 16px;
  547. transition: all 0.3s ease;
  548. }
  549. </style>