li.shaoyi před 3 roky
rodič
revize
5f4e8ab32c

+ 4 - 3
src/business/trade/index.ts

@@ -1,4 +1,4 @@
-import { reactive, shallowRef } from 'vue'
+import { ref, shallowRef } from 'vue'
 import { v4 } from 'uuid'
 import { ClientType } from '@/constants/client'
 import { useLoginStore } from '@/stores'
@@ -9,7 +9,7 @@ export function usePurchaseOrderDesting() {
     const { getUserId, getFirstAccountId } = useLoginStore()
     const loading = shallowRef(false)
 
-    const formData = reactive<Partial<Proto.SpotPresaleDestingOrderReq>>({
+    const formData = ref<Partial<Proto.SpotPresaleDestingOrderReq>>({
         UserID: getUserId(), // 用户ID,必填
         AccountID: getFirstAccountId(), // 资金账号,必填
         ClientType: ClientType.Web // 终端类型
@@ -19,7 +19,8 @@ export function usePurchaseOrderDesting() {
         loading.value = true
         return spotPresaleDestingOrder({
             data: {
-                ...formData,
+                ...formData.value,
+                Qty: Number(formData.value.Qty),
                 ClientSerialNo: v4() // 客户端流水号
             },
             complete: () => {

+ 0 - 22
src/constants/index.ts

@@ -1,22 +0,0 @@
-/**
- * 枚举类型
- */
-export interface EnumType {
-    label: string;
-    value: number;
-    disabled?: boolean;
-}
-
-/**
- * 根据枚举值获取枚举名称
- * @param enums 
- * @param value 
- * @returns 
- */
-export function getEnumTypeName(enums: EnumType[], value: number) {
-    const item = enums.find((e) => e.value === value)
-    if (item) {
-        return item.label
-    }
-    return '--'
-}

+ 3 - 1
src/constants/menu.ts

@@ -1,4 +1,6 @@
-import { getEnumTypeName } from './index'
+import { useEnumStore } from '@/stores'
+
+const { getEnumTypeName } = useEnumStore()
 
 /**
  * 权限类型

+ 20 - 0
src/constants/unit.ts

@@ -0,0 +1,20 @@
+import { useEnumStore } from '@/stores'
+
+const { getEnumTypeList, getEnumTypeName } = useEnumStore()
+
+/**
+ * 获取商品单位列表
+ * @returns 
+ */
+export function getGoodsUnitList() {
+    return getEnumTypeList('goodsunit')
+}
+
+/**
+ * 获取商品单位名称
+ * @returns 
+ */
+export function getGoodsUnitName(value?: number) {
+    const enums = getGoodsUnitList()
+    return getEnumTypeName(enums, value)
+}

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

@@ -19,8 +19,17 @@
     &__container {
         display       : flex;
         flex-direction: column;
-        gap           : .24rem;
-        padding       : .32rem 0;
+        padding-bottom: .32rem;
+
+        /* 父元素的第一个子元素 */
+        .van-cell-group--inset:first-of-type {
+            margin-top: .32rem;
+        }
+
+        /* 相邻兄弟元素 */
+        .van-cell-group--inset+.van-cell-group--inset {
+            margin-top: .24rem;
+        }
     }
 
     &__footer {

+ 9 - 7
src/packages/mobile/components/base/select/index.vue

@@ -83,19 +83,21 @@ const onConfirm = (currentValue: PickerOption, currentIndex: number) => {
     if (selectedIndex.value !== currentIndex) {
         // 更新当前选中的值
         const item = props.options[currentIndex]
-        const value = item[props.optionProps.value]
-        selectedIndex.value = currentIndex
-        fieldRef.value?.validate()
+        if (item) {
+            const value = item[props.optionProps.value]
+            selectedIndex.value = currentIndex
+            fieldRef.value?.validate()
 
-        emit('update:modelValue', value)
-        emit('confirm', value, currentIndex)
+            emit('update:modelValue', value)
+            emit('confirm', value, currentIndex)
+        }
     }
 }
 
 watch(() => [props.modelValue, props.options], ([value, items]) => {
     selectedIndex.value = items.findIndex((e) => e[props.optionProps.value]?.toString() === value?.toString())
-},{
-    immediate:true
+}, {
+    immediate: true
 })
 </script>
 

+ 4 - 1
src/packages/mobile/views/credit/signin/index.vue

@@ -16,7 +16,10 @@
                 <div class="iconbar">
                     <ul>
                         <li @click="userSignin">
-                            <app-iconfont icon="icon-qiandao" label-direction="bottom">签到</app-iconfont>
+                            <app-iconfont icon="icon-qiandao" label-direction="bottom">
+                                <span v-if="userAccount.issigned">已签到</span>
+                                <span v-else>签到</span>
+                            </app-iconfont>
                         </li>
                         <li>
                             <app-iconfont icon="icon-jifenchoujiang" label-direction="bottom"

+ 55 - 4
src/packages/mobile/views/goods/details/components/address/index.vue

@@ -1,18 +1,69 @@
 <template>
     <app-modal direction="right" height="100%" v-model:show="showModal">
-        <app-view class="goods-details-address">
+        <app-view class="goods-details-address g-form">
             <template #header>
-                <app-navbar title="自提信息" @back="closed" />
+                <app-navbar :title="formItem.THJDeliveryMode === 2 ? '自提信息' : '代办运输信息'" @back="closed" />
+            </template>
+            <Form ref="formRef" class="g-form__container" @submit="onSubmit">
+                <CellGroup title="联系信息" inset>
+                    <Field v-model="formItem.ContactName" name="username" label="联系人" placeholder="必填"
+                        :rules="formRules.ContactName" />
+                    <Field v-model="formItem.ContactInfo" name="username" label="联系方式" placeholder="必填"
+                        :rules="formRules.ContactInfo" />
+                    <Field v-model="formItem.DesAddress" name="username" label="目的地地址" placeholder="必填"
+                        :rules="formRules.DesAddress" v-if="formItem.THJDeliveryMode === 3" />
+                </CellGroup>
+                <CellGroup title="发票信息" inset>
+                    <Field v-model="formItem.ReceiptInfo" type="textarea" name="username" label="发票信息" rows="3"
+                        placeholder="选填" :rules="formRules.ReceiptInfo" />
+                </CellGroup>
+            </Form>
+            <template #footer>
+                <div class="g-form__footer">
+                    <Button type="primary" @click="formRef?.submit" round block>确认</Button>
+                </div>
             </template>
         </app-view>
     </app-modal>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef } from 'vue'
+import { shallowRef, reactive, PropType } from 'vue'
+import { CellGroup, Button, Field, Form, FormInstance, FieldRule } from 'vant'
 import AppModal from '@/components/base/modal/index.vue'
 
-const showModal = shallowRef(true);
+const props = defineProps({
+    formData: {
+        type: Object as PropType<Proto.SpotPresaleDestingOrderReq>,
+        required: true
+    }
+})
+
+const emit = defineEmits(['updated'])
+const formRef = shallowRef<FormInstance>()
+const showModal = shallowRef(true)
+const formItem = reactive({ ...props.formData })
+
+// 表单验证规则
+const formRules: { [key in keyof Proto.SpotPresaleDestingOrderReq]?: FieldRule[] } = {
+    ContactName: [{
+        required: true,
+        message: '请输入联系人',
+    }],
+    ContactInfo: [{
+        required: true,
+        message: '请输入联系方式',
+    }],
+    DesAddress: [{
+        required: true,
+        message: '请输入目的地地址',
+    }],
+}
+
+const onSubmit = () => {
+    emit('updated', formItem)
+    closed()
+}
 
 // 关闭弹窗
 const closed = () => {

+ 50 - 1
src/packages/mobile/views/goods/details/index.less

@@ -1 +1,50 @@
-.goods-details {}
+.goods-details {
+    &__form {
+        .form {
+            &-month {
+                display: flex;
+
+                .app-select {
+                    flex: 1;
+                }
+
+                .van-field {
+                    padding: 0;
+
+                    input {
+                        width: 100%;
+                    }
+                }
+            }
+
+            &-qty {
+                width: 100%;
+
+                &__input {
+                    display: flex;
+
+                    input {
+                        flex: 1;
+                    }
+                }
+
+                span {
+                    color: #999;
+                }
+            }
+
+            &-address {
+                display       : flex;
+                flex-direction: column;
+
+                &__placeholder {
+                    color: var(--van-field-placeholder-text-color);
+                }
+            }
+        }
+    }
+
+    &__content {
+        background-color: #fff;
+    }
+}

+ 167 - 39
src/packages/mobile/views/goods/details/index.vue

@@ -3,37 +3,83 @@
         <template #header>
             <app-navbar title="采购详情" />
         </template>
-        <Form ref="formRef" @submit="onSubmit">
+        <Form ref="formRef" class="goods-details__form" @submit="onSubmit">
             <CellGroup>
                 <app-select v-model="formData.THJDeliveryMode" name="THJDeliveryMode" label="交割方式"
                     :rules="formRules.THJDeliveryMode" :options="details.deliverymodes"
                     :optionProps="{ label: 'enumdicname', value: 'enumitemname' }" />
-                <Field label="交割月份">
+                <Field label="交割月份" name="PresaleApplyID" :rules="formRules.PresaleApplyID">
                     <template #input>
-                        <app-select placeholder="开始月份" :options="deliveryMonths" :is-link="false"
-                            @confirm="onMonthChange" />
-                        <app-select v-model="presaleApplyId" placeholder="结束日期" :options="deliveryDays"
-                            :optionProps="{ label: 'enddate', value: 'presaleapplyid' }" :is-link="false"
-                            @confirm="onDayChange" />
+                        <div class="form-month">
+                            <app-select placeholder="开始月份" :options="deliveryMonths" :is-link="false"
+                                @confirm="onMonthChange" />
+                            <app-select placeholder="结束日期" :options="deliveryDays"
+                                :optionProps="{ label: 'enddate', value: 'presaleapplyid' }" :is-link="false"
+                                @confirm="onDayChange" />
+                        </div>
                     </template>
                 </Field>
                 <app-select v-model="formData.DepositID" name="DepositID" label="支付方式" :rules="formRules.DepositID"
-                    :options="presaleApplyDeposits" v-if="presaleApplyId" />
-                <Field v-model="formData.Qty" name="Qty" type="digit" label="采购数量" placeholder="必填"
-                    :rules="formRules.Qty" />
-                <Field label="收货信息" placeholder="请输入" @click="openComponent('address')" is-link
-                    v-if="showReceiptInfo" />
+                    :options="presaleApplyDeposits" v-if="selectedDate?.presaleapplyid" />
+                <Field v-model="formData.Qty" name="Qty" type="digit" label="采购数量" :rules="formRules.Qty">
+                    <template #input>
+                        <div class="form-qty">
+                            <div class="form-qty__input">
+                                <input type="number" v-model="formData.Qty" placeholder="必填" />
+                                <span>≤{{ selectedDate?.remainqty ?? 0
+                                }}{{ getGoodsUnitName(details.goodsinfo?.unitid) }}</span>
+                            </div>
+                            <div class="form-qty__label">
+                                <span>定金{{ deposit }}元</span>
+                            </div>
+                        </div>
+                    </template>
+                </Field>
+                <Field label="收货信息" :rules="formRules.addressInfo" @click="openComponent('address')" is-link
+                    v-if="showAddressInfo">
+                    <template #input>
+                        <div class="form-address">
+                            <template v-if="validatorAddressInfo">
+                                <span>联系人:{{ formData.ContactName }}</span>
+                                <span>联系方式:{{ formData.ContactInfo }}</span>
+                                <span v-if="formData.THJDeliveryMode === 3">目的地:{{ formData.DesAddress }}</span>
+                                <span v-if="formData.ReceiptInfo">发票信息:{{ formData.ReceiptInfo }}</span>
+                            </template>
+                            <template v-else>
+                                <span class="form-address__placeholder">必填</span>
+                            </template>
+                        </div>
+                    </template>
+                </Field>
             </CellGroup>
         </Form>
-        <Button type="primary" @click="formRef?.submit" round block>采购下单</Button>
-        <component ref="componentRef" :is="componentMap.get(componentId)" @closed="closeComponent" v-if="componentId" />
+        <div class="goods-details__content">
+            <div class="">
+                <Button type="primary" @click="formRef?.submit" round block>采购下单</Button>
+            </div>
+            <div class="" v-if="selectedDate">
+                <span>参考价(元/{{ getGoodsUnitName(details.goodsinfo?.unitid) }})</span>
+                <span>{{ selectedDate.unitprice }}</span>
+            </div>
+            <Divider>历史价格走势</Divider>
+            <Divider>商品详情</Divider>
+            <div class="">
+                <template v-for="(url, index) in goodsImages" :key="index">
+                    <img :src="url" alt="" />
+                </template>
+            </div>
+        </div>
+        <component ref="componentRef" :is="componentMap.get(componentId)" v-bind="{ formData }" @updated="updateForm"
+            @closed="closeComponent" v-if="componentId" />
     </app-view>
 </template>
 
 <script lang="ts" setup>
 import { shallowRef, computed, defineAsyncComponent } from 'vue'
-import { CellGroup, Cell, Button, Field, Form, FormInstance, Checkbox, Toast, FieldRule, Popup, Picker, PickerOption } from 'vant'
-import { fullloading, dialog } from '@/utils/vant'
+import { CellGroup, Button, Field, Form, FormInstance, Toast, FieldRule, Divider } from 'vant'
+import { fullloading } from '@/utils/vant'
+import { getImageUrl } from '@/filters'
+import { getGoodsUnitName } from '@/constants/unit'
 import { useComponent } from '@/hooks/component'
 import { useNavigation } from '@/hooks/navigation'
 import { useWrstandardDetails } from '@/business/goods'
@@ -54,19 +100,15 @@ const formRef = shallowRef<FormInstance>()
 const { details, getWrstandardDetails } = useWrstandardDetails(wrstandardid)
 const { formData, formSubmit } = usePurchaseOrderDesting()
 
-// 表单验证规则
-const formRules: { [key in keyof Proto.SpotPresaleDestingOrderReq]?: FieldRule[] } = {
-    THJDeliveryMode: [{
-        message: '请选择交割方式',
-        validator: () => {
-            return !!formData.THJDeliveryMode
-        }
-    }],
-}
+// 交割日期列表
+const deliveryDays = shallowRef<Model.THJWrstandardDetailRsp['deliverymonth']>([])
+// 当前选中的交割日期
+const selectedDate = shallowRef<Model.THJWrstandardDetailRsp['deliverymonth'][number]>()
 
-// 是否显示收货信息
-const showReceiptInfo = computed(() => {
-    return formData.THJDeliveryMode === 2 || formData.THJDeliveryMode === 3
+// 商品图片列表
+const goodsImages = computed(() => {
+    const pictureurls = details.value.goodsinfo?.pictureurls ?? ''
+    return pictureurls.split(',').map((path) => getImageUrl(path))
 })
 
 // 交割月份列表
@@ -84,35 +126,121 @@ const deliveryMonths = computed(() => {
     return [...monthMap.values()]
 })
 
-// 交割日期列表
-const deliveryDays = shallowRef<Model.THJWrstandardDetailRsp['deliverymonth']>([])
-// 当前选中的预售申请ID
-const presaleApplyId = shallowRef('')
-
 // 预售申请列表
 const presaleApplyDeposits = computed(() => {
     const deposits = details.value.presaleapplydeposits ?? []
-    return deposits.filter((e) => e.presaleapplyid === presaleApplyId.value).map(({ depositrate, presaleapplyid }) => ({
+    const presaleApplyId = selectedDate.value?.presaleapplyid
+
+    return deposits.filter((e) => e.presaleapplyid === presaleApplyId).map(({ depositid, depositrate }) => ({
         label: `${depositrate * 100}%`,
-        value: presaleapplyid
+        value: depositid
     }))
 })
 
+// 计算定价
+const deposit = computed(() => {
+    const { Qty = 0, DepositID } = formData.value
+    const { presaleapplydeposits } = details.value
+    const { unitprice = 0 } = selectedDate.value ?? {}
+    const apply = presaleapplydeposits?.find((e) => e.depositid === DepositID) // 选中的支付方式
+
+    if (apply) {
+        return unitprice * apply.depositrate * Qty
+    }
+    return 0
+})
+
+// 是否显示收货信息
+const showAddressInfo = computed(() => {
+    const { THJDeliveryMode } = formData.value
+    return THJDeliveryMode === 2 || THJDeliveryMode === 3
+})
+
+// 验证收货信息
+const validatorAddressInfo = computed(() => {
+    const { THJDeliveryMode, ContactName, ContactInfo, DesAddress } = formData.value
+    switch (THJDeliveryMode) {
+        case 2: {
+            if (ContactName && ContactInfo) return true
+            return false
+        }
+        case 3: {
+            if (ContactName && ContactInfo && DesAddress) return true
+            return false
+        }
+    }
+    return true
+})
+
+// 表单验证规则
+const formRules: { [key in keyof Proto.SpotPresaleDestingOrderReq | 'addressInfo']?: FieldRule[] } = {
+    THJDeliveryMode: [{
+        message: '请选择交割方式',
+        validator: () => {
+            return !!formData.value.THJDeliveryMode
+        }
+    }],
+    PresaleApplyID: [
+        {
+            message: '请选择交割日期',
+            validator: () => {
+                return !!formData.value.PresaleApplyID
+            }
+        }
+    ],
+    DepositID: [{
+        message: '请选择支付方式',
+        validator: () => {
+            return !!formData.value.DepositID
+        }
+    }],
+    Qty: [{
+        message: '请输入采购数量',
+        validator: (val) => {
+            if (val) {
+                const qty = Number(val)
+                const { remainqty = 0 } = selectedDate.value ?? {}
+                if (qty > 0 && qty <= remainqty) {
+                    return true
+                }
+                return '采购数量不正确'
+            }
+            return false
+        }
+    }],
+    addressInfo: [{
+        message: '请输入收货信息',
+        validator: () => {
+            return validatorAddressInfo.value
+        }
+    }],
+}
+
 // 切换交割月份
 const onMonthChange = (value: string) => {
     const months = details.value.deliverymonth ?? []
-    deliveryDays.value = months.filter((e) => e.endmonth === value)
-    presaleApplyId.value = ''
+    deliveryDays.value = months.filter((e) => e.endmonth === value) // 重新过滤交割日期列表
+    selectedDate.value = undefined // 清除选中的交割日期
+    formData.value.PresaleApplyID = undefined
+    formRef.value?.validate('PresaleApplyID')
 }
 
 // 切换交割日期
 const onDayChange = (value: string) => {
-    presaleApplyId.value = value
+    const months = details.value.deliverymonth ?? []
+    selectedDate.value = months.find((e) => e.presaleapplyid === value)
+    formData.value.PresaleApplyID = Long.fromString(value)
+    formData.value.DepositID = undefined
+    formRef.value?.validate('PresaleApplyID')
+}
+
+// 更新表单数据
+const updateForm = (formValue: Proto.SpotPresaleDestingOrderReq) => {
+    formData.value = formValue
 }
 
 const onSubmit = () => {
     fullloading(() => {
-        formData.PresaleApplyID = Long.fromString(presaleApplyId.value)
         formSubmit().then(() => {
             Toast.success('下单成功')
         }).catch((err) => {

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

@@ -59,7 +59,7 @@ const tabList: Tabbar[] = [
 ]
 
 const onChange = (index: number) => {
-  if (![1, 2].includes(index)) {
+  if (![2].includes(index)) {
     componentId.value = tabList[index].name
   }
 }

+ 8 - 0
src/packages/mobile/views/order/list/index.vue

@@ -0,0 +1,8 @@
+<template>
+    <app-view>
+
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+</script>

+ 1 - 1
src/services/api/credit/index.ts

@@ -1,4 +1,4 @@
-import { Market } from '@/constants/market';
+import { Market } from '@/constants/market'
 import { httpRequest } from '@/services/http'
 import { HttpParams } from '@/services/http/interface'
 import { tradeServerRequest } from '@/services/socket/trade'

+ 2 - 1
src/services/api/trade/index.ts

@@ -1,3 +1,4 @@
+import { Market } from '@/constants/market'
 import { tradeServerRequest } from '@/services/socket/trade'
 import { TradeParams } from '@/services/socket/trade/interface'
 
@@ -5,7 +6,7 @@ import { TradeParams } from '@/services/socket/trade/interface'
  * 铁合金现货预售摘牌
  */
 export function spotPresaleDestingOrder(params: TradeParams<Partial<Proto.SpotPresaleDestingOrderReq>, Proto.SpotPresaleDestingOrderRsp>) {
-    return tradeServerRequest('SpotPresaleDestingOrderReq', 'SpotPresaleDestingOrderRsp', params);
+    return tradeServerRequest('SpotPresaleDestingOrderReq', 'SpotPresaleDestingOrderRsp', params, Market.THJ);
 }
 
 /**

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

@@ -17,7 +17,7 @@ interface StoreState {
     allEnums: ShallowRef<Model.EnumRsp[]>;
 }
 
-const enumKeys = ['clientType', 'scoreConfigType', 'accountBusinessCode', 'certificatetype', 'signstatus'] as const
+const enumKeys = ['clientType', 'scoreConfigType', 'accountBusinessCode', 'certificatetype', 'signstatus', 'goodsunit'] as const
 
 /**
  * 枚举存储类

+ 2 - 0
src/types/model/goods.d.ts

@@ -108,10 +108,12 @@ declare namespace Model {
             presaleapplyid: string; // 预售申请ID(184+Unix秒时间戳(10位)+xxxxxx)
             remainqty: number; // 可用数量
             tradeqty: number; // 成交数量
+            unitprice: number; // [参考价]商品单价
         }[];
         goodsinfo: {
             pictureurls: string; // 详情图片(逗号分隔)
             spotgoodsprice: number; // 现货价格
+            unitid: number; // 现货商品单位ID
             wrstandardcode: string; // 现货商品代码
             wrstandardid: number; // 现货商品ID(自增 SEQ_GOODS 确保不重复)
             wrstandardname: string; // 现货商品名称