選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 

465 行
13 KiB

  1. <script>
  2. import Layout from "@/layout/custom.vue";
  3. import ApiServiece from "@/services/ApiService";
  4. import { onMounted, ref, watch, computed } from "vue";
  5. import DatePicker from "vue3-persian-datetime-picker";
  6. import VueSelect from "vue3-select-component";
  7. import moment from "jalali-moment";
  8. export default {
  9. name: "PRODUCT-LIST",
  10. components: {
  11. Layout,
  12. VueSelect,
  13. DatePicker,
  14. },
  15. setup() {
  16. const isLoading = ref(false);
  17. const date = ref([]);
  18. const brands = ref([]);
  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 filterLoading = ref(false);
  25. const selectedBrand = ref();
  26. const allProducts = ref([]);
  27. const getAllProducts = () => {
  28. filterLoading.value = true;
  29. ApiServiece.get(
  30. `admin/orders/order-items/approved?brand_id=${
  31. selectedBrand.value || ""
  32. }&start_date=${date.value[0] || ""}&end_date=${
  33. date.value[1] || ""
  34. }&paginate=${paginate.value || 10}&page=${page.value || 1}`
  35. )
  36. .then((resp) => {
  37. console.log(resp);
  38. allProducts.value = resp.data.data.data;
  39. currentPage.value = resp.data.data.current_page;
  40. totalPages.value = resp.data.data.last_page;
  41. })
  42. .catch((err) => {
  43. console.log(err);
  44. });
  45. };
  46. const getAllBrands = () => {
  47. ApiServiece.get("admin/brands")
  48. .then((resp) => {
  49. console.log(resp);
  50. brands.value = resp.data.data;
  51. })
  52. .catch((err) => {
  53. console.log(err);
  54. });
  55. };
  56. const convertToJalali = (date) => {
  57. return moment(date, "YYYY-MM-DD HH:mm:ss")
  58. .locale("fa")
  59. .format("YYYY/MM/DD");
  60. };
  61. const formattedBrands = computed(() => {
  62. return brands.value.map((brand) => ({
  63. value: brand?.id,
  64. label: brand?.title,
  65. }));
  66. });
  67. const getFile = () => {
  68. isLoading.value = true;
  69. ApiServiece.post(
  70. "admin/orders/order-items/approved/export",
  71. {},
  72. { responseType: "blob" }
  73. )
  74. .then((resp) => {
  75. const excelBlob = resp.data;
  76. const url = window.URL.createObjectURL(excelBlob);
  77. const link = document.createElement("a");
  78. link.href = url;
  79. link.setAttribute("download", "order-items-export.xlsx");
  80. document.body.appendChild(link);
  81. link.click();
  82. window.URL.revokeObjectURL(url);
  83. document.body.removeChild(link);
  84. isLoading.value = false;
  85. })
  86. .catch((err) => {
  87. console.log(err);
  88. isLoading.value = false;
  89. });
  90. };
  91. watch(selectedBrand, () => {
  92. getAllProducts();
  93. page.value = 1;
  94. });
  95. const visiblePages = computed(() => {
  96. const pages = [];
  97. if (totalPages.value <= 5) {
  98. for (let i = 1; i <= totalPages.value; i++) {
  99. pages.push(i);
  100. }
  101. } else {
  102. let start = currentPage.value - 2;
  103. let end = currentPage.value + 2;
  104. if (start < 1) start = 1;
  105. if (end > totalPages.value) end = totalPages.value;
  106. for (let i = start; i <= end; i++) {
  107. pages.push(i);
  108. }
  109. }
  110. return pages;
  111. });
  112. function handlePageInput() {
  113. if (searchPage.value < 1) {
  114. searchPage.value = 1;
  115. } else if (searchPage.value > totalPages.value) {
  116. searchPage.value = totalPages.value;
  117. }
  118. if (searchPage.value >= 1 && searchPage.value <= totalPages.value) {
  119. page.value = searchPage.value;
  120. }
  121. }
  122. watch(selectedBrand, () => {
  123. getAllProducts();
  124. });
  125. watch(date, () => {
  126. getAllProducts();
  127. });
  128. watch(page, () => {
  129. getAllProducts();
  130. });
  131. const nextPage = () => {
  132. if (currentPage.value < totalPages.value) {
  133. page.value++;
  134. getAllProducts();
  135. }
  136. };
  137. const prevPage = () => {
  138. if (currentPage.value > 1) {
  139. page.value--;
  140. getAllProducts();
  141. }
  142. };
  143. function formatWithCommas(number) {
  144. return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  145. }
  146. onMounted(() => {
  147. getAllProducts();
  148. getAllBrands();
  149. });
  150. return {
  151. allProducts,
  152. visiblePages,
  153. nextPage,
  154. prevPage,
  155. handlePageInput,
  156. currentPage,
  157. totalPages,
  158. page,
  159. getFile,
  160. convertToJalali,
  161. searchPage,
  162. brands,
  163. formattedBrands,
  164. selectedBrand,
  165. date,
  166. isLoading,
  167. formatWithCommas,
  168. };
  169. },
  170. };
  171. </script>
  172. <template>
  173. <Layout>
  174. <BRow>
  175. <BCol class="col-sm-12">
  176. <BCard no-body class="table-card">
  177. <BCardBody>
  178. <div class="text-end p-sm-4 pb-sm-2">
  179. <!-- Button to Trigger Export -->
  180. <BRow>
  181. <BCol sm="3" class="mt-3">
  182. <VueSelect
  183. style="--vs-border-radius: 8px"
  184. v-model="selectedBrand"
  185. :options="formattedBrands"
  186. placeholder="انتخاب برند"
  187. />
  188. </BCol>
  189. <BCol style="margin-right: 180px" class="mt-3" sm="3">
  190. <div class="form-group">
  191. <DatePicker
  192. format="YYYY/MM/DD HH:mm:ss"
  193. type="date"
  194. :range="true"
  195. v-model="date"
  196. @input="handleInput"
  197. ></DatePicker>
  198. </div>
  199. </BCol>
  200. <BCol sm="4" class="mt-3">
  201. <button
  202. @click="getFile"
  203. type="button"
  204. class="btn btn-primary"
  205. :disabled="isLoading"
  206. >
  207. <span
  208. v-if="isLoading"
  209. class="spinner-border spinner-border-sm"
  210. role="status"
  211. aria-hidden="true"
  212. ></span>
  213. <span v-else>گرفتن خروجی</span>
  214. </button>
  215. </BCol>
  216. </BRow>
  217. <!-- Product Selection Section -->
  218. </div>
  219. <div class="table-responsive">
  220. <table class="table table-hover tbl-product" id="pc-dt-simple">
  221. <thead>
  222. <tr>
  223. <th class="text-end">#</th>
  224. <th>جزییات محصول</th>
  225. <th>تاریخ ایحاد</th>
  226. <th class="text-end">قیمت عمده</th>
  227. <th class="text-end">قیمت تک</th>
  228. <th class="text-end">تعداد سفارش</th>
  229. <th class="text-end">تعداد ارسال شده</th>
  230. <th class="text-center">عنوان برند</th>
  231. <th class="text-center">تصویر برند</th>
  232. </tr>
  233. </thead>
  234. <tbody>
  235. <tr v-for="product in allProducts" :key="product?.id">
  236. <td class="text-end">5</td>
  237. <td>
  238. <BRow>
  239. <BCol class="col-auto pe-5">
  240. <img
  241. :src="product?.product?.image"
  242. alt="user-image"
  243. class="wid-40 rounded product-img"
  244. />
  245. </BCol>
  246. <BCol>
  247. <h6 class="mb-1">{{ product?.product?.title }}</h6>
  248. <p class="text-muted f-12 mb-0">
  249. {{ product.product.description.slice(0, 25)
  250. }}{{
  251. product.product.description.length > 25
  252. ? "..."
  253. : ""
  254. }}
  255. </p>
  256. </BCol>
  257. </BRow>
  258. </td>
  259. <td>{{ convertToJalali(product.created_at) }}</td>
  260. <td
  261. v-if="product?.product?.wholesale_price"
  262. class="text-end"
  263. >
  264. {{
  265. formatWithCommas(product?.product?.wholesale_price)
  266. }}تومان
  267. </td>
  268. <td
  269. v-if="!product?.product?.wholesale_price"
  270. class="text-end"
  271. >
  272. <i
  273. class="ph-duotone ph-x-circle text-danger f-24"
  274. data-bs-toggle="tooltip"
  275. data-bs-title="danger"
  276. ></i>
  277. </td>
  278. <td v-if="product?.product?.retail_price" class="text-end">
  279. {{ formatWithCommas(product?.product?.retail_price) }}
  280. </td>
  281. <td v-if="!product?.product?.retail_price" class="text-end">
  282. <i
  283. class="ph-duotone ph-x-circle text-danger f-24"
  284. data-bs-toggle="tooltip"
  285. data-bs-title="danger"
  286. ></i>
  287. </td>
  288. <td class="text-end">{{ product.count }}</td>
  289. <td class="text-end">{{ product.send_count }}</td>
  290. <td class="text-center">
  291. {{ product.product?.brand?.title }}
  292. </td>
  293. <td class="text-center">
  294. <img
  295. :src="product.product?.brand?.image"
  296. alt="user-image"
  297. class="wid-40 brand-img"
  298. />
  299. </td>
  300. </tr>
  301. </tbody>
  302. </table>
  303. </div>
  304. </BCardBody>
  305. </BCard>
  306. </BCol>
  307. </BRow>
  308. <BRow>
  309. <BCol sm="12">
  310. <div class="d-flex justify-content-center">
  311. <nav aria-label="Page navigation">
  312. <ul class="pagination">
  313. <li class="page-item" :class="{ disabled: currentPage === 1 }">
  314. <span class="page-link" @click="prevPage">قبلی</span>
  315. </li>
  316. <li v-if="currentPage > 2" class="page-item" @click="page = 1">
  317. <a class="page-link" href="javascript:void(0)">1</a>
  318. </li>
  319. <li v-if="currentPage > 3" class="page-item" disabled>
  320. <span class="page-link">...</span>
  321. </li>
  322. <li
  323. v-for="n in visiblePages"
  324. :key="n"
  325. class="page-item"
  326. :class="{ active: currentPage === n }"
  327. >
  328. <a
  329. class="page-link"
  330. href="javascript:void(0)"
  331. @click="page = n"
  332. >
  333. {{ n }}
  334. </a>
  335. </li>
  336. <li
  337. v-if="currentPage < totalPages - 2"
  338. class="page-item"
  339. disabled
  340. >
  341. <span class="page-link">...</span>
  342. </li>
  343. <li
  344. v-if="currentPage < totalPages - 1"
  345. class="page-item"
  346. @click="page = totalPages"
  347. >
  348. <a class="page-link" href="javascript:void(0)">{{
  349. totalPages
  350. }}</a>
  351. </li>
  352. <li
  353. class="page-item"
  354. :class="{ disabled: currentPage === totalPages }"
  355. >
  356. <span class="page-link" @click="nextPage">بعدی</span>
  357. </li>
  358. </ul>
  359. </nav>
  360. </div>
  361. </BCol>
  362. <BCol sm="4">
  363. <div class="ms-0 search-number">
  364. <input
  365. v-model="searchPage"
  366. type="text"
  367. class="form-control"
  368. placeholder="برو به صفحه"
  369. :max="totalPages"
  370. min="1"
  371. @input="handlePageInput"
  372. />
  373. </div>
  374. </BCol>
  375. </BRow>
  376. </Layout>
  377. </template>
  378. <style scoped>
  379. .product-img {
  380. max-width: 48px;
  381. min-width: 48px;
  382. max-height: 45px;
  383. min-height: 45px;
  384. object-fit: cover;
  385. }
  386. .brand-img {
  387. max-width: 43px;
  388. min-width: 43px;
  389. max-height: 40px;
  390. min-height: 40px;
  391. object-fit: cover;
  392. }
  393. .search-number {
  394. display: flex;
  395. align-items: center;
  396. }
  397. .search-number input {
  398. width: 150px;
  399. padding: 0.5rem;
  400. font-size: 1rem;
  401. border-radius: 0.375rem;
  402. margin-bottom: 7px;
  403. border: 1px solid #ced4da;
  404. transition: border-color 0.3s ease, box-shadow 0.3s ease;
  405. }
  406. .search-number input:focus {
  407. border-color: #007bff;
  408. box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.25);
  409. }
  410. .search-number input::placeholder {
  411. color: #6c757d;
  412. }
  413. .search-number input:disabled {
  414. background-color: #f8f9fa;
  415. cursor: not-allowed;
  416. }
  417. .pagination {
  418. display: flex;
  419. flex-wrap: wrap;
  420. gap: 5px;
  421. }
  422. .page-item {
  423. flex: 0 0 auto;
  424. }
  425. .page-link {
  426. cursor: pointer;
  427. user-select: none;
  428. }
  429. </style>