evtAdd.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. <template>
  2. <div>
  3. <div class="inner--headers">
  4. <h2>{{ pageId }}</h2>
  5. <div class="bread--crumbs--wrap">
  6. <span>홈</span>
  7. <span>이벤트 관리</span>
  8. <span>{{ pageId }}</span>
  9. <span v-if="pageIdSub">{{ pageIdSub }}</span>
  10. </div>
  11. </div>
  12. <div class="data--list--wrap">
  13. <div class="table--wrap">
  14. <v-form ref="addForm">
  15. <table>
  16. <colgroup>
  17. <col width="20%" />
  18. <col width="80%" />
  19. </colgroup>
  20. <tbody>
  21. <tr>
  22. <th class="bg le">
  23. 이벤트명<span v-if="!isDisabled" class="bul">*</span>
  24. </th>
  25. <td class="le" v-if="isDisabled">
  26. {{ form.formValue1 }}
  27. </td>
  28. <td v-else>
  29. <v-text-field
  30. maxlength="50"
  31. v-model="form.formValue1"
  32. style="width: 100%"
  33. :rules="[useValid.required('이벤트명')]"
  34. class="custom-input mini left"
  35. placeholder="이벤트명을 입력하세요"
  36. ></v-text-field>
  37. </td>
  38. </tr>
  39. <tr>
  40. <th class="bg le">
  41. 이벤트 기간<span v-if="!isDisabled" class="bul">*</span>
  42. </th>
  43. <td v-if="!isDisabled">
  44. <div class="calendar-wrap ml--0">
  45. <div class="calendar">
  46. <VueDatePicker
  47. :format="datePickerFormat"
  48. v-model="form.formValue2"
  49. placeholder="날짜를 선택하세요"
  50. :auto-apply="true"
  51. :readonly="isDisabled"
  52. week-start="0"
  53. ></VueDatePicker>
  54. </div>
  55. <span class="text">~</span>
  56. <div class="calendar">
  57. <VueDatePicker
  58. :format="datePickerFormat"
  59. v-model="form.formValue3"
  60. placeholder="날짜를 선택하세요"
  61. :auto-apply="true"
  62. week-start="0"
  63. :readonly="isDisabled"
  64. ></VueDatePicker>
  65. </div>
  66. <div class="btn--wrap evt--btn ml--10">
  67. <v-btn
  68. v-show="pageType == 'U' && evtStatus == '0'"
  69. class="custom-btn btn-sky"
  70. @click="fnStUpdEvt"
  71. ><i class="ico"></i>이벤트 진행</v-btn
  72. >
  73. <v-btn
  74. v-show="pageType == 'U' && evtStatus == '1'"
  75. class="custom-btn btn-red"
  76. @click="fnStUpdEvt"
  77. ><i class="ico"></i>이벤트 마감</v-btn
  78. >
  79. </div>
  80. </div>
  81. </td>
  82. <td v-else>
  83. <div class="calendar-wrap ml--0">
  84. {{ form.formValue2 }} ~ {{ form.formValue3 }}
  85. <div class="btn--wrap evt--btn ml--10">
  86. <v-btn
  87. v-show="pageType == 'U' && evtStatus == '0'"
  88. class="custom-btn btn-sky"
  89. @click="fnStUpdEvt"
  90. ><i class="ico"></i>이벤트 진행</v-btn
  91. >
  92. <v-btn
  93. v-show="pageType == 'U' && evtStatus == '1'"
  94. class="custom-btn btn-red"
  95. @click="fnStUpdEvt"
  96. ><i class="ico"></i>이벤트 마감</v-btn
  97. >
  98. </div>
  99. </div>
  100. </td>
  101. </tr>
  102. <tr v-for="(item, index) in itemsForm.items" :key="index">
  103. <th class="bg le">
  104. 아이템 등록
  105. <span v-if="itemsForm.items.length > 0"> {{ index + 1 }} </span>
  106. <span v-if="!isDisabled && index < 2" class="bul">*</span>
  107. </th>
  108. <td v-if="!isDisabled">
  109. <div class="input--wrap">
  110. <v-text-field
  111. maxlength="50"
  112. v-model="item.name"
  113. style="width: 30%"
  114. class="custom-input mini left"
  115. placeholder="아이템명을 입력하세요"
  116. :rules="[useValid.required('아이템명')]"
  117. :readonly="isDisabled"
  118. ></v-text-field>
  119. <v-text-field
  120. v-model="item.qty"
  121. placeholder="당첨 수량을 입력하세요"
  122. style="width: 20%"
  123. class="custom-input mini left"
  124. :rules="[useValid.required('당첨 수량')]"
  125. maxlength="10"
  126. @input="(e) => formatNumber(e, item, 'qty')"
  127. :readonly="isDisabled"
  128. />
  129. <v-text-field
  130. v-model="item.rate"
  131. placeholder="당첨 확률을 입력하세요"
  132. style="width: 15%"
  133. :rules="[useValid.required('당첨 확률')]"
  134. class="custom-input mini left"
  135. maxlength="7"
  136. @input="(e) => formatFloat(e, item, 'rate')"
  137. :readonly="isDisabled"
  138. />
  139. <span class="text"> % </span>
  140. <v-btn
  141. icon="mdi-minus"
  142. color="red"
  143. size="small"
  144. :disabled="itemsForm.items.length <= 2 || isDisabled"
  145. @click="removeItem(index)"
  146. ></v-btn>
  147. <v-btn
  148. icon="mdi-plus"
  149. color="primary"
  150. size="small"
  151. :disabled="itemsForm.items.length >= 15 || isDisabled"
  152. v-if="index === itemsForm.items.length - 1"
  153. @click="addItem"
  154. ></v-btn>
  155. </div>
  156. </td>
  157. <td v-else>
  158. <div class="input--wrap">
  159. {{ item.name }}
  160. / 당첨 수량 : {{ item.qty }} / 당첨 확률 : {{ item.rate }} %
  161. </div>
  162. </td>
  163. </tr>
  164. <tr>
  165. <th class="bg le">
  166. 문자 메시지<span v-if="!isDisabled" class="bul">*</span>
  167. </th>
  168. <td>
  169. <div class="btn--wrap evt--btn">
  170. <v-btn class="custom-btn btn-sky" @click="messageFormOpen()"
  171. ><i class="ico"></i>문자 메시지 입력</v-btn
  172. >
  173. <v-dialog v-model="messageForm" persistent width="50rem">
  174. <div class="v-common-dialog-wrapper custom-dialog">
  175. <div class="modal-tit">
  176. <strong>문자 메시지 입력</strong>
  177. <button class="btn-close" @click="messageForm = false"></button>
  178. </div>
  179. <div class="v-common-dialog-content">
  180. <div class="ms--pop">
  181. <div class="ms--input--wrap">
  182. <v-textarea
  183. row-height="15"
  184. v-model="form.formValue4Temp"
  185. hide-details=""
  186. rows="15"
  187. :readonly="isDisabled"
  188. variant="outlined"
  189. no-resize
  190. >
  191. </v-textarea>
  192. </div>
  193. <div class="ms--desc--wrap">
  194. <p>
  195. <strong>코드명</strong>
  196. 코드명은 자동으로 노출됩니다.
  197. <br />
  198. {이벤트명} : 등록된 이벤트명이 노출됩니다.<br />
  199. {상품명} : 등록된 상품이 노출됩니다.<br />
  200. {당첨일} : 당첨된 날짜가 노출됩니다.
  201. </p>
  202. </div>
  203. </div>
  204. </div>
  205. <div class="btn-wrap">
  206. <v-btn
  207. class="custom-btn btn-white mini"
  208. v-if="!isDisabled"
  209. @click="messageForm = false"
  210. ><i class="ico"></i>취소</v-btn
  211. >
  212. <v-btn
  213. class="custom-btn btn-blue mini"
  214. v-if="!isDisabled"
  215. @click="messageFormUpt()"
  216. ><i class="ico"></i>저장</v-btn
  217. >
  218. </div>
  219. </div>
  220. </v-dialog>
  221. </div>
  222. </td>
  223. </tr>
  224. <tr>
  225. <th class="bg le">이벤트 바로가기</th>
  226. <td>
  227. <div class="btn--wrap evt--btn" v-if="evtStatus == '1'">
  228. <v-btn class="custom-btn btn-sky" @click="evtLocationAction"
  229. ><i class="ico"></i>이벤트 바로가기</v-btn
  230. >
  231. </div>
  232. <div class="caution--text text-left" v-else>
  233. * 이벤트 진행전에 룰렛페이지 접속이 불가능합니다.
  234. </div>
  235. </td>
  236. </tr>
  237. </tbody>
  238. </table>
  239. </v-form>
  240. </div>
  241. <div class="view-btm-btn">
  242. <div class="btn-l">
  243. <v-btn class="custom-btn btn-list" @click="router.back()"
  244. ><i class="ico"></i>목록</v-btn
  245. >
  246. </div>
  247. <div class="btn-r">
  248. <v-btn v-if="pageType == 'I'" class="custom-btn btn-blue2" @click="fnRegCheck"
  249. ><i class="ico"></i>저장</v-btn
  250. >
  251. <v-btn
  252. v-else-if="pageType == 'U' && evtStatus == '0'"
  253. class="custom-btn btn-blue2"
  254. @click="fnRegCheck"
  255. ><i class="ico"></i>수정</v-btn
  256. >
  257. <v-btn
  258. v-else
  259. class="custom-btn btn-blue2"
  260. @click="router.push('/view/event/evtList')"
  261. >참여자 보기</v-btn
  262. >
  263. </div>
  264. </div>
  265. </div>
  266. </div>
  267. </template>
  268. <script setup>
  269. import VueDatePicker from "@vuepic/vue-datepicker";
  270. import "@vuepic/vue-datepicker/dist/main.css";
  271. import { reactive } from "vue";
  272. /************************************************************************
  273. | 레이아웃
  274. ************************************************************************/
  275. definePageMeta({
  276. layout: "default",
  277. });
  278. /************************************************************************
  279. | PROPS
  280. ************************************************************************/
  281. const props = defineProps({
  282. propsData: {
  283. type: Object,
  284. default: () => {},
  285. },
  286. });
  287. /************************************************************************
  288. | 스토어
  289. ************************************************************************/
  290. const useAuthStore = useAuthStore();
  291. const useDtStore = useDetailStore();
  292. /************************************************************************
  293. | 전역
  294. ************************************************************************/
  295. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
  296. const router = useRouter();
  297. const pageType = useDtStore.boardInfo.pageType;
  298. const evtStatus = useDtStore.boardInfo.status;
  299. const pageId =
  300. pageType == "U"
  301. ? evtStatus == "1"
  302. ? "진행중 이벤트"
  303. : evtStatus == "2"
  304. ? "마감된 이벤트"
  305. : "이벤트 수정"
  306. : "이벤트 등록";
  307. const isDisabled = pageType == "I" ? false : evtStatus !== "0" ? true : false;
  308. const pageIdSub = ref();
  309. const addForm = ref(null);
  310. const datePickerFormat = "yyyy-MM-dd";
  311. const messageForm = ref(false);
  312. const form = ref({
  313. formValue1: "",
  314. formValue2: "",
  315. formValue3: "",
  316. formValue4:
  317. "안녕하세요. 고객님!\n{이벤트명}\n{상품명} 당첨을 축하드립니다. \n당첨일자 : {당첨일}\n\n*이 메시지는 고객님이 참여한 이벤트 당첨으로 지급된 안내 메시지입니다.\n\n문의 : 고객센터 1234-1234\n\n감사합니다.",
  318. formValue4Temp: "",
  319. });
  320. const itemsForm = reactive({
  321. items: [
  322. {
  323. name: "",
  324. qty: "",
  325. rate: "",
  326. },
  327. {
  328. name: "",
  329. qty: "",
  330. rate: "",
  331. },
  332. ],
  333. });
  334. /* eslint-disable */
  335. /* prettier-ignore */
  336. /************************************************************************
  337. | 함수(METHODS)
  338. ************************************************************************/
  339. const addItem = () => {
  340. if (itemsForm.items.length < 15) {
  341. itemsForm.items.push({ name: '', qty: '', rate: '' })
  342. }
  343. }
  344. const removeItem = (index) => {
  345. if (itemsForm.items.length > 1) {
  346. itemsForm.items.splice(index, 1);
  347. }
  348. };
  349. const formatNumber = (event, row, field) => {
  350. let input = event.target.value.replace(/[^0-9]/g, "");
  351. let formattedInput = input ? new Intl.NumberFormat().format(input) : "";
  352. row[field] = formattedInput;
  353. };
  354. const formatFloat = (event, row, field) => {
  355. let input = event.target.value;
  356. // 숫자만 추출
  357. input = input.replace(/[^0-9]/g, "");
  358. // 입력값이 없으면 빈값 처리
  359. if (!input) {
  360. row[field] = "";
  361. return;
  362. }
  363. // 2자리까진 그대로, 3자리 이상이면 소수점 자동 삽입
  364. let result = "";
  365. if (input.length <= 2) {
  366. result = input;
  367. } else {
  368. const intPart = input.slice(0, 2);
  369. const decimalPart = input.slice(2, 5); // 소수점 이하 최대 3자리
  370. result = `${intPart}.${decimalPart}`;
  371. }
  372. // 숫자로 변환해서 100 초과면 강제로 100으로 고정
  373. const num = parseFloat(result);
  374. if (!isNaN(num) && num > 100) {
  375. result = "100";
  376. }
  377. // 최종 결과 저장
  378. row[field] = result;
  379. };
  380. const messageFormOpen = () => {
  381. messageForm.value = true;
  382. form.value.formValue4Temp = form.value.formValue4;
  383. };
  384. const messageFormUpt = () => {
  385. if (form.value.formValue4Temp) {
  386. form.value.formValue4 = form.value.formValue4Temp;
  387. messageForm.value = false;
  388. } else {
  389. let param = {
  390. id: pageId,
  391. title: "문자 메시지 입력",
  392. content: "문자 메시지를 입력해주세요.",
  393. yes: {
  394. text: "확인",
  395. isProc: false,
  396. },
  397. };
  398. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  399. }
  400. };
  401. const formatDate = (date) => {
  402. const d = new Date(date);
  403. const year = d.getFullYear();
  404. const month = ("0" + (d.getMonth() + 1)).slice(-2);
  405. const day = ("0" + d.getDate()).slice(-2);
  406. return `${year}-${month}-${day}`;
  407. };
  408. const fnRegCheck = () => {
  409. nextTick(() => {
  410. if (addForm.value && typeof addForm.value.validate === "function") {
  411. addForm.value
  412. .validate()
  413. .then((isValid) => {
  414. if (isValid.valid) {
  415. if (pageType == "I") {
  416. // 등록시 이벤트 기간만 별도로 필수 체크
  417. if (form.value.formValue2 && form.value.formValue3) {
  418. fnRegEvt();
  419. } else {
  420. let param = {
  421. id: pageId,
  422. title: "이벤트 등록",
  423. content: "이벤트 기간을 선택해주세요.",
  424. yes: {
  425. text: "확인",
  426. isProc: false,
  427. },
  428. };
  429. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  430. }
  431. } else {
  432. fnUpdEvt();
  433. }
  434. } else {
  435. if (pageType == "I") {
  436. let param = {
  437. id: pageId,
  438. title: "이벤트 등록",
  439. content: "필수항목을 입력해주세요.",
  440. yes: {
  441. text: "확인",
  442. isProc: false,
  443. },
  444. };
  445. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  446. } else {
  447. let param = {
  448. id: pageId,
  449. title: "이벤트 수정",
  450. content: "필수항목을 입력해주세요.",
  451. yes: {
  452. text: "확인",
  453. isProc: false,
  454. },
  455. };
  456. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  457. }
  458. }
  459. })
  460. .catch((err) => {
  461. console.error("벨리데이션 에러", err);
  462. });
  463. } else {
  464. console.error("항목 누락체크[fnRegCheck]]");
  465. }
  466. });
  467. };
  468. const fnRegEvt = () => {
  469. let param = {
  470. id: pageId,
  471. title: "이벤트 등록",
  472. content: "등록하시겠습니까?",
  473. yes: {
  474. text: "등록",
  475. isProc: true,
  476. event: "FN_INSERT",
  477. param: "",
  478. },
  479. no: {
  480. text: "취소",
  481. isProc: false,
  482. },
  483. };
  484. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  485. };
  486. const fnInsert = () => {
  487. let params = {
  488. compId: useAuthStore().getCompanyId,
  489. title: form.value.formValue1,
  490. startdate: form.value.formValue2,
  491. enddate: form.value.formValue3,
  492. item: itemsForm.items.map((item) => ({
  493. name: item.name,
  494. qty: item.qty.replace(/,/g, ""),
  495. rate: item.rate,
  496. })),
  497. };
  498. useAxios()
  499. .post("/evt/reg", params)
  500. .then((res) => {
  501. router.push("/view/event/evtList");
  502. })
  503. .catch((error) => {})
  504. .finally(() => {});
  505. };
  506. const fnUpdate = () => {
  507. let _req = {
  508. seq: useDtStore.boardInfo.seq,
  509. title: form.value.formValue1,
  510. startdate: form.value.formValue2,
  511. enddate: form.value.formValue3,
  512. item: itemsForm.items.map((item) => ({
  513. name: item.name,
  514. qty: item.qty.replace(/,/g, ""),
  515. rate: item.rate,
  516. })),
  517. };
  518. useAxios()
  519. .post("/evt/update", _req)
  520. .then((res) => {
  521. router.push({
  522. path: "/view/event/evtList",
  523. });
  524. })
  525. .catch((error) => {});
  526. };
  527. const fnDetail = () => {
  528. let _req = {
  529. seq: useDtStore.boardInfo.seq,
  530. };
  531. useAxios()
  532. .get(`evt/detail/${_req.seq}`)
  533. .then((res) => {
  534. form.value.formValue1 = res.data.title;
  535. form.value.formValue2 = new Date(res.data.startdate);
  536. form.value.formValue3 = new Date(res.data.enddate);
  537. //form.value.formValue4 =
  538. itemsForm.items.splice(
  539. 0,
  540. itemsForm.items.length,
  541. ...res.data.items.map((item) => ({
  542. name: item.name || "",
  543. qty: item.qty ? new Intl.NumberFormat().format(item.qty) : "",
  544. rate: item.rate || "",
  545. }))
  546. );
  547. })
  548. .catch((error) => {});
  549. };
  550. const fnStUpdate = () => {
  551. let _req = {
  552. seq: useDtStore.boardInfo.seq,
  553. };
  554. useAxios()
  555. .post(`/evt/stupdate/${_req.seq}`)
  556. .then((res) => {
  557. router.push({
  558. path: "/view/event/evtList",
  559. });
  560. })
  561. .catch((error) => {});
  562. };
  563. const fnUpdEvt = () => {
  564. let param = {
  565. id: pageId,
  566. title: "이벤트 수정",
  567. content: "수정하시겠습니까?",
  568. yes: {
  569. text: "확인",
  570. isProc: true,
  571. event: "FN_UPDATE",
  572. param: "",
  573. },
  574. no: {
  575. text: "취소",
  576. isProc: false,
  577. },
  578. };
  579. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  580. };
  581. const fnStUpdEvt = () => {
  582. if (evtStatus == 0) {
  583. let param = {
  584. id: pageId,
  585. title: "이벤트 진행",
  586. content: "이벤트를 진행하시겠습니까?",
  587. yes: {
  588. text: "확인",
  589. isProc: true,
  590. event: "FN_STUPDATE",
  591. param: "",
  592. },
  593. no: {
  594. text: "취소",
  595. isProc: false,
  596. },
  597. };
  598. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  599. } else if (evtStatus == 1) {
  600. let param = {
  601. id: pageId,
  602. title: "이벤트 마감",
  603. content: "이벤트를 마감하시겠습니까?",
  604. yes: {
  605. text: "확인",
  606. isProc: true,
  607. event: "FN_STUPDATE",
  608. param: "",
  609. },
  610. no: {
  611. text: "취소",
  612. isProc: false,
  613. },
  614. };
  615. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  616. }
  617. };
  618. const evtLocationAction = () => {
  619. window.open("/view/roulette", "_blank");
  620. };
  621. /************************************************************************
  622. | 팝업 이벤트버스 정의
  623. ************************************************************************/
  624. $eventBus.off("FN_STUPDATE");
  625. $eventBus.on("FN_STUPDATE", () => {
  626. fnStUpdate();
  627. });
  628. $eventBus.off("FN_UPDATE");
  629. $eventBus.on("FN_UPDATE", () => {
  630. fnUpdate();
  631. });
  632. $eventBus.off("FN_INSERT");
  633. $eventBus.on("FN_INSERT", () => {
  634. fnInsert();
  635. });
  636. /************************************************************************
  637. | WATCH
  638. ************************************************************************/
  639. watch(
  640. [() => form.value.formValue2, () => form.value.formValue3],
  641. ([newVal2, newVal3]) => {
  642. if (newVal2) {
  643. form.value.formValue2 = formatDate(newVal2);
  644. }
  645. if (newVal3) {
  646. form.value.formValue3 = formatDate(newVal3);
  647. }
  648. }
  649. );
  650. onMounted(() => {
  651. if (pageType == "U") fnDetail();
  652. });
  653. </script>