Ver código fonte

refactor(app): 优化消息通知和订单确认功能

- 修改消息通知页面,增加详情跳转功能
- 优化订单确认流程,支持使用优惠券
- 调整积分计算逻辑,考虑优惠券影响
- 优化发布案例功能,支持多图上传
EvilDragon 3 meses atrás
pai
commit
c27115747f

+ 3 - 0
package.json

@@ -12,5 +12,8 @@
   },
   "resolutions": {
     "bin-wrapper": "npm:bin-wrapper-china"
+  },
+  "devDependencies": {
+    "@types/qs": "^6.9.17"
   }
 }

+ 1 - 1
packages/app/src/components/bottom-app-bar.vue

@@ -40,7 +40,7 @@ onMounted(async () => {
       ref="aRef"
       :class="[
         (fixed ?? true)
-          ? 'fixed bottom-0 left-0 right-0 after:content-empty after:w-full after:h-full after:relative'
+          ? 'fixed bottom-0 left-0 right-0 z-1 after:content-empty after:w-full after:h-full after:relative'
           : '',
         border ? 'border-t border-t-solid border-[#ececec]' : '',
       ]"

+ 1 - 0
packages/app/src/components/data-form.ts

@@ -5,6 +5,7 @@ export interface DataFormProps {
   disabled?: boolean
   'onUpdate:modelValue'?: (value: string) => void
   type?: 'nickname'
+  maxlength?: number
 }
 export interface DataFormSchema {
   [key: symbol | string]: {

+ 34 - 14
packages/app/src/components/data-form.vue

@@ -56,9 +56,11 @@ const horizontalDefaultProps = {
   TextField: {
     customClass: 'text-red!',
     placeholderClass: 'text-black/30',
+    noBorder: true,
   },
   Select: {
-    customClass: 'text-black/30! border-b-1 border-b-[#e1e1e1] border-b-solid',
+    // customClass: 'text-black/30! border-b-1 border-b-[#e1e1e1] border-b-solid',
+    customClass: 'text-black/30!',
   },
   Radio: {
     customClass: 'my--4!',
@@ -69,10 +71,13 @@ const horizontalDefaultProps = {
   TimePick: {
     customClass: 'm-0!',
   },
+  Textarea: {
+    customClass: 'p-0!',
+  },
 }
 const themeVars: ConfigProviderThemeVars = {
   cellPadding: '0',
-  cellWrapperPadding: '10rpx',
+  cellWrapperPadding: '0px',
   radioButtonRadius: '8rpx',
   radioButtonBg: 'transparent',
   checkboxButtonRadius: '8rpx',
@@ -101,7 +106,7 @@ defineExpose({
         <div
           v-if="existing ?? true"
           class="grid mb-4"
-          :class="[direction === 'horizontal' ? 'items-center' : '']"
+          :class="[direction === 'horizontal' ? 'items-start' : '']"
           :style="
             direction === 'horizontal'
               ? { 'grid-template-columns': `${addUnit(labelWidth || 100)} auto` }
@@ -143,17 +148,25 @@ defineExpose({
               ...props,
             }"
           />
-          <wd-textarea
-            v-if="type === 'Textarea'"
-            v-model="modelValue[prop]"
-            v-bind="{
-              ...(direction === 'vertical'
-                ? verticalDefaultProps[type]
-                : horizontalDefaultProps[type]),
-              cell: false,
-              ...props,
-            }"
-          />
+          <div v-if="type === 'Textarea'" class="textarea-evo relative">
+            <wd-textarea
+              v-model="modelValue[prop]"
+              v-bind="{
+                ...(direction === 'vertical'
+                  ? verticalDefaultProps[type]
+                  : horizontalDefaultProps[type]),
+                cell: false,
+                ...props,
+              }"
+            />
+            <div v-if="props?.maxlength" class="absolute bottom-0 right-0">
+              <div
+                class="w-10 h-5 text-right text-black/40 text-xs font-normal font-['PingFang SC'] leading-tight"
+              >
+                {{ modelValue[prop]?.length }}/{{ props?.maxlength }}
+              </div>
+            </div>
+          </div>
           <wd-picker
             v-if="type === 'Select'"
             v-bind="{
@@ -216,3 +229,10 @@ defineExpose({
     </wd-form>
   </wd-config-provider>
 </template>
+<style lang="scss">
+.textarea-evo {
+  .wd-textarea__inner {
+    height: 250rpx !important;
+  }
+}
+</style>

+ 4 - 4
packages/app/src/composables/permissions.ts

@@ -44,10 +44,10 @@ export const usePermissions = () => {
       path: '/pages/mine/orders/index',
       meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
     },
-    {
-      path: '/pages/mine/agents/index',
-      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
-    },
+    // {
+    //   path: '/pages/mine/agents/index',
+    //   meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
+    // },
     {
       path: '/pages/mine/invite/index',
       meta: { canNotLogin: false, canNotDesigner: false, toLogin: true },

+ 3 - 3
packages/app/src/core/libs/message-types.ts

@@ -43,7 +43,7 @@ export const messageTypes = [
   { subType: 2, desc: '认证驳回', path: '/pages/mine/authentication/index' },
   { subType: 3, desc: '材料商入住', path: '/pages/material/detail/index?id=0' },
   { subType: 4, desc: '数据仓驾驶' },
-  { subType: 5, desc: '消息通知', path: '' },
+  { subType: 5, desc: '消息通知', path: '/pages/messages/detail/index' },
   { subType: 6, desc: '客户预约' },
   { subType: 7, desc: '分享数据' },
   { subType: 8, desc: '会员变动通知', path: '/pages/mine/levels/index' },
@@ -56,8 +56,8 @@ export const messageTypes = [
   { subType: 11, desc: '账号冻结通知' },
   { subType: 12, desc: '会员升级', path: '/pages/mine/levels/index' },
   { subType: 13, desc: '会员降级', path: '/pages/mine/levels/index' },
-  { subType: 14, desc: '优惠卷获取', path: '/pages/mine/coupon/index' },
-  { subType: 15, desc: '优惠卷过期', path: '/pages/mine/coupon/index' },
+  { subType: 14, desc: '优惠卷获取', path: '/pages/mine/coupons/index' },
+  { subType: 15, desc: '优惠卷过期', path: '/pages/mine/coupons/index' },
   { subType: 16, desc: '证书获取', path: '/pages/mine/honors/index' },
   { subType: 17, desc: '徽章获取', path: '/pages/mine/honors/index' },
   {

+ 25 - 0
packages/app/src/core/libs/models.ts

@@ -49,6 +49,10 @@ export interface MaterialDealer {
   orderCount: number
   pointsExchangeRate: number
   shopList: ShopList[]
+  /**
+   * 累计到店次数
+   */
+  cumulativeStoreNum?: number
 }
 export interface ShopList {
   id: number
@@ -740,6 +744,27 @@ export interface Message {
   viewTime: any
   pointsDetail?: PointsDetail
 }
+
+export interface ConfirmOrder {
+  isShoppingCart: number
+  userId: number
+  list: {
+    orderNo: any
+    productId: string
+    vendorId: number
+    productName: string
+    points: number
+    nums: number
+    orderImgUrl: string
+  }[]
+  couponList: any[]
+  totalsPoints: number
+  totalsCouponPoints: any
+  totalsCurrPoints: any
+  item: number
+  type: any
+}
+
 export interface Coupon {
   id: number
   couponId: number

+ 23 - 2
packages/app/src/core/libs/requests.ts

@@ -21,7 +21,8 @@ import {
   Badge,
   Certificate,
   UserBasicInfo,
-  ActivitySignUp, Product,
+  ActivitySignUp,
+  Product, ConfirmOrder,
 } from './models'
 import dayjs from 'dayjs'
 
@@ -494,7 +495,7 @@ export const productPlacing = (data: {
   /**
    * 1-游学项目,2-线下活动,3-品质商城,4-案例拍摄,5-微信代运营,6-积分支付,7-其他
    */
-  item: 1 | 2 | 3 | 4 | 5 | 6 | 7
+  item: number | 1 | 2 | 3 | 4 | 5 | 6 | 7
   list: {
     orderNo?: string
     productId?: string
@@ -516,6 +517,26 @@ export const productPlacing = (data: {
   totalsCurrPoints?: number
 }) => httpPost('/app-api/member/points-order/placing', data)
 /**
+ * 获取订单金额
+ */
+export const getOrderAmount = (data: {
+  isShoppingCart: number
+  userId: number
+  list: {
+    orderNo?: string
+    productId?: string
+    points?: number
+    nums?: number
+  }[]
+  couponList: {
+    couponId: number
+    brandPoints: number
+  }[]
+  totalsPoints?: number
+  totalsCouponPoints?: number
+  totalsCurrPoints?: number
+}) => httpPost<ConfirmOrder>('/app-api/member/points-order/orderAmount', data)
+/**
  * 订单结算
  */
 export const orderPay = (data: {

+ 13 - 4
packages/app/src/core/utils/common.ts

@@ -45,24 +45,33 @@ export const toast = (
 }
 export const requestToast = async <T>(
   func: () => Promise<IResData<T>>,
-  options: { error?: boolean; errorTitle?: string; success?: boolean; successTitle?: string } = {
+  options: {
+    error?: boolean
+    errorTitle?: string
+    success?: boolean
+    successTitle?: string
+    duration?: number
+  } = {
     error: true,
   },
 ) => {
   const { code, data, msg } = await func()
+  const duration = options.duration || 3000
   if (code === 0 && options.success) {
-    uni.showToast({
+    await uni.showToast({
       title: options.successTitle,
       icon: 'none',
+      duration,
     })
   }
   if (code !== 0 && options.error) {
-    uni.showToast({
+    await uni.showToast({
       title: options.errorTitle || msg,
       icon: 'none',
+      duration: 5000,
     })
   }
-  return { code, data, msg }
+  return { code, data, msg, duration }
 }
 
 export const getActivityStatus = (startAt: string | number, endAt: string | number) => {

+ 8 - 0
packages/app/src/pages.json

@@ -273,6 +273,14 @@
       }
     },
     {
+      "path": "pages/messages/detail/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "详情",
+        "navigationBarBackgroundColor": "#fff"
+      }
+    },
+    {
       "path": "pages/mine/agents/index",
       "type": "page",
       "style": {

+ 8 - 8
packages/app/src/pages/common/components/coupons-selector.vue

@@ -33,7 +33,7 @@ const request = computed(() =>
           userId: userInfo.value.userId,
           //   productIds: props.products.map((it) => it.productId).join(','),
           businessId: props.businessId,
-          isUse: 0,
+          // isUse: 0,
           ...query.value,
         })
     : () =>
@@ -62,11 +62,7 @@ const handleTabsChange = async ({ index, name }) => {
 watch(
   () => props.show,
   async (val) => {
-    console.log(val)
-
     if (val) {
-      console.log(111111)
-
       await setCoupons()
     }
   },
@@ -74,11 +70,15 @@ watch(
 onMounted(async () => {})
 </script>
 <template>
-  <wd-action-sheet title="优惠券" :modelValue="show" @close="emits('close')">
+  <wd-action-sheet
+    :title="{ points: '销售积分券', product: '商品优惠券' }[type]"
+    :modelValue="show"
+    @close="emits('close')"
+  >
     <view class="">
       <wd-tabs v-model="tab" @change="handleTabsChange">
-        <wd-tab title="可用优惠券"></wd-tab>
-        <wd-tab title="不可用优惠券"></wd-tab>
+        <wd-tab :title="`可用${{ points: '积分券', product: '商品券' }[type]}`"></wd-tab>
+        <wd-tab :title="`不可用${{ points: '积分券', product: '商品券' }[type]}`"></wd-tab>
       </wd-tabs>
       <div class="h-100 overflow-y-scroll">
         <div class="bg-[#f6f6f6] py-3.5 flex flex-col gap-4 min-h-full">

+ 6 - 3
packages/app/src/pages/home/components/moment-video.vue

@@ -1,6 +1,4 @@
 <script setup lang="ts">
-import { getCurrentInstance } from 'vue'
-
 const instance = getCurrentInstance()
 const props = withDefaults(defineProps<{ src: string; enableProgressGesture: boolean }>(), {
   enableProgressGesture: false,
@@ -16,7 +14,12 @@ defineExpose({
 </script>
 
 <template>
-  <video class="w-full h-full" id="video" v-bind="props"></video>
+  <video
+    class="w-full h-full"
+    id="video"
+    :src="src"
+    :enable-progress-gesture="enableProgressGesture"
+  ></video>
 </template>
 
 <style scoped lang="scss"></style>

+ 26 - 16
packages/app/src/pages/home/mall/confirm-order/index.vue

@@ -12,7 +12,7 @@ import Card from '@/components/card.vue'
 import SectionHeading from '@/components/section-heading.vue'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import { requestToast } from '../../../../core/utils/common'
-import { getProductCoupons, orderPay } from '../../../../core/libs/requests'
+import { getOrderAmount, getProductCoupons, orderPay } from '../../../../core/libs/requests'
 import { useUserStore } from '../../../../store'
 import { storeToRefs } from 'pinia'
 import { useRouter } from '../../../../core/utils/router'
@@ -31,12 +31,17 @@ const show = ref(false)
 const { alert } = useMessage()
 const data = ref()
 const selectedCoupons = ref<Coupon[]>()
-const { data: coupons, run: setCoupons } = useRequest(() =>
-  getProductCoupons({
-    userId: userInfo.value.userId,
-    productIds: data.value.list.map((it) => it.productId).join(','),
-    isUse: 0,
-  }),
+const requestData = computed(() => ({
+  ...data.value,
+  couponList:
+    selectedCoupons.value?.map((it) => ({
+      couponId: it.id,
+      projectIds: it.productIds,
+      buinessId: it.buinessId,
+    })) || [],
+}))
+const { data: confirmOrder, run: setConfirmOrder } = useRequest(() =>
+  getOrderAmount(requestData.value),
 )
 const offerPoints = computed(() => {
   const products = sort(data.value?.list, (it: any) => it.points).reverse()
@@ -56,12 +61,8 @@ const offerPoints = computed(() => {
   console.log(sumBrandPoints)
   return Number(sumBrandPoints)
 })
-const paidPoints = computed(() => {
-  return (data.value?.totalsPoints - offerPoints.value || 0).toString()
-})
 const handlePay = async () => {
   console.log(111)
-
   const couponList =
     selectedCoupons.value?.map((it) => ({
       couponId: it.id,
@@ -77,7 +78,7 @@ const handlePay = async () => {
     { success: true, successTitle: '兑换成功' },
   )
   if (code === 0) {
-    router.replace('/pages/home/mall/purchased/success/index')
+    await router.replace('/pages/home/mall/purchased/success/index')
   }
 }
 const handleQ = async () => {
@@ -88,8 +89,13 @@ const handleSelect = (coupon: Coupon) => {
   selectedCoupons.value = [coupon]
   show.value = false
 }
+const handleClose = () => {
+  show.value = false
+  setConfirmOrder()
+}
 onLoad(async (query: { data: string }) => {
   data.value = JSON.parse(query.data)
+  await setConfirmOrder()
 })
 </script>
 
@@ -123,7 +129,7 @@ onLoad(async (query: { data: string }) => {
       <div class="flex flex-col gap-8">
         <SectionHeading
           title="总积分"
-          :end-text="data?.totalsPoints.toString()"
+          :end-text="confirmOrder?.totalsPoints.toString()"
           size="sm"
         ></SectionHeading>
         <div @click="handleQ">
@@ -166,14 +172,18 @@ onLoad(async (query: { data: string }) => {
             </template>
           </SectionHeading>
         </div>
-        <SectionHeading title="实付积分" :end-text="paidPoints" size="sm"></SectionHeading>
+        <SectionHeading
+          title="实付积分"
+          :end-text="confirmOrder?.totalsCurrPoints"
+          size="sm"
+        ></SectionHeading>
       </div>
     </Card>
     <BottomAppBar fixed placeholder>
       <div class="h-[63px] bg-white backdrop-blur-[20px] flex items-center justify-between">
         <div class="flex items-end gap-1.25">
           <div class="text-[#ef4343] text-2xl font-normal font-['D-DIN_Exp'] leading-7">
-            {{ paidPoints }}
+            {{ confirmOrder?.totalsCurrPoints }}
           </div>
           <div class="text-black/40 text-base font-normal font-['PingFang_SC']">积分</div>
         </div>
@@ -187,7 +197,7 @@ onLoad(async (query: { data: string }) => {
       type="product"
       :show="show"
       :products="data?.list"
-      @close="show = false"
+      @close="handleClose"
       @click-instruction="(e) => handleClickInstruction(alert, e)"
     ></CouponsSelector>
     <!-- <CouponsSelector></CouponsSelector> -->

+ 25 - 20
packages/app/src/pages/home/mall/detail/index.vue

@@ -32,26 +32,25 @@ const handleConfirm = async () => {
   // 积分
   const points = data.value?.showFavourable ? data.value?.favourablePoints : data.value?.points
   if (type.value === 'orderNow') {
-    const { data: res, code } = await requestToast(() =>
-      productPlacing({
-        isShoppingCart: 0,
-        userId: userInfo.value.userId,
-        item: 3,
-        list: [
-          {
-            productId: id.value,
-            points,
-            nums: nums.value,
-            productName: data.value.prodcutName,
-            orderImgUrl: data.value.productCoverImgUrl,
-            vendorId: data.value.vendorId,
-          },
-        ],
-        couponList: [],
-      }),
-    )
+    const body = {
+      isShoppingCart: 0,
+      userId: userInfo.value.userId,
+      item: 3,
+      list: [
+        {
+          productId: id.value,
+          points,
+          nums: nums.value,
+          productName: data.value.prodcutName,
+          orderImgUrl: data.value.productCoverImgUrl,
+          vendorId: data.value.vendorId,
+        },
+      ],
+      couponList: [],
+    }
+    const { data: res, code } = await requestToast(() => productPlacing(body))
     if (code !== 0) return
-    await router.push(`/pages/home/mall/confirm-order/index?data=${JSON.stringify(res)}`)
+    await router.push(`/pages/home/mall/confirm-order/index?data=${JSON.stringify(body)}`)
   }
   if (type.value === 'add2Cart') {
     await requestToast(
@@ -81,7 +80,13 @@ onLoad(async (query: { id: string }) => {
   <view class="flex-grow flex flex-col">
     <div class="aspect-[1.34/1] relative">
       <div class="absolute aspect-[1.26/1] top-0 w-full">
-        <wd-img width="100%" height="100%" mode="aspectFill" :src="data?.productDetailsImgUrl" />
+        <swiper>
+          <template v-for="(it, index) in data?.productDetailsImgUrl?.split(',')" :key="index">
+            <swiper-item>
+              <wd-img width="100%" height="100%" mode="aspectFill" :src="it" />
+            </swiper-item>
+          </template>
+        </swiper>
       </div>
     </div>
     <div class="relative flex-1 bg-white p-4 flex flex-col gap-4 rounded-tl-2xl rounded-tr-2xl">

+ 1 - 1
packages/app/src/pages/material/index.vue

@@ -201,7 +201,7 @@ onMounted(async () => {
                     <div
                       class="text-black/30 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]"
                     >
-                      {{ it.virtualArrival || 0 }}次到店打卡
+                      {{ it.virtualArrival || it.cumulativeStoreNum || 0 }}次到店打卡
                     </div>
                   </div>
                 </Card>

+ 57 - 16
packages/app/src/pages/messages/components/message-card.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { Message } from '../../../core/libs/models'
+import { Coupon, Message } from '../../../core/libs/models'
 import Card from '@/components/card.vue'
 import { integral, interact, message, system } from '../../../core/libs/svgs'
 import { beforeNow } from '../../../utils/date-util'
@@ -10,10 +10,14 @@ import { getMessageType } from '../../../core/libs/message-types'
 import { useRouter } from '../../../core/utils/router'
 import ButtonEvo from '@/components/button-evo.vue'
 import { messages } from '../../../core/libs/messages'
+import { stringify } from 'qs'
 
-const props = withDefaults(defineProps<{ options?: Message }>(), {})
+const props = withDefaults(
+  defineProps<{ options?: Message & { selectedCoupons?: Coupon[] } }>(),
+  {},
+)
 const emits = defineEmits<{
-  submit: [message: Message]
+  submit: [message: Message, coupons: Coupon[]]
   cancel: [message: Message]
   selectCoupon: [message: Message, coupons: any[]]
 }>()
@@ -27,6 +31,16 @@ const { data: coupons, run: setCoupons } = useRequest(
     }),
   { initialData: [] },
 )
+const couponSelectText = computed(() => {
+  // `${coupons.value.length > 0 ? `${coupons.value.length}张可用` : '无可用'}`
+  if (props.options.selectedCoupons?.length) {
+    return `${props.options.selectedCoupons.at(0)?.brandPoints ?? 0}积分`
+  }
+  if (coupons.value.length) {
+    return `${coupons.value.length}张可用`
+  }
+  return '无可用'
+})
 const init = async () => {
   if (
     props.options.messageType === MessageType.Integral &&
@@ -34,17 +48,34 @@ const init = async () => {
     props.options.isRead !== '1'
   ) {
     await setCoupons()
-    // console.log(coupons.value)
   }
 }
+const handleJump = () => {
+  console.log(props)
+  const query: Record<string, string> = {}
+  switch (props.options.messageSubType) {
+    case 5:
+      query.title = props.options.title
+      query.contentDetail = props.options.detailBody
+      query.createTime = String(props.options.createTime)
+      query.viewsCount = props.options.viewCount
+      break
+    default:
+      break
+  }
+  console.log(stringify(query))
+  // return
+  router.push(getMessageType(props.options.messageSubType)?.path + '?' + stringify(query))
+}
 watch(
   () => props.options,
-  async () => {
+  async (value, oldValue, onCleanup) => {
+    // console.log('====>', value)
     await init()
   },
 )
 onMounted(async () => {
-  // await init()
+  await init()
 })
 </script>
 <template>
@@ -69,7 +100,11 @@ onMounted(async () => {
       </div>
       <div class="row-start-1 col-start-2 text-start">
         <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-[30px]">
-          {{ options.title }}
+          {{
+            String(options.messageSubType) === '5'
+              ? getMessageType(options.messageSubType)?.desc
+              : options.title
+          }}
         </div>
       </div>
       <div class="row-start-1 col-start-3 text-end">
@@ -81,7 +116,10 @@ onMounted(async () => {
         <div class="my-3 text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[25px]">
           <!-- {{ options.detailBody }} -->
           <div
-            v-if="[MessageType.Integral, MessageType.System].includes(options.messageType)"
+            v-if="
+              [MessageType.Integral, MessageType.System].includes(options.messageType) &&
+              String(options.messageSubType) !== '5'
+            "
             v-html="options.detailBody"
           ></div>
           <div class="grid grid-cols-[auto_1fr] gap-x-5 gap-y-4.5">
@@ -93,13 +131,14 @@ onMounted(async () => {
               </div>
               <div
                 class="flex items-center h-full overflow-hidden"
-                @click="coupons.length && emits('selectCoupon', options, coupons)"
+                @click="emits('selectCoupon', options, coupons)"
               >
                 <div
                   class="text-sm font-normal font-['PingFang_SC']"
                   :class="`${coupons.length > 0 ? 'text-[#ef4343]' : 'text-black/60'}`"
                 >
-                  {{ `${coupons.length > 0 ? `${coupons.length}张可用` : '无可用'}` }}
+                  <!--                  {{ `${coupons.length > 0 ? `${coupons.length}张可用` : '无可用'}` }}-->
+                  {{ couponSelectText }}
                 </div>
                 <div class="h-5.5 flex items-center overflow-hidden">
                   <wd-icon
@@ -111,11 +150,13 @@ onMounted(async () => {
               </div>
             </template>
           </div>
-          <!-- {{ options.detailBody }} -->
         </div>
       </div>
-      <div v-if="options.coverUrl" class="row-start-3 col-start-2 col-end-4">
-        <img class="w-[279px] h-[164px] rounded-md" :src="options.coverUrl" />
+      <div
+        v-if="options.coverUrl"
+        class="row-start-3 col-start-2 col-end-4 aspect-[1.7/1] rounded-md overflow-hidden"
+      >
+        <wd-img width="100%" height="100%" mode="aspectFill" :src="options.coverUrl" />
       </div>
       <div class="row-start-4 col-start-1 col-end-4 my-2">
         <div v-if="!options.coverUrl" class="bg-[#dadada] w-full h-[1px]"></div>
@@ -142,7 +183,7 @@ onMounted(async () => {
             <template v-if="options.pointsDetail?.pointsStauts === PointStatus.Rejected">
               <span class="text-[#EF4343]">
                 <!-- 确认积分后,即刻到账,如有问题请驳回,联系材料商修改积分后再次确认 -->
-                驳回原因:{{ options.pointsDetail?.cancelReason }}
+                已驳回,驳回原因:{{ options.pointsDetail?.cancelReason }}
               </span>
             </template>
           </template>
@@ -156,7 +197,7 @@ onMounted(async () => {
           </template>
           <template v-if="getMessageType(options.messageSubType)?.path">
             <div
-              @click="router.push(getMessageType(options.messageSubType)?.path)"
+              @click="handleJump"
               class="flex items-center text-[rgba(0,0,0,0.85)] text-xs font-normal font-['PingFang_SC'] leading-[25px]"
             >
               查看详情
@@ -185,7 +226,7 @@ onMounted(async () => {
           </div> -->
           <div class="flex-1">
             <!-- <wd-button block :round="false" @click="emits('submit', options)">确认</wd-button> -->
-            <ButtonEvo block @click="emits('submit', options)">确认</ButtonEvo>
+            <ButtonEvo block @click="emits('submit', options, coupons)">确认</ButtonEvo>
           </div>
         </div>
       </div>

+ 51 - 0
packages/app/src/pages/messages/detail/index.vue

@@ -0,0 +1,51 @@
+<route lang="json">
+{
+  "style": {
+    "navigationBarTitleText": "详情",
+    "navigationBarBackgroundColor": "#fff"
+  }
+}
+</route>
+<script setup lang="ts">
+import { logo } from '../../../core/libs/svgs'
+import Article from '../../home/components/article.vue'
+import { Content } from '@/core/libs/models'
+
+const id = ref()
+const type = ref()
+const request = ref<() => Promise<IResData<Partial<Content>>>>()
+const data = ref()
+onLoad(async (query?: { id: string; type?: 'banner' }) => {
+  data.value = query
+  // id.value = query.id
+  // type.value = query.type
+  // if (type.value === 'banner') {
+  //   request.value = () =>
+  //     getBanner(id.value).then((res) => ({
+  //       ...res,
+  //       data: {
+  //         title: res.data?.name,
+  //         contentDetail: res.data?.bannerDetailsContent,
+  //         createTime: res.data?.createTime.toString(),
+  //         viewsCount: res.data?.viewCount,
+  //       },
+  //     }))
+  // } else {
+  //   request.value = () => getContent({ id: id.value })
+  // }
+  // await run()
+})
+</script>
+<template>
+  <div class="flex-grow bg-white">
+    <Article
+      :title="data?.title"
+      :author="{ name: '筑巢荟' }"
+      :content="data?.contentDetail"
+      :createAt="data?.createTime"
+      :viewNum="data?.viewsCount || 0"
+    >
+      <template #avatar><wd-img width="28" height="28" :src="logo"></wd-img></template>
+    </Article>
+  </div>
+</template>

+ 29 - 8
packages/app/src/pages/messages/index.vue

@@ -44,18 +44,18 @@ const selectedCoupons = ref<Coupon[]>([])
 const coupons = ref<Coupon[]>([])
 const cancelReason = ref('')
 const currentMessage = ref<Message>()
-const { alert } = useMessage()
-const { confirm } = useMessage('wd-message-box-slot')
+const { alert, confirm } = useMessage()
+const { confirm: rejectConfirm } = useMessage('wd-message-box-slot')
 
 const query = computed(() => ({ messageType: tabs.value[tab.value]?.value }))
 
 const handleCancel = async (message: Message) => {
-  await confirm({
+  await rejectConfirm({
     title: '驳回',
     beforeConfirm: async ({ resolve }) => {
       if (!cancelReason.value) {
         resolve(false)
-        await uni.showToast({title: '请输入驳回原因', icon: 'none'})
+        await uni.showToast({ title: '请输入驳回原因', icon: 'none' })
         return
       }
       await requestToast(
@@ -73,9 +73,27 @@ const handleCancel = async (message: Message) => {
   await updateMessage({ id: message.id, isRead: '1' })
   await pageHelperRef.value?.refresh()
 }
-const handleSubmit = async (message: Message) => {
+const handleSubmit = async (message: Message, coupons: Coupon[]) => {
+  console.log(
+    Number(message.pointsDetail?.points) + Number(selectedCoupons.value.at(0)?.brandPoints ?? 0),
+  )
+  if (coupons.length && !selectedCoupons.value.length) {
+    const result = await confirm({
+      title: `您有${coupons.length}张销售积分券可使用,若不使用请点击确定`,
+    }).catch((reason) => reason)
+    console.log(result.action)
+    if (result.action === 'cancel') {
+      return false
+    }
+  }
+  console.log(11111)
+
+  // return;
+  // if (!selectedCoupons.value.length) {
+  //
+  // }
   await requestToast(
-    () =>
+    async () =>
       orderPointsSubmit({
         id: message.businessId,
         userId: message.designerId,
@@ -83,7 +101,7 @@ const handleSubmit = async (message: Message) => {
       }),
     {
       success: true,
-      successTitle: '积分已确认',
+      successTitle: `订单积分确认成功,获得${Number(message.pointsDetail?.points) + Number(selectedCoupons.value.at(0)?.brandPoints ?? 0)}积分`,
     },
   )
   // await deleteMessage(message.id.toString())
@@ -123,7 +141,10 @@ onShow(async () => {
         <div class="flex-grow p-3.5 gap-3.5 flex flex-col">
           <template v-for="(it, i) in source.list" :key="i">
             <MessageCard
-              :options="it"
+              :options="{
+                ...it,
+                selectedCoupons,
+              }"
               @submit="handleSubmit"
               @cancel="handleCancel"
               @select-coupon="handleQ"

+ 46 - 35
packages/app/src/pages/publish/moment/index.vue

@@ -17,7 +17,7 @@ import DataForm from '@/components/data-form.vue'
 import { zipToObject } from 'radash'
 import { useRouter } from '../../../core/utils/router'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
-import { toast } from '../../../core/utils/common'
+import { requestToast, toast } from '../../../core/utils/common'
 import { messages } from '../../../core/libs/messages'
 import { DataFormSchema } from '../../../components/data-form'
 import { ComponentExposed } from 'vue-component-type-helpers'
@@ -32,12 +32,20 @@ const fileList = ref([])
 const action = ref(`${import.meta.env.VITE_SERVER_BASEURL}/app-api/infra/file/upload`)
 const dataFormRef = ref<ComponentExposed<typeof DataForm>>()
 const schema = ref<DataFormSchema>({
+  caseName: {
+    label: '案例名称:',
+    type: 'TextField',
+    required: true,
+    labelWidth: 80,
+    props: { placeholder: '填写案例位置/名称' },
+  },
   spaceType: {
-    label: '空间类型:',
+    label: '空间类:',
     type: 'Select',
     required: true,
     labelWidth: 80,
     props: {
+      placeholder: '选择',
       columns: [],
     },
   },
@@ -46,7 +54,7 @@ const schema = ref<DataFormSchema>({
     type: 'Select',
     required: true,
     labelWidth: 80,
-    props: { columns: [] },
+    props: { columns: [], placeholder: '选择' },
   },
   // spaceAddr: { label: '空间位置', type: 'TextField' },
   spaceExtent: {
@@ -54,29 +62,22 @@ const schema = ref<DataFormSchema>({
     type: 'TextField',
     required: true,
     labelWidth: 80,
-    props: { placeholder: '请输入空间面积' },
-  },
-  caseName: {
-    label: '案例名称:',
-    type: 'TextField',
-    required: true,
-    labelWidth: 80,
-    props: { placeholder: '请输入案例名称' },
+    props: { placeholder: '填写面积' },
   },
   circleDesc: {
     label: '案例描述:',
     type: 'Textarea',
     required: true,
     labelWidth: 80,
-    props: { placeholder: '请输入案例描述' },
+    props: { placeholder: '请简单描述您的案例和设计理念', maxlength: 500 },
   },
   // customerDemand: { label: '客户需求', type: 'TextField' },
 })
 const rules = {
-  spaceType: [{ required: true, message: '请选择空间类型' }],
+  caseName: [{ required: true, message: '请填写案例名称' }],
+  spaceType: [{ required: true, message: '请选择空间类别' }],
   designStyle: [{ required: true, message: '请选择设计风格' }],
   spaceExtent: [{ required: true, message: '请填写空间面积' }],
-  caseName: [{ required: true, message: '请填写案例名称' }],
   circleDesc: [{ required: true, message: '请填写案例描述' }],
 }
 const formData = ref({})
@@ -101,25 +102,30 @@ const handleSubmit = async () => {
     return false
   }
   publishing.value = true
-  const { code, msg } = await createCircle({
-    stylistId: userInfo.value.userId,
-    stylistName: userInfo.value.nickname,
-    bannerUrls: fileList.value.map(({ response }) => JSON.parse(response).data),
-    tagName: tagName.value,
-    headUrl: userInfo.value.avatar,
-    circleDesc: content.value,
-    circleType: circleType.value,
-    tagIds: tagIds.value,
-    ...formData.value,
-  })
-  if (code !== 0) {
-    error(msg)
+  const { code, msg, duration } = await requestToast(
+    () =>
+      createCircle({
+        stylistId: userInfo.value.userId,
+        stylistName: userInfo.value.nickname,
+        bannerUrls: fileList.value.map(({ response }) => JSON.parse(response).data),
+        tagName: tagName.value,
+        headUrl: userInfo.value.avatar,
+        circleDesc: content.value,
+        circleType: circleType.value,
+        tagIds: tagIds.value,
+        ...formData.value,
+      }),
+    { success: true, successTitle: '发布成功' },
+  )
+
+  if (code === 0) {
+    setTimeout(() => {
+      publishing.value = false
+      router.back()
+    }, duration)
+    // publishing.value = false
+    // router.back()
   }
-  uni.showToast({
-    title: '发布成功',
-  })
-  publishing.value = false
-  router.back()
 }
 const updateTagName = (options: { tagNames: string[]; tagIds: string[] }) => {
   if (options.tagNames.length === 0) {
@@ -179,7 +185,12 @@ onLoad(async (query: { circleType: '1' | '2' }) => {
       <span class="text-[#ef4343] text-base font-normal font-['PingFang_SC'] leading-normal">
         *
       </span>
-      <SectionHeading title="实景图:" size="base" custom-class="my-5"></SectionHeading>
+      <SectionHeading
+        title="实景图:"
+        subtitle="首图建议尺寸1125x1104,最多可上传30张"
+        size="base"
+        custom-class="my-5"
+      ></SectionHeading>
     </div>
     <wd-upload
       :file-list="fileList"
@@ -187,7 +198,7 @@ onLoad(async (query: { circleType: '1' | '2' }) => {
       accept="media"
       :action="action"
       :multiple="true"
-      :limit="9"
+      :limit="String(circleType) === '2' ? 30 : 9"
       @change="handleChange"
     ></wd-upload>
     <SectionHeading
@@ -215,7 +226,7 @@ onLoad(async (query: { circleType: '1' | '2' }) => {
       </template>
     </SectionHeading>
     <div class="flex-1"></div>
-    <BottomAppBar fixed safe-area-inset-bottom>
+    <BottomAppBar fixed safe-area-inset-bottom placeholder>
       <div class="w-full">
         <wd-button type="primary" :round="false" block :loading="publishing" @click="handleSubmit">
           发布

+ 1 - 0
packages/app/src/types/uni-pages.d.ts

@@ -27,6 +27,7 @@ interface NavigateToOptions {
        "/pages/material/mini-class/index" |
        "/pages/material/recommend/index" |
        "/pages/material/settled-in/index" |
+       "/pages/messages/detail/index" |
        "/pages/mine/agents/index" |
        "/pages/mine/authentication/index" |
        "/pages/mine/convention/index" |

+ 1 - 1
packages/merchant/src/pages/agent/designer/archives/index.vue

@@ -55,7 +55,7 @@ const familyPageRef = ref<ComponentExposed<typeof PageHelperEvo>>()
 const awardsListRef = ref<ComponentExposed<typeof ListHelperEvo>>()
 // const {} = useRequest()
 const handleEditBasicInfo = async () => {
-  await uni.navigateTo({ url: `/pages/designer/archives/basic-info/index?id=${id.value}` })
+  await uni.navigateTo({ url: `/pages/agent/designer/archives/basic-info/index?id=${id.value}` })
 }
 const handleAddFamilyInfo = async () => {
   submitType.value = 'family'

+ 12 - 0
pnpm-lock.yaml

@@ -14,6 +14,10 @@ importers:
       wot-design-uni:
         specifier: 1.3.13
         version: 1.3.13(vue@3.5.13(typescript@5.7.2))
+    devDependencies:
+      '@types/qs':
+        specifier: ^6.9.17
+        version: 6.9.17
 
   packages/app:
     dependencies:
@@ -2015,6 +2019,7 @@ packages:
   '@esbuild/darwin-arm64@0.20.2':
     resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==}
     engines: {node: '>=12'}
+    cpu: [arm64]
     os: [darwin]
 
   '@esbuild/darwin-arm64@0.21.5':
@@ -2026,6 +2031,7 @@ packages:
   '@esbuild/darwin-x64@0.20.2':
     resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==}
     engines: {node: '>=12'}
+    cpu: [x64]
     os: [darwin]
 
   '@esbuild/darwin-x64@0.21.5':
@@ -2881,6 +2887,7 @@ packages:
 
   '@rollup/rollup-darwin-x64@4.28.0':
     resolution: {integrity: sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==}
+    cpu: [x64]
     os: [darwin]
 
   '@rollup/rollup-freebsd-arm64@4.28.0':
@@ -3042,6 +3049,9 @@ packages:
   '@types/prettier@2.7.3':
     resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==}
 
+  '@types/qs@6.9.17':
+    resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
+
   '@types/responselike@1.0.3':
     resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
 
@@ -12517,6 +12527,8 @@ snapshots:
 
   '@types/prettier@2.7.3': {}
 
+  '@types/qs@6.9.17': {}
+
   '@types/responselike@1.0.3':
     dependencies:
       '@types/node': 20.17.9