Explorar o código

feat: 移动端三级页组件资源添加

cuiyalong hai 1 ano
pai
achega
c4d4b7f1fc
Modificáronse 22 ficheiros con 2043 adicións e 2 borrados
  1. 8 0
      apps/mobile/src/api/modules/pay.js
  2. 40 0
      apps/mobile/src/assets/style/modules/tag.scss
  3. 155 0
      apps/mobile/src/components/share/AppShareSheet.vue
  4. 151 0
      apps/mobile/src/components/share/WxShareGuide.vue
  5. 12 2
      apps/mobile/src/router/modules/article.js
  6. 90 0
      apps/mobile/src/utils/mixins/modules/app-wx-share.js
  7. 1 0
      apps/mobile/src/utils/mixins/modules/wx-share.js
  8. 87 0
      apps/mobile/src/views/article/components/AbstractEnt.vue
  9. 261 0
      apps/mobile/src/views/article/components/ContentAbstract.vue
  10. 30 0
      apps/mobile/src/views/article/components/ContentAbstractEntList.vue
  11. 47 0
      apps/mobile/src/views/article/components/ContentBusinessRecommend.vue
  12. 89 0
      apps/mobile/src/views/article/components/ContentCustomerRecommend.vue
  13. 107 0
      apps/mobile/src/views/article/components/ContentMainText.vue
  14. 48 0
      apps/mobile/src/views/article/components/ContentModuleCard.vue
  15. 86 0
      apps/mobile/src/views/article/components/ContentProjectTimeline.vue
  16. 65 0
      apps/mobile/src/views/article/components/ContentTouBiaoService.vue
  17. 72 0
      apps/mobile/src/views/article/components/DataExportBanner.vue
  18. 43 0
      apps/mobile/src/views/article/components/MaskCard.vue
  19. 43 0
      apps/mobile/src/views/article/components/NpsCard.vue
  20. 117 0
      apps/mobile/src/views/article/components/RecommendProjectCard.vue
  21. 145 0
      apps/mobile/src/views/article/components/ServiceIntroCard.vue
  22. 346 0
      apps/mobile/src/views/article/content.vue

+ 8 - 0
apps/mobile/src/api/modules/pay.js

@@ -254,3 +254,11 @@ export function getEffectiveTime(data) {
     data
   })
 }
+
+// 获取域名配置
+export function getAppsDomain() {
+  return request({
+    url: '/jypay/getDomain',
+    method: 'post'
+  })
+}

+ 40 - 0
apps/mobile/src/assets/style/modules/tag.scss

@@ -14,3 +14,43 @@ $jy-tag-padding: 6px;
   padding: 0 $jy-tag-padding;
   box-sizing: border-box;
 }
+
+
+.j-tag-item {
+  $blue: #05a6f3;
+
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 3px 6px;
+  font-size: 12px;
+  height: 22px;
+  color: #5f5e64;
+  background-color: #f7f9fa;
+  &.border {
+    border: 1px solid rgba(0, 0, 0, 0.05);
+  }
+  &.main {
+    color: $main;
+    border-color: $main;
+    background-color: rgba($main, 0.1);
+  }
+  &.red {
+    color: $red;
+    border-color: $red;
+    background-color: rgba($red, 0.1);
+  }
+  &.orange {
+    color: $orange;
+    border-color: $orange;
+    background-color: rgba($orange, 0.1);
+  }
+  &.blue {
+    color: $blue;
+    border-color: $blue;
+    background-color: rgba($blue, 0.1);
+  }
+  &.round {
+    border-radius: 4px;
+  }
+}

+ 155 - 0
apps/mobile/src/components/share/AppShareSheet.vue

