li.shaoyi 2 yıl önce
ebeveyn
işleme
b49b53465a

+ 22 - 16
package-lock.json

@@ -25,7 +25,7 @@
         "qrcode": "^1.5.1",
         "sortablejs": "^1.15.0",
         "uuid": "^8.3.2",
-        "vant": "^4.0.3",
+        "vant": "^4.5.0",
         "vue": "^3.2.13",
         "vue-class-component": "^8.0.0-0",
         "vue-i18n": "^9.2.2",
@@ -2625,9 +2625,12 @@
       "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw=="
     },
     "node_modules/@vant/use": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.4.3.tgz",
-      "integrity": "sha512-rSnETN7P9qT1WbItMpQxBqe3cHeK2ZFYp1sCxWUXaTeI71TqA8sOdzC36ledZ36NQgFNTch9fsRPYOkrCgZfQA=="
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.5.1.tgz",
+      "integrity": "sha512-Zxd7lDz/LliVYEQi3PR9a8CQa/kGCVzF0u9hqDMaTlgXlbG0wHMFPllrcG0ThR6bfs8xrYVuSFM9pJn6HSoUGQ==",
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
     },
     "node_modules/@vue/babel-helper-vue-jsx-merge-props": {
       "version": "1.2.1",
@@ -12391,12 +12394,13 @@
       }
     },
     "node_modules/vant": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmmirror.com/vant/-/vant-4.0.3.tgz",
-      "integrity": "sha512-rBQCSdMyYxO9I3weQmdTxY28OkVLM7MSIjMx3qwMTsFONek6bodVGEYXF0+mzh/5EBMVXjqtHj6CvD6QphKkCg==",
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/vant/-/vant-4.5.0.tgz",
+      "integrity": "sha512-MK7TlTvp+n0HRFAi7SoRZwTt1pquJ2aUa8nQ899Mf+x9gi8OLYrMFqEQX+l1e4Cl4RO0vD1Q5w9rs4+Wehesog==",
       "dependencies": {
         "@vant/popperjs": "^1.3.0",
-        "@vant/use": "^1.4.3"
+        "@vant/use": "^1.5.1",
+        "@vue/shared": "^3.0.0"
       },
       "peerDependencies": {
         "vue": "^3.0.0"
@@ -15384,9 +15388,10 @@
       "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw=="
     },
     "@vant/use": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.4.3.tgz",
-      "integrity": "sha512-rSnETN7P9qT1WbItMpQxBqe3cHeK2ZFYp1sCxWUXaTeI71TqA8sOdzC36ledZ36NQgFNTch9fsRPYOkrCgZfQA=="
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.5.1.tgz",
+      "integrity": "sha512-Zxd7lDz/LliVYEQi3PR9a8CQa/kGCVzF0u9hqDMaTlgXlbG0wHMFPllrcG0ThR6bfs8xrYVuSFM9pJn6HSoUGQ==",
+      "requires": {}
     },
     "@vue/babel-helper-vue-jsx-merge-props": {
       "version": "1.2.1",
@@ -22667,12 +22672,13 @@
       }
     },
     "vant": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmmirror.com/vant/-/vant-4.0.3.tgz",
-      "integrity": "sha512-rBQCSdMyYxO9I3weQmdTxY28OkVLM7MSIjMx3qwMTsFONek6bodVGEYXF0+mzh/5EBMVXjqtHj6CvD6QphKkCg==",
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/vant/-/vant-4.5.0.tgz",
+      "integrity": "sha512-MK7TlTvp+n0HRFAi7SoRZwTt1pquJ2aUa8nQ899Mf+x9gi8OLYrMFqEQX+l1e4Cl4RO0vD1Q5w9rs4+Wehesog==",
       "requires": {
         "@vant/popperjs": "^1.3.0",
-        "@vant/use": "^1.4.3"
+        "@vant/use": "^1.5.1",
+        "@vue/shared": "^3.0.0"
       }
     },
     "vary": {
@@ -23505,4 +23511,4 @@
       }
     }
   }
-}
+}

+ 2 - 2
package.json

@@ -27,7 +27,7 @@
     "qrcode": "^1.5.1",
     "sortablejs": "^1.15.0",
     "uuid": "^8.3.2",
-    "vant": "^4.0.3",
+    "vant": "^4.5.0",
     "vue": "^3.2.13",
     "vue-class-component": "^8.0.0-0",
     "vue-i18n": "^9.2.2",
@@ -60,4 +60,4 @@
     "vconsole": "^3.14.6",
     "worker-loader": "^3.0.8"
   }
-}
+}

+ 8 - 2
src/packages/mobile/assets/themes/global/global.less

