Handy_Cao vor 1 Woche
Ursprung
Commit
560a34a2e2

+ 1 - 1
oem/digital/config/appconfig.json

@@ -3,7 +3,7 @@
   "appName": "Digital",
   "version": "1.0.0",
   "versionCode": "100000",
-  "apiUrl": "http://192.168.31.104:8080/cfg?key=test_104",
+  "apiUrl": "http://192.168.31.169:8080/cfg?key=test_169",
   "tradeChannel": "ws",
   "modules": [
     "register"

+ 6 - 2
public/locales/en-US.json

@@ -1502,13 +1502,17 @@
             "tips2": "Please enter your login password",
             "tips3": "Please enter your confirm password",
             "tips4": "Login password and confirm password do not match",
-            "tips5": "Please enter the SMS verification code",
+            "tips5": "Please enter the verification code",
             "tips6": "Please enter the registration code",
             "tips7": "Sending failed",
             "tips8": "Your account has been successfully registered.",
             "tips9": "Registering...",
             "tips10": "Please agree to the registration terms first",
-            "tips11": "Your registration application has been submitted!"
+            "tips11": "Your registration application has been submitted!",
+            "emailregister": "Email register",
+            "vscode": "V-Code",
+            "tips12": "Please enter the email",
+            "tips13": "The password length cannot be less than 6 characters"
         },
         "password": {
             "title": "Change password",

+ 5 - 1
public/locales/th-TH.json

@@ -1511,7 +1511,11 @@
             "tips8": "บัญชีของคุณลงทะเบียนสำเร็จแล้ว",
             "tips9": "กำลังลงทะเบียน...",
             "tips10": "กรุณายอมรับเงื่อนไขการลงทะเบียนก่อน",
-            "tips11": "ใบสมัครของคุณได้ส่งแล้ว!"
+            "tips11": "ใบสมัครของคุณได้ส่งแล้ว!",
+            "emailregister": "郵箱註冊",
+            "vscode": "驗證碼",
+            "tips12": "請輸入郵箱地址",
+            "tips13": "密碼長度不能小於6位"
         },
         "password": {
             "title": "เปลี่ยนรหัสผ่าน",

+ 5 - 1
public/locales/vi-VN.json

@@ -1508,7 +1508,11 @@
             "tips8": "Tài khoản của bạn đã được đăng ký thành công",
             "tips9": "Đang đăng ký...",
             "tips10": "Vui lòng đồng ý điều khoản đăng ký trước",
-            "tips11": "Đơn đăng ký của bạn đã được gửi!"
+            "tips11": "Đơn đăng ký của bạn đã được gửi!",
+            "emailregister": "邮箱注册",
+            "vscode": "验证码",
+            "tips12": "请输入邮箱地址",
+            "tips13": "密码长度不能小于6位"
         },
         "password": {
             "title": "Đổi mật khẩu",

+ 5 - 1
public/locales/zh-CN.json

@@ -1511,7 +1511,11 @@
             "tips8": "您的账号已成功注册。",
             "tips9": "正在注册...",
             "tips10": "请先同意注册条款",
-            "tips11": "您的注册申请已提交!"
+            "tips11": "您的注册申请已提交!",
+            "emailregister": "邮箱注册",
+            "vscode": "验证码",
+            "tips12": "请输入邮箱地址",
+            "tips13": "密码长度不能小于6位"
         },
         "password": {
             "title": "修改密码",

+ 6 - 2
public/locales/zh-TW.json

@@ -1505,13 +1505,17 @@
             "tips2": "請輸入登錄密碼",
             "tips3": "請輸入確認密碼",
             "tips4": "登錄密碼和確認密碼不一致",
-            "tips5": "請輸入短信驗證碼",
+            "tips5": "請輸入驗證碼",
             "tips6": "請輸入註冊編碼",
             "tips7": "發送失敗",
             "tips8": "您的賬號已成功註冊。",
             "tips9": "正在註冊...",
             "tips10": "請先同意註冊條款",
-            "tips11": "您的註冊申請已提交!"
+            "tips11": "您的註冊申請已提交!",
+            "emailregister": "郵箱註冊",
+            "vscode": "驗證碼",
+            "tips12": "請輸入郵箱地址",
+            "tips13": "密碼長度不能小於6位"
         },
         "password": {
             "title": "修改密碼",

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

@@ -77,6 +77,11 @@ const routes: Array<RouteRecordRaw> = [
             component: () => import('../views/contract/goods/list/index.vue'),
           },
           {
+            path: 'listing',
+            name: 'home-listing',
+            component: () => import('../views/listing/goods/list/index.vue'),
+          },
+          {
             path: 'wallet',
             name: 'home-wallet',
             component: () => import('../views/wallet/index.vue'),
@@ -128,6 +133,27 @@ const routes: Array<RouteRecordRaw> = [
     ]
   },
   {
+    path: '/listing',
+    component: Page,
+    children: [
+      {
+        path: 'detail',
+        name: 'listing-detail',
+        component: () => import('../views/listing/detail/index.vue'),
+      },
+      {
+        path: 'goods/detail',
+        name: 'listing-goods-detail',
+        component: () => import('../views/listing/goods/detail/index.vue'),
+      },
+      {
+        path: 'goods/chart',
+        name: 'listing-goods-chart',
+        component: () => import('../views/listing/goods/chart/index.vue'),
+      }
+    ]
+  },
+  {
     path: '/wallet',
     component: Page,
     children: [

+ 5 - 0
src/packages/digital/views/home/index.vue

@@ -50,6 +50,11 @@ const tabbarItems = [
     icon: 'records',
   },
   {
+    name: 'home-listing',
+    label: '挂牌',
+    icon: 'records',
+  },
+  {
     name: 'home-spot',
     label: t('tabbar.spot'),
     icon: 'graphic',

+ 121 - 0
src/packages/digital/views/listing/detail/index.vue

@@ -0,0 +1,121 @@
+<template>
+    <app-view class="spot-detail g-form">
+        <template #header>
+            <app-navbar :title="t('pcroute.bottom.bottom_spot_position')" />
+        </template>
+        <Grid :border="false" :column-num="quotes.length ? 3 : 2">
+            <GridItem icon="pending-payment" :text="t('digital.wallet-deposit')"
+                :to="{ name: 'wallet-deposit', query: { id: currencyid } }" />
+            <GridItem icon="paid" :text="t('digital.wallet-withdraw')"
+                :to="{ name: 'wallet-withdraw', query: { id: currencyid } }" />
+            <GridItem icon="chart-trending-o" :text="t('spot-goods-detail')" @click="navigateToSpotDetail()"
+                v-if="quotes.length" />
+        </Grid>
+        <div class="g-detail-table" v-if="accountItem">
+            <table cellspacing="0" cellpadding="0">
+                <tbody>
+                    <tr>
+                        <td colspan="2">
+                            <span class="text-small">{{ t('account.balance') + `(${accountItem.currencycode})` }}</span>
+                            <span>
+                                {{ formatDecimal(accountItem.currentbalance, accountItem.currencydecimalplace) }}
+                            </span>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            <span class="text-small">{{
+                                t('account.availableFunds') + `(${accountItem.currencycode})` }}</span>
+                            <span>
+                                {{ formatDecimal(spotAccountStore.getAvailableBalance(accountItem),
+                                    accountItem.currencydecimalplace) }}
+                            </span>
+                        </td>
+                        <td>
+                            <span class="text-small">{{ t('account.freeze') + `(${accountItem.currencycode})` }}</span>
+                            <span>
+                                {{ formatDecimal(accountItem.freezemargin, accountItem.currencydecimalplace) }}
+                            </span>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+        <Tabs>
+            <Tab :title="t('digital.wallet-deposit')">
+                <wallet-record :params="{ digitalaccountid, transfertypes: '1' }" />
+            </Tab>
+            <Tab :title="t('digital.wallet-withdraw')">
+                <wallet-record :params="{ digitalaccountid, transfertypes: '2' }" />
+            </Tab>
+            <Tab :title="t('digital.transfer-in')">
+                <wallet-record :params="{ digitalaccountid, transfertypes: '3' }" />
+            </Tab>
+            <Tab :title="t('digital.transfer-out')">
+                <wallet-record :params="{ digitalaccountid, transfertypes: '4' }" />
+            </Tab>
+            <Tab :title="t('digital.order')">
+                <spot-order :params="{ digitalaccountid }" showDatePicker />
+            </Tab>
+            <Tab :title="t('digital.trade')">
+                <spot-trade :params="{ digitalaccountid }" showDatePicker />
+            </Tab>
+            <Tab :title="t('digital.funds')">
+                <spot-statement :params="{ digitalaccountid }" />
+            </Tab>
+        </Tabs>
+        <ActionSheet v-model:show="showSheet" :title="t('common.choice')">
+            <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 clickable
+                        @click="navigateToSpotDetail(item.goodsid)" />
+                </template>
+            </CellGroup>
+        </ActionSheet>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, computed } from 'vue'
+import { Cell, CellGroup, Tab, Tabs, Grid, GridItem, ActionSheet } from 'vant'
+import { formatDecimal } from '@/filters'
+import { useNavigation } from '@mobile/router/navigation'
+import { i18n, useFuturesStore } from '@/stores'
+import { useSpotAccountStore } from '../../wallet/components/spot/composables'
+import WalletRecord from '../../wallet/components/record/index.vue'
+import SpotOrder from '../../spot/components/order/index.vue'
+import SpotTrade from '../../spot/components/trade/index.vue'
+import SpotStatement from '../../spot/components/statement/index.vue'
+
+const { router, getQueryStringToNumber } = useNavigation()
+
+const { global: { t } } = i18n
+
+const futuresStore = useFuturesStore()
+const spotAccountStore = useSpotAccountStore()
+
+const currencyid = getQueryStringToNumber('id')
+const showSheet = shallowRef(false)
+
+const accountItem = computed(() => spotAccountStore.getAccountItem({ currencyid }))
+
+const digitalaccountid = computed(() => accountItem.value?.digitalaccountid || '0')
+
+const quotes = computed(() => futuresStore.quotationList.filter((e) => e.trademode === 80 && (e.goodscurrencyid === currencyid || e.currencyid === currencyid)))
+
+const navigateToSpotDetail = (goodsid?: number) => {
+    showSheet.value = false
+
+    // 多个商品弹出列表选择,单个商品直接跳转
+    if (quotes.value.length > 1 && !goodsid) {
+        showSheet.value = true
+    } else {
+        router.push({
+            name: 'listing-goods-detail',
+            query: {
+                id: goodsid ?? quotes.value[0].goodsid
+            }
+        })
+    }
+}
+</script>

+ 43 - 0
src/packages/digital/views/listing/goods/chart/index.less

@@ -0,0 +1,43 @@
+.spot-goods-chart {
+    .quote-table {
+        padding: var(--van-padding-md) 0;
+
+        .data-table {
+            width: 100%;
+
+            &__cell {
+                font-size: 12px;
+                line-height: 1.5;
+
+                &--merged {
+                    width: 50%;
+                    text-align: center;
+
+                    span:not(:first-child) {
+                        margin-left: 10px;
+                    }
+                }
+
+                .text-small {
+                    color: #666;
+                }
+            }
+        }
+    }
+
+    .app-quote-chart {
+        background-color: #000;
+
+        &__header {
+            border-color: #171f2d;
+        }
+
+        .tabs {
+            border-color: #171f2d !important;
+
+            &-item.is-active {
+                color: var(--color-primary);
+            }
+        }
+    }
+}

+ 84 - 0
src/packages/digital/views/listing/goods/chart/index.vue

@@ -0,0 +1,84 @@
+<template>
+    <app-view class="spot-goods-chart g-layout">
+        <template #header>
+            <app-navbar :title="quote ? quote.goodscode : '图表'" />
+        </template>
+        <template v-if="quote">
+            <div class="quote-table">
+                <table class="data-table" cellspacing="0" cellpadding="0">
+                    <tbody>
+                        <tr class="data-table__row">
+                            <td class="data-table__cell data-table__cell--merged" rowspan="2">
+                                <div :class="quote.lastColor">
+                                    <h2>{{ handleNumberValue(quote.last.toFixed(quote.decimalplace)) }}</h2>
+                                </div>
+                                <div>
+                                    <span :class="quote.lastColor">{{ quote.rise.toFixed(quote.decimalplace) }}</span>
+                                    <span :class="quote.lastColor">{{ parsePercent(quote.change) }}</span>
+                                </div>
+                            </td>
+                            <td class="data-table__cell">
+                                <div class="text-small">昨结</div>
+                                <div>
+                                    {{ handleNumberValue(quote.presettle.toFixed(quote.decimalplace)) }}
+                                </div>
+                            </td>
+                            <td class="data-table__cell">
+                                <div class="text-small">最高</div>
+                                <div :class="quote.highestColor">
+                                    {{ handleNumberValue(quote.highest.toFixed(quote.decimalplace)) }}
+                                </div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="data-table__cell">
+                                <div class="text-small">开盘</div>
+                                <div :class="quote.openedColor">
+                                    {{ handleNumberValue(quote.opened.toFixed(quote.decimalplace)) }}
+                                </div>
+                            </td>
+                            <td class="data-table__cell">
+                                <div class="text-small">最低</div>
+                                <div :class="quote.lowestColor">
+                                    {{ handleNumberValue(quote.lowest.toFixed(quote.decimalplace)) }}
+                                </div>
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+            <goods-chart v-bind="{ theme: 'Dark', goodsCode: quote.goodscode }" />
+        </template>
+        <template #footer>
+            <Row class="g-layout-block g-layout-block--inset" gutter="10">
+                <Col span="12">
+                <Button type="success" block @click="routerBack({ buyOrSell: BuyOrSell.Buy })">买入</Button>
+                </Col>
+                <Col span="12">
+                <Button type="danger" block @click="routerBack({ buyOrSell: BuyOrSell.Sell })">卖出</Button>
+                </Col>
+            </Row>
+        </template>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { computed, defineAsyncComponent } from 'vue'
+import { Button, Col, Row } from 'vant'
+import { handleNumberValue, parsePercent } from '@/filters'
+import { BuyOrSell } from '@/constants/order'
+import { useNavigation } from '@mobile/router/navigation'
+import { useFuturesStore } from '@/stores'
+
+const GoodsChart = defineAsyncComponent(() => import('@mobile/components/modules/hqchart/index.vue'))
+
+const { getQueryStringToNumber, routerBack } = useNavigation()
+const goodsid = getQueryStringToNumber('id')
+const futuresStore = useFuturesStore()
+
+const quote = computed(() => futuresStore.getQuoteItem({ goodsid }))
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 2 - 0
src/packages/digital/views/listing/goods/detail/index.less

@@ -0,0 +1,2 @@
+.listing-goods-detail {
+}

+ 255 - 0
src/packages/digital/views/listing/goods/detail/index.vue

@@ -0,0 +1,255 @@
+<template>
+    <app-view class="listing-goods-detail g-layout g-form">
+        <template #header>
+            <app-navbar :title="quote ? quote.goodscode : '交易'">
+                <template #footer>
+                    <Cell>
+                        <template #title v-if="quote">
+                            <h3 :class="quote.lastColor">
+                                <span>{{ handleNumberValue(quote.last.toFixed(quote.decimalplace)) }}</span>
+                            </h3>
+                            <span :class="quote.lastColor">{{ parsePercent(quote.change) }}</span>
+                        </template>
+                        <template #right-icon>
+                            <span @click="navigateToGoodsChart">图表</span>
+                        </template>
+                    </Cell>
+                </template>
+            </app-navbar>
+        </template>
+        <component :is="Tik" v-bind="{ goodsCode }" />
+        <Row class="g-layout-block g-layout-block--inset" gutter="10">
+            <Col span="12">
+            <Button :type="formData.BuyOrSell === BuyOrSell.Buy ? 'success' : 'default'" size="small" block
+                @click="formData.BuyOrSell = BuyOrSell.Buy">买入</Button>
+            </Col>
+            <Col span="12">
+            <Button :type="formData.BuyOrSell === BuyOrSell.Sell ? 'danger' : 'default'" size="small" block
+                @click="formData.BuyOrSell = BuyOrSell.Sell">卖出</Button>
+            </Col>
+        </Row>
+        <Form ref="formRef" class="g-form__container" @submit="onSubmit">
+            <CellGroup inset>
+                <Field label="类型" is-link>
+                    <template #input>
+                        <app-select v-model="formData.PriceMode" :options="getPricemode2List()" />
+                    </template>
+                </Field>
+                <Field label="价格" :rules="formRules.OrderPrice" v-if="formData.PriceMode === PriceMode.Limit">
+                    <template #input>
+                        <app-stepper v-model="formData.OrderPrice" :min="0" :decimal-length="quote?.decimalplace"
+                            :step="quote?.decimalvalue" />
+                    </template>
+                </Field>
+                <Cell title="价格" value="最优市价" v-if="formData.PriceMode === PriceMode.Market" />
+                <Field label="数量" :rules="formRules.OrderQty">
+                    <template #input>
+                        <app-stepper v-model="formData.OrderQty" :min="0"
+                            :decimal-length="baseAccount?.currencydecimalplace" />
+                    </template>
+                </Field>
+                <Cell :title="formData.BuyOrSell === BuyOrSell.Buy ? '预估支付' : '预估获取'"
+                    :value="formatDecimal(calculations.estimatedAmount, quoteAccount?.currencydecimalplace)" />
+            </CellGroup>
+            <CellGroup inset v-if="formData.BuyOrSell === BuyOrSell.Buy">
+                <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="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">
+            <Col span="24">
+            <Button type="success" block v-if="formData.BuyOrSell === BuyOrSell.Buy"
+                @click="formRef?.submit">买入</Button>
+            <Button type="danger" block v-if="formData.BuyOrSell === BuyOrSell.Sell"
+                @click="formRef?.submit">卖出</Button>
+            </Col>
+        </Row>
+        <Tabs v-model:active="tabIndex" sticky>
+            <Tab :title="t('digital.order')">
+                <spot-order :params="{ goodsid: goodsId, orderstatuses: '3,7' }" />
+            </Tab>
+            <Tab :title="t('digital.account')">
+                <spot-account v-bind="{ goodsId }" />
+            </Tab>
+        </Tabs>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, reactive, computed, onMounted, onUnmounted, onActivated, defineAsyncComponent } from 'vue'
+import { Form, Button, CellGroup, Field, Cell, Tab, Tabs, Col, Row, FormInstance, showDialog, FieldRule } from 'vant'
+import { fullloading } from '@/utils/vant'
+import { parsePercent, formatDecimal,handleNumberValue } from '@/filters'
+import { EBuildType, EDelistingType, EListingSelectType, EOrderOperateType, EValidType } from '@/constants/client'
+import { BuyOrSell, PriceMode, getPricemode2List } from '@/constants/order'
+import { useNavigation } from '@mobile/router/navigation'
+import { digitalOrder } from '@/services/api/digital'
+import { i18n, useFuturesStore, useUserStore } from '@/stores'
+import { useSpotAccountStore } from '../../../wallet/components/spot/composables'
+import { useComponent } from '@/hooks/component'
+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 '../../../spot/components/order/index.vue'
+import SpotAccount from '../../../spot/components/account/index.vue'
+
+const Tik = defineAsyncComponent(() => import('@mobile/components/modules/quote/tik/index.vue'))
+const { componentRef, componentId, openComponent, closeComponent } = useComponent()
+
+const subscribe = quoteSocket.createSubscribe()
+
+const { global: { t } } = i18n
+
+const { router, getQueryStringToNumber, getGlobalUrlParams } = useNavigation()
+const goodsId = getQueryStringToNumber('id')
+const userStore = useUserStore()
+const futuresStore = useFuturesStore()
+const spotAccountStore = useSpotAccountStore()
+const tabIndex = shallowRef(0)
+const formRef = shallowRef<FormInstance>()
+
+const formData = reactive<Partial<Proto.DigitalOrderReq>>({
+    BuyOrSell: BuyOrSell.Buy,
+    PriceMode: PriceMode.Market,
+    OperateType: EOrderOperateType.ORDEROPERATETYPE_NORMAL,
+    ListingSelectType: EListingSelectType.LISTINGSELECTTYPE_DELISTING,
+    DelistingType: EDelistingType.DELISTINGTYPE_PRICE,
+    BuildType: EBuildType.BUILDTYPE_OPEN,
+    TimevalidType: EValidType.VALIDTYPE_DR,
+    OrderFlag: 1,
+    OrderQty: 1,
+})
+
+const quote = computed(() => futuresStore.getQuoteItem({ goodsid: goodsId }))
+const baseAccount = computed(() => spotAccountStore.getAccountItem({ currencyid: quote.value?.goodscurrencyid })) // 基础货币账户
+const quoteAccount = computed(() => spotAccountStore.getAccountItem({ currencyid: quote.value?.currencyid })) // 计价货币账户
+
+const goodsCode = computed(() => quote.value?.goodscode ?? '')
+
+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 * amount
+
+    // 可用余额
+    const maxBalance = spotAccountStore.getAvailableBalance(quoteAccount.value)
+    // 预估手续费
+    const buyEstimatedFee = (buyFeeValue.FeeAlgorithm === 2 ? amount : estimatedAmount) * buyFeeValue.feeValue
+    // 可买数量
+    const maxBuyQty = price ? (maxBalance - buyEstimatedFee) / (price * agreeunit) : 0
+
+    // 可卖数量
+    const maxSellQty = spotAccountStore.getAvailableBalance(baseAccount.value)
+    // 可获金额
+    const maxAmount = price * maxSellQty * agreeunit
+    // 预估手续费
+    const sellEstimatedFee = (sellFeeValue.FeeAlgorithm === 2 ? amount : estimatedAmount) * sellFeeValue.feeValue
+
+    return {
+        estimatedAmount,
+        maxBalance,
+        maxBuyQty,
+        buyEstimatedFee,
+        maxAmount,
+        maxSellQty,
+        sellEstimatedFee
+    }
+})
+
+// 表单验证规则
+const formRules: { [key: string]: FieldRule[] } = {
+    OrderPrice: [{
+        message: '请输入价格',
+        validator: () => {
+            return Number(formData.OrderPrice) > 0
+        }
+    }],
+    OrderQty: [{
+        message: '请输入数量',
+        validator: () => Number(formData.OrderQty) > 0
+    }],
+}
+
+const navigateToGoodsChart = () => {
+    router.push({
+        name: 'spot-goods-chart',
+        query: {
+            id: goodsId
+        }
+    })
+}
+
+const onSubmit = () => {
+    const baseAccountId = baseAccount.value?.digitalaccountid
+    const quoteAccountId = quoteAccount.value?.digitalaccountid
+
+    if (baseAccountId && quoteAccountId) {
+        fullloading((hideLoading) => {
+            formData.BaseAccountID = Long.fromString(baseAccountId)
+            formData.QuoteAccountID = Long.fromString(quoteAccountId)
+            formData.GoodsID = quote.value?.goodsid
+            formData.MarketID = quote.value?.marketid
+            // 市价
+            if (formData.PriceMode === PriceMode.Market) {
+                const param112 = userStore.getSystemParamValue('112')
+                formData.OrderPrice = quote.value?.last
+                formData.MarketMaxSub = Number(param112) || 100
+            }
+
+            digitalOrder({
+                data: formData
+            }).then(() => {
+                hideLoading('提交成功', 'success')
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    } else {
+        showDialog({
+            message: '请先激活资金账户'
+        }).then(() => {
+            tabIndex.value = 1
+        })
+    }
+}
+
+onMounted(() => {
+    if (quote.value) {
+        subscribe.start(quote.value.goodscode)
+        formData.OrderPrice = quote.value.last
+    }
+
+    onActivated(() => {
+        const params = getGlobalUrlParams()
+        if (params.buyOrSell !== undefined) {
+            formData.BuyOrSell = params.buyOrSell
+        }
+    })
+})
+
+onUnmounted(() => {
+    subscribe.stop()
+})
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 59 - 0
src/packages/digital/views/listing/goods/list/index.less

@@ -0,0 +1,59 @@
+.spot {
+    .table {
+        width: 100%;
+        text-align: right;
+
+        &-row {
+            position: relative;
+
+            &::after {
+                content: '';
+                position: absolute;
+                bottom: 0;
+                right: 0;
+                left: 0;
+                pointer-events: none;
+                border-bottom: 1px solid #292929;
+                transform: scaleY(.5);
+            }
+
+            th,
+            td {
+                &:first-child {
+                    text-align: left;
+                }
+            }
+
+            th {
+                font-size: 12px;
+                font-weight: normal;
+                color: #999;
+            }
+        }
+
+        &-cell {
+            display: flex;
+            flex-direction: column;
+            padding: 10px;
+
+            &--media {
+                flex-direction: row;
+                align-items: center;
+            }
+
+            &__image {
+                margin-right: 10px;
+            }
+
+            &__info {
+                display: flex;
+                flex-direction: column;
+            }
+
+            .text-small {
+                font-size: 12px;
+                color: #999;
+            }
+        }
+    }
+}

+ 110 - 0
src/packages/digital/views/listing/goods/list/index.vue

@@ -0,0 +1,110 @@
+<template>
+    <app-view class="spot">
+        <template #header>
+            <app-statusbar>
+                <Search shape="round" :placeholder="t('digital.search')" />
+            </app-statusbar>
+        </template>
+        <Tabs v-model:active="currentGroupId" v-if="goodsGroups.length">
+            <template v-for="(item, index) in goodsGroups" :key="index">
+                <Tab :title="item.goodsgroupname" :name="item.goodsgroupid">
+                    <table class="table" cellspacing="0" cellpadding="0">
+                        <thead class="table-thead">
+                            <tr class="table-row">
+                                <th>
+                                    <div class="table-cell">{{ $t('digital.goodscode') }}</div>
+                                </th>
+                                <th>
+                                    <div class="table-cell">{{ $t('digital.last') }}</div>
+                                </th>
+                                <th>
+                                    <div class="table-cell">{{ $t('digital.updown') }}</div>
+                                </th>
+                            </tr>
+                        </thead>
+                        <tbody class="table-body">
+                            <template v-for="(item, index) in goodsList" :key="index">
+                                <tr class="table-row" @click="rowClick(item)">
+                                    <td>
+                                        <div class="table-cell table-cell--media">
+                                            <div class="table-cell__image">
+                                                <image-icon :url="getFirstImage(item.thumurls)" />
+                                            </div>
+                                            <div class="table-cell__info">
+                                                <span>
+                                                    <b>{{ item.goodscode }}</b>
+                                                </span>
+                                                <span class="text-small">
+                                                    {{ item.goodsname }}
+                                                </span>
+                                            </div>
+                                        </div>
+                                    </td>
+                                    <td>
+                                        <div class="table-cell">
+                                            <span :class="item.lastColor">
+                                                {{ handleNumberValue(item.last) }}
+                                            </span>
+                                        </div>
+                                    </td>
+                                    <td>
+                                        <div class="table-cell">
+                                            <span>
+                                                {{ parsePercent(item.change) }}
+                                            </span>
+                                        </div>
+                                    </td>
+                                </tr>
+                            </template>
+                        </tbody>
+                    </table>
+                </Tab>
+            </template>
+        </Tabs>
+        <Empty :description="t('common.nodatas')" v-else />
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, onMounted, computed } from 'vue'
+import { Search, Tab, Tabs, Empty } from 'vant'
+import { useNavigation } from '@mobile/router/navigation'
+import { parsePercent, handleNumberValue, getFirstImage } from '@/filters'
+import { i18n, useFuturesStore, useUserStore } from '@/stores'
+import quoteSocket from '@/services/websocket/quote'
+import ImageIcon from '@mobile/components/base/image-icon/index.vue'
+
+const { router } = useNavigation()
+const userStore = useUserStore()
+const futuresStore = useFuturesStore()
+const subscribe = quoteSocket.createSubscribe()
+const currentGroupId = shallowRef(0)
+const { global: { t } } = i18n
+
+const goodsGroups = userStore.userData.goodsgroups.filter((e) => e.marketid === 81201)
+
+const goodsList = computed(() => futuresStore.quotationList.filter((e) => e.goodsgroupid === currentGroupId.value))
+
+const rowClick = (row: Model.GoodsQuote) => {
+    router.push({
+        name: 'listing-goods-detail',
+        query: {
+            id: row.goodsid
+        }
+    })
+}
+
+futuresStore.onDataCompleted(() => {
+    const goodsCodes = goodsList.value.map((e) => e.goodscode)
+    subscribe.start(...goodsCodes)
+})
+
+onMounted(() => {
+    const [firstGroup] = goodsGroups
+    currentGroupId.value = firstGroup ? firstGroup.goodsgroupid : 0
+})
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

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

@@ -30,9 +30,9 @@
         </Field>
       </CellGroup>
       <CellGroup :inset="insetStyle" v-if="selectedMethod === 2">
-        <Field name="email" label="邮箱地址" v-model="formData.mobilephone" :placeholder="t('common.required')"
+        <Field name="email" :label="t('user.register.emailregister')" v-model="formData.mobilephone" :placeholder="t('common.required')"
           :rules="formRules.email" />
-        <Field v-model="formData.vcode" type="digit" name="vcode" label="验证码" :placeholder="t('common.required')"
+        <Field v-model="formData.vcode" type="digit" name="vcode" :label="t('user.register.vscode')" :placeholder="t('common.required')"
           :rules="formRules.vcode">
           <template #button>
             <Button size="small" :disabled="loading" @click="sendVerifyCode">
@@ -208,7 +208,7 @@ const formRules: { [key: string]: FieldRule[] } = {
   }],
   email: [{
     required: true,
-    message: '请输入邮箱地址',
+    message: t('user.register.tips12'),
     validator: (val) => {
       if (validateRules.email.validate(val)) {
         return true
@@ -226,7 +226,7 @@ const formRules: { [key: string]: FieldRule[] } = {
         }
         return validateRules.password.message
       }
-      return val?.length < 6 ? '密码长度不能小于6位' : true
+      return val?.length < 6 ? t('user.register.tips13') : true
     }
   }],
   confirmpassword: [{