@@ -0,0 +1,155 @@
+<template>
+  <van-share-sheet
+    class="app-share-sheet"
+    get-container="body"
+    :value="value"
+    @input="onInput"
+    :options="options"
+    cancel-text=""
+    @select="onSelect"
+  >
+    <PopupLayout
+      slot="title"
+      :title="popupTitle"
+      @closeIconClick="onCloseIconClick"
+    >
+      <slot name="subtitle"></slot>
+    </PopupLayout>
+  </van-share-sheet>
+</template>
+<script>
+import { ShareSheet } from 'vant'
+import PopupLayout from '@/components/common/PopupLayout.vue'
+import imgPengyouquan from '@/assets/image/icon/share/pengyouquan.png'
+import imgWeiXin from '@/assets/image/icon/share/weixin.png'
+import imgQQ from '@/assets/image/icon/share/qq.png'
+
+export default {
+  name: 'AppShareSheet',
+  components: {
+    [ShareSheet.name]: ShareSheet,
+    PopupLayout
+  },
+  props: {
+    value: Boolean,
+    popupTitle: {
+      type: String,
+      default: '分享有礼'
+    },
+    shareTitle: {
+      type: String,
+      default: ''
+    },
+    shareContent: {
+      type: String,
+      default: ''
+    },
+    shareLink: {
+      type: String,
+      default: location.href
+    }
+  },
+  data() {
+    return {
+      options: [
+        [
+          {
+            id: 1,
+            name: '微信',
+            icon: imgWeiXin
+            // icon: '/jyapp/images/img/weixin.png',
+            // icon: 'wechat'
+          },
+          {
+            id: 3,
+            name: '朋友圈',
+            icon: imgPengyouquan
+            // icon: '/jyapp/images/img/pengyouquan.png',
+            // icon: 'wechat-moments'
+          },
+          {
+            id: 2,
+            name: 'QQ',
+            icon: imgQQ
+            // icon: '/jyapp/images/img/qq.png',
+            // icon: 'qq'
+          }
+        ]
+      ]
+    }
+  },
+  methods: {
+    onCloseIconClick: function () {
+      this.onInput(false)
+    },
+    onInput: function (f) {
+      this.$emit('input', f)
+    },
+    onSelect: function (item) {
+      var _this = this
+      this.$emit('share', item)
+      setTimeout(function () {
+        _this.doAppShare(item)
+      }, 10)
+    },
+    doAppShare: function (item) {
+      var shareType = item.id
+      var shareTitle = this.shareTitle
+      var content = this.shareContent
+      var link = this.shareLink
+      console.log(shareType, shareTitle, content, link)
+      try {
+        if (typeof window.__compatibleAppFn === 'function') {
+          window.__compatibleAppFn(
+            JyObj.share,
+            shareType,
+            shareTitle,
+            content,
+            link,
+            '剑鱼标讯需要您的存储权限、电话权限,将用于帮助您下载、保存图片到本地,将内容成功分享到社交平台。'
+          )
+        }
+      } catch (error) {
+        console.log(
+          '__compatibleAppFn error --->',
+          shareType,
+          shareTitle,
+          content,
+          link
+        )
+        console.log(error)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-share-sheet {
+  ::v-deep {
+    .van-share-sheet__header {
+      padding: 0;
+      min-height: 64px;
+    }
+    .van-share-sheet__title {
+      margin-top: 0;
+    }
+    .van-share-sheet__option {
+      flex: 1;
+    }
+  }
+
+  .title-content {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 16px;
+    color: #171826;
+    height: 64px;
+    .title-text {
+      font-size: 20px;
+      color: #171826;
+    }
+  }
+}
+</style>

+ 151 - 0
apps/mobile/src/components/share/WxShareGuide.vue

@@ -0,0 +1,151 @@
+<template>
+  <van-popup
+    class="open-tip-popup"
+    get-container="body"
+    :value="value"
+    @click="onClickPopup"
+    @input="onInput"
+  >
+    <div class="arrow-img">
+      <img :src="themeConf.arrowImg" alt="" />
+    </div>
+    <div class="popup-tip-container">
+      <div
+        class="popup-tip-text-container hexagon-radius"
+        :class="themeConf.popClass"
+      >
+        <span class="share-icon icon-logo-mini" v-show="logoMini"></span>
+        <span class="popup-tip-text" v-text="tip"></span>
+      </div>
+      <div class="popup-open-in-browser" v-show="false">
+        <span class="share-icon icon-earth"></span>
+        <span class="text">在浏览器中打开</span>
+      </div>
+    </div>
+  </van-popup>
+</template>
+<script>
+import { Popup } from 'vant'
+import arrowTop from '@/assets/image/guide/arrow-top-rightx3@2x.png'
+import arrowTopWhite from '@/assets/image/guide/arrow-top-rightx3-white@2x.png'
+
+export default {
+  name: 'AppShareSheet',
+  components: {
+    [Popup.name]: Popup
+  },
+  props: {
+    value: Boolean,
+    tip: {
+      type: String,
+      default: '点击右上角,将活动分享给好友吧'
+    },
+    theme: {
+      type: String,
+      default: 'main' // main蓝色主题,red红色主题
+    },
+    logoMini: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      themeMap: {
+        main: {
+          arrowImg: arrowTop,
+          popClass: 'main'
+        },
+        red: {
+          arrowImg: arrowTopWhite,
+          popClass: 'red'
+        }
+      }
+    }
+  },
+  computed: {
+    themeList() {
+      return Object.keys(this.themeMap)
+    },
+    themeConf() {
+      var theme = this.theme
+      if (this.themeList.indexOf(theme) === -1) {
+        theme = 'main'
+      }
+      return this.themeMap[theme]
+    }
+  },
+  methods: {
+    onInput(f) {
+      this.$emit('input', f)
+    },
+    onClickPopup() {
+      this.$emit('click-overlay')
+      this.onInput(false)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.open-tip-popup {
+  width: 100%;
+  height: 100%;
+  background-color: transparent;
+}
+.arrow-img {
+  position: absolute;
+  top: 16px;
+  right: 24px;
+  width: 46px;
+}
+.arrow-img > img {
+  display: block;
+  width: 100%;
+}
+.popup-tip-container {
+  position: absolute;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  top: 90px;
+  left: 50%;
+  width: 100%;
+  transform: translate(-50%, 0);
+}
+.popup-tip-text-container {
+  display: flex;
+  align-items: center;
+  padding: 0 14px;
+  height: 40px;
+  color: #fff;
+  font-size: 16px;
+  line-height: 22px;
+  letter-spacing: 1px;
+}
+
+.hexagon-radius {
+  background: url(@/assets/image/guide/hexagon-radius-main@2x.png) no-repeat;
+  background-position: center center;
+  background-size: contain;
+}
+.hexagon-radius.red {
+  background-image: url(@/assets/image/guide/hexagon-radius-red@2x.png);
+}
+.popup-open-in-browser {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-top: 20px;
+  padding: 0 8px;
+  height: 32px;
+  border-radius: 32px;
+  background-color: rgba(255, 255, 255, 0.16);
+}
+.popup-open-in-browser .text {
+  margin-left: 8px;
+  color: #fff;
+  font-size: 14px;
+  line-height: 24px;
+}
+</style>

+ 12 - 2
apps/mobile/src/router/modules/article.js

@@ -1,7 +1,17 @@
 export default [
   {
-    path: '/:type/:id',
-    name: 'content',
+    path: '/content/:id.html',
+    name: 'article-content',
+    alias: ['/article/content/:id', '/jyapp/article/content/:id.html'],
+    component: () => import('@/views/article/content.vue'),
+    meta: {
+      header: true,
+      title: '公告信息'
+    }
+  },
+  {
+    path: '/issued/:id',
+    name: 'issued-content',
     component: () => import('@/views/article/index.vue'),
     meta: {
       header: true,

+ 90 - 0
apps/mobile/src/utils/mixins/modules/app-wx-share.js

@@ -0,0 +1,90 @@
+import { envs } from '@/utils/prototype/modules/platform'
+import { registerWxShare } from '@/utils/wxShare'
+import appShareSheet from '@/components/share/AppShareSheet'
+import wxShareGuide from '@/components/share/WxShareGuide'
+import { getAppsDomain } from '@/api/modules'
+
+export const appWxShareMixin = {
+  components: {
+    appShareSheet: appShareSheet,
+    wxShareGuide: wxShareGuide
+  },
+  data() {
+    return {
+      _wxSdk: null,
+      isWeiXinBrowser: envs.inWX,
+      shareShow: false, // app分享share-sheet显示隐藏
+      guideMask: false, // 微信分享引导显示隐藏
+      domainConf: {
+        app: '',
+        web: '',
+        wx: ''
+      },
+      shareEnableConf: {
+        shareActionImmediate: true,
+        app: true, // 开启app分享
+        wx: true // 开启wx分享
+      },
+      shareConf: {
+        fetchDomain: true,
+        title: '',
+        content: '',
+        pathname: '',
+        origin: location.origin,
+        link: ''
+      }
+    }
+  },
+  watch: {
+    'shareConf.link': function () {
+      this.refreshShareLink()
+    }
+  },
+  created() {
+    if (this.shareEnableConf.shareActionImmediate) {
+      this.initShareMixin()
+    }
+  },
+  methods: {
+    initShareMixin() {
+      if (this.isWeiXinBrowser && this.shareEnableConf.wx) {
+        this.registerWxShare()
+      } else {
+        if (this.shareEnableConf.app) {
+          if (this.shareConf.fetchDomain) {
+            this.getShareDomain()
+          } else {
+            this.refreshShareLink()
+            this.registerWxShare()
+          }
+        }
+      }
+    },
+    refreshShareLink() {
+      this.shareConf.link = this.shareConf.origin + this.shareConf.pathname
+    },
+    async getShareDomain() {
+      const { data, error_code: code, error_msg: msg } = await getAppsDomain()
+      console.log(msg)
+      if (code === 0 && data) {
+        // app往微信分享会用到(app分享到微信,需要分享微信的域名)
+        Object.assign(this.domainConf, data)
+        if (data.wx) {
+          this.shareConf.origin = data.wx
+          this.refreshShareLink()
+        }
+        this.registerWxShare()
+      }
+    },
+    async registerWxShare() {
+      this.refreshShareLink()
+      this._wxSdk = await registerWxShare({
+        title: this.shareConf.title,
+        desc: this.shareConf.content,
+        link: this.shareConf.link,
+        imgUrl: this.shareConf.imgUrl
+      })
+      console.log(this._wxSdk)
+    }
+  }
+}

+ 1 - 0
apps/mobile/src/utils/mixins/modules/wx-share.js

@@ -1,5 +1,6 @@
 import { initWxSDK, setWxSDKShareOptions } from '@/utils/wxShare'
 
+// 仅微信分享
 export const wxShareMixin = {
   data() {
     return {

+ 87 - 0
apps/mobile/src/views/article/components/AbstractEnt.vue

@@ -0,0 +1,87 @@
+<template>
+  <section class="abstract-ent-container clickable bg-white">
+    <div class="abstract-ent-title-container flex">
+      <span class="j-tag-item border round blue" v-if="entType === 'winner'">
+        中标单位画像
+      </span>
+      <span class="j-tag-item border round orange" v-if="entType === 'buyer'">
+        采购单位画像
+      </span>
+      <p class="abstract-ent-title van-ellipsis flex-1">
+        大通回族土族自治县住房和城乡建设局
+      </p>
+    </div>
+    <div
+      class="abstract-ent-info-list flex flex-(items-center justify-between)"
+    >
+      <div class="abstract-ent-info label-value">
+        <span class="ent-info-label">联系人</span>
+        <span class="ent-info-value"><i class="value-number">20</i> 个</span>
+      </div>
+      <div class="abstract-ent-info label-value">
+        <span class="ent-info-label">合作企业</span>
+        <span class="ent-info-value"><i class="value-number">20</i> 个</span>
+      </div>
+      <div class="abstract-ent-info more value-number highlight-text">
+        <span class="ent-info-label">查看详情</span>
+        <span class="ent-info-value">
+          <van-icon name="arrow" />
+        </span>
+      </div>
+    </div>
+  </section>
+</template>
+<script>
+import { Icon } from 'vant'
+
+export default {
+  name: 'AbstractEnt',
+  components: {
+    [Icon.name]: Icon
+  },
+  props: {
+    entType: {
+      type: String,
+      default: 'winner',
+      validator(value) {
+        return ['buyer', 'winner'].includes(value)
+      }
+    }
+  },
+  data() {
+    return {}
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.abstract-ent-container {
+  padding: 8px 6px;
+  border-radius: 8px;
+}
+.abstract-ent-title {
+  margin-left: 10px;
+  color: #171826;
+  font-size: 14px;
+  line-height: 20px;
+}
+.abstract-ent-info-list {
+  margin-top: 8px;
+}
+.abstract-ent-info {
+  font-size: 13px;
+  line-height: 20px;
+  .value-number {
+    color: $color_main;
+    font-size: 14px;
+  }
+  &.label-value {
+    .ent-info-label {
+      margin-right: 6px;
+      color: #9b9ca3;
+    }
+  }
+}
+</style>

+ 261 - 0
apps/mobile/src/views/article/components/ContentAbstract.vue

@@ -0,0 +1,261 @@
+<template>
+  <section class="abstract-container bg-white">
+    <!-- 超前项目 -->
+    <template v-if="futureProject">
+      <div class="abstract-line">
+        <div class="abstract-line-label">项目名称</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">
+            江西省长征医院医疗管理系统更新升级项目
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line-container">
+        <div class="abstract-line">
+          <div class="abstract-line-label">省份</div>
+          <div class="abstract-line-value">
+            <span class="abstract-line-value-text">江西</span>
+          </div>
+        </div>
+        <div class="abstract-line">
+          <div class="abstract-line-label">业主类型</div>
+          <div class="abstract-line-value">
+            <span class="abstract-line-value-text">信息技术</span>
+          </div>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">业主单位</div>
+        <div class="abstract-line-value">
+          <span class="highlight-text underline clickable">江西省长征医院</span>
+        </div>
+      </div>
+      <div class="abstract-line-container">
+        <div class="abstract-line">
+          <div class="abstract-line-label">总投资</div>
+          <div class="abstract-line-value">
+            <span class="abstract-line-value-text">1182万元</span>
+          </div>
+        </div>
+        <div class="abstract-line">
+          <div class="abstract-line-label">建设年份</div>
+          <div class="abstract-line-value">
+            <span class="abstract-line-value-text">xxx</span>
+          </div>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">建设地点</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">
+            江西省XXXXXXXXXXX嘻嘻嘻嘻嘻嘻嘻
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">审批机关</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">
+            江西省XXXXXXXXXXX嘻嘻嘻嘻嘻嘻嘻2
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">审批事项</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">
+            江西省XXXXXXXXXXX嘻嘻嘻嘻嘻嘻嘻2
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">批准文号</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">
+            江西省XXXXXXXXXXX嘻嘻嘻嘻嘻嘻嘻2
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line-container">
+        <div class="abstract-line">
+          <div class="abstract-line-label">审批时间</div>
+          <div class="abstract-line-value">
+            <span class="abstract-line-value-text">2024-2-2</span>
+          </div>
+        </div>
+        <div class="abstract-line">
+          <div class="abstract-line-label">审批结果</div>
+          <div class="abstract-line-value">
+            <span class="abstract-line-value-text">办结(通过)</span>
+          </div>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">建设内容</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">
+            江西省XXXXXXXXXXX嘻嘻嘻嘻嘻嘻嘻2
+          </span>
+        </div>
+      </div>
+    </template>
+    <!-- 非超前项目 -->
+    <template v-else>
+      <div class="abstract-line">
+        <div class="abstract-line-label">采购单位</div>
+        <div class="abstract-line-value">
+          <span class="highlight-text underline clickable">
+            122222213333333333322222222222
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">采购联系人 / 联系电话</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">xx / 13283800000</span>
+          <span class="abstract-line-value-actions clickable color-main">
+            <span class="j-icon icon-phone-blue"></span>
+            <span class="a-l-v-a-text">更多联系人</span>
+            <van-icon name="arrow" />
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">招标代理机构</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">
+            大通回族土族自治县住房和城XXXXX
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">代理联系人 / 联系电话</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">xx / 13283800000</span>
+          <span class="abstract-line-value-actions clickable color-main">
+            <span class="j-icon icon-phone-blue"></span>
+            <span class="a-l-v-a-text">更多联系人</span>
+            <van-icon name="arrow" />
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line-container">
+        <div class="abstract-line">
+          <div class="abstract-line-label">报名截止日期</div>
+          <div class="abstract-line-value">
+            <span class="abstract-line-value-text">2023-09-23</span>
+          </div>
+        </div>
+        <div class="abstract-line">
+          <div class="abstract-line-label">投标截止日期</div>
+          <div class="abstract-line-value">
+            <span class="abstract-line-value-text">2023-10-23</span>
+          </div>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">中标单位</div>
+        <div class="abstract-line-value">
+          <span class="highlight-text underline clickable">
+            122222213333333333322222222222
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">中标联系人 / 联系电话</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">xx / 13283800000</span>
+          <span class="abstract-line-value-actions clickable color-main">
+            <span class="j-icon icon-phone-blue"></span>
+            <span class="a-l-v-a-text">更多联系人</span>
+            <van-icon name="arrow" />
+          </span>
+        </div>
+      </div>
+      <div class="abstract-line">
+        <div class="abstract-line-label">中标金额(元)</div>
+        <div class="abstract-line-value">
+          <span class="abstract-line-value-text">1,314</span>
+        </div>
+      </div>
+    </template>
+    <div class="abstract-bottom-desc">
+      <span>*以上摘要信息由剑鱼标讯智能提取,仅供参考。如有误差,请</span>
+      <span class="highlight-text underline" @click="concatKf">联系客服</span>
+      <span>进行处理。</span>
+    </div>
+  </section>
+</template>
+<script>
+import { Icon } from 'vant'
+export default {
+  name: 'ContentAbstract',
+  components: {
+    [Icon.name]: Icon
+  },
+  props: {
+    futureProject: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {}
+  },
+  created() {},
+  methods: {
+    concatKf() {}
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.icon-phone-blue {
+  width: 16px;
+  height: 16px;
+}
+
+.abstract-line-container {
+  display: flex;
+  align-items: center;
+  .abstract-line {
+    flex: 1;
+  }
+}
+.abstract-line {
+  padding: 8px 16px;
+  font-size: 14px;
+  line-height: 20px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+  .abstract-line-label {
+    margin-bottom: 4px;
+    font-size: 13px;
+    color: #9b9ca3;
+  }
+  .abstract-line-value {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    color: #171826;
+  }
+  .abstract-line-value-actions {
+    display: flex;
+    align-items: center;
+    &.color-main {
+      color: $main;
+    }
+    .a-l-v-a-text {
+      margin: 0 8px;
+    }
+  }
+}
+.abstract-bottom-desc {
+  padding: 8px 16px;
+  color: #9b9ca3;
+  font-size: 12px;
+  line-height: 20px;
+  .underline {
+    white-space: nowrap;
+  }
+}
+</style>

+ 30 - 0
apps/mobile/src/views/article/components/ContentAbstractEntList.vue

@@ -0,0 +1,30 @@
+<template>
+  <section class="abstract-ent-list">
+    <AbstractEnt class="abstract-ent-item"></AbstractEnt>
+    <AbstractEnt class="abstract-ent-item"></AbstractEnt>
+    <AbstractEnt class="abstract-ent-item"></AbstractEnt>
+  </section>
+</template>
+<script>
+import AbstractEnt from '@/views/article/components/AbstractEnt.vue'
+export default {
+  name: 'ContentAbstractEntList',
+  components: {
+    AbstractEnt
+  },
+  data() {
+    return {}
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.abstract-ent-list {
+  padding: 8px 12px;
+  .abstract-ent-item:not(:last-of-type) {
+    margin-bottom: 8px;
+  }
+}
+</style>

+ 47 - 0
apps/mobile/src/views/article/components/ContentBusinessRecommend.vue

@@ -0,0 +1,47 @@
+<template>
+  <ContentModuleCard title="商机推荐" class="content-business-recommend">
+    <div class="business-list">
+      <RecommendProjectCard
+        class="business-list-item"
+        titleIcon="icon-flexible-selection"
+        title="超前项目推荐"
+      >
+        <span slot="header-actions"></span>
+      </RecommendProjectCard>
+      <RecommendProjectCard
+        class="business-list-item"
+        title="采购单位名称XXX的其他招标动态"
+      ></RecommendProjectCard>
+      <RecommendProjectCard
+        class="business-list-item"
+        title="中标单位名称XXX的其他中标动态"
+      ></RecommendProjectCard>
+    </div>
+  </ContentModuleCard>
+</template>
+<script>
+import { Icon } from 'vant'
+import ContentModuleCard from '@/views/article/components/ContentModuleCard.vue'
+import RecommendProjectCard from '@/views/article/components/RecommendProjectCard.vue'
+
+export default {
+  name: 'ContentCustomerRecommend',
+  components: {
+    [Icon.name]: Icon,
+    ContentModuleCard,
+    RecommendProjectCard
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.business-list {
+  .business-list-item {
+    &:not(:last-of-type) {
+      margin-bottom: 8px;
+    }
+  }
+}
+</style>

+ 89 - 0
apps/mobile/src/views/article/components/ContentCustomerRecommend.vue

@@ -0,0 +1,89 @@
+<template>
+  <ContentModuleCard title="客户推荐" class="content-customer-recommend">
+    <div class="customer-list">
+      <CustomerCell
+        v-for="(item, index) in entList"
+        :key="index"
+        :name="item.name"
+        :location="item.location"
+        :subInfoList="item.subInfoList"
+        v-visited:ent="item.id"
+        @click="toDetail(item)"
+      >
+      </CustomerCell>
+    </div>
+    <div class="show-more clickable bg-white" slot="footer">查看更多</div>
+  </ContentModuleCard>
+</template>
+<script>
+import { Icon } from 'vant'
+import { CustomerCell } from '@/ui'
+import ContentModuleCard from '@/views/article/components/ContentModuleCard.vue'
+
+export default {
+  name: 'ContentCustomerRecommend',
+  components: {
+    [Icon.name]: Icon,
+    ContentModuleCard,
+    CustomerCell
+  },
+  data() {
+    return {
+      entList: [
+        {
+          id: 'e-1111',
+          name: '中铁电气化局集团有限公司',
+          location: '北京 北京市',
+          subInfoList: [
+            {
+              label: '项目数量',
+              value: '8248'
+            },
+            {
+              label: '项目总金额',
+              value: '24.07亿元'
+            }
+          ]
+        },
+        {
+          id: 'e-1111',
+          name: '中铁电气化局集团有限公司',
+          location: '北京 北京市',
+          subInfoList: [
+            {
+              label: '项目数量',
+              value: '8248'
+            },
+            {
+              label: '项目总金额',
+              value: '24.07亿元'
+            }
+          ]
+        }
+      ]
+    }
+  },
+  methods: {
+    toDetail(item) {
+      console.log(item)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.customer-list {
+  &:not(:last-of-type) {
+    margin-bottom: 8px;
+  }
+}
+.show-more {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 48px;
+  font-size: 14px;
+  line-height: 20px;
+  color: $main;
+}
+</style>

+ 107 - 0
apps/mobile/src/views/article/components/ContentMainText.vue

@@ -0,0 +1,107 @@
+<template>
+  <ContentModuleCard title="正文" class="content-main-text-container">
+    <section class="content-main-text">
+      <p v-for="index in 20" :key="index">正文正文正文</p>
+    </section>
+    <section class="content-main-others-container">
+      <div class="others-header flex flex-(items-center justify-between)">
+        <div class="content-file-attachment-left flex flex-items-center">
+          <span class="j-icon icon-data-download"></span>
+          <span class="file-attachment-text">附件下载</span>
+          <span class="j-tag-item main round attachment-tag"
+            >本月剩余:8个</span
+          >
+        </div>
+        <div class="content-file-attachment-actions">
+          <span class="charge-button">立即充值</span>
+        </div>
+      </div>
+      <div class="file-attachment-list">
+        <div class="file-attachment-item highlight-text underline clickable">
+          颍东区职业教育中心二期、区委党校人防防化设备采购及安装项目清单、控制价.rar
+        </div>
+        <div class="file-attachment-item highlight-text underline clickable">
+          颍东区职业教育中心二期、区委党校人防防化设备采购及安装项目清单、控制价.rar
+        </div>
+        <div class="file-attachment-item highlight-text underline clickable">
+          颍东区职业教育中心二期、区委党校人防防化设备采购及安装项目清单、控制价.rar
+        </div>
+      </div>
+      <div class="others-footer">
+        <span class="highlight-text origin-link clickable">查看原文链接</span>
+        <button class="feedback-button clickable">意见反馈</button>
+      </div>
+    </section>
+  </ContentModuleCard>
+</template>
+<script>
+import ContentModuleCard from '@/views/article/components/ContentModuleCard.vue'
+export default {
+  name: 'ContentMainText',
+  components: {
+    ContentModuleCard
+  },
+  data() {
+    return {}
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .content-module-main {
+    padding: 12px 16px;
+    background-color: $white;
+  }
+}
+.icon-data-download {
+  width: 24px;
+  height: 24px;
+}
+.content-main-others-container {
+  margin-top: 10px;
+}
+.file-attachment-text {
+  margin: 0 12px;
+  font-size: 16px;
+  line-height: 24px;
+}
+.attachment-tag {
+  padding: 0 8px;
+  font-size: 11px;
+  line-height: 18px;
+  border-radius: 10px;
+}
+.charge-button {
+  padding: 3px 8px;
+  font-size: 12px;
+  line-height: 18px;
+  border-radius: 8px;
+  color: $white;
+  background-color: $main;
+}
+.file-attachment-list {
+  margin-top: 16px;
+  font-size: 15px;
+  line-height: 22px;
+  .file-attachment-item {
+    margin-bottom: 16px;
+  }
+}
+.others-footer {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 15px;
+  line-height: 22px;
+  .origin-link {
+    color: #05a6f3;
+  }
+  .feedback-button {
+    color: #9b9ca3;
+    background: transparent;
+  }
+}
+</style>

+ 48 - 0
apps/mobile/src/views/article/components/ContentModuleCard.vue

@@ -0,0 +1,48 @@
+<template>
+  <section class="content-module-card">
+    <header
+      class="content-module-header bg-white flex flex-(item-center justify-between)"
+    >
+      <h4 class="content-module-title">
+        <slot name="title">{{ title }}</slot>
+      </h4>
+      <div class="content-module-header-actions flex flex-(item-center)">
+        <slot name="header-actions"></slot>
+      </div>
+    </header>
+    <main class="content-module-main">
+      <slot name="default"></slot>
+    </main>
+    <footer class="content-module-footer">
+      <slot name="footer"></slot>
+    </footer>
+  </section>
+</template>
+<script>
+export default {
+  name: 'ContentModuleCard',
+  props: {
+    title: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {}
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.content-module-header {
+  padding: 8px 16px;
+  min-height: 36px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+}
+.content-module-title {
+  font-size: 14px;
+  line-height: 20px;
+}
+</style>

+ 86 - 0
apps/mobile/src/views/article/components/ContentProjectTimeline.vue

@@ -0,0 +1,86 @@
+<template>
+  <ContentModuleCard title="招标/采购进度" class="content-project-timeline">
+    <div class="action-right-container" slot="header-actions">
+      <span class="action-item clickable download-project-doc">
+        <AppIcon name="xiazaixiangmubaogao"></AppIcon>
+        <span class="action-text">下载项目报告</span>
+      </span>
+      <span class="action-item clickable follow-project">
+        <AppIcon name="jiankong" v-if="true" color="#9b9ca3"></AppIcon>
+        <AppIcon name="yijiankong" v-else color="#9b9ca3"></AppIcon>
+        <span class="action-text">监控</span>
+      </span>
+    </div>
+    <div class="bg-white">
+      <TimeLine
+        :markedSameId="articleId"
+        :stepList="stepList"
+        @itemClick="itemClick"
+      ></TimeLine>
+    </div>
+  </ContentModuleCard>
+</template>
+<script>
+import ContentModuleCard from '@/views/article/components/ContentModuleCard.vue'
+import { AppIcon, TimeLine } from '@/ui'
+
+export default {
+  name: 'ContentProjectTimeline',
+  components: {
+    AppIcon,
+    TimeLine,
+    ContentModuleCard
+  },
+  props: {
+    articleId: {
+      type: String,
+      default: '226'
+    }
+  },
+  data() {
+    return {
+      stepList: [
+        {
+          id: '226',
+          step_time: '2024-02-26',
+          // step_bidamount: 'moneyUnit(111)',
+          tags: [111],
+          content: '226-内容内容内容内容内容内容'
+        },
+        {
+          id: '225',
+          step_time: '2024-02-25',
+          tags: [111, 222, 333],
+          content: '225-内容内容内容内容内容内容'
+        },
+        {
+          id: '224',
+          step_time: '2024-02-24',
+          tags: [111, 222, 333],
+          content: '224-内容内容内容内容内容内容'
+        }
+      ]
+    }
+  },
+  created() {},
+  methods: {
+    itemClick(item) {
+      console.log(item)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.action-item {
+  color: $main;
+  font-size: 14px;
+  line-height: 20px;
+  &:not(:last-of-type) {
+    margin-right: 16px;
+  }
+  .action-text {
+    margin-left: 4px;
+  }
+}
+</style>

+ 65 - 0
apps/mobile/src/views/article/components/ContentTouBiaoService.vue

@@ -0,0 +1,65 @@
+<template>
+  <ContentModuleCard title="投标服务" class="content-toubiao-service">
+    <div class="service-card-list">
+      <ServiceIntroCard
+        class="service-card-item"
+        showHeader
+        title="超级订阅、大会员权益"
+        serviceNameText="查看原文链接"
+        serviceDescText="提供公告原始网站地址,便于用户核验信息真实性,了解项目采购更详细的信息。"
+        :plainButtonShow="true"
+        plainButtonText="了解详情"
+        :confirmButtonShow="true"
+        confirmButtonText="免费体验"
+      />
+      <ServiceIntroCard
+        class="service-card-item"
+        showHeader
+        title="超级订阅、大会员权益"
+        serviceNameText="查看原文链接"
+        serviceDescText="提供公告原始网站地址,便于用户核验信息真实性,了解项目采购更详细的信息。"
+        :plainButtonShow="true"
+        plainButtonText="了解详情"
+        :confirmButtonShow="true"
+        confirmButtonText="免费体验"
+      />
+      <ServiceIntroCard
+        class="service-card-item"
+        title="超级订阅、大会员权益"
+        serviceNameText="查看原文链接"
+        serviceDescText="提供公告原始网站地址,便于用户核验信息真实性,了解项目采购更详细的信息。"
+        :plainButtonShow="true"
+        plainButtonText="了解详情"
+        :confirmButtonShow="true"
+        confirmButtonText="免费体验"
+      />
+    </div>
+  </ContentModuleCard>
+</template>
+<script>
+import { Icon } from 'vant'
+import ContentModuleCard from '@/views/article/components/ContentModuleCard.vue'
+import ServiceIntroCard from '@/views/article/components/ServiceIntroCard.vue'
+
+export default {
+  name: 'ContentTouBiaoService',
+  components: {
+    [Icon.name]: Icon,
+    ContentModuleCard,
+    ServiceIntroCard
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.service-card-list {
+  padding: 8px 12px;
+  .service-card-item {
+    &:not(:last-of-type) {
+      margin-bottom: 8px;
+    }
+  }
+}
+</style>

+ 72 - 0
apps/mobile/src/views/article/components/DataExportBanner.vue

@@ -0,0 +1,72 @@
+<template>
+  <section class="data-export-banner-container clickable bg-white">
+    <div class="banner-left">
+      <span class="j-icon icon-data-download"></span>
+    </div>
+    <div class="banner-center">
+      <div class="banner-center-title">数据导出</div>
+      <p class="banner-center-subtitle">
+        最近5年招标采购数据均可导出下载,如需更多年份和行业字段您可申请数据定制
+      </p>
+    </div>
+    <div class="banner-right">
+      <van-icon name="arrow" />
+    </div>
+  </section>
+</template>
+<script>
+import { Icon } from 'vant'
+export default {
+  name: 'DataExportBanner',
+  components: {
+    [Icon.name]: Icon
+  },
+  props: {
+    entType: {
+      type: String,
+      default: 'winner',
+      validator(value) {
+        return ['buyer', 'winner'].includes(value)
+      }
+    }
+  },
+  data() {
+    return {}
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.data-export-banner-container {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 16px;
+  border-radius: 8px;
+}
+.banner-left {
+  .j-icon {
+    width: 24px;
+    height: 24px;
+  }
+}
+.banner-center {
+  margin: 0 12px;
+}
+.banner-center-title {
+  color: #171826;
+  font-size: 16px;
+  line-height: 24px;
+}
+.banner-center-subtitle {
+  margin-top: 2px;
+  color: #5f5e64;
+  font-size: 12px;
+  line-height: 18px;
+}
+.banner-right {
+  color: #c0c4cc;
+}
+</style>

+ 43 - 0
apps/mobile/src/views/article/components/MaskCard.vue

@@ -0,0 +1,43 @@
+<template>
+  <ContentModuleCard class="mask-card bg-white">
+    <div class="header-title" slot="title">
+      <AppIcon name="arrow" />
+      <span class="header-title-text"></span>
+    </div>
+    <div class="banner-center">
+      <div class="banner-center-title">数据导出</div>
+      <p class="banner-center-subtitle">
+        最近5年招标采购数据均可导出下载,如需更多年份和行业字段您可申请数据定制
+      </p>
+    </div>
+    <div class="banner-right">
+      <AppIcon name="arrow" />
+    </div>
+  </ContentModuleCard>
+</template>
+<script>
+import ContentModuleCard from '@/views/article/components/ContentModuleCard.vue'
+import { AppIcon } from '@/ui'
+export default {
+  name: 'MaskCard',
+  components: {
+    ContentModuleCard,
+    AppIcon
+  },
+  props: {
+    dataList: {
+      type: Array,
+      default() {
+        return []
+      }
+    }
+  },
+  data() {
+    return {}
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 43 - 0
apps/mobile/src/views/article/components/NpsCard.vue

@@ -0,0 +1,43 @@
+<template>
+  <ContentModuleCard class="info-list-card bg-white">
+    <div class="header-title" slot="title">
+      <AppIcon name="arrow" />
+      <span class="header-title-text"></span>
+    </div>
+    <div class="banner-center">
+      <div class="banner-center-title">数据导出</div>
+      <p class="banner-center-subtitle">
+        最近5年招标采购数据均可导出下载,如需更多年份和行业字段您可申请数据定制
+      </p>
+    </div>
+    <div class="banner-right">
+      <AppIcon name="arrow" />
+    </div>
+  </ContentModuleCard>
+</template>
+<script>
+import ContentModuleCard from '@/views/article/components/ContentModuleCard.vue'
+import { AppIcon } from '@/ui'
+export default {
+  name: 'NpsCard',
+  components: {
+    ContentModuleCard,
+    AppIcon
+  },
+  props: {
+    dataList: {
+      type: Array,
+      default() {
+        return []
+      }
+    }
+  },
+  data() {
+    return {}
+  },
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 117 - 0
apps/mobile/src/views/article/components/RecommendProjectCard.vue

@@ -0,0 +1,117 @@
+<template>
+  <ContentModuleCard class="recommend-project-card bg-white">
+    <div class="header-title" slot="title">
+      <span class="j-icon" :class="[titleIcon]"></span>
+      <span class="header-title-text">{{ title }}</span>
+      <span class="highlight-text total-number">{{ totalNumber }}</span>
+    </div>
+    <template #header-actions>
+      <slot name="header-actions">
+        <div class="action-item">
+          <AppIcon name="jiankong" v-if="true" color="#9b9ca3"></AppIcon>
+          <AppIcon name="yijiankong" v-else color="#9b9ca3"></AppIcon>
+          <span class="action-text">监控</span>
+        </div>
+      </slot>
+    </template>
+    <div class="info-list-main">
+      <ProjectCell
+        v-for="(item, index) in list"
+        :key="index"
+        :title="item.title"
+        :keys="['信息']"
+        :tags="item.tags"
+        :time="item.time"
+        v-visited:issued="item.id"
+        @click="toDetail(item)"
+      ></ProjectCell>
+    </div>
+    <div class="show-more clickable bg-white" slot="footer">查看更多</div>
+  </ContentModuleCard>
+</template>
+<script>
+import ContentModuleCard from '@/views/article/components/ContentModuleCard.vue'
+import { AppIcon, ProjectCell } from '@/ui'
+console.log()
+export default {
+  name: 'RecommendProjectCard',
+  components: {
+    ContentModuleCard,
+    ProjectCell,
+    AppIcon
+  },
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    titleIcon: {
+      type: String,
+      default: 'icon-ent-info'
+    },
+    totalNumber: {
+      type: [Number, String],
+      default: 0
+    }
+  },
+  data() {
+    return {
+      list: [
+        {
+          id: '11111',
+          tags: ['河南-郑州市-金水区', '建筑工程', '预告'],
+          title:
+            '淮安市高级职业技术学校台式电脑采购项目淮安市高级职业技术学校台式电脑采购项目',
+          time: 1709366382763
+        },
+        {
+          id: '2222',
+          tags: ['河南-郑州市-金水区', '建筑工程', '预告'],
+          title:
+            '淮安市高级职业技术学校台式电脑采购项目淮安市高级职业技术学校台式电脑采购项目',
+          time: 1709366382763
+        },
+        {
+          id: '3333',
+          tags: ['河南-郑州市-金水区', '建筑工程', '预告'],
+          title:
+            '淮安市高级职业技术学校台式电脑采购项目淮安市高级职业技术学校台式电脑采购项目',
+          time: 1709366382763
+        }
+      ]
+    }
+  },
+  created() {},
+  methods: {
+    toDetail(item) {
+      console.log(item)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.header-title {
+  display: flex;
+  align-items: center;
+}
+.header-title-text {
+  margin: 0 8px;
+}
+.total-number {
+  font-weight: 400;
+}
+.action-item {
+  display: flex;
+  align-items: center;
+}
+.show-more {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 48px;
+  font-size: 14px;
+  line-height: 20px;
+  color: $main;
+}
+</style>

+ 145 - 0
apps/mobile/src/views/article/components/ServiceIntroCard.vue

@@ -0,0 +1,145 @@
+<template>
+  <section class="service-intro-card bg-white">
+    <header
+      v-if="showHeader"
+      class="service-intro-header bg-white flex flex-(item-center justify-between)"
+    >
+      <h5 class="service-intro-title">
+        <slot name="title">{{ title }}</slot>
+      </h5>
+      <div class="service-intro-header-actions">
+        <slot name="header-actions">
+          <span class="header-action-item">
+            <span class="header-action-item-text">查看服务介绍</span>
+            <van-icon name="arrow" />
+          </span>
+        </slot>
+      </div>
+    </header>
+    <main
+      class="service-intro-main flex flex-(col items-center justify-center)"
+    >
+      <slot name="default">
+        <p class="service-name-text">{{ serviceNameText }}</p>
+        <p class="service-desc-text">{{ serviceDescText }}</p>
+        <div class="action-group">
+          <van-button
+            class="action-button plain"
+            type="primary"
+            plain
+            @click="leftButtonClick"
+            v-if="plainButtonShow"
+          >
+            {{ plainButtonText }}
+          </van-button>
+          <van-button
+            class="action-button"
+            type="primary"
+            @click="rightButtonClick"
+            v-if="confirmButtonShow"
+          >
+            {{ confirmButtonText }}
+          </van-button>
+        </div>
+      </slot>
+    </main>
+  </section>
+</template>
+<script>
+import { Icon, Button } from 'vant'
+export default {
+  name: 'ServiceIntroCard',
+  components: {
+    [Icon.name]: Icon,
+    [Button.name]: Button
+  },
+  props: {
+    title: {
+      type: String,
+      default: '标题'
+    },
+    showHeader: {
+      type: Boolean,
+      default: false
+    },
+    serviceNameText: {
+      type: String,
+      default: '权限名称'
+    },
+    serviceDescText: {
+      type: String,
+      default: '权限介绍'
+    },
+    plainButtonShow: {
+      type: Boolean,
+      default: false
+    },
+    plainButtonText: {
+      type: String,
+      default: '了解详情'
+    },
+    confirmButtonShow: {
+      type: Boolean,
+      default: true
+    },
+    confirmButtonText: {
+      type: String,
+      default: '免费体验'
+    }
+  },
+  created() {},
+  methods: {
+    leftButtonClick() {
+      this.$emit('leftButtonClick')
+    },
+    rightButtonClick() {
+      this.$emit('rightButtonClick')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.service-intro-card {
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(0, 0, 0, 0.05);
+  box-shadow: 0px 2px 8px 0px rgba(54, 147, 179, 0.05);
+}
+.service-intro-header {
+  padding: 8px 16px;
+  min-height: 36px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+  background: linear-gradient(270deg, #f1d090 0%, #fae7ca 100%);
+}
+.service-intro-main {
+  padding: 8px 24px 16px;
+  background: linear-gradient(180deg, #fff 0%, #f4f4f4 100%);
+}
+.service-intro-title {
+  font-size: 14px;
+  line-height: 20px;
+  font-weight: normal;
+}
+.service-name-text {
+  font-size: 18px;
+  font-weight: 700;
+  line-height: 26px;
+}
+.service-desc-text {
+  margin: 8px 0;
+  color: #5f5e64;
+  font-size: 14px;
+  line-height: 20px;
+  text-align: center;
+}
+.action-group {
+  .action-button {
+    height: 32px;
+    font-size: 16px;
+    &:not(:first-of-type) {
+      margin-left: 24px;
+    }
+  }
+}
+</style>

+ 346 - 0
apps/mobile/src/views/article/content.vue

@@ -0,0 +1,346 @@
+<template>
+  <div class="article-content">
+    <div class="j-container page-container">
+      <div class="j-main article-content-main">
+        <div class="content-title-container bg-white">
+          <span class="j-tag-item border red fixed-header-top-left">
+            业主委托项目
+          </span>
+          <h2
+            class="article-title"
+            :class="{ 'van-multi-ellipsis--l2': false }"
+          >
+            阜阳市颍东区职业教育中心二期、区委党校人防防化设备采购及安装项目补充公告目补充公告
+          </h2>
+          <div class="tag-list">
+            <span class="j-tag-item border gray round">河南-郑州市-金水区</span>
+            <span class="j-tag-item border gray round">招标</span>
+            <span class="j-tag-item border gray round">建筑工程</span>
+            <span class="j-tag-item border gray round">265.63万元</span>
+          </div>
+          <div class="sub-info-line">
+            <span class="info-publish-time">12小时前</span>
+            <span class="info-canbiao-persons">参标人:张xx等</span>
+          </div>
+        </div>
+        <div class="content-top-guide-container">
+          <p class="content-top-guide-content">
+            该公告由业主方/采购单位直接发布,急寻供应商,立即报名直接联系业主方/采购单位参与投标采购。
+          </p>
+          <button class="content-top-guide-action-button">立即报名</button>
+        </div>
+        <van-tabs
+          v-model="pageInfo.tabActive"
+          :offset-top="offsetTop"
+          scrollspy
+          sticky
+        >
+          <!-- 摘要 -->
+          <van-tab title="摘要" key="abstract">
+            <div class="tab-module">
+              <ContentAbstract class="content-abstract-module" />
+              <ContentAbstractEntList
+                class="content-abstract-ent-list-module"
+              />
+              <section class="data-export-banner-module">
+                <DataExportBanner />
+              </section>
+            </div>
+          </van-tab>
+          <!-- 正文 -->
+          <van-tab title="正文" key="mainText">
+            <ContentMainText class="tab-module" />
+          </van-tab>
+          <!-- 进度 -->
+          <van-tab title="进度" key="timeline">
+            <ContentProjectTimeline class="tab-module" />
+          </van-tab>
+          <!-- 投标服务 -->
+          <van-tab title="投标服务" key="tbService">
+            <ContentTouBiaoService class="tab-module" />
+          </van-tab>
+          <!-- 商机推荐 -->
+          <van-tab title="商机推荐" key="businessRecommend">
+            <ContentBusinessRecommend class="tab-module" />
+          </van-tab>
+          <!-- 客户推荐 -->
+          <van-tab title="客户推荐" key="customerRecommend">
+            <ContentCustomerRecommend class="tab-module" />
+          </van-tab>
+        </van-tabs>
+      </div>
+      <div class="j-footer article-content-footer">
+        footer
+        <!-- <van-tabbar v-model="active">
+          <AppIcon name="youbian" color="#c0c4cc" size="14"></AppIcon>
+          <van-tabbar-item icon="home-o">标签</van-tabbar-item>
+          <van-tabbar-item icon="search">标签</van-tabbar-item>
+          <van-tabbar-item icon="friends-o">标签</van-tabbar-item>
+          <van-tabbar-item icon="setting-o">标签</van-tabbar-item>
+        </van-tabbar> -->
+      </div>
+    </div>
+    <appShareSheet
+      v-model="shareShow"
+      @share="doShareAjax"
+      popup-title="分享有礼"
+      :share-title="shareConf.title"
+      :share-content="shareConf.content"
+      :share-link="shareConf.link"
+    >
+      <div class="share-subtitle-container" slot="subtitle">
+        <span class="share-subtitle-left j-icon icon-points"></span>
+        <span class="share-subtitle-content">
+          分享招标信息及文档,好友访问立得剑鱼币
+        </span>
+        <a href="/page_points_mobile/share" class="share-subtitle-right">
+          了解详情
+        </a>
+      </div>
+    </appShareSheet>
+    <wxShareGuide></wxShareGuide>
+  </div>
+</template>
+<script>
+import { Tabs, Tab, Icon } from 'vant'
+import { mixinHeader } from '@/utils/mixins/header'
+import { appWxShareMixin } from '@/utils/mixins/modules/app-wx-share'
+import ContentAbstract from '@/views/article/components/ContentAbstract.vue'
+import DataExportBanner from '@/views/article/components/DataExportBanner.vue'
+import ContentAbstractEntList from '@/views/article/components/ContentAbstractEntList.vue'
+import ContentMainText from '@/views/article/components/ContentMainText.vue'
+import ContentProjectTimeline from '@/views/article/components/ContentProjectTimeline.vue'
+import ContentTouBiaoService from '@/views/article/components/ContentTouBiaoService.vue'
+import ContentBusinessRecommend from '@/views/article/components/ContentBusinessRecommend.vue'
+import ContentCustomerRecommend from '@/views/article/components/ContentCustomerRecommend.vue'
+
+export default {
+  name: 'ArticleContent',
+  mixins: [mixinHeader, appWxShareMixin],
+  components: {
+    [Tabs.name]: Tabs,
+    [Tab.name]: Tab,
+    [Icon.name]: Icon,
+    ContentAbstract,
+    DataExportBanner,
+    ContentMainText,
+    ContentAbstractEntList,
+    ContentProjectTimeline,
+    ContentTouBiaoService,
+    ContentBusinessRecommend,
+    ContentCustomerRecommend
+  },
+  data() {
+    return {
+      pageLayoutConf: {
+        actionRightStyle: {
+          display: 'flex',
+          flexDirection: 'column',
+          alignItems: 'center',
+          color: '#171826',
+          fontSize: '10px',
+          lineHeight: '18px'
+        }
+      },
+      shareConf: {
+        // execShare
+        title: '剑鱼双11,百万补贴限量抢',
+        content:
+          '快来和我一起参与吧!剑鱼双11,百万补贴限量抢,快速获取商机信息',
+        pathname: '/weixin/frontPage/activity/sess/task-20221111',
+        origin: location.origin,
+        link: ''
+      },
+      offsetTop: 0,
+      // tabList: [
+      //   {
+      //     label: '摘要',
+      //     value: 'abstract'
+      //   },
+      //   {
+      //     label: '正文',
+      //     value: 'mainText'
+      //   },
+      //   {
+      //     label: '进度',
+      //     value: 'timeline'
+      //   },
+      //   {
+      //     label: '投标服务',
+      //     value: 'service'
+      //   },
+      //   {
+      //     label: '商机推荐',
+      //     value: 'businessRecommend'
+      //   },
+      //   {
+      //     label: '客户推荐',
+      //     value: 'customerRecommend'
+      //   }
+      // ],
+      pageInfo: {
+        tabActive: ''
+      }
+    }
+  },
+  created() {
+    this.appHeaderActions()
+  },
+  mounted() {
+    this.getStickyOffset()
+  },
+  methods: {
+    appHeaderActions() {
+      const { $envs } = this
+      if ($envs.inWX) return
+      const actionRightConf = {
+        actionRightText:
+          '<div class="j-icon icon-points" style="width:24px;height:24px"></div><p>分享有礼</p>',
+        onClickRight: this.clickRight
+      }
+      for (const key in actionRightConf) {
+        this.$set(this.pageLayoutConf, key, actionRightConf[key])
+      }
+      this.mergePageConf()
+    },
+    getStickyOffset() {
+      const header = document.querySelector('.common-app-header')
+      if (header) {
+        const offsetTop = header?.clientHeight || 0
+        if (offsetTop > 0) {
+          this.offsetTop = offsetTop - 1
+        }
+      }
+    },
+    clickRight() {
+      this.doShare()
+    },
+    doShare() {
+      this.shareShow = true
+    },
+    doShareAjax() {}
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .van-tabs__wrap {
+    padding-bottom: 4px;
+    border: 1px solid rgba(0, 0, 0, 0.05);
+  }
+  .van-tabs__line {
+    width: 24px;
+  }
+  .van-tab {
+    padding: 0 8px;
+    color: #5f5e64;
+  }
+
+  .tab-module.content-module-card {
+    margin-top: 8px;
+  }
+}
+
+.page-container {
+  height: calc(100% - 1px - 1px);
+}
+
+.fixed-header-top-left {
+  position: absolute;
+  left: -1px;
+  top: 1px;
+}
+
+.content-title-container {
+  position: relative;
+  padding: 24px 16px 12px;
+}
+
+.article-title {
+  font-size: 18px;
+  font-weight: 400;
+  line-height: 26px;
+  color: #171826;
+}
+
+.tag-list {
+  margin: 8px 0 4px;
+  display: flex;
+  .j-tag-item {
+    margin-bottom: 4px;
+    &:not(:last-of-type) {
+      margin-right: 4px;
+    }
+  }
+}
+
+.sub-info-line {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 12px;
+  line-height: 18px;
+  color: #9b9ca3;
+}
+.info-canbiao-persons {
+  font-size: 14px;
+  left: 20px;
+  color: #5f5e64;
+}
+
+.content-top-guide-container {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 6px 16px;
+  font-size: 12px;
+  line-height: 18px;
+  color: $main;
+  background-color: #eaf8fa;
+  .content-top-guide-content {
+    flex: 1;
+  }
+  .content-top-guide-action-button {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0 10px;
+    font-size: 13px;
+    line-height: 24px;
+    height: 28px;
+    color: #fff;
+    border-radius: 30px 30px 30px 0;
+    background: linear-gradient(
+      180deg,
+      #9cebf5 7.14%,
+      #47dfe9 40.18%,
+      #20c0d6 70.54%,
+      #70e4ec 92.86%
+    );
+  }
+}
+
+.icon-phone-blue {
+  width: 16px;
+  height: 16px;
+}
+
+.data-export-banner-module {
+  padding: 0 12px;
+}
+
+.share-subtitle-container {
+  display: flex;
+  align-items: center;
+  white-space: nowrap;
+  font-size: 13px;
+  padding: 0 16px;
+  .share-subtitle-content {
+    margin: 0 6px;
+  }
+  .share-subtitle-right {
+    color: $main;
+  }
+}
+</style>