songzhen пре 1 месец
родитељ
комит
2173f3dcb3

+ 2 - 0
.env.development

@@ -15,4 +15,6 @@ VITE_APP_BASE_URL=https://service.xiaoyaotravel.com/api/
 VITE_APP_BASE_URL=http://192.168.1.204:8082
 # VITE_APP_BASE_URL=http://192.168.1.3:8082
 
+VITE_APP_WEBSITE_BASE_URL=http://101.126.146.250:8086
+
 VITE_APP_IM_USER_SUFFIX=dev

+ 2 - 0
.env.production

@@ -5,6 +5,8 @@ VITE_APP_BASE_URL=https://service.xiaoyaotravel.com/api/
 
 VITE_APP_IM_USER_SUFFIX=''
 
+VITE_APP_WEBSITE_BASE_URL=https://www.xiaoyaotravel.com/
+
 
 VITE_APP_EMOJI_API=https://v.xiaoyaotravel.com/emoji/
 

+ 2 - 0
.env.staging

@@ -5,4 +5,6 @@ VITE_APP_BASE_URL=http://101.126.146.250:8088/api/
 
 VITE_APP_IM_USER_SUFFIX=''
 
+VITE_APP_WEBSITE_BASE_URL=http://101.126.146.250:8086
+
 VITE_APP_EMOJI_API=https://t.xiaoyaotravel.com/emoji/

BIN
src/assets/img/profile/profile_my_wallet.png


BIN
src/assets/img/profile/wallet_bg.png


+ 1 - 1
src/components/NavBar/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div
-    class="fixed left-0 right-0 top-0 z-[999] flex h-60 items-center justify-center bg-white shadow-[0_4px_4px_0_rgba(0,0,0,0.1)]"
+    class="fixed left-0 right-0 top-0 z-[600] flex h-60 items-center justify-center bg-white shadow-[0_4px_4px_0_rgba(0,0,0,0.1)]"
   >
     <NuxtLink to="/" class="absolute left-50">
       <img src="~/assets/img/nav_bar/logo.png" class="h-32 w-60" />

+ 6 - 0
src/components/Profile/Home/Tabs.vue

@@ -30,6 +30,7 @@ import profile_tab_travel_note from '~/assets/img/profile/profile_tab_travel_not
 import profile_tab_travel_order from '~/assets/img/profile/profile_tab_travel_order.png'
 import profile_tab_travel_visa from '~/assets/img/profile/profile_tab_travel_visa.png'
 import profile_my_comment from '~/assets/img/profile/profile_my_comment.png'
+import profile_my_wallet from '~/assets/img/profile/profile_my_wallet.png'
 
 const tabs = [
   {
@@ -67,6 +68,11 @@ const tabs = [
     label: '我的评论',
     icon: profile_my_comment,
     to: '/profile/my-comment'
+  },
+  {
+    label: '我的返利',
+    icon: profile_my_wallet,
+    to: '/profile/wallet'
   }
 ]
 

+ 26 - 6
src/components/TravelDetail/PinTuan/BookModal.vue

@@ -175,7 +175,9 @@ const props = defineProps({
   projectInfo: {
     type: Object,
     default: () => {}
-  }
+  },
+  adultNumber: Number,
+  childrenNumber: Number
 })
 
 const emit = defineEmits('onSuccess')
@@ -184,7 +186,7 @@ const show = defineModel('show', false)
 
 const id = useRouteParam('id')
 
