| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709 |
- <template>
- <div>
- <div class="inner--headers">
- <h2>벤더사 검색</h2>
- <div class="bread--crumbs--wrap">
- <span>홈</span>
- <span>벤더사 검색</span>
- </div>
- </div>
- <!-- 검색 및 필터 영역 -->
- <div class="search--modules type2">
- <div class="search--inner">
- <div class="form--cont--filter">
- <v-select
- v-model="searchFilter.category"
- :items="categoryOptions"
- variant="outlined"
- class="custom-select"
- label="카테고리"
- clearable
- >
- </v-select>
- </div>
- <div class="form--cont--filter">
- <v-select
- v-model="searchFilter.region"
- :items="regionOptions"
- variant="outlined"
- class="custom-select"
- label="지역"
- clearable
- >
- </v-select>
- </div>
- <div class="form--cont--text">
- <v-text-field
- v-model="searchFilter.keyword"
- class="custom-input mini"
- style="width: 100%"
- placeholder="벤더사명을 입력하세요"
- @keyup.enter="handleSearch"
- ></v-text-field>
- </div>
- </div>
- <v-btn
- class="custom-btn btn-blue mini sch--btn"
- @click="handleSearch"
- :loading="loading"
- >
- <v-icon>mdi-magnify</v-icon>
- 검색
- </v-btn>
- </div>
- <!-- 파트너십 상태 탭 -->
- <div class="partnership--tabs">
- <v-tabs v-model="activeTab" class="custom-tabs">
- <v-tab value="new">신규 벤더사</v-tab>
- <v-tab value="current">현재 파트너십</v-tab>
- <v-tab value="terminated">해지된 파트너십</v-tab>
- </v-tabs>
- </div>
- <!-- 검색 결과 -->
- <div class="vendor--grid">
- <div class="vendors--list">
- <!-- 로딩 상태 -->
- <div v-if="loading" class="loading-wrap">
- <v-progress-circular
- indeterminate
- color="primary"
- size="64"
- ></v-progress-circular>
- <p>검색 중...</p>
- </div>
- <!-- 검색 결과 없음 -->
- <div v-else-if="vendors.length === 0" class="no-results">
- <div class="no-data">
- <v-icon size="64" color="grey-lighten-1">mdi-office-building-outline</v-icon>
- <h3>검색 결과가 없습니다</h3>
- <p>다른 키워드로 검색해보세요</p>
- </div>
- </div>
- <!-- 벤더사 카드 리스트 -->
- <div v-else class="vendor--cards">
- <div
- v-for="vendor in vendors"
- :key="vendor.SEQ"
- class="vendor--card"
- :class="{ 'partnership-exists': vendor.PARTNERSHIP_STATUS }"
- >
- <!-- 벤더사 로고 -->
- <div class="vendor--logo">
- <v-img
- v-if="vendor.LOGO"
- :src="vendor.LOGO"
- :alt="vendor.COMPANY_NAME"
- width="80"
- height="80"
- cover
- ></v-img>
- <div v-else class="no-logo">
- {{ vendor.COMPANY_NAME?.charAt(0) || "V" }}
- </div>
- </div>
- <!-- 벤더사 정보 -->
- <div class="vendor--info">
- <h3 class="vendor--name">{{ vendor.COMPANY_NAME }}</h3>
- <div class="vendor--meta">
- <div v-if="vendor.CATEGORY" class="meta--item">
- <v-icon size="16">mdi-tag-outline</v-icon>
- <span>{{ getCategoryText(vendor.CATEGORY) }}</span>
- </div>
- <div v-if="vendor.REGION" class="meta--item">
- <v-icon size="16">mdi-map-marker-outline</v-icon>
- <span>{{ vendor.REGION }}</span>
- </div>
- <div class="meta--item">
- <v-icon size="16">mdi-handshake-outline</v-icon>
- <span>{{ vendor.PARTNERSHIP_COUNT || 0 }}개 파트너십</span>
- </div>
- </div>
- <p v-if="vendor.DESCRIPTION" class="vendor--description">
- {{ vendor.DESCRIPTION }}
- </p>
- <!-- 파트너십 상태 -->
- <div v-if="vendor.PARTNERSHIP_STATUS" class="partnership--status">
- <v-chip
- :color="getPartnershipColor(vendor.PARTNERSHIP_STATUS)"
- size="small"
- variant="tonal"
- >
- {{ getPartnershipText(vendor.PARTNERSHIP_STATUS) }}
- </v-chip>
- </div>
- </div>
- <!-- 액션 버튼 -->
- <div class="vendor--actions">
- <!-- 신규 벤더사 - 승인요청 -->
- <v-btn
- v-if="!vendor.PARTNERSHIP_STATUS"
- color="primary"
- variant="flat"
- size="small"
- @click="requestPartnership(vendor)"
- :loading="processing"
- >
- <v-icon left size="16">mdi-handshake</v-icon>
- 승인요청
- </v-btn>
- <!-- 해지된 파트너십 - 재승인요청 -->
- <v-btn
- v-else-if="vendor.PARTNERSHIP_STATUS === 'TERMINATED'"
- color="success"
- variant="flat"
- size="small"
- @click="requestReapply(vendor)"
- :loading="processing"
- >
- <v-icon left size="16">mdi-refresh</v-icon>
- 재승인요청
- </v-btn>
- <!-- 진행중인 파트너십 -->
- <v-btn
- v-else
- variant="outlined"
- size="small"
- @click="viewPartnership(vendor)"
- >
- 파트너십 보기
- </v-btn>
- <!-- 상세보기 버튼 -->
- <v-btn variant="text" size="small" @click="viewVendorDetail(vendor.SEQ)">
- 상세보기
- </v-btn>
- </div>
- </div>
- </div>
- </div>
- <!-- 페이지네이션 -->
- <div v-if="pagination.totalPages > 1" class="pagination-wrap">
- <v-pagination
- v-model="currentPage"
- :length="pagination.totalPages"
- :total-visible="5"
- @update:model-value="handlePageChange"
- ></v-pagination>
- </div>
- </div>
- <!-- 승인요청 모달 -->
- <v-dialog v-model="requestModal.show" max-width="600px" persistent>
- <v-card>
- <v-card-title class="d-flex align-center">
- <v-icon class="mr-3" color="primary">mdi-handshake</v-icon>
- {{ requestModal.isReapply ? "재승인요청" : "파트너십 승인요청" }}
- </v-card-title>
- <v-card-text>
- <div class="request--content">
- <div class="vendor--summary">
- <h4>{{ requestModal.vendor?.COMPANY_NAME }}</h4>
- <p>
- {{ getCategoryText(requestModal.vendor?.CATEGORY) }} ·
- {{ requestModal.vendor?.REGION }}
- </p>
- </div>
- <v-divider class="my-4"></v-divider>
- <v-textarea
- v-model="requestModal.message"
- label="요청 메시지"
- placeholder="파트너십을 원하는 이유나 제안사항을 입력해주세요"
- rows="4"
- variant="outlined"
- class="mb-4"
- ></v-textarea>
- <div class="form-row">
- <v-text-field
- v-model="requestModal.commissionRate"
- label="희망 수수료율 (%)"
- type="number"
- variant="outlined"
- class="mr-2"
- :disabled="requestModal.isReapply"
- ></v-text-field>
- <v-text-field
- v-model="requestModal.specialConditions"
- label="특별 조건"
- variant="outlined"
- :disabled="requestModal.isReapply"
- ></v-text-field>
- </div>
- <div v-if="requestModal.isReapply" class="reapply--info">
- <v-alert type="info" variant="tonal" class="mb-3">
- 재승인요청 시 이전 계약 조건이 자동으로 적용됩니다.
- </v-alert>
- </div>
- </div>
- </v-card-text>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn variant="text" @click="closeRequestModal">취소</v-btn>
- <v-btn
- color="primary"
- variant="flat"
- @click="submitRequest"
- :loading="processing"
- :disabled="!requestModal.message.trim()"
- >
- {{ requestModal.isReapply ? "재승인요청" : "승인요청" }}
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted } from "vue";
- definePageMeta({
- layout: "default",
- });
- const { $toast } = useNuxtApp();
- const authStore = useAuthStore();
- // 반응형 데이터
- const loading = ref(false);
- const processing = ref(false);
- const vendors = ref([]);
- const currentPage = ref(1);
- const activeTab = ref("new");
- const searchFilter = ref({
- keyword: "",
- category: "",
- region: "",
- });
- const pagination = ref({
- currentPage: 1,
- totalPages: 1,
- totalCount: 0,
- pageSize: 12,
- });
- const requestModal = ref({
- show: false,
- vendor: null,
- message: "",
- commissionRate: "",
- specialConditions: "",
- isReapply: false,
- });
- // 옵션 데이터
- const categoryOptions = [
- { title: "패션·뷰티", value: "FASHION_BEAUTY" },
- { title: "식품·건강", value: "FOOD_HEALTH" },
- { title: "라이프스타일", value: "LIFESTYLE" },
- { title: "테크·가전", value: "TECH_ELECTRONICS" },
- { title: "스포츠·레저", value: "SPORTS_LEISURE" },
- { title: "문화·엔터테인먼트", value: "CULTURE_ENTERTAINMENT" },
- ];
- const regionOptions = [
- { title: "서울", value: "SEOUL" },
- { title: "경기", value: "GYEONGGI" },
- { title: "인천", value: "INCHEON" },
- { title: "부산", value: "BUSAN" },
- { title: "대구", value: "DAEGU" },
- { title: "대전", value: "DAEJEON" },
- { title: "광주", value: "GWANGJU" },
- { title: "울산", value: "ULSAN" },
- { title: "기타", value: "OTHER" },
- ];
- // 현재 사용자 SEQ
- const currentUserSeq = computed(() => authStore.getUserSeq);
- // 필터링된 벤더사 목록
- const filteredVendors = computed(() => {
- if (activeTab.value === "new") {
- return vendors.value.filter((v) => !v.PARTNERSHIP_STATUS);
- } else if (activeTab.value === "current") {
- return vendors.value.filter((v) =>
- ["PENDING", "APPROVED"].includes(v.PARTNERSHIP_STATUS)
- );
- } else if (activeTab.value === "terminated") {
- return vendors.value.filter((v) => v.PARTNERSHIP_STATUS === "TERMINATED");
- }
- return vendors.value;
- });
- // 메서드들
- const handleSearch = async () => {
- loading.value = true;
- currentPage.value = 1;
- try {
- const params = {
- keyword: searchFilter.value.keyword,
- category: searchFilter.value.category,
- region: searchFilter.value.region,
- sortBy: "latest",
- page: currentPage.value,
- size: pagination.value.pageSize,
- influencerSeq: currentUserSeq.value,
- };
- useAxios()
- .post("/api/vendor-influencer/search-vendors", params)
- .then((res) => {
- if (res.data.success) {
- vendors.value = res.data.data.items || [];
- pagination.value = res.data.data.pagination || {};
- } else {
- $toast.error(res.data.message || "검색에 실패했습니다.");
- }
- })
- .catch((err) => {
- $toast.error("검색 중 오류가 발생했습니다.");
- console.error("Search error:", err);
- })
- .finally(() => {
- loading.value = false;
- });
- } catch (err) {
- $toast.error("검색 중 오류가 발생했습니다.");
- loading.value = false;
- }
- };
- const handlePageChange = (page) => {
- currentPage.value = page;
- handleSearch();
- };
- // 파트너십 요청
- const requestPartnership = (vendor) => {
- requestModal.value = {
- show: true,
- vendor: vendor,
- message: "",
- commissionRate: "",
- specialConditions: "",
- isReapply: false,
- };
- };
- // 재승인요청
- const requestReapply = (vendor) => {
- requestModal.value = {
- show: true,
- vendor: vendor,
- message: "",
- commissionRate: vendor.COMMISSION_RATE || "",
- specialConditions: vendor.SPECIAL_CONDITIONS || "",
- isReapply: true,
- };
- };
- const submitRequest = async () => {
- try {
- processing.value = true;
- const endpoint = requestModal.value.isReapply
- ? "/api/vendor-influencer/reapply-request"
- : "/api/vendor-influencer/create-request";
- const params = {
- vendorSeq: requestModal.value.vendor.SEQ,
- influencerSeq: currentUserSeq.value,
- requestMessage: requestModal.value.message,
- requestedBy: currentUserSeq.value,
- ...(requestModal.value.isReapply
- ? {}
- : {
- commissionRate: requestModal.value.commissionRate,
- specialConditions: requestModal.value.specialConditions,
- }),
- };
- useAxios()
- .post(endpoint, params)
- .then((res) => {
- if (res.data.success) {
- $toast.success(res.data.message);
- closeRequestModal();
- handleSearch(); // 리스트 새로고침
- } else {
- $toast.error(res.data.message || "요청 처리에 실패했습니다.");
- }
- })
- .catch((err) => {
- $toast.error("요청 처리 중 오류가 발생했습니다.");
- console.error("Request error:", err);
- })
- .finally(() => {
- processing.value = false;
- });
- } catch (err) {
- $toast.error("요청 처리 중 오류가 발생했습니다.");
- processing.value = false;
- }
- };
- const closeRequestModal = () => {
- requestModal.value = {
- show: false,
- vendor: null,
- message: "",
- commissionRate: "",
- specialConditions: "",
- isReapply: false,
- };
- };
- const viewPartnership = (vendor) => {
- navigateTo(`/view/influencer/partnerships`);
- };
- const viewVendorDetail = (vendorSeq) => {
- navigateTo(`/view/vendor/${vendorSeq}`);
- };
- // 유틸리티 함수들
- const getCategoryText = (category) => {
- const categoryMap = {
- FASHION_BEAUTY: "패션·뷰티",
- FOOD_HEALTH: "식품·건강",
- LIFESTYLE: "라이프스타일",
- TECH_ELECTRONICS: "테크·가전",
- SPORTS_LEISURE: "스포츠·레저",
- CULTURE_ENTERTAINMENT: "문화·엔터테인먼트",
- };
- return categoryMap[category] || category || "기타";
- };
- const getPartnershipColor = (status) => {
- const colorMap = {
- PENDING: "warning",
- APPROVED: "success",
- REJECTED: "error",
- TERMINATED: "grey",
- CANCELLED: "grey",
- };
- return colorMap[status] || "grey";
- };
- const getPartnershipText = (status) => {
- const textMap = {
- PENDING: "승인 대기중",
- APPROVED: "파트너십 진행중",
- REJECTED: "승인 거부",
- TERMINATED: "파트너십 해지됨",
- CANCELLED: "요청 취소됨",
- };
- return textMap[status] || status || "알 수 없음";
- };
- // 라이프사이클
- onMounted(() => {
- handleSearch();
- });
- </script>
- <style scoped>
- .partnership--tabs {
- margin: 24px 0;
- }
- .vendor--grid {
- margin-top: 24px;
- }
- .vendor--cards {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
- gap: 24px;
- }
- .vendor--card {
- background: white;
- border-radius: 12px;
- padding: 24px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- transition: transform 0.2s, box-shadow 0.2s;
- display: flex;
- gap: 20px;
- }
- .vendor--card:hover {
- transform: translateY(-4px);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
- }
- .vendor--card.partnership-exists {
- border-left: 4px solid #4caf50;
- }
- .vendor--logo {
- width: 80px;
- height: 80px;
- border-radius: 8px;
- overflow: hidden;
- flex-shrink: 0;
- background: #f5f5f5;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .no-logo {
- font-size: 32px;
- font-weight: bold;
- color: #666;
- }
- .vendor--info {
- flex: 1;
- }
- .vendor--name {
- margin: 0 0 12px 0;
- font-size: 18px;
- font-weight: 600;
- color: #333;
- }
- .vendor--meta {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- margin-bottom: 12px;
- }
- .meta--item {
- display: flex;
- align-items: center;
- gap: 4px;
- color: #666;
- font-size: 14px;
- }
- .vendor--description {
- font-size: 14px;
- line-height: 1.5;
- color: #666;
- margin: 0 0 16px 0;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- }
- .partnership--status {
- margin-bottom: 16px;
- }
- .vendor--actions {
- display: flex;
- flex-direction: column;
- gap: 8px;
- flex-shrink: 0;
- }
- .request--content {
- padding: 8px 0;
- }
- .vendor--summary h4 {
- margin: 0 0 4px 0;
- font-size: 16px;
- font-weight: 600;
- }
- .vendor--summary p {
- margin: 0;
- color: #666;
- font-size: 14px;
- }
- .form-row {
- display: flex;
- gap: 12px;
- }
- .reapply--info {
- margin-top: 16px;
- }
- .loading-wrap {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 60px 20px;
- }
- .loading-wrap p {
- margin-top: 16px;
- color: #666;
- }
- .no-results {
- display: flex;
- justify-content: center;
- padding: 60px 20px;
- }
- .no-data {
- text-align: center;
- }
- .no-data h3 {
- margin: 16px 0 8px;
- color: #666;
- }
- .no-data p {
- color: #999;
- }
- .pagination-wrap {
- display: flex;
- justify-content: center;
- margin-top: 32px;
- }
- @media (max-width: 768px) {
- .vendor--cards {
- grid-template-columns: 1fr;
- }
- .vendor--card {
- flex-direction: column;
- text-align: center;
- }
- .vendor--actions {
- flex-direction: row;
- justify-content: center;
- }
- .form-row {
- flex-direction: column;
- }
- }
- </style>
|