li.shaoyi před 3 roky
rodič
revize
a58b6706f9

+ 11 - 0
package-lock.json

@@ -15,6 +15,7 @@
         "default-passive-events": "^2.0.0",
         "echarts": "^5.3.2",
         "element-plus": "^2.2.8",
+        "html5-qrcode": "^2.2.5",
         "long": "^5.2.0",
         "moment": "^2.29.3",
         "protobufjs": "^6.11.2",
@@ -7233,6 +7234,11 @@
         "node": ">=6"
       }
     },
+    "node_modules/html5-qrcode": {
+      "version": "2.2.5",
+      "resolved": "https://registry.npmjs.org/html5-qrcode/-/html5-qrcode-2.2.5.tgz",
+      "integrity": "sha512-qwjKPsSjih96efezZ0qvpzW5Tyf/qmKKFaVR862lXdiigH0j3bDB4ne8yKjmoLz+eE/iS6q+JQQ0v2udIZ97cg=="
+    },
     "node_modules/htmlparser2": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -18044,6 +18050,11 @@
         }
       }
     },
+    "html5-qrcode": {
+      "version": "2.2.5",
+      "resolved": "https://registry.npmjs.org/html5-qrcode/-/html5-qrcode-2.2.5.tgz",
+      "integrity": "sha512-qwjKPsSjih96efezZ0qvpzW5Tyf/qmKKFaVR862lXdiigH0j3bDB4ne8yKjmoLz+eE/iS6q+JQQ0v2udIZ97cg=="
+    },
     "htmlparser2": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "default-passive-events": "^2.0.0",
     "echarts": "^5.3.2",
     "element-plus": "^2.2.8",
+    "html5-qrcode": "^2.2.5",
     "long": "^5.2.0",
     "moment": "^2.29.3",
     "protobufjs": "^6.11.2",

+ 2 - 2
public/manifest.json

@@ -5,9 +5,9 @@
     "name" : "铁合金掌上行",
     /*应用名称,程序桌面图标名称*/
     "version" : {
-        "name" : "1.0.1",
+        "name" : "1.0.2",
         /*应用版本名称*/
-        "code" : 100001
+        "code" : 100002
     },
     "description" : "",
     /*应用描述信息*/

+ 11 - 0
src/filters/index.ts

@@ -110,6 +110,17 @@ export function formatDate(date: string, format = 'YYYY-MM-DD HH:mm:ss') {
     return moment(date).format(format)
 }
 
