Răsfoiți Sursa

代码提交

zhangyaowen 1 lună în urmă
părinte
comite
9d1a6be382

+ 4 - 4
pages/ai/index.vue

@@ -3,13 +3,13 @@
   <s-layout :bgStyle="{ color: 'transparent' }" :title="state.title" class="set-wrap">
     <view>
       <!-- #ifdef H5 -->
-      <iframe src="http://42.194.163.46:9005/chatbot/Daahm2N28l24ECJx"
-        style="width: 100%; height: calc(100vh - 50px);"></iframe>
+      <!-- <iframe src="http://42.194.163.46:9005/chatbot/Daahm2N28l24ECJx"
+        style="width: 100%; height: calc(100vh - 50px);"></iframe> -->
       <!-- #endif -->
 
       <!-- #ifdef MP-WEIXIN -->
-      <web-view src="http://42.194.163.46:9005/chatbot/Daahm2N28l24ECJx"
-        style="width: 100vw; height: 100vh;"></web-view>
+      <!-- <web-view src="http://42.194.163.46:9005/chatbot/Daahm2N28l24ECJx"
+        style="width: 100vw; height: 100vh;"></web-view> -->
       <!-- #endif -->
     </view>
   </s-layout>

+ 185 - 0
pages/chat/components/components/enterpriseCard.vue