@@ -238,7 +238,7 @@
             align-items: center;
             color: #fff;
             background-image: linear-gradient(to right, #ee0a24, #ff6034);
-            padding: .24rem;
+            padding: .2rem .24rem;
 
             &-left {
                 .price-text {
@@ -264,6 +264,10 @@
             padding: .24rem;
             padding-bottom: 0;
 
+            .van-tag {
+                font-weight: normal;
+            }
+
             span {
                 margin-right: .1rem;
             }
@@ -301,7 +305,9 @@
     }
 
     &__desc {
+        background-color: #fff;
         margin-top: .24rem;
+        padding-bottom: 1.2rem;
     }
 
     &__footer {
@@ -324,7 +330,7 @@
             }
 
             &-integer {
-                font-size: .36rem;
+                font-size: .32rem;
                 color: #f2270c;
             }
         }

+ 22 - 6
src/packages/mobile/router/index.ts

@@ -126,7 +126,7 @@ const routes: Array<RouteRecordRaw> = [
     component: Page,
     children: [
       {
-        path: 'list',
+        path: '',
         name: 'news-list',
         component: () => import('../views/news/list/Index.vue'),
         meta: {
@@ -148,7 +148,7 @@ const routes: Array<RouteRecordRaw> = [
     component: Page,
     children: [
       {
-        path: 'list',
+        path: '',
         name: 'market-list',
         component: () => import('../views/market/list/Index.vue'),
       },
@@ -164,7 +164,7 @@ const routes: Array<RouteRecordRaw> = [
     component: Page,
     children: [
       {
-        path: 'list',
+        path: '',
         name: 'goods-list',
         component: () => import('../views/goods/list/Index.vue'),
       },
@@ -218,7 +218,7 @@ const routes: Array<RouteRecordRaw> = [
     component: Page,
     children: [
       {
-        path: 'list',
+        path: '',
         name: 'spot-list',
         component: () => import('../views/spot/list/Index.vue'),
       },
@@ -227,6 +227,11 @@ const routes: Array<RouteRecordRaw> = [
         name: 'spot-detail',
         component: () => import('../views/spot/detail/Index.vue'),
       },
+      {
+        path: 'add',
+        name: 'spot-add',
+        component: () => import('../views/spot/add/index.vue'),
+      },
     ],
   },
   {
@@ -234,7 +239,7 @@ const routes: Array<RouteRecordRaw> = [
     component: Page,
     children: [
       {
-        path: 'list',
+        path: '',
         name: 'pricing-list',
         component: () => import('../views/pricing/list/Index.vue'),
       },
@@ -250,7 +255,7 @@ const routes: Array<RouteRecordRaw> = [
     component: Page,
     children: [
       {
-        path: 'list',
+        path: '',
         name: 'ballot-list',
         component: () => import('../views/ballot/list/Index.vue'),
       },
@@ -340,6 +345,17 @@ const routes: Array<RouteRecordRaw> = [
     ],
   },
   {
+    path: '/notice',
+    component: Page,
+    children: [
+      {
+        path: '',
+        name: 'notice-list',
+        component: () => import('../views/notice/list/index.vue'),
+      },
+    ],
+  },
+  {
     path: '/rules',
     component: Page,
     children: [

+ 3 - 7
src/packages/mobile/views/ballot/detail/Index.vue

@@ -43,7 +43,7 @@
             </template>
         </div>
         <template #footer>
-            <div class="g-detail__footer">
+            <div class="g-detail__footer" v-if="detail.presalestatus === 2">
                 <div class="price">
                     <span class="price-text">发行价:</span>
                     <span class="price-unit">¥</span>
@@ -61,7 +61,7 @@
 
 <script lang="ts" setup>
 import { computed, defineAsyncComponent } from 'vue'
-import { Button, Tag } from 'vant'
+import { Tag } from 'vant'
 import { getFileUrl, parsePercent } from '@/filters'
 import { useComponent } from '@/hooks/component'
 import { useNavigation } from '@/hooks/navigation'
@@ -99,8 +99,4 @@ const detailImages = computed(() => {
     const pictureurls = detail.pictureurls ?? ''
     return pictureurls.split(',').map((path) => getFileUrl(path))
 })
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>
+</script>

+ 0 - 129
src/packages/mobile/views/ballot/detail/index.less

@@ -1,129 +0,0 @@
-.g-detail {
-    &__buy {
-        background-color: #fff;
-
-        .topic {
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            color: #fff;
-            background-image: linear-gradient(to right, #ee0a24, #ff6034);
-            padding: .24rem;
-
-            &-left {
-                .price-text {
-                    font-size: .24rem;
-                }
-
-                .price-integer {
-                    font-size: .44rem;
-                }
-            }
-
-            &-right {
-                display: flex;
-                flex-direction: column;
-                font-size: .24rem;
-            }
-        }
-
-        .title {
-            font-size: .3rem;
-            font-weight: bold;
-            line-height: .48rem;
-            padding: .24rem;
-            padding-bottom: 0;
-
-            span {
-                margin-right: .1rem;
-            }
-        }
-
-        .qty {
-            font-size: .24rem;
-            color: #999;
-            padding: .1rem .24rem 0 .24rem;
-        }
-
-        .info {
-            background-color: #fff;
-            padding: .2rem;
-
-            ul {
-                display: flex;
-                flex-wrap: wrap;
-                font-size: .26rem;
-
-                li {
-                    display: flex;
-                    justify-content: space-between;
-                    width: 50%;
-                    padding: .08rem .24rem;
-
-                    span {
-                        &:first-child {
-                            color: #999;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    &__desc {
-        margin-top: .24rem;
-    }
-
-    &__footer {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        height: .88rem;
-        background-color: #fff;
-
-        .price {
-            padding-left: .32rem;
-
-            &-text,
-            &-unit {
-                font-size: .24rem;
-            }
-
-            &-unit {
-                color: #f2270c;
-            }
-
-            &-integer {
-                font-size: .36rem;
-                color: #f2270c;
-            }
-        }
-
-        .submit {
-            align-self: stretch;
-            display: flex;
-            margin-left: auto;
-
-            &-button {
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                height: 100%;
-                min-width: 2rem;
-                font-weight: bold;
-                color: #fff;
-                padding: 0 .48rem;
-
-                &.warning {
-                    background-image: linear-gradient(to right, #ffd01e, #ff8917);
-                    background-color: #ff8a17;
-                }
-
-                &.danger {
-                    background-image: linear-gradient(to right, #ff6034, #ee0a24);
-                    background-color: #ee270a;
-                }
-            }
-        }
-    }
-}

+ 4 - 1
src/packages/mobile/views/home/main/Index.vue

@@ -13,6 +13,9 @@
       </div>
     </app-statusbar>
     <PullRefresh class="home-main__container" v-model="refreshing" @refresh="onRefresh">
+      <app-block>
+        <Cell title="通知公告" value="更多" :to="{ name: 'notice-list' }" icon="volume" is-link />
+      </app-block>
       <app-block class="home-main__iconbar bg">
         <ul>
           <li @click="switchTab(1)">
@@ -179,7 +182,7 @@ const switchTab = (tabIndex: number) => {
 // 打开新闻详情
 const openNewsDetails = (id: number) => {
   if (id) {
-    router.push({ name: 'news-details', query: { id } })
+    router.push({ name: 'news-detail', query: { id } })
   }
 }
 

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

@@ -27,7 +27,6 @@
             .van-swipe {
                 min-height: 3rem;
                 border-radius: .1rem;
-                box-shadow: 0 0 .12rem .02rem rgba(0, 0, 0, .35);
 
                 &-item {
                     height: 3rem;

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

@@ -63,7 +63,7 @@ const emit = defineEmits(['change'])
 const { dataList, run } = useRequest(queryUserReceiveInfo)
 const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => run())
 const checkedRow = shallowRef(props.checked)
-const selectedRow = shallowRef()
+const selectedRow = shallowRef<Model.UserReceiveInfoRsp>()
 
 // 选择地址
 const onChange = (item: Model.UserReceiveInfoRsp) => {

+ 1 - 1
src/packages/mobile/views/news/detail/Index.vue

@@ -30,7 +30,7 @@
                 <template v-for="(item, index) in dataList" :key="index">
                     <Cell class="article-item" title-class="article-item__title" value-class="article-item__time"
                         :title="item.title" :value="formatDate(item.publishdate, 'MM/DD')"
-                        :to="{ name: 'news-details', query: { id: item.id } }" replace />
+                        :to="{ name: 'news-detail', query: { id: item.id } }" replace />
                 </template>
             </CellGroup>
         </template>

+ 34 - 0
src/packages/mobile/views/notice/list/components/detail/index.less

@@ -0,0 +1,34 @@
+.notice-detail {
+    background-color: #fff;
+
+    .app-view__body {
+        padding-bottom: 1.2rem;
+    }
+
+    &__container {
+        padding: .32rem;
+
+        >h1 {
+            font-size: .35rem;
+            font-weight: bold;
+        }
+
+        >h4 {
+            display: flex;
+            align-items: center;
+            gap: .12rem;
+            font-size: .24rem;
+            color: #999;
+            padding: .32rem 0;
+        }
+
+        >p {
+            font-size: .28rem;
+            line-height: .48rem;
+
+            img {
+                max-width: 100%;
+            }
+        }
+    }
+}

+ 60 - 0
src/packages/mobile/views/notice/list/components/detail/index.vue

@@ -0,0 +1,60 @@
+<template>
+    <app-modal direction="right" height="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="notice-detail">
+            <template #header>
+                <app-navbar title="公公详情" @back="closed" />
+            </template>
+            <section class="notice-detail__container">
+                <h1>{{ selectedRow.title }}</h1>
+                <h4>
+                    <span>{{ formatDate(selectedRow.createtime, 'YYYY-MM-DD') }}</span>
+                </h4>
+                <HtmlContainer :context="formatHtmlString(selectedRow.content)" />
+            </section>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType, onMounted } from 'vue'
+import { formatDate, formatHtmlString } from '@/filters'
+import { postNoticeReaded } from '@/services/api/common'
+import AppModal from '@/components/base/modal/index.vue'
+import HtmlContainer from '@mobile/components/base/html-container/index.vue'
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Model.NoticeRsp>,
+        required: true
+    }
+})
+
+const showModal = shallowRef(true)
+const refresh = shallowRef(false) // 是否刷新父组件数据
+
+// 关闭弹窗
+const closed = () => {
+    showModal.value = false
+}
+
+onMounted(() => {
+    if (!props.selectedRow.readed) {
+        postNoticeReaded({
+            data: {
+                noticeID: props.selectedRow.autoid
+            }
+        }).then(() => {
+            refresh.value = true
+        })
+    }
+})
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 79 - 0
src/packages/mobile/views/notice/list/index.vue

@@ -0,0 +1,79 @@
+<template>
+    <app-view>
+        <template #header>
+            <app-navbar title="公告通知">
+                <template #footer>
+                    <Tabs v-model:active="active" @change="onTabChange">
+                        <Tab title="公告" :name="1" />
+                        <Tab title="通知" :name="2" />
+                    </Tabs>
+                </template>
+            </app-navbar>
+        </template>
+        <app-pull-refresh ref="pullRefreshRef" v-model:loading="loading" v-model:error="error" v-model:pageIndex="pageIndex"
+            :page-count="pageCount" @refresh="onRefresh">
+            <CellGroup class="article" style="margin-top: .2rem;" v-if="dataList.length">
+                <template v-for="(item, index) in dataList" :key="index">
+                    <Cell :value="formatDate(item.createtime, 'YYYY-MM-DD')" @click="openDetail(item)">
+                        <template #title>
+                            <TextEllipsis :content="item.title" />
+                        </template>
+                    </Cell>
+                </template>
+            </CellGroup>
+        </app-pull-refresh>
+        <component ref="componentRef" v-bind="{ selectedRow }" :is="componentMap.get(componentId)" @closed="closeComponent"
+            v-if="componentId" />
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, defineAsyncComponent } from 'vue'
+import { Tab, Tabs, CellGroup, Cell, TextEllipsis } from 'vant'
+import { formatDate } from '@/filters'
+import { useComponent } from '@/hooks/component'
+import { useRequest } from '@/hooks/request'
+import { queryNotice } from '@/services/api/common'
+import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
+
+const componentMap = new Map<string, unknown>([
+    ['detail', defineAsyncComponent(() => import('./components/detail/index.vue'))],
+])
+
+const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => onTabChange())
+const active = shallowRef(1)
+const error = shallowRef(false)
+const dataList = shallowRef<Model.NoticeRsp[]>([])
+const selectedRow = shallowRef<Model.NoticeRsp>()
+
+const { loading, pageIndex, pageCount, run } = useRequest(queryNotice, {
+    params: {
+        msgType: active.value,
+    },
+    onSuccess: (res) => {
+        if (pageIndex.value === 1) {
+            dataList.value = []
+        }
+        dataList.value.push(...res.data)
+    },
+    onError: () => {
+        error.value = true
+    }
+})
+
+const openDetail = (item: Model.NoticeRsp) => {
+    selectedRow.value = item
+    openComponent('detail')
+}
+
+const onTabChange = () => {
+    pageIndex.value = 1
+    onRefresh()
+}
+
+const onRefresh = () => {
+    run({
+        msgType: active.value,
+    })
+}
+</script>

+ 1 - 1
src/packages/mobile/views/presale/detail/Index.vue

@@ -43,7 +43,7 @@
             </template>
         </div>
         <template #footer>
-            <div class="g-detail__footer">
+            <div class="g-detail__footer" v-if="detail.presalestatus === 2">
                 <div class="price">
                     <span class="price-text">起拍价:</span>
                     <span class="price-unit">¥</span>

+ 3 - 0
src/packages/mobile/views/presale/list/Index.vue

@@ -55,6 +55,7 @@ import Waterfall from '@mobile/components/base/waterfall/index.vue'
 const { router } = useNavigation()
 const bannerList = shallowRef<string[]>([])
 
+// 预售准备
 const { dataList } = useRequest(queryPresaleAuctions, {
     params: {
         presalemode: 4,
@@ -65,6 +66,7 @@ const { dataList } = useRequest(queryPresaleAuctions, {
     }
 })
 
+// 预售开始
 const { dataList: startList } = useRequest(queryPresaleAuctions, {
     params: {
         presalemode: 4,
@@ -72,6 +74,7 @@ const { dataList: startList } = useRequest(queryPresaleAuctions, {
     },
 })
 
+// 预售结束
 const { dataList: endList } = useRequest(queryPresaleAuctions, {
     params: {
         presalemode: 4,

+ 127 - 0
src/packages/mobile/views/spot/add/components/buy/index.vue

@@ -0,0 +1,127 @@
+<template>
+    <app-view class="g-form">
+        <Form ref="formRef" class="g-form__container" @submit="formSubmit">
+            <CellGroup inset>
+                <Field name="WRFactorTypeId" label="现货仓单" placeholder="请选择" v-model="selectedRow.wrfactortypeid"
+                    :rules="formRules.WRFactorTypeId" @click="showWarehouseReceipt = true" is-link readonly />
+                <template v-if="selectedRow.wrfactortypeid">
+                    <Field label="商品" v-model="selectedRow.wrstandardname" readonly />
+                    <Field label="仓库" v-model="selectedRow.warehousename" readonly />
+                    <Field label="可用量">
+                        <template #input>
+                            <span>{{ selectedRow.enableqty }}吨</span>
+                        </template>
+                    </Field>
+                </template>
+                <Field name="FixedPrice" :rules="formRules.FixedPrice" label="价格">
+                    <template #input>
+                        <Stepper v-model="formData.FixedPrice" theme="round" :min="0" :decimal-length="2" :default-value="0"
+                            :auto-fixed="false" button-size="22" />
+                    </template>
+                </Field>
+                <Field name="OrderQty" :rules="formRules.OrderQty" label="数量">
+                    <template #input>
+                        <Stepper v-model="formData.OrderQty" theme="round" button-size="22" :min="0" :default-value="0"
+                            :auto-fixed="false" integer />
+                    </template>
+                </Field>
+                <Field label="货款金额">
+                    <template #input>
+                        <span>{{ amount }}元</span>
+                    </template>
+                </Field>
+            </CellGroup>
+        </Form>
+        <template #footer>
+            <div class="g-form__footer">
+                <Button type="primary" @click="formRef?.submit" round block>提交</Button>
+            </div>
+        </template>
+        <component :is="WarehouseReceipt" v-model:show="showWarehouseReceipt" @change="onChange" />
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, defineAsyncComponent } from 'vue'
+import { onBeforeRouteLeave } from 'vue-router'
+import { CellGroup, Button, Field, Form, FormInstance, Stepper, FieldRule, showSuccessToast, showFailToast } from 'vant'
+import { fullloading } from '@/utils/vant'
+import { useNavigation } from '@/hooks/navigation'
+import { useHdWROrder } from '@/business/trade'
+
+const WarehouseReceipt = defineAsyncComponent(() => import('./warehouse-receipt.vue'))
+
+const { routerBack } = useNavigation()
+const { formData, listingSubmit, amount } = useHdWROrder()
+const formRef = shallowRef<FormInstance>()
+const showWarehouseReceipt = shallowRef(false)
+const selectedRow = shallowRef<Partial<Model.HoldLBRsp>>({}) //选中的现货仓单
+
+// 表单验证规则
+const formRules: { [key in keyof Proto.HdWROrderReq]?: FieldRule[] } = {
+    WRFactorTypeId: [{
+        message: '请选择现货仓单',
+        validator: () => {
+            return !!selectedRow.value.wrfactortypeid
+        }
+    }],
+    FixedPrice: [{
+        message: '请输入价格',
+        validator: () => {
+            return !!formData.FixedPrice
+        }
+    }],
+    OrderQty: [{
+        message: '请输入数量',
+        validator: (val) => {
+            if (val) {
+                const { enableqty = 0 } = selectedRow.value
+                if (val <= enableqty) {
+                    return true
+                }
+                return '可用数量不足'
+            }
+            return false
+        }
+    }],
+}
+
+// 选择现货仓单
+const onChange = (item: Model.HoldLBRsp) => {
+    selectedRow.value = item
+    formData.FixedPrice = item.spotgoodsprice
+    if (formData.OrderQty) {
+        formRef.value?.validate('OrderQty')
+    }
+    showWarehouseReceipt.value = false
+}
+
+// 表单提交
+const formSubmit = () => {
+    const { wrstandardid, subnum, deliverygoodsid, ladingbillid = '0', wrfactortypeid = '0' } = selectedRow.value
+    formData.WRStandardID = wrstandardid
+    formData.DeliveryGoodsID = deliverygoodsid
+    formData.LadingBillId = ladingbillid
+    formData.WRFactorTypeId = wrfactortypeid
+    formData.SubNum = subnum
+
+    fullloading(() => {
+        listingSubmit().then(() => {
+            showSuccessToast('挂牌提交成功。')
+            routerBack()
+        }).catch((err) => {
+            showFailToast(err)
+        })
+    })
+}
+
+// 离开页面前关闭组件
+onBeforeRouteLeave((to, from, next) => {
+    if (showWarehouseReceipt.value) {
+        showWarehouseReceipt.value = false
+        next(false)
+    } else {
+        next()
+    }
+})
+</script>

+ 51 - 0
src/packages/mobile/views/spot/add/components/buy/warehouse-receipt.vue

@@ -0,0 +1,51 @@
+<template>
+    <app-modal direction="right" height="100%">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar title="选择现货仓单" />
+            </template>
+            <RadioGroup class="g-form__container" v-model="checkedRow" v-if="dataList.length">
+                <CellGroup v-for="(item, index) in dataList" :key="index" @click="onChange(item)" inset>
+                    <Cell>
+                        <template #title>
+                            <Radio :name="item" checked-color="#ee0a24">
+                                <ul style="margin-left: .2rem;">
+                                    <li>
+                                        <span>商品:</span>
+                                        <span>{{ item.wrstandardname }}</span>
+                                    </li>
+                                    <li>
+                                        <span>仓库:</span>
+                                        <span>{{ item.warehousename }}</span>
+                                    </li>
+                                    <li>
+                                        <span>可用:</span>
+                                        <span>{{ item.enableqty }}吨</span>
+                                    </li>
+                                </ul>
+                            </Radio>
+                        </template>
+                    </Cell>
+                </CellGroup>
+            </RadioGroup>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef } from 'vue'
+import { RadioGroup, Radio, Cell, CellGroup } from 'vant'
+import { useRequest } from '@/hooks/request'
+import { queryHoldLB } from '@/services/api/order'
+import AppModal from '@/components/base/modal/index.vue'
+
+const emit = defineEmits(['update:show', 'change'])
+const checkedRow = shallowRef<Model.HoldLBRsp>()
+const { dataList } = useRequest(queryHoldLB)
+
+// 选择仓单
+const onChange = (item: Model.HoldLBRsp) => {
+    checkedRow.value = item
+    emit('change', item)
+}
+</script>

+ 159 - 0
src/packages/mobile/views/spot/add/components/sell/index.vue

@@ -0,0 +1,159 @@
+<template>
+    <app-view class="g-form">
+        <Form ref="formRef" class="g-form__container" @submit="formSubmit">
+            <CellGroup inset>
+                <Field name="DeliveryGoodsID" label="品类" :rules="formRules.DeliveryGoodsID" is-link>
+                    <template #input>
+                        <app-select v-model="formData.DeliveryGoodsID" :options="ftDeliveryGoodsList"
+                            :optionProps="{ label: 'deliverygoodsname', value: 'deliverygoodsid' }"
+                            @confirm="onDeliveryGoodsChange" />
+                    </template>
+                </Field>
+                <Field name="WRStandardID" label="商品" :rules="formRules.WRStandardID" is-link
+                    v-if="formData.DeliveryGoodsID">
+                    <template #input>
+                        <app-select v-model="formData.WRStandardID" :options="goodsList"
+                            :optionProps="{ label: 'wrstandardname', value: 'wrstandardid' }" @confirm="onGoodsChange" />
+                    </template>
+                </Field>
+                <Field name="FactoryItems" label="仓库" :rules="formRules.FactoryItems" is-link v-if="formData.WRStandardID">
+                    <template #input>
+                        <app-select :options="warehouseList"
+                            :optionProps="{ label: 'dgfactoryitemvalue', value: 'dgfactoryitemid' }"
+                            @confirm="onWarehouseChange" />
+                    </template>
+                </Field>
+                <Field name="FixedPrice" :rules="formRules.FixedPrice" label="价格">
+                    <template #input>
+                        <Stepper v-model="formData.FixedPrice" theme="round" :min="0" :decimal-length="2" :default-value="0"
+                            :auto-fixed="false" button-size="22" />
+                    </template>
+                </Field>
+                <Field name="OrderQty" :rules="formRules.OrderQty" label="数量">
+                    <template #input>
+                        <Stepper v-model="formData.OrderQty" theme="round" button-size="22" :min="0" :default-value="0"
+                            :auto-fixed="false" integer />
+                    </template>
+                </Field>
+                <Field label="货款金额">
+                    <template #input>
+                        <span>{{ amount }}元</span>
+                    </template>
+                </Field>
+                <Field label="可用资金">
+                    <template #input>
+                        <span>{{ accountStore.avaiableMoney.toFixed(2) }}元</span>
+                    </template>
+                </Field>
+            </CellGroup>
+        </Form>
+        <template #footer>
+            <div class="g-form__footer">
+                <Button type="primary" @click="formRef?.submit" round block>提交</Button>
+            </div>
+        </template>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, computed } from 'vue'
+import { CellGroup, Button, Field, Form, FormInstance, Stepper, FieldRule, showSuccessToast, showFailToast } from 'vant'
+import { fullloading } from '@/utils/vant'
+import { BuyOrSell } from '@/constants/order'
+import { useNavigation } from '@/hooks/navigation'
+import { useRequest } from '@/hooks/request'
+import { queryFtDeliveryGoods, queryWrStandardFactoryItem } from '@/services/api/goods'
+import { useHdWROrder } from '@/business/trade'
+import { useAccountStore } from '@/stores'
+import AppSelect from '@mobile/components/base/select/index.vue'
+
+const { routerBack } = useNavigation()
+const { formData, listingSubmit, amount } = useHdWROrder()
+const accountStore = useAccountStore()
+const formRef = shallowRef<FormInstance>()
+
+const { dataList: ftDeliveryGoodsList } = useRequest(queryFtDeliveryGoods)
+const { dataList: wrStandardFactoryItems, run: getWrStandardFactoryItems } = useRequest(queryWrStandardFactoryItem, { manual: true })
+
+const goodsList = shallowRef<Model.FtDeliveryGoodsRsp['wdlst']>([]) // 商品列表
+
+// 仓库列表
+const warehouseList = computed(() => {
+    return wrStandardFactoryItems.value.reduce((res, item) => [...res, ...item.itemlst], [] as Model.WrStandardFactoryItemRsp['itemlst'])
+})
+
+// 表单验证规则
+const formRules: { [key in keyof Proto.HdWROrderReq]?: FieldRule[] } = {
+    DeliveryGoodsID: [{
+        message: '请选择品类',
+        validator: () => {
+            return !!formData.DeliveryGoodsID
+        }
+    }],
+    WRStandardID: [{
+        message: '请选择商品',
+        validator: () => {
+            return !!formData.WRStandardID
+        }
+    }],
+    FactoryItems: [{
+        message: '请选择仓库',
+        validator: () => {
+            return !!formData.FactoryItems
+        }
+    }],
+    FixedPrice: [{
+        message: '请输入价格',
+        validator: () => {
+            return !!formData.FixedPrice
+        }
+    }],
+    OrderQty: [{
+        message: '请输入数量',
+        validator: () => {
+            return !!formData.OrderQty
+        }
+    }],
+}
+
+// 选择品类时触发
+const onDeliveryGoodsChange = (deliverygoodsid: number) => {
+    const item = ftDeliveryGoodsList.value.find((e) => e.deliverygoodsid === deliverygoodsid)
+    if (item) {
+        goodsList.value = item.wdlst
+    }
+    formData.WRStandardID = undefined
+    formData.FactoryItems = undefined
+    formRef.value?.validate('DeliveryGoodsID')
+}
+
+// 选择商品时触发
+const onGoodsChange = (wrstandardid: number) => {
+    formData.FactoryItems = undefined
+    getWrStandardFactoryItems({ wrstandardid })
+    formRef.value?.validate('WRStandardID')
+}
+
+// 选择仓库时触发
+const onWarehouseChange = (dgfactoryitemid: number) => {
+    formData.FactoryItems = [{
+        DGFactoryItemTypeID: 1, // 要素项类型ID
+        DGFactoryItemID: dgfactoryitemid, // 预约要素项类型值
+        ItemTypeMode: 1, // 要素项类型模式
+    }]
+    formRef.value?.validate('FactoryItems')
+}
+
+// 表单提交
+const formSubmit = () => {
+    formData.BuyOrSell = BuyOrSell.Buy
+    fullloading(() => {
+        listingSubmit().then(() => {
+            showSuccessToast('挂牌提交成功。')
+            routerBack()
+        }).catch((err) => {
+            showFailToast(err)
+        })
+    })
+}
+</script>

+ 21 - 0
src/packages/mobile/views/spot/add/index.vue

@@ -0,0 +1,21 @@
+<template>
+    <app-view>
+        <template #header>
+            <app-navbar title="现货下单" />
+        </template>
+        <Tabs class="van-tabs--list">
+            <Tab title="我要卖">
+                <buy />
+            </Tab>
+            <Tab title="我要买">
+                <sell />
+            </Tab>
+        </Tabs>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { Tab, Tabs } from 'vant'
+import Buy from './components/buy/index.vue'
+import Sell from './components/sell/index.vue'
+</script>

+ 6 - 2
src/packages/mobile/views/spot/list/Index.vue

@@ -1,7 +1,11 @@
 <template>
     <app-view class="spot-list">
         <template #header>
-            <app-navbar title="现货挂牌" />
+            <app-navbar title="现货挂牌">
+                <template #right>
+                    <Icon name="add" size=".4rem" @click="$router.push({ name: 'spot-add' })" />
+                </template>
+            </app-navbar>
         </template>
         <app-pull-refresh ref="pullRefreshRef" class="purchase__container" v-model:loading="loading" v-model:error="error"
             v-model:pageIndex="pageIndex" :page-count="pageCount" @refresh="run">
@@ -34,7 +38,7 @@
 
 <script lang="ts" setup>
 import { shallowRef } from 'vue'
-import { Tag } from 'vant'
+import { Tag, Icon } from 'vant'
 import { getFileUrl } from '@/filters'
 import { useRequest } from '@/hooks/request'
 import { queryOrderQuote } from '@/services/api/goods'

+ 31 - 5
src/services/api/common/index.ts

@@ -4,7 +4,6 @@ import http from '@/services/http'
 import { RequestConfig } from '@/services/http/types'
 
 const loginStore = useLoginStore()
-const { userId } = loginStore.$toRefs()
 
 /**
  * 获取应用配置信息
@@ -116,7 +115,7 @@ export function signin(config: RequestConfig<{ userid: number }> = {}) {
         method: 'post',
         url: '/Ferroalloy/Signin',
         data: {
-            userid: userId.value,
+            userid: loginStore.userId,
             ...config.data
         },
     })
@@ -171,7 +170,7 @@ export function queryTHJProfits(config: RequestConfig<Model.THJProfitsReq> = {})
     return http.commonRequest<Model.THJProfitsRsp[]>({
         url: '/Ferroalloy/QueryTHJProfits',
         params: {
-            userid: userId.value,
+            userid: loginStore.userId,
             ...config.data
         },
     })
@@ -184,7 +183,7 @@ export function queryMyDeposit(config: RequestConfig<Model.MyDepositReq> = {}) {
     return http.commonRequest<Model.MyDepositRsp[]>({
         url: '/Ferroalloy/QueryMyDeposit',
         params: {
-            userid: userId.value,
+            userid: loginStore.userId,
             ...config.data
         },
     })
@@ -197,7 +196,7 @@ export function queryTHJinvesotrdeposit(config: RequestConfig<Model.THJinvesotrd
     return http.commonRequest<Model.THJinvesotrdepositRsp[]>({
         url: '/Ferroalloy/QueryTHJinvesotrdeposit',
         params: {
-            userid: userId.value,
+            userid: loginStore.userId,
             ...config.data
         },
     })
@@ -211,4 +210,31 @@ export function queryTHJInvesotrDepositLog(config: RequestConfig<Model.THJInveso
         url: '/Ferroalloy/QueryTHJInvesotrDepositLog',
         params: config.data,
     })
+}
+
+/**
+ * 通知公告系统消息查询
+ */
+export function queryNotice(config: RequestConfig<Model.NoticeReq> = {}) {
+    return http.commonRequest<Model.NoticeRsp[]>({
+        url: '/Common/QueryNotice',
+        params: {
+            loginID: loginStore.loginId,
+            ...config.data
+        },
+    })
+}
+
+/**
+ * 通知公告设置已读
+ */
+export function postNoticeReaded(config: RequestConfig<Model.NoticeReadedReq> = {}) {
+    return http.commonRequest({
+        method: 'post',
+        url: '/Common/NoticeReaded',
+        data: {
+            loginID: loginStore.loginId,
+            ...config.data
+        },
+    })
 }

+ 2 - 1
src/stores/index.ts

@@ -6,4 +6,5 @@ export { useAccountStore } from './modules/account'
 export { useFuturesStore } from './modules/futures'
 //export { i18n, useLanguageStore } from './modules/language'
 export { useEnumStore } from './modules/enum'
-export { useErrorInfoStore } from './modules/errorInfo'
+export { useErrorInfoStore } from './modules/errorInfo'
+export { useNotice } from './modules/notice'

+ 56 - 0
src/stores/modules/notice.ts

@@ -0,0 +1,56 @@
+import { reactive, toRefs, computed } from 'vue'
+import { timerTask } from '@/utils/timer'
+import { queryNotice, postNoticeReaded } from '@/services/api/common'
+import { defineStore } from '../store'
+
+/**
+ * 系统通知存储对象
+ */
+export const useNotice = defineStore(() => {
+    const state = reactive({
+        loading: false,
+        noticeList: <Model.NoticeRsp[]>[], // 通知列表
+    })
+
+    // 未读列表
+    const unreadList = computed(() => {
+        return state.noticeList.filter((e) => !e.readed)
+    })
+
+    // 获取通知列表
+    const getNoticeList = async () => {
+        try {
+            state.loading = true
+            timerTask.clearTimeout('systemNotice')
+            const res = await queryNotice()
+            state.noticeList = res.data
+        } finally {
+            state.loading = false
+            // 轮询查询系统通知
+            timerTask.setTimeout(() => {
+                getNoticeList()
+            }, 60 * 1000, 'systemNotice')
+        }
+    }
+
+    // 更新已读
+    const updateNoticeReaded = (id: number) => {
+        const item = state.noticeList.find((e) => e.autoid === id)
+        if (item && !item.readed) {
+            postNoticeReaded({
+                data: {
+                    noticeID: id,
+                }
+            }).then(() => {
+                item.readed = true
+            })
+        }
+    }
+
+    return {
+        ...toRefs(state),
+        unreadList,
+        getNoticeList,
+        updateNoticeReaded,
+    }
+})

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

@@ -280,5 +280,43 @@ declare global {
             /// 上传文件名
             fileName?: string
         }
+
+        /** 通知公告系统消息查询 请求 */
+        interface NoticeReq {
+            page?: number; // 页码
+            pagesize?: number; // 每页条数
+            loginID?: number; // 登录账号
+            msgType?: number; // 消息类型 - 1:公告通知 2:系统消息
+            onlyUnRead?: boolean; // 是否只获取未读信息
+            lastID?: number; // 自增ID,传入后会返回这个ID后面的记录
+        }
+
+        /** 通知公告系统消息查询 响应 */
+        interface NoticeRsp {
+            auditoruserid: number; // 审核人
+            auditremark: string; // 审核备注
+            audittime: string; // 审核日期
+            autoid: number; // 自增ID
+            content: string; // 内容
+            createtime: string; // 创建时间
+            creatorid: number; // 建仓人
+            endtime: string; // 结束时间
+            istop: number; // 是否置顶 - 0:不置顶 1:置顶
+            msgiconurl: string; // 消息图标Url
+            msgtype: number; // 消息类型 - 1:公告通知 2:系统消息 3:商品到期提货通知
+            publisher: string; // 消息发布者
+            readed: boolean; // 是否已读
+            scheduletime: string; // 计划发送时间
+            sendtype: number; // 推送方式 - 1:全体广播 2:按会员广播 3:个人推送 4:按会员广播(仅会员)
+            sentstatus: number; // 推送状态 - 0:未推送 1:已推送 2:审核拒绝
+            title: string; // 标题
+            userid: number; // 会员/投资者ID推送方式 为 个人时,填写投资者ID
+        }
+
+        /** 通知公告设置已读 请求 */
+        interface NoticeReadedReq {
+            loginID?: number; // 登录账号
+            noticeID: number; // 通知公告ID
+        }
     }
 }