-const dayjs = useDayjs()
+const fromUserId = useRouteQuery('fromUserId')
 
 const bookInfo = reactive({
   adultNumber: 1,
@@ -195,6 +197,11 @@ const bookInfo = reactive({
   customerWechat: ''
 })
 
+watchEffect(() => {
+  bookInfo.adultNumber = props.adultNumber
+  bookInfo.childrenNumber = props.childrenNumber
+})
+
 function cleanBookInfo() {
   bookInfo.adultNumber = 1
   bookInfo.childrenNumber = null
@@ -238,10 +245,22 @@ const formRules = reactive({
 })
 
 const adultCountOptions = computed(() =>
-  Array.from({ length: props.pinTuanInfo.maxCount ?? 0 }, (_, i) => i + 1)
+  Array.from(
+    {
+      length:
+        (props.pinTuanInfo.maxCount ?? 0) - (props.pinTuanInfo.nowCount ?? 0)
+    },
+    (_, i) => i + 1
+  )
 )
 const childrenCountOptions = computed(() =>
-  Array.from({ length: props.pinTuanInfo.maxCount ?? 0 }, (_, i) => i + 1)
+  Array.from(
+    {
+      length:
+        (props.pinTuanInfo.maxCount ?? 0) - (props.pinTuanInfo.nowCount ?? 0)
+    },
+    (_, i) => i + 1
+  )
 )
 
 const totalPrice = ref(0)
@@ -295,7 +314,7 @@ async function getPinTuanBaseInfo() {
   )
 }
 
-function handleSubmit() {
+async function handleSubmit() {
   formRef.value.validate(async (valid) => {
     if (valid) {
       try {
@@ -321,7 +340,8 @@ function handleSubmit() {
             startDate: props.pinTuanInfo.travelStartTime,
             type: '1',
             groupId: props.pinTuanInfo.id,
-            childrenNumber: bookInfo.childrenNumber ?? 0
+            childrenNumber: bookInfo.childrenNumber ?? 0,
+            shareId: fromUserId.value
           }
         })
         show.value = false

+ 1 - 1
src/components/TravelDetail/PinTuan/Card.vue

@@ -16,7 +16,7 @@
         :class="[{ active }, active ? 'text-white' : 'text-black-6']"
       >
         <div class="mt-23 text-sm">
-          <span>¥&nbsp;</span>
+          <span>{{ itemData.priceUnit ?? '¥' }}&nbsp;</span>
           <span class="text-2xl">{{
             priceToArray(itemData.adultPrice)[0]
           }}</span>

+ 61 - 10
src/components/TravelDetail/PinTuan/ResultModal.vue

@@ -1,28 +1,65 @@
 <template>
   <div>
-    <el-dialog title="提交成功" width="420" destroy-on-close v-model="show">
+    <el-dialog
+      title="提交成功"
+      width="460"
+      :z-index="650"
+      destroy-on-close
+      v-model="show"
+    >
       <div class="flex flex-col items-center text-base text-black-3">
         <img
           src="~/assets/img/travel_detail/pintuan_success.png"
-          class="h-80 w-80"
+          class="mb-20 h-80 w-80"
         />
-        <div class="mt-20">
-          <span v-if="pinTuanInfo.success == 0"
-            >你已选择{{ pinTuanInfo.maxCount }}人团,再
+        <template v-if="pinTuanInfo.success == 0">
+          <div>
+            <span>你已选择{{ pinTuanInfo.maxCount }}人团,再邀请</span>
             <span class="text-3xl font-semibold text-primary"
               >{{ pinTuanInfo.nextStageNum ?? 0 }}人</span
-            >,就可享受<span class="text-3xl font-semibold text-primary"
-              >¥{{ pinTuanInfo.nowPrice - pinTuanInfo.nextPrice }}</span
-            >的优惠</span
-          >
-          <span v-else>拼团成功</span>
+            ><span>拼团,就可享受</span>
+            <span class="text-base font-semibold text-primary">{{
+              pinTuanInfo.priceUnit
+            }}</span>
+            <span class="text-3xl font-semibold text-primary">{{
+              pinTuanInfo.nowPrice - pinTuanInfo.nextPrice
+            }}</span
+            >的优惠
+          </div>
+          <div class="">
+            <span>同时,每邀请</span>
+            <span class="text-3xl font-semibold text-primary">1</span>
+            <span>人拼团,</span>
+            <span>可得到</span>
+            <span class="text-base font-semibold text-primary">{{
+              pinTuanInfo.priceUnit
+            }}</span>
+            <span class="text-3xl font-semibold text-primary">{{
+              pinTuanInfo.singleRebate ?? 0
+            }}</span>
+            <span>返利</span>
+          </div>
+        </template>
+        <span v-else>拼团成功</span>
+        <div class="mt-20 w-[90%] bg-[#f7f8f9] p-10 text-base">
+          <span>分享链接:</span>
+          <span class="text-primary underline">{{ shareUrl }}</span>
         </div>
+        <el-button
+          type="primary"
+          size="large"
+          style="width: 200px; margin-top: 20px"
+          @click="handleCopy"
+          >复制并分享</el-button
+        >
       </div>
     </el-dialog>
   </div>
 </template>
 
 <script setup>
+import { useClipboard } from '@vueuse/core'
+
 const show = defineModel('show', false)
 
 const props = defineProps({
@@ -31,6 +68,20 @@ const props = defineProps({
     default: () => {}
   }
 })
+
+const userInfoStore = useUserInfoStore()
+const { userInfo } = storeToRefs(userInfoStore)
+const userId = computed(() => userInfo.value?.userId ?? '')
+
+const shareUrl = computed(() => {
+  return `${import.meta.env.VITE_APP_WEBSITE_BASE_URL}/t/${props.pinTuanInfo.projectId}?pinTuanId=${props.pinTuanInfo.id}&fromUserId=${userId.value}`
+})
+const { copy } = useClipboard({ shareUrl })
+
+function handleCopy() {
+  copy(shareUrl.value)
+  ElMessage.success('复制成功')
+}
 </script>
 
 <style lang="scss" scoped></style>

+ 229 - 0
src/components/TravelDetail/PinTuan/SharedPanel.vue

@@ -0,0 +1,229 @@
+<template>
+  <div class="flex items-end bg-[#FD9A001A] px-20 py-20">
+    <div v-if="pinTuanInfo" class="">
+      <div class="flex space-x-40">
+        <div class="flex flex-col space-y-10 text-base text-black-3">
+          <div>出发时间</div>
+          <el-date-picker
+            v-model="pinTuanInfo.travelStartTime"
+            disabled
+            value-format="YYYY-MM-DD"
+            type="date"
+            placeholder="选择出发时间"
+            size="large"
+            style="width: 240px"
+          />
+        </div>
+        <div class="flex flex-col space-y-10 text-base text-black-3">
+          <div>团购</div>
+          <el-input
+            disabled
+            :value="`${pinTuanInfo.maxCount}人团`"
+            size="large"
+            style="width: 240px"
+          />
+        </div>
+        <div class="flex flex-col space-y-10 text-base text-black-3">
+          <div class="flex items-end space-x-10">
+            <span>成人</span>
+            <span class="text-sm text-black-9">12周岁及以上</span>
+          </div>
+
+          <el-select
+            v-model="bookInfo.adultNumber"
+            placeholder="选择成人数量"
+            style="width: 240px"
+            size="large"
+          >
+            <el-option
+              v-for="item in adultCountOptions"
+              :key="item"
+              :label="`${item}人`"
+              :value="item"
+            />
+          </el-select>
+        </div>
+        <div class="flex flex-col space-y-10 text-base text-black-3">
+          <div class="flex items-end space-x-10">
+            <span>儿童</span>
+            <span class="text-sm text-black-9"
+              >3周岁至未满12周岁(3周岁以下免费)</span
+            >
+          </div>
+
+          <el-select
+            v-model="bookInfo.childrenNumber"
+            placeholder="选择儿童数量"
+            style="width: 240px"
+            size="large"
+            clearable
+          >
+            <el-option
+              v-for="item in childrenCountOptions"
+              :key="item"
+              :label="`${item}人`"
+              :value="item"
+            />
+          </el-select>
+        </div>
+      </div>
+      <div class="mt-20 flex items-center justify-end space-x-15">
+        <div class="text-sm text-black-6">
+          <span>总价&nbsp; </span>
+          <span>¥</span>
+          <span class="text-3xl text-[#FF2525]">{{
+            priceToArray(totalPrice)[0]
+          }}</span>
+          <span class="text-[#FF2525]">.{{ priceToArray(totalPrice)[1] }}</span>
+          <span>/人起&nbsp; </span>
+          <el-popover placement="top-start" :width="260" trigger="hover">
+            <template #reference>
+              <span
+                class="iconfont icon-info-circle cursor-pointer text-primary"
+              ></span>
+            </template>
+            <div class="text-base text-black-3">
+              <div class="text-xl font-semibold">价格详情</div>
+              <div
+                class="mt-15 flex items-center justify-between font-semibold"
+              >
+                <span class=" ">基本团费</span>
+                <span>¥{{ totalPrice }}</span>
+              </div>
+              <div class="my-15 h-1 bg-[#F0F0F0]"></div>
+              <div class="flex items-center justify-between">
+                <span class="text-base">成人</span>
+                <span>¥{{ adultTotalPrice }}</span>
+              </div>
+              <div class="mt-10 flex items-center justify-between">
+                <span class="text-base">儿童</span>
+                <span>¥{{ childTotalPrice }}</span>
+              </div>
+              <div class="my-15 h-1 bg-[#F0F0F0]"></div>
+              <div class="flex items-center justify-end space-x-10">
+                <span class="text-xl font-semibold">总价</span>
+                <span class="text-4xl font-semibold text-[#FF0000]"
+                  >{{ data.priceUnit }}{{ totalPrice }}</span
+                >
+              </div>
+            </div>
+          </el-popover>
+        </div>
+        <el-button
+          class="ml-10"
+          type="primary"
+          size="large"
+          @click="pintuanFormModalOptions.show = true"
+          >立即参团</el-button
+        >
+        <NuxtLink
+          target="_blank"
+          class="flex h-40 w-96 items-center justify-center rounded border border-current text-primary"
+          :to="`/t/${id}`"
+          >查看更多</NuxtLink
+        >
+      </div>
+    </div>
+    <TravelDetailPinTuanBookModal
+      v-model:show="pintuanFormModalOptions.show"
+      :pin-tuan-info="pinTuanInfo"
+      :adult-number="bookInfo.adultNumber"
+      :children-number="bookInfo.childrenNumber"
+      @on-success="handlePintuanSuccess"
+    />
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({})
+  }
+})
+const id = useRouteParam('id')
+
+const pinTuanId = useRouteQuery('pinTuanId')
+
+const loading = ref(false)
+
+const pinTuanInfo = ref()
+
+const adultCountOptions = computed(() =>
+  Array.from(
+    {
+      length:
+        (pinTuanInfo.value.maxCount ?? 0) - (pinTuanInfo.value.nowCount ?? 0)
+    },
+    (_, i) => i + 1
+  )
+)
+const childrenCountOptions = computed(() =>
+  Array.from(
+    {
+      length:
+        (pinTuanInfo.value.maxCount ?? 0) - (pinTuanInfo.value.nowCount ?? 0)
+    },
+    (_, i) => i + 1
+  )
+)
+
+const bookInfo = reactive({
+  adultNumber: 1,
+  childrenNumber: null
+})
+
+const totalPrice = ref(0)
+const adultTotalPrice = ref(0)
+const childTotalPrice = ref(0)
+async function calcTotalPrice() {
+  try {
+    loading.value = true
+    const { data } = await request(
+      '/website/app/tourProjectGroupPurchase/calcTotalAmount',
+      {
+        query: {
+          id: pinTuanId.value,
+          adultCount: bookInfo.adultNumber ?? 0,
+          childrenCount: bookInfo.childrenNumber ?? 0
+        }
+      }
+    )
+    totalPrice.value = data.totalPrice ?? 0
+    adultTotalPrice.value = data.adultTotalPrice ?? 0
+    childTotalPrice.value = data.childTotalPrice ?? 0
+  } catch (error) {}
+}
+
+watch(
+  [() => bookInfo.adultNumber, () => bookInfo.childrenNumber],
+  () => {
+    calcTotalPrice()
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+async function getPinTuanBaseInfo() {
+  loading.value = true
+  const { data } = await request(
+    `/website/app/tourProjectGroupPurchase/view?id=${pinTuanId.value}`
+  )
+  loading.value = false
+  pinTuanInfo.value = data
+}
+
+const pintuanFormModalOptions = reactive({
+  show: false
+})
+
+function handlePintuanSuccess() {}
+
+onMounted(() => {
+  getPinTuanBaseInfo()
+})
+</script>
+
+<style lang="scss" scoped></style>

+ 1 - 1
src/components/TravelDetail/PinTuan/Users.vue

@@ -58,7 +58,7 @@ const loading = ref(false)
 async function getListData() {
   loading.value = true
   const { data } = await request(
-    `/website/app/tourProjectGroupPurchase/queryGroupPurchaseUser?projectId=${props.pinTuanId}`
+    `/website/app/tourProjectGroupPurchase/queryGroupPurchaseUser?groupPurchaseProgressId=${props.pinTuanId}`
   )
   loading.value = false
   listData.value = data

+ 62 - 6
src/pages/profile/index/travel-orders.vue

@@ -83,20 +83,52 @@
                 >{{ item.currency }}{{ item.totalAmount }}</span
               >
             </div>
-            <el-button
-              v-if="item.orderStatus == '0'"
-              @click.prevent="handleCancel(item)"
-              style="margin-top: 10px"
-              >取消订单</el-button
-            >
+            <div class="mt-10">
+              <el-button
+                v-if="
+                  item.tourProjectGroupPurchase &&
+                  item.tourProjectGroupPurchase.maxCount >
+                    item.tourProjectGroupPurchase.nowCount
+                "
+                type="primary"
+                @click.prevent="handleShare(item)"
+                >分享有返利</el-button
+              >
+              <el-button
+                v-if="item.orderStatus == '0'"
+                @click.prevent="handleCancel(item)"
+                >取消订单</el-button
+              >
+            </div>
           </div>
         </NuxtLink>
       </div>
     </div>
+    <el-dialog v-model="shareDialogOptions.show" width="493px">
+      <div class="flex flex-col items-center">
+        <img
+          src="~/assets/img/travel_detail/pintuan_success.png"
+          class="mb-20 h-80 w-80"
+        />
+        <div class="text-xl text-black-3">
+          每邀请1人拼团,可得{{
+            shareDialogOptions.itemData?.tourProjectGroupPurchase?.singleRebate
+          }}元返利
+        </div>
+        <div class="mt-10 w-[80%] break-all text-xl text-primary">
+          {{ shareUrl }}
+        </div>
+        <el-button type="primary" size="large" class="mt-20" @click="handleCopy"
+          >复制链接并分享</el-button
+        >
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
+import { useClipboard } from '@vueuse/core'
+
 const tabs = [
   {
     value: '',
@@ -181,6 +213,30 @@ function handleCancel(item) {
     })
 }
 
+const shareDialogOptions = reactive({
+  show: false,
+  itemData: {}
+})
+
+function handleShare(item) {
+  shareDialogOptions.show = true
+  shareDialogOptions.itemData = item
+}
+
+const shareUrl = computed(() => {
+  return `${import.meta.env.VITE_APP_WEBSITE_BASE_URL}/t/${shareDialogOptions.itemData.projectId}?pinTuanId=${shareDialogOptions.itemData.tourProjectGroupPurchase?.id}&fromUserId=${userId.value}`
+})
+const { copy } = useClipboard({ shareUrl })
+
+function handleCopy() {
+  copy(shareUrl.value)
+  ElMessage.success('复制成功')
+}
+
+const userInfoStore = useUserInfoStore()
+const { userInfo } = storeToRefs(userInfoStore)
+const userId = computed(() => userInfo.value?.userId ?? '')
+
 onMounted(() => {
   getOrderList()
 })

+ 27 - 0
src/pages/profile/index/wallet.vue

@@ -0,0 +1,27 @@
+<template>
+  <div class="flex items-center justify-center">
+    <div
+      class="h-146 w-600 bg-[url('~/assets/img/profile/wallet_bg.png')] bg-cover bg-no-repeat px-20 text-white"
+    >
+      <div class="mt-60 text-xl">拼团返利余额</div>
+      <div class="mt-10 text-3xl font-semibold">¥{{ totalAmount }}</div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const totalAmount = ref(0)
+
+async function getAmount() {
+  const { data } = await request(
+    '/website/app/tourProjectGroupPurchaseRebate/getTotalRebateAmount'
+  )
+  totalAmount.value = data.rebateAmount
+}
+
+onMounted(() => {
+  getAmount()
+})
+</script>
+
+<style lang="scss" scoped></style>

+ 11 - 5
src/pages/t/[id].vue

@@ -11,11 +11,17 @@
     </div>
     <div class="flex justify-center pt-20">
       <div class="w-wrap shrink-0">
-        <TravelDetailPinTuan v-if="detailData?.hasGroup == 1" />
-        <TravelDetailNomalBookInfo
-          v-else-if="detailData?.hasGroup == 0"
-          :data="detailData"
-        />
+        <template v-if="$route.query.fromUserId">
+          <TravelDetailPinTuanSharedPanel />
+        </template>
+        <template v-else>
+          <TravelDetailPinTuan v-if="detailData?.hasGroup == 1" />
+          <TravelDetailNomalBookInfo
+            v-else-if="detailData?.hasGroup == 0"
+            :data="detailData"
+          />
+        </template>
+
         <div class="mt-20 bg-white">
           <TravelDetailTabs @change="handleTabsChange" />
           <div class="mt-20">