Răsfoiți Sursa

Merge branch 'main' into feature/v1.1.13

yuelujie 3 săptămâni în urmă
părinte
comite
2eaf4f28b4

+ 9 - 0
apps/bigmember_pc/src/api/modules/message.js

@@ -8,3 +8,12 @@ export function getUserSessionList(data) {
     data
   })
 }
+// 物料消息详情
+export function materialDetail(data) {
+  return request({
+    url: '/jyapi/messageCenter/materialDetail',
+    method: 'post',
+    data
+  })
+}
+

+ 11 - 0
apps/bigmember_pc/src/router/modules/message.js

@@ -0,0 +1,11 @@
+
+export default [
+    // 物料消息详情
+  {
+    path: '/materialDetail',
+    name: 'materialDetail',
+    alias: ['/message/materialDetail'],
+    component: () => import('@/views/message/materialDetail.vue')
+  }
+
+]

+ 2 - 1
apps/bigmember_pc/src/router/router-interceptors.js

@@ -62,7 +62,8 @@ const powerCheckWhiteList = [
   'report_download_record',
   'api-doc',
   'zhima',
-  'ent_report_year'
+  'ent_report_year',
+  'materialDetail'
 ]
 
 const regListCheck = function (regList, path) {

+ 2 - 0
apps/bigmember_pc/src/router/router.js

@@ -13,6 +13,7 @@ import report from './modules/report'
 import relationship from './modules/relationship'
 import api from './modules/api'
 import other from './modules/other'
+import message from './modules/message'
 
 if (import.meta.env.NODE_ENV !== 'production') {
   Vue.use(VueRouter)
@@ -36,6 +37,7 @@ const router = new VueRouter({
     ...report,
     ...relationship,
     ...api,
+    ...message,
     ...other,
     {
       path: '/404',

+ 229 - 0
apps/bigmember_pc/src/views/message/materialDetail.vue

@@ -0,0 +1,229 @@
+<template>
+  <div class="materialDetail">
+    <div class="content" v-loading="loading">
+      <div class="title">{{ info.title }}</div>
+      <div class="time">{{ time }}</div>
+      <div class="desc"> {{ info.content }}</div>
+      <div class="taskDescription"><span>任务描述:</span>{{ info.task_description }}</div>
+      <div class="detail"><span>分享物料详情:</span></div>
+      <div class="text">{{ info.material_content }}</div>
+      <div class="link">{{ info.imgWebpage }}</div>
+      <div class="button" @click="copy_text" v-if="info.material_content || info.imgWebpage">复制</div>
+      <div class="img_box">
+        <div class="box" v-for="(item, index) in imglist" :key="index" @click="imgIndex = index">
+          <div class="img_background">
+            <img :src="item" alt="图片">
+          </div>
+          <div class="img_btn" @click="down_load(item)">保存图片</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import dayjs from 'dayjs'
+import { materialDetail } from '@/api/modules/message'
+export default {
+  name: 'materialDetail',
+  data() {
+    return {
+      info: {},
+      loading: true,
+      imglist: [],
+      imgIndex: 0,
+      time: ''
+    }
+  },
+  computed: {
+    chooseImgurl() {
+      return this.imglist[this.imgIndex]
+    }
+
+  },
+  created() {
+    this.getDetail()
+  },
+  methods: {
+    getDetail() {
+      let { msgLogId, id } = this.$route.query
+      materialDetail({
+        msgLogId: Number(msgLogId), id: Number(id)
+      }).then(res => {
+        this.loading = false
+        if (res.code === 1 && res.data) {
+          this.info = res.data
+          this.time = dayjs(this.info.createtime).format('YYYY-MM-DD HH:mm')
+          if (this.info.attUrl) {
+            this.imglist = this.info.attUrl.split(',')
+          } else if (this.info.file_url) {
+            this.imglist = this.info.file_url.split(',')
+          }
+        } else {
+          this.$toast(res.errMsg)
+        }
+
+      }).catch(err => {
+        console.log(err)
+        this.loading = false
+      })
+
+    },
+    copy_text() {
+      const text = this.info.material_content || ''
+      const link = this.info.imgWebpage || ''
+      this.$copyText(
+        text
+        + '\r\n' +
+        link
+      ).then(res => {
+        this.$toast('复制成功')
+      }).catch(err => {
+        this.$toast('复制失败')
+      })
+
+    },
+    down_load(link) {
+      try {
+        // window.open(link)
+        this.download(link, '')
+        this.$toast('保存图片成功')
+
+      } catch (err) {
+        console.log(err)
+        this.$toast('保存图片失败')
+      }
+    },
+    download(href, name) {
+      // 创建隐藏的<a>标签
+      let eleLink = document.createElement('a')
+      // 设置下载的文件名
+      eleLink.download = name
+      // 设置下载链接
+      eleLink.href = href
+      // 模拟点击触发下载
+      eleLink.click()
+      // 移除临时创建的<a>标签
+      eleLink.remove()
+    },
+
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.materialDetail {
+  margin: 20px 0;
+}
+
+.materialDetail .content {
+  border-radius: 4px;
+  background-color: #fff;
+  max-width: 1200px;
+  min-height: 400px;
+  margin: auto;
+  padding: 40px;
+
+  .title {
+    color: #1D1D1D;
+    text-align: center;
+    font-size: 20px;
+    line-height: 32px;
+  }
+
+  .time {
+    color: #AAA;
+    font-size: 13px;
+    line-height: 20px;
+    margin: 20px 0;
+  }
+
+  .desc {
+    color: #1D1D1D;
+    font-size: 14px;
+    line-height: 24px;
+  }
+
+  .taskDescription,
+  .detail,
+  .text {
+    color: #1D1D1D;
+    font-size: 14px;
+    line-height: 24px;
+    margin-top: 10px;
+
+  }
+
+  .link {
+    font-size: 14px;
+    line-height: 24px;
+    margin-top: 10px;
+    margin-bottom: 10px;
+    color: #2ABED1;
+
+  }
+
+  .button {
+    font-size: 14px;
+    color: #fff;
+    background-color: #2ABED1;
+    border-radius: 4px;
+    width: 120px;
+    height: 35px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: pointer;
+  }
+
+  .img_btn {
+    font-size: 14px;
+    color: #fff;
+    background-color: #2ABED1;
+    border-radius: 4px;
+    width: 120px;
+    height: 35px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: pointer;
+    margin: auto;
+  }
+
+  .img_box {
+    width: 100%;
+    margin-top: 10px;
+    display: flex;
+    flex-wrap: wrap;
+
+  }
+
+  .border_h {
+    border: 2px solid #2ABED1;
+  }
+  .box{
+    margin-right: 24px;
+    margin-bottom: 10px;
+  }
+
+  .img_background {
+     
+    margin-bottom: 10px;
+    border-radius: 2px;
+    // background-color: #f2f2f4;
+    cursor: pointer;
+    box-sizing: border-box;
+
+    // display: flex;
+    // justify-content: center;
+    // align-items: center;
+    img {
+      max-width: 375px;
+      height: 375px;
+      border-radius: 2px;
+      // max-height: 100%
+    }
+  }
+
+}
+</style>

+ 12 - 1
apps/mobile/src/api/modules/message.js

@@ -1,5 +1,5 @@
-import request from '@/api'
 import qs from 'qs'
+import request from '@/api'
 
 /**
  * 获取用户消息列表,用于首页展示
@@ -211,3 +211,14 @@ export function ajaxMessageOpenLog(data) {
     data
   })
 }
+/**
+ * 物料消息详情
+ */
+export function materialDetail(data) {
+  // data = qs.stringify(data)
+  return request({
+    url: '/jyapi/messageCenter/materialDetail',
+    method: 'post',
+    data
+  })
+}

+ 47 - 30
apps/mobile/src/components/message/message-card.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="message-bg" v-if="list.length">
+  <div v-if="list.length" class="message-bg">
     <div class="message-main clickable">
       <!-- <div class="card-title">
         <span>最新消息</span>
@@ -10,27 +10,27 @@
           <AppIcon name="youbian" size="14" color="#c0c4cc" />
         </div>
       </div> -->
-      <div class="message-card" ref="msgCard">
+      <div ref="msgCard" class="message-card">
         <div
           class="message-group"
           :style="{
             transform: `translateY(-${offsetHeight}px)`,
-            height: groupHeight + 'px'
+            height: `${groupHeight}px`,
           }"
         >
           <div
-            class="message-item"
             v-for="(item, index) in list"
             :key="index"
-            :class="{ visited: item.visited }"
             ref="listItem"
-            @click="goMessage(item)"
             v-bound-phone="bindPhoneGoMessage(item)"
+            class="message-item"
+            :class="{ visited: item.visited }"
+            @click="goMessage(item)"
           >
             <!-- <span class="dot"></span> -->
             <van-cell class="message-item-cell" is-link>
               <template #title>
-                <span class="icon iconfont icon-nav_un_message"></span>
+                <span class="icon iconfont icon-nav_un_message" />
                 <span class="type">{{ item.type }}</span>
                 <span class="flex-1 title van-ellipsis">{{ item.title }}</span>
               </template>
@@ -48,23 +48,22 @@
 </template>
 
 <script>
+import { Cell } from 'vant'
+import { mapGetters } from 'vuex'
 import {
-  ajaxMessageList,
   ajaxMarkRead,
+  ajaxMessageList,
   ajaxMessageOpenLog
 } from '@/api/modules/message'
 import { AppIcon } from '@/ui'
-import { Cell } from 'vant'
 import { dateMatter } from '@/utils/date'
 import { appCallReloadTab } from '@/utils/callFn/appFn'
-import { callChangeTab, openLinkOfOther } from '@/utils'
-import { mapGetters } from 'vuex'
-import { vw2px, px2viewport } from '@/utils'
+import { callChangeTab, openLinkOfOther, px2viewport, vw2px } from '@/utils'
 
 // const MSG_TYPE = ['活动通知', '服务通知', '订阅消息', '项目动态', '企业动态', '分析报告', '系统通知', '剑鱼学堂', '商机情报']
 
 export default {
-  name: 'message-card',
+  name: 'MessageCard',
   components: {
     [AppIcon.name]: AppIcon,
     [Cell.name]: Cell
@@ -97,7 +96,8 @@ export default {
       const height = !this.msgData.unread
       if (height) {
         return 70 / 3.75
-      } else {
+      }
+      else {
         return 76 / 3.75
       }
     }
@@ -120,7 +120,7 @@ export default {
   methods: {
     decimalValue(numberValue) {
       // 将数值转换为字符串并截取前两位小数
-      const decimalValue = numberValue.toString().match(/^\d+(?:\.\d{0,1})?/)
+      const decimalValue = numberValue.toString().match(/^\d+(?:\.\d?)?/)
       // 返回处理后的数值字符串
       return decimalValue ? decimalValue[0] : ''
     },
@@ -131,7 +131,8 @@ export default {
       let listItem
       if (this.$refs.listItem) {
         listItem = this.$refs.listItem[1] // 列表项高度
-      } else {
+      }
+      else {
         listItem = {
           offsetHeight: offsetYPx
         }
@@ -163,7 +164,7 @@ export default {
         }
         this.list = [].concat(
           data.map((v) => {
-            const MSG_TYPE = column.filter((m) => m.msg_type === v.msg_type)
+            const MSG_TYPE = column.filter(m => m.msg_type === v.msg_type)
             return {
               title: v.title,
               visited: v.isRead === 1,
@@ -176,13 +177,24 @@ export default {
       })
     },
     goVisited(item) {
-      this.$router.push({
-        path: '/message/msgDetail',
-        query: {
-          id: Number(item.data.id),
-          msgLogId: Number(item.data.msgLogId)
-        }
-      })
+      if (item.data.realType === 14) {
+        this.$router.push({
+          path: '/message/materialDetail',
+          query: {
+            id: Number(item.data.id),
+            msgLogId: Number(item.data.msgLogId)
+          }
+        })
+      }
+      else {
+        this.$router.push({
+          path: '/message/msgDetail',
+          query: {
+            id: Number(item.data.id),
+            msgLogId: Number(item.data.msgLogId)
+          }
+        })
+      }
     },
     goMore() {
       callChangeTab('message', this.$router)
@@ -201,7 +213,8 @@ export default {
       this.onClickMsg(item.data.msgLogId)
       if (item.visited) {
         return this.goVisited(item)
-      } else {
+      }
+      else {
         this.changeMessageStatus(item)
       }
     },
@@ -222,18 +235,21 @@ export default {
       if (inWX) {
         if (url.weChatUrl) {
           this.wxLocationTo(url.weChatUrl)
-        } else {
+        }
+        else {
           this.goVisited(item)
         }
-      } else {
+      }
+      else {
         let toLink = inIOS ? url.iosUrl : url.androidUrl
         if (toLink) {
           const hasHttp = /^http(s)?:\/\//.test(toLink)
           if (!hasHttp && toLink[0] !== '/') {
-            toLink = '/' + toLink
+            toLink = `/${toLink}`
           }
           openLinkOfOther(toLink)
-        } else {
+        }
+        else {
           this.goVisited(item)
         }
       }
@@ -243,7 +259,8 @@ export default {
       // 链接中如果是www.jianyu360.cn域名,微信中会唤起app,该用window.open
       // window.open(url)
       // window.open在安卓正常,但是ios无法跳转。改为location.href跳转
-      if (!url) return
+      if (!url)
+        return
       location.href = url
     },
     bindPhoneGoMessage(item) {

+ 18 - 0
apps/mobile/src/router/modules/message.js

@@ -30,6 +30,24 @@ export default [
       title: '消息详情'
     }
   },
+  {
+    path: '/materialDetail',
+    name: 'material-detail',
+    component: () => import('@/views/message/materialDetail.vue'),
+    meta: {
+      header: true,
+      title: '消息详情'
+    }
+  },
+  {
+    path: '/imgDetail',
+    name: 'img-detail',
+    component: () => import('@/views/message/imgDetail.vue'),
+    meta: {
+      header: true,
+      title: '图片保存'
+    }
+  },
   {
     path: '/msgGuide',
     name: 'msg-guide',

+ 89 - 43
apps/mobile/src/views/message/Detail.vue

@@ -1,32 +1,33 @@
 <template>
   <div class="j-container message-detail">
-    <div class="j-main" ref="main" v-show="!emptyShow">
+    <div v-show="!emptyShow" ref="main" class="j-main">
       <van-list
-        ref="listRef"
-        v-model="listState.loading"
-        :finished="listState.finished"
-        :offset="listState.offset"
-        direction="up"
-        finished-text=""
-        @load="getList"
+        ref="listRef" v-model="listState.loading" :finished="listState.finished" :offset="listState.offset"
+        direction="up" finished-text="" @load="getList"
       >
-        <div class="d-list" v-for="item in listState.list" :key="item.id">
-          <p class="list-time">{{ item.createTime }}</p>
+        <div v-for="item in listState.list" :key="item.id" class="d-list">
+          <p class="list-time">
+            {{ item.createTime }}
+          </p>
           <div class="list-main">
-            <div class="red-hot" v-if="item.isRead === 0"></div>
-            <img class="list-icon" :src="item.icon" />
+            <div v-if="item.isRead === 0" class="red-hot" />
+            <img class="list-icon" :src="item.icon">
             <div class="list-words clickable" @click="onMsgClick(item)">
-              <span class="triangle"></span>
-              <p class="l-w-title">{{ item.title }}</p>
-              <p class="l-w-content" v-html="item.content"></p>
+              <span class="triangle" />
+              <p class="l-w-title">
+                {{ item.title }}
+              </p>
+              <p class="l-w-content" v-html="item.content" />
             </div>
           </div>
         </div>
       </van-list>
     </div>
-    <div class="j-main" v-show="emptyShow">
+    <div v-show="emptyShow" class="j-main">
       <AppEmpty>
-        <p slot="default">暂无消息</p>
+        <p slot="default">
+          暂无消息
+        </p>
       </AppEmpty>
     </div>
   </div>
@@ -35,9 +36,9 @@
 <script>
 import { List } from 'vant'
 import {
-  ajaxMessageList,
+  ajaxAppClickMessage,
   ajaxMarkRead,
-  ajaxAppClickMessage
+  ajaxMessageList
 } from '@/api/modules'
 import { dateFormatter } from '@/utils/date/'
 import { mixinHeader } from '@/utils/mixins/header'
@@ -49,11 +50,11 @@ import { openLinkOfOther, replaceKeyword } from '@/utils'
 
 export default {
   name: 'AppMessageDetail',
-  mixins: [mixinHeader],
   components: {
     [List.name]: List,
     [AppEmpty.name]: AppEmpty
   },
+  mixins: [mixinHeader],
   data() {
     return {
       pageLayoutConf: {
@@ -88,7 +89,8 @@ export default {
     this.restored = this.restoreState()
     if (this.restored) {
       this.mergePageConf()
-    } else {
+    }
+    else {
       this.type = this.$route.query?.type
       this.isClassSearch = this.$route.query?.isClassSearch
     }
@@ -104,9 +106,9 @@ export default {
       // this.appClickMsg(this.type)
       this.listState.loading = true
       try {
-        let { data, total, column } = await ajaxMessageList({
+        const { data, total, column } = await ajaxMessageList({
           offset: this.listState.pageNum,
-          msgType: parseInt(this.type),
+          msgType: Number.parseInt(this.type),
           isColumn: true,
           isColumnNewMsg: false,
           isRead: -1,
@@ -116,7 +118,7 @@ export default {
         })
         let selectData = []
         if (Array.isArray(column)) {
-          selectData = column.filter((m) => m.msg_type === parseInt(this.type))
+          selectData = column.filter(m => m.msg_type === Number.parseInt(this.type))
           this.setDocumentTitle(selectData[0])
         }
         if (Array.isArray(data) && data.length > 0) {
@@ -124,7 +126,7 @@ export default {
             .map((v) => {
               const arr = ['点击前往支付>>', '请再次购买>>', '前往续费>>']
               arr.forEach((r, i) => {
-                if (v.content.indexOf(r) !== -1) {
+                if (v.content.includes(r)) {
                   v.content = replaceKeyword(v.content, r)
                 }
               })
@@ -138,7 +140,8 @@ export default {
                 content: v.content,
                 msgType: v.msg_type,
                 link: v.link,
-                url: v.url
+                url: v.url,
+                realType: v.realType
               }
             })
             .reverse()
@@ -154,28 +157,33 @@ export default {
             // 必须在list赋值后,loading结束前进行滚动,才不会直接触发加载下页
             if (this.listState.pageNum <= 1) {
               this.scrollToBottom()
-            } else {
+            }
+            else {
               this.scrollTo()
             }
 
             if (
-              this.listState.pageNum * this.listState.pageSize <
-              this.listState.total
+              this.listState.pageNum * this.listState.pageSize
+              < this.listState.total
             ) {
               this.listState.loading = false
               this.listState.pageNum++
-            } else {
+            }
+            else {
               this.listState.finished = true
             }
           })
-        } else {
+        }
+        else {
           this.listState.finished = true
           this.$forceUpdate()
         }
-      } catch (error) {
+      }
+      catch (error) {
         this.listState.finished = true
         console.log(error)
-      } finally {
+      }
+      finally {
         this.listState.loaded = true
       }
     },
@@ -186,15 +194,22 @@ export default {
       if (inWX) {
         if (url.weChatUrl) {
           this.wxLocationTo(url.weChatUrl)
-        } else {
-          this.setLinkDetail(item)
         }
-      } else {
+        else {
+          if (item.realType === 14) {
+            this.gomaterialDetail(item)
+          }
+          else {
+            this.setLinkDetail(item)
+          }
+        }
+      }
+      else {
         let toLink = inIOS ? url.iosUrl : url.androidUrl
         if (toLink) {
           const hasHttp = /^http(s)?:\/\//.test(toLink)
           if (!hasHttp && toLink[0] !== '/') {
-            toLink = '/' + toLink
+            toLink = `/${toLink}`
           }
           // 检查是否tabBar页面
           const tabBarKey = checkNowInAppTabbarPage(toLink)
@@ -202,12 +217,19 @@ export default {
             // 刷新客户端对应 tabBar页面
             appCallReloadTab(tabBarKey, 1)
             callChangeTab(tabBarKey)
-          } else {
+          }
+          else {
             appCallReloadTab('search', 0)
             openLinkOfOther(toLink)
           }
-        } else {
-          this.setLinkDetail(item)
+        }
+        else {
+          if (item.realType === 14) {
+            this.gomaterialDetail(item)
+          }
+          else {
+            this.setLinkDetail(item)
+          }
         }
       }
     },
@@ -222,7 +244,7 @@ export default {
     async onMsgClick(item) {
       item.isRead = 1
       const { code } = await ajaxMarkRead({
-        msgId: parseInt(item.id)
+        msgId: Number.parseInt(item.id)
       })
       if (code === 0) {
         this.saveState()
@@ -240,17 +262,29 @@ export default {
         })
       }, 1000)
     },
+    gomaterialDetail(item) {
+      setTimeout(() => {
+        this.$router.push({
+          path: '/message/materialDetail',
+          query: {
+            msgLogId: item.msgLogId,
+            id: item.id
+          }
+        })
+      }, 1000)
+    },
     // 微信端跳转
     wxLocationTo(url) {
       // 链接中如果是www.jianyu360.cn域名,微信中会唤起app,该用window.open
       // window.open(url)
       // window.open在安卓正常,但是ios无法跳转。改为location.href跳转
-      if (!url) return
+      if (!url)
+        return
       location.href = url
     },
     saveState() {
       const { main } = this.$refs
-      this.listState.scrollTop = main ? parseInt(main.scrollTop) : 0
+      this.listState.scrollTop = main ? Number.parseInt(main.scrollTop) : 0
       this.$storage.set(
         this.$route.path,
         {
@@ -309,16 +343,20 @@ export default {
   line-height: 24px;
   color: $gray_9;
 }
+
 .message-detail {
   .j-main {
     padding: 0 24px 0 12px;
   }
+
   .d-list {
     margin-top: 24px;
+
     &:last-of-type {
       padding-bottom: 24px;
     }
   }
+
   .list-time {
     font-size: 11px;
     color: $gray_6;
@@ -326,10 +364,12 @@ export default {
     text-align: center;
     padding-left: 12px;
   }
+
   .list-main {
     position: relative;
     display: flex;
     margin-top: 8px;
+
     .red-hot {
       position: absolute;
       top: 1px;
@@ -340,12 +380,14 @@ export default {
       background: #fb483d;
     }
   }
+
   .list-icon {
     width: 40px;
     height: 40px;
     flex-shrink: 0;
     margin-right: 16px;
   }
+
   .list-words {
     position: relative;
     flex: 1;
@@ -355,10 +397,12 @@ export default {
     box-shadow: 0px 2px 8px 0px rgb(54 147 179 / 5%);
     z-index: 2;
     word-break: break-word;
+
     &::before {
       z-index: 3;
     }
   }
+
   .triangle {
     position: absolute;
     left: -8px;
@@ -369,10 +413,12 @@ export default {
     border-bottom: 8px solid transparent;
     border-right: 12px solid #fff;
   }
+
   .l-w-title {
     font-weight: 700;
     @extend %base-text;
   }
+
   .l-w-content {
     margin-top: 4px;
     font-size: 13px;

+ 79 - 0
apps/mobile/src/views/message/imgDetail.vue

@@ -0,0 +1,79 @@
+<template>
+  <div id="imgdetail">
+    <img :src="url" alt="">
+    <p class="tip">
+      提示:点击右上角“...”选择“在浏览器中打开”,即可保存图片。
+    </p>
+    <div class="button" @click="down_load(url)">
+      保存图片
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      url: ''
+    }
+  },
+  created() {
+    this.url = this.$route.query.url
+  },
+  methods: {
+    down_load(url) {
+      try {
+        const a = document.createElement('a') // 生成一个a元素
+        const event = new MouseEvent('click', {
+          bubbles: true,
+          cancelable: true,
+          view: window
+        }) // 创建一个单击事件
+        a.download = '' // 设置图片名称
+        a.href = url // 将生成的URL设置为a.href属性
+        a.dispatchEvent(event) // 触发a的单击事件
+        a.remove()
+      }
+      catch (e) {
+        console.log(e)
+        this.$toast('保存图片失败')
+      }
+    }
+
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+#imgdetail {
+  padding: 16px;
+  display: block;
+  .button{
+    width: 100%;
+    height: 40px;
+    font-size: 16px;
+    color: #fff;
+    background-color: #2ABED1;
+    border-radius: 4px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin: 0 auto;
+    margin-top: 10px;
+
+  }
+
+  img{
+    width: 100%;
+    margin: auto;
+
+  }
+    .tip{
+    font-size: 12px;
+    color: #9b9ca3;
+    margin-top: 10px;
+    line-height: 20px;
+  }
+
+}
+</style>

+ 277 - 0
apps/mobile/src/views/message/materialDetail.vue

@@ -0,0 +1,277 @@
+<template>
+  <div class="msg-detail">
+    <header>
+      {{ time }}
+    </header>
+    <main>
+      <h1 class="title">
+        {{ info.title }}
+      </h1>
+      <p class="desc">
+        {{ info.content }}
+      </p>
+      <div class="taskDescription">
+        <span style="color: #171826;">任务描述:</span>{{ info.task_description }}
+      </div>
+      <div class="detail">
+        <span style="color: #171826;">分享物料详情:</span>
+      </div>
+      <div class="text">
+        {{ info.material_content }}
+      </div>
+      <div class="link">
+        {{ info.imgWebpage }}
+      </div>
+      <div v-if="info.material_content || info.imgWebpage" class="button" @click="copy_text">
+        复制
+      </div>
+      <div class="img_box">
+        <div v-for="(item, index) in imglist" :key="index" class="img_background">
+          <img :src="item" alt="图片">
+          <!-- ios 微信隐藏下载按钮 使用微信自带长按保存 -->
+          <div v-show="$env.platform !== 'wx' || ($env.platform === 'wx' && !$envs.inIOS)" class="img_button" @click="down_load(item)">
+            保存图片
+          </div>
+        </div>
+      </div>
+      <!-- iOS微信引导 -->
+      <p v-if="$env.platform === 'wx' && chooseImgurl && $envs.inIOS" class="tip">
+        提示:若使用微信打开此页面,长按保存图片即可。
+      </p>
+    </main>
+  </div>
+</template>
+
+<script>
+import dayjs from 'dayjs'
+import { materialDetail } from '@/api/modules'
+import { copyText } from '@/utils'
+import { savePic } from '@/utils/callFn/appFn'
+
+export default {
+  name: 'MaterialDetail',
+  data() {
+    return {
+      info: {},
+      imglist: [],
+      imgIndex: 0,
+      time: ''
+    }
+  },
+  computed: {
+    chooseImgurl() {
+      return this.imglist[this.imgIndex]
+    }
+  },
+  created() {
+    this.getMsgDetail()
+  },
+  methods: {
+    async getMsgDetail() {
+      const { msgLogId, id } = this.$route.query
+      const loading = this.$toast.loading()
+      const { code, data, errMsg } = await materialDetail({ msgLogId: Number(msgLogId), id: Number(id) })
+      loading.clear()
+      if (code === 1 && data) {
+        this.info = data
+        this.time = dayjs(this.info.createtime).format('YYYY-MM-DD HH:mm')
+        if (this.info.attUrl) {
+          this.imglist = this.info.attUrl.split(',')
+        }
+        else if (this.info.file_url) {
+          this.imglist = this.info.file_url.split(',')
+        }
+      }
+      else {
+        this.$toast(errMsg)
+      }
+    },
+    copy_text() {
+      const text = this.info.material_content || ''
+      const link = this.info.imgWebpage || ''
+      try {
+        copyText(`${text}\r\n${link}`)
+        this.$toast('复制成功')
+      }
+      catch (e) {
+        console.log(e)
+        this.$toast('复制失败')
+      }
+    },
+    down_load(link) {
+      this.saveEvent(link)
+    },
+
+    saveEvent(url) {
+      if (this.$env.platform === 'app') {
+        try {
+          savePic(
+            url,
+            '剑鱼标讯需要您的存储权限,将用于保存图片到本地。'
+          )
+          this.$toast('保存图片成功')
+        }
+        catch (e) {
+          console.log(e)
+          this.$toast('保存图片失败')
+        }
+      }
+      else { // 微信
+        if (!this.$envs.inIOS) { // 安卓微信存在部分处理后带二维码图片无法长按保存,跳转引导页
+          this.$router.push({
+            path: '/message/imgDetail',
+            query: {
+              url
+            }
+          })
+        }
+        else {
+          console.log('ios')
+        }
+      }
+
+      // 后端返回图片链接非同域且不支持跨域,无法转base64,故弃用客户端方法
+      // if (this.$envs.inIOS) {
+      //   location.href = url
+      // }
+      // else {
+      //   if (this.$env.platform === 'wx') { // 安卓微信存在部分处理后带二维码图片无法长按保存,跳转引导页
+      //     this.$router.push({
+      //       path: '/message/imgDetail',
+      //       query: {
+      //         url
+      //       }
+      //     })
+      //   }
+      //   else {
+      //     try {
+      //       const a = document.createElement('a') // 生成一个a元素
+      //       const event = new MouseEvent('click', {
+      //         bubbles: true,
+      //         cancelable: true,
+      //         view: window
+      //       }) // 创建一个单击事件
+      //       a.download = '' // 设置图片名称
+      //       a.href = url // 将生成的URL设置为a.href属性
+      //       a.dispatchEvent(event) // 触发a的单击事件
+      //       a.remove()
+      //       this.$toast('保存图片成功')
+      //     }
+      //     catch (e) {
+      //       console.log(e)
+      //       this.$toast('保存图片失败')
+      //     }
+      //   }
+      // }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.msg-detail {
+  header {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-top: 12px;
+    color: #9b9ca3;
+    font-size: 11px;
+    line-height: 15.4px;
+  }
+  main {
+    padding: 0 16px 16px 16px;
+    margin-top: 20px;
+    .title {
+      font-size: 20px;
+      color: #171826;
+      font-weight: 400;
+      line-height: 30px;
+    }
+    .desc {
+      margin-top: 15px;
+      font-size: 14px;
+      color: #5f5e64;
+      line-height: 20px;
+    }
+
+  .taskDescription,
+  .detail,
+  .text {
+    color: #5f5e64;
+    font-size: 14px;
+    line-height: 20px;
+    margin-top: 15px;
+
+  }
+
+  .link {
+    font-size: 14px;
+    line-height: 20px;
+    margin-top: 15px;
+    margin-bottom: 15px;
+    color: #2ABED1;
+    word-break: break-all;
+
+  }
+
+  .button {
+    font-size: 14px;
+    color: #fff;
+    background-color: #2ABED1;
+    border-radius: 4px;
+    width: 110px;
+    height: 30px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+  }
+    .img_button {
+    font-size: 14px;
+    color: #fff;
+    background-color: #2ABED1;
+    border-radius: 4px;
+    width: 110px;
+    height: 30px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin: auto;
+    margin-top: 10px;
+
+  }
+
+  .img_box{
+     width: 100%;
+      margin-top: 15px;
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: space-between;
+
+  }
+  .border_h{
+    border: 2px solid #2ABED1;
+  }
+  .img_background {
+    // max-width: 45%;
+    // margin-right: 15px;
+    margin-bottom: 15px;
+    border-radius: 2px;
+    box-sizing: border-box;
+    cursor: pointer;
+    img{
+      max-width: 300px;
+      height: 300px;
+      // width: 100%;
+      border-radius: 2px;
+    }
+  }
+  }
+  .tip{
+    font-size: 12px;
+    color: #9b9ca3;
+    margin-top: 10px;
+  }
+}
+</style>