+export function formatText(text: string) {
+    const { uploadUrl } = service.config
+    const html = text.replace(/[\n\r]/g, '<br />')
+
+    //return html.replace(/(<img[^>]*src=['"])(?:(?!(https|http)))([^>]*>)/g, `$1${uploadUrl}$2/$3`)
+
+    return html.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, (match, capture) => {
+        return `<img src="${uploadUrl.replace('upload', '') + capture.replace('./', '')}"  alt=""/>`
+    })
+}
+
 /**
  * Des密钥
  * 手机发送验证码时加密方法

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
src/packages/mobile/assets/iconfont/iconfont.js


binární
src/packages/mobile/assets/images/guide-1.png


binární
src/packages/mobile/assets/images/guide-2.png


binární
src/packages/mobile/assets/images/hongbao.png


+ 10 - 0
src/packages/mobile/components/base/qrcode-scan/index.less

@@ -0,0 +1,10 @@
+.app-qrcode-scan {
+    &__camera {
+        position: fixed;
+        bottom  : 0;
+        left    : 0;
+        z-index : 1000;
+        width   : 100vw;
+        height  : 30vh;
+    }
+}

+ 79 - 0
src/packages/mobile/components/base/qrcode-scan/index.vue

@@ -0,0 +1,79 @@
+<template>
+    <div class="app-qrcode-scan">
+        <div @click="scan">
+            <slot>
+                <Button type="primary">扫一扫</Button>
+            </slot>
+        </div>
+        <div id="camera" class="app-qrcode-scan__camera"></div>
+    </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, shallowRef } from 'vue'
+import { Button, Toast } from 'vant'
+import { Html5Qrcode } from 'html5-qrcode'
+import plus from '@/utils/h5plus'
+
+const emit = defineEmits(['success'])
+const html5QrCode = shallowRef<Html5Qrcode>()
+const showCamera = shallowRef(false)
+
+const scan = () => {
+    plus.onPlusReady((plus) => {
+        const barcode = plus.barcode.create('barcode', [plus.barcode.QR], {
+            background: '#fff',
+            frameColor: '#07c160',
+            scanbarColor: '#07c160',
+            top: '100px',
+            left: '0px',
+            width: '100%',
+            height: '580px',
+            position: 'static'
+        });
+        barcode.onmarked = (type: any, result: any) => {
+            console.log('扫码成功', type, result)
+            emit('success', result)
+            barcode.close()
+        }
+        barcode.onerror = (err: any) => {
+            console.log('扫码失败', err)
+            Toast.fail('扫码失败')
+        }
+        plus.webview.currentWebview().append(barcode);//必要的
+    })
+}
+
+const onScan = () => {
+    Html5Qrcode.getCameras().then(devices => {
+        if (devices && devices.length) {
+            html5QrCode.value?.start(
+                {
+                    facingMode: 'environment'
+                },
+                {
+                    fps: 10,
+                    qrbox: { width: 250, height: 250 }
+                },
+                (decodedText) => {
+                    emit('success', decodedText)
+                },
+                (err) => {
+                    console.log('html5QrCode', err)
+                }).catch(() => {
+                    Toast.fail('获取设备信息失败')
+                })
+        }
+    }).catch(() => {
+        Toast.fail('您需要授予相机访问权限')
+    })
+}
+
+onMounted(() => {
+    html5QrCode.value = new Html5Qrcode('camera')
+})
+</script>
+
+<style lang="less" scoped>
+@import './index.less';
+</style>

+ 12 - 4
src/packages/mobile/components/base/tabbar/index.vue

@@ -4,10 +4,18 @@
       <template v-for="(item, index) in dataList" :key="index">
         <div :class="['app-tabbar__item', selectedIndex === index && 'is-active']" @click="onChange(index)">
           <slot :item="item" :index="index">
-            <app-iconfont label-direction="bottom" :icon="item.icon" :active-icon="item.activeIcon"
-              :active="selectedIndex === index">
-              {{ item.label }}
-            </app-iconfont>
+            <!--判断是否图片图标-->
+            <template v-if="item.iconType === 'image'">
+              <div :class="['g-icon', selectedIndex === index && 'active']">
+                <img :src="item.icon" v-if="selectedIndex === index" />
+                <img :src="item.activeIcon" v-else />
+                <span>{{ item.label }}</span>
+              </div>
+            </template>
+            <template v-else>
+              <app-iconfont label-direction="bottom" :icon="item.icon" :active-icon="item.activeIcon"
+                :active="selectedIndex === index">{{ item.label }}</app-iconfont>
+            </template>
           </slot>
         </div>
       </template>

+ 1 - 0
src/packages/mobile/components/base/tabbar/interface.ts

@@ -1,6 +1,7 @@
 export interface Tabbar {
     name: string;
     label: string;
+    iconType?: 'iconfont' | 'image';
     icon: string;
     activeIcon?: string;
 }

+ 8 - 31
src/packages/mobile/views/boot/index.less

@@ -1,39 +1,16 @@
 .boot {
     height         : 100vh;
-    background     : #fff url('@mobile/assets/images/boot-1080p.png') no-repeat center top;
-    background-size: 100% auto;
+    background     : #fff url('@mobile/assets/images/boot-720p.png') no-repeat center top;
+    background-size: 100%;
     overflow       : hidden;
 
-    &-skip {
-        position       : fixed;
-        top            : 0;
-        display        : flex;
-        justify-content: space-between;
-        align-items    : center;
-        width          : 100%;
-        padding        : .32rem;
+    &__guide {
+        width : 100%;
+        height: 100%;
 
-        &:before {
-            content  : '广告时间';
-            font-size: .28rem;
+        img {
+            width : 100%;
+            height: 100%;
         }
-
-        .van-circle {
-            width          : .6rem;
-            height         : .6rem;
-            display        : flex;
-            flex-direction : column;
-            justify-content: center;
-            align-items    : center;
-            font-size      : .24rem;
-            color          : #ccc;
-        }
-    }
-
-    &-wrapper {
-        display        : flex;
-        justify-content: center;
-        align-items    : center;
-        height         : 100%;
     }
 }

+ 19 - 14
src/packages/mobile/views/boot/index.vue

@@ -1,24 +1,23 @@
 <template>
   <div class="boot">
-    <template v-if="false">
-      <div class="boot-skip" v-if="state.second > 0">
-        <Circle v-model:current-rate="state.currentRate" :rate="state.rate" :speed="100" :stroke-width="60"
-          @click="skip">
-          <span>{{ state.second }}</span>
-        </Circle>
-      </div>
-      <div class="boot-wrapper">
-        <Loading v-if="state.loading">正在加载...</Loading>
-        <span v-else>加载完毕!</span>
-      </div>
-    </template>
+    <Swipe class="boot__guide" :loop="false" v-if="guidePage">
+      <SwipeItem>
+        <img src="@mobile/assets/images/boot-720p.png" />
+      </SwipeItem>
+      <SwipeItem>
+        <img src="@mobile/assets/images/guide-1.png" />
+      </SwipeItem>
+      <SwipeItem>
+        <img src="@mobile/assets/images/guide-2.png" @click="skip" />
+      </SwipeItem>
+    </Swipe>
   </div>
 </template>
 
 <script lang="ts" setup>
 import { reactive } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
-import { Circle, Loading } from 'vant'
+import { Swipe, SwipeItem } from 'vant'
 import { initBaseData } from '@/business/common'
 import service from '@/services'
 import socket from '@/services/socket'
@@ -26,6 +25,7 @@ import plus from '@/utils/h5plus'
 
 const route = useRoute()
 const router = useRouter()
+const guidePage = localStorage.getItem('thj_app_guide') === 'false' ? false : true // 是否显示引导页
 const countdown = 1  // 倒计时秒数
 
 const state = reactive({
@@ -40,7 +40,12 @@ const timer = window.setInterval(() => {
   state.second--
   state.rate = (100 / countdown) * state.second
   if (state.second <= 0) {
-    skip()
+    // 判断是否首次打开应用
+    if (guidePage) {
+      localStorage.setItem('thj_app_guide', 'false')
+    } else {
+      skip()
+    }
   }
 }, 1000)
 

+ 1 - 1
src/packages/mobile/views/home/components/main/index.less

@@ -1,7 +1,7 @@
 @import '@mobile/assets/themes/base/mixin.less';
 
 .home-main {
-    .app-view__wrapper {
+    .app-view__body {
         display       : flex;
         flex-direction: column;
     }

+ 1 - 1
src/packages/mobile/views/home/components/main/index.vue

@@ -28,7 +28,7 @@
             <app-iconfont icon="icon-woderenwu" label-direction="bottom">我的任务</app-iconfont>
           </li>
           <li @click="routerTo('rules-myrz')">
-            <app-iconfont icon="icon-a-bianzu17" label-direction="bottom">贸易融资</app-iconfont>
+            <app-iconfont icon="icon-maoyirongzi" label-direction="bottom">贸易融资</app-iconfont>
           </li>
           <li @click="routerTo('rules-ccwl')">
             <app-iconfont icon="icon-a-bianzu12" label-direction="bottom">仓储物流</app-iconfont>

+ 1 - 1
src/packages/mobile/views/mine/generalize/index.vue

@@ -15,7 +15,7 @@
                 </div>
                 <div class="block-right">
                     <h4>推广编号</h4>
-                    <span @click="showQRCode = true">{{ userAccount.refernum }}</span>
+                    <span @click="showQRCode = false">{{ userAccount.refernum }}</span>
                 </div>
             </div>
         </div>

+ 33 - 0
src/packages/mobile/views/news/details/index.less

@@ -0,0 +1,33 @@
+.new-details {
+    &__container {
+        background-color: #fff;
+        padding         : .32rem;
+
+        >h1 {
+            font-size  : .35rem;
+            font-weight: bold;
+        }
+
+        >h4 {
+            font-size: .24rem;
+            color    : #999;
+            padding  : .32rem 0;
+
+            span {
+                &:first-child {
+                    color       : dodgerblue;
+                    margin-right: .12rem;
+                }
+            }
+        }
+
+        >p {
+            font-size  : .32rem;
+            line-height: .48rem;
+
+            img {
+                max-width: 100%;
+            }
+        }
+    }
+}

+ 16 - 4
src/packages/mobile/views/news/details/index.vue

@@ -1,17 +1,29 @@
 <template>
-    <app-view>
+    <app-view class="new-details">
         <template #header>
             <app-navbar title="资讯详情" />
         </template>
-        <div v-html="details?.context"></div>
+        <section class="new-details__container" v-if="details">
+            <h1>{{ details.title }}</h1>
+            <h4>
+                <span>{{ details.author }}</span>
+                <span>{{ details.creaedate }}</span>
+            </h4>
+            <p v-html="formatText(details.context)"></p>
+        </section>
     </app-view>
 </template>
 
 <script lang="ts" setup>
 import { shallowRef } from 'vue'
 import { useNavigation } from '@/hooks/navigation'
+import { formatText } from '@/filters'
 
 const { route } = useNavigation()
 const params = route.params.details
-const details = shallowRef<Model.SiteColumnDetailRsp>(JSON.parse(params.toString()))
-</script>
+const details = shallowRef<Model.SiteColumnDetailRsp>(JSON.parse(params?.toString()))
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 39 - 0
src/packages/mobile/views/user/register/index.less

@@ -9,4 +9,43 @@
             }
         }
     }
+
+    &__reward {
+        display        : flex;
+        justify-content: center;
+        align-items    : center;
+
+        .wrapper {
+            display        : flex;
+            flex-direction : column;
+            align-items    : center;
+            justify-content: center;
+            width          : 100%;
+            height         : 100%;
+            color          : #ffff00;
+            background     : url('@mobile/assets/images/hongbao.png') no-repeat center center;
+            background-size: 100%;
+            padding-top    : 2.5rem;
+
+            .title {
+                font-size  : .4rem;
+                font-weight: bold;
+            }
+
+            .details {
+                font-size  : .72rem;
+                font-weight: bold;
+            }
+
+            .buttons {
+                width     : 100%;
+                text-align: center;
+                margin-top: 1.8rem;
+
+                .van-button {
+                    width: 50%;
+                }
+            }
+        }
+    }
 }

+ 57 - 8
src/packages/mobile/views/user/register/index.vue

@@ -21,7 +21,9 @@
         <Field v-model="formData.refernum" name="refernum" label="推荐码" placeholder="必填" autocomplete="off"
           :rules="formRules.refernum">
           <!-- <template #button>
-            <Button size="small" type="primary">扫码</Button>
+            <app-qrcode-scan @success="onScanSuccess">
+              <Button size="small" type="primary">扫码</Button>
+            </app-qrcode-scan>
           </template> -->
         </Field>
       </CellGroup>
@@ -41,24 +43,37 @@
       <div class="g-form__footer">
         <Button type="primary" @click="formRef?.submit" round block>免费注册</Button>
       </div>
+      <Overlay class="register__reward" :show="showReward">
+        <div class="wrapper">
+          <div class="title">注册成功!</div>
+          <div class="title">恭喜获得红包</div>
+          <div class="details">{{ redEnvelope }}</div>
+          <div class="buttons">
+            <Button @click="router.back()" round>好的</Button>
+          </div>
+        </div>
+      </Overlay>
     </template>
   </app-view>
 </template>
 
 <script lang="ts" setup>
 import { reactive, ref, computed } from 'vue'
-import { CellGroup, Cell, Button, Field, Form, FormInstance, Checkbox, Toast, FieldRule } from 'vant'
+import { CellGroup, Cell, Button, Field, Form, FormInstance, Checkbox, Toast, FieldRule, Overlay } from 'vant'
 import { useCountDown } from '@vant/use'
 import { fullloading, dialog } from '@/utils/vant'
 import { validateRules } from '@/constants/regex'
 import { useNavigation } from '@/hooks/navigation'
-import { userRegister, sendRegisterVerifyCode } from '@/services/api/common'
+import { userRegister, sendRegisterVerifyCode, queryMyRegisterMoney } from '@/services/api/common'
+// import AppQrcodeScan from '@mobile/components/base/qrcode-scan/index.vue'
 import cryptojs from 'crypto-js'
 
 const { router, routerTo } = useNavigation()
 const formRef = ref<FormInstance>()
 const checked = ref(false) // 是否同意注册条款
 const isCountdown = ref(false) // 是否正在倒计时
+const showReward = ref(false) // 显示红包
+const redEnvelope = ref(0) // 红包金额
 
 // 倒计时函数
 const countdown = useCountDown({
@@ -117,6 +132,11 @@ const formRules: { [key in keyof Model.RegisterReq]?: FieldRule[] } = {
   }],
 }
 
+// 扫码成功
+// const onScanSuccess = (text: string) => {
+//   formData.refernum = text
+// }
+
 // 发送手机验证码
 const sendVerifyCode = () => {
   formRef.value?.validate('mobilephone').then(() => {
@@ -133,6 +153,33 @@ const sendVerifyCode = () => {
   })
 }
 
+// 获取注册红包
+const getRegisterMoney = (accountid: number) => {
+  const toggleDialog = () => {
+    dialog('您的账号已成功注册。').then(() => {
+      router.back()
+    })
+  }
+  return queryMyRegisterMoney({
+    data: {
+      accountid
+    },
+    success: (res) => {
+      // 查询成功显示获得红包
+      if (res.data.length) {
+        redEnvelope.value = res.data[0].amount
+        showReward.value = true
+      } else {
+        toggleDialog()
+      }
+    },
+    fail: () => {
+      // 查询失败提示注册成功后返回登录页面
+      toggleDialog()
+    }
+  })
+}
+
 // 表单提交
 const formSubmit = () => {
   if (checked.value) {
@@ -149,10 +196,12 @@ const formSubmit = () => {
         },
         success: (res) => {
           if (res.code === 0) {
-            hideLoading()
-            dialog('您的账号已成功注册。').then(() => {
-              router.back()
-            })
+            // 等待3秒后查询注册红包
+            setTimeout(() => {
+              getRegisterMoney(res.taaccount[0].accountid).finally(() => {
+                hideLoading()
+              })
+            }, 3000)
           } else {
             Toast.fail(res.message)
           }
@@ -161,7 +210,7 @@ const formSubmit = () => {
           Toast.fail(err)
         }
       })
-    })
+    }, '正在注册...')
   } else {
     Toast('请先同意注册条款')
   }

+ 7 - 0
src/services/api/common/index.ts

@@ -70,4 +70,11 @@ export function queryErrorInfos(params: HttpParams<{ req: Model.ErrorInfosReq, r
  */
 export function signin(params: HttpParams<{ req: { userid: number }, rsp: { signinstatus: number } }>) {
     return httpRequest('/Ferroalloy/Signin', 'post', params);
+}
+
+/**
+ * 查询我的注册红包
+ */
+export function queryMyRegisterMoney(params: HttpParams<{ req: Model.MyRegisterMoneyReq, rsp: Model.MyRegisterMoneyRsp[] }>) {
+    return httpRequest('/Ferroalloy/QueryMyRegisterMoney', 'get', params);
 }

