li.shaoyi 1 maand geleden
bovenliggende
commit
7471fd6a90

+ 1 - 2
src/packages/digital/assets/themes/default/default.less

@@ -36,8 +36,7 @@
     /* Vant-Button */
     --van-button-default-background: #37393e;
     --van-button-default-border-color: var(--van-button-default-background);
-    --van-button-primary-color: #000;
-    --van-button-primary-background: #fff;
+    --van-button-primary-background: #e55b24;
     --van-button-primary-border-color: var(--van-button-primary-background);
 
     /* Vant-Checkbox */

+ 4 - 0
src/packages/digital/assets/themes/global/global.less

@@ -50,6 +50,10 @@
             }
         }
     }
+
+    &__footer {
+        padding: 32px var(--van-padding-md);
+    }
 }
 
 .g-list-table {

+ 0 - 5
src/packages/digital/router/index.ts

@@ -38,7 +38,6 @@ const routes: Array<RouteRecordRaw> = [
         },
         props: {
           showYhkhfxgzs: false,
-          insetStyle: false
         }
       }
     ]
@@ -174,7 +173,6 @@ const routes: Array<RouteRecordRaw> = [
         },
         props: {
           showYhkhfxgzs: false,
-          insetStyle: false
         }
       },
       {
@@ -184,9 +182,6 @@ const routes: Array<RouteRecordRaw> = [
         meta: {
           ignoreAuth: true,
         },
-        props: {
-          insetStyle: false
-        }
       },
       {
         path: 'cancel',

+ 1 - 1
src/packages/digital/views/spot/detail/index.vue

@@ -55,7 +55,7 @@
             </Tab>
         </Tabs>
         <ActionSheet v-model:show="showSheet" title="请选择">
-            <CellGroup>
+            <CellGroup style="min-height: 200px;">
                 <template v-for="(item, index) in quotes" :key="index">
                     <Cell :title="item.goodsname" :value="item.goodscode" :border="false" is-link
                         @click="navigateToSpotDetail(item.goodsid)" />

+ 52 - 29
src/packages/digital/views/spot/goods/detail/index.vue

@@ -8,8 +8,10 @@
                             <b>{{ quote?.goodscode }}</b>
                         </template>
                         <template #label>
-                            <span :class="quote?.lastColor">{{ quote?.last }}</span>
-                            <span>{{ parsePercent(quote?.change) }}</span>
+                            <div style="display: flex; align-items: center; gap: 10px;">
+                                <h2 :class="quote?.lastColor">{{ quote?.last }}</h2>
+                                <span>{{ parsePercent(quote?.change) }}</span>
+                            </div>
                         </template>
                         <template #right-icon>
                             <span @click="navigateToGoodsChart">图表</span>
@@ -44,20 +46,24 @@
                     v-if="formData.PriceMode === PriceMode.Market" />
                 <Field label="数量">
                     <template #input>
-                        <app-stepper v-model="formData.OrderQty" />
+                        <app-stepper v-model="formData.OrderQty" :min="0" />
                     </template>
                 </Field>
-                <Cell :title="formData.BuyOrSell === BuyOrSell.Buy ? '预估支付' : '预估获取'" :value="calculations.estimatedAmount" />
+                <Cell :title="formData.BuyOrSell === BuyOrSell.Buy ? '预估支付' : '预估获取'"
+                    :value="formatDecimal(calculations.estimatedAmount, quoteAccount?.currencydecimalplace)" />
             </CellGroup>
             <CellGroup inset v-if="formData.BuyOrSell === BuyOrSell.Buy">
-                <Cell title="可用余额" :value="calculations.availableBalance" />
-                <Cell title="可开数量" :value="calculations.availableQty" />
-                <Cell title="预估手续费" :value="calculations.buyEstimatedFee" />
+                <Cell title="预估手续费"
+                    :value="formatDecimal(calculations.buyEstimatedFee, quoteAccount?.currencydecimalplace)" />
+                <Cell title="可用余额"
+                    :value="formatDecimal(calculations.maxBalance, quoteAccount?.currencydecimalplace)" />
+                <Cell title="可买数量" :value="formatDecimal(calculations.maxBuyQty, baseAccount?.currencydecimalplace)" />
             </CellGroup>
             <CellGroup inset v-if="formData.BuyOrSell === BuyOrSell.Sell">
-                <Cell title="可获金额" :value="calculations.availableAmount" />
-                <Cell title="可卖数量" :value="calculations.sellQty" />
-                <Cell title="预估手续费" :value="calculations.sellEstimatedFee" />
+                <Cell title="预估手续费"
+                    :value="formatDecimal(calculations.sellEstimatedFee, quoteAccount?.currencydecimalplace)" />
+                <Cell title="可获金额" :value="formatDecimal(calculations.maxAmount, quoteAccount?.currencydecimalplace)" />
+                <Cell title="可卖数量" :value="formatDecimal(calculations.maxSellQty, baseAccount?.currencydecimalplace)" />
             </CellGroup>
         </Form>
         <Row class="g-layout-block g-layout-block--inset">
@@ -80,22 +86,25 @@
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, reactive, computed, onMounted } from 'vue'
+import { shallowRef, reactive, computed, onMounted, onUnmounted } from 'vue'
 import { Form, Button, CellGroup, Field, Cell, Tab, Tabs, Col, Row, FormInstance } from 'vant'
 import { fullloading } from '@/utils/vant'
 import { EBuildType, EDelistingType, EListingSelectType, EOrderOperateType, EValidType } from '@/constants/client'
-import { parsePercent, handleNumberValue } from '@/filters'
+import { parsePercent, handleNumberValue, formatDecimal } from '@/filters'
 import { BuyOrSell, PriceMode, getPricemode2List } from '@/constants/order'
 import { useNavigation } from '@mobile/router/navigation'
 import { digitalOrder } from '@/services/api/digital'
 import { useFuturesStore, useUserStore } from '@/stores'
 import { useSpotAccountStore } from '../../../wallet/components/spot/composables'
+import quoteSocket from '@/services/websocket/quote'
 import Long from 'long'
 import AppSelect from '@mobile/components/base/select/index.vue'
 import AppStepper from '@mobile/components/base/stepper/index.vue'
 import SpotOrder from '../../components/order/index.vue'
 import SpotAccount from '../../components/account/index.vue'
 
+const subscribe = quoteSocket.createSubscribe()
+
 const { router, getQueryStringToNumber } = useNavigation()
 const goodsId = getQueryStringToNumber('id')
 const userStore = useUserStore()
@@ -106,7 +115,7 @@ const formRef = shallowRef<FormInstance>()
 
 const formData = reactive<Partial<Proto.DigitalOrderReq>>({
     BuyOrSell: BuyOrSell.Buy,
-    PriceMode: PriceMode.Limit,
+    PriceMode: PriceMode.Market,
     OperateType: EOrderOperateType.ORDEROPERATETYPE_NORMAL,
     ListingSelectType: EListingSelectType.LISTINGSELECTTYPE_DELISTING,
     DelistingType: EDelistingType.DELISTINGTYPE_SELECTED,
@@ -121,35 +130,39 @@ const baseAccount = computed(() => spotAccountStore.getAccountItem({ currencyid:
 const quoteAccount = computed(() => spotAccountStore.getAccountItem({ currencyid: quote.value?.currencyid })) // 计价货币账户
 
 const calculations = computed(() => {
+    const buyFeeValue = futuresStore.getFeeValue(quote.value, 101)
+    const sellFeeValue = futuresStore.getFeeValue(quote.value, 102)
+
     const { last = 0, agreeunit = 0 } = quote.value ?? {}
     const { OrderPrice = 0, OrderQty = 0 } = formData
 
     const price = formData.PriceMode === PriceMode.Market ? last : OrderPrice
+    const amount = OrderQty * agreeunit
 
     // 预估金额
-    const estimatedAmount = price * OrderQty * agreeunit
+    const estimatedAmount = price * amount
 
     // 可用余额
-    const availableBalance = spotAccountStore.getAvailableBalance(quoteAccount.value)
-    // 可数量
-    const availableQty = availableBalance / agreeunit
+    const maxBalance = spotAccountStore.getAvailableBalance(quoteAccount.value)
+    // 可数量
+    const maxBuyQty = price ? maxBalance / (price * agreeunit) : 0
     // 预估手续费
-    const buyEstimatedFee = 0
+    const buyEstimatedFee = (buyFeeValue.FeeAlgorithm === 2 ? amount : estimatedAmount) * buyFeeValue.feeValue
 
     // 可卖数量
-    const sellQty = spotAccountStore.getAvailableBalance(baseAccount.value)
+    const maxSellQty = spotAccountStore.getAvailableBalance(baseAccount.value)
     // 可获金额
-    const availableAmount = price * sellQty * agreeunit
+    const maxAmount = price * maxSellQty * agreeunit
     // 预估手续费
-    const sellEstimatedFee = 0
+    const sellEstimatedFee = (sellFeeValue.FeeAlgorithm === 2 ? amount : estimatedAmount) * sellFeeValue.feeValue
 
     return {
         estimatedAmount,
-        availableBalance,
-        availableQty,
+        maxBalance,
+        maxBuyQty,
         buyEstimatedFee,
-        availableAmount,
-        sellQty,
+        maxAmount,
+        maxSellQty,
         sellEstimatedFee
     }
 })
@@ -165,9 +178,12 @@ const navigateToGoodsChart = () => {
 
 const onSubmit = () => {
     fullloading((hideLoading) => {
-        if (baseAccount.value && quoteAccount.value) {
-            formData.BaseAccountID = Long.fromString(baseAccount.value.digitalaccountid)
-            formData.QuoteAccountID = Long.fromString(quoteAccount.value.digitalaccountid)
+        const baseAccountId = baseAccount.value?.digitalaccountid
+        const quoteAccountId = quoteAccount.value?.digitalaccountid
+
+        if (baseAccountId && quoteAccountId) {
+            formData.BaseAccountID = Long.fromString(baseAccountId)
+            formData.QuoteAccountID = Long.fromString(quoteAccountId)
             formData.GoodsID = quote.value?.goodsid
             formData.MarketID = quote.value?.marketid
 
@@ -192,7 +208,14 @@ const onSubmit = () => {
 }
 
 onMounted(() => {
-    formData.OrderPrice = quote.value?.last
+    if (quote.value) {
+        subscribe.start(quote.value.goodscode)
+        formData.OrderPrice = quote.value.last
+    }
+})
+
+onUnmounted(() => {
+    subscribe.stop()
 })
 </script>
 

+ 66 - 7
src/packages/digital/views/user/login/index.less

@@ -1,17 +1,76 @@
 .login {
-    background: var(--app-bg-color);
+    background-color: var(--app-bg-color);
 
-    &-logo {
-        img {
-            height: 48px;
+    .app-navbar {
+        &__wrapper {
+            background-color: transparent;
+        }
+    }
+
+    .app-luanguage {
+        color: #999;
+    }
+
+    &-container {
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        padding: var(--van-padding-md);
+    }
+
+    &-title {
+        padding: var(--van-padding-md);
+
+        h1 {
+            font-size: 24px;
         }
     }
 
     &-form {
-        background-color: #111827;
+        margin-top: 32px;
+
+        .button {
+            &-submit {
+                margin: var(--van-padding-md);
+                margin-top: 32px;
+            }
+        }
     }
 
-    .van-field__control {
-        background-color: #333 !important;
+    &-link {
+        display: flex;
+        justify-content: space-between;
+        font-size: 13px;
+        padding: 0 var(--van-padding-md);
+        margin: 0 24px;
+
+        span:only-child {
+            margin-left: auto;
+        }
+    }
+
+    &-footer {
+        color: #999;
+        padding: var(--van-padding-md);
+        margin-top: auto;
+
+        &__trem {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            flex-wrap: wrap;
+            font-size: 12px;
+
+            .van-checkbox {
+                margin-right: 6px;
+            }
+        }
+
+        &__version {
+            font-size: 12px;
+            color: #666;
+            text-align: center;
+            margin-top: 4px;
+        }
     }
 }

+ 98 - 2
src/packages/digital/views/user/login/index.vue

@@ -1,9 +1,105 @@
 <template>
-  <app-login />
+  <app-view class="login">
+    <template #header>
+      <app-navbar>
+        <template #right>
+          <app-luanguage v-if="globalStore.getSystemInfo('i18nEnabled')" />
+        </template>
+      </app-navbar>
+    </template>
+    <div class="login-container">
+      <div class="login-title">
+        <h1>登录</h1>
+      </div>
+      <Form ref="formRef" class="login-form" @submit="formValidate">
+        <CellGroup inset>
+          <Field v-model="formData.userName" name="account" :label="$t('user.login.username')" label-align="top"
+            size="large" :placeholder="$t('user.login.Pleaseenterausername')"
+            :rules="[{ required: true, message: $t('user.login.Pleaseenterausername') }]" />
+          <Field v-model="formData.password" name="password" type="password" :label="$t('user.login.password')"
+            label-align="top" size="large" :placeholder="$t('user.login.Pleaseenterthepassword')"
+            :rules="[{ required: true, message: $t('user.login.Pleaseenterthepassword') }]" autocomplete="off" />
+        </CellGroup>
+        <ClickVerify class="button-submit" v-model:show="showClickVerify" @success="formSubmit">
+          <Button type="primary" native-type="submit" @click="formRef?.submit" round block>
+            {{ $t('user.login.login') }}
+          </Button>
+        </ClickVerify>
+        <div class="login-link">
+          <span @click="routerTo('user-register')" v-if="globalStore.hasSystemModule('register')">
+            {{ $t('user.login.register') }}
+          </span>
+          <span @click="routerTo('user-forget')">
+            {{ $t('user.login.forgetpassword') }}
+          </span>
+        </div>
+      </Form>
+      <div class="login-footer">
+        <div class="login-footer__trem">
+          <Checkbox shape="square" icon-size="16px" v-model="checked">{{ $t('user.login.checked') }}</Checkbox>
+          <span @click="routerTo('rules-zcxy')">{{ $t('user.login.ruleszcxy') }}</span>
+          <span>、</span>
+          <span @click="routerTo('rules-yhkhfxgzs')">{{ $t('user.login.rulesyhkhfxgzs') }}</span>
+          <span>、</span>
+          <span @click="routerTo('rules-yszc')">{{ $t('user.login.rulesyszc') }}</span>
+        </div>
+        <div class="login-footer__version">
+          <span>v1.0.{{ appVersion }}</span>
+        </div>
+      </div>
+    </div>
+  </app-view>
 </template>
 
 <script lang="ts" setup>
-import AppLogin from '@mobile/components/layouts/login/index.vue'
+import { shallowRef } from 'vue'
+import { FormInstance, Button, Field, CellGroup, Form, Checkbox, showToast } from 'vant'
+import { fullloading, dialog } from '@/utils/vant'
+import { useLogin } from '@/business/login'
+import { useNavigation } from '@mobile/router/navigation'
+import { useGlobalStore, i18n } from '@/stores'
+import ClickVerify from '@mobile/components/base/click-verify/index.vue'
+import AppLuanguage from '@mobile/components/modules/luanguage/index.vue'
+
+const { global: { t } } = i18n
+const globalStore = useGlobalStore()
+const { setGlobalUrlParams, routerTo, routerBack } = useNavigation()
+const { formData, userLogin } = useLogin()
+const formRef = shallowRef<FormInstance>()
+const checked = shallowRef(false) // 是否同意协议管理
+const showClickVerify = shallowRef(false) // 显示验证窗口
+
+const meta = document.getElementsByTagName('meta')
+const appVersion = meta.namedItem('revised')?.content ?? '0'
+
+// 表单验证
+const formValidate = () => {
+  if (checked.value) {
+    showClickVerify.value = true
+  } else {
+    showToast(t('user.login.tips2'))
+  }
+}
+
+// 表单提交
+const formSubmit = () => {
+  fullloading((hideLoading) => {
+    userLogin().then((forcedPasswordChange) => {
+      hideLoading()
+      if (forcedPasswordChange) {
+        dialog(t('user.login.tips1')).then(() => {
+          setGlobalUrlParams({ forcedPasswordChange })
+          routerTo('user-password', true)
+        })
+      } else {
+        routerBack()
+      }
+    }).catch((err) => {
+      formData.password = ''
+      hideLoading(err, 'fail')
+    })
+  }, t('user.login.logining'))
+}
 </script>
 
 <style lang="less">

+ 6 - 8
src/packages/mobile/views/user/forget/Index.vue

@@ -20,7 +20,7 @@
                 <Field v-model="formData.vcode" type="digit" name="vcode" :label="t('user.forget.vcode')"
                     :placeholder="t('common.pleaseenter')" autocomplete="off" :rules="formRules.vcode">
                     <template #button>
-                        <Button size="small" type="danger" :disabled="loading" @click="sendVerifyCode">
+                        <Button size="small" :disabled="loading" @click="sendVerifyCode">
                             <span v-if="isCountdown">{{ t('user.forget.sendagain') }}({{ currentTime }})</span>
                             <span v-else>{{ t('user.forget.getsmscode') }}</span>
                         </Button>
@@ -35,13 +35,11 @@
                     :rules="formRules.confirmpassword" />
             </CellGroup>
         </Form>
-        <template #footer>
-            <div class="g-form__footer inset">
-                <Button type="danger" @click="formRef?.submit()" :round="insetStyle" block>
-                    {{ t('user.forget.resetpwd') }}
-                </Button>
-            </div>
-        </template>
+        <div class="g-form__footer inset">
+            <Button type="primary" @click="formRef?.submit()" :round="insetStyle" block>
+                {{ t('user.forget.resetpwd') }}
+            </Button>
+        </div>
     </app-view>
 </template>
 

+ 8 - 10
src/packages/mobile/views/user/register/Index.vue

@@ -20,7 +20,7 @@
         <Field v-model="formData.vcode" type="digit" name="vcode" :label="t('user.register.vcode')"
           :placeholder="t('common.required')" :rules="formRules.vcode">
           <template #button>
-            <Button size="small" type="danger" :disabled="loading" @click="sendVerifyCode">
+            <Button size="small" :disabled="loading" @click="sendVerifyCode">
               <span v-if="isCountdown">{{ t('user.register.sendagain') }}({{ currentTime }})</span>
               <span v-else>{{ t('user.register.getsmscode') }}</span>
             </Button>
@@ -60,15 +60,13 @@
         </Cell>
       </CellGroup>
     </Form>
-    <template #footer>
-      <div class="g-form__footer inset">
-        <Button type="danger" @click="formRef?.submit" :round="insetStyle" block>
-          {{ t('user.register.freeregister') }}
-        </Button>
-      </div>
-      <app-reward :show="showReward" :value="redEnvelope" :title="t('user.register.registersuccess')"
-        @click="routerAction" />
-    </template>
+    <div class="g-form__footer inset">
+      <Button type="primary" @click="formRef?.submit" :round="insetStyle" block>
+        {{ t('user.register.freeregister') }}
+      </Button>
+    </div>
+    <app-reward :show="showReward" :value="redEnvelope" :title="t('user.register.registersuccess')"
+      @click="routerAction" />
   </app-view>
 </template>
 

+ 15 - 3
src/stores/modules/futures.ts

@@ -193,8 +193,19 @@ export const useFuturesStore = defineStore(() => {
         return computed(() => state.quotationList.find((e) => e.goodscode === code || e.goodsid === code))
     }
 
-    const getQuoteInfo = (prop: Partial<Model.GoodsQuote>) => {
-        return state.quotationList.find(item => isMatch(item, prop))
+    const getQuoteInfo = (query: Partial<Model.GoodsQuote>) => {
+        return state.quotationList.find(item => isMatch(item, query))
+    }
+
+    // 获取费用值
+    const getFeeValue = (goodsItem?: Model.GoodsQuote, feeId = 0) => {
+        const { tradefees = [] } = goodsItem ?? {}
+        const { FeeAlgorithm = 0, ExchangeValue = 0, MemberDefaultValue = 0 } = tradefees.find((e) => e.FeeID === feeId) ?? {}
+
+        return {
+            FeeAlgorithm,
+            feeValue: ExchangeValue + MemberDefaultValue
+        }
     }
 
     // 通过 goodscode 获取实时行情报价
@@ -572,6 +583,7 @@ export const useFuturesStore = defineStore(() => {
         quotePushNotify,
         getGoods,
         goodsdisplay,
-        getQuoteInfo
+        getQuoteInfo,
+        getFeeValue
     }
 })