85 Commits 2b45704f25 ... 592b923fef

Author SHA1 Message Date
  EvilDragon 592b923fef fix:... 5 months ago
  EvilDragon 0947587775 feat(app): 添加优惠券记录组件并集成到订单详情页面 5 months ago
  EvilDragon a619611532 refactor: 统一字体名称 'PingFang SC' 修改为格式 5 months ago
  EvilDragon 8e32c21c91 refactor(app): 优化积分支付功能并修复相关问题 5 months ago
  EvilDragon d08d6b97ac refactor(app): 更新用户协议类型并添加新协议链接 5 months ago
  EvilDragon e6bd573b53 refactor(app): 重构消息详情页面 5 months ago
  EvilDragon 5857175264 feat(core): 添加用户协议相关功能 5 months ago
  EvilDragon df924bfc31 feat(core): 新增格式化时长函数并优化浏览时长显示 5 months ago
  EvilDragon af1928cb50 feat(权限管理): 新增素材下载、任务完成和扫码功能的权限控制 5 months ago
  Jake 08f2149116 修改案例详情 轮播图 5 months ago
  Jake 0a8bef4bb5 Merge branch 'main' of https://github.com/omnia96/designer-hub 5 months ago
  Jake 580ea59daa 案例拍摄,线下折扣 5 months ago
  EvilDragon 99d173dace Merge remote-tracking branch 'origin/main' into main 5 months ago
  EvilDragon b0ed6ec65e fix(agent-mine): 修复指标完成率和差值计算逻辑 5 months ago
  Jake 092d6170f2 修改bug 5 months ago
  EvilDragon d9a5cc0022 feat(agent): 添加设计师关注点功能并优化相关页面 5 months ago
  Jake 14d0be2fd4 修改品质商城超值预划算 5 months ago
  EvilDragon e7df84acc5 refactor(merchant): 重构设计师档案页面 5 months ago
  EvilDragon 0fd965d442 feat(mine): 优化会员等级相关功能 5 months ago
  EvilDragon 09cbfd4e4b refactor(app): 优化多个组件和页面的样式及功能 5 months ago
  EvilDragon 9351eeaf98 refactor(app): 优化首页活动展示和用户认证功能 5 months ago
  EvilDragon 2ace750fa0 feat(mine): 更新代理邀请海报功能 5 months ago
  EvilDragon b920d70ce8 refactor(app): 优化活动和游学相关页面 5 months ago
  EvilDragon 5f0b9c4892 Merge remote-tracking branch 'origin/main' into main 5 months ago
  EvilDragon a8083c090b feat(agent):中,添加了对任务状态的 优化任务详情页面显示逻辑 5 months ago
  EvilDragon d0e89d0ed6 style(font): 更新字体样式并统一使用 D-DIN-PRO 5 months ago
  Jake ce4c991955 refactor(merchant): 修改积分商城,超值预划算 5 months ago
  EvilDragon 74fdc11b77 feat(app): 新增通用内容页面和修改密码功能 5 months ago
  EvilDragon a8cc620dc5 feat(authentication): 优化用户认证页面功能 5 months ago
  EvilDragon 5757c0ffac refactor(merchant): 优化代理设计师页面和任务卡片组件 5 months ago
  EvilDragon c980ef3e34 feat(mine): 优化个人信息页面展示 5 months ago
  EvilDragon e1fc595f13 feat/offline-activity(cycling-rankings): 优化骑行排行榜页面 5 months ago
  EvilDragon a0fc858ad4 refactor(app): 优化活动相关组件和页面 5 months ago
  EvilDragon b89ed59597 feat(coupon): 优化优惠券展示和逻辑 5 months ago
  EvilDragon b90700adc0 feat(app): 添加页面浏览数据分析功能 5 months ago
  EvilDragon e53246ad5d feat(app): 优化视频号关联功能并添加相关页面 5 months ago
  EvilDragon 5d546981fb feat(app): 优化个人主页并添加二维码功能 5 months ago
  EvilDragon 855539f332 Merge remote-tracking branch 'origin/main' into main 5 months ago
  Jake e15f4d324b 1 5 months ago
  Jake 6ba5390c1f 修改微信代运营超值预划算 5 months ago
  EvilDragon 2dda16aabf feat(app): 添加页面分享功能- 在多个页面中添加 onShareAppMessage 和 onShareTimeline 方法- 设置分享标题为各页面的标题或产品名称 5 months ago
  EvilDragon 0d5bfda2b1 feat(权限管理): 添加分享功能的等级限制 5 months ago
  Jake c540936913 修改设计师主页编辑个人主页bug 5 months ago
  Jake e268b856a7 feat:(渠道端):任务详情,首页任务,任务列表修改bug 5 months ago
  Jake fda9c983ed Merge branch 'main' of https://github.com/omnia96/designer-hub 5 months ago
  Jake 556906530c 微信代运营-商品信息、商品详情 5 months ago
  EvilDragon e4ca47ad2a Merge remote-tracking branch 'origin/main' into main 5 months ago
  EvilDragon 05f1cbe364 fix: 修改文件名大小写 5 months ago
  Jake 26381fb3b2 根据游学计划列表页-游学项目对应的第x站,详情页也显示第x站 5 months ago
  Jake 50a885045d Merge branch 'main' of https://github.com/omnia96/designer-hub 5 months ago
  Jake 3159031671 feat(merchant):设计师列表写跟进 5 months ago
  EvilDragon 9f44ca4c8c Merge remote-tracking branch 'origin/main' into main 5 months ago
  EvilDragon 9fcfe6d273 feat(honor-dialog): 重构荣誉弹窗组件并添加新功能- 重构荣誉弹窗组件,支持徽章和证书两种类型- 添加查看奖励跳转功能- 优化弹窗样式和布局 5 months ago
  Jake c9ffe79c4f feat(merchant):首添加分页,请求10条,基础信息婚姻增修改为4中类型,家庭信息列表 5 months ago
  EvilDragon 0e9a1c5bc7 feat(core): 添加表单验证功能并优化咨询页面 5 months ago
  EvilDragon c937bc9ccb feat(share): 实现自定义分享功能 5 months ago
  EvilDragon c27115747f refactor(app): 优化消息通知和订单确认功能 5 months ago
  EvilDragon fa3986ae96 refactor(mall): 优化订单确认页面的优惠券选择逻辑- 将 selectedCoupons 数组中的 couponId 字段改为 id 字段 5 months ago
  EvilDragon a9b5146ec1 feat(app): 优化 coupon-card组件并添加新功能 5 months ago
  EvilDragon 77cada79a8 Merge remote-tracking branch 'origin/main' into main 5 months ago
  EvilDragon 3c58752101 refactor(mall): 重构积分商城相关功能 5 months ago
  Jake 221ef49ac0 接单和不接单弹出确认框 5 months ago
  EvilDragon ff789b0dcc Merge remote-tracking branch 'origin/main' into main 5 months ago
  EvilDragon 55b22b19c0 refactor(app): 优化返回商城首页逻辑 5 months ago
  Jake 24a83a8da2 Merge branch 'main' of https://github.com/omnia96/designer-hub 5 months ago
  Jake b8468607bd 接单弹框 5 months ago
  EvilDragon bed6095882 refactor(app): 优化路由回退逻辑并统一字体样式 5 months ago
  EvilDragon 5c96a515d9 refactor(mine): 重构个人中心页面 5 months ago
  EvilDragon 2ac85ea3f7 feat(app): 优化活动相关组件显示 5 months ago
  EvilDragon 91962e8e44 refactor(agent): 优化任务详情页面样式和功能 5 months ago
  EvilDragon 041137fcb5 feat(agent): 增加设计师强弱绑定筛选功能 5 months ago
  EvilDragon d5b3fdc6cf refactor(mine): 重构设计师数据展示逻辑 5 months ago
  EvilDragon 03b5fac162 refactor(merchant): 重构任务相关页面 5 months ago
  EvilDragon c53dcdf9c2 feat(merchant): 添加设计师浏览记录统计功能 5 months ago
  EvilDragon 6dc11bbfb6 feat(mine): 优化代理个人页面并添加跟进记录功能 5 months ago
  EvilDragon f667b11b00 feat(agent): 添加设计师其他活动信息页面 5 months ago
  EvilDragon dbb39e53f7 feat(agent): 新增设计师其他销售信息功能 5 months ago
  EvilDragon 31cf225c77 feat(merchant): 优化设计师详情页面积分相关功能 5 months ago
  EvilDragon df66c403dc feat(agent): 添加设计师积分统计功能 5 months ago
  EvilDragon 0553c84c83 refactor(merchant): 优化跳转到个人主页功能并调整相关组件 5 months ago
  EvilDragon ebb5be0c0e feat(agent): 添加设计师销售信息功能 5 months ago
  EvilDragon 3fb8ff310a feat(merchant): 积分商城订单详情 5 months ago
  EvilDragon 5e1a2c503b feat(agent): 优化设计师详情页面展示 5 months ago
  EvilDragon c42b1309f0 refactor(agent): 重构设计师详情页面 5 months ago
  EvilDragon 4bdfda9e37 refactor(agent): 重构设计师积分页面 5 months ago
100 changed files with 3077 additions and 993 deletions
  1. 4 1
      package.json
  2. 3 2
      packages/app/env/.env.development
  3. 3 1
      packages/app/package.json
  4. 3 0
      packages/app/src/App.vue
  5. 3 2
      packages/app/src/components/bottom-app-bar.vue
  6. 8 0
      packages/app/src/components/card.vue
  7. 1 0
      packages/app/src/components/data-form.ts
  8. 34 14
      packages/app/src/components/data-form.vue
  9. 7 0
      packages/app/src/components/hot-activity-item.vue
  10. 1 1
      packages/app/src/components/hot-activity.vue
  11. 1 1
      packages/app/src/components/image-evo.vue
  12. 76 0
      packages/app/src/components/list-helper-evo.vue
  13. 44 22
      packages/app/src/components/moment-item.vue
  14. 3 2
      packages/app/src/components/page-helper-evo.vue
  15. 7 4
      packages/app/src/components/section-heading.vue
  16. 2 1
      packages/app/src/components/upload-evo.vue
  17. 3 2
      packages/app/src/composables/activity.ts
  18. 75 0
      packages/app/src/composables/analysis.ts
  19. 0 20
      packages/app/src/composables/honor-dialog.ts
  20. 24 6
      packages/app/src/composables/permissions.ts
  21. 55 0
      packages/app/src/composables/share.ts
  22. 1 0
      packages/app/src/core/libs/actions.ts
  23. 29 0
      packages/app/src/core/libs/enums.ts
  24. 3 3
      packages/app/src/core/libs/message-types.ts
  25. 1 0
      packages/app/src/core/libs/messages.ts
  26. 338 1
      packages/app/src/core/libs/models.ts
  27. 5 1
      packages/app/src/core/libs/net-images.ts
  28. 69 72
      packages/app/src/core/libs/requests.ts
  29. 8 0
      packages/app/src/core/themes/default.scss
  30. 1 0
      packages/app/src/core/themes/default.ts
  31. 21 9
      packages/app/src/core/utils/canvas.ts
  32. 81 7
      packages/app/src/core/utils/common.ts
  33. 1 1
      packages/app/src/core/utils/page-spy.ts
  34. 2 2
      packages/app/src/core/utils/router.ts
  35. 1 1
      packages/app/src/layouts/tabbar.vue
  36. 1 1
      packages/app/src/main.ts
  37. 1 1
      packages/app/src/pages-sub/material/calculator/index.vue
  38. 24 0
      packages/app/src/pages.json
  39. 65 36
      packages/app/src/pages/common/components/coupon-card.vue
  40. 90 0
      packages/app/src/pages/common/components/coupon-record.vue
  41. 17 10
      packages/app/src/pages/common/components/coupons-selector.vue
  42. 0 82
      packages/app/src/pages/common/components/honor-dialog.vue
  43. 185 0
      packages/app/src/pages/common/components/honor-dialog/honor-dialog.vue
  44. 63 0
      packages/app/src/pages/common/components/honor-dialog/index.ts
  45. 50 0
      packages/app/src/pages/common/components/share-action-sheet.vue
  46. 33 0
      packages/app/src/pages/common/content-html/index.vue
  47. 10 5
      packages/app/src/pages/home/about/index.vue
  48. 55 59
      packages/app/src/pages/home/activity/detail/index.vue
  49. 19 5
      packages/app/src/pages/home/components/activity-as-of.vue
  50. 1 1
      packages/app/src/pages/home/components/activity-count-down.vue
  51. 1 0
      packages/app/src/pages/home/components/banner.vue
  52. 3 1
      packages/app/src/pages/home/components/class-item.vue
  53. 3 1
      packages/app/src/pages/home/components/comment-item.vue
  54. 1 1
      packages/app/src/pages/home/components/info-card.vue
  55. 3 1
      packages/app/src/pages/home/components/menus.vue
  56. 25 0
      packages/app/src/pages/home/components/moment-video.vue
  57. 17 14
      packages/app/src/pages/home/components/offline-activity-item.vue
  58. 17 11
      packages/app/src/pages/home/components/register-card.vue
  59. 13 8
      packages/app/src/pages/home/components/schedule-card.vue
  60. 6 0
      packages/app/src/pages/home/content/index.vue
  61. 78 24
      packages/app/src/pages/home/index.vue
  62. 29 11
      packages/app/src/pages/home/mall/components/product.vue
  63. 41 20
      packages/app/src/pages/home/mall/confirm-order/index.vue
  64. 94 93
      packages/app/src/pages/home/mall/detail/index.vue
  65. 82 27
      packages/app/src/pages/home/mall/index.vue
  66. 9 1
      packages/app/src/pages/home/mall/purchased/success/index.vue
  67. 50 35
      packages/app/src/pages/home/mall/shopping-cart/index.vue
  68. 137 71
      packages/app/src/pages/home/moment/index.vue
  69. 137 15
      packages/app/src/pages/home/offline-activity/cycling-rankings/index.vue
  70. 1 3
      packages/app/src/pages/home/offline-activity/index.vue
  71. 7 1
      packages/app/src/pages/home/offline-activity/list/index.vue
  72. 20 18
      packages/app/src/pages/home/schedule/index.vue
  73. 11 4
      packages/app/src/pages/home/spread/case-shooting/index.vue
  74. 1 1
      packages/app/src/pages/home/spread/case-shooting/photographer/index.vue
  75. 3 1
      packages/app/src/pages/home/spread/index.vue
  76. 58 33
      packages/app/src/pages/home/spread/product-detail/index.vue
  77. 10 3
      packages/app/src/pages/home/spread/wx-agent-operation/index.vue
  78. 16 11
      packages/app/src/pages/home/study-tour/components/register-card.vue
  79. 1 1
      packages/app/src/pages/home/study-tour/components/study-tour-card.vue
  80. 1 1
      packages/app/src/pages/home/study-tour/detail.vue
  81. 6 3
      packages/app/src/pages/home/study-tour/index.vue
  82. 26 4
      packages/app/src/pages/home/study-tour/list.vue
  83. 28 2
      packages/app/src/pages/login/index.vue
  84. 135 49
      packages/app/src/pages/material/detail/index.vue
  85. 1 1
      packages/app/src/pages/material/index.vue
  86. 4 5
      packages/app/src/pages/material/mini-class/index.vue
  87. 80 21
      packages/app/src/pages/messages/components/message-card.vue
  88. 40 0
      packages/app/src/pages/messages/detail/index.vue
  89. 30 11
      packages/app/src/pages/messages/index.vue
  90. 29 9
      packages/app/src/pages/mine/authentication/index.vue
  91. 3 1
      packages/app/src/pages/mine/components/tasks-card.vue
  92. 3 1
      packages/app/src/pages/mine/convention/index.vue
  93. 1 1
      packages/app/src/pages/mine/coupons/index.vue
  94. 58 14
      packages/app/src/pages/mine/homepage/channels/index.vue
  95. 12 2
      packages/app/src/pages/mine/homepage/consult/index.vue
  96. 6 5
      packages/app/src/pages/mine/homepage/edit/index.vue
  97. 45 42
      packages/app/src/pages/mine/homepage/index.vue
  98. 84 14
      packages/app/src/pages/mine/homepage/qr-code/index.vue
  99. 4 4
      packages/app/src/pages/mine/homepage/statistics/index.vue
  100. 75 17
      packages/app/src/pages/mine/honors/index.vue

+ 4 - 1
package.json

@@ -8,9 +8,12 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
-    "wot-design-uni": "1.3.13"
+    "wot-design-uni": "1.5.1"
   },
   "resolutions": {
     "bin-wrapper": "npm:bin-wrapper-china"
+  },
+  "devDependencies": {
+    "@types/qs": "^6.9.17"
   }
 }

+ 3 - 2
packages/app/env/.env.development

@@ -5,7 +5,7 @@ VITE_DELETE_CONSOLE = false
 # 是否开启sourcemap
 VITE_SHOW_SOURCEMAP = true
 
-VITE_SERVER_BASEURL = 'https://www.zhuchaohui.com'
+VITE_SERVER_BASEURL='https://www.zhuchaohui.com'
 # VITE_SERVER_BASEURL = 'http://39.106.91.179:48080'
 # VITE_SERVER_BASEURL = 'http://192.168.2.50:48080'
 # 王超
@@ -16,5 +16,6 @@ VITE_SERVER_BASEURL = 'https://www.zhuchaohui.com'
 # 赵要军
 # VITE_SERVER_BASEURL = 'http://192.168.2.41:48080'
 # 赵添更
-# VITE_SERVER_BASEURL = 'http://192.168.0.108:48080'
+# VITE_SERVER_BASEURL='http://192.168.2.45:48080'
+# VITE_SERVER_BASEURL='http://192.168.2.58:48080'
 

+ 3 - 1
packages/app/package.json

@@ -98,6 +98,8 @@
     "@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4020820240920001",
     "@designer-hub/assets": "workspace:*",
     "@huolala-tech/page-spy-uniapp": "^1.9.9",
+    "@uqrcode/js": "^4.0.7",
+    "@uqrcode/uni-app": "^4.0.7",
     "czg": "^1.9.3",
     "dayjs": "1.11.10",
     "extract-colors": "^4.1.0",
@@ -112,7 +114,7 @@
     "vue": "^3.4.21",
     "vue-component-type-helpers": "^2.1.8",
     "vue-i18n": "^9.1.9",
-    "wot-design-uni": "^1.3.14",
+    "wot-design-uni": "^1.5.1",
     "z-paging": "^2.7.10"
   },
   "devDependencies": {

+ 3 - 0
packages/app/src/App.vue

@@ -1,5 +1,8 @@
 <script setup lang="ts">
 import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
+import { useAnalysis } from '@/composables/analysis'
+
+useAnalysis(true, true)
 
 onLaunch(() => {
   console.log('App Launch')

+ 3 - 2
packages/app/src/components/bottom-app-bar.vue

@@ -7,6 +7,7 @@ const props = defineProps<{
   placeholder?: boolean
   border?: boolean
   transparent?: boolean
+  customClass?: string
 }>()
 
 const { proxy } = getCurrentInstance() as any
@@ -40,12 +41,12 @@ 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]' : '',
       ]"
     >
-      <div class="px-3.5 py-2.5" :class="[transparent ? '' : 'bg-white']">
+      <div class="px-3.5 py-2.5" :class="[transparent ? '' : 'bg-white', customClass]">
         <div class=""><slot></slot></div>
       </div>
       <div

+ 8 - 0
packages/app/src/components/card.vue

@@ -10,6 +10,14 @@ defineProps({
   },
 })
 </script>
+<script lang="ts">
+export default {
+  options: {
+    virtualHost: true,
+    styleIsolation: 'shared',
+  },
+}
+</script>
 <template>
   <view
     class="rounded-2xl bg-white shadow-[0_16rpx_20rpx_-10rpx_rgba(0,0,0,0.05)] p-3.5 overflow-hidden box-border"

+ 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>

+ 7 - 0
packages/app/src/components/hot-activity-item.vue

@@ -39,6 +39,13 @@ const { listItemButtonText, statusText, status, difference, startAt, endAt, refr
           mode="scaleToFill"
         />
         <div
+          class=" px-2 h-4 bg-black/60 rounded-[20px] backdrop-blur-[15px] absolute top-2.5 left-[7px] flex items-center justify-center"
+        >
+          <div class="text-white text-[9px] font-normal font-['PingFang_SC']">
+            {{statusText}}
+          </div>
+        </div>
+        <div
           class="w-[202px] left-[119px] top-0 absolute text-black text-base font-normal font-['PingFang_SC'] leading-relaxed"
         >
           <!-- 活动预告 | 日本研学·东京艺术大学设计游学 -->

+ 1 - 1
packages/app/src/components/hot-activity.vue

@@ -26,7 +26,7 @@ onMounted(() => {
     </view>
     <div class="absolute left-0 right-0 top-1 bottom-6 z-1 py-3.5">
       <div class="h-full w-full relative">
-        <swiper class="" autoplay @change="(e) => (current = e.detail.current)">
+        <swiper class="" :autoplay="true" @change="(e) => (current = e.detail.current)">
           <template v-for="it of items" :key="it.id">
             <swiper-item class="">
               <HotActivityItem ref="item" :options="it.data" :type="it.type"></HotActivityItem>

+ 1 - 1
packages/app/src/components/image-evo.vue

@@ -6,7 +6,7 @@ const imgClass = ref('blur-lg transition duration-500 hover:blur-none')
 const handleLoad = async () => {
   visible.value = true
   imgClass.value = 'transition duration-500'
-  nextTick(() => {
+  await nextTick(() => {
     emits('displayed')
   })
 }

+ 76 - 0
packages/app/src/components/list-helper-evo.vue

@@ -0,0 +1,76 @@
+<script setup lang="ts" generic="T extends AnyObject">
+import { UnwrapRef } from 'vue'
+
+const props = withDefaults(
+  defineProps<{
+    request: (query: Partial<T>) => Promise<IResData<T[]>>
+    query?: Partial<T>
+    automatic?: boolean
+    mockList?: Partial<T>[]
+  }>(),
+  {
+    automatic: true,
+    query: () => ({}),
+  },
+)
+const slot = defineSlots<{
+  default(props: { item: UnwrapRef<T>; index: number; isLast: boolean }): any
+}>()
+const { data, run: setData } = useRequest(() => props.request({ ...props.query }), {
+  immediate: false,
+})
+onMounted(async () => {
+  if (props.mockList) {
+    data.value = props.mockList as T[]
+    return
+  }
+  if (props.automatic) {
+    await setData()
+  }
+})
+watch(
+  () => props.query,
+  async () => {
+    if (props.mockList) {
+      data.value = props.mockList as T[]
+      return
+    }
+    await setData()
+  },
+)
+defineExpose({
+  reload: async () => {
+    await setData()
+  },
+})
+</script>
+
+<template>
+  <div class="flex-grow flex flex-col relative">
+    <template v-for="(it, index) in data" :key="index">
+      <slot :item="it as UnwrapRef<T>" :index="index" :isLast="index == data.length - 1"></slot>
+    </template>
+    <div
+      class="construction-dashed absolute top-0 right-0 left-0 bottom-0 bg-red/20 flex items-center justify-center pointer-events-none"
+    >
+      <div class="text-16 text-black/30">Debug</div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+@layer utilities {
+  .construction-dashed {
+    @apply relative border-4 border-black/50 border-dashed; /* 基础虚线样式 */
+    animation: dashed-move 2s linear infinite; /* 虚线移动动画 */
+  }
+}
+@keyframes dashed-move {
+  from {
+    border-spacing: 0;
+  }
+  to {
+    border-spacing: 10px;
+  }
+}
+</style>

+ 44 - 22
packages/app/src/components/moment-item.vue

@@ -19,10 +19,15 @@ const props = withDefaults(
   defineProps<{
     options: CircleRes
     isOwn?: boolean
+    isShared?: boolean
   }>(),
   {},
 )
-const emits = defineEmits<{ delete: [id: number]; like: [options: any] }>()
+const emits = defineEmits<{
+  delete: [id: number]
+  like: [options: any]
+  share: [options: CircleRes]
+}>()
 const router = useRouter()
 const { features, clickByPermission } = usePermissions()
 const memberLevelsStore = useMemberLevelsStore()
@@ -30,10 +35,13 @@ const { getMemberLevelLogo } = memberLevelsStore
 const dictStore = useDictStore()
 const { getOptionLabel } = dictStore
 const imgClass = ref('')
-const isVideo = ref(false)
+// const isVideo = ref(false)
+const isVideo = computed(
+  () => props.options.bannerUrls?.length && isImageOrVideo(props.options.bannerUrls[0]) === 'video',
+)
 const toDetail = () => {
   uni.navigateTo({
-    url: `/pages/home/moment/index?${stringify({ id: props.options.id })}`,
+    url: `/pages/home/moment/index?${stringify({ id: props.options.id })}${props.isShared ? '&isShared=true' : ''}`,
   })
 }
 const handleDelete = async () => {
@@ -55,12 +63,12 @@ onMounted(async () => {
       imgClass.value = 'w-[44vw]'
     }
   }
-  if (
-    props.options.bannerUrls?.length === 1 &&
-    isImageOrVideo(props.options.bannerUrls[0]) === 'video'
-  ) {
-    isVideo.value = true
-  }
+  // if (
+  //   props.options.bannerUrls?.length === 1 &&
+  //   isImageOrVideo(props.options.bannerUrls[0]) === 'video'
+  // ) {
+  //   isVideo.value = true
+  // }
 })
 </script>
 <template>
@@ -71,9 +79,9 @@ onMounted(async () => {
           class="overflow-hidden rounded-full"
           @click.stop="
             features.toDesignerHomePage &&
-              ['1', '2'].includes(options?.circleType) &&
-              currRoute().path !== '/pages/mine/homepage/index' &&
-              router.push(`/pages/mine/homepage/index?id=${options.stylistId}`)
+            ['1', '2'].includes(options?.circleType) &&
+            currRoute().path !== '/pages/mine/homepage/index' &&
+            router.push(`/pages/mine/homepage/index?id=${options.stylistId}`)
           "
         >
           <wd-img
@@ -93,7 +101,9 @@ onMounted(async () => {
           ></wd-img>
         </template>
         <view class="flex-1"></view>
-        <view>{{ beforeNow(dayjs(props.options.createTime).toDate()) }}</view>
+        <div class="text-black/40 text-sm font-medium font-['PingFang_SC'] leading-[10.18px]">
+          {{ beforeNow(dayjs(props.options.createTime).toDate()) }}
+        </div>
       </view>
       <div v-if="isVideo" class="aspect-[1.64/1] rounded-lg overflow-hidden my-6" @click.stop>
         <video class="w-full h-full" :src="options.bannerUrls[0]"></video>
@@ -157,28 +167,40 @@ onMounted(async () => {
         <!-- {{ options.spaceAddr }}·{{ options.spaceType }}·{{ options.designStyle }} -->
         {{ options.circleDesc }}
       </div>
-      <view class="my-5.5 flex flex-wrap gap-3.5">
+      <view v-if="props.options.tagName !== ''" class="my-5.5 flex flex-wrap gap-3.5">
         <template v-if="props.options.tagName !== ''">
           <template v-for="it of props.options.tagName?.split(',')" :key="it">
             <Tag>{{ it }}</Tag>
           </template>
         </template>
       </view>
-      <view class="flex justify-between">
+
+      <view v-if="!isShared" class="flex justify-between">
         <div>
-          <button
-            open-type="share"
-            class="bg-transparent! p-0!"
-            :data-options="options"
-            @click.stop
-          >
+          <template v-if="features.shareMoment">
+            <button
+              open-type="share"
+              class="bg-transparent! p-0!"
+              :data-options="options"
+              @click.stop
+            >
+              <view
+                class="flex items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
+              >
+                <wd-img width="15" height="15" src="/static/svgs/share.svg"></wd-img>
+                <view class="ml-1">{{ props.options.shareCount }}</view>
+              </view>
+            </button>
+          </template>
+          <template v-else>
             <view
+              @click.stop="clickByPermission('share', () => {})"
               class="flex items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
             >
               <wd-img width="15" height="15" src="/static/svgs/share.svg"></wd-img>
               <view class="ml-1">{{ props.options.shareCount }}</view>
             </view>
-          </button>
+          </template>
         </div>
         <view class="flex items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5">
           <wd-img width="15" height="15" src="/static/svgs/comment.svg"></wd-img>

+ 3 - 2
packages/app/src/components/page-helper-evo.vue

@@ -8,8 +8,9 @@ const props = withDefaults(
     request: (query: any) => Promise<IResData<R>>
     query?: Partial<Q> & { [key: symbol]: any }
     automatic?: boolean
+    isLoadMore?: boolean
   }>(),
-  { automatic: true, query: () => ({}) },
+  { automatic: true, isLoadMore: true, query: () => ({}) },
 )
 const slot = defineSlots<{
   default(props: { data?: R['list']; items?: T[]; source?: R }): any
@@ -56,7 +57,7 @@ watch(
 )
 onReachBottom(async () => {
   console.log('Page Helper Reach Bottom')
-
+  if (!props.isLoadMore) return
   if (data.value?.list?.length < pageSize.value) {
     return (nomore.value = true)
   }

+ 7 - 4
packages/app/src/components/section-heading.vue

@@ -38,7 +38,7 @@ const props = defineProps({
   dark: {
     type: Boolean,
     default: false,
-  }
+  },
 })
 const handleMore = async () => {
   props.path && (await uni.navigateTo({ url: props.path }))
@@ -56,9 +56,12 @@ const handleMore = async () => {
         `text-${size}`,
         (dark
           ? { sm: 'text-white', base: 'text-white', lg: 'text-white', xl: 'text-white' }
-          : { sm: 'text-black/90', base: 'text-black/90', lg: 'text-white', xl: 'text-black' })[
-          size
-        ],
+          : {
+              sm: 'text-black/90',
+              base: 'text-black/90',
+              lg: 'text-white',
+              xl: 'text-black font-bold',
+            })[size],
       ]"
     >
       {{ title }}

+ 2 - 1
packages/app/src/components/upload-evo.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 // defineProps<>()
 const modelValue = defineModel({ type: String, default: '' })
-const props = defineProps<{ multiple?: boolean; limit?: number }>()
+const props = defineProps<{ multiple?: boolean; limit?: number; disabled?: boolean }>()
 const fileList = ref([])
 const action = ref(`${import.meta.env.VITE_SERVER_BASEURL}/app-api/infra/file/upload`)
 const fileUrls = computed(() => fileList.value.map(({ response }) => JSON.parse(response).data))
@@ -30,6 +30,7 @@ onMounted(() => {
 </script>
 <template>
   <wd-upload
+    :disabled="disabled"
     :file-list="fileList"
     image-mode="aspectFill"
     :action="action"

+ 3 - 2
packages/app/src/composables/activity.ts

@@ -8,7 +8,7 @@ import { storeToRefs } from 'pinia'
 /**
  * 游学活动
  */
-export const useActivity = (options: globalThis.Ref<Partial<Activity | StudyTour>>) => {
+export const useActivity = (options: globalThis.Ref<Partial<Activity> | Partial<StudyTour>>) => {
   const userStore = useUserStore()
   const { userInfo } = storeToRefs(userStore)
   const applyStartAt = computed(
@@ -47,9 +47,10 @@ export const useActivity = (options: globalThis.Ref<Partial<Activity | StudyTour
       return 'overdue'
     }
   }
+  // 报名未开始、报名中、报名已结束、活动进行中、活动已结束
   const getActivityStatusText = () =>
     ({
-      waiting: '未开始',
+      waiting: '报名未开始',
       registering: '报名中',
       closed: '报名已结束',
       running: '活动进行中',

+ 75 - 0
packages/app/src/composables/analysis.ts

@@ -0,0 +1,75 @@
+import { createBrowseHistory, createBrowseRecord } from '@/core/libs/requests'
+import { useUserStore } from '@/store'
+import { storeToRefs } from 'pinia'
+import dayjs from 'dayjs'
+
+export enum AnalysisEventType {
+  LaunchApp = 4,
+  /**
+   * 发布圈子
+   */
+  PublishCircle = 5,
+  /**
+   * 浏览页面
+   */
+  ViewPage = 3,
+}
+export const useAnalysis = (automatic: boolean, isApp = false) => {
+  const userStore = useUserStore()
+  const { userInfo, isLogined } = storeToRefs(userStore)
+  const viewDuration = ref(0)
+  const viewStartAt = ref<Date>()
+  const option = ref<Record<string, any>>({})
+
+  const report = async (type) => {
+    if (!isLogined.value) return
+    const duration = automatic
+      ? (viewDuration.value = dayjs().diff(viewStartAt.value, 'second'))
+      : 0
+
+    await createBrowseRecord({
+      stylistId: userInfo.value.userId,
+      bizType: type,
+      duration,
+      ...option.value,
+    })
+    viewDuration.value = 0
+    viewStartAt.value = undefined
+    option.value = {}
+  }
+
+  onLaunch(async () => {
+    // if (automatic && isLogined) {
+    //   await createBrowseRecord({
+    //     stylistId: userInfo.value.userId,
+    //     bizType: AnalysisEventType.LaunchApp,
+    //     duration: 1,
+    //   })
+    // }
+  })
+  onLoad(() => {
+    console.log('analysis')
+  })
+  onShow(() => {
+    console.log('show')
+    if (automatic) {
+      viewStartAt.value = new Date()
+      // if (isApp) {
+      //
+      // }
+    }
+  })
+  onHide(async () => {
+    if (automatic) {
+      if (isApp) {
+        await report(AnalysisEventType.LaunchApp)
+      }
+    }
+  })
+  onUnload(async () => {
+    if (automatic) {
+      await report(AnalysisEventType.ViewPage)
+    }
+  })
+  return { option, report }
+}

+ 0 - 20
packages/app/src/composables/honor-dialog.ts

@@ -1,20 +0,0 @@
-import { inject, InjectionKey } from 'vue'
-export interface DialogShowOptions {
-  title: string
-  content: string
-  path: string
-  image: string
-}
-export const HonorDialogSymbol: InjectionKey<{
-  show: (options: DialogShowOptions) => void
-}> = Symbol.for('HonorDialogContext')
-export const useHonorDialog = () => {
-  const honorDialog = inject(HonorDialogSymbol)
-  // if (!honorDialog) {
-  //   throw new Error('useHonorDialog must be used inside setup()')
-  // }
-  const show = computed(() => honorDialog?.show)
-  return {
-    show,
-  }
-}

+ 24 - 6
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 },
@@ -61,10 +61,11 @@ export const usePermissions = () => {
     /**
      * 1分钟了解筑巢荟
      */
-    about: isDesigner.value,
+    about: true,
     toDesignerHomePage: isLogined.value,
     checkInAtStoreTask: isDesigner.value,
     shareMoment: isLogined.value && isDesigner.value && userInfo.value.level.level > 1,
+    personalCode: isLogined.value && isDesigner.value && userInfo.value.level.level > 1,
     /**
      * 微信代运营兑换
      */
@@ -82,7 +83,16 @@ export const usePermissions = () => {
    * 按钮操作权限
    */
   const clickByPermission = (
-    name: 'wechatAgentExchange' | 'caseExchange' | 'mallExchange' | 'thumbsUp' | 'exchange',
+    name:
+      | 'wechatAgentExchange'
+      | 'caseExchange'
+      | 'mallExchange'
+      | 'thumbsUp'
+      | 'exchange'
+      | 'share'
+      | 'scan'
+      | 'download'
+      | 'task',
     callback: () => void,
   ) => {
     const features = [
@@ -96,6 +106,10 @@ export const usePermissions = () => {
        * 活动游学兑换
        */
       { name: 'exchange', meta: { canNotLogin: false, canNotDesigner: false } },
+      { name: 'share', meta: { canNotLogin: false, canNotDesigner: false, minLevel: 2 } },
+      { name: 'scan', meta: { canNotLogin: false, canNotDesigner: false, minLevel: 2 } },
+      { name: 'task', meta: { canNotLogin: false, canNotDesigner: false } },
+      { name: 'download', meta: { canNotLogin: false, canNotDesigner: false } },
     ]
     const feature = features.find((item) => item.name === name)
     if (feature) {
@@ -109,6 +123,10 @@ export const usePermissions = () => {
         useRouter().push('/pages/mine/authentication/index')
         return
       }
+      if (feature.meta.minLevel && userInfo.value.level.level < feature.meta.minLevel) {
+        uni.showToast({ title: messages.components.toast.levelNotEnough, icon: 'none' }).then()
+        return
+      }
     }
     callback()
   }

+ 55 - 0
packages/app/src/composables/share.ts

@@ -0,0 +1,55 @@
+import { shareCircle, shareDesignerHome } from '@/core/libs/requests'
+import { messages } from '@/core/libs/messages'
+
+export const useShare = () => {
+  const shareAppMessage = async ({ from, target }) => {
+    // const res: Page.CustomShareContent = {}
+    // if (from === 'button') {
+    //   console.log(target)
+    //
+    //   if (target.dataset.type === 'homepage') {
+    //     res.title = `${userInfo.value.nickname}: “${designerInfo.value.designDesc}”`
+    //     res.imageUrl = designerInfo.value?.sharePageUrl
+    //     res.path = `/pages/mine/homepage/index?id=${id.value}&isShared=true`
+    //   } else {
+    //     await shareCircle(target.dataset.options.id)
+    //     res.path = `/pages/home/moment/index?id=${target.dataset.options.id}&isShared=true`
+    //     res.imageUrl = target.dataset.options.bannerUrls[0]
+    //     res.title = `${target.dataset.options.stylistName}: ${target.dataset.options.circleDesc}`
+    //   }
+    // }
+    // if (from === 'menu') {
+    //   res.title = `${userInfo.value.nickname}: “${designerInfo.value.designDesc}”`
+    //   res.imageUrl = designerInfo.value?.sharePageUrl
+    //   res.path = `/pages/mine/homepage/index?id=${id.value}&isShared=true`
+    // }
+    // return res
+
+    console.log('from', from)
+    console.log('target', target)
+    const res: Page.CustomShareContent = {}
+    if (from === 'button') {
+      if (target.dataset.type === 'homepage') {
+        await shareDesignerHome({
+          stylistId: target.dataset.options.homepageId,
+          bizId: target.dataset.options.userId,
+        })
+        res.title = target.dataset.shareContent.title
+        res.imageUrl = target.dataset.shareContent.imageUrl
+        res.path = target.dataset.shareContent.path
+      } else {
+        if (['1', '2'].includes(target.dataset.options.circleType)) {
+          await shareCircle(target.dataset.options.id)
+        }
+        res.path = `/pages/home/moment/index?id=${target.dataset.options.id}&isShared=true`
+        res.imageUrl = target.dataset.options.bannerUrls[0]
+        res.title = `${target.dataset.options.stylistName}: ${target.dataset.options.circleDesc}`
+      }
+    }
+    if (from === 'menu') {
+      res.title = messages.home.shareTitle
+    }
+    return res
+  }
+  return { shareAppMessage }
+}

+ 1 - 0
packages/app/src/core/libs/actions.ts

@@ -5,6 +5,7 @@ import { useRouter } from '../utils/router'
 import { Coupon } from './models'
 import { useMessage } from 'wot-design-uni'
 import { MessageOptions, MessageResult } from 'wot-design-uni/components/wd-message-box/types'
+import {AgreementType} from "@/core/libs/enums";
 
 const toast = (title: string) => uni.showToast({ title, icon: 'none' })
 export const handleUpvoteClick = async (

+ 29 - 0
packages/app/src/core/libs/enums.ts

@@ -49,3 +49,32 @@ export enum PointStatus {
    */
   Revoked = 4,
 }
+// TYPE_1(1, "个人微信视频号授权使用协议"),
+//
+//   TYPE_2(2, "筑巢荟服务使用协议"),
+//   TYPE_3(3, "筑巢荟用户注册协议"),
+//   TYPE_4(4, "筑巢荟用户隐私协议"),
+//   TYPE_5(5, "荟务服务使用协议"),
+
+export enum AgreementType {
+  /**
+   * 个人微信视频号授权使用协议
+   */
+  PersonalWeChatVideoAuthorization = 1,
+  /**
+   * 筑巢荟服务使用协议
+   */
+  ZCHServiceAgreement = 2,
+  /**
+   * 筑巢荟用户注册协议
+   */
+  ZCHUserRegistrationAgreement = 3,
+  /**
+   * 筑巢荟用户隐私协议
+   */
+  ZCHUserPrivacyAgreement = 4,
+  /**
+   * 荟务服务使用协议
+   */
+  HuiWuServiceAgreement = 5,
+}

+ 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' },
   {

+ 1 - 0
packages/app/src/core/libs/messages.ts

@@ -18,6 +18,7 @@ export const messages = {
     toast: {
       pleaseLogin: '请先登录',
       pleaseAuthentication: '请先完成认证',
+      levelNotEnough: '等级不足',
     },
   },
 }

+ 338 - 1
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
@@ -323,6 +327,12 @@ export interface Activity {
    * 类型补充字段
    */
   studyTravelList?: Schedule[]
+  surplus?: number
+  studyAllowType?: string
+  studyAllowCount?: number
+  planStudyEndTime?: string
+  planStudyStartTime?: string
+  sort?: number
 }
 export interface StudyTour {
   id: number
@@ -371,12 +381,15 @@ export interface StudyTour {
    * 剩余名额
    */
   surplus: number
+  sort: number
   /**
    * 补充字段
    */
   activityStartTime?: any
   activityEndTime?: any
   activityAddr?: any
+  activityAllowType?: string
+  activityAllowCount?: number
 }
 /**
  * 游学/活动的报名信息
@@ -405,6 +418,7 @@ export interface ActivitySignUp {
    * 活动ID
    */
   activityId?: number
+  avatar?: string
 }
 export interface MyStudyTour {
   id: number
@@ -494,6 +508,109 @@ export interface Banner {
   designDesc: any
   createTime: number
 }
+export interface Product {
+  /* id */
+  id: number
+
+  /* 商品名称 */
+  prodcutName: string
+
+  /* 商品id */
+  productId: string
+
+  /* 商品一级类目 */
+  oneCategory: string
+
+  /* 商品一级类目 */
+  oneCategoryName: string
+
+  /* 商品二级类目 */
+  secondCategory: string
+
+  /* 商品二级类目 */
+  secondCategoryName: string
+
+  /* 商品数量是否限制 0:否 1:是 */
+  isRestrict: number
+
+  /* 商品市场价 */
+  productPrice: string
+
+  /* 商品类别 */
+  productType: number
+
+  /* 商品库存 */
+  productRepertory: number
+
+  /* 供应商id */
+  vendorId: number
+
+  /* 供应商名称 */
+  vendorName: string
+
+  /* 供应商状态,可用值:0,1 */
+  vendorStatus: string
+
+  /* 是否需要积分 0:否 1:是 */
+  needPoints: number
+
+  /* 积分大小 */
+  points: number
+
+  /* 获取方式 1:到店核销 3:其他 */
+  gainType: number
+
+  /* 兑换说明 */
+  exchangeDesc: string
+
+  /* 商品封面地址 */
+  productCoverImgUrl: string
+
+  /* 商品详情地址 */
+  productDetailsImgUrl: string
+
+  /* 图文详情 */
+  contentDesc: string
+
+  /* 状态(0正常 1停用) */
+  status: number
+
+  /* */
+  statusStr: string
+
+  /* 兑换量 */
+  exchangeCount: number
+
+  /* 会员等级ids */
+  memberLevelIds: string
+
+  /* 会员等级名称 */
+  memberLevelName: string
+
+  /* 超值划算积分 */
+  favourablePoints: number
+
+  /* 是否展示超值划算 */
+  showFavourable: boolean
+
+  /* 超值划算截止时间 */
+  favourableEndDate: Record<string, unknown>
+
+  /* 是否限购 */
+  purchaseLimit: boolean
+
+  /* 限购数量 */
+  purchaseQuantity: number
+
+  /* 创建时间 */
+  createTime: Record<string, unknown>
+
+  /* 更新时间 */
+  updateTime: Record<string, unknown>
+
+  /* 排序 */
+  sort: number
+}
 export interface PointsOrder {
   id: number
   orderType: number
@@ -536,6 +653,14 @@ export interface PointsOrder {
    * 获取方式: 1:到店核销 3:其他
    */
   gainType?: number
+  couponList?: {
+    materialId: number
+    couponImgUrl: any
+    materialsName: string
+    validityStartDate: number
+    validityEndDate: number
+    brandPoints: number
+  }[]
 }
 export interface UserBasicInfo {
   id: number
@@ -629,6 +754,37 @@ 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 enum CouponStatus {
+  //   HAVE_USE(1, "已使用"),
+  //   NOT_USE(2, "未使用"),
+  //   EXPIRED(3, "已过期"),
+  //   BECAME_INVALID(4, "已失效"),
+  // ;
+  HAVE_USE = 1,
+  NOT_USE = 2,
+  EXPIRED = 3,
+  BECAME_INVALID = 4,
+}
 export interface Coupon {
   id: number
   couponId: number
@@ -664,6 +820,7 @@ export interface Coupon {
   materialName: string
   couponImgUrl: string
   couponDO?: { couponDesc?: string }
+  couponStatus: CouponStatus
 }
 export interface PointsDetail {
   id: number
@@ -725,6 +882,7 @@ export interface Agent {
   yearOrders: any
   createTime: number
   inviteCode: string
+  inviteCodeUrl: string
   customer: any
   customerName: string
   points: number
@@ -742,6 +900,10 @@ export interface Designer {
   expendPoints60Days: boolean
   focus: boolean
   brokerId: number
+  /**
+   * 0 弱绑定 1 强绑定
+   */
+  retryStatus?: 0 | 1
 }
 export interface DesignerBasicInfo {
   id: number
@@ -807,9 +969,87 @@ export interface DesignerFamilyInfo {
   familyOccupation: string
   createTime: string
 }
+
+/**
+ * 设计师奖项
+ */
+export interface DesignerAward {
+  id: number
+  userId: number
+  awardsName: string
+  awardsTime: string
+  awardsRank: string
+  awardsFileUrl: string
+  createTime: string
+}
 export interface DesignerEvent {
   name: string
-  applyTime: string
+  applyTime?: string
+  userId?: number
+  participationDate: string
+  id?: number
+}
+export interface DesignerPointsStatistics {
+  /**
+   * 剩余积分
+   */
+  points: number
+  /**
+   * 积分变动时间
+   */
+  pointsTime: string
+  /**
+   * 累计获得积分
+   */
+  gainPoints: number
+  /**
+   * 今年获得积分
+   */
+  gainPointsYear: number
+  /**
+   * 今年消耗积分
+   */
+  usePoints: number
+  /**
+   * 消耗积分
+   */
+  usePointsYear: number
+  /**
+   * 累计跟进次数
+   */
+  followUpCount: number
+  /**
+   * 今年跟进次数
+   */
+  followUpYearCount: number
+  /**
+   * 消耗次数
+   */
+  usageCount: number
+  /**
+   * 今年消耗次数
+   */
+  usageYearCount: number
+  /**
+   * 累计获得次数
+   */
+  obtainedCount: number
+  /**
+   * 今年获得次数
+   */
+  obtainedYearCount: number
+}
+export interface DesignerOrderSaleOther {
+  id: number
+  supplierName: string
+  brandName: string
+  saleTime: string
+  projectName: string
+  customerName: string
+  customerPhone: string
+  orderAmount: number
+  createTime: string
+  userId: number
 }
 export interface Broker {
   createTime: string
@@ -896,6 +1136,13 @@ export interface AgentTask {
   finalTypeName: string
   roleTypeName: string
   statusName: string
+  qrList?: {
+    avatar: string
+    createTime: number
+    id: number
+    name: string
+    remark: string
+  }[]
 }
 export interface FollowUp {
   id: number
@@ -929,6 +1176,89 @@ export interface AgentPoint {
   remark: string
   createTime: string
 }
+export interface BrowseRecord {
+  /* */
+  createTime: Record<string, unknown>
+
+  /* */
+  updateTime: Record<string, unknown>
+
+  /* */
+  creator: string
+
+  /* */
+  updater: string
+
+  /* */
+  deleted: boolean
+
+  /* id */
+  id: number
+
+  /* 设计师id */
+  stylistId: number
+
+  /* 设计师名称 */
+  stylistName: string
+
+  /* 业务id */
+  bizId: string
+
+  /* 业务类型:分享-1,获客-2,浏览-3等具体查看BrowseTypeEnum类,可用值:1,2,3,4,5 */
+  bizType: string
+
+  /* 分享类型,可用值:1,2 */
+  shareType: string
+
+  /* 时长:单位-秒 */
+  duration: string
+
+  /* 创建人 */
+  creatorName: string
+
+  /* 备注 */
+  remark: string
+}
+/**
+ * 设计师动态统计
+ */
+export interface BrowseRecordCountRes {
+  /* 打开次数 */
+  openNumber: number
+
+  /* 最近打开时间 */
+  openTime: string
+
+  /* 浏览时间 */
+  duration: number
+
+  /* 本年浏览时间 */
+  durationYear: number
+
+  /* 发圈次数 */
+  circleNumber: number
+
+  /* 本年发圈次数 */
+  circleNumberYear: number
+
+  /* 主页分享 */
+  homeShareNumber: number
+
+  /* 本年分享 */
+  homeShareNumberYear: number
+
+  /* 分享浏览数 */
+  shareViewNumber: number
+
+  /* 本年分享浏览数 */
+  shareViewNumberYear: number
+
+  /* 分享获客数 */
+  customersAcquired: number
+
+  /* 本年分享获客数 */
+  customersAcquiredYear: number
+}
 
 export enum DictType {
   /**
@@ -971,11 +1301,16 @@ export enum DictType {
    * 活动类型
    */
   MemberActivityType = 'member_activity_type',
+  /**
+   * 渠道类型
+   */
+  MemberChannelType = 'member_channel_type',
 }
 /**
  * 徽章
  */
 export interface Badge {
+  id: number
   userId: number
   quantity: number
   badgeId: number
@@ -984,6 +1319,7 @@ export interface Badge {
   badgeNotObtainedImage: string
   badgeYesObtainedImage: string
   badgeDescription: string
+  popUp: boolean
 }
 /**
  * 证书
@@ -997,6 +1333,7 @@ export interface Certificate {
   certificateImage: string
   certificateDescription: string
   createTime: string
+  popUp: boolean
 }
 export enum CircleType {
   moment = '1',

+ 5 - 1
packages/app/src/core/libs/net-images.ts

@@ -2,7 +2,7 @@ export enum NetImages {
   'default' = 'https://cdn.jsdelivr.net/gh/yangyang-yangyang/yangyang-yangyang.github.io@master/images/default.png',
   'avatar' = 'https://cdn.jsdelivr.net/gh/yangyang-yangyang/yangyang-yangyang.github.io@master/images/avatar.png',
   'NotContent' = 'https://image.zhuchaohui.com/zhucaohui/3819d411440c23cc9e4f4bd3a520325386d7f038ed6dfa7c2ba076bd5110d2d2.png',
-  'DesigerHomepageDefaultBg' = 'https://image.zhuchaohui.com/zhucaohui/58dcb982d2957c5578478abbf000936efe9d11c96c5af4d457177cf5d90a9d39.png',
+  DesignerHomepageDefaultBg = 'https://image.zhuchaohui.com/zhucaohui/c706ec14a5a927c10e9e1fa0153affb11bbdd9255882e18c67ee82687ff9813a.png',
   ConsultDefaultBg = 'https://image.zhuchaohui.com/zhucaohui/7e98b5995902cd9484a6baecfc1219420cbcc30d8ae11e058af0c140a3c11137.png',
   StudyTourItemBg = 'https://image.zhuchaohui.com/zhucaohui/254b7ea7fdecaba8e318a7f50e292d036cafe49904fc7fc160a5477ce921e261.png',
   DefaultAvatar = 'https://image.zhuchaohui.com/zhucaohui/0b57771c2fbe60157e592a5b0e51a2b2b6c5263300663ad33efd55b235a2402a.png',
@@ -14,4 +14,8 @@ export enum NetImages {
   CyclingRankingsHeaderBg = 'https://image.zhuchaohui.com/zhucaohui/2351014a57a0df427516c4993876ade7e3695ce33b1f227c52c8381aa631ba02.png',
   HonorsLogo = 'https://image.zhuchaohui.com/zhucaohui/109805f7e6a8866e6484eea793de572e97cde85c5d65a2f514cf66aacc41609d.svg',
   WechatChannelsGuide = 'https://image.zhuchaohui.com/zhucaohui/4127d21eb75ab479b89f566f0d9479664d595091098acedf0ec29d696a60d034.png',
+  VideoTutorial = '',
+  RankingOne = 'https://image.zhuchaohui.com/zhucaohui/5743d060b96571f4540adf66b1f40ed8f0c313d721ce0932d06227b4e31e6a48.svg',
+  RankingTwo = 'https://image.zhuchaohui.com/zhucaohui/2709798b4445b57e349b8e244825885983bf634e2b7f3706f181912753e2d567.svg',
+  RankingThree = 'https://image.zhuchaohui.com/zhucaohui/d70fae2ba6688e0e82bfc1025f2005432de2dc1a1353eddb0da061245b37d970.svg',
 }

+ 69 - 72
packages/app/src/core/libs/requests.ts

@@ -22,8 +22,11 @@ import {
   Certificate,
   UserBasicInfo,
   ActivitySignUp,
+  Product,
+  ConfirmOrder,
 } from './models'
 import dayjs from 'dayjs'
+import { AgreementType } from '@/core/libs/enums'
 
 export const getUserInfo = () =>
   httpGetMock<any>({
@@ -200,6 +203,14 @@ export const getCircle = (id: string) =>
  */
 export const deleteCircle = (id: string) => httpDelete('/app-api/member/circle/delete', { id })
 export const shareCircle = (id: string) => httpGet('/app-api/member/circle/share', { id })
+/**
+ * /app-api/member/designer/shareHomeHistory 分享设计师主页
+ */
+export const shareDesignerHome = (data: {
+  stylistId: number
+  bizId: number
+  // shareType:
+}) => httpPost('/app-api/member/designer/shareHomeHistory', data)
 export const getCircleUpvotes = (id) =>
   httpGet<{
     total: number
@@ -342,6 +353,11 @@ export const getMaterialHomePage = (id: number) =>
   })
 export const getMaterialDetail = (query: { id: string }) =>
   httpGet<MaterialDealer>('/app-api/member/materials/getDetail', query)
+/**
+ * 下载材料商
+ */
+export const downloadMaterials = (query: { materialsId: string }) =>
+  httpGet('/app-api/member/materials/download', query)
 export const createMaterialsReferrer = (data) =>
   httpPost('/app-api/member/materials-referrer/create', data)
 export const getContents = (query: {
@@ -375,69 +391,9 @@ export const getProducts = (query: { oneCategory?: string; secondCategory?: stri
  * 获取超值划算商品
  */
 export const getFavourableProducts = () =>
-  httpPost<
-    {
-      id: number
-      prodcutName: string
-      productId: string
-      oneCategory: string
-      oneCategoryName: string
-      secondCategory: string
-      secondCategoryName: string
-      isRestrict: number
-      productRepertory: number
-      productPrice: string
-      productType: number
-      vendorId: number
-      vendorName: string
-      needPoints: number
-      points: number
-      gainType: number
-      exchangeDesc: string
-      productCoverImgUrl: string
-      productDetailsImgUrl: string
-      contentDesc: string
-      status: number
-      exchangeCount: number
-      memberLevelIds: string
-      memberLevelName: string
-      favourablePoints: number
-      favourableEndDate: string
-      createTime: string
-      updateTime: string
-    }[]
-  >('/app-api/member/product/listFavourableProduct')
+  httpPost<Product[]>('/app-api/member/product/listFavourableProduct')
 export const getProduct = (id: string) =>
-  httpGet<{
-    id: number
-    prodcutName: string
-    productId: string
-    oneCategory: any
-    oneCategoryName: any
-    secondCategory: string
-    secondCategoryName: any
-    isRestrict: number
-    productRepertory: any
-    productPrice: string
-    productType: number
-    vendorId: any
-    vendorName: string
-    needPoints: number
-    points: number
-    gainType: number
-    exchangeDesc: string
-    productCoverImgUrl: string
-    productDetailsImgUrl: string
-    contentDesc: string
-    status: number
-    exchangeCount: any
-    memberLevelId: any
-    memberLevelName: any
-    favourablePoints: any
-    favourableEndDate: any
-    createTime: number
-    updateTime: number
-  }>('/app-api/member/product/detail', { productId: id })
+  httpGet<Product>('/app-api/member/product/detail', { productId: id })
 export const getProductItemBuy = (query: { userId: number }) =>
   httpPost<{
     list: Partial<{
@@ -469,6 +425,7 @@ export const getProductItemBuy = (query: { userId: number }) =>
       createTime: any
       userId: number
       nums: number
+      deleted?: boolean
     }>[]
     total: number
   }>('/app-api/member/product-item-buy/select', query)
@@ -522,7 +479,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
@@ -544,6 +501,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: {
@@ -566,8 +543,12 @@ export const orderPay = (data: {
 /**
  * 积分结账
  */
-export const pointsPay = (data: { userId: number; points: number; vendorId: number }) =>
-  httpPost('/app-api/member/points-order/orderAndPaymentCompleted', data)
+export const pointsPay = (data: {
+  userId: number
+  points: number
+  vendorId: number
+  orderMoney?: number
+}) => httpPost('/app-api/member/points-order/orderAndPaymentCompleted', data)
 export const getPointsOrders = (query) =>
   httpGet<{
     list: PointsOrder[]
@@ -590,7 +571,7 @@ export const activitySignup = (data: { id: number }) =>
 /**
  * 获取活动报名列表
  */
-export const getActivitySignups = (query: { activityId: string }) =>
+export const getActivitySignups = (query: { activityId: string; pageSize?: number }) =>
   httpGet<ResPageData<ActivitySignUp>>('/app-api/member/activity/signup/page', query)
 /**
  * 获取游学列表
@@ -610,7 +591,7 @@ export const studyTourSignup = (data: { id: number }) =>
 /**
  * 获取游学报名列表
  */
-export const getStudyTourSignups = (query: { studyId: string }) =>
+export const getStudyTourSignups = (query: { studyId: string; pageSize?: number }) =>
   httpGet<ResPageData<ActivitySignUp>>('/app-api/member/app-study-abroad/signup/page', query)
 /**
  * 获取游学或活动的照片列表
@@ -792,6 +773,11 @@ export const createBrowseHistory = (data: {
    */
   duration?: string
 }) => httpPost('/app-api/member/designer/browseHistory', data)
+/**
+ * 浏览记录
+ */
+export const createBrowseRecord = (data: any) =>
+  httpPost('/app-api/member/browse-record/create', data)
 export const getBrowseHistories = (query) =>
   httpGet<
     ResPageData<{
@@ -922,28 +908,33 @@ export const getPointsCoupons = (query) =>
 export const getMyStudyTours = (query = {}) =>
   httpGet<MyStudyTour[]>('/app-api/member/app-study-abroad/getSignUpStudyAbroad', query)
 /**
+ * /app-api/member/stylist-honor/updatePopUp 更新弹窗
+ */
+export const updateHonorPopUp = (query: { bizId: string; bizType: string }) =>
+  httpGet('/app-api/member/stylist-honor/updatePopUp', query)
+/**
  * 获取徽章列表
  */
 export const getBadges = (query = {}) =>
-  httpGet<{ [key: string]: Badge[] }>('/app-api/member/stylist-honor/get-badge-list')
+  httpGet<{ [key: string]: Badge[] }>('/app-api/member/stylist-honor/get-badge-list', query)
 /**
  * 获取个人徽章
  */
 export const getOwnBadges = (query = {}) =>
-  httpGet<Badge[]>('/app-api/member/stylist-honor/getConferBadgeList')
+  httpGet<Badge[]>('/app-api/member/stylist-honor/getConferBadgeList', query)
 /**
  * 获取证书列表
  */
 export const getCertificates = (query = {}) =>
-  httpGet<Certificate[]>('/app-api/member/stylist-honor/get-certificate-list')
+  httpGet<Certificate[]>('/app-api/member/stylist-honor/get-certificate-list', query)
 /**
  * 获取荣誉统计
  */
-export const getHonorStatistics = () =>
+export const getHonorStatistics = (query = {}) =>
   httpGet<{
     certCount: number
     badgeCount: number
-  }>('/app-api/member/stylist-honor/get-honor-count')
+  }>('/app-api/member/stylist-honor/get-honor-count', query)
 /**
  * 获取骑行下拉
  */
@@ -988,6 +979,12 @@ export const getAgents = () =>
       customerName: string
     }[]
   >('/app-api/member/user/getCustomerServiceList')
+export const fakeThis = (query: { agreement: AgreementType }) =>
+  httpGet<string>('/app-api/infra/file/download', query)
+export const fuckYouMom = (query: { id: string }) =>
+  httpGet('/app-api/member/message-manage/get', query)
+export const fuckYou = (data: { id: number }) =>
+  httpPost('/app-api/member/message-manage/isRead', data)
 export const refreshToken = (refreshToken: string) =>
   httpPost<any>('/app-api/member/auth/refresh-token', {}, { refreshToken })
 export const httpGetMock = <T>(data: T) =>

+ 8 - 0
packages/app/src/core/themes/default.scss

@@ -2,6 +2,14 @@
 page {
   --evo-theme-primary: black;
 }
+
+::-webkit-scrollbar{
+  width: 0;
+  height: 0;
+  color: transparent;
+  display:none;
+}
+
 .wd-button {
   line-height: 0;
 }

+ 1 - 0
packages/app/src/core/themes/default.ts

@@ -20,4 +20,5 @@ export const defaultThemeVars: ConfigProviderThemeVars = {
   inputNumberBtnWidth: '44rpx',
   inputNumberHeight: '44rpx',
   inputNumberIconSize: '20rpx',
+  overlayBg: 'rgba(0, 0, 0, 0.8)',
 }

+ 21 - 9
packages/app/src/core/utils/canvas.ts

@@ -131,15 +131,27 @@ export class Canvas {
   }
 
   /**
-   * 绘制水平的多个不同样式的文本 例如:'文本1: 123, 文本2: 456'
+   * 绘制水平居中一行多个不同样式的文本 例如:'文本1: 123, 文本2: 456'
    */
-  FillTexts(texts, colors, fontSizes, designX, designY, designSpacing) {
-    let x = designX
-    for (let i = 0; i < texts.length; i++) {
-      this.context.setFillStyle(colors[i])
-      this.context.setFontSize(this.px(fontSizes[i]))
-      this.context.fillText(texts[i], this.px(x), this.px(designY))
-      x += designSpacing[i]
-    }
+  FillTexts(textSegments: { text: string; font: string; color: string }[], designY): void {
+    // Calculate total text width
+    let totalWidth = 0
+    textSegments.forEach((segment) => {
+      this.context.font = segment.font
+      totalWidth += this.context.measureText(segment.text).width
+    })
+
+    // Starting x position for centered text
+    let startX = (this.size.width - totalWidth) / 2
+    const y = this.px(designY) // Fixed vertical position
+
+    // Draw each text segment
+    textSegments.forEach((segment) => {
+      this.context.font = segment.font
+      this.context.fillStyle = segment.color
+
+      this.context.fillText(segment.text, startX, y)
+      startX += this.context.measureText(segment.text).width // Move x position for next segment
+    })
   }
 }

+ 81 - 7
packages/app/src/core/utils/common.ts

@@ -1,6 +1,18 @@
 import dayjs from 'dayjs'
+import {AgreementType} from "@/core/libs/enums";
 export const handleCall = (phone: string) => {
-  uni.makePhoneCall({ phoneNumber: phone })
+  uni.makePhoneCall({ phoneNumber: phone }).then()
+}
+/**
+ * 预览图片
+ */
+export const previewImage = (current: number, urls: string[]) => {
+  uni
+    .previewImage({
+      current,
+      urls,
+    })
+    .then()
 }
 /**
  * 打开位置
@@ -26,29 +38,41 @@ export const isImageOrVideo = (url) => {
     return 'unknown'
   }
 }
-export const toast = (msg: string, icon = 'none') => {
-  uni.showToast({ title: msg, icon })
+export const toast = (
+  msg: string,
+  icon: 'none' | 'success' | 'loading' | 'error' | 'fail' | 'exception' = 'none',
+) => {
+  uni.showToast({ title: msg, icon }).then()
 }
 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) => {
@@ -125,3 +149,53 @@ export const qrCodeString2Object = (str: string): Record<string, any> => {
   }
   return { type, options: obj }
 }
+export const validate = (
+  form,
+  rules: { [key: string]: { required?: boolean; message?: string; pattern?: RegExp }[] },
+) => {
+  for (const field in rules) {
+    for (const rule of rules[field]) {
+      if (rule.required && !form[field]) {
+        uni
+          .showToast({
+            title: rule.message,
+            icon: 'none',
+            duration: 2000,
+          })
+          .then()
+        return false
+      }
+      if (rule.pattern && !rule.pattern.test(form[field])) {
+        uni
+          .showToast({
+            title: rule.message,
+            icon: 'none',
+            duration: 2000,
+          })
+          .then()
+        return false
+      }
+    }
+  }
+  return true
+}
+
+// 浏览时长 小于60秒,显示秒 大于60秒小于1小时,显示分钟 大于1小时小于1天,显示x小时x分钟 大于1天,显示x天x小时x分钟
+export const formatDuration = (duration: number) => {
+  if (duration < 60) {
+    return `${duration}秒`
+  } else if (duration < 3600) {
+    return `${Math.floor(duration / 60)}分钟`
+  } else if (duration < 86400) {
+    return `${Math.floor(duration / 3600)}小时${Math.floor((duration % 3600) / 60)}分钟`
+  } else {
+    return `${Math.floor(duration / 86400)}天${Math.floor((duration % 86400) / 3600)}小时${Math.floor(
+      (duration % 3600) / 60,
+    )}分钟`
+  }
+}
+export const toContentHtml = async (option: { title: string; type: AgreementType }) => {
+  await uni.navigateTo({
+    url: `/pages/common/content-html/index?title=${option.title}&type=${option.type}`,
+  })
+}

+ 1 - 1
packages/app/src/core/utils/page-spy.ts

@@ -1,5 +1,5 @@
 import PageSpy from '@huolala-tech/page-spy-uniapp'
 export const pageSpy = new PageSpy({
-  api: 'page-spy.sxwobo.cn',
+  api: 'page-spy.sxwbny.com',
   title: '',
 })

+ 2 - 2
packages/app/src/core/utils/router.ts

@@ -29,8 +29,8 @@ export const useRouter = () => {
   const replace = async (path: string) => {
     uni.redirectTo({ url: path })
   }
-  const back = () => {
-    uni.navigateBack()
+  const back = (delta = 1) => {
+    uni.navigateBack({ delta }).then()
   }
   return { push, replace, back }
 }

+ 1 - 1
packages/app/src/layouts/tabbar.vue

@@ -15,7 +15,7 @@ import {
 import { currRoute } from '../utils'
 import { defaultThemeVars } from '../core/themes/default'
 import { useRouter } from '../core/utils/router'
-import HonorDialog from '@/pages/common/components/honor-dialog.vue'
+import HonorDialog from "@/pages/common/components/honor-dialog/honor-dialog.vue";
 
 const router = useRouter()
 const publishState = ref(false)

+ 1 - 1
packages/app/src/main.ts

@@ -14,7 +14,7 @@ export function createApp() {
   app.use(routeInterceptor)
   app.use(requestInterceptor)
   app.use(prototypeInterceptor)
-  pageSpy.init()
+  pageSpy.init().then()
   return {
     app,
   }

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

@@ -28,7 +28,7 @@ import calculatorBg from '@designer-hub/assets/src/libs/assets/calculatorBg'
     </div>
     <Card>
       <div
-        class="text-center text-[#ef4343] text-[40px] font-normal font-['D-DIN Exp'] leading-normal"
+        class="text-center text-[#ef4343] text-[40px] font-normal font-['D-DIN_Exp'] leading-normal"
       >
         16000
       </div>

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

@@ -141,6 +141,14 @@
       }
     },
     {
+      "path": "pages/common/content-html/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "",
+        "navigationBarBackgroundColor": "#fff"
+      }
+    },
+    {
       "path": "pages/common/webview/index",
       "type": "page"
     },
@@ -273,6 +281,14 @@
       }
     },
     {
+      "path": "pages/messages/detail/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "详情",
+        "navigationBarBackgroundColor": "#fff"
+      }
+    },
+    {
       "path": "pages/mine/agents/index",
       "type": "page",
       "style": {
@@ -514,6 +530,14 @@
       }
     },
     {
+      "path": "pages/mine/levels/rules/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "等级规则",
+        "navigationBarBackgroundColor": "#fff"
+      }
+    },
+    {
       "path": "pages/mine/orders/code/index",
       "type": "page",
       "style": {

+ 65 - 36
packages/app/src/pages/common/components/coupon-card.vue

@@ -1,8 +1,11 @@
 <script setup lang="ts">
 import Card from '@/components/card.vue'
-import { Coupon } from '../../../core/libs/models'
+import { Coupon, CouponStatus } from '../../../core/libs/models'
 import dayjs from 'dayjs'
-import { useMessage } from 'wot-design-uni'
+import used from '@designer-hub/assets/src/libs/assets/used'
+import expired from '@designer-hub/assets/src/libs/assets/expired'
+import invalid from '@designer-hub/assets/src/libs/assets/invalid'
+
 const props = withDefaults(
   defineProps<{ options?: Coupon; canSelect?: boolean; selected?: boolean }>(),
   {
@@ -14,45 +17,71 @@ const emits = defineEmits<{ select: [coupon: Coupon]; clickInstruction: [coupon:
 </script>
 <template>
   <Card custom-class="mx-3.5">
-    <div class="flex gap-3">
-      <div class="w-[94px] h-[94px] bg-[#f6f6f6] rounded-2.5 overflow-hidden">
-        <template v-if="options.couponType === 1">
-          <div class="bg-[#fff8f8] w-full h-full flex flex-col items-center justify-center">
-            <div class="text-[#ff7878] text-[26px] font-normal font-['PingFang_SC']">
-              {{ options.brandPoints }}
+    <div class="relative" @click="canSelect && emits('select', options)">
+      <div class="flex gap-3">
+        <div class="w-[94px] h-[94px] bg-[#f6f6f6] rounded-2.5 overflow-hidden">
+          <template v-if="options.couponType === 1">
+            <div class="bg-[#fff8f8] w-full h-full flex flex-col items-center justify-center">
+              <div class="text-[#ff7878] text-[26px] font-normal font-['PingFang_SC']">
+                {{ options.brandPoints }}
+              </div>
+              <div class="text-[#ff7878] text-base font-normal font-['PingFang_SC']">积分</div>
             </div>
-            <div class="text-[#ff7878] text-base font-normal font-['PingFang_SC']">积分</div>
-          </div>
-        </template>
-        <template v-else>
-          <wd-img width="100%" height="100%" :src="options.couponImgUrl"></wd-img>
-        </template>
-      </div>
-      <div class="flex-1 flex flex-col justify-around">
-        <div class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal">
-          <!-- GELATO咖啡兑换券 -->
-          {{ options.couponName }}
+          </template>
+          <template v-else>
+            <wd-img width="100%" height="100%" :src="options.couponImgUrl"></wd-img>
+          </template>
         </div>
-        <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
-          有效期:
-          <!-- 2024/04/01-2024/05/30 -->
-          {{ dayjs(options.validityStartDate).format('YYYY/MM/DD') }}-{{
-            dayjs(options.validityEndDate).format('YYYY/MM/DD')
-          }}
+        <div class="flex-1 flex flex-col justify-around">
+          <div class="flex items-center gap-1">
+            <div
+              class="px-[3px] rounded border border-solid border-[#ff3636] justify-center items-center gap-2.5 inline-flex"
+            >
+              <div
+                class="text-[#ff3e3e] text-[10px] font-normal font-['PingFang_SC'] leading-normal"
+              >
+                {{ options.materialName }}
+              </div>
+            </div>
+            <div class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal">
+              <!-- GELATO咖啡兑换券 -->
+              {{ options.couponName }}
+            </div>
+          </div>
+          <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
+            有效期:
+            <!-- 2024/04/01-2024/05/30 -->
+            {{ dayjs(options.validityStartDate).format('YYYY/MM/DD') }}-{{
+              dayjs(options.validityEndDate).format('YYYY/MM/DD')
+            }}
+          </div>
+          <div
+            class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal flex items-center"
+            @click.stop="emits('clickInstruction', options)"
+          >
+            使用说明
+            <wd-icon name="arrow-right" size="14"></wd-icon>
+          </div>
         </div>
-        <div
-          class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal flex items-center"
-          @click="emits('clickInstruction', options)"
-        >
-          使用说明
-          <wd-icon name="arrow-right" size="14"></wd-icon>
+        <div class="flex items-center" v-if="canSelect">
+          <div
+            class="w-4 h-4 rounded-full border border-black/60 border-solid"
+            :class="`${selected ? 'bg-black' : ''}`"
+          ></div>
         </div>
       </div>
-      <div class="flex items-center" v-if="canSelect" @click="emits('select', options)">
-        <div
-          class="w-4 h-4 rounded-full border border-black/60 border-solid"
-          :class="`${selected ? 'bg-black' : ''}`"
-        ></div>
+      <div v-if="options.couponStatus !== CouponStatus.NOT_USE" class="absolute top--4 right--4">
+        <wd-img
+          width="58"
+          height="58"
+          :src="
+            {
+              [CouponStatus.HAVE_USE]: used,
+              [CouponStatus.EXPIRED]: expired,
+              [CouponStatus.BECAME_INVALID]: invalid,
+            }[options.couponStatus]
+          "
+        ></wd-img>
       </div>
     </div>
   </Card>

+ 90 - 0
packages/app/src/pages/common/components/coupon-record.vue

@@ -0,0 +1,90 @@
+<script setup lang="ts">
+import Card from '@/components/card.vue'
+import dayjs from 'dayjs'
+import used from '@designer-hub/assets/src/libs/assets/used'
+import expired from '@designer-hub/assets/src/libs/assets/expired'
+import invalid from '@designer-hub/assets/src/libs/assets/invalid'
+import { CouponStatus } from '@/core/libs/models'
+
+const props = withDefaults(
+  defineProps<{
+    option?: {
+      materialId: number
+      couponImgUrl: any
+      materialsName: string
+      validityStartDate: number
+      validityEndDate: number
+      brandPoints: number
+    }
+    canSelect?: boolean
+    selected?: boolean
+  }>(),
+  {
+    canSelect: false,
+    selected: false,
+  },
+)
+</script>
+<template>
+  <div
+    class="mx-3.5 p-4 bg-[rgba(255,255,255,0.7)] rounded-2xl shadow-[0px_5px_8px_0px_rgba(21,3,3,0.03)]"
+  >
+    <div class="relative">
+      <div class="flex gap-3">
+        <div class="w-[68px] h-[68px] bg-[#f6f6f6] rounded-2.5 overflow-hidden">
+          <!--          <template v-if="option.couponType === 1">-->
+          <!--            <div class="bg-[#fff8f8] w-full h-full flex flex-col items-center justify-center">-->
+          <!--              <div class="text-[#ff7878] text-[26px] font-normal font-['PingFang_SC']">-->
+          <!--                {{ options.brandPoints }}-->
+          <!--              </div>-->
+          <!--              <div class="text-[#ff7878] text-base font-normal font-['PingFang_SC']">积分</div>-->
+          <!--            </div>-->
+          <!--          </template>-->
+          <!--          <template v-else>-->
+          <wd-img width="100%" height="100%" :src="option.couponImgUrl"></wd-img>
+          <!--          </template>-->
+        </div>
+        <div class="flex-1 flex flex-col justify-around">
+          <div class="flex items-center gap-1">
+            <div
+              class="px-[3px] rounded border border-solid border-[#ff3636] justify-center items-center gap-2.5 inline-flex"
+            >
+              <div
+                class="text-[#ff3e3e] text-[10px] font-normal font-['PingFang_SC'] leading-normal"
+              >
+                {{ option.materialsName }}
+              </div>
+            </div>
+            <div class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal">
+              <!-- GELATO咖啡兑换券 -->
+              {{ option.couponName }}
+            </div>
+          </div>
+          <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
+            <!--            有效期:-->
+            <!-- 2024/04/01-2024/05/30 -->
+            {{ dayjs(option.validityStartDate).format('YYYY/MM/DD') }}-{{
+              dayjs(option.validityEndDate).format('YYYY/MM/DD')
+            }}
+          </div>
+          <!--          <div-->
+          <!--            class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal flex items-center"-->
+          <!--            @click.stop="emits('clickInstruction', options)"-->
+          <!--          >-->
+          <!--            使用说明-->
+          <!--            <wd-icon name="arrow-right" size="14"></wd-icon>-->
+          <!--          </div>-->
+        </div>
+        <div class="flex items-center" v-if="canSelect">
+          <div
+            class="w-4 h-4 rounded-full border border-black/60 border-solid"
+            :class="`${selected ? 'bg-black' : ''}`"
+          ></div>
+        </div>
+      </div>
+      <div class="absolute top--4 right--4">
+        <wd-img width="58" height="58" :src="used"></wd-img>
+      </div>
+    </div>
+  </div>
+</template>

+ 17 - 10
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,
         })
     : () =>
@@ -50,8 +50,15 @@ const handleSelect = (coupon: Coupon) => {
   //   modelValue.value = modelValue.value.filter(({ id }) => id !== coupon.id)
   // } else {
   //   modelValue.value = [...modelValue.value, coupon]
-  // }
-  modelValue.value = [coupon]
+  if (modelValue.value.length) {
+    if (modelValue.value.map(({ id }) => id).includes(coupon.id)) {
+      modelValue.value = modelValue.value.filter(({ id }) => id !== coupon.id)
+    } else {
+      modelValue.value.splice(0, 1, coupon)
+    }
+  } else {
+    modelValue.value.push(coupon)
+  }
   emits('close')
 }
 const handleTabsChange = async ({ index, name }) => {
@@ -62,11 +69,7 @@ const handleTabsChange = async ({ index, name }) => {
 watch(
   () => props.show,
   async (val) => {
-    console.log(val)
-
     if (val) {
-      console.log(111111)
-
       await setCoupons()
     }
   },
@@ -74,11 +77,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">

+ 0 - 82
packages/app/src/pages/common/components/honor-dialog.vue

@@ -1,82 +0,0 @@
-<script setup lang="ts">
-import { close } from '../../../core/libs/svgs'
-import { DialogShowOptions, HonorDialogSymbol } from '../../../composables/honor-dialog'
-import earnBadgeTitle from '@designer-hub/assets/src/libs/assets/earnBadgeTitle'
-import radiation from '@designer-hub/assets/src/libs/assets/radiation'
-import { NetImages } from '../../../core/libs/net-images'
-import { ConfigProviderThemeVars } from 'wot-design-uni'
-
-const modelValue = defineModel({ default: false, type: Boolean })
-const themeVars: ConfigProviderThemeVars = {
-  overlayBg: 'rgba(0,0,0,0.85)',
-}
-const title = ref('东方研习营')
-const content = ref('获得东方研习营游学徽章')
-const path = ref('')
-const src = ref(
-  'https://image.zhuchaohui.com/zhucaohui/e104215c64d39e4a0f8676c48b8e7221c891eade1c8d7f02b2a7f0be862e3f76.png',
-)
-const show = (options: DialogShowOptions) => {
-  title.value = options.title
-  content.value = options.content
-  path.value = options.path
-  src.value = options.image
-  modelValue.value = true
-}
-provide(HonorDialogSymbol, { show })
-</script>
-<template>
-  <wd-config-provider :themeVars="themeVars">
-    <wd-popup v-model="modelValue" custom-class="bg-transparent! bg-[#f6f6f6]!">
-      <div class="flex flex-col items-center relative">
-        <wd-img width="60vw" mode="widthFix" :src="earnBadgeTitle"></wd-img>
-        <div class="w-[100vw] h-[68vw] pt-2 flex">
-          <div class="w-100vw h-100vw absolute left-0 right-0 top--8">
-            <wd-img
-              custom-class="absolute! vertical-bottom"
-              width="68%"
-              height="68%"
-              :src="radiation"
-            ></wd-img>
-            <wd-img
-              custom-class="absolute! left-16 top-4 vertical-bottom"
-              width="58vw"
-              mode="widthFix"
-              :src="NetImages.Stars"
-            ></wd-img>
-            <wd-img
-              custom-class="absolute! ma-a top-50% left-50% translate-[-50%,-50%]  vertical-bottom blur-60"
-              width="40vw"
-              mode="widthFix"
-              :src="src"
-            ></wd-img>
-            <wd-img
-              custom-class="absolute! ma-a top-50% left-50% translate-[-50%,-50%]  vertical-bottom"
-              width="40vw"
-              mode="widthFix"
-              :src="src"
-            ></wd-img>
-          </div>
-        </div>
-        <div class="text-center">
-          <div class="text-white text-2xl font-normal font-['PingFang_SC'] uppercase">
-            {{ title }}
-          </div>
-          <div class="mt-2 text-[#9f9b94] text-base font-normal font-['PingFang_SC'] uppercase">
-            {{ content }}
-          </div>
-        </div>
-        <div
-          class="my-10 w-[155px] px-5 py-2.5 rounded-full border border-solid border-[#c8beab] justify-center items-center gap-1 inline-flex"
-        >
-          <div
-            class="text-center text-[#c7bdab] text-base font-normal font-['PingFang_SC'] leading-normal"
-          >
-            查看奖励
-          </div>
-        </div>
-        <wd-img width="24" height="24" :src="close" @click="modelValue = false"></wd-img>
-      </div>
-    </wd-popup>
-  </wd-config-provider>
-</template>

+ 185 - 0
packages/app/src/pages/common/components/honor-dialog/honor-dialog.vue

@@ -0,0 +1,185 @@
+<script setup lang="ts">
+import { close } from '../../../../core/libs/svgs'
+import { HonorDialogOptions, HonorDialogSymbol, HonorDialogType } from '.'
+import earnBadgeTitle from '@designer-hub/assets/src/libs/assets/earnBadgeTitle'
+import radiation from '@designer-hub/assets/src/libs/assets/radiation'
+import { NetImages } from '@/core/libs/net-images'
+import { ConfigProviderThemeVars } from 'wot-design-uni'
+import { useRouter } from '@/core/utils/router'
+import earnCertificate from '@designer-hub/assets/src/libs/assets/earnCertificate'
+import envelopeFront from '@designer-hub/assets/src/libs/assets/envelopeFront'
+import envelopeBack from '@designer-hub/assets/src/libs/assets/envelopeBack'
+import ribbonTl from '@designer-hub/assets/src/libs/assets/ribbonTl'
+import ribbonBr from '@designer-hub/assets/src/libs/assets/ribbonBr'
+
+const dialogOption = inject(HonorDialogSymbol, ref<HonorDialogOptions>({}))
+const { push } = useRouter()
+const lazyRender = ref<boolean>(true)
+const modelValue = defineModel({ default: false, type: Boolean })
+const themeVars: ConfigProviderThemeVars = {
+  overlayBg: 'rgba(0,0,0,0.85)',
+}
+const title = ref('东方研习营')
+const content = ref('获得东方研习营游学徽章')
+const path = ref('')
+const src = ref(
+  'https://image.zhuchaohui.com/zhucaohui/e104215c64d39e4a0f8676c48b8e7221c891eade1c8d7f02b2a7f0be862e3f76.png',
+)
+const isBadge = computed(() => dialogOption.value?.type === HonorDialogType.Badge)
+const isCertificate = computed(() => dialogOption.value?.type === HonorDialogType.Certificate)
+const reset = (option) => {
+  if (option) {
+    modelValue.value = option.show
+    lazyRender.value = option.lazyRender
+    title.value = option.title || '东方研习营'
+    content.value = option.content || '获得东方研习营游学徽章'
+    path.value = option.path || ''
+    src.value = option.image || src.value
+  }
+}
+const jumpTo = () => {
+  if (dialogOption.value?.type && dialogOption.value?.type === 'certificate') {
+    push('/pages/mine/honors/index?active=certificate')
+  }
+  if (dialogOption.value?.type && dialogOption.value?.type === 'badge') {
+    push('/pages/mine/honors/index?active=badge')
+  }
+  modelValue.value = false
+}
+const handleLoad = () => {
+  console.log(1111)
+  if (dialogOption.value?.onLoad && typeof dialogOption.value?.onLoad === 'function') {
+    dialogOption.value.onLoad()
+  }
+}
+watch(
+  () => dialogOption.value,
+  (newVal) => {
+    reset(newVal)
+  },
+)
+// provide(HonorDialogSymbol, { show })
+</script>
+<template>
+  <wd-config-provider :themeVars="themeVars">
+    <wd-popup
+      v-model="modelValue"
+      :lazy-render="lazyRender"
+      custom-class="bg-transparent! bg-[#f6f6f6]!"
+    >
+      <div class="flex flex-col items-center relative">
+        <wd-img
+          width="60vw"
+          mode="widthFix"
+          :src="isBadge ? earnBadgeTitle : earnCertificate"
+        ></wd-img>
+        <div v-if="isBadge" class="w-[100vw] h-[68vw] pt-2 flex">
+          <div class="w-100vw h-100vw absolute left-0 right-0 top--8">
+            <wd-img
+              v-if="isBadge"
+              custom-class="absolute! top-50% left-50% translate-[-50%,-50%] vertical-bottom"
+              width="68%"
+              height="68%"
+              :src="radiation"
+            ></wd-img>
+            <wd-img
+              custom-class="absolute! left-16 top-4 vertical-bottom"
+              width="58vw"
+              mode="widthFix"
+              :src="NetImages.Stars"
+            ></wd-img>
+            <wd-img
+              v-if="isBadge"
+              custom-class="absolute! ma-a top-50% left-50% translate-[-50%,-50%]  vertical-bottom blur-60"
+              width="40vw"
+              mode="widthFix"
+              :src="src"
+            ></wd-img>
+            <wd-img
+              v-if="isBadge"
+              custom-class="absolute! ma-a top-50% left-50% translate-[-50%,-50%]  vertical-bottom"
+              width="40vw"
+              mode="widthFix"
+              @load="handleLoad"
+              :src="src"
+            ></wd-img>
+          </div>
+        </div>
+        <div v-if="isCertificate" class="relative mb-16">
+          <wd-img
+            v-if="isCertificate"
+            custom-class="absolute! bottom-0 left-7.5vw vertical-bottom"
+            width="85%"
+            mode="widthFix"
+            :src="envelopeBack"
+          ></wd-img>
+          <wd-img
+            custom-class="absolute! left-12 top--4 vertical-bottom"
+            width="58vw"
+            mode="widthFix"
+            :src="NetImages.Stars"
+          ></wd-img>
+          <div class="w-[100vw] center">
+            <wd-img
+              v-if="isCertificate"
+              custom-class="mt-9.5 mb-22 vertical-bottom"
+              width="80vw"
+              mode="widthFix"
+              @load="handleLoad"
+              :src="src"
+            ></wd-img>
+          </div>
+          <wd-img custom-class="absolute! top-0 left-0" width="114" height="114" :src="ribbonTl" />
+          <wd-img
+            v-if="isCertificate"
+            custom-class="absolute! bottom-0 left-7.5vw vertical-bottom"
+            width="85vw"
+            mode="widthFix"
+            :src="envelopeFront"
+          ></wd-img>
+          <wd-img
+            custom-class="absolute! bottom-0 right-0"
+            width="76"
+            height="56"
+            :src="ribbonBr"
+          />
+          <div class="absolute bottom-0 left-50% translate-[-50%,-50%]">
+            <div
+              class="w-[115.50px] h-[41.16px] bg-gradient-to-r from-[#f2b36f] to-[#ce995c] rounded-[28.68px] flex center"
+              @click="jumpTo"
+            >
+              <div
+                class="w-[110.71px] h-[37.17px] bg-gradient-to-r from-[#f1bf84] to-[#e6c99f] rounded-[28.19px] shadow shadow-inner flex center"
+              >
+                <div class="text-center text-[#242323] text-base font-normal font-['PingFang_SC']">
+                  去查看
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div v-if="isBadge" class="text-center">
+          <div class="text-white text-2xl font-normal font-['PingFang_SC'] uppercase">
+            {{ title }}
+          </div>
+          <div class="mt-2 text-[#9f9b94] text-base font-normal font-['PingFang_SC'] uppercase">
+            {{ content }}
+          </div>
+        </div>
+        <div
+          v-if="isBadge"
+          class="my-10 w-[155px] px-5 py-2.5 rounded-full border border-solid border-[#c8beab] justify-center items-center gap-1 inline-flex"
+        >
+          <div
+            class="text-center text-[#c7bdab] text-base font-normal font-['PingFang_SC'] leading-normal"
+            @click.stop="jumpTo"
+          >
+            查看奖励
+          </div>
+        </div>
+        <wd-img width="24" height="24" :src="close" @click="modelValue = false"></wd-img>
+      </div>
+    </wd-popup>
+  </wd-config-provider>
+</template>

+ 63 - 0
packages/app/src/pages/common/components/honor-dialog/index.ts

@@ -0,0 +1,63 @@
+export interface DialogShowOptions {
+  title: string
+  content: string
+  path: string
+  image: string
+}
+export enum HonorDialogType {
+  Badge = 'badge',
+  Certificate = 'certificate',
+}
+export interface HonorDialogOptions {
+  title?: string
+  content?: string
+  image?: string
+  type?: HonorDialogType
+  onLoad?: () => void
+}
+// export const HonorDialogSymbol: InjectionKey<{
+//   show: (options: DialogShowOptions) => void
+// }> = Symbol.for('HonorDialogContext')
+export const HonorDialogSymbol = Symbol.for('HonorDialogContext')
+// export const useHonorDialog = () => {
+//   const honorDialog = inject(HonorDialogSymbol)
+//   // if (!honorDialog) {
+//   //   throw new Error('useHonorDialog must be used inside setup()')
+//   // }
+//   const show = computed(() => honorDialog?.show)
+//   return {
+//     show,
+//   }
+// }
+export const useHonorDialog = () => {
+  const dialogOption = ref({})
+  // inject(HonorDialogSymbol, dialogOption)
+  // const honorDialog = inject(HonorDialogSymbol)
+  // console.log(honorDialog)
+  provide(HonorDialogSymbol, dialogOption)
+  // if (!honorDialog) {
+  //   throw new Error('useHonorDialog must be used inside setup()')
+  // }
+  const show = (option: HonorDialogOptions) => {
+    return new Promise((resolve, reject) => {
+      const options = {
+        ...option,
+      }
+      dialogOption.value = {
+        ...options,
+        ...{
+          show: true,
+          onConfirm: (res) => {
+            resolve(res)
+          },
+          onCancel: (res) => {
+            reject(res)
+          },
+        },
+      }
+    })
+  }
+  return {
+    show,
+  }
+}

+ 50 - 0
packages/app/src/pages/common/components/share-action-sheet.vue

@@ -0,0 +1,50 @@
+<script setup lang="ts">
+import timeline from '@designer-hub/assets/src/libs/assets/timeline'
+import wechat from '@designer-hub/assets/src/libs/assets/wechat'
+
+const modelValue = defineModel({
+  default: false,
+  type: Boolean,
+})
+const props = withDefaults(
+  defineProps<{
+    options?: any
+  }>(),
+  {},
+)
+const emits = defineEmits<{ select: [action: string | 'share' | 'timeline'] }>()
+const actions = [
+  { icon: wechat, title: '微信好友', value: 'share' },
+  { icon: timeline, title: '朋友圈', value: 'timeline' },
+]
+const handleAction = (it: { value: string }) => {
+  modelValue.value = false
+  emits('select', it.value)
+}
+</script>
+
+<template>
+  <wd-action-sheet v-model="modelValue" title="分享到" @close="modelValue = false">
+    <view class="" style="">
+      <div class="flex justify-around">
+        <template v-for="(it, index) in actions" :key="index">
+          <button :open-type="it.value" :data-options="options">
+            <div class="flex flex-col items-center gap-2" @click="handleAction(it)">
+              <div class="w-12 h-12 relative">
+                <wd-img width="100%" height="100%" :src="it.icon"></wd-img>
+              </div>
+              <div class="text-black/60 text-xs font-normal font-['PingFang_SC'] leading-relaxed">
+                {{ it.title }}
+              </div>
+            </div>
+          </button>
+        </template>
+      </div>
+      <div>
+        <wd-button block type="text" @click="modelValue = false">取消</wd-button>
+      </div>
+    </view>
+  </wd-action-sheet>
+</template>
+
+<style scoped lang="scss"></style>

+ 33 - 0
packages/app/src/pages/common/content-html/index.vue

@@ -0,0 +1,33 @@
+<route lang="json">
+{
+  "style": {
+    "navigationBarTitleText": "",
+    "navigationBarBackgroundColor": "#fff"
+  }
+}
+</route>
+<script setup lang="ts">
+import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html.vue'
+import { fakeThis } from '@/core/libs/requests'
+
+const type = ref()
+const { data: content, run: setData } = useRequest(() => fakeThis({ agreement: type.value }))
+onLoad(async (query?: Record<string | 'title' | 'type', string>) => {
+  if (query?.title) {
+    await uni.setNavigationBarTitle({ title: query.title })
+  }
+  if (query?.type) {
+    type.value = query.type
+  }
+  await setData()
+})
+</script>
+
+<template>
+  <mpHtml :content="content"></mpHtml>
+  <!--  <web-view-->
+  <!--    :src="`https://www.zhuchaohui.com/app-api/infra/file/download?agreement=${type}`"-->
+  <!--  ></web-view>-->
+</template>
+
+<style scoped lang="scss"></style>

+ 10 - 5
packages/app/src/pages/home/about/index.vue

@@ -9,12 +9,17 @@
 <script setup lang="ts">
 import NavbarEvo from '@/components/navbar-evo.vue'
 const imgs = ref([
-  'https://image.zhuchaohui.com/zhucaohui/9da47a2ee9863786851bea968c4693e1d3540c7bbcac0ba53a3b1d90c203c1c2.png',
-  'https://image.zhuchaohui.com/zhucaohui/65f8902cee44826abd8e97ce4940dee459fcaaa2f5f3535016b670dd0006953b.png',
-  'https://image.zhuchaohui.com/zhucaohui/a43e3377643811fbb182b82758962153f93b30cec62e2c389f93f3a157bba6a6.png',
-  'https://image.zhuchaohui.com/zhucaohui/7f212c009d75cb2fd1c89f5934e5bbc97042344dcc2a9818894edcd7bb33dfe6.png',
-  'https://image.zhuchaohui.com/zhucaohui/a81c225a5ad394aaba99594f1410c0865f94c8e941e0c0f868d62fb8111a3c5f.png',
+  'https://image.zhuchaohui.com/zhucaohui/498f4e8c68e554890c7ac23e684044d238fcb46568cf15b652da8827279d24a3.png',
+  'https://image.zhuchaohui.com/zhucaohui/1e818f8ea7ede66cee4b31cd114c054b950e5992407e8c145f6d823770b204b2.png',
+  'https://image.zhuchaohui.com/zhucaohui/45bb68f12d3eb9b4f70eb46769a0ab1adeb55d8242a23c45001fe56986b30a36.png',
+  'https://image.zhuchaohui.com/zhucaohui/9d6713cc39aeb20faae4f374bf92764252ee28f38de2540ab02e1a9d7496bdcb.png',
 ])
+onShareAppMessage(() => ({
+  title: '1分钟快速了解筑巢荟',
+}))
+onShareTimeline(() => ({
+  title: '1分钟快速了解筑巢荟',
+}))
 </script>
 <template>
   <div>

+ 55 - 59
packages/app/src/pages/home/activity/detail/index.vue

@@ -17,7 +17,6 @@ import {
   getStudyTourSignups,
   studyTourSignup,
 } from '../../../../core/libs/requests'
-import { bell, map, rightFill } from '@designer-hub/assets/src/assets/svgs'
 import dayjs from 'dayjs'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import { useRouter } from '../../../../core/utils/router'
@@ -32,7 +31,7 @@ import signupListDialogBg from '@designer-hub/assets/src/libs/assets/signupListD
 import { getActivityStatusText, getCountsArr } from '../../../../core/utils/common'
 import { extractColorsFromImageData } from 'extract-colors/lib/extract-colors.mjs'
 import { group, mapEntries, sort } from 'radash'
-import { Activity, StudyTour } from '../../../../core/libs/models'
+import { Activity, ActivitySignUp, ResPageData, StudyTour } from '../../../../core/libs/models'
 import mapLocation from '@designer-hub/assets/src/libs/assets/mapLocation'
 import cameraWhite from '@designer-hub/assets/src/libs/assets/cameraWhite'
 import ButtonEvo from '@/components/button-evo.vue'
@@ -43,6 +42,8 @@ import TooltipEvo from '@/components/tooltip-evo.vue'
 import ActivityAsOf from '../../components/activity-as-of.vue'
 import images from '@designer-hub/assets/src/libs/assets/images'
 import { usePermissions } from '../../../../composables/permissions'
+import { useAnalysis } from '@/composables/analysis'
+import mapGray from '@designer-hub/assets/src/libs/assets/mapGray'
 const themeVars = ref<ConfigProviderThemeVars>({
   tableBorderColor: 'white',
   tabsNavLineBgColor: 'white',
@@ -50,16 +51,17 @@ const themeVars = ref<ConfigProviderThemeVars>({
 })
 const router = useRouter()
 const { clickByPermission } = usePermissions()
+const { option } = useAnalysis(true, false)
 const id = ref()
 const type = ref<'activity' | 'studyTour'>()
 const activityTypes = ref({ activity: '活动', studyTour: '游学' })
 const tab = ref(0)
 const request = ref<() => Promise<IResData<Partial<StudyTour> | Partial<Activity>>>>()
 const { data, run: setData } = useRequest(() => request.value(), { initialData: {} })
-const { data: signups, run: setSignups } = useRequest(
-  () => getActivitySignups({ activityId: id.value }),
-  { initialData: { list: [], total: 0 } },
-)
+const signUpsReq = ref<() => Promise<IResData<ResPageData<ActivitySignUp>>>>()
+const { data: signups, run: setSignups } = useRequest(() => signUpsReq.value(), {
+  initialData: { list: [], total: 0 },
+})
 const { data: levels, run: setLevels } = useRequest(() => getAppMemberLevelConfigs(), {
   initialData: [],
 })
@@ -67,7 +69,7 @@ const { data: photos, run: setPhotos } = useRequest(
   () =>
     getPhotoList({
       bizId: id.value,
-      bizType: { studyTour: '2', activity: '1' }[type.value],
+      bizType: { studyTour: '2', activity: '1' }[type.value] as '1' | '2',
       pageSize: -1,
     }),
   { initialData: { list: [], total: 0 } },
@@ -78,12 +80,6 @@ const listShow = ref(false)
 const dominantColor = ref()
 const isActivity = computed(() => type.value === 'activity')
 const isStudyTour = computed(() => type.value === 'studyTour')
-const levelsById = computed(() =>
-  levels.value.reduce((acc, item) => {
-    acc[item.id] = item
-    return acc
-  }, {}),
-)
 const levelsByMemberLevel = computed(() =>
   levels.value.reduce((acc, item) => {
     acc[item.memberLevel] = item
@@ -99,15 +95,6 @@ const places = computed(() => {
   }
   return '不限制'
 })
-const remainedCount = computed(() => {
-  if (isActivity.value && data.value?.activityAllowType === '1') {
-    return data.value?.activityAllowCount - signups.value.total
-  }
-  if (isStudyTour.value && data.value?.studyAllowType === '1') {
-    return data.value?.studyAllowCount - signups.value.total
-  }
-  return '不限制'
-})
 const infos = computed(() => [
   {
     icon: clock,
@@ -142,9 +129,7 @@ const infos = computed(() => [
     icon: user,
     title: `${activityTypes.value[type.value]}名额`,
     content: [
-      places.value === '不限制'
-        ? `不限制`
-        : `${data.value.signUpNumber}人/剩余${data.value.surplus}人`,
+      places.value === '不限制' ? `不限制` : `${places.value}人/剩余${data.value.surplus}人`,
     ],
     visable: true,
   },
@@ -183,11 +168,16 @@ onLoad(async (query: { id: string; type: 'activity' | 'studyTour' }) => {
   type.value = query.type
   if (type.value === 'activity') {
     request.value = () => getActivity(id.value)
+    signUpsReq.value = () => getActivitySignups({ activityId: id.value, pageSize: -1 })
   }
   if (type.value === 'studyTour') {
     request.value = () => getStudyTour(id.value)
+    signUpsReq.value = () => getStudyTourSignups({ studyId: id.value, pageSize: -1 })
   }
   await setData()
+  option.value = {
+    remark: `最近浏览${{ activity: '活动', studyTour: '游学' }[type.value]}: ${data.value.name}`,
+  }
   const { path } = await uni.getImageInfo({ src: data.value.backgroundUrl })
   const ctx = uni.createCanvasContext('firstCanvas')
   uni
@@ -243,24 +233,32 @@ onShareTimeline(() => ({ title: data.value.name, imageUrl: data.value.thumbnailU
         canvas-id="firstCanvas"
         id="firstCanvas"
       ></canvas>
-      <ImageEvo :src="data?.backgroundUrl"></ImageEvo>
+      <!--      <ImageEvo :src="data?.bannerUrl" mode="aspectFill"></ImageEvo>-->
+      <div
+        class="w-full h-full bg-[length:100%_auto]"
+        :style="{ backgroundImage: `url(${data?.bannerUrl})` }"
+      ></div>
       <!-- <wd-img width="100%" height="100%" :src="data?.backgroundUrl"></wd-img> -->
-      <div class="absolute left-3.5 bottom-3" @click="listShow = true">
-        <div
-          v-if="isStudyTour"
-          class="bg-white/20 rounded-[20px] backdrop-blur-[6px] px-3.5 py-1 flex gap-2.5"
-        >
-          <wd-img width="20" height="20" :src="bell"></wd-img>
-          <div class="text-[#c1c1c1] text-base font-normal font-['PingFang_SC'] leading-normal">
-            白金会员王凯峰已报名
-          </div>
-          <div class="w-6 bg-black aspect-square rounded-full flex items-center justify-center">
-            <wd-img width="18" height="18" :src="rightFill"></wd-img>
-          </div>
-        </div>
-        <div v-if="isActivity" class="flex items-center gap-1.25">
+      <div class="absolute left-3.5 bottom-3" @click="isActivity && (listShow = true)">
+        <!--        <div-->
+        <!--          v-if="isStudyTour"-->
+        <!--          class="bg-white/20 rounded-[20px] backdrop-blur-[6px] px-3.5 py-1 flex gap-2.5"-->
+        <!--        >-->
+        <!--          <wd-img width="20" height="20" :src="bell"></wd-img>-->
+        <!--          <div class="text-[#c1c1c1] text-base font-normal font-['PingFang_SC'] leading-normal">-->
+        <!--            白金会员王凯峰已报名-->
+        <!--          </div>-->
+        <!--          <div class="w-6 bg-black aspect-square rounded-full flex items-center justify-center">-->
+        <!--            <wd-img width="18" height="18" :src="rightFill"></wd-img>-->
+        <!--          </div>-->
+        <!--        </div>-->
+        <div class="flex items-center gap-1.25">
           <AvatarGroupCasual
-            :urls="signups.list.map((it) => it.headImgUrl || NetImages.DefaultAvatar)"
+            :urls="
+              signups.list
+                .filter((it) => it.userId)
+                .map((it) => it.headImgUrl || NetImages.DefaultAvatar)
+            "
             :width="40"
             :height="40"
           ></AvatarGroupCasual>
@@ -272,24 +270,20 @@ onShareTimeline(() => ({ title: data.value.name, imageUrl: data.value.thumbnailU
     </div>
     <div class="h-9">
       <div v-if="type === 'studyTour'" class="flex items-center h-full mt-9 gap-1.5">
-        <wd-img width="18" height="18" :src="map"></wd-img>
+        <wd-img width="18" height="18" :src="mapGray"></wd-img>
         <div class="text-[#c1c1c1] text-base font-normal font-['PingFang_SC'] leading-normal">
-          第一站
-        </div>
-      </div>
-    </div>
-    <div
-      class="text-white text-[26px] font-normal font-['PingFang_SC'] leading-[44px] flex items-center gap-4"
-    >
-      <!-- 日本研学·东京艺术大学设计游学 -->
-      <div class="inline-block">{{ data?.name }}</div>
-      <div class="inline-block py-1.5 px-4 bg-white rounded-[20px] backdrop-blur-[15px]">
-        <div class="text-[#a60707] text-sm font-normal font-['PingFang_SC'] leading-relaxed">
-          <!-- {{ getActivityStatusText(data?.applyStartTime, data?.applyEndTime) }} -->
-          {{ statusText }}
+          第{{ data?.sort }}站
         </div>
       </div>
     </div>
+    <p class="text-white text-[26px] font-normal font-['PingFang_SC'] vertical-mid">
+      <span class="leading-[44px] vertical-mid">{{ data?.name }}</span>
+      <span
+        class="ml-4 px-4 bg-white rounded-full backdrop-blur-[15px]! text-[#a60707] text-sm font-normal font-['PingFang_SC'] leading-[28px] vertical-mid inline-flex"
+      >
+        {{ statusText }}
+      </span>
+    </p>
 
     <div
       class="px-4 py-6 bg-[#010102]/30 backdrop-blur-[20px] rounded-2xl my-8 flex flex-col gap-3"
@@ -376,7 +370,7 @@ onShareTimeline(() => ({ title: data.value.name, imageUrl: data.value.thumbnailU
                       {{ item.travelDesc }}
                     </span>
                   </div>
-                  <div class="flex items-center gap-1">
+                  <div v-if="(item.clockExplainUrl ?? '') !== ''" class="flex items-center gap-1">
                     <wd-img width="16" height="16" :src="cameraWhite"></wd-img>
                     <div class="text-white text-xs font-normal font-['PingFang_SC'] leading-normal">
                       打卡示例
@@ -389,8 +383,10 @@ onShareTimeline(() => ({ title: data.value.name, imageUrl: data.value.thumbnailU
                     custom-class="rounded-2xl overflow-hidden"
                     :src="item.clockExplainUrl"
                     mode="widthFix"
+                    enable-preview
                   ></wd-img>
                   <div
+                    v-if="(item.clockExplainUrl ?? '') !== ''"
                     class="text-white/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
                   >
                     {{ item.clockExplainDesc }}
@@ -477,9 +473,9 @@ onShareTimeline(() => ({ title: data.value.name, imageUrl: data.value.thumbnailU
                 {{ data.needPointsCount || 0 }}
               </div>
               <div class="text-black/40 text-sm font-normal font-['PingFang_SC']">积分</div>
-              <div class="ml-1 text-black/40 text-xs font-normal font-['PingFang_SC']">
-                剩余:{{ remainedCount || 0 }}
-              </div>
+              <!--              <div class="ml-1 text-black/40 text-xs font-normal font-['PingFang_SC']">-->
+              <!--                剩余:{{ remainedCount || 0 }}-->
+              <!--              </div>-->
               <div class="flex-1"></div>
             </div>
           </div>

+ 19 - 5
packages/app/src/pages/home/components/activity-as-of.vue

@@ -3,12 +3,15 @@ import dayjs from 'dayjs'
 
 const props = defineProps<{ startAt?: string | number; endAt?: string | number }>()
 const emits = defineEmits<{ end: [] }>()
+const currentDate = ref(new Date())
+const interval = ref()
 const status = computed(() => {
-  // 如果当前时间小于开始时间返回等待中,如果当前时间大于等于开始时间小于等于结束时间返回进行中,当前时间大于结束时间返回已结束
-  const now = new Date()
-  if (dayjs(now).isBefore(dayjs(props.startAt))) {
+  if (dayjs(currentDate.value).isBefore(dayjs(props.startAt))) {
     return 'waiting'
-  } else if (dayjs(now).isAfter(dayjs(props.startAt)) && dayjs(now).isBefore(dayjs(props.endAt))) {
+  } else if (
+    dayjs(currentDate.value).isAfter(dayjs(props.startAt)) &&
+    dayjs(currentDate.value).isBefore(dayjs(props.endAt))
+  ) {
     return 'running'
   } else {
     return 'overdue'
@@ -20,6 +23,14 @@ const time = computed(() =>
     'millisecond',
   ),
 )
+onMounted(() => {
+  interval.value = setInterval(() => {
+    currentDate.value = new Date()
+  }, 1000)
+})
+onUnmounted(() => {
+  clearInterval(interval.value)
+})
 </script>
 <template>
   <div>
@@ -49,7 +60,10 @@ const time = computed(() =>
           <span v-if="current.seconds && !current.minutes && !current.hours && !current.days">
           </span>
-          <span>后 报名{{ { waiting: '开始', running: '截止' }[status] }}</span>
+          <span v-if="current.days || current.hours || current.minutes || current.seconds">
+            后 报名{{ { waiting: '开始', running: '截止' }[status] }}
+          </span>
+          <span v-else>加载中...</span>
         </div>
       </template>
     </wd-count-down>

+ 1 - 1
packages/app/src/pages/home/components/activity-count-down.vue

@@ -26,7 +26,7 @@ const time = ref(
     <wd-count-down :time="time" @finish="emits('end')">
       <template #default="{ current }">
         <div v-if="time" class="flex items-center gap-1.25 text-black/40 text-sm">
-          <div>距{{ { waiting: '报名开始', running: '报名结束' }[status] }}还</div>
+          <div>距{{ { waiting: '报名开始', running: '报名结束' }[status] }}还</div>
           <div
             v-if="current.days"
             class="w-4 h-4 bg-black/90 rounded text-white text-2.5 flex items-center justify-center"

+ 1 - 0
packages/app/src/pages/home/components/banner.vue

@@ -13,6 +13,7 @@ const swiperList = computed(() => banners.value.map((it) => it.bannerImgUrl))
 const handleClick = ({ index }: { index: number }) => {
   const banner = banners.value[index]
   console.log(banner)
+  if (!banner.bannerLinkUrl && !banner.bannerDetailsContent) return
   if (banner.bannerDetailsType === '2') {
     if (banner.bannerLinkUrl?.startsWith('http')) {
       router.push(`/pages/common/webview/index?url=${banner.bannerLinkUrl}`)

+ 3 - 1
packages/app/src/pages/home/components/class-item.vue

@@ -47,7 +47,9 @@ const router = useRouter()
     <div
       class="w-full h-[145px] pl-39 pt-6 pr-6 pb-6 flex flex-col box-border bg-white rounded-2xl shadow"
     >
-      <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal">
+      <div
+        class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal text-ellipsis line-clamp-1 overflow-hidden"
+      >
         <!-- 2024届米兰国际家具展 -->
         {{ options.title }}
       </div>

+ 3 - 1
packages/app/src/pages/home/components/comment-item.vue

@@ -150,7 +150,9 @@ defineExpose({
         :options="it"
         :isChild="true"
         @replay="
-          (options) => emits('replay', { ...options, refreshId: props.options.id, index: index })
+          (options) => {
+            emits('replay', { ...options, refreshId: props.options.id, index: index })
+          }
         "
         @delete="emits('delete', index)"
         @upvote="emits('upvote', index)"

+ 1 - 1
packages/app/src/pages/home/components/info-card.vue

@@ -12,7 +12,7 @@ const props = defineProps<{
       {{ props.title }}
     </div>
     <div
-      class="w-[319px] h-[264px] text-justify text-black/40 text-base font-normal font-['PingFang_SC'] leading-relaxed"
+      class="text-justify text-black/40 text-base font-normal font-['PingFang_SC'] leading-relaxed"
     >
       {{ props.desc }}
     </div>

+ 3 - 1
packages/app/src/pages/home/components/menus.vue

@@ -42,7 +42,9 @@ const handleClick = (path: string) => {
         <Card>
           <view class="flex justify-between">
             <view class="text-[rgba(0,0,0,0.85)] text-4 font-400 line-height-2.5">
-              <view class="my-0.75">{{ it.title }}</view>
+              <div class="text-black/90 text-base font-bold font-['PingFang_SC'] leading-[10.18px]">
+                {{ it.title }}
+              </div>
               <view class="text-[rgba(0,0,0,0.45)] text-3.5 my-2.5">{{ it.desc }}</view>
             </view>
             <view class="">

+ 25 - 0
packages/app/src/pages/home/components/moment-video.vue

@@ -0,0 +1,25 @@
+<script setup lang="ts">
+const instance = getCurrentInstance()
+const props = withDefaults(defineProps<{ src: string; enableProgressGesture: boolean }>(), {
+  enableProgressGesture: false,
+})
+const videoRef = ref()
+const videoContext = ref<UniNamespace.VideoContext>()
+onMounted(() => {
+  videoContext.value = uni.createVideoContext('video', instance)
+})
+defineExpose({
+  videoContext,
+})
+</script>
+
+<template>
+  <video
+    class="w-full h-full"
+    id="video"
+    :src="src"
+    :enable-progress-gesture="enableProgressGesture"
+  ></video>
+</template>
+
+<style scoped lang="scss"></style>

+ 17 - 14
packages/app/src/pages/home/components/offline-activity-item.vue

@@ -21,23 +21,24 @@ const router = useRouter()
 <template>
   <div @click="router.push(`/pages/home/classmates-detail/index?id=${options.id}`)">
     <card :custom-class="[customClass, 'p-0!']">
-      <view class="relative">
+      <view class="relative aspect-[1.72/1]">
         <wd-img
           width="100%"
+          height="100%"
           custom-class="vertical-bottom"
           class="w-[347px] h-[202px] rounded-tl-2xl rounded-tr-2xl"
-          mode="widthFix"
+          mode="aspectFill"
           :src="options.bannerUrl"
         />
-        <template v-if="dayjs().isAfter(dayjs(options?.hostDate).add(1, 'd'))">
-          <div
-            class="absolute top-4 right-4 px-2.5 bg-black/30 rounded-[20px] backdrop-blur-[15px]"
-          >
-            <div class="text-white text-xs font-normal font-['PingFang_SC'] leading-relaxed">
-              已结束
-            </div>
-          </div>
-        </template>
+        <!--        <template v-if="dayjs().isAfter(dayjs(options?.hostDate).add(1, 'd'))">-->
+        <!--          <div-->
+        <!--            class="absolute top-4 right-4 px-2.5 bg-black/30 rounded-[20px] backdrop-blur-[15px]"-->
+        <!--          >-->
+        <!--            <div class="text-white text-xs font-normal font-['PingFang_SC'] leading-relaxed">-->
+        <!--              已结束-->
+        <!--            </div>-->
+        <!--          </div>-->
+        <!--        </template>-->
         <div
           class="absolute top-4 left-4 px-2.5 rounded-md border border-solid border-white justify-center items-center gap-2.5 inline-flex"
         >
@@ -47,7 +48,9 @@ const router = useRouter()
         </div>
       </view>
       <div class="p-5 bg-white rounded-bl-[20px] rounded-br-[20px] shadow">
-        <div class="w-[244px] text-black text-base font-normal font-['PingFang_SC'] leading-normal">
+        <div
+          class="text-black text-base font-normal font-['PingFang_SC'] leading-normal text-ellipsis line-clamp-1 overflow-hidden"
+        >
           {{ options.title }}
         </div>
         <view class="mt-1.5 flex items-center mb-4">
@@ -58,7 +61,7 @@ const router = useRouter()
             <!-- <div class="">{{ dayjs(options.studyStartDate).format('MM-DD') }}</div>
             <wd-img custom-class="mx-1" width="5" height="5" :src="polygon16" />
             <div>{{ dayjs(options.studyEndDate).format('MM-DD') }}</div> -->
-            {{ dayjs(options?.hostDate).format('MM.DD HH:mm') }}
+            {{ dayjs(options?.hostDate).format('YYYY-MM-DD') }}
           </div>
         </view>
         <view class="flex gap-4">
@@ -74,7 +77,7 @@ const router = useRouter()
             class="inline-block px-2.5 rounded-md border border-solid border-black/30 backdrop-blur-[6px] flex justify-center items-center"
           >
             <div class="text-black/60 text-xs font-normal font-['PingFang_SC'] leading-normal">
-              {{ options.supportBrand }}
+              赞助品牌:{{ options.supportBrand }}
             </div>
           </div>
         </view>

+ 17 - 11
packages/app/src/pages/home/components/register-card.vue

@@ -6,7 +6,9 @@ import TiltedButton from '@/components/tilted-button.vue'
 import { getActivitySignups } from '../../../core/libs/requests'
 import { NetImages } from '../../../core/libs/net-images'
 import { useRouter } from '../../../core/utils/router'
-import { getActivityStatusButtonText, getActivityStatusText } from '../../../core/utils/common'
+import AvatarGroupCasual from '@/components/avatar-group-casual/avatar-group-casual.vue'
+import { omit } from 'radash'
+import { useActivity } from '@/composables/activity'
 
 const props = defineProps<{
   customClass?: string
@@ -14,9 +16,11 @@ const props = defineProps<{
 }>()
 const router = useRouter()
 const { data: signups, run: setSignups } = useRequest(
-  () => getActivitySignups({ activityId: props.options!.id.toString() }),
+  () => getActivitySignups({ activityId: props.options!.id.toString(), pageSize: -1 }),
   { initialData: { list: [], total: 0 } },
 )
+const activityOptions = computed(() => omit(props.options, ['levelsByMemberLevel']))
+const { statusText, listItemButtonText } = useActivity(activityOptions)
 onMounted(async () => {
   await setSignups()
 })
@@ -33,17 +37,22 @@ onMounted(async () => {
         ></wd-img>
       </div>
       <div
-        class="w-[63px] h-[29px] bg-black/60 rounded-[20px] backdrop-blur-[15px] absolute top-5 right-3.5 flex items-center justify-center"
+        class="px-2.5 h-[29px] bg-black/60 rounded-[20px] backdrop-blur-[15px] absolute top-5 right-3.5 flex items-center justify-center"
       >
         <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-relaxed">
-          {{ getActivityStatusText(options?.applyStartTime, options?.applyEndTime) }}
+          <!--          {{ getActivityStatusText(options?.applyStartTime, options?.applyEndTime) }}-->
+          {{ statusText }}
         </div>
       </div>
       <view class="absolute bottom-0 left-0 right-0">
         <view class="flex items-center mx-4 my-2.5 gap-1">
           <avatar-group-casual
             :show-number="3"
-            :urls="signups.list.map((it) => it.avatar || NetImages.DefaultAvatar)"
+            :urls="
+              signups.list
+                .filter((it) => it.userId)
+                .map((it) => it.headImgUrl || NetImages.DefaultAvatar)
+            "
           ></avatar-group-casual>
           <div
             class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
@@ -51,7 +60,8 @@ onMounted(async () => {
             {{ signups.total }}人已报名
           </div>
         </view>
-        <div class="bg-[#27130d]/50 rounded-bl-2xl rounded-br-2xl backdrop-blur-[20px] p-3.5">
+        <div class="w-full h-[46px] absolute top-0 bg-gradient-to-t from-black to-black/00"></div>
+        <div class="bg-[#010102]/50 rounded-bl-2xl rounded-br-2xl backdrop-blur-[20px] p-3.5">
           <div
             class="w-[293px] text-white text-xl font-normal font-['PingFang_SC'] leading-relaxed"
           >
@@ -90,11 +100,7 @@ onMounted(async () => {
               <div class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC']">积分</div>
             </view>
             <tilted-button custom-class="" size="large" color="white">
-              {{
-                options?.ifSingnUp
-                  ? '已报名'
-                  : getActivityStatusButtonText(options?.applyStartTime, options?.applyEndTime)
-              }}
+              {{ options?.ifSingnUp ? '已报名' : listItemButtonText }}
             </tilted-button>
           </view>
         </div>

+ 13 - 8
packages/app/src/pages/home/components/schedule-card.vue

@@ -54,13 +54,15 @@ onMounted(() => {
 </script>
 <template>
   <view class="flex flex-col items-center aspect-[1.28/1]" :class="[customClass]">
-    <SectionHeading title="我的游学日程" custom-class="w-full"></SectionHeading>
-    <div
-      class="w-full my-3.5 text-[#acacac] text-sm font-normal font-['PingFang_SC'] leading-normal"
-    >
-      <!-- 6月26日 第二天 -->
-      {{ dayjs().format('M月D日') }}
-      第{{ Object.keys(schedules).findIndex((it) => it === dayjs().format('YYYY-MM-DD')) + 1 }}天
+    <div class="px-3.5 w-full box-border">
+      <SectionHeading title="我的游学日程" custom-class="w-full"></SectionHeading>
+      <div
+        class="w-full my-3.5 text-[#acacac] text-sm font-normal font-['PingFang_SC'] leading-normal"
+      >
+        <!-- 6月26日 第二天 -->
+        {{ dayjs().format('M月D日') }}
+        第{{ Object.keys(schedules).findIndex((it) => it === dayjs().format('YYYY-MM-DD')) + 1 }}天
+      </div>
     </div>
     <div
       class="w-80 bg-gradient-to-r from-[#141414] to-[#4b4949] rounded-tl-2xl rounded-tr-2xl p-6 box-border"
@@ -81,7 +83,10 @@ onMounted(() => {
               {{ it?.title }}
             </div>
           </view>
-          <div class="ml-6.5 text-white/40 text-sm font-normal font-['PingFang_SC'] leading-normal">
+          <div
+            v-if="!(data?.length === 2 && index === data?.length - 1)"
+            class="ml-6.5 text-white/40 text-sm font-normal font-['PingFang_SC'] leading-normal line-clamp-2 text-ellipsis overflow-hidden"
+          >
             <!-- 学习灯光设计师课程 -->
             {{ it?.travelDesc }}
           </div>

+ 6 - 0
packages/app/src/pages/home/content/index.vue

@@ -35,6 +35,12 @@ onLoad(async (query: { id: string; type?: 'banner' }) => {
   }
   await run()
 })
+onShareAppMessage(() => ({
+  title: data.value?.title,
+}))
+onShareTimeline(() => ({
+  title: data.value?.title,
+}))
 </script>
 <template>
   <div class="flex-grow bg-white">

+ 78 - 24
packages/app/src/pages/home/index.vue

@@ -18,19 +18,20 @@ import useRequest from '../../hooks/useRequest'
 import Menus from './components/menus.vue'
 import {
   getActivities,
+  getBadges,
+  getCertificates,
   getCircles,
   getMyStudyTours,
   getSetIndexConfigs,
   getStudyTours,
-  shareCircle,
+  updateHonorPopUp,
   updateSetIndexConfig,
 } from '../../core/libs/requests'
 import { logo } from '../../core/libs/svgs'
 import { ComponentExposed } from 'vue-component-type-helpers'
 import { usePermissions } from '../../composables/permissions'
 import { storeToRefs } from 'pinia'
-import { messages } from '../../core/libs/messages'
-import { handleUpvoteClick, handleShareClick } from '../../core/libs/actions'
+import { handleUpvoteClick } from '../../core/libs/actions'
 import { useUserStore } from '../../store'
 import ScheduleCard from './components/schedule-card.vue'
 import dayjs from 'dayjs'
@@ -38,7 +39,9 @@ import { pick, sort } from 'radash'
 import { Activity, StudyTour } from '../../core/libs/models'
 import PageHelperEvo from '@/components/page-helper-evo.vue'
 import { useMessage } from 'wot-design-uni'
-import { useHonorDialog } from '../../composables/honor-dialog'
+import ShareActionSheet from '@/pages/common/components/share-action-sheet.vue'
+import { useShare } from '@/composables/share'
+import { HonorDialogType, useHonorDialog } from '@/pages/common/components/honor-dialog'
 
 defineOptions({
   name: 'Home',
@@ -48,6 +51,7 @@ const { show } = useHonorDialog()
 const userStore = useUserStore()
 const { userInfo } = storeToRefs(userStore)
 const { features, isLogined } = usePermissions()
+const { shareAppMessage } = useShare()
 const pageHelperRef = ref<ComponentExposed<typeof PageHelperEvo>>()
 const { data: indexConfigsData, run: setIndexConfigsData } = useRequest(
   () => getSetIndexConfigs(),
@@ -62,6 +66,9 @@ const autoplay = ref(true)
 const homeBannerRef = ref<ComponentExposed<typeof HomeBanner>[]>()
 const hotActivities =
   ref<{ type: 'studyTour' | 'activity'; data: StudyTour & Activity; startAt: string | number }[]>()
+const shareActionState = ref(false)
+// const shareRef = ref<Comp>()
+const shareOptions = ref()
 const currentStudyTour = computed(() =>
   studyTours.value.find(
     (it) => dayjs(it.studyStartTime).isBefore(dayjs()) && dayjs(it.studyEndTime).isAfter(dayjs()),
@@ -84,7 +91,7 @@ const handleLike = async (options) => {
     userId: userInfo.value.userId,
     userName: userInfo.value.nickname,
   })
-  pageHelperRef.value?.refresh()
+  await pageHelperRef.value?.refresh()
 }
 const setHotActivities = async () => {
   const res = await Promise.all([
@@ -103,8 +110,12 @@ const handlePlay = async (id) => {
   autoplay.value = false
   await updateSetIndexConfig(body)
 }
+const handleShare = (options: any) => {
+  shareOptions.value = options
+  shareActionState.value = true
+}
 onShow(async () => {
-  pageHelperRef.value?.reload()
+  await pageHelperRef.value?.reload()
   const reqs = [setHotActivities()]
   if (isLogined.value) {
     reqs.push(setStudyTours())
@@ -116,27 +127,65 @@ onLoad(async () => {
   swiperData.value = indexConfigsData.value.list.map((it) => ({
     data: it,
   }))
-  show.value({ title: '看不见', content: '看不见', path: '', image: '' })
+  const { data: badgeData } = await getBadges({})
+  const { data: certificates } = await getCertificates()
+  console.log(certificates)
+  const badges = Object.values(badgeData)
+    .flat()
+    .filter((it) => !it.popUp && it.quantity)
+  const honors = [
+    ...badges.map((it) => ({
+      type: HonorDialogType.Badge,
+      id: it.id,
+      title: it.badgeName,
+      content: it.badgeDescription,
+      image: it.badgeYesObtainedImage,
+    })),
+    ...certificates
+      .filter((it) => !it.popUp)
+      .map((it) => ({
+        type: HonorDialogType.Certificate,
+        id: it.id,
+        title: it.certificateName,
+        content: it.certificateDescription,
+        image: it.certificateImage,
+      })),
+  ]
+  // console.log(honors)
+  // console.log(badges)
+  if (honors.length) {
+    const honor = honors[0]
+    await show({
+      title: honor.title ?? ' ',
+      content: honor.content ?? ' ',
+      image: honor.image,
+      type: honor.type,
+      onLoad: async () => {
+        await updateHonorPopUp({
+          bizId: String(honor.id),
+          bizType: honor.type === HonorDialogType.Badge ? '1' : '2',
+        })
+      },
+    })
+  }
+  // await show({
+  //   title: badges
+  // })
+  // show.value({ title: '看不见', content: '看不见', path: '', image: '' })
 })
 onHide(() => {
   // autoplay.value = true
   homeBannerRef.value?.[swiperCurrent.value]?.videoContext.pause()
 })
-onShareAppMessage(async ({ from, target }) => {
-  console.log('from', from)
-  console.log('target', target)
-  const res: Page.CustomShareContent = {}
-  if (from === 'button') {
-    await shareCircle(target.dataset.options.id)
-    res.path = `/pages/home/moment/index?id=${target.dataset.options.id}&isShared=true`
-    res.imageUrl = target.dataset.options.bannerUrls[0]
-    res.title = `${target.dataset.options.stylistName}: ${target.dataset.options.circleDesc}`
-  }
-  if (from === 'menu') {
-    res.title = messages.home.shareTitle
-  }
-  return res
-})
+onShareAppMessage(shareAppMessage)
+// onShareTimeline(async ({from, target}) => {
+//   const res: Page.ShareTimelineContent = {}
+//   if (from === 'button') {
+//     // await shareCircle(target.dataset.options.id)
+//     // res.path = `/pages/home/moment/index?id=${target.dataset.options.id}&isShared=true`
+//   }
+//   return res
+// })
 </script>
 
 <template>
@@ -202,19 +251,24 @@ onShareAppMessage(async ({ from, target }) => {
           </div>
         </Card>
       </view>
-      <view class="mx-3.5 text-5 font-400">设计圈</view>
+      <view class="mx-3.5 text-5 font-bold">设计圈</view>
       <view class="mx-3.5">
         <PageHelperEvo ref="pageHelperRef" :request="getCircles" class="">
           <template #default="{ source }">
             <template v-for="it of source.list" :key="it.id">
               <view class="my-3">
-                <MomentItem :options="it" @like="handleLike"></MomentItem>
+                <MomentItem :options="it" @like="handleLike" @share="handleShare"></MomentItem>
               </view>
             </template>
           </template>
         </PageHelperEvo>
       </view>
     </view>
+    <ShareActionSheet
+      ref="shareRef"
+      v-model="shareActionState"
+      :options="shareOptions"
+    ></ShareActionSheet>
   </view>
 </template>
 

+ 29 - 11
packages/app/src/pages/home/mall/components/product.vue

@@ -6,6 +6,7 @@ import { requestToast } from '../../../../core/utils/common'
 import { createProductItemBuy } from '../../../../core/libs/requests'
 import { useUserStore } from '../../../../store'
 import { storeToRefs } from 'pinia'
+import { needLoginPages } from '@/utils'
 
 const props = defineProps({
   options: {
@@ -51,12 +52,13 @@ const handleAddToCart = async () => {
       ></wd-img>
     </div>
     <div class="flex">
-      <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal">
+      <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal line-clamp-1 text-ellipsis overflow-hidden">
         <!-- 阿芙佳朵 -->
         {{ options.prodcutName }}
       </div>
       <div class="flex-1"></div>
       <div
+        v-if="Number(options.productPrice)"
         class="w-[26px] text-black/30 text-xs font-normal font-['PingFang_SC'] line-through leading-normal"
       >
         <!-- ¥60 -->
@@ -64,17 +66,33 @@ const handleAddToCart = async () => {
       </div>
     </div>
     <div class="flex items-center mb-6">
-      <div class="flex items-end gap-1">
-        <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-5.5">
-          <!-- 1000 -->
-          {{ options.points }}
+      <template v-if="String(options.needPoints) === '0'">
+        <div class="flex items-end gap-1">
+          <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] leading-5.5">
+            <!-- 1000 -->
+            {{ options.showFavourable ? options.favourablePoints : options.points }}
+          </div>
+          <div class="text-black/60 text-sm font-normal font-['PingFang_SC']">积分</div>
         </div>
-        <div class="text-black/60 text-sm font-normal font-['PingFang_SC']">积分</div>
-      </div>
-      <div class="flex-1"></div>
-      <div class="" @click.stop="handleAddToCart">
-        <wd-img width="32" height="32" :src="addBlack"></wd-img>
-      </div>
+        <div class="flex-1"></div>
+        <div class="" @click.stop="handleAddToCart">
+          <wd-img width="32" height="32" :src="addBlack"></wd-img>
+        </div>
+      </template>
+      <template v-if="String(options.needPoints) === '1'">
+        <div class="flex items-end gap-1">
+          <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] leading-5.5">
+            <!-- 1000 -->
+            {{ options.points }}
+          </div>
+          <div class="text-black/60 text-sm font-normal font-['PingFang_SC']">折</div>
+          <div class="text-black/60 text-sm font-normal font-['PingFang_SC']">(积分结算)</div>
+        </div>
+        <div class="flex-1"></div>
+        <!--        <div class="" @click.stop="handleAddToCart">-->
+        <!--          <wd-img width="32" height="32" :src="addBlack"></wd-img>-->
+        <!--        </div>-->
+      </template>
     </div>
   </div>
 </template>

+ 41 - 20
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'
@@ -30,13 +30,26 @@ const { userInfo } = storeToRefs(userStore)
 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 selectedCoupons = ref<Coupon[]>([])
+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 { data: coupons, run: setCoupons } = useRequest(
+  () =>
+    getProductCoupons({
+      userId: userInfo.value?.userId,
+      productIds: data.value?.list.map((it) => it.productId).join(','),
+    }),
+  {},
 )
 const offerPoints = computed(() => {
   const products = sort(data.value?.list, (it: any) => it.points).reverse()
@@ -56,12 +69,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 +86,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 +97,14 @@ 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 Promise.all([setCoupons(), setConfirmOrder()])
+  // await setConfirmOrder()
 })
 </script>
 
@@ -123,10 +138,11 @@ 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">
+          <!--          {{ selectedCoupons }}-->
           <SectionHeading
             title="优惠券"
             :end-text="`已选${selectedCoupons?.length || 0}张`"
@@ -149,16 +165,17 @@ onLoad(async (query: { data: string }) => {
               <div class="flex items-center gap-1">
                 <template v-if="!selectedCoupons?.length">
                   <div
-                    class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
+                    class="text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
+                    :class="coupons.length ? 'text-[#ef4343]' : 'text-black/40'"
                   >
-                    选择优惠券
+                    {{ coupons.length ? `${coupons.length}张可用` : '无可用' }}
                   </div>
                 </template>
                 <template v-else>
                   <div
                     class="text-[#ef4343] text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
                   >
-                    -{{ offerPoints }}
+                    -{{ confirmOrder.totalsCouponPoints }}
                   </div>
                 </template>
                 <wd-img :src="right" width="12" height="12"></wd-img>
@@ -166,14 +183,18 @@ onLoad(async (query: { data: string }) => {
             </template>
           </SectionHeading>
         </div>
-        <SectionHeading title="实付积分" :end-text="paidPoints" size="sm"></SectionHeading>
+        <SectionHeading
+          title="实付积分"
+          :end-text="String(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 +208,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> -->

+ 94 - 93
packages/app/src/pages/home/mall/detail/index.vue

@@ -8,11 +8,6 @@
 </route>
 
 <script setup lang="ts">
-import TiltedButton from '@/components/tilted-button.vue'
-import Product from '../components/product.vue'
-import { shoppingBag } from '@designer-hub/assets/src/assets/svgs/index'
-import InvertedTrapezoidButton from '@/components/inverted-trapezoid-button.vue'
-import TrapeziumButton from '@/components/trapezium-button.vue'
 import { useRouter } from '../../../../core/utils/router'
 import { createProductItemBuy, getProduct, productPlacing } from '../../../../core/libs/requests'
 import { requestToast } from '../../../../core/utils/common'
@@ -21,6 +16,7 @@ import { storeToRefs } from 'pinia'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import ButtonEvo from '@/components/button-evo.vue'
 import { usePermissions } from '../../../../composables/permissions'
+import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html.vue'
 
 const { clickByPermission } = usePermissions()
 const userStore = useUserStore()
@@ -33,27 +29,28 @@ const type = ref<'add2Cart' | 'orderNow'>()
 const { data, run: setData } = useRequest(() => getProduct(id.value))
 
 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: data.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
-    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(
@@ -63,7 +60,7 @@ const handleConfirm = async () => {
             {
               userId: userInfo.value.userId,
               productId: data.value?.productId || '',
-              points: data.value?.points,
+              points,
               nums: nums.value,
             },
           ],
@@ -77,37 +74,55 @@ onLoad(async (query: { id: string }) => {
   id.value = query.id
   await setData()
 })
+onShareAppMessage(() => ({
+  title: data.value?.prodcutName,
+}))
+onShareTimeline(() => ({
+  title: data.value?.prodcutName,
+}))
 </script>
 
 <template>
   <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%" :src="data?.productCoverImgUrl" />
+        <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">
-      <div class="flex items-center gap-1">
-        <div class="text-[#ef4343] text-[26px] font-normal font-['D-DIN Exp'] leading-normal">
+      <div class="flex items-end gap-1">
+        <div class="text-[#ef4343] text-[26px] font-normal font-['D-DIN_Exp'] leading-[20px]">
           <!-- 1000 -->
-          {{ data?.points }}
-        </div>
-        <div class="text-black/60 text-base font-normal font-['PingFang_SC'] leading-[34px]">
-          积分
+          {{ data?.showFavourable ? data?.favourablePoints : data?.points }}
         </div>
+        <template v-if="String(data?.needPoints) === '0'">
+          <div class="text-black/60 text-base font-normal font-['PingFang_SC'] leading-4">积分</div>
+        </template>
+        <template v-if="String(data?.needPoints) === '1'">
+          <div class="text-black/60 text-base font-normal font-['PingFang_SC'] leading-4">
+            折(积分结算)
+          </div>
+        </template>
         <div
-          class="w-[66px] text-black/30 text-xs font-normal font-['PingFang_SC'] line-through leading-normal"
+          v-if="Number(data?.productPrice)"
+          class="w-[66px] text-black/30 text-xs font-normal font-['PingFang_SC'] leading-3"
         >
           <!-- ¥60 -->
           ¥{{ data?.productPrice }}
         </div>
         <div class="flex-1"></div>
-        <div class="text-[#999999] text-xs font-normal font-['PingFang_SC'] leading-[10.18px]">
+        <div class="text-[#999999] text-xs font-normal font-['PingFang_SC']">
           <!-- 已售5件 -->
-          已售{{ data?.exchangeCount || 0 }}件
+          库存:{{ data?.productRepertory || 0 }}
         </div>
       </div>
-      <div class="h-4 text-black text-xl font-normal font-['PingFang_SC'] leading-[10.18px]">
+      <div class="text-black text-xl font-normal font-['PingFang_SC']">
         <!-- 阿芙佳朵 -->
         {{ data?.prodcutName }}
       </div>
@@ -115,16 +130,15 @@ onLoad(async (query: { id: string }) => {
       <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal">
         积分兑换说明:
       </div>
-      <div
-        class="w-[346px] h-[95px] text-black/40 text-xs font-normal font-['PingFang_SC'] leading-[23px]"
-      >
-        · 不限制兑换个数
-        <br />
-        · 兑换后不支持退换货,如有问题可联系官方客户
-        <br />
-        · 规格:件
-        <br />
-        · 配送方式:到店自取
+      <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-[23px]">
+        {{ data?.exchangeDesc }}
+        <!--        · 不限制兑换个数-->
+        <!--        <br />-->
+        <!--        · 兑换后不支持退换货,如有问题可联系官方客户-->
+        <!--        <br />-->
+        <!--        · 规格:件-->
+        <!--        <br />-->
+        <!--        · 配送方式:到店自取-->
       </div>
       <div class="mx--4 h-2.5 bg-neutral-100"></div>
       <wd-divider>
@@ -134,52 +148,38 @@ onLoad(async (query: { id: string }) => {
           商品详情
         </div>
       </wd-divider>
-      <wd-img width="100%" mode="widthFix" :src="data?.productDetailsImgUrl"></wd-img>
+      <mpHtml :content="data?.contentDesc"></mpHtml>
     </div>
-    <BottomAppBar fixed placeholder>
-      <div class="h-[63px] bg-white backdrop-blur-[20px] flex items-center justify-between gap-2">
-        <div class="flex-1">
-          <ButtonEvo
-            block
-            color="white"
-            location="right"
-            @click="((show = true), (type = 'add2Cart'))"
-          >
-            <span class="text-black/80">加入购物车</span>
-          </ButtonEvo>
-        </div>
-        <!-- <div @click="(show = true), (type = 'add2Cart')">
-          <InvertedTrapezoidButton>
-            <div
-              class="w-20 h-[22px] text-black text-base font-normal font-['PingFang_SC'] leading-tight"
+    <template v-if="String(data?.needPoints) === '0'">
+      <BottomAppBar fixed placeholder>
+        <div class="h-[63px] bg-white backdrop-blur-[20px] flex items-center justify-between gap-2">
+          <div class="flex-1">
+            <ButtonEvo
+              block
+              color="white"
+              location="right"
+              @click="((show = true), (type = 'add2Cart'))"
             >
-              加入购物车
-            </div>
-          </InvertedTrapezoidButton>
-        </div> -->
-        <div class="flex-1">
-          <!-- <TrapeziumButton size="large">
-            <div class="text-white text-base font-normal font-['PingFang_SC'] leading-tight">
-              <div class="text-white text-base font-normal font-['PingFang_SC'] leading-tight">
-                立即兑换
-              </div>
-            </div>
-          </TrapeziumButton> -->
-          <ButtonEvo
-            block
-            size="lg"
-            @click="
-              clickByPermission('mallExchange', () => {
-                show = true
-                type = 'orderNow'
-              })
-            "
-          >
-            立即兑换
-          </ButtonEvo>
+              <span class="text-black/80">加入购物车</span>
+            </ButtonEvo>
+          </div>
+          <div class="flex-1">
+            <ButtonEvo
+              block
+              size="lg"
+              @click="
+                clickByPermission('mallExchange', () => {
+                  show = true
+                  type = 'orderNow'
+                })
+              "
+            >
+              立即兑换
+            </ButtonEvo>
+          </div>
         </div>
-      </div>
-    </BottomAppBar>
+      </BottomAppBar>
+    </template>
     <wd-action-sheet v-model="show">
       <view class="px-7 py-11">
         <div class="flex gap-3 mb-13.5">
@@ -190,11 +190,12 @@ onLoad(async (query: { id: string }) => {
             <div class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-normal">
               {{ data?.prodcutName }}
             </div>
-            <div class="flex items-center">
-              <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-normal">
-                {{ data?.points }}
+            <div class="flex items-end gap-1">
+              <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] leading-4">
+                <!--                {{ data?.points }}-->
+                {{ data?.showFavourable ? data?.favourablePoints : data?.points }}
               </div>
-              <div class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[34px]">
+              <div class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-3">
                 积分
               </div>
               <div class="flex-1"></div>

+ 82 - 27
packages/app/src/pages/home/mall/index.vue

@@ -56,7 +56,7 @@ const categories = computed(() => productCategories.value.find(({ id }) => id ==
 const time = computed(
   () =>
     dayjs(favourableProducts.value[current.value].favourableEndDate).diff(
-      dayjs(),
+      dayjs().toDate(),
       'milliseconds',
     ) || 0,
 )
@@ -78,6 +78,12 @@ onMounted(async () => {
 onShow(async () => {
   await Promise.all([setCarts()])
 })
+onShareAppMessage(() => ({
+  title: '品质商城',
+}))
+onShareTimeline(() => ({
+  title: '品质商城',
+}))
 </script>
 
 <template>
@@ -94,15 +100,44 @@ onShow(async () => {
           >
             <wd-count-down :time="time">
               <template #default="{ current }">
-                <span
-                  class="text-center text-white text-base font-normal font-['PingFang_SC'] leading-relaxed"
-                >
-                  <span class="custom-count-down">{{ current.hours }}</span>
-                  <span class="custom-count-down-colon">:</span>
-                  <span class="custom-count-down">{{ current.minutes }}</span>
-                  <span class="custom-count-down-colon">:</span>
-                  <span class="custom-count-down">{{ current.seconds }}</span>
-                </span>
+                <!--                <span-->
+                <!--                  class="text-center text-white text-base font-normal font-['PingFang_SC'] leading-relaxed"-->
+                <!--                >-->
+                <!--                  <span class="custom-count-down">{{ current.hours }}</span>-->
+                <!--                  <span class="custom-count-down-colon">:</span>-->
+                <!--                  <span class="custom-count-down">{{ current.minutes }}</span>-->
+                <!--                  <span class="custom-count-down-colon">:</span>-->
+                <!--                  <span class="custom-count-down">{{ current.seconds }}</span>-->
+                <!--                </span>-->
+                <div v-if="time" class="flex h-full items-center gap-1.25 text-black/40 text-sm">
+                  <!--                  <div>距{{ { waiting: '报名开始', running: '报名结束' }[status] }}还有</div>-->
+                  <div
+                    v-if="current.days"
+                    class="w-4 h-4 bg-white/90 rounded text-black text-2.5 flex items-center justify-center"
+                  >
+                    {{ current.days }}
+                  </div>
+                  <span v-if="current.days" class="custom-count-down-colon text-white">天</span>
+                  <div
+                    class="w-4 h-4 bg-white/90 rounded text-black text-2.5 flex items-center justify-center"
+                  >
+                    {{ current.hours }}
+                  </div>
+                  <span class="custom-count-down-colon text-white">时</span>
+                  <div
+                    class="w-4 h-4 bg-white/90 rounded text-black text-2.5 flex items-center justify-center"
+                  >
+                    {{ current.minutes }}
+                  </div>
+                  <span class="custom-count-down-colon text-white">分</span>
+                  <div
+                    v-if="!current.days"
+                    class="w-4 h-4 bg-white/90 rounded text-black text-2.5 flex items-center justify-center"
+                  >
+                    {{ current.seconds }}
+                  </div>
+                  <span v-if="!current.days" class="custom-count-down-colon text-white">秒</span>
+                </div>
               </template>
             </wd-count-down>
             <!-- 17:02:18 -->
@@ -125,29 +160,48 @@ onShow(async () => {
               />
               <div class="flex-1">
                 <div
-                  class="w-[178px] text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal"
+                  class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal line-clamp-1 text-ellipsis overflow-hidden"
                 >
                   <!-- 海蓝之谜精华面霜60ml -->
                   {{ it.prodcutName }}
                 </div>
                 <div class="flex items-center gap-2.5">
-                  <div class="flex-1">
-                    <!-- {{ (it.exchangeCount || 0 / it.productRepertory || 0) * 100 }} -->
-                    <ProgressEvo
-                      :height="6"
-                      :model-value="(it.exchangeCount || 0 / it.productRepertory || 0) * 100"
-                      color="black"
-                    ></ProgressEvo>
-                  </div>
-                  <div
-                    class="text-black/40 text-[10px] font-normal font-['PingFang_SC'] leading-normal"
-                  >
-                    还剩{{ it.productRepertory - it.exchangeCount }}件
-                  </div>
+                  <template v-if="Number(it.isRestrict)">
+                    <div class="flex-1">
+                      <!-- {{ (it.exchangeCount || 0 / it.productRepertory || 0) * 100 }} -->
+                      <!--                      1-->
+                      <!--                      {{ it.isRestrict }}-->
+
+                      <ProgressEvo
+                        :height="6"
+                        :model-value="(it.exchangeCount || 0 / it.productRepertory || 0) * 100"
+                        color="black"
+                      ></ProgressEvo>
+                    </div>
+                    <div
+                      class="text-black/40 text-[10px] font-normal font-['PingFang_SC'] leading-normal"
+                    >
+                      还剩{{ it.productRepertory - it.exchangeCount }}件
+                    </div>
+                  </template>
+                  <template v-else>
+                    <div class="flex-1 h-6">
+                      <!--                      <ProgressEvo-->
+                      <!--                        :height="6"-->
+                      <!--                        :model-value="(it.exchangeCount || 0 / it.productRepertory || 0) * 100"-->
+                      <!--                        color="black"-->
+                      <!--                      ></ProgressEvo>-->
+                    </div>
+                    <div
+                      class="text-black/40 text-[10px] font-normal font-['PingFang_SC'] leading-normal"
+                    >
+                      <!--                      还剩{{ it.productRepertory - it.exchangeCount }}件-->
+                      数量不限
+                    </div>
+                  </template>
                 </div>
                 <div class="flex items-end gap-1 mt-5">
                   <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] pb-3">
-                    <!-- 1600 -->
                     {{ it.favourablePoints }}
                   </div>
                   <div class="text-black/60 text-sm font-normal font-['PingFang_SC'] pb-3">
@@ -164,10 +218,11 @@ onShow(async () => {
                   <div
                     class="text-black/30 text-[10px] font-normal font-['PingFang_SC'] line-through leading-normal"
                   >
-                    {{ it.points }}积分
+                    {{ it.showFavourable ? it.favourablePoints : it.points }} 积分
                   </div>
                   <div
                     class="text-black/30 text-[10px] font-normal font-['PingFang_SC'] line-through leading-normal"
+                    v-if="it.productPrice"
                   >
                     ¥{{ it.productPrice }}
                   </div>
@@ -178,7 +233,7 @@ onShow(async () => {
         </SwiperEvo>
       </div>
     </div>
-    <div class="w-full inline-flex gap-2">
+    <div class="w-full inline-flex gap-2 overflow-x-scroll whitespace-nowrap">
       <template v-for="(it, i) in categories" :key="i">
         <div>
           <wd-button

+ 9 - 1
packages/app/src/pages/home/mall/purchased/success/index.vue

@@ -17,8 +17,16 @@ const handle2Orders = () => {
   router.replace('/pages/mine/orders/index')
 }
 const handle2Mall = () => {
-  router.replace('/pages/home/mall/index')
+  const pages = getCurrentPages()
+  const mallIndex = pages.findIndex((it) => it.route === 'pages/home/mall/index')
+  if (mallIndex > -1) {
+    console.log(pages.length - mallIndex - 1)
+    router.back(pages.length - mallIndex - 1)
+  } else {
+    router.replace('/pages/home/mall/index')
+  }
 }
+onLoad(() => {})
 </script>
 <template>
   <div class="flex-grow flex flex-col px-3.5 justify-center items-center gap-7 bg-white">

+ 50 - 35
packages/app/src/pages/home/mall/shopping-cart/index.vue

@@ -11,8 +11,6 @@
 import TiltedButton from '@/components/tilted-button.vue'
 import Product from '../components/product.vue'
 import { shoppingBag } from '@designer-hub/assets/src/assets/svgs/index'
-import InvertedTrapezoidButton from '@/components/inverted-trapezoid-button.vue'
-import TrapeziumButton from '@/components/trapezium-button.vue'
 import {
   createProductItemBuy,
   deleteProductItemBuy,
@@ -36,8 +34,13 @@ const router = useRouter()
 const { userInfo } = storeToRefs(userStore)
 const selected = ref([])
 const points = computed(() =>
-  selected.value.reduce((acc, item) => acc + item.points * item.nums, 0),
+  selected.value.reduce(
+    (acc, item) =>
+      item.showFavourable ? acc + item.favourablePoints * item.nums : acc + item.points * item.nums,
+    0,
+  ),
 )
+
 const query = ref({ userId: userInfo.value?.userId })
 const handleSelect = (product) => {
   if (selected.value.map((it) => it.productId).includes(product.productId)) {
@@ -104,38 +107,29 @@ const handleProductNumsChange = async (nums, product) => {
 }
 const handlePlaceOrder = async () => {
   if (!selected.value.length) {
-    uni.showToast({ title: '请选择商品', icon: 'none' })
+    await uni.showToast({ title: '请选择商品', icon: 'none' })
     return ''
   }
-  const { code, data: res } = await requestToast(() =>
-    productPlacing({
-      isShoppingCart: 1,
-      userId: userInfo.value.userId,
-      item: 3,
-      list: selected.value.map(
-        ({ productId, prodcutName, productCoverImgUrl, nums, points, vendorId }) => ({
-          productId,
-          productName: prodcutName,
-          orderImgUrl: productCoverImgUrl,
-          nums,
-          points,
-          vendorId,
-        }),
-      ),
-      couponList: [],
-    }),
-  )
+  const body = {
+    isShoppingCart: 1,
+    userId: userInfo.value.userId,
+    item: 3,
+    list: selected.value.map(
+      ({ productId, prodcutName, productCoverImgUrl, nums, points, vendorId }) => ({
+        productId,
+        productName: prodcutName,
+        orderImgUrl: productCoverImgUrl,
+        nums,
+        points,
+        vendorId,
+      }),
+    ),
+    couponList: [],
+  }
+  const { code, data: res } = await requestToast(() => productPlacing(body))
   if (code === 0) {
-    // router.push(`/pages/home/mall/confirm-order`)
-    // await deleteProductItemBuy({
-    //   doList: selected.value.map(({ productId }) => ({
-    //     productId,
-    //     deleted: true,
-    //     userId: userInfo.value.userId,
-    //   })),
-    // })
-    pageHelperRef.value?.reload()
-    router.push(`/pages/home/mall/confirm-order/index?data=${JSON.stringify(res)}`)
+    await pageHelperRef.value?.reload()
+    await router.push(`/pages/home/mall/confirm-order/index?data=${JSON.stringify(body)}`)
   }
 }
 </script>
@@ -160,8 +154,28 @@ const handlePlaceOrder = async () => {
                     :class="`${selected.map((it) => it.productId).includes(it.productId) ? 'bg-black' : ''}`"
                   ></div>
                 </div>
-                <div class="w-[110px] h-[110px] bg-[#f6f6f6] rounded-2xl overflow-hidden">
+                <div class="w-[110px] h-[110px] bg-[#f6f6f6] rounded-2xl overflow-hidden relative">
                   <wd-img width="100%" height="100%" :src="it.productCoverImgUrl"></wd-img>
+                  <div
+                    v-if="it.status"
+                    class="absolute bottom-0 w-full h-5.5 bg-[#D7D7D7] flex items-center justify-center"
+                  >
+                    <div
+                      class="text-black/60 text-xs font-normal font-['PingFang_SC'] leading-normal"
+                    >
+                      已下架
+                    </div>
+                  </div>
+                  <div
+                    v-if="it.deleted"
+                    class="absolute bottom-0 w-full h-5.5 bg-[#D7D7D7] flex items-center justify-center"
+                  >
+                    <div
+                      class="text-black/60 text-xs font-normal font-['PingFang_SC'] leading-normal"
+                    >
+                      已失效
+                    </div>
+                  </div>
                 </div>
                 <div class="flex flex-col justify-between flex-1">
                   <div
@@ -171,9 +185,10 @@ const handlePlaceOrder = async () => {
                   </div>
                   <div class="flex items-center gap-1.25">
                     <div
-                      class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-normal"
+                      class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] leading-normal"
                     >
-                      {{ it.points }}
+                      <!--                      {{ it.points }}-->
+                      {{ it.showFavourable ? it.favourablePoints : it.points }}
                     </div>
                     <div
                       class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[34px]"

+ 137 - 71
packages/app/src/pages/home/moment/index.vue

@@ -34,19 +34,24 @@ import { getRect, addUnit } from 'wot-design-uni/components/common/util'
 import Card from '@/components/card.vue'
 import { get } from 'radash'
 import { DictType } from '../../../core/libs/models'
+import MomentVideo from '@/pages/home/components/moment-video.vue'
+import { ComponentExposed } from 'vue-component-type-helpers'
+import { useShare } from '@/composables/share'
 
 const { features, clickByPermission } = usePermissions()
+const { shareAppMessage } = useShare()
 const userStore = useUserStore()
 const { userInfo } = storeToRefs(userStore)
 const router = useRouter()
 const dictStore = useDictStore()
 const { getOptionLabel } = dictStore
 const id = ref()
-const currentImg = ref(0)
+const current = ref(0)
 const isShared = ref(false)
-const commeentRef = ref<InstanceType<typeof WdInput>>()
+const commentRef = ref<InstanceType<typeof WdInput>>()
 const commentItemRef = ref<InstanceType<typeof CommentItem>[]>()
 const instance = getCurrentInstance()
+const momentVideoRef = ref<ComponentExposed<typeof MomentVideo>[]>()
 
 const focus = ref(false)
 const { data, run } = useRequest(() => getCircle(id.value), { initialData: {} })
@@ -62,24 +67,25 @@ const { data: circleUpvotes, run: setCircleUpvotes } = useRequest(
   () => getCircleUpvotes(id.value),
   { initialData: { list: [], total: 0 } },
 )
-const swiperSizes = ref()
 const swiperStyle = ref()
 const reviewContent = ref('')
-const isVideo = ref(false)
 const reviewId = ref()
 const refreshIndex = ref<number>()
-const handleChange = ({ detail: { current } }) => {
-	// console.log(current)
-	currentImg.value = current;
-  // swiperStyle.value = {
-  //   height: swiperSizes.value[current].height + 'px',
-  // }
+const isVideo = computed(
+  () => data.value?.bannerUrls?.length && isImageOrVideo(data.value.bannerUrls.at(0)) === 'video',
+)
+const handleChange = ({ detail }) => {
+  console.log('current', current.value, detail.current)
+  console.log(momentVideoRef.value)
+  if (isVideo.value) {
+    momentVideoRef.value[current.value]?.videoContext.pause()
+  }
+  current.value = detail.current
 }
 const setSwiperStyle = async () => {
   if (!data.value.bannerUrls.length) return
   const { screenWidth } = await uni.getSystemInfo()
-  if (data.value.bannerUrls.length === 1 && isImageOrVideo(data.value.bannerUrls[0]) === 'video') {
-    isVideo.value = true
+  if (isImageOrVideo(data.value.bannerUrls[0]) === 'video') {
     return
   }
 
@@ -90,17 +96,19 @@ const setSwiperStyle = async () => {
     height:
       height > width
         ? addUnit(500)
-        : height === width?addUnit(screenWidth):addUnit(
-            screenWidth / width > 1
-              ? height / (screenWidth / width)
-              : height * (screenWidth / width),
-          ),
+        : height === width
+          ? addUnit(screenWidth)
+          : addUnit(
+              screenWidth / width > 1
+                ? height / (screenWidth / width)
+                : height * (screenWidth / width),
+            ),
   }
   console.log('swiperStyle', swiperStyle.value)
 }
 const handleSend = async () => {
   if (!reviewContent.value) {
-    uni.showToast({ title: '请输入评论内容', icon: 'none' })
+    await uni.showToast({ title: '请输入评论内容', icon: 'none' })
     return
   }
   const { code, msg } = await createCircleReview({
@@ -111,13 +119,14 @@ const handleSend = async () => {
     replayReviewId: reviewId.value,
   })
   if (code !== 0) {
-    uni.showToast({ title: msg, icon: 'none' })
+    await uni.showToast({ title: msg, icon: 'none' })
   } else {
     reviewContent.value = ''
-    uni.showToast({ title: '评论成功', icon: 'none' })
-    if (refreshIndex.value) {
+    console.log('refreshIndex', refreshIndex.value)
+    await uni.showToast({ title: '评论成功', icon: 'none' })
+    if (refreshIndex.value !== undefined) {
       console.log(instance.refs)
-      commentItemRef.value.at(refreshIndex.value).refresh()
+      await commentItemRef.value.at(refreshIndex.value).refresh()
       reviewId.value = undefined
       refreshIndex.value = undefined
     } else {
@@ -133,22 +142,29 @@ const handleReplay = async (options) => {
 }
 const handleDelete = async (index?: number) => {
   if (index !== undefined) {
-    commentItemRef.value.at(index).refresh()
+    await commentItemRef.value.at(index).refresh()
   } else {
     await runGetReviews()
   }
 }
 const handleUpvote = async (index?: number) => {
   if (index !== undefined) {
-    commentItemRef.value.at(index).refresh()
+    await commentItemRef.value.at(index).refresh()
   } else {
     await run()
   }
 }
+const toDesignerHomepage = () => {
+  if (['1', '2'].includes(String(data.value?.circleType))) {
+    router.push(
+      `/pages/mine/homepage/index?id=${data.value?.stylistId}${isShared.value ? '&isShared=true' : ''}`,
+    )
+  }
+}
 onMounted(async () => {})
-onLoad(async (query: { id: string; isShared?: boolean }) => {
-  id.value = query.id
-  isShared.value = query.isShared
+onLoad(async (query?: { id: string; isShared?: boolean }) => {
+  id.value = query?.id
+  isShared.value = query?.isShared
   await run()
   await setSwiperStyle()
   await runGetReviews()
@@ -158,31 +174,13 @@ onLoad(async (query: { id: string; isShared?: boolean }) => {
 //   await shareCircle(id.value)
 //   return { title: data.value?.circleDesc }
 // })
-onShareAppMessage(async ({ from, target }) => {
-  console.log('from', from)
-  console.log('target', target)
-  // if (!features.value.shareMoment) {
-  //   return handleShareClick()
-  // }
-  const res: Page.CustomShareContent = {}
-  await shareCircle(id.value)
-  res.path = `/pages/home/moment/index?id=${id.value}&isShared=true`
-  res.imageUrl = data.value?.bannerUrls[0]
-  res.title = `${data.value?.stylistName}: ${data.value?.circleDesc}`
-  return res
-})
+onShareAppMessage(shareAppMessage)
 </script>
 <template>
   <view class="bg-white flex-grow">
     <NavBarEvo placeholder :isShowBack="!isShared">
       <template #prepend>
-        <div
-          class="flex items-center gap-2"
-          @click="
-            ['1', '2'].includes(data.circleType) &&
-            router.push(`/pages/mine/homepage/index?id=${data.stylistId}`)
-          "
-        >
+        <div class="flex items-center gap-2" @click="toDesignerHomepage">
           <wd-img width="24" height="24" round :src="data.headUrl"></wd-img>
           <div class="text-black/90 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]">
             {{ data.stylistName || data.marketing }}
@@ -196,23 +194,58 @@ onShareAppMessage(async ({ from, target }) => {
     <template v-if="data.circleType === '1'">
       <template v-if="!isVideo">
         <div class="pos-relative">
-		  <div class="currentImg">{{ currentImg + 1 }}/{{data?.bannerUrls.length}}</div>	
-          <swiper :current="currentImg" :style="swiperStyle" @change="handleChange" :indicator-dots="true">
+          <div class="currentImg">{{ current + 1 }}/{{ data?.bannerUrls.length }}</div>
+          <swiper
+            :current="current"
+            :style="swiperStyle"
+            @change="handleChange"
+            :indicator-dots="true"
+          >
             <template v-for="it of data?.bannerUrls" :key="it">
               <swiper-item>
-                <wd-img width="100%" height="100%" :src="it" mode="aspectFill" :enable-preview="true"></wd-img>
+                <wd-img
+                  width="100%"
+                  height="100%"
+                  :src="it"
+                  mode="aspectFill"
+                  :enable-preview="true"
+                ></wd-img>
               </swiper-item>
             </template>
           </swiper>
         </div>
       </template>
       <template v-if="isVideo">
-        <video width="100%" class="w-full aspect-[1.64/1]" :src="data?.bannerUrls[0]"></video>
+        <div class="relative aspect-[1.64/1]">
+          <div class="currentImg">{{ current + 1 }}/{{ data?.bannerUrls.length }}</div>
+          <swiper @change="handleChange">
+            <template v-for="it of data?.bannerUrls" :key="it">
+              <swiper-item>
+                <!--                <video-->
+                <!--                  ref="momentVideo"-->
+                <!--                  width="100%"-->
+                <!--                  class="w-full aspect-[1.64/1]"-->
+                <!--                  :controls="'contimg'"-->
+                <!--                  :loop="true"-->
+                <!--                  :enable-progress-gesture="false"-->
+                <!--                  :src="it"-->
+                <!--                ></video>-->
+                <MomentVideo
+                  ref="momentVideoRef"
+                  :src="it"
+                  :enable-progress-gesture="false"
+                ></MomentVideo>
+              </swiper-item>
+            </template>
+          </swiper>
+        </div>
       </template>
     </template>
     <template v-if="data.circleType === '2'">
       <div>
-        <wd-img width="100%" mode="widthFix" :src="data.bannerUrls[0]"></wd-img>
+        <div class="aspect-[1.02/1]">
+          <wd-img width="100%" height="100%" mode="aspectFill" :src="data.bannerUrls[0]"></wd-img>
+        </div>
         <div class="relative">
           <div
             class="absolute top-0 left-3.5 right-3.5 box-border h-full flex items-center justify-center"
@@ -287,8 +320,8 @@ onShareAppMessage(async ({ from, target }) => {
         <view class="flex-1"></view>
         <view><wd-icon class="text-black/65" name="arrow-right" size="22px"></wd-icon></view>
       </view> -->
-      <div class="h-0.25 bg-[#dadada] my-7"></div>
-      <SectionHeading :title="`评论`" size="base">
+      <div v-if="!isShared" class="h-0.25 bg-[#dadada] my-7"></div>
+      <SectionHeading v-if="!isShared" :title="`评论`" size="base">
         <template #append>
           <view v-if="reviews?.list" class="flex">
             <div class="text-black/90 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]">
@@ -306,7 +339,7 @@ onShareAppMessage(async ({ from, target }) => {
         </template>
       </SectionHeading>
 
-      <view clas="mt-8.25">
+      <view v-if="!isShared" clas="mt-8.25">
         <template v-if="reviews?.list.length">
           <template v-for="(it, i) in reviews?.list" :key="it.id">
             <CommentItem
@@ -338,11 +371,11 @@ onShareAppMessage(async ({ from, target }) => {
         </template>
       </view>
     </div>
-    <BottomAppBar fixed placeholder border custom-class="">
+    <BottomAppBar v-if="!isShared" fixed placeholder border custom-class="">
       <div class="bg-white flex items-center">
         <div class="w-[168px] bg-[#f6f6f6] rounded-[60px] px-3.5 py-2 flex items-center">
           <wd-input
-            ref="commeentRef"
+            ref="commentRef"
             custom-class="bg-transparent!"
             no-border
             confirm-type="send"
@@ -356,16 +389,44 @@ onShareAppMessage(async ({ from, target }) => {
         </div>
         <view class="flex justify-around flex-1">
           <div>
-            <button open-type="share" class="bg-transparent! p-0!">
+            <template v-if="features.shareMoment">
+              <button
+                open-type="share"
+                class="bg-transparent! p-0!"
+                :data-options="data"
+                @click.stop
+              >
+                <view
+                  class="flex flex-col items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
+                >
+                  <div class="w-4.5 h-4.5 flex items-center justify-center">
+                    <wd-img width="15" height="15" src="/static/svgs/share.svg"></wd-img>
+                  </div>
+                  <view class="">{{ data?.shareCount || 0 }}</view>
+                </view>
+              </button>
+            </template>
+            <template v-else>
+              <!--              <view-->
+              <!--                @click.stop="clickByPermission('share', () => emits('share', options))"-->
+              <!--                class="flex items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"-->
+              <!--              >-->
+              <!--                <wd-img width="15" height="15" src="/static/svgs/share.svg"></wd-img>-->
+              <!--                <view class="ml-1">{{ props.options.shareCount }}</view>-->
+              <!--              </view>-->
               <view
                 class="flex flex-col items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
+                @click.stop="clickByPermission('share', () => run())"
               >
                 <div class="w-4.5 h-4.5 flex items-center justify-center">
                   <wd-img width="15" height="15" src="/static/svgs/share.svg"></wd-img>
                 </div>
                 <view class="">{{ data?.shareCount || 0 }}</view>
               </view>
-            </button>
+            </template>
+
+            <!--            <button open-type="share" class="bg-transparent! p-0!">-->
+            <!--            </button>-->
           </div>
           <view
             class="flex flex-col items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
@@ -402,18 +463,23 @@ onShareAppMessage(async ({ from, target }) => {
         </view>
       </div>
     </BottomAppBar>
+    <BottomAppBar v-if="isShared" fixed placeholder>
+      <div>
+        <wd-button block :round="false" @click="toDesignerHomepage">查看设计师主页</wd-button>
+      </div>
+    </BottomAppBar>
   </view>
 </template>
 <style lang="scss" scope>
-	.currentImg{
-		position:absolute;
-		z-index:2;
-		color:#fff;
-		background-color: rgba(0,0,0,.5);
-		top:10rpx;
-		right:10rpx;
-		padding:4rpx 20rpx;
-		font-size:24rpx;
-		border-radius: 8rpx;
-	}
-</style>
+.currentImg {
+  position: absolute;
+  z-index: 2;
+  color: #fff;
+  background-color: rgba(0, 0, 0, 0.5);
+  top: 10rpx;
+  right: 10rpx;
+  padding: 4rpx 20rpx;
+  font-size: 24rpx;
+  border-radius: 8rpx;
+}
+</style>

+ 137 - 15
packages/app/src/pages/home/offline-activity/cycling-rankings/index.vue

@@ -4,28 +4,80 @@
 <script setup lang="ts">
 import NavbarEvo from '@/components/navbar-evo.vue'
 import { NetImages } from '../../../../core/libs/net-images'
-import { getRidings } from '../../../../core/libs/requests'
+import { getRidingOptions, getRidings } from '../../../../core/libs/requests'
 import PageHelperEvo from '@/components/page-helper-evo.vue'
+import BottomAppBar from '@/components/bottom-app-bar.vue'
+import { useUserStore } from '@/store'
+import { storeToRefs } from 'pinia'
 
+const userStore = useUserStore()
+const { userInfo } = storeToRefs(userStore)
+const current = ref('')
 // const { data, run } = useRequest(() => ({}), { initialData: [] })
+const { data: ridingOptions, run: setRidingOptions } = useRequest(() => getRidingOptions(), {
+  initialData: [],
+})
+const query = computed(() => (current.value ? { activityId: current.value } : {}))
+onLoad(async () => {
+  await setRidingOptions()
+})
+onShareAppMessage(() => ({
+  title: '骑行排行榜',
+}))
+onShareTimeline(() => ({
+  title: '骑行排行榜',
+}))
 </script>
 <template>
   <div class="flex-grow flex flex-col bg-[length:100%]">
     <NavbarEvo transparent dark title="骑行榜"></NavbarEvo>
-    <div class="aspect-[1.19/1]">
+    <div class="aspect-[1.19/1] relative">
       <wd-img
         width="100%"
         custom-class="vertical-bottom"
         mode="widthFix"
         :src="NetImages.CyclingRankingsHeaderBg"
       ></wd-img>
+      <div class="absolute bottom-50 left-7.25">
+        <wd-picker
+          v-model="current"
+          :columns="[
+            { label: '全部', value: '' },
+            ...ridingOptions.map((it) => ({ label: it.name, value: it.id })),
+          ]"
+          use-default-slot
+        >
+          <div class="text-[white] text-sm font-normal font-['PingFang_SC'] leading-relaxed">
+            <!--          {{ dayjs(year).format('YYYY') }}-->
+            {{ ridingOptions.find((it) => it.id === current)?.name ?? '全部' }}
+            <wd-icon name="arrow-down" size="12" class="text-[#ffffff]"></wd-icon>
+          </div>
+        </wd-picker>
+      </div>
     </div>
-    <PageHelperEvo class="flex-grow flex flex-col" :request="getRidings" :query="{}">
+    <PageHelperEvo
+      class="flex-grow flex flex-col"
+      :request="getRidings"
+      :is-load-more="false"
+      :query="query"
+    >
       <template #default="{ source }">
         <div class="flex-grow relative bg-[linear-gradient(180deg,#EFEFEF_0.4%,#FFF_21.41%)]">
           <div class="absolute h-4 top--4 left-0 right-0 bg-[#EFEFEF] rounded-t-2xl"></div>
           <div class="flex justify-between">
-            <template v-for="(it, i) in source.list.slice(0, 3)" :key="i">
+            <template
+              v-for="(it, i) in [
+                ...source.list.slice(0, 3),
+                ...Array.from({
+                  length: source?.list?.length < 3 ? 3 - source?.list?.length : 0,
+                }).map(() => ({
+                  radeKmTotal: '0',
+                  avatar: '',
+                  memberStylistName: '暂无',
+                })),
+              ]"
+              :key="i"
+            >
               <div
                 class="flex-1 py-2 relative"
                 :class="`${i === 0 ? 'w-[143px] h-[141px] bg-white rounded-xl relative bottom-8 pt-4 order-1 ' : ''}`"
@@ -43,14 +95,34 @@ import PageHelperEvo from '@/components/page-helper-evo.vue'
                 >
                   骑行里程km
                 </div>
-                <div class="absolute top--30 left-0 right-0 flex flex-col items-center gap-2">
-                  <wd-img
-                    class="rounded-full border-2 border-solid border-[#c5cdd4] overflow-hidden"
-                    width="71"
-                    height="71"
-                    round
-                    :src="it.avatar"
-                  ></wd-img>
+                <div class="absolute top--34 left-0 right-0 flex flex-col items-center gap-2">
+                  <div class="relative">
+                    <wd-img
+                      class="rounded-full border-2 border-solid border-[#c5cdd4] overflow-hidden"
+                      width="71"
+                      height="71"
+                      round
+                      :src="it.avatar || NetImages.DefaultAvatar"
+                    ></wd-img>
+                    <div
+                      class="absolute"
+                      :class="
+                        { 0: 'top--5.5 left--.5', 1: 'top--0 left--.5', 2: 'top--.5 left--.5' }[i]
+                      "
+                    >
+                      <wd-img
+                        width="76"
+                        mode="widthFix"
+                        :src="
+                          {
+                            0: NetImages.RankingOne,
+                            1: NetImages.RankingTwo,
+                            2: NetImages.RankingThree,
+                          }[i]
+                        "
+                      ></wd-img>
+                    </div>
+                  </div>
                   <div
                     class="text-center text-white text-base font-normal font-['PingFang_SC'] leading-relaxed"
                   >
@@ -61,15 +133,19 @@ import PageHelperEvo from '@/components/page-helper-evo.vue'
               </div>
             </template>
           </div>
-          <div class="flex flex-col gap-8 mt--10">
+          <div class="flex flex-col gap-8 mt--14">
             <template v-for="(it, i) in source.list" :key="i">
               <div v-if="i > 2" class="flex items-center gap-2 px-7">
                 <div
-                  class="text-center w-8 text-black/40 text-base font-bold font-['DIN'] leading-relaxed"
+                  class="text-center w-8 text-black/40 text-base font-bold font-['D-DIN-PRO'] leading-relaxed"
                 >
                   {{ i < 9 ? '0' + (i + 1) : i + 1 }}
                 </div>
-                <wd-img class="w-8 h-8 rounded-full" round :src="it.avatar" />
+                <wd-img
+                  class="w-8 h-8 rounded-full"
+                  round
+                  :src="it.avatar || NetImages.DefaultAvatar"
+                />
                 <div
                   class="text-center text-black/80 text-sm font-normal font-['PingFang_SC'] leading-relaxed"
                 >
@@ -92,6 +168,52 @@ import PageHelperEvo from '@/components/page-helper-evo.vue'
             </template>
           </div>
         </div>
+        <BottomAppBar fixed placeholder custom-class="p-0!">
+          <div class="bg-gradient-to-b from-neutral-100 to-white shadow-inner">
+            <div class="py-3 flex items-center gap-2 px-7">
+              <div
+                class="text-center w-8 text-black/40 text-base font-bold font-['D-DIN-PRO'] leading-relaxed"
+              >
+                <!--            {{ i < 9 ? '0' + (i + 1) : i + 1 }}-->
+                {{
+                  source.list.findIndex((it) => it.memberStylistName === userInfo.nickname) === -1
+                    ? '-'
+                    : source.list.findIndex((it) => it.memberStylistName === userInfo.nickname) +
+                          1 <
+                        9
+                      ? '0' +
+                        (source.list.findIndex((it) => it.memberStylistName === userInfo.nickname) +
+                          1)
+                      : source.list.findIndex((it) => it.memberStylistName === userInfo.nickname) +
+                        1
+                }}
+              </div>
+              <wd-img class="w-12 h-12 rounded-full" round :src="userInfo.avatar" />
+              <div
+                class="text-center text-black/80 text-sm font-normal font-['PingFang_SC'] leading-relaxed"
+              >
+                {{ userInfo.nickname ?? NetImages.DefaultAvatar }}
+              </div>
+              <div class="flex-1"></div>
+              <div>
+                <div
+                  class="text-center text-black/80 text-xl font-bold font-['D-DIN'] leading-relaxed"
+                >
+                  <!--              {{ it.radeKmTotal }}-->
+                  {{
+                    source.list.find((it) => it.memberStylistName === userInfo.nickname)
+                      ?.radeKmTotal ?? 0
+                  }}
+                </div>
+                <div
+                  class="text-center text-black/40 text-[10px] font-normal font-['PingFang_SC'] leading-relaxed"
+                >
+                  累计里程km
+                </div>
+              </div>
+            </div>
+          </div>
+        </BottomAppBar>
       </template>
     </PageHelperEvo>
   </div>

+ 1 - 3
packages/app/src/pages/home/offline-activity/index.vue

@@ -90,9 +90,7 @@ onMounted(async () => {
       <div
         class="text-justify text-black/40 text-base font-normal font-['PingFang_SC'] leading-relaxed"
       >
-        我们为您精心打造了一个独特且极具价值的活动营。这个项目的核心旨在全方位提升您作为设计师的能力。
-        在这里,您将拥有无比优质的游学资源。我们与全球知名的设计学府、顶尖设计工作室以及具有代表性的经典建筑和室内空间建立了紧密合作。您将有机会深入这些卓越的场所,亲身体验最前沿的设计理念和实践。
-        参与专业的研讨会和工作坊,与同行们分享见解、碰撞思维火花,进一步深化对设计的理解。
+        以师为核心策划线下活动体系,通过深入的交流、创新的体验、贴心的关爱,打造丰富多彩的活动。这一体系旨在创建一个充满互动的平台,促进设计师之间的思想碰撞与灵感交流,同时会为设计师打造专业的线下课程,通过专业的培训和讲座不断成长,提升自身技能和职业素养。
       </div>
     </card>
     <wd-tabs

+ 7 - 1
packages/app/src/pages/home/offline-activity/list/index.vue

@@ -20,6 +20,12 @@ const { data: tabs, run: setTabs } = useRequest(() => getByDictType(DictType.Mem
 onMounted(async () => {
   await setTabs()
 })
+onShareAppMessage(() => ({
+  title: '线下活动',
+}))
+onShareTimeline(() => ({
+  title: '线下活动',
+}))
 </script>
 <template>
   <div class="flex-grow flex flex-col gap-6 mx-3.5">
@@ -79,7 +85,7 @@ onMounted(async () => {
                           兑换积分:
                         </div>
                         <div
-                          class="text-[#ef4343] text-xl font-normal font-['D-DIN Exp'] leading-[34px]"
+                          class="text-[#ef4343] text-xl font-normal font-['D-DIN_Exp'] leading-[34px]"
                         >
                           {{ it.needPointsCount }}
                         </div>

+ 20 - 18
packages/app/src/pages/home/schedule/index.vue

@@ -110,26 +110,28 @@ onMounted(async () => {
                   {{ item.travelDesc }}
                 </span>
               </div>
-              <view class="flex items-center my-4">
-                <wd-img width="16" height="16" :src="camera"></wd-img>
+              <template v-if="item.clockExplainUrl">
+                <view class="flex items-center my-4">
+                  <wd-img width="16" height="16" :src="camera"></wd-img>
+                  <div
+                    class="ml-1 text-black/90 text-xs font-normal font-['PingFang_SC'] leading-normal"
+                  >
+                    打卡示例
+                  </div>
+                </view>
+                <div class="w-[285px]">
+                  <img
+                    v-if="item.clockExplainUrl"
+                    class="w-[285px] h-[157px] rounded-lg"
+                    :src="item.clockExplainUrl"
+                  />
+                </div>
                 <div
-                  class="ml-1 text-black/90 text-xs font-normal font-['PingFang_SC'] leading-normal"
+                  class="mt-2.5 text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
                 >
-                  打卡示例
+                  {{ item.clockExplainDesc }}
                 </div>
-              </view>
-              <div class="w-[285px]">
-                <img
-                  v-if="item.clockExplainUrl"
-                  class="w-[285px] h-[157px] rounded-lg"
-                  :src="item.clockExplainUrl"
-                />
-              </div>
-              <div
-                class="mt-2.5 text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
-              >
-                {{ item.clockExplainDesc }}
-              </div>
+              </template>
             </view>
           </view>
         </template>
@@ -142,7 +144,7 @@ onMounted(async () => {
         <!-- <div class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[34px]">
           发布圈子可得
         </div>
-        <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-normal">
+        <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] leading-normal">
           16000
         </div>
         <div class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[34px]">

+ 11 - 4
packages/app/src/pages/home/spread/case-shooting/index.vue

@@ -7,6 +7,12 @@ import { useRouter } from '../../../../core/utils/router'
 import { getProducts } from '../../../../core/libs/requests'
 
 const router = useRouter()
+onShareAppMessage(() => ({
+  title: '案例拍摄',
+}))
+onShareTimeline(() => ({
+  title: '案例拍摄',
+}))
 </script>
 <template>
   <div class="flex-grow p-3.5">
@@ -45,19 +51,20 @@ const router = useRouter()
               </div>
               <div class="flex items-center">
                 <div
-                  class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-normal"
+                  class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] leading-normal"
                 >
                   <!-- 1600 -->
-                  {{ it.points }}
+                  {{ it.showFavourable ? it.favourablePoints : it.points }}
                 </div>
                 <div class="text-black/60 text-sm font-normal font-['PingFang_SC'] leading-[34px]">
-                  积分
+                  {{ String(it.needPoints) == '1' ? '折' : '积分' }}
                 </div>
                 <div class="flex-1"></div>
                 <div
                   class="w-[53px] text-black/30 text-xs font-normal font-['PingFang_SC'] line-through leading-normal"
+                  v-if="Number(data?.productPrice)"
                 >
-                  2000积分
+                  ¥{{ it.productPrice }}
                 </div>
               </div>
             </div>

+ 1 - 1
packages/app/src/pages/home/spread/case-shooting/photographer/index.vue

@@ -34,7 +34,7 @@ const data = ref([{}, {}, {}, {}, {}])
     <div
       class="bg-white/90 backdrop-blur-[20px] flex px-10 py-2.5 border-t-1 border-t-solid border-t-[#ececec]"
     >
-      <div class="text-[#ef4343] text-2xl font-normal font-['D-DIN Exp'] leading-normal">0</div>
+      <div class="text-[#ef4343] text-2xl font-normal font-['D-DIN_Exp'] leading-normal">0</div>
       <div class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-[34px]">
         积分
       </div>

+ 3 - 1
packages/app/src/pages/home/spread/index.vue

@@ -19,6 +19,8 @@ const { data, run: setData } = useRequest(
   () =>
     getContents({
       contentType: '3',
+      contentCategory: '301',
+      pageSize: '3',
     }),
   {
     initialData: { list: [] },
@@ -87,7 +89,7 @@ onMounted(async () => {
 "
     ></info-card>
     <section-heading title="最新资讯"></section-heading>
-    <div class="my-6">
+    <div class="my-6 flex flex-col gap-6">
       <template v-for="(it, i) in data.list" :key="i">
         <ElegantInfoCard :options="it"></ElegantInfoCard>
       </template>

+ 58 - 33
packages/app/src/pages/home/spread/product-detail/index.vue

@@ -35,26 +35,32 @@ const { data, run: setData } = useRequest(() => getProduct(id.value))
 
 const handleConfirm = async () => {
   if (type.value === 'orderNow') {
-    const { data: res, code } = await requestToast(() =>
-      productPlacing({
-        isShoppingCart: 0,
-        userId: userInfo.value.userId,
-        item: item.value,
-        list: [
-          {
-            productId: id.value,
-            points: data.value.points,
-            nums: 1,
-            productName: data.value.prodcutName,
-            orderImgUrl: data.value.productCoverImgUrl,
-            vendorId: data.value.vendorId,
-          },
-        ],
-        couponList: [],
-      }),
-    )
-    if (code !== 0) return
-    router.push(`/pages/home/mall/confirm-order/index?data=${JSON.stringify(res)}`)
+    const body = {
+      isShoppingCart: 0,
+      userId: userInfo.value.userId,
+      item: item.value,
+      list: [
+        {
+          productId: id.value,
+          points: data.value.showFavourable ? data.value.favourablePoints : data.value.points,
+          nums: 1,
+          productName: data.value.prodcutName,
+          orderImgUrl: data.value.productCoverImgUrl,
+          vendorId: data.value.vendorId,
+        },
+      ],
+      couponList: [],
+    }
+    const { data: res, code } = await requestToast(() => productPlacing(body))
+    if (code !== 0) {
+      uni.showToast({
+        title: res.msg,
+        icon: 'none',
+      })
+    } else {
+      await router.push(`/pages/home/mall/confirm-order/index?data=${JSON.stringify(body)}`)
+    }
+    // router.push(`/pages/home/mall/confirm-order/index?data=${JSON.stringify(res)}`)
   }
 }
 onLoad(async (query: { id: string; title: string; item: string }) => {
@@ -63,6 +69,12 @@ onLoad(async (query: { id: string; title: string; item: string }) => {
   uni.setNavigationBarTitle({ title: query.title })
   await setData()
 })
+onShareAppMessage(() => ({
+  title: data.value?.prodcutName,
+}))
+onShareTimeline(() => ({
+  title: data.value?.prodcutName,
+}))
 </script>
 
 <template>
@@ -74,7 +86,7 @@ onLoad(async (query: { id: string; title: string; item: string }) => {
     </div>
     <div class="relative flex-1 bg-white p-4 flex flex-col gap-4 rounded-tl-2xl rounded-tr-2xl">
       <div class="flex items-center gap-1">
-        <div class="text-[#ef4343] text-[26px] font-normal font-['D-DIN Exp'] leading-normal">
+        <div class="text-[#ef4343] text-[26px] font-normal font-['D-DIN_Exp'] leading-normal">
           {{ data?.points }}
         </div>
         <div class="text-black/60 text-base font-normal font-['PingFang_SC'] leading-[34px]">
@@ -122,31 +134,44 @@ onLoad(async (query: { id: string; title: string; item: string }) => {
   <div 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%" :src="data?.productDetailsImgUrl" />
+<!--        <wd-img width="100%" height="100%" :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-7 flex flex-col gap-6 rounded-tl-2xl rounded-tr-2xl">
+      <div
+        v-if="data.needPoints == 1"
+        class="text-black text-xl font-normal font-['PingFang_SC'] leading-[10.18px] mr-1"
+      >
+        {{ data.points }}折
+      </div>
       <div class="flex">
-        <div class="w-[67px] text-black text-xl font-normal font-['PingFang_SC'] leading-[10.18px]">
+        <div class="text-black text-xl font-normal font-['PingFang_SC'] leading-[10.18px]">
           {{ data?.prodcutName }}
         </div>
-        <div
-          class="w-[67px] text-black/60 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
-        >
-          实景摄影
-        </div>
+      </div>
+      <div class="text-black/60 text-sm font-normal font-['PingFang_SC']">
+        {{ data?.exchangeDesc }}
       </div>
       <div
         class="text-justify text-black/40 text-base font-normal font-['PingFang_SC'] leading-relaxed"
         v-html="data?.contentDesc"
       ></div>
     </div>
-    <BottomAppBar fixed placeholder>
+
+    <BottomAppBar fixed placeholder v-if="data.needPoints != 1">
       <div
         class="bg-white/90 backdrop-blur-[20px] flex px-10 py-2.5 border-t-1 border-t-solid border-t-[#ececec]"
       >
-        <div class="text-[#ef4343] text-2xl font-normal font-['D-DIN Exp'] leading-normal">
-          {{ data?.points }}
+        <div class="text-[#ef4343] text-2xl font-normal font-['D-DIN_Exp'] leading-normal">
+          <!--          {{ data?.points }}-->
+          {{ data?.showFavourable ? data.favourablePoints : data.points }}
         </div>
         <div class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-[34px]">
           积分
@@ -175,8 +200,8 @@ onLoad(async (query: { id: string; title: string; item: string }) => {
               {{ data?.prodcutName }}
             </div>
             <div class="flex items-center">
-              <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-normal">
-                {{ data?.points }}
+              <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] leading-normal">
+                {{ data?.showFavourable ? data?.favourablePoints : data?.points }}
               </div>
               <div class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[34px]">
                 积分

+ 10 - 3
packages/app/src/pages/home/spread/wx-agent-operation/index.vue

@@ -7,6 +7,12 @@ import { useRouter } from '../../../../core/utils/router'
 import { getProducts } from '../../../../core/libs/requests'
 
 const router = useRouter()
+onShareAppMessage(() => ({
+  title: '微信代运营',
+}))
+onShareTimeline(() => ({
+  title: '微信代运营',
+}))
 </script>
 <template>
   <PageHelper
@@ -43,18 +49,19 @@ const router = useRouter()
               </div>
             </div>
             <div class="flex items-center">
-              <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-normal">
+              <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] leading-normal">
                 <!-- 1600 -->
-                {{ it.points }}
+                {{ it.showFavourable ? it.favourablePoints : it.points }}
               </div>
               <div class="text-black/60 text-sm font-normal font-['PingFang_SC'] leading-[34px]">
                 积分
               </div>
               <div class="flex-1"></div>
               <div
+                v-if="Number(data?.productPrice)"
                 class="w-[53px] text-black/30 text-xs font-normal font-['PingFang_SC'] line-through leading-normal"
               >
-                2000积分
+                ¥{{it.productPrice}}
               </div>
             </div>
           </div>

+ 16 - 11
packages/app/src/pages/home/study-tour/components/register-card.vue

@@ -6,7 +6,9 @@ import TiltedButton from '@/components/tilted-button.vue'
 import { getActivitySignups, getStudyTourSignups } from '../../../../core/libs/requests'
 import { NetImages } from '../../../../core/libs/net-images'
 import { useRouter } from '../../../../core/utils/router'
-import { getActivityStatusButtonText, getActivityStatusText } from '../../../../core/utils/common'
+import AvatarGroupCasual from '@/components/avatar-group-casual/avatar-group-casual.vue'
+import { omit } from 'radash'
+import { useActivity } from '@/composables/activity'
 
 const props = defineProps<{
   customClass?: string
@@ -14,9 +16,11 @@ const props = defineProps<{
 }>()
 const router = useRouter()
 const { data: signups, run: setSignups } = useRequest(
-  () => getStudyTourSignups({ studyId: props.options!.id.toString() }),
+  () => getStudyTourSignups({ studyId: props.options!.id.toString(), pageSize: -1 }),
   { initialData: { list: [], total: 0 } },
 )
+const activityOptions = computed(() => omit(props.options, ['levelsByMemberLevel']))
+const { statusText, listItemButtonText } = useActivity(activityOptions)
 onMounted(async () => {
   await setSignups()
 })
@@ -33,17 +37,21 @@ onMounted(async () => {
         ></wd-img>
       </div>
       <div
-        class="w-[63px] h-[29px] bg-black/60 rounded-[20px] backdrop-blur-[15px] absolute top-5 right-3.5 flex items-center justify-center"
+        class="px-2.5 h-[29px] bg-black/60 rounded-[20px] backdrop-blur-[15px] absolute top-5 right-3.5 flex items-center justify-center"
       >
         <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-relaxed">
-          {{ getActivityStatusText(options?.applyStartTime, options?.applyEndTime) }}
+          {{ statusText }}
         </div>
       </div>
       <view class="absolute bottom-0 left-0 right-0">
         <view class="flex items-center mx-4 my-2.5 gap-1">
           <avatar-group-casual
             :show-number="3"
-            :urls="signups.list.map((it) => it.avatar || NetImages.DefaultAvatar)"
+            :urls="
+              signups.list
+                .filter((it) => it.userId)
+                .map((it) => it.headImgUrl || NetImages.DefaultAvatar)
+            "
           ></avatar-group-casual>
           <div
             class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
@@ -51,7 +59,8 @@ onMounted(async () => {
             {{ signups.total }}人已报名
           </div>
         </view>
-        <div class="bg-[#27130d]/50 rounded-bl-2xl rounded-br-2xl backdrop-blur-[20px] p-3.5">
+        <div class="w-full h-[46px] absolute top-0 bg-gradient-to-t from-black to-black/00"></div>
+        <div class="bg-[#010102]/50 rounded-bl-2xl rounded-br-2xl backdrop-blur-[20px] p-3.5">
           <div
             class="w-[293px] text-white text-xl font-normal font-['PingFang_SC'] leading-relaxed"
           >
@@ -90,11 +99,7 @@ onMounted(async () => {
               <div class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC']">积分</div>
             </view>
             <tilted-button custom-class="" size="large" color="white">
-              {{
-                options?.ifSingnUp
-                  ? '已报名'
-                  : getActivityStatusButtonText(options?.applyStartTime, options?.applyEndTime)
-              }}
+              {{ options?.ifSingnUp ? '已报名' : listItemButtonText }}
             </tilted-button>
           </view>
         </div>

+ 1 - 1
packages/app/src/pages/home/study-tour/components/study-tour-card.vue

@@ -34,7 +34,7 @@ const toDetail = () => {
       <div class="flex gap-1">
         <wd-img width="23" height="23" :src="map"></wd-img>
         <div class="text-white text-base font-normal font-['PingFang_SC'] leading-relaxed">
-          第{{ options?.index + 1 }}站
+          第{{ options?.sort }}站
         </div>
         <div class="flex-1"></div>
         <div

+ 1 - 1
packages/app/src/pages/home/study-tour/detail.vue

@@ -108,7 +108,7 @@ onLoad(async (query: { id: string }) => {
     <div
       class="fixed bottom-4 w-[347px] h-[63px] bg-white/90 rounded-2xl backdrop-blur-[20px] flex items-center px-4 box-border"
     >
-      <div class="text-[#ef4343] text-2xl font-normal font-['D-DIN Exp'] leading-normal">16000</div>
+      <div class="text-[#ef4343] text-2xl font-normal font-['D-DIN_Exp'] leading-normal">16000</div>
       <div class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-[34px]">
         积分
       </div>

+ 6 - 3
packages/app/src/pages/home/study-tour/index.vue

@@ -11,7 +11,6 @@ import Card from '@/components/card.vue'
 import MomentItem from '@/components/moment-item.vue'
 import SectionHeading from '@/components/section-heading.vue'
 import ClassItem from '../components/class-item.vue'
-import TimeLine from './components/time-line.vue'
 import {
   getAppMemberLevelConfigs,
   getBanners,
@@ -61,7 +60,11 @@ onMounted(async () => {
             height="100%"
             custom-class="aspect-[1.73/1]"
             :src="banners[0].bannerImgUrl"
-            @click="router.push(`/pages/home/study-tour/list`)"
+            @click="
+              router.push(
+                `/pages/home/study-tour/list?designStudyAbroadYear=${banners[0].designStudyAbroadYear}&designDesc=${banners[0].designDesc}`,
+              )
+            "
           ></wd-img>
         </div>
       </template>
@@ -128,7 +131,7 @@ onMounted(async () => {
         </div>
         <view class="flex items-center justify-between">
           <view class="flex items-end">
-            <div class="text-white text-3xl font-bold font-['D-DIN Exp'] leading-normal">16000</div>
+            <div class="text-white text-3xl font-bold font-['D-DIN_Exp'] leading-normal">16000</div>
             <div class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC'] leading-[34px]">
               积分
             </div>

+ 26 - 4
packages/app/src/pages/home/study-tour/list.vue

@@ -9,11 +9,15 @@
 <script setup lang="ts">
 import SectionHeading from '@/components/section-heading.vue'
 import { getAppMemberLevelConfigs, getStudyTours } from '../../../core/libs/requests'
-import dayjs from 'dayjs'
 import StudyTourCard from './components/study-tour-card.vue'
 import PageHelper from '@/components/page-helper.vue'
 
-const title = computed(() => `${dayjs().year()}年游学计划`)
+// const title = computed(() => `${dayjs().year()}年游学计划`)
+const designStudyAbroadYear = ref('')
+const title = ref('')
+const studyYear = ref('')
+const designDesc = ref()
+
 const { data: levels, run: setLevels } = useRequest(() => getAppMemberLevelConfigs(), {
   initialData: [],
 })
@@ -23,9 +27,23 @@ const levelsByMemberLevel = computed(() =>
     return acc
   }, {}),
 )
+onLoad(async (query?: Record<string | 'designStudyAbroadYear' | 'designDesc', string>) => {
+  designStudyAbroadYear.value = query.designStudyAbroadYear
+  title.value = `${designStudyAbroadYear.value}年游学计划`
+  if (query.designDesc) {
+    designDesc.value = query.designDesc
+  }
+})
+
 onMounted(async () => {
   await setLevels()
 })
+onShareAppMessage(() => ({
+  title: '游学计划',
+}))
+onShareTimeline(() => ({
+  title: '游学计划',
+}))
 </script>
 <template>
   <div class="flex flex-col gap-6 p-3.5">
@@ -33,9 +51,13 @@ onMounted(async () => {
     <div
       class="mx-3.5 relative top--4 text-justify text-black/40 text-sm font-normal font-['PingFang_SC'] leading-relaxed"
     >
-      *我们为您精心打造了一个独特且极具价值的游学项目。这个项目的核心旨在全方位提升
+      <!--      *我们为您精心打造了一个独特且极具价值的游学项目。这个项目的核心旨在全方位提升-->
+      {{ designDesc }}
     </div>
-    <PageHelper :request="getStudyTours" :query="{ showStatus: '1' }">
+    <PageHelper
+      :request="getStudyTours"
+      :query="{ showStatus: '1', studyYear: designStudyAbroadYear }"
+    >
       <template #default="{ source }">
         <div class="py-4 flex flex-col gap-6">
           <template v-for="(it, index) in source?.list" :key="index">

+ 28 - 2
packages/app/src/pages/login/index.vue

@@ -6,6 +6,8 @@ style:
 import { testLogin, weixinMiniAppLogin } from '../../core/libs/requests'
 import { logo } from '../../core/libs/svgs'
 import { useUserStore } from '../../store'
+import { toContentHtml } from '@/core/utils/common'
+import { AgreementType } from '@/core/libs/enums'
 
 const userStore = useUserStore()
 const { setUserInfo } = userStore
@@ -88,8 +90,32 @@ onLoad(async (query: { type?: 'test' }) => {
           <span class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-tight">
             如您点击授权,您将同意并授权
           </span>
-          <span class="text-[#0cbe7c] text-xs font-normal font-['PingFang_SC'] leading-tight">
-            《筑巢荟用户服务协议》、《隐私政策》、《注册协议》
+          <span
+            @click="
+              toContentHtml({
+                title: '筑巢荟用户服务协议',
+                type: AgreementType.ZCHServiceAgreement,
+              })
+            "
+            class="text-[#0cbe7c] text-xs font-normal font-['PingFang_SC'] leading-tight"
+          >
+            《筑巢荟用户服务协议》、
+          </span>
+          <span
+            @click="
+              toContentHtml({ title: '隐私政策', type: AgreementType.ZCHUserPrivacyAgreement })
+            "
+            class="text-[#0cbe7c] text-xs font-normal font-['PingFang_SC'] leading-tight"
+          >
+            《隐私政策》、
+          </span>
+          <span
+            @click="
+              toContentHtml({ title: '注册协议', type: AgreementType.ZCHUserRegistrationAgreement })
+            "
+            class="text-[#0cbe7c] text-xs font-normal font-['PingFang_SC'] leading-tight"
+          >
+            《注册协议》
           </span>
         </div>
       </template>

+ 135 - 49
packages/app/src/pages/material/detail/index.vue

@@ -4,14 +4,27 @@
 <script setup lang="ts">
 import Card from '@/components/card.vue'
 import SectionHeading from '@/components/section-heading.vue'
-import { getMaterialDetail, getByDictType, getMaterialHomePage } from '../../../core/libs/requests'
+import {
+  getMaterialDetail,
+  getByDictType,
+  getMaterialHomePage,
+  downloadMaterials,
+} from '../../../core/libs/requests'
 import NavbarEvo from '@/components/navbar-evo.vue'
 import { DictType } from '../../../core/libs/models'
 import { phone } from '../../../core/libs/svgs'
-import { handleCall, openLocation } from '../../../core/utils/common'
+import { handleCall, openLocation, previewImage } from '../../../core/utils/common'
 import router from '@designer-hub/assets/src/assets/svgs/router'
+import { isVideoUrl } from 'wot-design-uni/components/common/util'
+import { useUserStore } from '@/store'
+import { AnalysisEventType, useAnalysis } from '@/composables/analysis'
+import { usePermissions } from '@/composables/permissions'
 
+const { clickByPermission } = usePermissions()
+const userStore = useUserStore()
+const { option } = useAnalysis(true, false)
 const id = ref()
+const downloading = ref(false)
 const { data, run: setData } = useRequest(() => getMaterialDetail({ id: id.value }))
 const { data: materialHomePageData, run: setMaterialHomePageData } = useRequest(
   () => getMaterialHomePage(id.value),
@@ -33,9 +46,46 @@ const { data: materialsManageBrands, run: setMaterialsManageBrands } = useReques
   () => getByDictType(DictType.materialsManageBrand),
   { initialData: [] },
 )
+const handleDownload = async () => {
+  downloading.value = true
+  const { filePath } = await uni.downloadFile({
+    url: data.value.agreementFileUrl,
+    filePath: `${uni.env.USER_DATA_PATH}/${data.value.materialsName}的素材包.zip`,
+    header: {
+      'Content-Type': 'application/x-zip-compressed',
+    },
+  })
+  downloading.value = false
+  await downloadMaterials({ materialsId: id.value })
+  const { deviceType } = await uni.getDeviceInfo()
+  if (deviceType === 'pc') {
+    uni.saveFileToDisk({
+      filePath,
+      success: (res) => {
+        console.log('saveFileToDisk success', res)
+      },
+      fail: (err) => {
+        console.log('saveFileToDisk fail', err)
+      },
+    })
+  } else {
+    uni.openDocument({
+      filePath,
+      success: (res) => {
+        console.log('openDocument success', res)
+      },
+      fail: (err) => {
+        console.log('openDocument fail', err)
+      },
+    })
+  }
+}
 onLoad(async (query: { id: number }) => {
   id.value = query.id
   await setData()
+  option.value = {
+    remark: `最近浏览品牌: ${data.value?.materialsName}`,
+  }
   await setMaterialHomePageData()
   console.log(data.value)
   await Promise.all([
@@ -45,6 +95,12 @@ onLoad(async (query: { id: number }) => {
     setMaterialsManageBrands(),
   ])
 })
+onShareAppMessage(() => ({
+  title: data.value?.materialsName,
+}))
+onShareTimeline(() => ({
+  title: data.value?.materialsName,
+}))
 </script>
 <template>
   <view class="flex-grow flex flex-col">
@@ -66,25 +122,24 @@ onLoad(async (query: { id: number }) => {
             ></wd-img>
             <div class="flex flex-col gap-2.5">
               <div class="flex gap-2 items-center">
-                <div
-                  class="text-black/90 text-lg font-normal font-['PingFang_SC'] leading-[10.18px]"
-                >
+                <div class="text-black/90 text-lg font-normal font-['PingFang_SC']">
                   <!-- IMOLA瓷砖 -->
                   {{ data?.materialsName }}
                 </div>
-                <div
-                  class="w-[52px] h-[17px] px-2 bg-[#ef4343] rounded-[3px] justify-center items-center gap-2.5 inline-flex"
-                >
-                  <div
-                    class="text-white text-[10px] font-normal font-['PingFang_SC'] leading-normal"
-                  >
-                    <!-- 自营品牌 -->
-                    {{
-                      materialBrandLevels.find(({ value }) => value === String(data.materialsType))
-                        ?.label
-                    }}
-                  </div>
-                </div>
+
+                <!--                <div-->
+                <!--                  class="w-[52px] h-[17px] px-2 bg-[#ef4343] rounded-[3px] justify-center items-center gap-2.5 inline-flex"-->
+                <!--                >-->
+                <!--                  <div-->
+                <!--                    class="text-white text-[10px] font-normal font-['PingFang_SC'] leading-normal"-->
+                <!--                  >-->
+                <!--                    &lt;!&ndash; 自营品牌 &ndash;&gt;-->
+                <!--                    {{-->
+                <!--                      materialBrandLevels.find(({ value }) => value === String(data.materialsType))-->
+                <!--                        ?.label-->
+                <!--                    }}-->
+                <!--                  </div>-->
+                <!--                </div>-->
               </div>
               <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]">
                 {{
@@ -112,26 +167,26 @@ onLoad(async (query: { id: number }) => {
                   }}
                 </span>
               </div>
-              <div class="flex gap-2.5">
-                <div
-                  class="w-[77px] h-5 px-2 py-px bg-neutral-100 rounded-[3px] justify-center items-center gap-2.5 inline-flex"
-                >
-                  <div
-                    class="text-black/60 text-[10px] font-normal font-['PingFang_SC'] leading-normal"
-                  >
-                    积分比例:{{ data?.pointsExchangeRate }}%
-                  </div>
-                </div>
-                <div
-                  class="w-[92px] h-5 px-2 py-px bg-neutral-100 rounded-[3px] justify-center items-center gap-2.5 inline-flex"
-                >
-                  <div
-                    class="text-black/60 text-[10px] font-normal font-['PingFang_SC'] leading-normal"
-                  >
-                    门店打卡:{{ data?.clockPoints }}积分
-                  </div>
-                </div>
-              </div>
+              <!--              <div class="flex gap-2.5">-->
+              <!--                <div-->
+              <!--                  class="w-[77px] h-5 px-2 py-px bg-neutral-100 rounded-[3px] justify-center items-center gap-2.5 inline-flex"-->
+              <!--                >-->
+              <!--                  <div-->
+              <!--                    class="text-black/60 text-[10px] font-normal font-['PingFang_SC'] leading-normal"-->
+              <!--                  >-->
+              <!--                    积分比例:{{ data?.pointsExchangeRate }}%-->
+              <!--                  </div>-->
+              <!--                </div>-->
+              <!--                <div-->
+              <!--                  class="w-[92px] h-5 px-2 py-px bg-neutral-100 rounded-[3px] justify-center items-center gap-2.5 inline-flex"-->
+              <!--                >-->
+              <!--                  <div-->
+              <!--                    class="text-black/60 text-[10px] font-normal font-['PingFang_SC'] leading-normal"-->
+              <!--                  >-->
+              <!--                    门店打卡:{{ data?.clockPoints }}积分-->
+              <!--                  </div>-->
+              <!--                </div>-->
+              <!--              </div>-->
             </div>
           </div>
           <template
@@ -197,14 +252,24 @@ onLoad(async (query: { id: number }) => {
               mode="aspectFill"
               custom-class=""
             ></wd-img> -->
-            <video class="w-full h-full"></video>
-          </div>
-          <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-[10.18px]">
-            <!-- {{ materialHomePageData. }} -->
-            {{
-              materialsManageBrands.find(({ value }) => value === String(data?.manageBrand))?.label
-            }}
+            <template v-if="isVideoUrl(materialHomePageData.brandAdvantageUrl)">
+              <video class="w-full h-full"></video>
+            </template>
+            <template v-else>
+              <wd-img
+                width="100%"
+                height="100%"
+                :src="materialHomePageData.brandAdvantageUrl"
+                mode="aspectFill"
+                custom-class=""
+              ></wd-img>
+            </template>
           </div>
+          <!--          <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-[10.18px]">-->
+          <!--            {{-->
+          <!--              materialsManageBrands.find(({ value }) => value === String(data?.manageBrand))?.label-->
+          <!--            }}-->
+          <!--          </div>-->
           <div
             class="text-justify text-black/60 text-sm font-normal font-['PingFang_SC'] leading-[25px]"
           >
@@ -216,18 +281,39 @@ onLoad(async (query: { id: number }) => {
         <SectionHeading title="产品展示"></SectionHeading>
       </template>
       <template v-for="(it, index) in materialHomePageData.productDOList" :key="index">
-        <div class="aspect-[1.66/1] rounded-2xl overflow-hidden">
+        <div class="aspect-[1.66/1] rounded-2xl overflow-hidden relative">
           <swiper class="h-[55.7vw]">
-            <template v-for="img of it?.productImgUrl?.split(',')" :key="img">
+            <template v-for="(img, index) in it?.productImgUrl?.split(',')" :key="img">
               <swiper-item class="h-full">
-                <wd-img width="100%" height="100%" class="" :src="img" mode="aspectFill" />
+                <wd-img
+                  width="100%"
+                  height="100%"
+                  class=""
+                  :src="img"
+                  mode="aspectFill"
+                  @click="previewImage(index, it?.productImgUrl?.split(','))"
+                />
               </swiper-item>
             </template>
           </swiper>
+          <div
+            class="absolute bottom-0 w-full h-[66px] bg-gradient-to-t from-black to-[#5e5e5e00] rounded-bl-2xl rounded-br-2xl flex items-center px-4.5"
+          >
+            <div class="text-white text-base font-normal font-['PingFang_SC'] leading-[10.18px]">
+              {{ it.productTitleName }}
+            </div>
+          </div>
         </div>
       </template>
 
-      <wd-button custom-class="w-full! rounded-lg!">下载所有素材包</wd-button>
+      <wd-button
+        custom-class="w-full! rounded-lg!"
+        :disabled="(data?.agreementFileUrl ?? '') === ''"
+        :loading="downloading"
+        @click="clickByPermission('download', () => handleDownload())"
+      >
+        {{ downloading ? '下载中...' : '下载所有素材包' }}
+      </wd-button>
     </div>
   </view>
 </template>

+ 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>

+ 4 - 5
packages/app/src/pages/material/mini-class/index.vue

@@ -9,11 +9,10 @@
 <script setup lang="ts">
 import NavbarEvo from '@/components/navbar-evo.vue'
 const imgs = ref([
-  'https://image.zhuchaohui.com/zhucaohui/9ecc362603763716ae5958193f8fe8c03f62000dd789a91c7f5323017eda2274.png',
-  'https://image.zhuchaohui.com/zhucaohui/c2458b6a9ea61953c40d329e1ce9ff7592da7c4d0f31241bd3c98f76f18ec65f.png',
-  'https://image.zhuchaohui.com/zhucaohui/4f1b8ea9dcbcf51c18fb2435712a0d51c24830a6f70c93938a3bef8ebf410c03.png',
-  'https://image.zhuchaohui.com/zhucaohui/4dd78ed62c7b40bb63a28f6a484cbd6f625bb00b087446ef97f398b4ae6d7611.png',
-  'https://image.zhuchaohui.com/zhucaohui/f39ad760767abc012d0b710fb4c95dc72767c8c45cab9a2e80e7e5d0633433a9.png',
+  'https://image.zhuchaohui.com/zhucaohui/fe7baaf514378aafe792df18400755302aed3206e2eb0b7b6743abd0fc513099.png',
+  'https://image.zhuchaohui.com/zhucaohui/0c8a33b400f9f54e7cf3ac2588178abe9a159284d0783f0114db19d0bb9157b4.png',
+  'https://image.zhuchaohui.com/zhucaohui/91b594940df7cbc517a21823879ae177f9fc36ad01107ce8e5885b69fe6b2dd6.png',
+  'https://image.zhuchaohui.com/zhucaohui/7b7171de3cae83030acca008dc03484fdd53af3398526623cd62afc024c124ef.png',
 ])
 </script>
 <template>

+ 80 - 21
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,15 +31,56 @@ const { data: coupons, run: setCoupons } = useRequest(
     }),
   { initialData: [] },
 )
-onMounted(async () => {
+const couponSelectText = computed(() => {
+  const selectedCoupons = props.options.selectedCoupons || []
+  const availableCoupons = coupons.value || []
+
+  if (selectedCoupons.length > 0) {
+    return `${selectedCoupons[0].brandPoints ?? 0}积分`
+  }
+  if (availableCoupons.length > 0) {
+    return `${availableCoupons.length}张可用`
+  }
+  return '无可用'
+})
+const hasLine = computed(() => getMessageType(props.options.messageSubType)?.path)
+const init = async () => {
   if (
     props.options.messageType === MessageType.Integral &&
     props.options.messageSubType === 31 &&
     props.options.isRead !== '1'
   ) {
     await setCoupons()
-    // console.log(coupons.value)
   }
+}
+const handleJump = () => {
+  if ((props.options.linkUrl ?? '') !== '') {
+    return router.push(props.options.linkUrl)
+  }
+  const query: Record<string, string> = {}
+  switch (props.options.messageSubType) {
+    case 5:
+      query.id = props.options.id
+      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))
+  router.push(getMessageType(props.options.messageSubType)?.path + '?' + stringify(query))
+}
+watch(
+  () => props.options,
+  async (value, oldValue, onCleanup) => {
+    // console.log('====>', value)
+    await init()
+  },
+)
+onMounted(async () => {
+  await init()
 })
 </script>
 <template>
@@ -60,7 +105,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">
@@ -72,26 +121,34 @@ onMounted(async () => {
         <div class="my-3 text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[25px]">
           <!-- {{ options.detailBody }} -->
           <div
-            v-if="options.messageType === MessageType.Integral"
+            v-if="
+              [MessageType.Integral, MessageType.System].includes(options.messageType) &&
+              String(options.messageSubType) !== '5'
+            "
             v-html="options.detailBody"
           ></div>
-          <div v-html="options.detailBody"></div>
-          <div class="grid grid-cols-[auto_1fr] gap-x-5 gap-y-4.5">
-            <template v-if="options.messageSubType === 31 && options.isRead !== '1'">
+          <div class="grid grid-cols-[auto_1fr] gap-x-1 gap-y-4.5">
+            <template
+              v-if="
+                options.messageSubType === 31 &&
+                options.pointsDetail?.pointsStauts === PointStatus.PendingConfirmation
+              "
+            >
               <div
                 class="text-black/40 text-sm font-normal font-['PingFang_SC'] h-5.5 flex items-center"
               >
-                积分券
+                销售积分券
               </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
@@ -103,14 +160,16 @@ 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>
+      <div v-if="hasLine" class="row-start-4 col-start-1 col-end-4 my-2">
+        <div class="bg-[#dadada] w-full h-[1px]"></div>
       </div>
       <div class="row-start-5 col-start-2 col-end-4">
         <div class="text-black/40 text-xs font-normal font-['PingFang_SC']">
@@ -134,7 +193,7 @@ onMounted(async () => {
             <template v-if="options.pointsDetail?.pointsStauts === PointStatus.Rejected">
               <span class="text-[#EF4343]">
                 <!-- 确认积分后,即刻到账,如有问题请驳回,联系材料商修改积分后再次确认 -->
-                驳回原因:{{ options.pointsDetail?.cancelReason }}
+                已驳回,驳回原因:{{ options.pointsDetail?.cancelReason }}
               </span>
             </template>
           </template>
@@ -148,7 +207,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]"
             >
               查看详情
@@ -177,7 +236,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>

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

@@ -0,0 +1,40 @@
+<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'
+import { fuckYou, fuckYouMom } from '@/core/libs/requests'
+
+const id = ref()
+const type = ref()
+const request = ref<() => Promise<IResData<Partial<Content>>>>()
+
+const { data, run: setData } = useRequest(() => fuckYouMom({ id: id.value }))
+onLoad(async (query?: { id: string; type?: 'banner' }) => {
+  if (query.id) {
+    id.value = query.id
+  }
+  await setData()
+  await fuckYou({ id: Number(id.value) })
+})
+</script>
+<template>
+  <div class="flex-grow bg-white">
+    <Article
+      :title="data?.title"
+      :author="{ name: '筑巢荟' }"
+      :content="data?.detailBody"
+      :createAt="data?.createTime"
+      :viewNum="data?.viewCount || 0"
+    >
+      <template #avatar><wd-img width="28" height="28" :src="logo"></wd-img></template>
+    </Article>
+  </div>
+</template>

+ 30 - 11
packages/app/src/pages/messages/index.vue

@@ -11,9 +11,7 @@
 <script setup lang="ts">
 import PageHelper from '@/components/page-helper.vue'
 import {
-  deleteMessage,
   getMessages,
-  getPointsCoupons,
   orderPointsCancel,
   orderPointsSubmit,
   updateMessage,
@@ -44,18 +42,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)
-        uni.showToast({ title: '请输入驳回原因', icon: 'none' })
+        await uni.showToast({ title: '请输入驳回原因', icon: 'none' })
         return
       }
       await requestToast(
@@ -73,9 +71,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 +99,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())
@@ -97,7 +113,7 @@ const handleQ = async (msg, res) => {
   show.value = true
 }
 onShow(async () => {
-  nextTick(() => {
+  await nextTick(() => {
     pageHelperRef.value?.reload()
   })
 })
@@ -123,7 +139,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"

+ 29 - 9
packages/app/src/pages/mine/authentication/index.vue

@@ -38,6 +38,7 @@ const { error } = useToast()
 const formData = ref<any>({ mobile: userInfo.value?.mobile })
 const attachment = ref()
 const formInited = ref(false)
+const uploadDisabled = ref(false)
 const { data: userAuthInfo, run: setUserAuthInfo } = useRequest(() => getUserAuthInfo())
 const schema = ref<DataFormSchema>({
   channelSource: {
@@ -104,6 +105,7 @@ const setReferrerExisting = (value: string) => {
     schema.value.referrer.props.placeholder = {
       '1': '请填写渠道邀请码',
       '2': '请填写设计师会员号',
+      '3': '请填写材料商名称',
     }[value]
   }
 }
@@ -111,7 +113,7 @@ const handleSubmit = async () => {
   console.log(formData.value)
   if (formData.value.channelSource && formData.value.channelSource !== '4' && !userAuthInfo.value) {
     if ((formData.value.referrer ?? '') === '') {
-      uni.showToast({ title: messages.mine.authentication.referrerErrorText, icon: 'none' })
+      await uni.showToast({ title: messages.mine.authentication.referrerErrorText, icon: 'none' })
       return
     }
     const { data, code: status } = await requestToast(() =>
@@ -121,7 +123,7 @@ const handleSubmit = async () => {
       }),
     )
     if (data === false || status !== 0) {
-      uni.showToast({ title: '推荐人编号不正确', icon: 'none' })
+      await uni.showToast({ title: '推荐人编号不正确', icon: 'none' })
       return
     }
   }
@@ -147,7 +149,7 @@ const handleSubmit = async () => {
     .map(([key, value]) => value.message)
   console.log(errors)
   if (errors.length > 0) {
-    uni.showToast({ title: errors[0], icon: 'none' })
+    await uni.showToast({ title: errors[0], icon: 'none' })
     return
   }
 
@@ -162,7 +164,7 @@ const handleSubmit = async () => {
       spatialExpertiseType: formData.value.spatialExpertiseType?.join(','),
     })
     if (code === 0) {
-      router.replace(`/pages/mine/authentication/submit/success/index`)
+      await router.replace(`/pages/mine/authentication/submit/success/index`)
     } else {
       error(msg)
     }
@@ -176,12 +178,27 @@ const handleSubmit = async () => {
     }
     const { code } = await requestToast(() => updateUserAuthInfo({ ...toBeUpdate, auditStatus: 1 }))
     if (code === 0) {
-      router.replace(`/pages/mine/authentication/submit/success/index`)
+      await router.replace(`/pages/mine/authentication/submit/success/index`)
     }
   }
 }
-onMounted(async () => {
+onLoad(async (query?: Record<string | 'scene', string>) => {
+  if (query?.scene) {
+    console.log(query.scene)
+    try {
+      const [referrer, channelSource] = decodeURIComponent(query.scene).split('&')
+      console.log(referrer, channelSource)
+      formData.value = {
+        ...formData.value,
+        referrer,
+        channelSource,
+      }
+    } catch (e) {
+      await uni.showToast({ title: e.message, icon: 'none' })
+    }
+  }
   await setUserAuthInfo()
+  console.log(userAuthInfo.value)
   if (userAuthInfo.value) {
     formData.value = {
       ...pick(userAuthInfo.value, [
@@ -207,17 +224,19 @@ onMounted(async () => {
   schema.value.spatialExpertiseType.props.columns = res
   formInited.value = true
   if (userInfo.value.userAuthStatus === 1) {
+    console.log(formData.value)
     Object.entries(schema.value).forEach(([key]) => {
       schema.value[key].props.disabled = true
     })
-    alert({
+    uploadDisabled.value = true
+    await alert({
       msg: '您的认证申请已提交,请耐心等待审核,审核通过后您将获得通知',
       title: '提示',
       confirmButtonText: '我知道了',
     })
   }
   if (userInfo.value.userAuthStatus === 2) {
-    alert({
+    await alert({
       title: '审核不通过',
       msg: userAuthInfo.value?.remark || '由于系统原因,您提交的认证暂时无法通过,请修改后重新提交',
     })
@@ -256,11 +275,12 @@ defineExpose({})
         ></SectionHeading>
         <div class="h-0.25 bg-[#e1e1e1] mb-5"></div>
         <!-- <wd-upload></wd-upload> -->
-        <UploadEvo v-if="formInited" v-model="attachment"></UploadEvo>
+        <UploadEvo v-if="formInited" v-model="attachment" :disabled="uploadDisabled"></UploadEvo>
       </Card>
       <div class="flex-1"></div>
       <div>
         <wd-button
+          v-if="!(isDesigner || String(userInfo.userAuthStatus) === '1')"
           block
           :round="false"
           :disabled="isDesigner || userInfo.userAuthStatus === 1"

+ 3 - 1
packages/app/src/pages/mine/components/tasks-card.vue

@@ -2,7 +2,9 @@
 import { Task } from '../../../core/libs/models'
 import { taskCenterBg } from '../../../core/libs/pngs'
 import { useRouter } from '../../../core/utils/router'
+import { usePermissions } from '@/composables/permissions'
 
+const { clickByPermission } = usePermissions()
 const router = useRouter()
 defineProps({
   customClass: {
@@ -70,7 +72,7 @@ onMounted(async () => {})
                 plain
                 size="small"
                 :disabled="btnProps?.disabled"
-                @click="btnProps?.onClick"
+                @click="clickByPermission('task', () => btnProps?.onClick())"
               >
                 {{ btnProps?.content }}
                 <!-- {{ taskExtendsById[id]?.completed ? '已完成' : '去完成' }} -->

+ 3 - 1
packages/app/src/pages/mine/convention/index.vue

@@ -3,7 +3,9 @@
 </route>
 <script setup lang="ts">
 const imgs = ref([
-  'https://image.zhuchaohui.com/zhucaohui/3014480570bd7b81a64eb1ca598c7e017f7952eb982e47aae4eb23de9d7de9f2.jpg',
+  'https://image.zhuchaohui.com/zhucaohui/4e9c3301a40e9dd01ecb8b014b17c86f33c7ad5f5b80e4a5d3623050ec49dce9.png',
+  'https://image.zhuchaohui.com/zhucaohui/a1694f628e7c30df5f4a8fe4ab980e4740aa67c60e7e01466a53c98fd4ea5118.png',
+  'https://image.zhuchaohui.com/zhucaohui/198e4eebd45a7fa1bf013465622a0c36bd71ad85cbf4bf54350d7443ffcd6e1c.png',
 ])
 const handleClickLeft = () => {
   uni.navigateBack()

+ 1 - 1
packages/app/src/pages/mine/coupons/index.vue

@@ -17,7 +17,7 @@ const tabs = ref([
   { label: '销售积分券', value: '1' },
   { label: '商品优惠券', value: '2' },
 ])
-const query = computed(() => ({ couponType: tab.value, isUse: 0 }))
+const query = computed(() => ({ couponType: tab.value }))
 // const {data, run: set} = useRequest(() => getCoupons({}))
 </script>
 <template>

+ 58 - 14
packages/app/src/pages/mine/homepage/channels/index.vue

@@ -7,14 +7,17 @@ import { getDesignerInfo, updateDesignerInfo } from '../../../../core/libs/reque
 import { useUserStore } from '../../../../store'
 import { storeToRefs } from 'pinia'
 import { pick } from 'radash'
-import { requestToast } from '../../../../core/utils/common'
+import { toContentHtml, validate } from '../../../../core/utils/common'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import { useMessage } from 'wot-design-uni'
 import { NetImages } from '../../../../core/libs/net-images'
+import { useRouter } from '@/core/utils/router'
+import { AgreementType } from '@/core/libs/enums'
 
-const { alert } = useMessage()
+const { alert, confirm } = useMessage('wd-message-box-slot')
 const userStore = useUserStore()
 const { userInfo } = storeToRefs(userStore)
+const { back } = useRouter()
 const form = ref<{
   userId?: number
   videoNumber?: string
@@ -22,9 +25,27 @@ const form = ref<{
 const { data, run: setData } = useRequest(() => getDesignerInfo(userInfo.value.userId))
 const { loading, run: submiting } = useRequest(() => updateDesignerInfo(form.value))
 const handleSubmit = async () => {
-  await submiting()
-  await setData()
-  alert({ title: '关联成功', confirmButtonText: '我知道了' })
+  if (!validate(form.value, { videoNumber: [{ required: true, message: '请输入视频号ID' }] }))
+    return
+  const { action } = await confirm({
+    title: '温馨提示',
+    confirmButtonText: '同意并关联',
+    cancelButtonText: '不同意',
+  })
+  console.log(action)
+  if (action === 'confirm') {
+    await submiting()
+    uni.showToast({
+      title: '视频号关联成功',
+      icon: 'none',
+      duration: 2000,
+      success: () => {
+        back()
+      },
+    })
+    await setData()
+  }
+  // await alert({ title: '关联成功', confirmButtonText: '我知道了' })
 }
 onMounted(async () => {
   await setData()
@@ -44,20 +65,43 @@ onMounted(async () => {
     </div>
     <SectionHeading title="如何关联视频号?" size="sm"></SectionHeading>
     <wd-img class="rounded-2xl" width="100%" mode="widthFix" :src="NetImages.WechatChannelsGuide" />
-    <BottomAppBar fixed :border="false">
+    <SectionHeading title="视频教程" size="sm"></SectionHeading>
+    <div class="aspect-[1.87/1] rounded-2xl overflow-hidden">
+      <video class="w-full h-full" :src="NetImages.VideoTutorial"></video>
+    </div>
+    <BottomAppBar fixed placeholder :border="false" v-if="(data.videoNumber ?? '') === ''">
       <div>
-        <div class="text-center mb-5.5">
-          <span class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-tight">
-            点击确认关联即表示同意
-          </span>
-          <span class="text-[#0cbe7c] text-xs font-normal font-['PingFang_SC'] leading-tight">
-            《个人微信视频号授权使用协议》
-          </span>
-        </div>
+        <!--        <div class="text-center mb-5.5">-->
+        <!--          <span class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-tight">-->
+        <!--            点击确认关联即表示同意-->
+        <!--          </span>-->
+        <!--          <span class="text-[#0cbe7c] text-xs font-normal font-['PingFang_SC'] leading-tight">-->
+        <!--            《个人微信视频号授权使用协议》-->
+        <!--          </span>-->
+        <!--        </div>-->
         <wd-button :round="false" block :loading="loading" @click="handleSubmit">
           确定关联
         </wd-button>
       </div>
     </BottomAppBar>
+
+    <wd-message-box selector="wd-message-box-slot">
+      <div class="">
+        <span class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-relaxed">
+          关联后,小程序开发者将获取您的视频号相关信息,并为您提供相关服务,请您充分阅读
+        </span>
+        <span
+          class="text-[#3f598f] text-base font-normal font-['PingFang_SC'] leading-relaxed"
+          @click="
+            toContentHtml({
+              title: '个人微信视频号授权使用协议',
+              type: AgreementType.PersonalWeChatVideoAuthorization,
+            })
+          "
+        >
+          《个人微信视频号授权使用协议》
+        </span>
+      </div>
+    </wd-message-box>
   </div>
 </template>

+ 12 - 2
packages/app/src/pages/mine/homepage/consult/index.vue

@@ -13,7 +13,7 @@ import { NetImages } from '../../../../core/libs/net-images'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import { useRouter } from '../../../../core/utils/router'
 import NavbarEvo from '@/components/navbar-evo.vue'
-import { requestToast } from '../../../../core/utils/common'
+import {requestToast, validate} from '../../../../core/utils/common'
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -53,6 +53,16 @@ onLoad(async (query: { id: string }) => {
   console.log(circleTypes.value)
 })
 const handleSubmit = async () => {
+  const rules = {
+    appointmentName: [{ required: true, message: '请输入姓名' }],
+    appointmentPhone: [
+      { required: true, message: '请输入手机号' },
+      // { pattern: /^1[3456789]\d{9}$/, message: '手机号格式不正确' },
+    ],
+  }
+
+  if (!validate(form.value, rules)) return
+
   const { code } = await requestToast(
     () =>
       reserveDesigner({
@@ -64,7 +74,7 @@ const handleSubmit = async () => {
   )
   if (code === 0) {
     form.value = { appointmentName: '', appointmentPhone: '' }
-    router.replace(`/pages/mine/homepage/consult/success/index?name=${''}`)
+    await router.replace(`/pages/mine/homepage/consult/success/index?name=${''}`)
   }
 }
 onShareAppMessage(() => ({ title: `${userInfo.value.nickname}` }))

+ 6 - 5
packages/app/src/pages/mine/homepage/edit/index.vue

@@ -44,6 +44,7 @@ onMounted(async () => {
     'designFee',
     'personalIdentity',
     'serviceCustomerCount',
+    'homePageName',
   ])
 })
 </script>
@@ -69,7 +70,7 @@ onMounted(async () => {
           <div
             class="mt-4.5 mb-2.5 text-black/40 text-xs font-normal font-['PingFang_SC'] leading-snug"
           >
-            用于主页形象封面图,请上传体现个人艺术设计风格的图片,建议竖图尺寸750x1920,也可上传自己的视频介绍
+            用于主页形象封面图,请上传体现个人艺术设计风格的图片,建议竖图尺寸1125x900,也可上传自己的视频介绍
           </div>
           <UploadEvo v-model="form.homePageUrl"></UploadEvo>
         </div>
@@ -80,7 +81,7 @@ onMounted(async () => {
           <div
             class="mt-4.5 mb-2.5 text-black/40 text-xs font-normal font-['PingFang_SC'] leading-snug"
           >
-            用于分享到微信好友的卡片封面图,尺寸1920x1080
+            用于分享到微信好友的卡片封面图,尺寸660x528
           </div>
           <UploadEvo v-model="form.sharePageUrl"></UploadEvo>
         </div>
@@ -104,12 +105,12 @@ onMounted(async () => {
       </Card>
       <Card>
         <div>
-          <SectionHeading title="个人信息" subtitle="请输入关于自己身份体现"></SectionHeading>
+          <SectionHeading title="个人信息" subtitle="请输入个人身份"></SectionHeading>
           <div
             class="mt-4.5 mx--3.5 text-black/40 text-xs font-normal font-['PingFang_SC'] leading-snug"
           >
             <wd-textarea
-              placeholder="例:中国室内装饰协会会员、 xxx 空间设计事务所创始人、筑巢奖金奖设计师等等"
+              placeholder="例:中国室内装饰协会会员、 xxx 空间设计事务所创始人、筑巢奖金奖设计师等等。&#10;多个身份时,请用“、”符号隔开"
               v-model="form.personalIdentity"
             />
           </div>
@@ -157,7 +158,7 @@ onMounted(async () => {
         </div>
       </Card>
       <!-- <BottomAppBar>
-      
+
     </BottomAppBar> -->
       <div class=""><wd-button block :round="false" @click="handleSubmit">保存</wd-button></div>
     </template>

+ 45 - 42
packages/app/src/pages/mine/homepage/index.vue

@@ -29,13 +29,15 @@ import { requestToast } from '../../../core/utils/common'
 import { ComponentExposed } from 'vue-component-type-helpers'
 import dayjs from 'dayjs'
 import wechatChannels from '@designer-hub/assets/src/libs/assets/wechatChannels'
-import { handleUpvoteClick, handleShareClick } from '../../../core/libs/actions'
+import { handleUpvoteClick } from '../../../core/libs/actions'
 import { usePermissions } from '../../../composables/permissions'
 import ImageEvo from '@/components/image-evo.vue'
 import more from '@designer-hub/assets/src/libs/assets/more'
 import qrCode from '@designer-hub/assets/src/libs/assets/qrCode'
+import { useShare } from '@/composables/share'
 
-const { features } = usePermissions()
+const { features, clickByPermission } = usePermissions()
+const { shareAppMessage } = useShare()
 const { alert, confirm } = useMessage()
 const router = useRouter()
 const userStore = useUserStore()
@@ -57,8 +59,8 @@ const { data: memberInfo, run: setMemberInfo } = useRequest(() => getUserInfoByI
 const { data: designerInfo, run: setDesignerInfo } = useRequest(() => getDesignerInfo(id.value), {
   initialData: {},
 })
-const { data: badges, run: setBadges } = useRequest(() => getOwnBadges())
-const isOwn = computed(() => userInfo.value?.userId === id.value)
+const { data: badges, run: setBadges } = useRequest(() => getOwnBadges({ userId: id.value }))
+const isOwn = computed(() => String(userInfo.value?.userId) === id.value)
 const skills = computed(() =>
   [
     {
@@ -99,7 +101,7 @@ const handleLike = async (options) => {
     userId: userInfo.value.userId,
     userName: userInfo.value.nickname,
   })
-  pageHelperRef.value?.refresh()
+  await pageHelperRef.value?.refresh()
 }
 const handle2Video = () => {
   try {
@@ -159,29 +161,7 @@ onUnload(async () => {
     })
   }
 })
-onShareAppMessage(async ({ from, target }) => {
-  const res: Page.CustomShareContent = {}
-  if (from === 'button') {
-    console.log(target)
-
-    if (target.dataset.type === 'homepage') {
-      res.title = `${userInfo.value.nickname}: “${designerInfo.value.designDesc}”`
-      res.imageUrl = designerInfo.value?.sharePageUrl
-      res.path = `/pages/mine/homepage/index?id=${id.value}&isShared=true`
-    } else {
-      await shareCircle(target.dataset.options.id)
-      res.path = `/pages/home/moment/index?id=${target.dataset.options.id}&isShared=true`
-      res.imageUrl = target.dataset.options.bannerUrls[0]
-      res.title = `${target.dataset.options.stylistName}: ${target.dataset.options.circleDesc}`
-    }
-  }
-  if (from === 'menu') {
-    res.title = `${userInfo.value.nickname}: “${designerInfo.value.designDesc}”`
-    res.imageUrl = designerInfo.value?.sharePageUrl
-    res.path = `/pages/mine/homepage/index?id=${id.value}&isShared=true`
-  }
-  return res
-})
+onShareAppMessage(shareAppMessage)
 onShareTimeline(() => ({}))
 defineExpose({
   navBarFixed: false,
@@ -194,7 +174,7 @@ defineExpose({
       <!-- <wd-img width="100%" custom-class="aspect-[1.14/1]" /> -->
       <div class="aspect-[1.14/1]">
         <ImageEvo
-          :src="designerInfo?.homePageUrl || NetImages.DesigerHomepageDefaultBg"
+          :src="designerInfo?.homePageUrl || NetImages.DesignerHomepageDefaultBg"
           mode="aspectFill"
         ></ImageEvo>
       </div>
@@ -205,15 +185,16 @@ defineExpose({
               <wd-img
                 width="100%"
                 height="100%"
-                :src="isOwn ? userInfo?.avatar : memberInfo?.avatar || NetImages.DefaultAvatar"
+                :src="memberInfo?.avatar || NetImages.DefaultAvatar"
               ></wd-img>
             </div>
-            <div class="pb-8 flex-1">
+            <div class="pb-8 flex-1 overflow-hidden">
               <div class="flex items-center justify-between">
                 <div class="text-white text-2xl font-normal font-['PingFang_SC'] leading-normal">
                   {{ designerInfo.homePageName || memberInfo.nickname }}
                 </div>
                 <div
+                  v-if="isOwn && features.personalCode"
                   class="flex items-center"
                   @click="router.push(`/pages/mine/homepage/qr-code/index`)"
                 >
@@ -222,10 +203,13 @@ defineExpose({
                 </div>
               </div>
 
-              <div class="flex flex-wrap gap-4">
+              <div
+                class="mt-2.5 flex gap-4 overflow-x-auto whitespace-nowrap"
+                v-if="designerInfo?.personalIdentity != ''"
+              >
                 <template v-for="(it, i) in designerInfo?.personalIdentity?.split('、')" :key="i">
                   <div
-                    class="h-6 px-2 bg-black/10 rounded-[30px] border border-solid border-white/60 justify-center items-center box-border inline-flex"
+                    class="inline-block h-6 px-2 bg-black/10 rounded-[30px] border border-solid border-white/60 justify-center items-center box-border inline-flex"
                   >
                     <div
                       class="text-center text-white text-[10px] font-normal font-['PingFang_SC']"
@@ -241,7 +225,7 @@ defineExpose({
       </div>
     </div>
     <div class="flex-grow flex flex-col bg-white rounded-t-2xl relative bottom-4 gap-5 px-3.5 pt-5">
-      <div class="flex gap-4" v-if="skills.length">
+      <div class="flex gap-4" v-if="skills?.length">
         <template v-for="(it, i) in skills" :key="i">
           <div>
             <span
@@ -262,18 +246,18 @@ defineExpose({
         {{ designerInfo?.designDesc }}
       </div>
 
-      <div class="h-[42px] relative mr--3.5">
+      <div v-if="badges?.length" class="h-[42px] relative mr--3.5">
         <div
           class="h-full left-0 pl-20 pr-6 right-20 top-0 bottom-0 absolute bg-gradient-to-r from-[#ffe9e9] via-[#fff7f7] to-[#fff8f8] rounded-tl-md rounded-bl-md flex flex-col justify-center"
         >
           <div class="">
             <div class="flex items-center gap-4">
-              <template v-for="(it, i) in badges.slice(0, badges.length > 5 ? 3 : 4)" :key="i">
+              <template v-for="(it, i) in badges?.slice(0, badges?.length > 5 ? 3 : 4)" :key="i">
                 <!-- <div class="bg-[#fa9d3b]"> -->
                 <wd-img width="26" mode="widthFix" :src="it.badgeYesObtainedImage"></wd-img>
                 <!-- </div> -->
               </template>
-              <div v-if="badges.length > 5" class="flex">
+              <div v-if="badges?.length > 5" class="flex">
                 <wd-img custom-class="m-a" width="26" mode="widthFix" :src="more"></wd-img>
               </div>
             </div>
@@ -289,7 +273,9 @@ defineExpose({
         </div>
         <div
           class="w-[76px] h-[30px] px-3 right-0 top-1.5 absolute bg-black/90 rounded-tl-[30px] rounded-bl-[30px] justify-center items-center gap-2.5 inline-flex"
-          @click="router.push('/pages/mine/honors/index')"
+          @click="
+            router.push(`/pages/mine/honors/index?id=${id}${isShared ? '&isShared=true' : ''}`)
+          "
         >
           <div class="text-center text-white text-xs font-normal font-['PingFang_SC']">
             查看荣誉
@@ -370,6 +356,7 @@ defineExpose({
                   <MomentItem
                     :options="it"
                     :is-own="userInfo.userId === it.stylistId"
+                    :is-shared="isShared"
                     @delete="handleMomentDelete"
                     @like="handleLike"
                   ></MomentItem>
@@ -382,23 +369,39 @@ defineExpose({
     </div>
     <BottomAppBar fixed placeholder>
       <div class="flex gap-7.5">
-        <div class="flex-1" v-if="userInfo.userId === Number(id)">
+        <div class="flex-1" v-if="isOwn && !isShared">
           <wd-button block :round="false" @click="router.push(`/pages/mine/homepage/edit/index`)">
             编辑
           </wd-button>
         </div>
-        <div class="flex-1" v-if="userInfo.userId === Number(id)">
+        <div class="flex-1" v-if="isOwn && !isShared">
           <button
+            v-if="features.shareMoment"
             class="p-0 after:b-none"
             block
             :round="false"
-            open-type="share"
+            :open-type="features.shareMoment ? 'share' : ''"
             :data-type="'homepage'"
+            :data-share-content="{
+              title: `${userInfo.nickname}: “${designerInfo.designDesc}”`,
+              imageUrl: designerInfo.sharePageUrl,
+              path: `/pages/mine/homepage/index?id=${id}&isShared=true`,
+            }"
+            :data-options="{
+              homepageId: id,
+              userId: userInfo.userId,
+            }"
           >
             <wd-button block :round="false">分享</wd-button>
           </button>
+          <template v-else>
+            <!--            1-->
+            <wd-button block :round="false" @click="clickByPermission('share', () => {})">
+              分享
+            </wd-button>
+          </template>
         </div>
-        <div class="flex-1" v-if="userInfo.userId !== Number(id)">
+        <div class="flex-1" v-if="!isOwn || isShared">
           <wd-button
             block
             :round="false"

+ 84 - 14
packages/app/src/pages/mine/homepage/qr-code/index.vue

@@ -2,20 +2,22 @@
 { "style": { "navigationBarTitleText": "个人码", "navigationBarBackgroundColor": "#fff" } }
 </route>
 <script setup lang="ts">
-import dayjs from 'dayjs'
 import UQRCode from 'uqrcodejs'
-import { getPointsOrder } from '../../../../core/libs/requests'
-import { toQrCodeString } from '../../../../core/utils/common'
+import { getPointsOrder, storeAndPunchIn } from '../../../../core/libs/requests'
+import { qrCodeString2Object, toQrCodeString } from '../../../../core/utils/common'
 import { QrCodeBusinessType } from '../../../../core/libs/enums'
 import { useUserStore } from '../../../../store'
 import { storeToRefs } from 'pinia'
+import { useRouter } from '@/core/utils/router'
 
 const id = ref()
 const userStore = useUserStore()
 const { userInfo } = storeToRefs(userStore)
+const router = useRouter()
+const qrCodeCanvas = ref()
 const { data, run: setData } = useRequest(() => getPointsOrder(id.value), { initialData: {} })
 
-const a = (canvasContext: UniApp.CanvasContext) => {
+const a = async (canvasContext: UniApp.CanvasContext) => {
   const qr = new UQRCode()
   // 设置二维码内容
   // qr.data = data.value.orderNo
@@ -29,42 +31,108 @@ const a = (canvasContext: UniApp.CanvasContext) => {
   // 设置二维码大小,必须与canvas设置的宽高一致
   qr.size = 200
   qr.foregroundImageSrc = userInfo.value?.avatar
+  // console.log(userInfo.value?.avatar)
   // 调用制作二维码方法
   qr.make()
   qr.canvasContext = canvasContext
   // 调用绘制方法将二维码图案绘制到canvas上
-  qr.drawCanvas()
+  await qr.drawCanvas()
 }
 const generateCode = async () => {
-  nextTick(async () => {
-    const currentInstance = getCurrentInstance()
+  const currentInstance = getCurrentInstance()
+  await nextTick(async () => {
     const canvasContext = uni.createCanvasContext('qrcode', currentInstance) // 如果是组件,this必须传入
     a(canvasContext)
   })
 }
 const generateCodeMp = async () => {
   const currentInstance = getCurrentInstance()
-  nextTick(() => {
+  await nextTick(() => {
     uni
       .createSelectorQuery()
       .in(currentInstance)
       .select('#qrcode')
-      .fields({ node: true, size: true }, (res) => {
-        console.log(res)
-      })
+      .fields({ node: true, size: true }, () => {})
       .exec((res) => {
         const canvas = res[0].node
-        canvas.width = 200
-        canvas.height = 200
+        const dpr = wx.getSystemInfoSync().pixelRatio
+        canvas.width = res[0].width * dpr
+        canvas.height = res[0].height * dpr
         const ctx = canvas.getContext('2d')
+        ctx.scale(dpr, dpr)
+        UQRCode.prototype.loadImage = (src: string) =>
+          new Promise((resolve, reject) => {
+            const img = canvas.createImage()
+            img.src = src
+            img.onload = () => {
+              resolve(img)
+            }
+            img.onerror = (err) => {
+              // reject返回错误信息
+              reject(err)
+            }
+          })
         a(ctx)
+        qrCodeCanvas.value = canvas
       })
   })
 }
 
+const handleClickScan = async () => {
+  const { result } = await uni.scanCode({})
+  console.log(result)
+  // const a = qrCodeString2Object('WIFI:S:KM;T:WPA;P:km666888;H:false;;')
+  // console.log(a)
+  // console.log(toQrCodeString('到店', { a: 1, orderId: 2222 }))
+  // console.log(qrCodeString2Object(toQrCodeString('到店', { a: 1, orderId: 2222 })))
+  const { type, options } = qrCodeString2Object(result)
+  // if (type === QrCodeBusinessType.InStoreClockIn) {
+  //   if (!features.value.checkInAtStoreTask) return router.push('/pages/mine/authentication/index')
+  //   try {
+  //     await storeAndPunchIn({ id: options.id })
+  //     // await storeAndPunchIn({ id: 24 })
+  //     router.push(`/pages/mine/scan/result/index?result=${result}`)
+  //   } catch (e) {
+  //     if (e.code === 1000) {
+  //       router.push(
+  //         `/pages/mine/scan/result/index?result=${toQrCodeString(type, { name: options.name, desc: e.msg })}`,
+  //       )
+  //     } else {
+  //       uni.showToast({ title: e.msg, icon: 'none' })
+  //     }
+  //   }
+  //   return
+  // }
+  if (type === QrCodeBusinessType.PagePath) {
+    await router.push(options.path)
+    return
+  }
+  await router.push(`/pages/mine/scan/result/index?result=${result}`)
+}
+const saveQrcode = async () => {
+  try {
+    console.log(qrCodeCanvas.value)
+    const option = {
+      // #ifdef H5
+      canvasId: 'qrcode',
+      // #endif
+      // #ifdef MP-WEIXIN
+      canvas: toRaw(qrCodeCanvas.value),
+      // #endif
+    }
+    console.log(option)
+    const { tempFilePath } = await wx.canvasToTempFilePath(option)
+    console.log(tempFilePath)
+    await uni.saveImageToPhotosAlbum({
+      filePath: tempFilePath,
+    })
+    await uni.showToast({ title: '保存成功' })
+  } catch (e) {
+    await uni.showToast({ title: '保存失败', icon: 'none' })
+  }
+}
 onLoad(async (query: { id: string }) => {
   id.value = query.id
-  //   await setData()
   // #ifdef MP-WEIXIN
   await generateCodeMp()
   // #endif
@@ -98,6 +166,7 @@ onReady(() => {})
     </div>
     <div class="w-full flex items-center justify-around my-10">
       <div
+        @click="handleClickScan"
         class="text-center text-[#3f588f] text-base font-normal font-['PingFang_SC'] leading-normal"
       >
         扫一扫
@@ -109,6 +178,7 @@ onReady(() => {})
       </div>
       <div
         class="text-center text-[#3f588f] text-base font-normal font-['PingFang_SC'] leading-normal"
+        @click="saveQrcode"
       >
         保存图片
       </div>

+ 4 - 4
packages/app/src/pages/mine/homepage/statistics/index.vue

@@ -57,9 +57,9 @@ const setData = async ({ value }) => {
           code: res.code,
           msg: res.msg,
           data: {
-            shareCount: res.data.find((it) => it.bizType === '1')?.quantity || 0,
-            viewCount: res.data.find((it) => it.bizType === '2')?.quantity || 0,
-            winCustomerCount: res.data.find((it) => it.bizType === '3')?.quantity || 0,
+            shareCount: res.data.find((it) => String(it.bizType) === '1')?.quantity || 0,
+            viewCount: res.data.find((it) => String(it.bizType) === '3')?.quantity || 0,
+            winCustomerCount: res.data.find((it) => String(it.bizType) === '2')?.quantity || 0,
           },
         }),
       ),
@@ -94,7 +94,7 @@ onMounted(async () => {
         <template v-for="(it, i) in info" :key="i">
           <div class="flex-1 flex flex-col items-center gap-2">
             <div class="flex items-end gap-0.5">
-              <div class="text-black text-2xl font-medium font-['DIN'] leading-6">
+              <div class="text-black text-2xl font-medium font-['D-DIN-PRO'] leading-6">
                 {{ it.value }}
               </div>
               <div class="text-[#333333] text-sm font-normal font-['PingFang_SC']">

+ 75 - 17
packages/app/src/pages/mine/honors/index.vue

@@ -15,25 +15,50 @@ import { useRouter } from '../../../core/utils/router'
 import arcBottom from '@designer-hub/assets/src/libs/assets/arcBottom'
 import { NetImages } from '../../../core/libs/net-images'
 import ProgressEvo from '@/components/progress-evo.vue'
+import { Certificate } from '@/core/libs/models'
+import dayjs from 'dayjs'
+import { useUserStore } from '@/store'
+import { storeToRefs } from 'pinia'
 
+const id = ref()
+const isShared = ref(false)
 const router = useRouter()
+const userStore = useUserStore()
+const { userInfo } = storeToRefs(userStore)
 const active = ref('badge')
 const tabs = ref([
   { label: '徽章', value: 'badge' },
   { label: '证书', value: 'certificate' },
 ])
-const { data: statistics, run: setStatistics } = useRequest(() => getHonorStatistics())
-const { data: badges, run: setBadges } = useRequest(() => getBadges({}), {
+const { data: statistics, run: setStatistics } = useRequest(() =>
+  getHonorStatistics({ userId: id.value }),
+)
+const { data: badges, run: setBadges } = useRequest(() => getBadges({ userId: id.value }), {
   initialData: {},
 })
-const { data: certificates, run: setCertificates } = useRequest(() => getCertificates({}), {
-  initialData: [],
-})
-onMounted(async () => {
+const { data: certificates, run: setCertificates } = useRequest(
+  () => getCertificates({ userId: id.value }),
+  {
+    initialData: [],
+  },
+)
+const currentCertificate = ref<Certificate | undefined>()
+const isOwner = computed(() => String(userInfo.value?.userId) === id.value)
+onLoad(async (query?: Record<string | 'active' | 'id' | 'isShared', string>) => {
+  if (query?.active) {
+    active.value = query.active
+  }
+  if (query?.id) {
+    id.value = query.id
+  }
+  if (query?.isShared) {
+    isShared.value = true
+  }
   await setStatistics()
   await setBadges()
   await setCertificates()
-  console.log(badges.value)
+  // currentCertificate.value = certificates.value[0]
+  // console.log(badges.value)
 })
 </script>
 <template>
@@ -67,7 +92,7 @@ onMounted(async () => {
                 {{ it.label }}
               </div>
               <div class="flex items-end gap-1">
-                <div class="text-center text-white text-2xl font-medium font-['DIN'] leading-6">
+                <div class="text-center text-white text-2xl font-medium font-['D-DIN-PRO'] leading-6">
                   {{ it.value }}
                 </div>
                 <div
@@ -101,10 +126,15 @@ onMounted(async () => {
       </template>
     </div>
     <template v-if="active === 'badge'">
+      <!--      v-if="String(userInfo.userId) === id"-->
       <Card custom-class="border border-solid bg-[#25221f]! border-[rgba(255,236,185,0.20)]">
         <div class="grid grid-cols-[90px_1fr] gap-x-4">
           <div class="grid-row-start-1 grid-row-end-4 col-start-1">
-            <wd-img width="90" height="90" src="https://via.placeholder.com/86x91"></wd-img>
+            <wd-img
+              width="90"
+              height="90"
+              src="https://image.zhuchaohui.com/zhucaohui/1f8f9129cc24b2a9bc25d8a2821da64233994c38efea36a80f4b1aade0476bab.png"
+            ></wd-img>
           </div>
           <div class="row-start-1 col-start-2 flex items-center justify-between">
             <div
@@ -159,8 +189,10 @@ onMounted(async () => {
           </div>
         </div>
       </Card>
+
       <template v-for="([key, it], index) in Object.entries(badges)" :key="index">
         <Card
+          v-if="!((!isOwner || isShared) && ['积分徽章', '积分徽章'].includes(key))"
           custom-class="bg-[#171615]! text-white border border-solid border-[rgba(255,236,185,0.20)]"
         >
           <div class="flex items-center gap-2 py-4">
@@ -199,18 +231,44 @@ onMounted(async () => {
       <div>
         <div class="grid grid-cols-2 gap-2.5">
           <template v-for="(it, i) in certificates" :key="i">
-            <div
-              @click="
-                router.push(
-                  `/pages/mine/honors/detail/index?type=certificate&data=${JSON.stringify(it)}`,
-                )
-              "
-            >
-              <wd-img width="100%" :src="it.certificateImage" mode="widthFix"></wd-img>
+            <div class="aspect-[1.35/1]" @click="currentCertificate = it">
+              <wd-img
+                width="100%"
+                height="100%"
+                :src="it.certificateImage"
+                mode="aspectFill"
+                custom-class="vertical-bottom"
+              ></wd-img>
             </div>
           </template>
         </div>
       </div>
     </template>
+    <wd-overlay :show="currentCertificate !== undefined" @click="currentCertificate = undefined">
+      <div class="h-full flex flex-col items-center justify-around">
+        <div class="flex flex-col">
+          <wd-img
+            width="85vw"
+            height="94vw"
+            :src="currentCertificate?.certificateImage"
+            mode="aspectFit"
+          ></wd-img>
+          <div
+            class="text-center text-white text-xl font-normal font-['PingFang_SC'] uppercase mt-7"
+          >
+            {{ currentCertificate?.certificateName }}
+          </div>
+          <div class="flex center gap-3">
+            <div class="w-3.5 h-[1.5px] bg-white"></div>
+            <div class="text-center text-white text-sm font-normal font-['PingFang_SC'] uppercase">
+              <!--          2024年10月2日获得-->
+              {{ dayjs(currentCertificate?.createTime).format('YYYY年MM月DD日') }}获得
+            </div>
+            <div class="w-3.5 h-[1.5px] bg-white"></div>
+          </div>
+        </div>
+        <wd-button custom-class="bg-[#0CBE7D]!">去分享</wd-button>
+      </div>
+    </wd-overlay>
   </div>
 </template>

Some files were not shown because too many files changed in this diff