+ 22 - 0
src/types/model/common.d.ts

@@ -1,4 +1,5 @@
 import { AuthType, UrlType } from '@/constants/enum/menu'
+import { NumberFormatResult } from 'vue-i18n';
 
 declare global {
     namespace Model {
@@ -35,6 +36,17 @@ declare global {
         interface RegisterRsp {
             code: number;
             message: string;
+            msg: string;
+            tapassword: string;
+            loginaccount: {
+                userid: number;
+            },
+            taaccount: {
+                accountid: number;
+            }[];
+            userinfo: {
+                userid: number;
+            }
         }
 
         /** 发送重置密码验证码 请求 */
@@ -106,5 +118,15 @@ declare global {
             operatecode: string; // 所属操作
             rownumber: string; // 行号
         }
+
+        /** 查询我的注册红包 请求 */
+        interface MyRegisterMoneyReq {
+            accountid: number; // 资金账户
+        }
+
+        /** 查询我的注册红包 响应 */
+        interface MyRegisterMoneyRsp {
+            amount: number; // 红包
+        }
     }
 }

+ 1 - 0
src/types/model/news.d.ts

@@ -8,6 +8,7 @@ declare namespace Model {
 
     /** 查询资讯 响应 */
     interface SiteColumnDetailRsp {
+        author: string;
         brief: string; // 简介
         context: string; // 内容
         coverimage: string; // 封面图片

+ 9 - 4
src/utils/h5plus/index.ts

@@ -130,6 +130,10 @@ export default new (class {
         })
     }
 
+    /**
+     * 更新应用
+     * @param url 
+     */
     updateApp(url: string) {
         this.onPlusReady((plus) => {
             const dtask = plus.downloader.createDownload(
@@ -170,10 +174,11 @@ export default new (class {
             function installApp(path: string) {
                 plus.nativeUI.showWaiting('正在更新...')
                 plus.runtime.install(
-                    path, {
-                    // true表示强制安装,不进行版本号的校验;false则需要版本号校验,如果将要安装应用的版本号不高于现有应用的版本号则终止安装,并返回安装失败。 仅安装wgt和wgtu时生效,默认值 false
-                    force: false
-                },
+                    path,
+                    {
+                        // true表示强制安装,不进行版本号的校验;false则需要版本号校验,如果将要安装应用的版本号不高于现有应用的版本号则终止安装,并返回安装失败。 仅安装wgt和wgtu时生效,默认值 false
+                        force: false
+                    },
                     function () {
                         plus.nativeUI.closeWaiting()
                         console.log('更新成功!')

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů