li.shaoyi vor 2 Jahren
Ursprung
Commit
f9308e7df9

+ 2 - 2
public/config/appconfig.json

@@ -1,10 +1,10 @@
 {
   "appId": "com.muchinfo.app",
-  "appName": "多元世纪交易中心",
+  "appName": "多元世纪",
   "version": "1.0.5",
   "versionCode": "10005",
   "apiUrl": "http://192.168.31.204:8080/cfg?key=test_204",
-  "tradeChannel": "ws",
+  "tradeChannel": "http",
   "modules": [
     "register",
     "delivery"

+ 23 - 19
src/business/bank/index.ts

@@ -1,14 +1,16 @@
-import {ref, reactive, shallowRef, computed } from 'vue'
+import { ref, reactive, shallowRef, computed } from 'vue'
 import { useAccountStore, useUserStore, useLoginStore } from '@/stores'
-import { t2bBankWithdraw, 
-         queryBankAccountSign, 
-         t2bBankDeposit, 
-         queryCusBankSignBank, 
-         t2bBankSign, 
-         t2bBankCancelSign, 
-         accountFundInfoReq, 
-         queryBankCusBankExtendConfigs, 
-         t2bSMSVerificationCode } from '@/services/api/bank'
+import {
+    t2bBankWithdraw,
+    queryBankAccountSign,
+    t2bBankDeposit,
+    queryCusBankSignBank,
+    t2bBankSign,
+    t2bBankCancelSign,
+    accountFundInfoReq,
+    queryBankCusBankExtendConfigs,
+    t2bSMSVerificationCode
+} from '@/services/api/bank'
 import { SignStatus } from '@/constants/bank'
 import { decryptAES } from '@/services/websocket/package/crypto'
 import moment from "moment"
@@ -122,7 +124,7 @@ export function useDoDeposit(userid?: number) {
 
 /// 银行签约请求
 export function useDoBankSign() {
-    const { userAccount, userInfo } = useUserStore()
+    const { userInfo } = useUserStore()
     const loading = shallowRef(false)
     const bankInfo = shallowRef<Model.BankAccountSignRsp>()
 
@@ -171,9 +173,9 @@ export function useDoBankSign() {
         AccountCode: accountStore.currentAccountId.toString(),
         CertID: decryptAES(userInfo?.cardnum ?? ''),
         CertType: userInfo?.cardtypeid.toString(),
-        BankAccountName: userAccount?.accountname,
+        BankAccountName: userInfo?.customername,
         MobilePhone: userInfo?.mobile2,
-        AccountName: userAccount?.accountname,
+        AccountName: userInfo?.customername,
     })
 
     const onSubmit = async () => {
@@ -296,7 +298,7 @@ export function useBankAccouuntSign() {
 /// 查询托管银行扩展配置信息
 export function useDoCusBankExtendConfigs(extendbiztype?: number) {
     /// 托管银行拓展信息
-    const configs = ref<(Model.BankCusBankExtendConfigRsp &{value:string})[]>([])
+    const configs = ref<(Model.BankCusBankExtendConfigRsp & { value: string })[]>([])
     /// 托管银行信息
     const cusBank = shallowRef<Partial<Model.CusBankSignBankRsp>>({})
     const end = shallowRef<string>('')
@@ -314,7 +316,7 @@ export function useDoCusBankExtendConfigs(extendbiztype?: number) {
                 },
             }).then((res) => {
                 if (res.data.length != 0) {
-                    configs.value = res.data.map(obj =>({
+                    configs.value = res.data.map(obj => ({
                         ...obj,
                         value: obj.fieldcode === 'legal_name' ? (useStore.userInfo?.legalpersonname ?? '') : ''
                     }))
@@ -323,9 +325,11 @@ export function useDoCusBankExtendConfigs(extendbiztype?: number) {
         }
     })
 
-    return { configs,
-                end,
-             cusBank }
+    return {
+        configs,
+        end,
+        cusBank
+    }
 }
 
 /// 查询托管银行信息
@@ -359,7 +363,7 @@ export function useT2bSMSVerificationCode() {
     const swsFormData = reactive<Partial<Proto.t2bSWSVerificationCodeReq>>({
         Mobile: useStore.userInfo?.mobile2,
         AccountCode: accountStore.currentAccountId.toString(),
-        extend_info: JSON.stringify({ "sex" : 1 })
+        extend_info: JSON.stringify({ "sex": 1 })
     })
 
     const smsVerificationCode = async () => {

+ 41 - 13
src/components/base/hqchart/index.vue

@@ -3,9 +3,11 @@
 </template>
   
 <script lang="ts" setup>
-import { shallowRef, onMounted, onUnmounted } from 'vue'
+import { shallowRef, onMounted, onUnmounted, watch, PropType } from 'vue'
 import { Chart } from 'hqchart'
 import { timerInterceptor } from '@/utils/timer'
+import { AppTheme } from '@/constants/theme'
+import { useGlobalStore } from '@/stores'
 import ResizeObserver from 'resize-observer-polyfill'
 
 const props = defineProps({
@@ -19,18 +21,18 @@ const props = defineProps({
         type: String,
         default: '100%',
     },
-    // 图表配置项
-    option: {
-        type: Object
-    },
     dataIndex: {
         type: Number,
         default: 0,
+    },
+    defaultTheme: {
+        type: String as PropType<keyof typeof AppTheme>
     }
 })
 
 const emit = defineEmits(['ready', 'update:dataIndex'])
 const chartRef = shallowRef()
+const globalStore = useGlobalStore()
 
 const resource = Chart.JSChart.GetResource()
 resource.FrameLogo.Text = ''
@@ -39,17 +41,34 @@ resource.FrameLogo.Text = ''
 Chart.JSConsole.Chart.Log = () => ({})
 Chart.JSConsole.Complier.Log = () => ({})
 
-// 默认配置项
-const defaultOption = {
-    Symbol: 'default', // 股票代码(必填)
-    IsAutoUpdate: false,
-    IsApiPeriod: true, // 每次切换周期请求接口数据
+console.log(Chart)
+
+const setChartStyle = (defaultTheme?: keyof typeof AppTheme) => {
+    const jsChart = Chart.JSChart
+    const hqChartStyle = Chart.HQChartStyle
+    const elStyle = chartRef.value.style
+    const theme = defaultTheme ? AppTheme[defaultTheme] : globalStore.appTheme
+
+    // https://blog.csdn.net/jones2000/article/details/90286933
+    switch (theme) {
+        case AppTheme.Dark: {
+            const blackStyle = hqChartStyle.GetStyleConfig(Chart.STYLE_TYPE_ID.BLACK_ID) // 黑色风格
+            jsChart.SetStyle(blackStyle)
+            elStyle.setProperty('background-color', '#000')
+            break
+        }
+        case AppTheme.Light: {
+            const whiteStyle = hqChartStyle.GetStyleConfig(Chart.STYLE_TYPE_ID.WHITE_ID) // 白色风格
+            jsChart.SetStyle(whiteStyle)
+            elStyle.setProperty('background-color', '#fff')
+        }
+    }
 }
 
 onMounted(() => {
+    setChartStyle(props.defaultTheme)
     const el = chartRef.value
-    const chartInstance = Chart.JSChart.Init(el)
-    chartInstance.SetOption(props.option ?? defaultOption)
+    let chartInstance = Chart.JSChart.Init(el)
 
     // 自适应图表大小
     const resize = timerInterceptor.setDebounce(() => {
@@ -62,9 +81,18 @@ onMounted(() => {
 
     emit('ready', chartInstance)
 
+    watch(() => globalStore.appTheme, () => {
+        setChartStyle()
+        chartInstance.ReloadResource({
+            Resource: null,
+            Draw: true,
+            Update: true
+        })
+    })
+
     onUnmounted(() => {
         resizeObserver.unobserve(el)
-        chartInstance.ClearChart()
+        chartInstance = null // 清空实例
     })
 })
 </script>

+ 1 - 1
src/packages/gstj/views/mine/Index.vue

@@ -12,7 +12,7 @@
                         </div>
                         <div class="profile-user__info">
                             <div class="top">
-                                <span>{{ userStore.accountName }}</span>
+                                <span>{{ userStore.customerName }}</span>
                                 <Icon name="checked" color="var(--van-tag-success-color)"
                                     v-if="authStatus === AuthStatus.Certified" />
                                 <Icon name="warning" color="var(--van-tag-warning-color)" v-else />

+ 3 - 3
src/packages/mobile/components/layouts/page/index.vue

@@ -1,8 +1,8 @@
 <template>
   <router-view class="app-page" v-slot="{ Component, route }">
-    <RouterTransition :transition-name="state.transitionName" @leave="onLeave" @after-enter="onAfterEnter">
+    <RouterTransition :transition-name="transitionName" @leave="onLeave" @after-enter="onAfterEnter">
       <!-- 缓存组件,前进刷新,后退缓存 -->
-      <keep-alive :exclude="state.excludeViews">
+      <keep-alive :exclude="excludeViews">
         <component :is="handleComponent(Component, route)" :key="getRouteKey(route)" />
       </keep-alive>
     </RouterTransition>
@@ -15,7 +15,7 @@ import animateRouter from '@mobile/router/animateRouter'
 import RouterTransition from '@mobile/components/base/router-transition/index.vue'
 import http from '@/services/http'
 
-const { state } = animateRouter
+const { transitionName, excludeViews } = animateRouter.getStateRef()
 
 // 手动给组件添加 name 属性,处理缓存 exclude 无效的问题
 const handleComponent = (component: Record<'type', { name: RouteRecordName | undefined }>, route: RouteRecordNormalized) => {

+ 4 - 4
src/packages/mobile/components/modules/chart/kline.ts → src/packages/mobile/components/modules/chart/historydata.ts

@@ -29,7 +29,7 @@ export interface HistoryMinuteData {
 
 export type NetworkFilterCallback = (historyMinuteData: HistoryMinuteData) => void
 
-export function useKLine(goodsCode: string) {
+export function useHistoryData(goodsCode: string) {
     const historyData = shallowRef<Model.HistoryDatasRsp[]>([])
 
     const state = reactive({
@@ -38,18 +38,18 @@ export function useKLine(goodsCode: string) {
     })
 
     const networkFilter = (data: NetworkFilterData, callback: NetworkFilterCallback) => {
-        const symbol = data.Request.Data.symbol
         data.PreventDefault = true
-
         switch (data.Name) {
+            // 历史分钟数据
             case 'KLineChartContainer::ReqeustHistoryMinuteData':
                 getHistoryData().then(() => {
                     callback({
-                        symbol,
+                        symbol: data.Request.Data.symbol,
                         data: handleChartData(historyData.value)
                     })
                 })
                 break
+            // 历史数据
             case 'KLineChartContainer::RequestHistoryData':
                 break
         }

+ 3 - 106
src/packages/mobile/components/modules/chart/index.vue

@@ -1,111 +1,8 @@
 <template>
-    <HQChart :option="chartOption" width="1000px" height="600px" @ready="onReady" />
-    <ul>
-        <li @click="changePeriod(4)">1分钟</li>
-        <li @click="changePeriod(5)">5分钟</li>
-    </ul>
+    <MLine />
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, onMounted, onUnmounted } from 'vue'
-import { ChartCycleType } from '@/constants/chart'
-import { useFuturesStore } from '@/stores'
-import { useKLine, NetworkFilterData, NetworkFilterCallback } from './kline'
-import quoteSocket from '@/services/websocket/quote'
-import HQChart from '@/components/base/hqchart/index.vue'
-
-const props = defineProps({
-    goodsCode: {
-        type: String,
-        default: 'XAUUSD'
-    }
-})
-
-const { cycleType, networkFilter, updateLastData } = useKLine(props.goodsCode)
-const { quoteWatch } = useFuturesStore()
-const chartSymbol = '600000.sh' // 股票代码(必填)
-const chartInstance = shallowRef() // 图表实例
-const subscribe = quoteSocket.createSubscribe()
-
-// https://blog.csdn.net/jones2000/article/details/90272733
-const chartOption = {
-    Symbol: chartSymbol,
-    Type: '历史K线图',
-    IsAutoUpdate: false,
-    IsApiPeriod: true, // 每次切换周期请求接口数据
-    NetworkFilter: (data: NetworkFilterData, callback: NetworkFilterCallback) => networkFilter(data, callback),
-    Windows: [
-        { Index: 'MA', Modify: false, Change: false },
-        { Index: 'VOL', Modify: false, Change: false }
-    ],
-    IsShowCorssCursorInfo: true,
-    Border: {
-        Left: 1,
-        Right: 1, //右边间距
-        Top: 25,
-        Bottom: 25,
-    },
-    KLine: {
-        Right: 1,
-        // [30001-32000) 秒周期
-        // 0=日线 1=周线 2=月线 3=年线 9=季线  [40001-50000) 自定义日线
-        // 4=1分钟 5=5分钟 6=15分钟 7=30分钟 8=60分钟 11=120分钟 12=240分钟 [20001-30000) 自定义分钟
-        Period: 4,
-        PageSize: 50,
-        IsShowTooltip: true,
-    },
-    KLineTitle:
-    {
-        IsShowName: false, // 不显示股票名称
-        IsShowSettingInfo: false // 不显示周期/复权
-    },
-    Frame: [
-        {
-            Custom: [
-                {
-                    Type: 0,
-                    Position: 'right',
-                }
-            ],
-            IsShowRightText: false // 是否显示Y轴右侧刻度
-        },
-        {
-            IsShowRightText: false // 是否显示Y轴右侧刻度
-        }
-    ]
-}
-
-// 切换周期
-const changePeriod = (period: number) => {
-    switch (period) {
-        case 1:
-            cycleType.value = ChartCycleType.Week
-            break
-        case 4:
-            cycleType.value = ChartCycleType.Minutes
-            break
-        case 5:
-            cycleType.value = ChartCycleType.Minutes5
-            break
-    }
-    //chartInstance.value.ChangeSymbol(chartSymbol) // 切换股票
-    chartInstance.value.ChangePeriod(period) // 切换周期
-}
-
-const onReady = (chart: unknown) => {
-    chartInstance.value = chart
-}
-
-quoteWatch(props.goodsCode, (q) => {
-    const data = updateLastData(q)
-    if (chartInstance.value && data.length) {
-        chartInstance.value.JSChartContainer.RecvMinuteRealtimeDataV2({
-            symbol: chartSymbol,
-            data
-        })
-    }
-})
-
-onMounted(() => subscribe.start(props.goodsCode))
-onUnmounted(() => subscribe.stop())
+import KLine from './kline.vue'
+import MLine from './mline.vue'
 </script>

+ 127 - 0
src/packages/mobile/components/modules/chart/kline.vue

@@ -0,0 +1,127 @@
+<template>
+    <HQChart width="1000px" height="500px" default-theme="Dark" @ready="onReady" />
+    <ul>
+        <li @click="changePeriod(4)">1分钟</li>
+        <li @click="changePeriod(5)">5分钟</li>
+    </ul>
+    <ul>
+        <li @click="changeIndex('VOL')">VOL</li>
+        <li @click="changeIndex('MACD')">MACD</li>
+        <li @click="changeIndex('KDJ')">KDJ</li>
+        <li @click="changeIndex('CCI')">CCI</li>
+    </ul>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, onMounted, onUnmounted } from 'vue'
+import { ChartCycleType } from '@/constants/chart'
+import { useFuturesStore } from '@/stores'
+import { useHistoryData, NetworkFilterData, NetworkFilterCallback } from './historydata'
+import quoteSocket from '@/services/websocket/quote'
+import HQChart from '@/components/base/hqchart/index.vue'
+
+const props = defineProps({
+    goodsCode: {
+        type: String,
+        default: 'XAUUSD'
+    }
+})
+
+const { cycleType, networkFilter, updateLastData } = useHistoryData(props.goodsCode)
+const { quoteWatch } = useFuturesStore()
+const chartSymbol = '600000.sh' // 股票代码(必填)
+const chartInstance = shallowRef() // 图表实例
+const subscribe = quoteSocket.createSubscribe()
+
+// https://blog.csdn.net/jones2000/article/details/90272733
+const chartOption = {
+    Symbol: chartSymbol,
+    Type: '历史K线图',
+    IsAutoUpdate: false,
+    IsApiPeriod: true, // 每次切换周期请求接口数据
+    NetworkFilter: (data: NetworkFilterData, callback: NetworkFilterCallback) => networkFilter(data, callback),
+    // 窗口指标
+    Windows: [
+        { Index: 'MA' },
+        { Index: 'VOL' }
+    ],
+    IsShowCorssCursorInfo: true,
+    Border: {
+        Left: 1,
+        Right: 1, //右边间距
+        Top: 25,
+        Bottom: 25,
+    },
+    KLine: {
+        Right: 1,
+        // [30001-32000) 秒周期
+        // 0=日线 1=周线 2=月线 3=年线 9=季线  [40001-50000) 自定义日线
+        // 4=1分钟 5=5分钟 6=15分钟 7=30分钟 8=60分钟 11=120分钟 12=240分钟 [20001-30000) 自定义分钟
+        Period: 4,
+        PageSize: 50,
+        IsShowTooltip: true,
+    },
+    KLineTitle:
+    {
+        IsShowName: false, // 不显示股票名称
+        IsShowSettingInfo: false // 不显示周期/复权
+    },
+    Frame: [
+        {
+            Custom: [
+                {
+                    Type: 0,
+                    Position: 'right',
+                }
+            ],
+            IsShowRightText: false // 是否显示Y轴右侧刻度
+        },
+        {
+            IsShowRightText: false // 是否显示Y轴右侧刻度
+        }
+    ]
+}
+
+// 切换周期
+const changePeriod = (period: number) => {
+    switch (period) {
+        case 1:
+            cycleType.value = ChartCycleType.Week
+            break
+        case 4:
+            cycleType.value = ChartCycleType.Minutes
+            break
+        case 5:
+            cycleType.value = ChartCycleType.Minutes5
+            break
+    }
+    //chartInstance.value.ChangeSymbol(chartSymbol) // 切换股票
+    chartInstance.value.ChangePeriod(period) // 切换周期
+}
+
+// 切换指标
+const changeIndex = (name: string) => {
+    // ChangeIndex(windowIndex, indexName, option)
+    chartInstance.value.ChangeIndex(1, name)
+}
+
+const onReady = (chart: unknown) => {
+    chartInstance.value = chart
+    chartInstance.value.SetOption(chartOption)
+}
+
+quoteWatch(props.goodsCode, (q) => {
+    const data = updateLastData(q)
+    const { JSChartContainer } = chartInstance.value ?? {}
+    // 在 SetOption 之后才会出现 JSChartContainer 属性
+    if (JSChartContainer && data.length) {
+        JSChartContainer.RecvMinuteRealtimeDataV2({
+            symbol: chartSymbol,
+            data
+        })
+    }
+})
+
+onMounted(() => subscribe.start(props.goodsCode))
+onUnmounted(() => subscribe.stop())
+</script>

+ 190 - 0
src/packages/mobile/components/modules/chart/minutedata.ts

@@ -0,0 +1,190 @@
+import { reactive, toRefs, shallowRef } from 'vue'
+import { queryTSData } from '@/services/api/quote'
+import { useFuturesStore } from '@/stores'
+import moment from 'moment'
+
+// https://blog.csdn.net/jones2000/article/details/100181279
+export interface NetworkFilterData {
+    Name: string; // 原始的类名::函数名
+    Explain: string;
+    PreventDefault: boolean; // 是否阻止默认网路协议发送, 默认false, 如果设置成true, HQChart就不会再请求数据
+    Request: {
+        Data: {
+            symbol: string;
+            count: number;
+            field: string[];
+            klineDrawType: number;
+            period: number;
+            right: number;
+        }
+    }
+}
+
+// https://blog.csdn.net/jones2000/article/details/100149703
+export interface HistoryMinuteData {
+    name?: string; // 股票名称
+    symbol: string; // 股票代码
+    yclose: number; // 昨收
+    open: number; // 当日开盘价
+    high: number; // 当日最高价
+    low: number; // 当日最低价
+    vol: number; // 当日成交量
+    amount: number; // 当日成交金额
+    price: number; // 最新价/收盘价
+    yclearing?: number; // 结算价
+    date: number; // 交易日期
+    time: number; // 最新的交易时间
+    minute: MinuteData[]; // 分钟数据
+    minutecount?: number; // 分钟数据个数
+    before?: []; // 集合竞价数据
+}
+
+export interface MinuteData {
+    price: number; // 价格
+    open: number; // 开盘价
+    high: number; // 最高 
+    low: number; // 最低
+    vol: number; // 成交量
+    amount: number; // 成交金额
+    date: number; // 交易日期 格式:yyyymmdd 如20191011=>2019-10-11
+    time: number; // 交易时间 格式:hhmm 如930 就是9:30
+    avprice: number; // 均价
+    increase?: number; // 涨幅
+    risefall?: number; // 涨跌
+    lead?: number; // 领先指标(指数才有)
+    position?: number; // 持仓量(期货才有)
+    yclearing?: number; // 昨结算价(期货才有)
+}
+
+export type NetworkFilterCallback = (historyMinuteData: { stock: HistoryMinuteData[] }) => void
+
+export function useMinuteData(goodsCode: string) {
+    const futuresStore = useFuturesStore()
+    const quote = futuresStore.getGoodsQuote(goodsCode)
+    const historyData = shallowRef<Model.TSDataRsp>()
+
+    const state = reactive({
+        loading: false,
+    })
+
+    const networkFilter = (data: NetworkFilterData, callback: NetworkFilterCallback) => {
+        data.PreventDefault = true
+        if (data.Name === 'MinuteChartContainer::RequestMinuteData') {
+            getTSData().then((res) => {
+                const { opened = 0, highest = 0, lowest = 0, totalvolume = 0, totalturnover = 0, last = 0 } = quote.value ?? {}
+                const lastItem=res.data.historyDatas[res.data.historyDatas.length-1]
+                callback({
+                    stock: [
+                        {
+                            symbol: data.Request.Data.symbol[0],
+                            yclose: res.data.preSettle,
+                            open: opened,
+                            high: highest,
+                            low: lowest,
+                            vol: totalvolume,
+                            amount: totalturnover,
+                            price: last,
+                            date: Number(moment(lastItem.ts).format('YYYYMMDD')),
+                            time: Number(moment(lastItem.ts).format('HHmm')),
+                            minute: res.data.historyDatas.map((e) => ({
+                                price: e.c,
+                                open: e.o,
+                                high: e.h,
+                                low: e.l,
+                                vol: e.tv,
+                                amount: e.tt,
+                                date: Number(moment(e.ts).format('YYYYMMDD')),
+                                time: Number(moment(e.ts).format('HHmm')),
+                                avprice: totalturnover/totalvolume
+                            }))
+                        }
+                    ]
+                })
+            })
+        }
+    }
+
+    // 获取历史行情
+    const getTSData = () => {
+        return queryTSData({
+            data: {
+                goodsCode,
+            }
+        })
+    }
+
+    // 处理图表数据
+    const handleChartData = (data: Model.HistoryDatasRsp[]) => {
+        return data.map((e, i) => {
+            const date = Number(moment(e.ts).format('YYYYMMDD'))
+            const time = Number(moment(e.ts).format('HHmm'))
+            const preclose = data[i - 1] // 上一个收盘价
+            const close = preclose ? preclose.c : e.c
+
+            return [
+                date, // 日期
+                close, // 前收盘
+                e.o, // 开盘价
+                e.h, // 最高
+                e.l, // 最低
+                e.c, // 收盘
+                e.tv, // 成交量
+                e.tt, // 成交金额
+                time, // 时间
+                e.hv // 持仓量
+            ]
+        })
+    }
+
+    // 更新最新数据
+    const updateLastData = (q: Partial<Model.QuoteDayRsp>) => {
+        const { last, lasttime } = q
+        // if (last && lasttime) {
+        //     const lastIndex = historyData.value.length - 1 // 历史数据最后索引位置
+        //     const cycleMilliseconds = getCycleMilliseconds()
+
+        //     const oldTime = lastIndex === -1 ? moment(lasttime) : moment(historyData.value[lastIndex].ts) // 历史行情最后时间
+        //     const diffTime = moment(lasttime).valueOf() - oldTime.valueOf() // 计算时间差
+
+        //     // 判断时间差是否大于周期时间
+        //     if (lastIndex === -1 || diffTime > cycleMilliseconds) {
+        //         historyData.value.push({
+        //             o: last,
+        //             h: last,
+        //             l: last,
+        //             c: last,
+        //             tv: q.lastvolume ?? 0,
+        //             tt: q.Lastturnover ?? 0,
+        //             hv: q.holdvolume ?? 0,
+        //             s: q.settle ?? 0,
+        //             ts: lasttime,
+        //             f: false
+        //         })
+        //     } else {
+        //         // 更新列表中最后一条记录的数据
+        //         const record = historyData.value[lastIndex]
+        //         if (record.l > last) {
+        //             record.l = last // 更新最低价
+        //         }
+        //         if (record.h < last) {
+        //             record.h = last // 更新最高价
+        //         }
+        //         record.c = last // 更新收盘价
+        //         record.tv = q.lastvolume ?? record.tv
+        //         record.tt = q.Lastturnover ?? record.tt
+        //         record.hv = q.holdvolume ?? record.hv
+        //         record.s = q.settle ?? record.s
+        //     }
+
+        //     const lastValue = historyData.value.slice(-1)
+        //     return handleChartData(lastValue)
+        // }
+        return []
+    }
+
+    return {
+        ...toRefs(state),
+        networkFilter,
+        updateLastData
+    }
+}

+ 67 - 0
src/packages/mobile/components/modules/chart/mline.vue

@@ -0,0 +1,67 @@
+<template>
+    <HQChart width="1000px" height="500px" @ready="onReady" default-theme="Dark" />
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, onMounted, onUnmounted } from 'vue'
+import { useFuturesStore } from '@/stores'
+import { useMinuteData, NetworkFilterData, NetworkFilterCallback } from './minutedata'
+import quoteSocket from '@/services/websocket/quote'
+import HQChart from '@/components/base/hqchart/index.vue'
+
+const props = defineProps({
+    goodsCode: {
+        type: String,
+        default: 'XAUUSD'
+    }
+})
+
+const { networkFilter, updateLastData } = useMinuteData(props.goodsCode)
+const { quoteWatch } = useFuturesStore()
+const chartSymbol = '600000.sh' // 股票代码(必填)
+const chartInstance = shallowRef() // 图表实例
+const subscribe = quoteSocket.createSubscribe()
+
+// https://blog.csdn.net/jones2000/article/details/90272733
+const chartOption = {
+    Symbol: chartSymbol,
+    Type: '分钟走势图',
+    IsAutoUpdate: false,
+    NetworkFilter: (data: NetworkFilterData, callback: NetworkFilterCallback) => networkFilter(data, callback),
+    IsShowCorssCursorInfo: true,
+    Border: {
+        Left: 1,
+        Right: 1, //右边间距
+        Top: 25,
+        Bottom: 25,
+    },
+    Windows: [
+    ],
+    MinuteLine: {
+    },
+    MinuteLineTitle:
+    {
+        IsShowName: false, // 不显示股票名称
+    }
+}
+
+const onReady = (chart: unknown) => {
+    chartInstance.value = chart
+    chartInstance.value.SetOption(chartOption)
+}
+
+quoteWatch(props.goodsCode, (q) => {
+    const data = updateLastData(q)
+    const { JSChartContainer } = chartInstance.value ?? {}
+    // 在 SetOption 之后才会出现 JSChartContainer 属性
+    if (JSChartContainer && data.length) {
+        // JSChartContainer.RecvMinuteRealtimeDataV2({
+        //     symbol: chartSymbol,
+        //     data
+        // })
+    }
+})
+
+onMounted(() => subscribe.start(props.goodsCode))
+onUnmounted(() => subscribe.stop())
+</script>

+ 88 - 75
src/packages/mobile/router/animateRouter.ts

@@ -1,38 +1,47 @@
-import { ref, toRefs, readonly } from 'vue'
+import { ref, toRefs } from 'vue'
 import { createRouter, RouterOptions, RouteRecordRaw, RouteLocationNormalized } from 'vue-router'
 
+interface historyRoute {
+    name: string;
+    fullPath: string;
+}
+
 interface HistoryState {
-    history: RouteLocationNormalized[]; // 已访问的路由列表
-    excludeViews: string[]; // 不缓存的页面
+    historyRoutes: historyRoute[]; // 历史路由列表
+    excludeViews: string[]; // 不缓存的视图
     actionName: '' | 'push' | 'replace' | 'forward' | 'back'; // 当前路由动作
     transitionName: '' | 'route-out' | 'route-in'; // 前进后退动画
     goStep: number; // 前进后退步数
 }
 
 export default new (class {
-    private storageKey = 'history_mobile';
+    private storageKey = 'history_mobile'
 
-    private _state = ref<HistoryState>({
-        history: [],
+    private state = ref<HistoryState>({
+        historyRoutes: [],
         excludeViews: [],
         actionName: '',
         transitionName: '',
-        goStep: 0,
+        goStep: 0
     })
 
-    /** 只读状态 */
-    state;
-
     constructor() {
-        const state = sessionStorage.getItem(this.storageKey);
+        const state = sessionStorage.getItem(this.storageKey)
         if (state) {
             // 合并赋值,防止 sessionStorage 获取的值有问题
-            this._state.value = {
-                ...this._state.value,
+            this.state.value = {
+                ...this.state.value,
                 ...JSON.parse(state)
-            };
+            }
         }
-        this.state = readonly(toRefs(this._state.value));
+    }
+
+    getState() {
+        return this.state.value
+    }
+
+    getStateRef() {
+        return toRefs(this.state.value)
     }
 
     /**
@@ -41,56 +50,55 @@ export default new (class {
      * @returns 
      */
     create = (options: RouterOptions) => {
-        const router = createRouter(options);
-        const { push, replace, go, forward, back } = router;
-        const { actionName, goStep } = toRefs(this._state.value);
+        const router = createRouter(options)
+        const { push, replace, go, forward, back } = router
+        const { excludeViews, actionName, goStep } = this.getStateRef()
 
         // 添加
         router.push = (to: RouteRecordRaw) => {
-            actionName.value = 'push';
-            return push(to);
+            actionName.value = 'push'
+            return push(to)
         }
 
         // 替换
         router.replace = (to: RouteRecordRaw) => {
-            actionName.value = 'replace';
-            return replace(to);
+            actionName.value = 'replace'
+            return replace(to)
         }
 
         // 前进后退
         router.go = (delta: number) => {
-            goStep.value = delta;
+            goStep.value = delta
             if (delta > 0) {
-                actionName.value = 'forward';
+                actionName.value = 'forward'
             }
             if (delta < 0) {
-                actionName.value = 'back';
+                actionName.value = 'back'
             }
-            go(delta);
+            go(delta)
         }
 
         // 前进
         router.forward = () => {
-            actionName.value = 'forward';
-            forward();
+            actionName.value = 'forward'
+            forward()
         }
 
         // 后退
         router.back = () => {
-            actionName.value = 'back';
-            back();
+            actionName.value = 'back'
+            back()
         }
 
         router.beforeResolve((to) => {
-            this.addHistory(to);
+            this.addHistory(to)
         })
 
         router.afterEach(() => {
-            const { excludeViews } = toRefs(this._state.value);
-            excludeViews.value = [];
+            excludeViews.value = []
         })
 
-        return router;
+        return router
     }
 
     /**
@@ -98,81 +106,86 @@ export default new (class {
      * @param route 
      */
     private addHistory = (route: RouteLocationNormalized) => {
-        const { history, excludeViews, actionName, transitionName, goStep } = toRefs(this._state.value);
+        const { historyRoutes, excludeViews, actionName, transitionName, goStep } = this.getStateRef()
+        const newRoute = {
+            name: route.name as string,
+            fullPath: route.fullPath
+        }
 
         // 如果是替换动作,必定是前进
         if (actionName.value === 'replace') {
-            const lastIndex = history.value.length - 1;
-            const lastView = history.value[lastIndex];
+            const lastIndex = historyRoutes.value.length - 1
+            const lastView = historyRoutes.value[lastIndex]
 
             if (lastView) {
-                excludeViews.value.push(lastView.name as string);
-                history.value[lastIndex] = route; // 更新最后一条记录
+                excludeViews.value.push(lastView.name as string)
+                historyRoutes.value[lastIndex] = newRoute // 更新最后一条记录
             } else {
-                history.value.push(route);
+                historyRoutes.value.push(newRoute)
             }
-            transitionName.value = 'route-in'; // 前进动画
+
+            transitionName.value = 'route-in' // 前进动画
         } else {
             // 倒序查找路由所在的位置
             const index = (() => {
-                for (let i = history.value.length - 1; i >= 0; i--) {
-                    if (history.value[i].fullPath === route.fullPath) {
-                        return i;
+                for (let i = historyRoutes.value.length - 1; i >= 0; i--) {
+                    if (historyRoutes.value[i].fullPath === route.fullPath) {
+                        return i
                     }
                 }
-                return -1;
-            })();
+                return -1
+            })()
 
             if (index > -1) {
                 if (actionName.value === 'push') {
-                    history.value.push(route);
-                    transitionName.value = 'route-in'; //前进动画
+                    historyRoutes.value.push(newRoute)
+                    transitionName.value = 'route-in' //前进动画
                 } else {
-                    if (history.value.length > 1) {
-                        const i = index + 1;
-                        const n = history.value.length - i;
+                    if (historyRoutes.value.length > 1) {
+                        const i = index + 1
+                        const n = historyRoutes.value.length - i
 
-                        excludeViews.value = history.value.map((e) => e.name).slice(-n) as string[]; // 返回数组最后位置开始的n个元素
-                        history.value.splice(i, n); // 从i位置开始删除后面所有元素(包括i)
+                        excludeViews.value = historyRoutes.value.map((e) => e.name).slice(-n) // 返回数组最后位置开始的n个元素
+                        historyRoutes.value.splice(i, n) // 从i位置开始删除后面所有元素(包括i)
                     }
-                    transitionName.value = 'route-out'; //后退动画
+                    transitionName.value = 'route-out' //后退动画
                 }
             } else {
                 if (goStep.value < 0) {
-                    const start = history.value.length + goStep.value
+                    const start = historyRoutes.value.length + goStep.value
                     if (start) {
-                        history.value.splice(start)
+                        historyRoutes.value.splice(start)
                     }
                 }
                 // 忽略重定向的页面
                 if (route.redirectedFrom) {
-                    transitionName.value = 'route-in'; // 前进动画
+                    transitionName.value = 'route-in' // 前进动画
                 } else {
-                    history.value.push(route);
-                    if (history.value.length > 1) {
-                        transitionName.value = 'route-in'; // 前进动画
+                    historyRoutes.value.push(newRoute)
+                    if (historyRoutes.value.length > 1) {
+                        transitionName.value = 'route-in' // 前进动画
                     }
                 }
             }
         }
 
-        actionName.value = '';
+        actionName.value = ''
         goStep.value = 0
 
         // 处理对象循环引用
-        const getCircularReplacer = () => {
-            const seen = new WeakSet()
-            return (key: string, value: unknown) => {
-                if (typeof value === 'object' && value !== null) {
-                    if (seen.has(value)) {
-                        return
-                    }
-                    seen.add(value)
-                }
-                return value
-            }
-        }
-
-        sessionStorage.setItem(this.storageKey, JSON.stringify(this._state.value, getCircularReplacer()))
+        // const getCircularReplacer = () => {
+        //     const seen = new WeakSet()
+        //     return (key: string, value: unknown) => {
+        //         if (typeof value === 'object' && value !== null) {
+        //             if (seen.has(value)) {
+        //                 return
+        //             }
+        //             seen.add(value)
+        //         }
+        //         return value
+        //     }
+        // }
+
+        sessionStorage.setItem(this.storageKey, JSON.stringify(this.state.value))
     }
 })

+ 5 - 5
src/packages/mobile/router/navigation.ts

@@ -12,8 +12,8 @@ export function useNavigation() {
 
     // 是否有历史页面
     const hasHistory = () => {
-        const { state } = animateRouter
-        return state.history.length > 1
+        const state = animateRouter.getState()
+        return state.historyRoutes.length > 1
     }
 
     // 获取全局Url参数(只能获取一次)
@@ -71,15 +71,15 @@ export function useNavigation() {
 
     // 返回主页
     const backHome = () => {
-        const { state } = animateRouter
-        const delta = state.history.length - 1
+        const state = animateRouter.getState()
+        const delta = state.historyRoutes.length - 1
         const params = { tabIndex: 0 }
 
         if (delta) {
             setGlobalUrlParams(params)
             router.go(-delta)
         } else {
-            const page = state.history[0]
+            const page = state.historyRoutes[0]
             if (page?.name !== 'home-index') {
                 setGlobalUrlParams(params)
                 router.replace({ name: 'home-index' })

+ 15 - 16
src/packages/mobile/views/bank/sign/components/edit/Index.vue

@@ -16,16 +16,16 @@
                         :rules="formRules.BankAccountNo" />
                     <Field name="AccountName" label="姓名" readonly v-model="formData.AccountName" placeholder="请输入银行卡账户名"
                         :rules="formRules.AccountName" />
-                    <Field name="MobilePhone" label="手机号码" :readonly="userStore.userInfo?.mobile2 != ''" 
-                           maxlength="50" v-model="formData.MobilePhone" placeholder="请输入手机号码" />
+                    <Field name="MobilePhone" label="手机号码" :readonly="userStore.userInfo?.mobile2 != ''" maxlength="50"
+                        v-model="formData.MobilePhone" placeholder="请输入手机号码" />
                     <template v-for="(item, index) in configs" :key="index">
                         <template v-if="(item.fieldcode === 'verify_code')">
-                            <Field v-if="(bankInfo === undefined) && 
-                                        ((item.usabletype === 1) || 
-                                        (item.usabletype === 2 && userStore.userInfo?.userinfotype === 2) || 
-                                        (item.usabletype === 3 && userStore.userInfo?.userinfotype === 1))" 
-                            v-model="item.value"  type="digit" name="vcode" :label="item.fieldname" placeholder="必填" autocomplete="off"
-                                :rules="item.fieldname">
+                            <Field v-if="(bankInfo === undefined) &&
+                                ((item.usabletype === 1) ||
+                                    (item.usabletype === 2 && userStore.userInfo?.userinfotype === 2) ||
+                                    (item.usabletype === 3 && userStore.userInfo?.userinfotype === 1))"
+                                v-model="item.value" type="digit" name="vcode" :label="item.fieldname" placeholder="必填"
+                                autocomplete="off" :rules="item.fieldname">
                                 <template #button>
                                     <Button size="small" type="danger" :disabled="isCountdown" @click="sendVerifyCode">
                                         <span v-if="isCountdown">重新发送({{ currentTime.seconds }})</span>
@@ -35,13 +35,12 @@
                             </Field>
                         </template>
                         <template v-else>
-                            <Field v-if="(item.usabletype === 1) || 
-                            (item.usabletype === 2 && userStore.userInfo?.userinfotype === 2) || 
-                            (item.usabletype === 3 && userStore.userInfo?.userinfotype === 1)"
-                                :name="item.fieldcode" :label="item.fieldname" :readonly="item.fieldcode === 'legal_name' && userStore.userInfo?.legalpersonname != ''" 
-                                v-model="item.value"
-                                maxlength="50"
-                                :placeholder="`请输入${item.fieldname}`" />
+                            <Field v-if="(item.usabletype === 1) ||
+                                (item.usabletype === 2 && userStore.userInfo?.userinfotype === 2) ||
+                                (item.usabletype === 3 && userStore.userInfo?.userinfotype === 1)" :name="item.fieldcode"
+                                :label="item.fieldname"
+                                :readonly="item.fieldcode === 'legal_name' && userStore.userInfo?.legalpersonname != ''"
+                                v-model="item.value" maxlength="50" :placeholder="`请输入${item.fieldname}`" />
                         </template>
                     </template>
                 </CellGroup>
@@ -171,7 +170,7 @@ const formSubmit = () => {
                 closed(true)
             })
         }).catch((err) => {
-            showFailToast(err)
+            hideLoading(err, 'fail')
         })
     })
 }

+ 6 - 6
src/packages/mobile/views/bank/wallet/components/deposit/Index.vue

@@ -99,12 +99,12 @@ const formRules: { [key: string]: FieldRule[] } = {
         required: true,
         message: '请填写入金金额',
     }],
-    filePath: [{
-        message: '请上传转账凭证',
-        validator: () => {
-            return !!certificate_photo_url.value
-        }
-    }]
+    // filePath: [{
+    //     message: '请上传转账凭证',
+    //     validator: () => {
+    //         return !!certificate_photo_url.value
+    //     }
+    // }]
 }
 
 const onCopy = (status: boolean) => {

+ 1 - 1
src/packages/mobile/views/mine/Index.vue

@@ -12,7 +12,7 @@
                         </div>
                         <div class="profile-user__info">
                             <div class="top">
-                                <span>{{ userStore.accountName }}</span>
+                                <span>{{ userStore.customerName }}</span>
                                 <Icon name="checked" color="var(--van-tag-success-color)"
                                     v-if="authStatus === AuthStatus.Certified" />
                                 <Icon name="warning" color="var(--van-tag-warning-color)" v-else />

+ 2 - 2
src/packages/pc/components/layouts/header/index.vue

@@ -17,8 +17,8 @@
             </div>
             <el-dropdown class="user-dropdown" trigger="click">
                 <span class="user-dropdown__link">
-                    <img class="g-image--avatar" :title="userStore.accountName" :src="userAvatar" />
-                    <span v-if="!globalStore.isMobile">{{ userStore.accountName }}</span>
+                    <img class="g-image--avatar" :title="userStore.customerName" :src="userAvatar" />
+                    <span v-if="!globalStore.isMobile">{{ userStore.customerName }}</span>
                     <app-icon class="el-icon--right" icon="ArrowDown" />
                 </span>
                 <template #dropdown>

+ 1 - 1
src/packages/qdhs/views/mine/Index.vue

@@ -12,7 +12,7 @@
                         </div>
                         <div class="profile-user__info">
                             <div class="top">
-                                <span>{{ userStore.accountName }}</span>
+                                <span>{{ userStore.customerName }}</span>
                                 <Icon name="checked" color="var(--van-tag-success-color)"
                                     v-if="authStatus === AuthStatus.Certified" />
                                 <Icon name="warning" color="var(--van-tag-warning-color)" v-else />

+ 1 - 1
src/packages/qxst/views/mine/Index.vue

@@ -12,7 +12,7 @@
                         </div>
                         <div class="profile-user__info">
                             <div class="top">
-                                <span>{{ userStore.accountName }}</span>
+                                <span>{{ userStore.customerName }}</span>
                                 <Icon name="checked" color="var(--van-tag-success-color)"
                                     v-if="authStatus === AuthStatus.Certified" />
                                 <Icon name="warning" color="var(--van-tag-warning-color)" v-else />

+ 1 - 1
src/packages/sbyj/views/mine/index.vue

@@ -12,7 +12,7 @@
                         </div>
                         <div class="profile-user__info">
                             <div class="top">
-                                <span>{{ userStore.accountName }}</span>
+                                <span>{{ userStore.customerName }}</span>
                                 <Icon name="checked" color="var(--van-tag-success-color)"
                                     v-if="authStatus === AuthStatus.Certified" />
                                 <Icon name="warning" color="var(--van-tag-warning-color)" v-else />

+ 1 - 1
src/packages/tc/views/mine/Index.vue

@@ -12,7 +12,7 @@
                         </div>
                         <div class="profile-user__info">
                             <div class="top">
-                                <span>{{ userStore.accountName }}</span>
+                                <span>{{ userStore.customerName }}</span>
                                 <Icon name="checked" color="var(--van-tag-success-color)"
                                     v-if="authStatus === AuthStatus.Certified" />
                                 <Icon name="warning" color="var(--van-tag-warning-color)" v-else />

+ 1 - 1
src/packages/zrwyt/views/mine/Index.vue

@@ -12,7 +12,7 @@
                         </div>
                         <div class="profile-user__info">
                             <div class="top">
-                                <span>{{ userStore.accountName }}</span>
+                                <span>{{ userStore.customerName }}</span>
                                 <Icon name="checked" color="var(--van-tag-success-color)"
                                     v-if="authStatus === AuthStatus.Certified" />
                                 <Icon name="warning" color="var(--van-tag-warning-color)" v-else />

+ 1 - 1
src/stores/modules/login.ts

@@ -90,7 +90,7 @@ export const useLoginStore = defineStore(() => {
     // 清除登录信息
     const clearLoginInfo = () => {
         localData.reset('loginInfo')
-        sessionData.reset()
+        sessionData.reset('loginInfo', 'userRoutes', 'performanceStepTypes', 'performanceTemplates')
         state.loginInfo = initLoginInfo()
     }
 

+ 7 - 0
src/stores/modules/user.ts

@@ -40,6 +40,12 @@ export const useUserStore = defineStore(() => {
         return userAccount?.accountname
     })
 
+    // 客户名称
+    const customerName = computed(() => {
+        const { userInfo } = state.userData
+        return userInfo?.customername
+    })
+
     // 账户类型
     const userType = computed(() => {
         const { userAccount } = state.userData
@@ -106,6 +112,7 @@ export const useUserStore = defineStore(() => {
         userInfo,
         userAccount,
         accountName,
+        customerName,
         userType,
         userAvatar,
         hasAuth,

+ 22 - 22
src/types/model/quote.d.ts

@@ -165,31 +165,31 @@ declare namespace Model {
 
     /** 分时历史数据 响应 */
     interface TSDataRsp {
-        Count: number;
-        decimalPlace: number;
-        endTime: string;
-        goodsCode: string;
-        outGoodsCode: string;
-        preSettle: number;
-        startTime: string;
-        tradeDate: string;
-        historyDatas: HistoryDatasRsp[];
-        runSteps: RunSteps[];
+        Count: number; // 交易日应有数据个数
+        decimalPlace: number; // 小数位
+        endTime: string; // 结束时间
+        goodsCode: string; // 商品代码
+        outGoodsCode: string; // 外部商品代码
+        preSettle: number; // 昨结价
+        startTime: string; // 开始时间
+        tradeDate: string; // 交易日
+        historyDatas: HistoryDatasRsp[]; // 历史数据
+        runSteps: RunSteps[]; // 交易日开休市计划
     }
 
     interface RunSteps {
-        end: string;
-        endflag: number;
-        endtime: string;
-        endweekday: number;
-        groupid: number;
-        runstep: number;
-        sectionid: number;
-        start: string;
-        startflag: number;
-        starttime: string;
-        startweekday: number;
-        tradeweekday: number;
+        end: string; // 真实结束时间
+        endflag: number; // 结束日标识 - (-1:上日 0:当日 1:次日 )
+        endtime: string; // 结束时间(HH:mm)
+        endweekday: number; // 结束周几
+        groupid: number; // 分组ID
+        runstep: number; // 运行阶段 - 2:连续交易
+        sectionid: number; // 从 1 开始 往下编 [0为系统清盘、结算时间] SectionId = 0时,开始时间=清盘时间 开始周几= 清盘周几, 结束时间=结算时间 结束周几=结算周几
+        start: string; // 真实开始时间
+        startflag: number; // 开始日标识 - (-1:上日 0:当日 1:次日 )
+        starttime: string; // 起始时间(HH:mm)
+        startweekday: number; // 起始周几
+        tradeweekday: number; // 交易日归属 - 0:星期天、1:星期一、2:星期二、3:星期三、4:星期四、5:星期五、6:星期六
     }
 
     /** 查询行情Tik数据 请求 */