Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

449 lignes
12 KiB

  1. <template>
  2. <div
  3. class="modal fade"
  4. id="addBrand"
  5. tabindex="-1"
  6. role="dialog"
  7. aria-labelledby="exampleModalLabel"
  8. aria-hidden="true"
  9. >
  10. <div class="modal-dialog modal-sm" role="document">
  11. <div class="modal-content">
  12. <div class="modal-header">
  13. <h5 class="modal-title" id="exampleModalLabel">
  14. اضافه کردن برند جدید
  15. </h5>
  16. <button
  17. type="button"
  18. class="btn-close"
  19. data-bs-dismiss="modal"
  20. aria-label="Close"
  21. @click="step = 1"
  22. ></button>
  23. </div>
  24. <div class="modal-body">
  25. <Steppy
  26. v-model:step="step"
  27. :tabs="[
  28. { title: 'ثبت دسته بندی', isValid: true },
  29. { title: 'ترجمه ها', isValid: true },
  30. ]"
  31. backText="قبلی"
  32. nextText="بعدی"
  33. doneText="ذخیره"
  34. primaryColor1="#04A9F5"
  35. circleSize="45"
  36. >
  37. <template #1>
  38. <!-- Brand Image Upload -->
  39. <div class="form-group">
  40. <label class="form-label">تصویر برند</label>
  41. <input
  42. type="file"
  43. accept="image/*"
  44. @change="handleImageChange"
  45. class="form-control"
  46. :class="{ 'is-invalid': errors.image }"
  47. />
  48. <div v-if="imagePreview" class="mt-2">
  49. <img
  50. :src="imagePreview"
  51. alt="Image Preview"
  52. class="img-fluid rounded shadow-sm Image-Preview"
  53. />
  54. <button
  55. type="button"
  56. @click="removeImage()"
  57. class="delete-btn"
  58. >
  59. <i class="fa fa-trash f-16"></i>
  60. </button>
  61. </div>
  62. <small v-if="errors.image" class="text-danger">
  63. {{ errors.image }}
  64. </small>
  65. </div>
  66. <div
  67. class="d-flex justify-content-end gap-2"
  68. style="margin-top: 20px"
  69. >
  70. <button :disabled="loadingStep" @click="handlerAddBrand" class="btn btn-primary">
  71. <span
  72. v-if="loadingStep"
  73. class="spinner-border spinner-border-sm"
  74. role="status"
  75. aria-hidden="true"
  76. />
  77. ذخیره
  78. </button>
  79. </div>
  80. </template>
  81. <template #2>
  82. <form @submit.prevent="addBrand">
  83. <BRow class="g-3">
  84. <!-- Brand Title -->
  85. <BCol lg="6">
  86. <div class="form-group">
  87. <label class="form-label">عنوان برند</label>
  88. <input
  89. v-model="title"
  90. @input="clearError('title')"
  91. type="text"
  92. class="form-control"
  93. placeholder="عنوان برند"
  94. :class="{ 'is-invalid': errors.title }"
  95. />
  96. <small v-if="errors.title" class="text-danger">
  97. {{ errors.title }}
  98. </small>
  99. </div>
  100. </BCol>
  101. <BCol lg="6">
  102. <div class="form-group">
  103. <label class="form-label">انتخاب زبان</label>
  104. <select
  105. v-model="locale"
  106. class="form-control"
  107. placeholder="انتخاب کنید"
  108. @change="handlerChangeLocale"
  109. >
  110. <option
  111. key="fa"
  112. value="fa"
  113. >
  114. فارسی
  115. </option>
  116. <option
  117. key="en"
  118. value="en"
  119. >
  120. انگلیسی
  121. </option>
  122. <option
  123. key="ar"
  124. value="ar"
  125. >
  126. عربی
  127. </option>
  128. </select>
  129. </div>
  130. </BCol>
  131. <!-- Brand Description -->
  132. <BCol lg="12">
  133. <div class="form-group">
  134. <label class="form-label">توضیحات برند</label>
  135. <textarea
  136. v-model="description"
  137. @input="clearError('description')"
  138. type="text"
  139. class="form-control"
  140. placeholder="توضیحات برند"
  141. :class="{ 'is-invalid': errors.description }"
  142. ></textarea>
  143. <small v-if="errors.description" class="text-danger">
  144. {{ errors.description }}
  145. </small>
  146. </div>
  147. </BCol>
  148. </BRow>
  149. <!-- Submit Buttons -->
  150. <div
  151. class="d-flex justify-content-end gap-2"
  152. style="margin-top: 20px"
  153. >
  154. <button
  155. type="button"
  156. class="btn btn-secondary"
  157. data-bs-dismiss="modal"
  158. id="close"
  159. @click="step = 1"
  160. >
  161. بستن
  162. </button>
  163. <button type="submit" class="btn btn-primary" :disabled="loading">
  164. <span
  165. v-if="loading"
  166. class="spinner-border spinner-border-sm"
  167. role="status"
  168. aria-hidden="true"
  169. ></span>
  170. ذخیره
  171. </button>
  172. </div>
  173. </form>
  174. </template>
  175. </Steppy>
  176. </div>
  177. </div>
  178. </div>
  179. </div>
  180. </template>
  181. <script>
  182. import { ref } from "vue";
  183. import { toast } from "vue3-toastify";
  184. import "vue3-toastify/dist/index.css";
  185. import ApiServiece from "@/services/ApiService";
  186. import {Steppy} from "vue3-steppy";
  187. export default {
  188. components: {Steppy},
  189. setup(props, { emit }) {
  190. const image = ref(null);
  191. const imagePreview = ref(null);
  192. const description = ref();
  193. const title = ref();
  194. const loadingStep = ref(false);
  195. const step = ref(1);
  196. const brandId = ref(null);
  197. const locale = ref('fa');
  198. const errors = ref({});
  199. const loading = ref(false);
  200. const removeImage = () => {
  201. image.value = null;
  202. imagePreview.value = null;
  203. const fileInput = document.querySelector('input[type="file"]');
  204. if (fileInput) {
  205. fileInput.value = "";
  206. }
  207. };
  208. const handleImageChange = (event) => {
  209. const file = event.target.files[0];
  210. if (file) {
  211. if (!file.type.startsWith("image/")) {
  212. errors.value.image = "لطفا یک فایل تصویری انتخاب کنید";
  213. imagePreview.value = null;
  214. return;
  215. }
  216. if (file.size > 5 * 1024 * 1024) {
  217. errors.value.image = "حجم تصویر باید کمتر از 5 مگابایت باشد";
  218. imagePreview.value = null;
  219. return;
  220. }
  221. errors.value.image = null;
  222. image.value = file;
  223. const reader = new FileReader();
  224. reader.onload = () => {
  225. imagePreview.value = reader.result;
  226. };
  227. reader.readAsDataURL(file);
  228. }
  229. };
  230. const validateForm = () => {
  231. errors.value = {};
  232. if (!description.value)
  233. errors.value.description = "وارد کردن توضیحات ضروری می باشد";
  234. if (!title.value) errors.value.title = "وارد کردن عنوان ضروری می باشد";
  235. if (!image.value) errors.value.image = "یک عکس انتخاب نمایید";
  236. return Object.keys(errors.value).length === 0;
  237. };
  238. const clearError = (field) => {
  239. errors.value[field] = "";
  240. };
  241. const addBrand = () => {
  242. if (!validateForm()) {
  243. toast.error("لطفا فیلد های لازم را وارد نمایید", {
  244. position: "top-right",
  245. autoClose: 1000,
  246. });
  247. return;
  248. }
  249. loading.value = true;
  250. const params = {
  251. title: title.value,
  252. description: description.value,
  253. locale: locale.value,
  254. }
  255. ApiServiece.post(`admin/brands/${brandId.value}/translations`, params, {
  256. headers: {
  257. Authorization: `Bearer ${localStorage.getItem("token")}`,
  258. },
  259. })
  260. .then((resp) => {
  261. console.log(resp);
  262. toast.success("!برند با موفقیت اضافه شد", {
  263. position: "top-right",
  264. autoClose: 1000,
  265. });
  266. })
  267. .then(() => {
  268. setTimeout(() => {
  269. emit("brand-updated");
  270. title.value = "";
  271. description.value = "";
  272. imagePreview.value = null;
  273. }, 500);
  274. })
  275. .catch((error) => {
  276. toast.error(`${error.response.data.message}`, {
  277. position: "top-right",
  278. autoClose: 1000,
  279. });
  280. })
  281. .finally(() => {
  282. loading.value = false;
  283. });
  284. };
  285. const handlerAddBrand = async () => {
  286. if (!image.value) {
  287. toast.error('تصویر برند نمی تواند خالی باشد.')
  288. return
  289. }
  290. try {
  291. loadingStep.value = true;
  292. const formData = new FormData();
  293. formData.append("image", image.value);
  294. const { data: { message, success, data } } = await ApiServiece.post(
  295. `admin/brands`,
  296. formData,
  297. {
  298. headers: {
  299. "content-type": "multipart",
  300. Authorization: `Bearer ${localStorage.getItem("token")}`,
  301. },
  302. })
  303. if (success) {
  304. toast.success(message)
  305. brandId.value = data?.id
  306. step.value++
  307. }
  308. } catch (e) {
  309. toast.error(e?.response?.data?.message)
  310. } finally {
  311. loadingStep.value = false;
  312. }
  313. }
  314. return {
  315. errors,
  316. loading,
  317. clearError,
  318. addBrand,
  319. handleImageChange,
  320. image,
  321. imagePreview,
  322. removeImage,
  323. title,
  324. description,
  325. loadingStep,
  326. step,
  327. handlerAddBrand,
  328. brandId,
  329. locale
  330. };
  331. },
  332. };
  333. </script>
  334. <style scoped>
  335. .modal-dialog {
  336. max-width: 50%;
  337. }
  338. .modal-content {
  339. padding: 20px;
  340. }
  341. .modal-body {
  342. padding: 20px;
  343. padding: 1rem 1.5rem;
  344. }
  345. .form-group {
  346. margin-bottom: 1rem;
  347. }
  348. .input-group {
  349. margin-top: 0.5rem;
  350. }
  351. .modal-dialog {
  352. max-width: 50%;
  353. }
  354. .modal-content {
  355. padding: 1.5rem;
  356. }
  357. .modal-header {
  358. border-bottom: 1px solid #dee2e6;
  359. padding-bottom: 1rem;
  360. }
  361. .form-group {
  362. margin-bottom: 1.5rem;
  363. }
  364. .input-group {
  365. margin-top: 0.5rem;
  366. }
  367. .Image-Preview {
  368. min-width: 100px;
  369. max-height: 100px;
  370. max-width: 100px;
  371. object-fit: cover;
  372. border-radius: 8px;
  373. border: 1px solid #ddd;
  374. }
  375. .delete-btn {
  376. display: flex;
  377. align-items: center;
  378. padding: 3px;
  379. font-size: 10px;
  380. background-color: #e74c3c;
  381. color: white;
  382. border: none;
  383. border-radius: 5px;
  384. cursor: pointer;
  385. transition: background-color 0.3s ease, transform 0.2s ease;
  386. gap: 8px; /* Space between icon and text */
  387. margin-right: 100px;
  388. }
  389. .delete-btn:hover {
  390. background-color: #c0392b;
  391. transform: scale(1.05);
  392. }
  393. .delete-btn:active {
  394. background-color: #a93226;
  395. }
  396. .delete-btn:focus {
  397. outline: none;
  398. }
  399. </style>