@@ -0,0 +1,185 @@
+<template>
+  <view class="titleClass">您好,我是赢伟达智能客服小助手,很高兴为您服务</view>
+  <view class="log-card">
+    <view class="log-card__content">
+      <view @click="handleClick(item)" class="log-card__text-wrapper" v-for="item in productSpuPageData" :key="item.id">
+        <text class="log-card__text">
+          {{ item.name }}
+        </text>
+        <scroll-view class="log-card__image-group" scroll-x>
+          <image class="log-card__image" mode="aspectFill" :src="item.picUrl" />
+        </scroll-view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, inject } from "vue";
+import AiApi from '@/sheep/api/aiApi/index.js'
+import KeFuApi from '@/sheep/api/promotion/kefu';
+import { onLoad } from "@dcloudio/uni-app";
+const props = defineProps({
+  item: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+console.log(props.item, 33333222);
+const productSpuPageData = ref([])
+const productSpuPageAxios = async () => {
+  const res = await AiApi.productSpuPage({
+    page: 1,
+    pageSize: 10,
+    categoryIds: props.item.id,
+  });
+  productSpuPageData.value = res.data.list
+  console.log(res, 33333222);
+};
+const route = ref({})
+onLoad((options) => {
+  route.value = options
+});
+const EventSourceFun = inject('EventSourceFun');
+const loadingInput = inject('loadingInput');
+const handleClick = async (item) => {
+  const data = {
+    conversationId: route.value.conversationId,
+    contentType: '1',
+    content: JSON.stringify({ text: `components|<enterpriseDetailCard id="${item.id}"></enterpriseDetailCard>` }),
+    relUserId: route.value.relUserId
+  };
+  console.log(data, 33333222);
+  loadingInput.value = true;
+  EventSourceFun(data, false)
+  // EventSourceFun.value.send(data)
+  // await KeFuApi.sendKefuMessageNew(data);
+}
+productSpuPageAxios();
+</script>
+
+<style lang="scss" scoped>
+.titleClass {
+  font-size: 15px;
+  color: #444444;
+  margin-bottom: 12px;
+  margin-left: 12px;
+}
+
+.log-card {
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  align-items: flex-start;
+  padding: 30rpx;
+  border-radius: 16rpx;
+  border: 1px solid var(--N3-, #ebedf0);
+  background-color: #ffffff;
+  position: relative;
+  height: auto;
+
+  &__content {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-start;
+    gap: 24rpx;
+    flex-shrink: 0;
+    position: relative;
+  }
+
+  &__text-wrapper {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-start;
+    gap: 24rpx;
+    flex-shrink: 0;
+    text-align: justify;
+    font-size: 32rpx;
+    font-family: "Source Han Sans";
+    font-weight: 400;
+    line-height: 46rpx;
+    color: var(---1, #222222);
+    position: relative;
+  }
+
+  &__text {
+    flex-shrink: 0;
+  }
+
+  &__image-group {
+    display: flex;
+    width: 100%;
+    flex-direction: row;
+    white-space: nowrap;
+  }
+
+  &__image {
+    width: 186rpx;
+    height: 186rpx;
+    flex-shrink: 0;
+    border-radius: 8rpx;
+    margin-right: 16rpx;
+  }
+
+  &__tags {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: flex-start;
+    gap: 16rpx;
+    flex-shrink: 0;
+    position: relative;
+  }
+
+  &__tag {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: flex-start;
+    // padding: 6rpx 16rpx;
+    flex-shrink: 0;
+    border-radius: 24rpx;
+    background-color: var(--, #f3f4f4);
+    line-height: 36rpx;
+    white-space: pre;
+    background: #f1f2f2;
+    padding: 5px 16px;
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+
+    &--primary {
+      flex-direction: row;
+      align-items: center;
+      gap: 4rpx;
+      color: #773df7;
+    }
+  }
+
+  &__tag-icon {
+    width: 28rpx;
+    height: 28rpx;
+    position: relative;
+    flex-shrink: 0;
+  }
+
+  &__tag-text {
+    font-size: 24rpx;
+    font-family: "Source Han Sans";
+    line-height: 36rpx;
+    font-weight: 400;
+    color: #3d3d3d;
+
+    &--gradient {
+      -webkit-background-clip: text;
+      background-clip: text;
+      -webkit-text-fill-color: transparent;
+      text-fill-color: transparent;
+    }
+  }
+}
+</style>

+ 185 - 0
pages/chat/components/components/enterpriseDetailCard.vue

@@ -0,0 +1,185 @@
+<template>
+  <view class="titleClass">您好,我是赢伟达智能客服小助手,很高兴为您服务</view>
+  <view class="log-card">
+    <view class="log-card__content">
+      <view @click="handleClick(item)" class="log-card__text-wrapper" v-for="item in productSpuPageData" :key="item.id">
+        <text class="log-card__text">
+          {{ item.name }}
+        </text>
+        <scroll-view class="log-card__image-group" scroll-x>
+          <image class="log-card__image" mode="aspectFill" :src="item.picUrl" />
+        </scroll-view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, inject } from "vue";
+import AiApi from '@/sheep/api/aiApi/index.js'
+import KeFuApi from '@/sheep/api/promotion/kefu';
+import { onLoad } from "@dcloudio/uni-app";
+const props = defineProps({
+  item: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+console.log(props.item, 33333222);
+const productSpuPageData = ref([])
+const productSpuPageAxios = async () => {
+  const res = await AiApi.productSpuPage({
+    page: 1,
+    pageSize: 10,
+    categoryIds: props.item.id,
+  });
+  productSpuPageData.value = res.data.list
+  console.log(res, 33333222);
+};
+const route = ref({})
+onLoad((options) => {
+  route.value = options
+});
+const EventSourceFun = inject('EventSourceFun');
+const loadingInput = inject('loadingInput');
+const handleClick = async (item) => {
+  const data = {
+    conversationId: route.value.conversationId,
+    contentType: '1',
+    content: JSON.stringify({ text: `<enterpriseDetailCard id="${item.id}" #spu='${item.id}'></enterpriseDetailCard>` }),
+    relUserId: route.value.relUserId
+  };
+  console.log(data, 33333222);
+  loadingInput.value = true;
+  EventSourceFun(data, false)
+  // EventSourceFun.value.send(data)
+  // await KeFuApi.sendKefuMessageNew(data);
+}
+productSpuPageAxios();
+</script>
+
+<style lang="scss" scoped>
+.titleClass {
+  font-size: 15px;
+  color: #444444;
+  margin-bottom: 12px;
+  margin-left: 12px;
+}
+
+.log-card {
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  align-items: flex-start;
+  padding: 30rpx;
+  border-radius: 16rpx;
+  border: 1px solid var(--N3-, #ebedf0);
+  background-color: #ffffff;
+  position: relative;
+  height: auto;
+
+  &__content {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-start;
+    gap: 24rpx;
+    flex-shrink: 0;
+    position: relative;
+  }
+
+  &__text-wrapper {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-start;
+    gap: 24rpx;
+    flex-shrink: 0;
+    text-align: justify;
+    font-size: 32rpx;
+    font-family: "Source Han Sans";
+    font-weight: 400;
+    line-height: 46rpx;
+    color: var(---1, #222222);
+    position: relative;
+  }
+
+  &__text {
+    flex-shrink: 0;
+  }
+
+  &__image-group {
+    display: flex;
+    width: 100%;
+    flex-direction: row;
+    white-space: nowrap;
+  }
+
+  &__image {
+    width: 186rpx;
+    height: 186rpx;
+    flex-shrink: 0;
+    border-radius: 8rpx;
+    margin-right: 16rpx;
+  }
+
+  &__tags {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: flex-start;
+    gap: 16rpx;
+    flex-shrink: 0;
+    position: relative;
+  }
+
+  &__tag {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: flex-start;
+    // padding: 6rpx 16rpx;
+    flex-shrink: 0;
+    border-radius: 24rpx;
+    background-color: var(--, #f3f4f4);
+    line-height: 36rpx;
+    white-space: pre;
+    background: #f1f2f2;
+    padding: 5px 16px;
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+
+    &--primary {
+      flex-direction: row;
+      align-items: center;
+      gap: 4rpx;
+      color: #773df7;
+    }
+  }
+
+  &__tag-icon {
+    width: 28rpx;
+    height: 28rpx;
+    position: relative;
+    flex-shrink: 0;
+  }
+
+  &__tag-text {
+    font-size: 24rpx;
+    font-family: "Source Han Sans";
+    line-height: 36rpx;
+    font-weight: 400;
+    color: #3d3d3d;
+
+    &--gradient {
+      -webkit-background-clip: text;
+      background-clip: text;
+      -webkit-text-fill-color: transparent;
+      text-fill-color: transparent;
+    }
+  }
+}
+</style>

+ 80 - 31
pages/chat/components/messageListItem.vue

@@ -15,6 +15,8 @@
           {{ message.content }}
         </view>
       </view>
+
+
       <!-- 消息体渲染管理员消息和用户消息并左右展示  -->
       <view v-if="message.contentType !== KeFuMessageContentTypeEnum.SYSTEM" class="ss-flex ss-col-top" :class="[
         message.senderId !== userInfo.id
@@ -24,40 +26,49 @@
         <image v-show="message.receiverId === userInfo.id" class="chat-avatar ss-m-r-24" :src="message.receiverAvatar ||
           default1
           " mode="aspectFill"></image>
-        <!-- 内容 -->
-        <template v-if="message.contentType === KeFuMessageContentTypeEnum.TEXT">
+        <template v-if="message.content.indexOf('components|') != -1">
           <view class="message-box" :class="{ admin: message.senderId === UserTypeEnum.ADMIN }">
-            <mp-html :content="replaceEmoji(getMessageContent(message).text || message.content)" />
-          </view>
-        </template>
-        <template v-if="message.contentType === KeFuMessageContentTypeEnum.IMAGE">
-          <view class="message-box" :class="{ admin: message.senderId === UserTypeEnum.ADMIN }"
-            :style="{ width: '200rpx' }">
-            <su-image class="message-img" isPreview
-              :previewList="[sheep.$url.cdn(getMessageContent(message).picUrl || message.content)]" :current="0"
-              :src="sheep.$url.cdn(getMessageContent(message).picUrl || message.content)" :height="200" :width="200"
-              mode="aspectFill"></su-image>
+            <!-- <component :item="extractAttributes(message.content.split('|')[1])"
+              :is="dynamicComponent(extractComponentName(message.content))" /> -->
           </view>
         </template>
-        <template v-if="message.contentType === KeFuMessageContentTypeEnum.PRODUCT">
-          <div class="ss-m-b-10">
-            <GoodsItem :goodsData="getMessageContent(message)"
-              @tap="sheep.$router.go('/pages/goods/index', { id: getMessageContent(message).spuId })" />
-          </div>
-        </template>
-        <template v-if="message.contentType === KeFuMessageContentTypeEnum.ORDER">
-          <OrderItem :orderData="getMessageContent(message)"
-            @tap="sheep.$router.go('/pages/order/detail', { id: getMessageContent(message).id })" />
-        </template>
-        <template v-if="message.contentType === KeFuMessageContentTypeEnum.AI">
-          <view class="message-box" :class="{ admin: message.senderId === UserTypeEnum.ADMIN }">
-            <zero-markdown-view
-              :markdown="replaceEmoji(getMessageContent(message).text || message.content)"></zero-markdown-view>
-            <!-- <mp-html :content="replaceEmoji(getMessageContent(message).text || message.content)" /> -->
-            <!-- {{ message.messageId }}----
-            {{ loadingId }} -->
-            <text v-if="loadingInput && message.isAi">...</text>
-          </view>
+        <template v-else>
+
+          <!-- 内容 -->
+          <template v-if="message.contentType === KeFuMessageContentTypeEnum.TEXT">
+            <view class="message-box" :class="{ admin: message.senderId === UserTypeEnum.ADMIN }">
+              <mp-html :content="replaceEmoji(getMessageContent(message).text || message.content)" />
+            </view>
+          </template>
+          <template v-if="message.contentType === KeFuMessageContentTypeEnum.IMAGE">
+            <view class="message-box" :class="{ admin: message.senderId === UserTypeEnum.ADMIN }"
+              :style="{ width: '200rpx' }">
+              <su-image class="message-img" isPreview
+                :previewList="[sheep.$url.cdn(getMessageContent(message).picUrl || message.content)]" :current="0"
+                :src="sheep.$url.cdn(getMessageContent(message).picUrl || message.content)" :height="200" :width="200"
+                mode="aspectFill"></su-image>
+            </view>
+          </template>
+          <template v-if="message.contentType === KeFuMessageContentTypeEnum.PRODUCT">
+            <div class="ss-m-b-10">
+              <GoodsItem :goodsData="getMessageContent(message)"
+                @tap="sheep.$router.go('/pages/goods/index', { id: getMessageContent(message).spuId })" />
+            </div>
+          </template>
+          <template v-if="message.contentType === KeFuMessageContentTypeEnum.ORDER">
+            <OrderItem :orderData="getMessageContent(message)"
+              @tap="sheep.$router.go('/pages/order/detail', { id: getMessageContent(message).id })" />
+          </template>
+          <template v-if="message.contentType === KeFuMessageContentTypeEnum.AI">
+            <view class="message-box" :class="{ admin: message.senderId === UserTypeEnum.ADMIN }">
+              <zero-markdown-view
+                :markdown="replaceEmoji(getMessageContent(message).text || message.content)"></zero-markdown-view>
+              <!-- <mp-html :content="replaceEmoji(getMessageContent(message).text || message.content)" /> -->
+              <!-- {{ message.messageId }}----
+              {{ loadingId }} -->
+              <text v-if="loadingInput && message.isAi">...</text>
+            </view>
+          </template>
         </template>
         <!-- user头像 -->
         <image v-if="message.senderId === userInfo.id" class="chat-avatar ss-m-l-24" :src="message.receiverAvatar ||
@@ -132,7 +143,36 @@ function replaceEmoji(data) {
   }
   return newData;
 }
+const extractAttributes = (tagString) => {
+  // 使用正则表达式匹配所有属性
+  const attrRegex = /(\w+)=["']({[^}]+}|[^"']+)["']/g;
+  const attributes = {};
 
+  let match;
+  while ((match = attrRegex.exec(tagString)) !== null) {
+    const [_, key, value] = match;
+    try {
+      // 尝试解析JSON格式的值
+      attributes[key] = value.startsWith("{") ? JSON.parse(value) : value;
+    } catch (e) {
+      // 如果不是JSON格式,直接使用原始值
+      attributes[key] = value;
+    }
+  }
+
+  return attributes;
+};
+const extractComponentName = (tagString) => {
+  // 使用正则表达式匹配所有标签中的组件名
+  const regex = /<(\w+)(?:\s+[^>]*)?><\/\1>/g;
+  const matches = [...tagString.matchAll(regex)];
+
+  if (matches.length > 0) {
+    // 返回所有匹配到的组件名称数组
+    return matches.map((match) => match[1])[0];
+  }
+  return null; // 如果没有匹配到则返回null
+};
 function selEmojiFile(name) {
   for (let index in emojiList) {
     if (emojiList[index].name === name) {
@@ -141,6 +181,15 @@ function selEmojiFile(name) {
   }
   return false;
 }
+import enterpriseCard from '@/pages/chat/components/components/enterpriseCard.vue';
+import enterpriseDetailCard from '@/pages/chat/components/components/enterpriseDetailCard.vue';
+const dynamicComponent = (conm) => {
+  const components = {
+    enterpriseCard: enterpriseCard,
+    enterpriseDetailCard: enterpriseDetailCard,
+  };
+  return components[conm];
+};
 </script>
 
 <style scoped lang="scss">

+ 43 - 38
pages/chat/index.vue

@@ -43,7 +43,7 @@ import { useWebSocket } from '@/sheep/hooks/useWebSocket';
 import { jsonParse } from '@/sheep/util';
 import { onLoad } from '@dcloudio/uni-app';
 const EventSourceRef = ref(null); //AIref
-
+const chatMsgData = ref({});
 const eventSourceUrl = import.meta.env.SHOPRO_BASE_URL + "/app-api/promotion/kefu-message/sendStream"; //ai客服URL
 // const eventSourceUrl = "https://192.168.10.17:9095/app-api/infra/ai-dify/chat-messages-stream"; //ai客服URL
 // ai客服流式上传参数
@@ -58,7 +58,7 @@ const eventSourceOptions = computed(() => {
     method: "POST",
     body: JSON.stringify({
       "contentType": 1,
-      "content": chat.msg,
+      "content": chatMsgData.value.content || chat.msg,
       "relUserId": route.value.relUserId
       // type: "律师咨询",
       // query: chat.msg
@@ -68,6 +68,46 @@ const eventSourceOptions = computed(() => {
 const answerArr = ref('');
 const loadingInput = ref(false); // 加载状态
 provide('loadingInput', loadingInput); // 依赖注入加载状态
+const EventSourceFun = async (data, is = true) => {
+  chatMsgData.value = data;
+  const avatarObj = messageListRef.value.getAvatar() || {}
+  const params = {
+    id: 1,
+    conversationId: route.value.conversationId,
+    senderId: userInfo.value.id,
+    senderAvatar: avatarObj.senderAvatar || '',
+    senderType: 1,
+    receiverId: route.value.relUserId,
+    receiverAvatar: avatarObj.receiverAvatar || '',
+    receiverType: null,
+    contentType: 1,
+    content: is ? (JSON.stringify({ text: chat.msg })) : data.content,
+    readStatus: true,
+    createTime: 1745546275000
+  }
+  await messageListRef.value.refreshMessageList(params);
+  const params1 = {
+    ids: 1,
+    isAi: true,
+    isLoading: true,
+    conversationId: route.value.conversationId,
+    senderId: route.value.relUserId,
+    senderAvatar: avatarObj.receiverAvatar || '',
+    senderType: 1,
+    receiverId: userInfo.value.id,
+    receiverAvatar: avatarObj.senderAvatar || '',
+    receiverType: null,
+    contentType: 22,
+    content: '',
+    readStatus: true,
+    createTime: 1745546275000
+  }
+  await messageListRef.value.refreshMessageList(params1);
+  console.log(data, 22222);
+  await EventSourceRef.value.send(data);
+}
+provide('EventSourceFun', EventSourceFun); // 依赖注入加载状态
+
 const loadingId = ref(""); // 加载的id
 // ai客服发送消息接收回调
 const handleCallback = async (e) => {
@@ -133,42 +173,7 @@ async function onSendMessage() {
       await KeFuApi.sendKefuMessageNew(data);
     } else {
       if (idArr.includes(route.value.relUserId)) {
-        console.log(messageListRef.value.getAvatar(), 555222233)
-        const avatarObj = messageListRef.value.getAvatar()
-        const params = {
-          id: 1,
-          conversationId: route.value.conversationId,
-          senderId: userInfo.value.id,
-          senderAvatar: avatarObj.senderAvatar || '',
-          senderType: 1,
-          receiverId: route.value.relUserId,
-          receiverAvatar: avatarObj.receiverAvatar || '',
-          receiverType: null,
-          contentType: 1,
-          content: JSON.stringify({ text: chat.msg }),
-          readStatus: true,
-          createTime: 1745546275000
-        }
-        await messageListRef.value.refreshMessageList(params);
-        const params1 = {
-          ids: 1,
-          isAi: true,
-          isLoading: true,
-          conversationId: route.value.conversationId,
-          senderId: route.value.relUserId,
-          senderAvatar: avatarObj.receiverAvatar || '',
-          senderType: 1,
-          receiverId: userInfo.value.id,
-          receiverAvatar: avatarObj.senderAvatar || '',
-          receiverType: null,
-          contentType: 22,
-          content: '',
-          readStatus: true,
-          createTime: 1745546275000
-        }
-        console.log(params1, params, 555222233)
-        await messageListRef.value.refreshMessageList(params1);
-        await EventSourceRef.value.send(data);
+        await EventSourceFun(data);
       } else {
         await KeFuApi.sendKefuMessageNew(data);
       }

+ 2 - 2
pages/goods/components/detail/detail-content-card.vue

@@ -10,12 +10,12 @@
         <!-- #ifdef H5 -->
         <iframe :src="goodsInfo.userHtml" style="width: 100%; height: calc(100vh - 94px); border: none;"></iframe>
         <!-- #endif -->
-
         <!-- #ifdef MP-WEIXIN -->
-        <web-view :src="goodsInfo.userHtml" style="width: 100%; height: 100%;"></web-view>
+        <!-- <web-view :src="goodsInfo.userHtml" style="width: 100%; height: 800rpx; overflow: auto;"></web-view> -->
         <!-- #endif -->
       </template>
       <template v-else>
+        <!-- {{ content }} -->
         <view style="padding: 10px;" v-html="content"></view>
       </template>
     </view>

+ 4 - 1
pages/index/category.vue

@@ -111,12 +111,15 @@ function loadMore() {
 }
 
 onLoad(async (params) => {
-  await getList();
 
+  await getList();
   // 首页点击分类的处理:查找满足条件的分类
   const foundCategory = state.categoryList.find((category) => category.id === Number(params.id));
   // 如果找到则调用 onMenu 自动勾选相应分类,否则调用 onMenu(0) 勾选第一个分类
   onMenu(foundCategory ? state.categoryList.indexOf(foundCategory) : 0);
+  if (params.type) {
+    state.activeMenu = Number(params.type);
+  }
 });
 
 function handleScrollToLower() {

+ 284 - 0
pages/index/com/productServic.vue

@@ -0,0 +1,284 @@
+<script setup>
+defineProps(['style']);
+import sheep from '@/sheep';
+const routerClick = (type) => {
+  sheep.$router.go('/pages/index/category?type=' + type);
+};
+</script>
+
+<template>
+  <view class="product-service">
+    <view class="financial-product" @tap="routerClick(0)">
+      <view class="product-content">
+        <image class="product-icon" :src="sheep.$url.static('/static/home/1.png')" />
+        <text class="title">金融产品</text>
+        <text class="desc">金融咨询,风险评估</text>
+      </view>
+      <text class="view-btn">点击查看</text>
+      <image class="product-bg" :src="sheep.$url.static('/static/home/2.png')" />
+    </view>
+    <view class="service-wrapper">
+      <view class="lawyer-service" @tap="routerClick(2)">
+        <view class="service-content">
+          <text class="title">律师服务</text>
+          <image class="service-icon" :src="sheep.$url.static('/static/home/3.png')" />
+        </view>
+        <text class="desc">轻松投资,稳创财富</text>
+        <image class="service-bg" :src="sheep.$url.static('/static/home/4.png')" />
+      </view>
+      <view class="partner" @tap="routerClick(4)">
+        <view class="partner-content">
+          <text class="title">合作企业</text>
+          <text class="desc">期待诚信,货真价实</text>
+          <image class="partner-icon" :src="sheep.$url.static('/static/home/5.png')" />
+        </view>
+        <image class="partner-bg" :src="sheep.$url.static('/static/home/6.png')" />
+      </view>
+    </view>
+  </view>
+</template>
+
+
+<style lang="scss" scoped>
+.product-service {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  align-items: flex-start;
+  gap: 11px;
+  position: relative;
+  width: 100%;
+  height: auto;
+  min-height: 100%;
+
+  .financial-product {
+    position: relative;
+    width: 158px;
+    height: 179px;
+    flex-shrink: 0;
+    overflow: hidden;
+    border-radius: 8px;
+    background-color: #e8f3ff;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    isolation: isolate;
+
+    .product-content {
+      position: relative;
+      width: 177px;
+      height: 120px;
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+      z-index: 2;
+      margin-top: -38px;
+      margin-left: auto;
+      margin-right: -35px;
+
+      .product-icon {
+        width: 120px;
+        height: auto;
+        position: absolute;
+        top: 0;
+        right: 0;
+        bottom: 0;
+      }
+
+      .title {
+        position: absolute;
+        bottom: 33px;
+        left: 0;
+        font-size: 18px;
+        font-family: 'PingFang SC';
+        line-height: 25px;
+        color: #333333;
+        white-space: pre;
+      }
+
+      .desc {
+        position: absolute;
+        bottom: 12px;
+        left: 0;
+        font-size: 12px;
+        font-family: 'PingFang SC';
+        line-height: 17px;
+        color: #666666;
+        white-space: pre;
+      }
+    }
+
+    .view-btn {
+      display: flex;
+      flex-direction: row;
+      justify-content: center;
+      align-items: center;
+      padding: 4px 8px;
+      gap: 2px;
+      border-radius: 58px;
+      background-color: #3a74f2;
+      font-size: 12px;
+      font-family: 'PingFang SC';
+      line-height: 17px;
+      color: #ffffff;
+      white-space: pre;
+      z-index: 1;
+      position: relative;
+      margin-top: 4px;
+      margin-left: 16px;
+    }
+
+    .product-bg {
+      width: 70px;
+      height: 68px;
+      position: absolute;
+      right: -7px;
+      bottom: 0;
+      z-index: 0;
+    }
+  }
+
+  .service-wrapper {
+    width: 174px;
+    height: 178px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: flex-start;
+    gap: 12px;
+    flex-shrink: 0;
+    position: relative;
+
+    .lawyer-service {
+      position: relative;
+      width: 174px;
+      height: 83px;
+      flex-shrink: 0;
+      overflow: hidden;
+      border-radius: 8px;
+      background-color: #e0f5f8;
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+      isolation: isolate;
+
+      .service-content {
+        position: relative;
+        width: 166px;
+        height: 95px;
+        display: flex;
+        flex-direction: column;
+        align-items: flex-start;
+        z-index: 1;
+        margin-top: -48px;
+        margin-left: auto;
+        margin-right: -8px;
+
+        .title {
+          position: absolute;
+          bottom: 4px;
+          left: 0;
+          font-size: 18px;
+          font-family: 'PingFang SC';
+          line-height: 25px;
+          color: #333333;
+          white-space: pre;
+        }
+
+        .service-icon {
+          width: 95px;
+          height: auto;
+          position: absolute;
+          top: 0;
+          right: 0;
+          bottom: 0;
+        }
+      }
+
+      .desc {
+        position: absolute;
+        bottom: 19px;
+        left: 16px;
+        font-size: 12px;
+        font-family: 'PingFang SC';
+        line-height: 17px;
+        color: #666666;
+        white-space: pre;
+        z-index: 0;
+      }
+
+      .service-bg {
+        width: 50px;
+        height: 55px;
+        position: absolute;
+        right: -8px;
+        bottom: -4px;
+        z-index: 2;
+      }
+    }
+
+    .partner {
+      position: relative;
+      width: 174px;
+      height: 83px;
+      flex-shrink: 0;
+      overflow: hidden;
+      border-radius: 8px;
+      background-color: #eeeffa;
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+
+      .partner-content {
+        position: absolute;
+        width: 181px;
+        height: 118px;
+        right: -23px;
+        bottom: 19px;
+        display: flex;
+        flex-direction: column;
+        align-items: flex-start;
+
+        .title {
+          position: absolute;
+          bottom: 21px;
+          left: 0;
+          font-size: 18px;
+          font-family: 'PingFang SC';
+          line-height: 25px;
+          color: #333333;
+          white-space: pre;
+        }
+
+        .desc {
+          position: absolute;
+          bottom: 0;
+          left: 0;
+          font-size: 12px;
+          font-family: 'PingFang SC';
+          line-height: 17px;
+          color: #666666;
+          white-space: pre;
+        }
+
+        .partner-icon {
+          width: 110px;
+          height: auto;
+          position: absolute;
+          top: 0;
+          right: 0;
+          bottom: 8px;
+        }
+      }
+
+      .partner-bg {
+        width: 48px;
+        height: 53px;
+        position: absolute;
+        right: -6.5px;
+        bottom: -1px;
+      }
+    }
+  }
+}
+</style>

+ 112 - 0
pages/index/com/serviceAdvantages.vue

@@ -0,0 +1,112 @@
+<script setup>
+defineProps(['style']);
+import sheep from '@/sheep';
+</script>
+
+<template>
+  <view class="service-advantages">
+    <view class="service-item">
+      <view class="service-content">
+        <image class="service-icon" :src="sheep.$url.static('/static/home/zytd.png')" />
+        <view class="service-text">
+          <text class="title">专业团队</text>
+          <text class="desc">专注于小企业\n经验丰富</text>
+        </view>
+      </view>
+    </view>
+    <view class="service-item">
+      <view class="service-content">
+        <image class="service-icon" :src="sheep.$url.static('/static/home/zyzh.png')" />
+        <view class="service-text">
+          <text class="title">资源整合</text>
+          <text class="desc">整合多方资源\n助力企业发展</text>
+        </view>
+      </view>
+    </view>
+    <view class="service-item">
+      <view class="service-content">
+        <image class="service-icon" :src="sheep.$url.static('/static/home/dzfw.png')" />
+        <view class="service-text">
+          <text class="title">定制服务</text>
+          <text class="desc">按需定制\n精准服务</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+
+<style lang="scss" scoped>
+.service-advantages {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
+  align-items: flex-start;
+  gap: 12px;
+  position: relative;
+  // width: 100vw;
+
+  .service-item {
+    width: calc(33.33% - 8px);
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: flex-start;
+    padding: 8px 18px;
+    flex-shrink: 0;
+    overflow: hidden;
+    border-radius: 8px;
+    background-color: #ffffff;
+    position: relative;
+
+    .service-content {
+      width: 72px;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+      gap: 8px;
+      flex-shrink: 0;
+      position: relative;
+
+      .service-icon {
+        width: 48px;
+        height: 48px;
+        position: relative;
+        flex-shrink: 0;
+        margin: -1px 0 0;
+      }
+
+      .service-text {
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        align-items: center;
+        gap: 4px;
+        flex-shrink: 0;
+        position: relative;
+
+        .title {
+          flex-shrink: 0;
+          font-size: 14px;
+          font-family: 'PingFang SC';
+          font-weight: normal;
+          line-height: 20px;
+          color: var(--333, #333333);
+          white-space: pre;
+        }
+
+        .desc {
+          flex-shrink: 0;
+          text-align: center;
+          font-size: 12px;
+          font-family: 'PingFang SC';
+          font-weight: normal;
+          line-height: 17px;
+          color: var(--999, #999999);
+        }
+      }
+    }
+  }
+}
+</style>

+ 46 - 0
pages/index/com/title.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="product-service-frame">
+    <div class="service-indicator"></div>
+    <span class="service-text"> {{ title }} </span>
+  </div>
+</template>
+<script setup>
+defineProps({
+  title: {
+    type: String,
+    default: ''
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.product-service-frame {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
+  align-items: center;
+  gap: 8px;
+  font-size: 16px;
+  font-family: 'Source Han Sans';
+  font-weight: 700;
+  line-height: 23px;
+  color: var(--N8---, #1b1b1b);
+  white-space: pre;
+  position: relative;
+  width: 100%;
+  height: auto;
+  min-height: 100%;
+  padding: 16px 0;
+}
+
+.service-indicator {
+  width: 3px;
+  height: 14px;
+  flex-shrink: 0;
+  background-color: #3a74f2;
+}
+
+.service-text {
+  flex-shrink: 0;
+}
+</style>

+ 88 - 0
pages/index/index copy.vue

@@ -0,0 +1,88 @@
+<!-- 首页,支持店铺装修 -->
+<template>
+	<view v-if="template">
+		<s-layout title="首页" navbar="custom" tabbar="/pages/index/index" :bgStyle="template.page"
+			:navbarStyle="template.navigationBar" onShareAppMessage>
+			<s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
+				<s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
+			</s-block>
+		</s-layout>
+	</view>
+</template>
+
+<script setup>
+import {
+	computed
+} from 'vue';
+import {
+	onLoad,
+	onPageScroll,
+	onPullDownRefresh
+} from '@dcloudio/uni-app';
+import sheep from '@/sheep';
+import $share from '@/sheep/platform/share';
+// 隐藏原生tabBar
+uni.hideTabBar();
+
+const template = computed(() => sheep.$store('app').template?.home);
+// 在此处拦截改变一下首页轮播图 此处先写死后期复活 放到启动函数里
+// (async function() {
+// console.log('原代码首页定制化数据',template)
+// let {
+// 	data
+// } = await index2Api.decorate();
+// console.log('首页导航配置化过高无法兼容',JSON.parse(data[1].value))
+// 改变首页底部数据 但是没有通过数组id获取产品数据接口
+// let {
+// 	data: datas
+// } = await index2Api.spids();
+// template.value.data[9].data.goodsIds = datas.list.map(item => item.id);
+// template.value.data[0].data.list = JSON.parse(data[0].value).map(item => {
+// 	return {
+// 		src: item.picUrl,
+// 		url: item.url,
+// 		title: item.name,
+// 		type: "image"
+// 	}
+// })
+// }())
+
+
+onLoad((options) => {
+	// #ifdef MP
+	// 小程序识别二维码
+	if (options.scene) {
+		const sceneParams = decodeURIComponent(options.scene).split('=');
+		console.log("sceneParams=>", sceneParams);
+		options[sceneParams[0]] = sceneParams[1];
+	}
+	// #endif
+
+	// 预览模板
+	if (options.templateId) {
+		sheep.$store('app').init(options.templateId);
+	}
+
+	// 解析分享信息
+	if (options.spm) {
+		$share.decryptSpm(options.spm);
+	}
+
+	// 进入指定页面(完整页面路径)
+	if (options.page) {
+		sheep.$router.go(decodeURIComponent(options.page));
+	}
+});
+
+// 下拉刷新
+onPullDownRefresh(() => {
+	sheep.$store('app').init();
+	setTimeout(function () {
+		uni.stopPullDownRefresh();
+	}, 800);
+});
+
+onPageScroll(() => { });
+</script>
+
+<style></style>

+ 149 - 70
pages/index/index.vue

@@ -3,86 +3,165 @@
 	<view v-if="template">
 		<s-layout title="首页" navbar="custom" tabbar="/pages/index/index" :bgStyle="template.page"
 			:navbarStyle="template.navigationBar" onShareAppMessage>
-			<s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
-				<s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
-			</s-block>
+
+			<view class="home-top">
+				<image class="disImg" :src="sheep.$url.static('/static/home/homeTop.png')" />
+				<view class="search-box">
+					<u-search placeholder="请输入搜索内容" shape="round" v-model="keyword" :showAction="false"></u-search>
+				</view>
+				<image class="home-xq" :src="sheep.$url.static('/static/home/homexq.png')" />
+			</view>
+			<view class="home-container">
+				<com-title title="产品服务" />
+				<productService />
+				<com-title title="服务优势" />
+				<serviceAdvantages />
+				<com-title title="服务案例" />
+				<vi v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
+					<s-block-item v-if="item.id == 'ProductCard'" :type="item.id" :data="item.property"
+						:styles="item.property.style" />
+				</vi>
+
+			</view>
 		</s-layout>
 	</view>
 </template>
 
 <script setup>
-	import {
-		computed
-	} from 'vue';
-	import {
-		onLoad,
-		onPageScroll,
-		onPullDownRefresh
-	} from '@dcloudio/uni-app';
-	import sheep from '@/sheep';
-	import $share from '@/sheep/platform/share';
-	// 隐藏原生tabBar
-	uni.hideTabBar();
-
-	const template = computed(() => sheep.$store('app').template?.home);
-	// 在此处拦截改变一下首页轮播图 此处先写死后期复活 放到启动函数里
-	// (async function() {
-		// console.log('原代码首页定制化数据',template)
-		// let {
-		// 	data
-		// } = await index2Api.decorate();
-		// console.log('首页导航配置化过高无法兼容',JSON.parse(data[1].value))
-		// 改变首页底部数据 但是没有通过数组id获取产品数据接口
-		// let {
-		// 	data: datas
-		// } = await index2Api.spids();
-		// template.value.data[9].data.goodsIds = datas.list.map(item => item.id);
-		// template.value.data[0].data.list = JSON.parse(data[0].value).map(item => {
-		// 	return {
-		// 		src: item.picUrl,
-		// 		url: item.url,
-		// 		title: item.name,
-		// 		type: "image"
-		// 	}
-		// })
-	// }())
-
-
-	onLoad((options) => {
-		// #ifdef MP
-		// 小程序识别二维码
-		if (options.scene) {
-			const sceneParams = decodeURIComponent(options.scene).split('=');
-      console.log("sceneParams=>",sceneParams);
-			options[sceneParams[0]] = sceneParams[1];
+import uSearch from '@/uni_modules/vk-uview-ui/components/u-search/u-search.vue'
+import {
+	computed,
+	ref
+} from 'vue';
+import {
+	onLoad,
+	onPageScroll,
+	onPullDownRefresh
+} from '@dcloudio/uni-app';
+import comTitle from './com/title.vue';
+import productService from './com/productServic.vue';
+import serviceAdvantages from './com/serviceAdvantages.vue';
+import sheep from '@/sheep';
+import $share from '@/sheep/platform/share';
+// 隐藏原生tabBar
+uni.hideTabBar();
+const navigationBar = ref({
+	"otherCells": [
+		{
+			"left": 0,
+			"right": 8,
+			"top": 0,
+			"bottom": 1,
+			"height": 1,
+			"width": 8,
+			"type": "text",
+			"fontSize": 20,
+			"fontWeight": "bold",
+			"textColor": "#1E2129",
+			"text": "赢伟达"
 		}
-		// #endif
+	],
+	"bgType": "color",
+	"bgColor": "transparent"
+})
+const template = computed(() => sheep.$store('app').template?.home);
 
-		// 预览模板
-		if (options.templateId) {
-			sheep.$store('app').init(options.templateId);
-		}
 
-		// 解析分享信息
-		if (options.spm) {
-			$share.decryptSpm(options.spm);
-		}
+onLoad((options) => {
+	// #ifdef MP
+	// 小程序识别二维码
+	if (options.scene) {
+		const sceneParams = decodeURIComponent(options.scene).split('=');
+		console.log("sceneParams=>", sceneParams);
+		options[sceneParams[0]] = sceneParams[1];
+	}
+	// #endif
 
-		// 进入指定页面(完整页面路径)
-		if (options.page) {
-			sheep.$router.go(decodeURIComponent(options.page));
-		}
-	});
+	// 预览模板
+	if (options.templateId) {
+		sheep.$store('app').init(options.templateId);
+	}
 
-	// 下拉刷新
-	onPullDownRefresh(() => {
-		sheep.$store('app').init();
-		setTimeout(function() {
-			uni.stopPullDownRefresh();
-		}, 800);
-	});
+	// 解析分享信息
+	if (options.spm) {
+		$share.decryptSpm(options.spm);
+	}
 
-	onPageScroll(() => {});
+	// 进入指定页面(完整页面路径)
+	if (options.page) {
+		sheep.$router.go(decodeURIComponent(options.page));
+	}
+});
+
+// 下拉刷新
+onPullDownRefresh(() => {
+	sheep.$store('app').init();
+	setTimeout(function () {
+		uni.stopPullDownRefresh();
+	}, 800);
+});
+
+
+onPageScroll(() => { });
 </script>
 
-<style></style>
+<style lang="scss" scoped>
+// #ifdef H5
+:deep(.ui-fixed-box) {
+	position: absolute !important;
+	background: transparent !important;
+
+	.nav-title {
+		font-weight: bold;
+	}
+}
+
+// #endif
+// #ifdef MP
+:deep(.ui-fixed-box) {
+	position: fixed !important;
+	background: #fff !important;
+
+	.nav-title {
+		font-weight: bold;
+	}
+}
+
+// #endif
+
+.home-container {
+	width: 100%;
+	height: 100%;
+	padding: 0 16px;
+}
+
+.home-top {
+	width: 100%;
+	position: relative;
+	height: 230px;
+	padding-top: 44px;
+	top: -44px;
+	z-index: 1;
+
+
+	.search-box {
+		padding: 0 16px;
+		margin-top: 12px;
+	}
+
+	.home-xq {
+		width: calc(100% - 32px);
+		height: 164px;
+		padding: 16px;
+	}
+}
+
+.disImg {
+	width: 100%;
+	position: absolute;
+	z-index: -1;
+	top: 0;
+	left: 0;
+	// height: 100%;
+}
+</style>

+ 144 - 170
pages/member/UserInfoAiEdit.vue

@@ -1,57 +1,26 @@
 <!-- 用户信息的新增/编辑 -->
 <template>
   <s-layout :title="state.model.id ? '编辑地址' : '新增用户信息'">
-    <uni-forms
-      ref="userinfoaiFormRef"
-      v-model="state.model"
-      :labelStyle="{ fontWeight: 'bold' }"
-      :rules="rules"
-      border
-      labelAlign="left"
-      labelWidth="160"
-      validateTrigger="bind"
-    >
+    <uni-forms ref="userinfoaiFormRef" v-model="state.model" :labelStyle="{ fontWeight: 'bold' }" :rules="rules" border
+      labelAlign="left" labelWidth="160" validateTrigger="bind">
       <view class="bg-white form-box ss-p-x-30">
         <uni-forms-item class="form-item" label="用户编号" name="userId">
-          <uni-easyinput
-            v-model="state.model.userId"
-            :inputBorder="false"
-            placeholder="请填写用户编号"
-            placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
-          />
+          <uni-easyinput v-model="state.model.userId" :inputBorder="false" placeholder="请填写用户编号"
+            placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" />
         </uni-forms-item>
-        <uni-forms-item
-          :formItemStyle="{ alignItems: 'flex-start' }"
-          :labelStyle="{ lineHeight: '5em' }"
-          class="textarea-item"
-          label="提取有效信息"
-          name="textInformation"
-        >
-          <uni-easyinput
-            v-model="state.model.textInformation"
-            :inputBorder="false"
-            clearable
-            placeholder="请输入提取有效信息"
-            placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
-            type="textarea"
-          />
+        <uni-forms-item class="form-item" label="文件" name="infoFiles">
+          <s-uploader v-model="state.model.infoFiles" fileMediatype="pdf" limit="10" mode="list" />
+        </uni-forms-item>
+        <uni-forms-item :formItemStyle="{ alignItems: 'flex-start' }" :labelStyle="{ lineHeight: '5em' }"
+          class="textarea-item" label="提取有效信息" name="textInformation">
+          <uni-easyinput v-model="state.model.textInformation" :inputBorder="false" clearable placeholder="请输入提取有效信息"
+            placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" type="textarea" />
         </uni-forms-item>
 
-        <uni-forms-item
-          :formItemStyle="{ alignItems: 'flex-start' }"
-          :labelStyle="{ lineHeight: '5em' }"
-          class="textarea-item"
-          label="补充信息"
-          name="additionalInfo"
-        >
-          <uni-easyinput
-            v-model="state.model.additionalInfo"
-            :inputBorder="false"
-            clearable
-            placeholder="请输入补充信息"
-            placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
-            type="textarea"
-          />
+        <uni-forms-item :formItemStyle="{ alignItems: 'flex-start' }" :labelStyle="{ lineHeight: '5em' }"
+          class="textarea-item" label="补充信息" name="additionalInfo">
+          <uni-easyinput v-model="state.model.additionalInfo" :inputBorder="false" clearable placeholder="请输入补充信息"
+            placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" type="textarea" />
         </uni-forms-item>
       </view>
     </uni-forms>
@@ -69,141 +38,146 @@
 </template>
 
 <script setup>
-  import { reactive, ref, unref } from 'vue';
-  import sheep from '@/sheep';
-  import { onLoad } from '@dcloudio/uni-app';
-  import UserInfoAiApi from '@/sheep/api/member/UserInfoAi';
-
-  const userinfoaiFormRef = ref(null);
-  const state = reactive({
-    showRegion: false,
-    model: {
-      name: '',
-      mobile: '',
-      detailUserInfoAi: '',
-      defaultStatus: false,
-      areaName: '',
+import { reactive, ref, unref } from 'vue';
+import sheep from '@/sheep';
+import { onLoad } from '@dcloudio/uni-app';
+import UserInfoAiApi from '@/sheep/api/member/UserInfoAi';
+
+const userinfoaiFormRef = ref(null);
+const state = reactive({
+  showRegion: false,
+  model: {
+    name: '',
+    mobile: '',
+    detailUserInfoAi: '',
+    defaultStatus: false,
+    areaName: '',
+  },
+  rules: {},
+});
+
+const rules = {
+  userId: {
+    rules: [
+      {
+        required: true,
+        errorMessage: '请输入用户编号',
+      },
+    ],
+  },
+};
+
+// 保存用户信息
+const onSave = async () => {
+  // 参数校验
+  const validate = await unref(userinfoaiFormRef)
+    .validate()
+    .catch((error) => {
+      console.log('error: ', error);
+    });
+  if (!validate) {
+    return;
+  }
+
+  // 提交请求
+  const formData = {
+    ...state.model,
+  };
+  const { code } =
+    state.model.id > 0
+      ? await UserInfoAiApi.updateUserInfoAi(formData)
+      : await UserInfoAiApi.createUserInfoAi(formData);
+  if (code === 0) {
+    sheep.$router.back();
+  }
+};
+
+// 删除用户信息
+const onDelete = () => {
+  uni.showModal({
+    title: '提示',
+    content: '确认删除此用户信息吗?',
+    success: async function (res) {
+      if (!res.confirm) {
+        return;
+      }
+      const { code } = await UserInfoAiApi.deleteUserInfoAi(state.model.id);
+      if (code === 0) {
+        sheep.$router.back();
+      }
     },
-    rules: {},
   });
+};
+const getUserInfoAiAxios = async (id) => {
+  const { data } = await UserInfoAiApi.getUserInfoAi(id);
+  state.model = data;
+};
+onLoad(async (options) => {
+  getUserInfoAiAxios(options.id);
+});
+</script>
 
-  const rules = {
-    userId: {
-      rules: [
-        {
-          required: true,
-          errorMessage: '请输入用户编号',
-        },
-      ],
-    },
-  };
+<style lang="scss" scoped>
+:deep() {
+  .uni-forms-item__label .label-text {
+    font-size: 28rpx !important;
+    color: #333333 !important;
+    line-height: normal !important;
+  }
 
-  // 保存用户信息
-  const onSave = async () => {
-    // 参数校验
-    const validate = await unref(userinfoaiFormRef)
-      .validate()
-      .catch((error) => {
-        console.log('error: ', error);
-      });
-    if (!validate) {
-      return;
-    }
-
-    // 提交请求
-    const formData = {
-      ...state.model,
-    };
-    const { code } =
-      state.model.id > 0
-        ? await UserInfoAiApi.updateUserInfoAi(formData)
-        : await UserInfoAiApi.createUserInfoAi(formData);
-    if (code === 0) {
-      sheep.$router.back();
-    }
-  };
+  .uni-easyinput__content-input {
+    font-size: 28rpx !important;
+    color: #333333 !important;
+    line-height: normal !important;
+    padding-left: 0 !important;
+  }
 
-  // 删除用户信息
-  const onDelete = () => {
-    uni.showModal({
-      title: '提示',
-      content: '确认删除此用户信息吗?',
-      success: async function (res) {
-        if (!res.confirm) {
-          return;
-        }
-        const { code } = await UserInfoAiApi.deleteUserInfoAi(state.model.id);
-        if (code === 0) {
-          sheep.$router.back();
-        }
-      },
-    });
-  };
+  .uni-easyinput__content-textarea {
+    font-size: 28rpx !important;
+    color: #333333 !important;
+    line-height: normal !important;
+    margin-top: 8rpx !important;
+  }
 
-  onLoad(async (options) => {});
-</script>
+  .uni-icons {
+    font-size: 40rpx !important;
+  }
 
-<style lang="scss" scoped>
-  :deep() {
-    .uni-forms-item__label .label-text {
-      font-size: 28rpx !important;
-      color: #333333 !important;
-      line-height: normal !important;
-    }
-
-    .uni-easyinput__content-input {
-      font-size: 28rpx !important;
-      color: #333333 !important;
-      line-height: normal !important;
-      padding-left: 0 !important;
-    }
-
-    .uni-easyinput__content-textarea {
-      font-size: 28rpx !important;
-      color: #333333 !important;
-      line-height: normal !important;
-      margin-top: 8rpx !important;
-    }
-
-    .uni-icons {
-      font-size: 40rpx !important;
-    }
-
-    .is-textarea-icon {
-      margin-top: 22rpx;
-    }
-
-    .is-disabled {
-      color: #333333;
-    }
+  .is-textarea-icon {
+    margin-top: 22rpx;
   }
 
-  .default-box {
-    width: 100%;
-    box-sizing: border-box;
-    height: 100rpx;
+  .is-disabled {
+    color: #333333;
+  }
+}
+
+.default-box {
+  width: 100%;
+  box-sizing: border-box;
+  height: 100rpx;
 
-    .default-box-title {
-      font-size: 28rpx;
-      color: #333333;
-      line-height: normal;
-    }
+  .default-box-title {
+    font-size: 28rpx;
+    color: #333333;
+    line-height: normal;
+  }
+}
+
+.footer-box {
+  .save-btn {
+    width: 710rpx;
+    height: 80rpx;
+    border-radius: 40rpx;
+    background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+    color: $white;
   }
 
-  .footer-box {
-    .save-btn {
-      width: 710rpx;
-      height: 80rpx;
-      border-radius: 40rpx;
-      background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
-      color: $white;
-    }
-
-    .cancel-btn {
-      width: 710rpx;
-      height: 80rpx;
-      border-radius: 40rpx;
-      background: var(--ui-BG);
-    }
+  .cancel-btn {
+    width: 710rpx;
+    height: 80rpx;
+    border-radius: 40rpx;
+    background: var(--ui-BG);
   }
+}
 </style>

+ 65 - 77
pages/member/UserInfoAiList.vue

@@ -2,105 +2,93 @@
 <template>
   <s-layout :bgStyle="{ color: '#FFF' }" title="用户信息">
     <view>
-      <s-userinfoai-item
-        v-for="item in state.list"
-        :key="item.id"
-        :item="item"
-        hasBorderBottom
-        @tap="onSelect(item)"
-      />
+      <s-userinfoai-item v-for="item in state.list" :key="item.id" :item="item" hasBorderBottom @tap="onSelect(item)" />
     </view>
 
     <su-fixed bottom placeholder>
       <view class="footer-box ss-flex ss-row-between ss-p-20">
-        <button
-          class="add-btn ss-reset-button ui-Shadow-Main"
-          @tap="sheep.$router.go('/pages/member/UserInfoAiEdit')"
-        >
+        <button class="add-btn ss-reset-button ui-Shadow-Main" @tap="sheep.$router.go('/pages/member/UserInfoAiEdit')">
           新增用户信息
         </button>
       </view>
     </su-fixed>
-    <s-empty
-      v-if="state.list.length === 0 && !state.loading"
-      icon="/static/data-empty.png"
-      text="暂无用户信息"
-    />
+    <s-empty v-if="state.list.length === 0 && !state.loading" icon="/static/data-empty.png" text="暂无用户信息" />
   </s-layout>
 </template>
 
 <script setup>
-  import { onBeforeMount, reactive } from 'vue';
-  import { onLoad, onShow } from '@dcloudio/uni-app';
-  import sheep from '@/sheep';
-  import AreaApi from '@/sheep/api/system/area';
-  import UserInfoAiApi from '@/sheep/api/member/UserInfoAi';
+import { onBeforeMount, reactive } from 'vue';
+import { onLoad, onShow } from '@dcloudio/uni-app';
+import sheep from '@/sheep';
+import AreaApi from '@/sheep/api/system/area';
+import UserInfoAiApi from '@/sheep/api/member/UserInfoAi';
 
-  const state = reactive({
-    list: [], // 地址列表
-    loading: true,
-    openType: '', // 页面打开类型
-  });
-
-  // 选择用户信息
-  const onSelect = (userinfoaiInfo) => {
-    if (state.openType !== 'select') {
-      // 不作为选择组件时阻断操作
-      return;
-    }
-    uni.$emit('SELECT_USERINFOAI', {
-      userinfoaiInfo,
-    });
-    sheep.$router.back();
-  };
+const state = reactive({
+  list: [], // 地址列表
+  loading: true,
+  openType: '', // 页面打开类型
+});
 
-  onLoad((option) => {
-    if (option.type) {
-      state.openType = option.type;
-    }
+// 选择用户信息
+const onSelect = (userinfoaiInfo) => {
+  if (state.openType !== 'select') {
+    // 不作为选择组件时阻断操作
+    return;
+  }
+  uni.$emit('SELECT_USERINFOAI', {
+    userinfoaiInfo,
   });
+  sheep.$router.back();
+};
 
-  onShow(async () => {
-    state.list = (await UserInfoAiApi.getUserInfoAiList()).data;
-    state.loading = false;
-  });
+onLoad((option) => {
+  if (option.type) {
+    state.openType = option.type;
+  }
+});
+
+onShow(async () => {
+  const res = await UserInfoAiApi.getUserInfoAiList();
+  state.list = res.data;
+  state.loading = false;
+});
 
-  onBeforeMount(() => {
-    if (!!uni.getStorageSync('areaData')) {
-      return;
+onBeforeMount(() => {
+  if (!!uni.getStorageSync('areaData')) {
+    return;
+  }
+  // 提前加载省市区数据
+  AreaApi.getAreaTree().then((res) => {
+    if (res.code === 0) {
+      uni.setStorageSync('areaData', res.data);
     }
-    // 提前加载省市区数据
-    AreaApi.getAreaTree().then((res) => {
-      if (res.code === 0) {
-        uni.setStorageSync('areaData', res.data);
-      }
-    });
   });
+});
 </script>
 
 <style lang="scss" scoped>
-  .footer-box {
-    .add-btn {
-      flex: 1;
-      background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
-      border-radius: 80rpx;
-      font-size: 30rpx;
-      font-weight: 500;
-      line-height: 80rpx;
-      color: $white;
-      position: relative;
-      z-index: 1;
-    }
+.footer-box {
+  .add-btn {
+    flex: 1;
+    background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+    border-radius: 80rpx;
+    font-size: 30rpx;
+    font-weight: 500;
+    line-height: 80rpx;
+    color: $white;
+    position: relative;
+    z-index: 1;
+  }
 
-    .sync-wxuserinfoai {
-      flex: 1;
-      line-height: 80rpx;
-      background: $white;
-      border-radius: 80rpx;
-      font-size: 30rpx;
-      font-weight: 500;
-      color: $dark-6;
-      margin-right: 18rpx;
-    }
+  .sync-wxuserinfoai {
+    flex: 1;
+    line-height: 80rpx;
+    background: $white;
+    border-radius: 80rpx;
+    font-size: 30rpx;
+    font-weight: 500;
+    color: $dark-6;
+    margin-right: 18rpx;
   }
+}
 </style>

+ 48 - 0
pages/user/com/goodsCard.vue

@@ -0,0 +1,48 @@
+<script setup>
+defineProps(['itemRow']);
+</script>
+
+<template>
+  <view class="goods-card">
+    <image class="goods-img" mode="aspectFill" :src="itemRow.picUrl" />
+    <view class="goods-info">
+      <text class="goods-title u-line-1">{{ itemRow.spuName }}</text>
+      <text class="goods-desc u-line-1">{{ itemRow.introduction }}</text>
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped>
+.goods-card {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  border-radius: 16rpx;
+  overflow: hidden;
+  background: #fff;
+
+  .goods-img {
+    width: 342rpx;
+    height: 342rpx;
+  }
+
+  .goods-info {
+    width: 342rpx;
+    padding: 16rpx;
+
+    .goods-title {
+      font-size: 28rpx;
+      font-weight: 500;
+      color: #3d3d3d;
+      line-height: 40rpx;
+    }
+
+    .goods-desc {
+      font-size: 24rpx;
+      color: #9c9c9c;
+      line-height: 34rpx;
+      margin-top: 10rpx;
+    }
+  }
+}
+</style>

+ 12 - 7
pages/user/goods-log.vue

@@ -3,7 +3,7 @@
   <s-layout :bgStyle="{ color: '#f2f2f2' }" tabbar="/pages/user/goods-log" title="我的足迹">
     <view class="cart-box ss-flex ss-flex-col ss-row-between">
       <!-- 头部 -->
-      <view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
+      <!-- <view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
         <view class="header-left ss-flex ss-col-center ss-font-26">
           <text class="goods-number ui-TC-Main ss-flex">
@@ -20,7 +20,7 @@
             编辑
           </button>
         </view>
-      </view>
+      </view> -->
       <!-- 内容 -->
       <view class="cart-content">
         <view class="goods-box ss-r-10 ss-m-b-14" v-for="item in state.pagination.list" :key="item.id">
@@ -29,13 +29,14 @@
               <radio :checked="state.selectedSpuIdList.includes(item.spuId)" color="var(--ui-BG-Main)"
                 style="transform: scale(0.8)" @tap.stop="onSelect(item.spuId)" />
             </label>
-            <s-goods-item :title="item.spuName" :img="item.picUrl" :price="item.price" :skuText="item.introduction"
+            <goodsCard :itemRow="item"></goodsCard>
+            <!-- <s-goods-item :title="item.spuName" :img="item.picUrl" :price="item.price" :skuText="item.introduction"
               priceColor="#FF3000" :titleWidth="400" @tap="
                 sheep.$router.go('/pages/goods/index', {
                   id: item.spuId,
                 })
                 ">
-            </s-goods-item>
+            </s-goods-item> -->
           </view>
         </view>
       </view>
@@ -80,7 +81,7 @@ import { onLoad, onReachBottom } from '@dcloudio/uni-app';
 import _ from 'lodash-es';
 import SpuHistoryApi from '@/sheep/api/product/history';
 import { cloneDeep } from '@/sheep/helper/utils';
-
+import goodsCard from './com/goodsCard.vue';
 const sys_navBar = sheep.$platform.navbar;
 const pagination = {
   list: [],
@@ -211,13 +212,17 @@ onLoad(() => {
     width: 100%;
     padding: 0 20rpx;
     box-sizing: border-box;
-    margin-top: 70rpx;
+    margin-top: 20rpx;
+    // margin-top: 70rpx;
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 20rpx;
 
     .goods-box {
       background-color: #fff;
 
       &:last-child {
-        margin-bottom: 40rpx;
+        // margin-bottom: 40rpx;
       }
     }
   }

+ 14 - 0
sheep/api/aiApi/index.js

@@ -0,0 +1,14 @@
+import request from '@/sheep/request';
+
+const AiApi = {
+  // 获得用户收件地址列表
+  productSpuPage: (params) => {
+    return request({
+      url: '/product/spu/page',
+      method: 'GET',
+      params,
+    });
+  },
+};
+
+export default AiApi;

+ 144 - 156
sheep/components/s-custom-navbar/s-custom-navbar.vue

@@ -1,14 +1,7 @@
 <!-- 顶部导航栏 -->
 <template>
-  <navbar
-    :alway="isAlways"
-    :back="false"
-    bg=""
-    :placeholder="isPlaceholder"
-    :bgStyles="bgStyles"
-    :opacity="isOpacity"
-    :sticky="sticky"
-  >
+  <navbar :alway="isAlways" :back="false" bg="" :placeholder="isPlaceholder" :bgStyles="bgStyles" :opacity="isOpacity"
+    :sticky="sticky">
     <template #item>
       <view class="nav-box">
         <view class="nav-icon" v-if="showLeftButton">
@@ -23,13 +16,8 @@
             </view>
           </view>
         </view>
-        <view
-          class="nav-item"
-          v-for="(item, index) in navList"
-          :key="index"
-          :style="[parseImgStyle(item)]"
-          :class="[{ 'ss-flex ss-col-center ss-row-center': item.type !== 'search' }]"
-        >
+        <view class="nav-item" v-for="(item, index) in navList" :key="index" :style="[parseImgStyle(item)]"
+          :class="[{ 'ss-flex ss-col-center ss-row-center': item.type !== 'search' }]">
           <navbar-item :data="item" :width="parseImgStyle(item).width" />
         </view>
       </view>
@@ -38,170 +26,170 @@
 </template>
 
 <script setup>
-  /**
-   *  装修组件 - 自定义标题栏
-   *
-   *
-   * @property {Number | String}  alwaysShow = [0,1]			    - 是否常驻
-   * @property {Number | String}  styleType = [inner]			   	- 是否沉浸式
-   * @property {String | Number} type              - 标题背景模式
-   * @property {String} color                  - 页面背景色
-   * @property {String} src                    - 页面背景图片
-   */
-  import { computed, unref } from 'vue';
-  import sheep from '@/sheep';
-  import Navbar from './components/navbar.vue';
-  import NavbarItem from './components/navbar-item.vue';
-  import { showMenuTools } from '@/sheep/hooks/useModal';
-
-  const props = defineProps({
-    data: {
-      type: Object,
-      default: () => ({}),
-    },
-    showLeftButton: {
-      type: Boolean,
-      default: false,
-    },
-  });
-  const hasHistory = sheep.$router.hasHistory();
-  const sticky = computed(() => {
-    if (props.data.styleType === 'inner') {
-      if (props.data.alwaysShow) {
-        return false;
-      }
-    }
-    if (props.data.styleType === 'normal') {
+/**
+ *  装修组件 - 自定义标题栏
+ *
+ *
+ * @property {Number | String}  alwaysShow = [0,1]			    - 是否常驻
+ * @property {Number | String}  styleType = [inner]			   	- 是否沉浸式
+ * @property {String | Number} type              - 标题背景模式
+ * @property {String} color                  - 页面背景色
+ * @property {String} src                    - 页面背景图片
+ */
+import { computed, unref } from 'vue';
+import sheep from '@/sheep';
+import Navbar from './components/navbar.vue';
+import NavbarItem from './components/navbar-item.vue';
+import { showMenuTools } from '@/sheep/hooks/useModal';
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+  showLeftButton: {
+    type: Boolean,
+    default: false,
+  },
+});
+const hasHistory = sheep.$router.hasHistory();
+const sticky = computed(() => {
+  if (props.data.styleType === 'inner') {
+    if (props.data.alwaysShow) {
       return false;
     }
-  });
-  const navList = computed(() => {
+  }
+  if (props.data.styleType === 'normal') {
+    return false;
+  }
+});
+const navList = computed(() => {
+  // #ifdef MP
+  return props.data.mpCells || [];
+  // #endif
+  return props.data.otherCells || [];
+});
+// 页面宽度
+const windowWidth = sheep.$platform.device.windowWidth;
+// 单元格宽度
+const cell = computed(() => {
+  if (unref(navList).length) {
+    // 默认宽度为8个格子,微信公众号右上角有胶囊按钮所以是6个格子
+    let cell = (windowWidth - 90) / 8;
     // #ifdef MP
-    return props.data.mpCells || [];
+    cell = (windowWidth - 80 - unref(sheep.$platform.capsule).width) / 6;
     // #endif
-    return props.data.otherCells || [];
-  });
-  // 页面宽度
-  const windowWidth = sheep.$platform.device.windowWidth;
-  // 单元格宽度
-  const cell = computed(() => {
-    if (unref(navList).length) {
-      // 默认宽度为8个格子,微信公众号右上角有胶囊按钮所以是6个格子
-      let cell = (windowWidth - 90) / 8;
-      // #ifdef MP
-      cell = (windowWidth - 80 - unref(sheep.$platform.capsule).width) / 6;
-      // #endif
-      return cell;
-    }
-  });
-  // 解析位置
-  const parseImgStyle = (item) => {
-    let obj = {
-      width: item.width * cell.value + (item.width - 1) * 10 + 'px',
-      left: item.left * cell.value + (item.left + 1) * 10 + 'px',
-      'border-radius': item.borderRadius + 'px',
-    };
-    return obj;
+    return cell;
+  }
+});
+// 解析位置
+const parseImgStyle = (item) => {
+  let obj = {
+    width: item.width * cell.value + (item.width - 1) * 10 + 'px',
+    left: item.left * cell.value + (item.left + 1) * 10 + 'px',
+    'border-radius': item.borderRadius + 'px',
   };
-  const isAlways = computed(() =>
-    props.data.styleType === 'inner' ? Boolean(props.data.alwaysShow) : true,
-  );
-  const isOpacity = computed(() =>
-    props.data.styleType === 'normal'
-      ? false
-      : props.showLeftButton
+  return obj;
+};
+const isAlways = computed(() =>
+  props.data.styleType === 'inner' ? Boolean(props.data.alwaysShow) : true,
+);
+const isOpacity = computed(() =>
+  props.data.styleType === 'normal'
+    ? false
+    : props.showLeftButton
       ? false
       : props.data.styleType === 'inner',
-  );
-  const isPlaceholder = computed(() => props.data.styleType === 'normal');
-  const bgStyles = computed(() => {
-    return {
-      background:
-        props.data.bgType === 'img' && props.data.bgImg
-          ? `url(${sheep.$url.cdn(props.data.bgImg)}) no-repeat top center / 100% 100%`
-          : props.data.bgColor,
-    };
-  });
-
-  // 左侧按钮:返回上一页或首页
-  function onClickLeft() {
-    if (hasHistory) {
-      sheep.$router.back();
-    } else {
-      sheep.$router.go('/pages/index/index');
-    }
+);
+const isPlaceholder = computed(() => props.data.styleType === 'normal');
+const bgStyles = computed(() => {
+  return {
+    background:
+      props.data.bgType === 'img' && props.data.bgImg
+        ? `url(${sheep.$url.cdn(props.data.bgImg)}) no-repeat top center / 100% 100%`
+        : props.data.bgColor,
+  };
+});
+
+// 左侧按钮:返回上一页或首页
+function onClickLeft() {
+  if (hasHistory) {
+    sheep.$router.back();
+  } else {
+    sheep.$router.go('/pages/index/index');
   }
+}
 
-  // 右侧按钮:打开快捷菜单
-  function onClickRight() {
-    showMenuTools();
-  }
+// 右侧按钮:打开快捷菜单
+function onClickRight() {
+  showMenuTools();
+}
 </script>
 
 <style lang="scss" scoped>
-  .nav-box {
-    width: 750rpx;
-    position: relative;
-    height: 100%;
-
-    .nav-item {
-      position: absolute;
-      top: 50%;
-      transform: translateY(-50%);
-    }
+.nav-box {
+  width: 750rpx;
+  position: relative;
+  height: 100%;
+
+  .nav-item {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+  }
 
-    .nav-icon {
-      position: absolute;
-      top: 50%;
-      transform: translateY(-50%);
-      left: 20rpx;
+  .nav-icon {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    left: 20rpx;
 
-      .inner-icon-box {
-        border: 1px solid rgba(#fff, 0.4);
-        background: none !important;
+    .inner-icon-box {
+      border: 1px solid rgba(#fff, 0.4);
+      background: none !important;
+    }
+
+    .icon-box {
+      background: #ffffff;
+      box-shadow: 0px 0px 4rpx rgba(51, 51, 51, 0.08), 0px 4rpx 6rpx 2rpx rgba(102, 102, 102, 0.12);
+      border-radius: 30rpx;
+      width: 134rpx;
+      height: 56rpx;
+      margin-left: 8rpx;
+
+      .line {
+        width: 2rpx;
+        height: 24rpx;
+        background: #e5e5e7;
       }
 
-      .icon-box {
-        background: #ffffff;
-        box-shadow: 0px 0px 4rpx rgba(51, 51, 51, 0.08), 0px 4rpx 6rpx 2rpx rgba(102, 102, 102, 0.12);
-        border-radius: 30rpx;
-        width: 134rpx;
-        height: 56rpx;
-        margin-left: 8rpx;
+      .sicon-back {
+        font-size: 32rpx;
+      }
 
-        .line {
-          width: 2rpx;
-          height: 24rpx;
-          background: #e5e5e7;
-        }
+      .sicon-home {
+        font-size: 32rpx;
+      }
 
-        .sicon-back {
-          font-size: 32rpx;
-        }
+      .sicon-more {
+        font-size: 32rpx;
+      }
 
-        .sicon-home {
-          font-size: 32rpx;
-        }
+      .icon-button {
+        width: 67rpx;
+        height: 56rpx;
 
-        .sicon-more {
-          font-size: 32rpx;
+        &-left:hover {
+          background: rgba(0, 0, 0, 0.16);
+          border-radius: 30rpx 0px 0px 30rpx;
         }
 
-        .icon-button {
-          width: 67rpx;
-          height: 56rpx;
-
-          &-left:hover {
-            background: rgba(0, 0, 0, 0.16);
-            border-radius: 30rpx 0px 0px 30rpx;
-          }
-
-          &-right:hover {
-            background: rgba(0, 0, 0, 0.16);
-            border-radius: 0px 30rpx 30rpx 0px;
-          }
+        &-right:hover {
+          background: rgba(0, 0, 0, 0.16);
+          border-radius: 0px 30rpx 30rpx 0px;
         }
       }
     }
   }
+}
 </style>

+ 270 - 0
sheep/components/s-goods-card/s-goods-card copy.vue

@@ -0,0 +1,270 @@
+<!-- 装修产品组件:产品卡片 -->
+<template>
+  <!-- 产品卡片 -->
+  <view>
+    <!-- 布局1. 单列大图(上图,下内容)-->
+    <view v-if="layoutType === LayoutTypeEnum.ONE_COL_BIG_IMG && state.goodsList.length" class="goods-sl-box">
+      <view class="goods-box" v-for="item in state.goodsList" :key="item.id"
+        :style="[{ marginBottom: data.space * 2 + 'rpx' }]">
+        <s-goods-column class="" size="sl" :goodsFields="data.fields" :tagStyle="data.badge" :data="item"
+          :titleColor="data.fields.name?.color" :subTitleColor="data.fields.introduction.color"
+          :topRadius="data.borderRadiusTop" :bottomRadius="data.borderRadiusBottom"
+          @click="sheep.$router.go('/pages/goods/index', { id: item.id })">
+          <!-- 购买按钮 -->
+          <template v-slot:cart>
+            <button class="ss-reset-button cart-btn" :style="[buyStyle]">
+              {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
+            </button>
+          </template>
+        </s-goods-column>
+      </view>
+    </view>
+
+    <!-- 布局2. 双列(每一列:上图,下内容)-->
+    <view v-if="layoutType === LayoutTypeEnum.TWO_COL && state.goodsList.length"
+      class="goods-md-wrap ss-flex ss-flex-wrap ss-col-top">
+      <view class="goods-list-box">
+        <view class="left-list" :style="[{ paddingRight: data.space + 'rpx', marginBottom: data.space + 'px' }]"
+          v-for="item in state.leftGoodsList" :key="item.id">
+          <s-goods-column class="goods-md-box" size="md" :goodsFields="data.fields" :tagStyle="data.badge" :data="item"
+            :titleColor="data.fields.name?.color" :subTitleColor="data.fields.introduction.color"
+            :topRadius="data.borderRadiusTop" :bottomRadius="data.borderRadiusBottom"
+            :titleWidth="330 - marginLeft - marginRight"
+            @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
+            @getHeight="calculateGoodsColumn($event, 'left')">
+            <!-- 购买按钮 -->
+            <template v-slot:cart>
+              <button class="ss-reset-button cart-btn" :style="[buyStyle]">
+                {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
+              </button>
+            </template>
+          </s-goods-column>
+        </view>
+      </view>
+      <view class="goods-list-box">
+        <view class="right-list" :style="[{ paddingLeft: data.space + 'rpx', marginBottom: data.space + 'px' }]"
+          v-for="item in state.rightGoodsList" :key="item.id">
+          <s-goods-column class="goods-md-box" size="md" :goodsFields="data.fields" :tagStyle="data.badge" :data="item"
+            :titleColor="data.fields.name?.color" :subTitleColor="data.fields.introduction.color"
+            :topRadius="data.borderRadiusTop" :bottomRadius="data.borderRadiusBottom"
+            :titleWidth="330 - marginLeft - marginRight"
+            @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
+            @getHeight="calculateGoodsColumn($event, 'right')">
+            <!-- 购买按钮 -->
+            <template v-slot:cart>
+              <button class="ss-reset-button cart-btn" :style="[buyStyle]">
+                {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
+              </button>
+            </template>
+          </s-goods-column>
+        </view>
+      </view>
+    </view>
+
+    <!-- 布局3. 单列小图(左图,右内容) -->
+    <view v-if="layoutType === LayoutTypeEnum.ONE_COL_SMALL_IMG && state.goodsList.length" class="goods-lg-box">
+      <view class="goods-box" :style="[{ marginBottom: data.space + 'px' }]" v-for="item in state.goodsList"
+        :key="item.id">
+
+        <s-goods-column class="goods-card" size="lg" :goodsFields="data.fields" :data="item" :tagStyle="data.badge"
+          :titleColor="data.fields.name?.color" :subTitleColor="data.fields.introduction.color"
+          :topRadius="data.borderRadiusTop" :bottomRadius="data.borderRadiusBottom"
+          @tap="sheep.$router.go('/pages/goods/index', { id: item.id })">
+          <!-- 购买按钮 -->
+          <template v-slot:cart>
+            <button @click="lxkfClick(item)" class="ss-reset-button cart-btn" :style="[buyStyle]">
+              {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
+            </button>
+          </template>
+        </s-goods-column>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+/**
+ * 产品卡片
+ */
+import { computed, reactive, onMounted } from 'vue';
+import sheep from '@/sheep';
+import SpuApi from '@/sheep/api/product/spu';
+import OrderApi from '@/sheep/api/trade/order';
+import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
+import CouponApi from '@/sheep/api/promotion/coupon';
+// 布局类型
+const LayoutTypeEnum = {
+  // 单列大图
+  ONE_COL_BIG_IMG: 'oneColBigImg',
+  // 双列
+  TWO_COL: 'twoCol',
+  // 单列小图
+  ONE_COL_SMALL_IMG: 'oneColSmallImg',
+};
+
+const state = reactive({
+  goodsList: [],
+  leftGoodsList: [],
+  rightGoodsList: [],
+});
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+  styles: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const { layoutType, btnBuy, spuIds } = props.data || {};
+const { marginLeft, marginRight } = props.styles || {};
+const userInfo = computed(() => sheep.$store('user').userInfo);
+const lxkfClick = (item) => {
+
+  CouponApi.getKefuConversationByRelID({
+    relID: state.goodsInfo.kefuId
+  }).then((res) => {
+    console.log(res, 44444)
+    if (state.goodsInfo.proType == 0) {
+      sheep.$router.go('/pages/chat/index', { conversationId: res.data.id, relUserId: userInfo.value.id === res.data.relUserId ? res.data.userId : res.data.relUserId })
+    } else {
+      sheep.$router.go('/pages/customerService/index', {
+        data: JSON.stringify({
+          url: state.goodsInfo.qrCodePath,
+        })
+      })
+    }
+  })
+  // sheep.$router.go('/pages/customerService/index')
+}
+
+// 购买按钮样式
+const buyStyle = computed(() => {
+  if (btnBuy.type === 'text') {
+    // 文字按钮:线性渐变背景颜色
+    return {
+      background: `linear-gradient(to right, ${btnBuy.bgBeginColor}, ${btnBuy.bgEndColor})`,
+    };
+  }
+  if (btnBuy.type === 'img') {
+    // 图片按钮
+    return {
+      width: '54rpx',
+      height: '54rpx',
+      background: `url(${sheep.$url.cdn(btnBuy.imgUrl)}) no-repeat`,
+      backgroundSize: '100% 100%',
+    };
+  }
+});
+
+//region 产品瀑布流布局
+// 下一个要处理的产品索引
+let count = 0;
+// 左列的高度
+let leftHeight = 0;
+// 右列的高度
+let rightHeight = 0;
+
+/**
+ * 计算产品在左列还是右列
+ * @param height 产品的高度
+ * @param where 添加到哪一列
+ */
+function calculateGoodsColumn(height = 0, where = 'left') {
+  // 处理完
+  if (!state.goodsList[count]) return;
+  // 增加列的高度
+  if (where === 'left') leftHeight += height;
+  if (where === 'right') rightHeight += height;
+  // 添加到矮的一列
+  if (leftHeight <= rightHeight) {
+    state.leftGoodsList.push(state.goodsList[count]);
+  } else {
+    state.rightGoodsList.push(state.goodsList[count]);
+  }
+  // 计数
+  count++;
+}
+
+//endregion
+
+/**
+ * 根据产品编号列表,获取产品列表
+ * @param ids 产品编号列表
+ * @return {Promise<undefined>} 产品列表
+ */
+async function getGoodsListByIds(ids) {
+  const { data } = await SpuApi.getSpuListByIds(ids);
+  return data;
+}
+
+// 初始化
+onMounted(async () => {
+  // 加载产品列表
+  state.goodsList = await getGoodsListByIds(spuIds.join(','));
+  // 拼接结算信息(营销)
+  await OrderApi.getSettlementProduct(state.goodsList.map((item) => item.id).join(',')).then(
+    (res) => {
+      if (res.code !== 0) {
+        return;
+      }
+      appendSettlementProduct(state.goodsList, res.data);
+    },
+  );
+  // 只有双列布局时需要
+  if (layoutType === LayoutTypeEnum.TWO_COL) {
+    // 分列
+    calculateGoodsColumn();
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.goods-md-wrap {
+  width: 100%;
+}
+
+.goods-list-box {
+  width: 50%;
+  box-sizing: border-box;
+
+  .left-list {
+    &:nth-last-child(1) {
+      margin-bottom: 0 !important;
+    }
+  }
+
+  .right-list {
+    &:nth-last-child(1) {
+      margin-bottom: 0 !important;
+    }
+  }
+}
+
+.goods-box {
+  &:nth-last-of-type(1) {
+    margin-bottom: 0 !important;
+  }
+}
+
+.goods-md-box,
+.goods-sl-box,
+.goods-lg-box {
+  position: relative;
+
+  .cart-btn {
+    position: absolute;
+    bottom: 18rpx;
+    right: 20rpx;
+    z-index: 11;
+    height: 50rpx;
+    line-height: 50rpx;
+    padding: 0 20rpx;
+    border-radius: 25rpx;
+    font-size: 24rpx;
+    color: #fff;
+  }
+}
+</style>

+ 1 - 0
sheep/components/s-goods-card/s-goods-card.vue

@@ -265,6 +265,7 @@ onMounted(async () => {
     border-radius: 25rpx;
     font-size: 24rpx;
     color: #fff;
+    background: #5274F2 !important;
   }
 }
 </style>

+ 11 - 7
sheep/components/s-goods-column/s-goods-column.vue

@@ -165,14 +165,14 @@
         <view class="tag-icon">拼团</view>
       </view>
       <image class="lg-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill" />
-      <view class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10 ss-p-t-20">
+      <view class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10">
         <view>
           <view v-if="goodsFields.title?.show || goodsFields.name?.show" class="lg-goods-title ss-line-2"
-            :style="[{ color: titleColor }]">
+            :style="[{ color: titleColor }]" style="padding: 0 16px 0 0px;">
             {{ data.title || data.name }}
           </view>
           <view v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
-            class="lg-goods-subtitle ss-m-t-10 ss-line-3"
+            class="lg-goods-subtitle ss-m-t-10 ss-line-3" style="padding: 0 16px 0 0px;"
             :style="[{ color: subTitleColor, background: subTitleBackground }]">
             {{ data.subtitle || data.introduction }}
           </view>
@@ -698,7 +698,7 @@ onMounted(() => {
   .cart-box {
     width: 54rpx;
     height: 54rpx;
-    background: linear-gradient(90deg, #fe8900, #ff5e00);
+    background: linear-gradient(90deg, #5274F2, #5274F2);
     border-radius: 50%;
     position: absolute;
     bottom: 50rpx;
@@ -718,12 +718,15 @@ onMounted(() => {
   position: relative;
   z-index: 1;
   background-color: $white;
-  height: 280rpx;
+  height: 300rpx;
+  padding: 16px;
+
 
   .lg-img-box {
-    width: 280rpx;
-    height: 280rpx;
+    width: 220rpx;
+    height: 220rpx;
     margin-right: 20rpx;
+    border-radius: 10px;
   }
 
   .lg-goods-title {
@@ -734,6 +737,7 @@ onMounted(() => {
     // width: 410rpx;
   }
 
+
   .lg-goods-subtitle {
     font-size: 24rpx;
     font-weight: 400;

+ 3 - 0
sheep/components/s-uploader/s-uploader.vue

@@ -270,6 +270,7 @@ export default {
       return this.uploadFiles(files);
     },
     async setValue(newVal, oldVal) {
+
       const newData = async (v) => {
         const reg = /cloud:\/\/([\w.]+\/?)\S*/;
         let url = '';
@@ -279,6 +280,7 @@ export default {
           url = v.url;
         }
         if (reg.test(url)) {
+
           v.fileID = url;
           v.url = await this.getTempFileURL(url);
         }
@@ -292,6 +294,7 @@ export default {
           newVal = {};
         }
       } else {
+
         if (!newVal) newVal = [];
         for (let i = 0; i < newVal.length; i++) {
           let v = newVal[i];

+ 73 - 73
sheep/components/s-userinfoai-item/s-userinfoai-item.vue

@@ -1,19 +1,13 @@
 <!-- 用户信息卡片 -->
 <template>
-  <view
-    :class="[{ 'border-bottom': props.hasBorderBottom }]"
-    class="userinfoai-item ss-flex ss-row-between ss-col-center"
-  >
+  <view :class="[{ 'border-bottom': props.hasBorderBottom }]"
+    class="userinfoai-item ss-flex ss-row-between ss-col-center">
     <view v-if="!isEmpty(props.item)" class="item-left">
-      <view class="area-text ss-flex ss-col-center">
-        <uni-tag
-          v-if="props.item.defaultStatus"
-          class="ss-m-r-10"
-          custom-style="background-color: var(--ui-BG-Main); border-color: var(--ui-BG-Main); color: #fff;"
-          size="small"
-          text="默认"
-        />
-        {{ props.item.areaName }}
+      <view class="area-text ss-flex ss-col-center" @click="onSelect">
+        <uni-tag v-if="props.item.defaultStatus" class="ss-m-r-10"
+          custom-style="background-color: var(--ui-BG-Main); border-color: var(--ui-BG-Main); color: #fff;" size="small"
+          text="默认" />
+        {{ props.item.textInformation }}
       </view>
     </view>
     <view v-else>
@@ -30,79 +24,85 @@
 </template>
 
 <script setup>
-  /**
-   * 基础组件 - 用户信息卡片
-   *
-   * @param {String}  icon = _icon-edit    - icon
-   *
-   * @event {Function()} click       - 点击
-   * @event {Function()} actionClick     - 点击工具栏
-   *
-   * @slot                 - 默认插槽
-   */
-  import sheep from '@/sheep';
-  import { isEmpty } from 'lodash-es';
+/**
+ * 基础组件 - 用户信息卡片
+ *
+ * @param {String}  icon = _icon-edit    - icon
+ *
+ * @event {Function()} click       - 点击
+ * @event {Function()} actionClick     - 点击工具栏
+ *
+ * @slot                 - 默认插槽
+ */
+import sheep from '@/sheep';
+import { isEmpty } from 'lodash-es';
 
-  const props = defineProps({
-    item: {
-      type: Object,
-      default() {},
-    },
-    hasBorderBottom: {
-      type: Boolean,
-      defult: true,
-    },
+const props = defineProps({
+  item: {
+    type: Object,
+    default() { },
+  },
+  hasBorderBottom: {
+    type: Boolean,
+    defult: true,
+  },
+});
+
+const onEdit = () => {
+  sheep.$router.go('/pages/member/userinfoai/edit', {
+    id: props.item.id,
   });
+};
 
-  const onEdit = () => {
-    sheep.$router.go('/pages/member/userinfoai/edit', {
-      id: props.item.id,
-    });
-  };
+const onSelect = () => {
+  sheep.$router.go('/pages/member/UserInfoAiEdit', {
+    id: props.item.id,
+  });
+};
 </script>
 
 <style lang="scss" scoped>
-  .userinfoai-item {
-    padding: 24rpx 30rpx;
+.userinfoai-item {
+  padding: 24rpx 30rpx;
 
-    .item-left {
-      width: 600rpx;
-    }
+  .item-left {
+    width: 600rpx;
+  }
 
-    .area-text {
-      font-size: 26rpx;
-      font-weight: 400;
-      color: $dark-9;
-    }
+  .area-text {
+    font-size: 26rpx;
+    font-weight: 400;
+    color: $dark-9;
+  }
 
-    .userinfoai-text {
-      font-size: 32rpx;
-      font-weight: 500;
-      color: #333333;
-      line-height: 48rpx;
-    }
+  .userinfoai-text {
+    font-size: 32rpx;
+    font-weight: 500;
+    color: #333333;
+    line-height: 48rpx;
+  }
 
-    .person-text {
-      font-size: 28rpx;
-      font-weight: 400;
-      color: $dark-9;
-    }
+  .person-text {
+    font-size: 28rpx;
+    font-weight: 400;
+    color: $dark-9;
   }
+}
 
-  .edit-btn {
-    width: 44rpx;
-    height: 44rpx;
-    background: $gray-f;
-    border-radius: 50%;
+.edit-btn {
+  width: 44rpx;
+  height: 44rpx;
+  background: $gray-f;
+  border-radius: 50%;
 
-    .edit-icon {
-      width: 24rpx;
-      height: 24rpx;
-    }
+  .edit-icon {
+    width: 24rpx;
+    height: 24rpx;
   }
+}
 
-  image {
-    width: 100%;
-    height: 100%;
-  }
+image {
+  width: 100%;
+  height: 100%;
+}
 </style>

+ 2 - 0
sheep/hooks/useModal.js

@@ -5,6 +5,7 @@ import { ref } from 'vue';
 import test from '@/sheep/helper/test.js';
 import AuthUtil from '@/sheep/api/member/auth';
 
+import { setIsRefreshToken } from '@/sheep/request/index.js';
 // 打开授权弹框
 export function showAuthModal(type = 'smsLogin') {
   const modal = $store('modal');
@@ -25,6 +26,7 @@ export function showAuthModal(type = 'smsLogin') {
 
 // 关闭授权弹框
 export function closeAuthModal() {
+  setIsRefreshToken(false);
   $store('modal').$patch((state) => {
     state.auth = '';
   });

+ 4 - 1
sheep/request/index.js

@@ -221,17 +221,20 @@ http.interceptors.response.use(
 // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
 let requestList = []; // 请求队列
 let isRefreshToken = false; // 是否正在刷新中
+export const setIsRefreshToken = (value) => {
+  isRefreshToken = value;
+};
 const refreshToken = async (config) => {
   // 如果当前已经是 refresh-token 的 URL 地址,并且还是 401 错误,说明是刷新令牌失败了,直接返回 Promise.reject(error)
   if (config.url.indexOf('/member/auth/refresh-token') >= 0) {
     return Promise.reject('error');
   }
-
   // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
   if (!isRefreshToken) {
     isRefreshToken = true;
     // 1. 如果获取不到刷新令牌,则只能执行登出操作
     const refreshToken = getRefreshToken();
+
     if (!refreshToken) {
       return handleAuthorized();
     }

+ 1 - 2
uni_modules/vk-uview-ui/libs/css/style.vue.scss

@@ -93,13 +93,12 @@ page {
 	top: 0;
 	pointer-events: none;
 	box-sizing: border-box;
-	-webkit-transform-origin: 0 0;
 	transform-origin: 0 0;
 	// 多加0.1%,能解决有时候边框缺失的问题
 	width: 199.8%;
 	height: 199.7%;
 	transform: scale(0.5, 0.5);
-	border: 0 solid $u-border-color;
+	// border: 0 solid $u-border-color;
 	z-index: 2;
 }