li.shaoyi há 3 anos atrás
pai
commit
20b1313c31

+ 112 - 0
package-lock.json

@@ -41,6 +41,8 @@
         "@vue/cli-plugin-typescript": "~5.0.0",
         "@vue/cli-service": "~5.0.0",
         "@vue/eslint-config-typescript": "^9.1.0",
+        "babel-polyfill": "^6.26.0",
+        "es6-promise": "^4.2.8",
         "eslint": "^7.32.0",
         "eslint-plugin-vue": "^8.0.3",
         "less": "^4.0.0",
@@ -4041,6 +4043,55 @@
         "@babel/core": "^7.0.0-0"
       }
     },
+    "node_modules/babel-polyfill": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
+      "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==",
+      "dev": true,
+      "dependencies": {
+        "babel-runtime": "^6.26.0",
+        "core-js": "^2.5.0",
+        "regenerator-runtime": "^0.10.5"
+      }
+    },
+    "node_modules/babel-polyfill/node_modules/core-js": {
+      "version": "2.6.12",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+      "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+      "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
+      "dev": true,
+      "hasInstallScript": true
+    },
+    "node_modules/babel-polyfill/node_modules/regenerator-runtime": {
+      "version": "0.10.5",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+      "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==",
+      "dev": true
+    },
+    "node_modules/babel-runtime": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+      "dev": true,
+      "dependencies": {
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
+      }
+    },
+    "node_modules/babel-runtime/node_modules/core-js": {
+      "version": "2.6.12",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+      "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+      "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
+      "dev": true,
+      "hasInstallScript": true
+    },
+    "node_modules/babel-runtime/node_modules/regenerator-runtime": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+      "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+      "dev": true
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -5788,6 +5839,12 @@
       "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==",
       "dev": true
     },
+    "node_modules/es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "dev": true
+    },
     "node_modules/escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -15692,6 +15749,55 @@
         "@babel/helper-define-polyfill-provider": "^0.3.1"
       }
     },
+    "babel-polyfill": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
+      "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==",
+      "dev": true,
+      "requires": {
+        "babel-runtime": "^6.26.0",
+        "core-js": "^2.5.0",
+        "regenerator-runtime": "^0.10.5"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "2.6.12",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+          "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+          "dev": true
+        },
+        "regenerator-runtime": {
+          "version": "0.10.5",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+          "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==",
+          "dev": true
+        }
+      }
+    },
+    "babel-runtime": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+      "dev": true,
+      "requires": {
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "2.6.12",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+          "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+          "dev": true
+        },
+        "regenerator-runtime": {
+          "version": "0.11.1",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+          "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+          "dev": true
+        }
+      }
+    },
     "balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -16990,6 +17096,12 @@
       "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==",
       "dev": true
     },
+    "es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "dev": true
+    },
     "escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",

+ 2 - 0
package.json

@@ -43,6 +43,8 @@
     "@vue/cli-plugin-typescript": "~5.0.0",
     "@vue/cli-service": "~5.0.0",
     "@vue/eslint-config-typescript": "^9.1.0",
+    "babel-polyfill": "^6.26.0",
+    "es6-promise": "^4.2.8",
     "eslint": "^7.32.0",
     "eslint-plugin-vue": "^8.0.3",
     "less": "^4.0.0",

+ 37 - 0
src/business/contract/index.ts

@@ -0,0 +1,37 @@
+import { shallowRef } from 'vue'
+import { useDataTable } from '@/hooks/datatable'
+import { queryTHJPurchaseTransfer } from '@/services/api/contract'
+
+// 合同转让列表
+export function useContractList() {
+    const { dataList, total, pageIndex, pageSize, pageCount } = useDataTable<Model.THJPurchaseTransferRsp>()
+    const loading = shallowRef(false)
+
+    const getWrstandardList = async () => {
+        loading.value = true
+        await queryTHJPurchaseTransfer({
+            data: {
+                page: pageIndex.value,
+                pagesize: pageSize.value,
+            },
+            success: (res) => {
+                total.value = res.total
+                dataList.value = res.data
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+        return dataList.value
+    }
+
+    return {
+        loading,
+        dataList,
+        total,
+        pageIndex,
+        pageSize,
+        pageCount,
+        getWrstandardList,
+    }
+}

+ 30 - 0
src/constants/receipt.ts

@@ -0,0 +1,30 @@
+import { useEnumStore } from '@/stores'
+
+const { getEnumTypeName } = useEnumStore()
+
+/**
+ * 发票类型
+ */
+export enum ReceiptType {
+    Consumer = 1, // 个人
+    Company = 2, // 企业
+}
+
+/**
+ * 获取发票类型列表
+ * @returns 
+ */
+export function getReceiptTypeList() {
+    return [
+        { label: '个人', value: ReceiptType.Consumer },
+        { label: '企业', value: ReceiptType.Company },
+    ]
+}
+
+/**
+ * 获取发票类型名称
+ * @returns 
+ */
+export function getReceiptTypeName(value: number) {
+    return getEnumTypeName(getReceiptTypeList(), value)
+}

BIN
src/packages/mobile/assets/images/hongbao-open.png


+ 3 - 1
src/packages/mobile/components/modules/reward/index.vue

@@ -1,7 +1,9 @@
 <template>
     <Overlay class="app-reward" :show="show">
         <div class="app-reward__wrapper" @click.stop>
-            <img class="image" src="@mobile/assets/images/hongbao.png" @click="showRedEnvelope = true" />
+            <img class="image"
+                :src="require(showRedEnvelope ? '@mobile/assets/images/hongbao-open.png' : '@mobile/assets/images/hongbao.png')"
+                @click="showRedEnvelope = true" />
             <div class="content" v-if="!showRedEnvelope">
                 <h1>{{ title }}</h1>
             </div>

+ 11 - 18
src/packages/mobile/router/index.ts

@@ -95,9 +95,6 @@ const routes: Array<RouteRecordRaw> = [
         path: 'certification',
         name: 'account-certification',
         component: () => import('../views/account/certification/index.vue'),
-        meta: {
-          ignoreAuth: true,
-        },
       }
     ]
   },
@@ -109,17 +106,11 @@ const routes: Array<RouteRecordRaw> = [
         path: 'details',
         name: 'news-details',
         component: () => import('../views/news/details/index.vue'),
-        meta: {
-          ignoreAuth: true,
-        },
       },
       {
         path: 'list',
         name: 'news-list',
         component: () => import('../views/news/list/index.vue'),
-        meta: {
-          ignoreAuth: true,
-        },
       }
     ]
   },
@@ -131,17 +122,11 @@ const routes: Array<RouteRecordRaw> = [
         path: '',
         name: 'product',
         component: () => import('../views/product/list/index.vue'),
-        meta: {
-          ignoreAuth: true,
-        },
       },
       {
         path: 'detail',
         name: 'product-detail',
         component: () => import('../views/product/detail/index.vue'),
-        meta: {
-          ignoreAuth: true,
-        },
       }
     ]
   },
@@ -153,9 +138,17 @@ const routes: Array<RouteRecordRaw> = [
         path: 'details',
         name: 'goods-details',
         component: () => import('../views/goods/details/index.vue'),
-        meta: {
-          ignoreAuth: true,
-        },
+      }
+    ]
+  },
+  {
+    path: '/contract',
+    component: Page,
+    children: [
+      {
+        path: '',
+        name: 'contract',
+        component: () => import('../views/contract/list/index.vue'),
       }
     ]
   },

+ 0 - 0
src/packages/mobile/views/contract/list/index.less


+ 51 - 0
src/packages/mobile/views/contract/list/index.vue

@@ -0,0 +1,51 @@
+<template>
+    <app-view class="contract">
+        <template #header>
+            <app-navbar title="合同转让">
+                <template #footer>
+                    <Search shape="round" placeholder="商品搜索" disabled />
+                </template>
+            </app-navbar>
+        </template>
+        <app-pull-refresh v-model:error="error" v-model:pageIndex="pageIndex" :page-count="pageCount"
+            @refresh="onRefresh">
+            <template v-for="(item, index) in dataList" :key="index">
+                <Cell @click="$router.push({ name: 'goods-details', query: { wrstandardid: item.wrstandardid } })">
+                    <template #title>
+                        <img :src="getImageUrl(item.thumurls)" />
+                        <span>{{ item.wrstandardname }}</span>
+                    </template>
+                </Cell>
+            </template>
+        </app-pull-refresh>
+    </app-view>
+</template>
+  
+<script lang="ts" setup>
+import { shallowRef } from 'vue'
+import { Cell, Search } from 'vant'
+import { getImageUrl } from '@/filters'
+import { useContractList } from '@/business/contract'
+import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
+
+const { pageIndex, pageCount, getWrstandardList } = useContractList()
+const dataList = shallowRef<Model.THJPurchaseTransferRsp[]>([])
+const error = shallowRef(false)
+
+const onRefresh = (finish: () => void) => {
+    getWrstandardList().then((res) => {
+        if (pageIndex.value === 1) {
+            dataList.value = []
+        }
+        dataList.value.push(...res)
+    }).catch(() => {
+        error.value = true
+    }).finally(() => {
+        finish()
+    })
+}
+</script>
+  
+<style lang="less">
+@import './index.less';
+</style>

+ 51 - 2
src/packages/mobile/views/goods/details/components/address/index.vue

@@ -23,12 +23,12 @@
                 <CellGroup inset>
                     <template #title>
                         <span>发票信息</span>
-                        <span style="color: #333;">
+                        <span @click="showReceipt = true" style="color: #333;">
                             <Icon name="add-o" />
                             添加发票信息
                         </span>
                     </template>
-                    <Field v-model="formItem.ReceiptInfo" type="textarea" name="username" label="发票信息" rows="3"
+                    <Field v-model="formItem.ReceiptInfo" type="textarea" name="username" label="发票信息" rows="5"
                         placeholder="选填" :rules="formRules.ReceiptInfo" />
                 </CellGroup>
             </Form>
@@ -39,14 +39,17 @@
             </template>
         </app-view>
         <app-contact v-model:show="showContact" @change="contactChange" />
+        <app-receipt v-model:show="showReceipt" @change="receiptChange" />
     </app-modal>
 </template>
 
 <script lang="ts" setup>
 import { shallowRef, reactive, PropType } from 'vue'
 import { CellGroup, Button, Field, Form, FormInstance, FieldRule, Icon } from 'vant'
+import { getReceiptTypeName } from '@/constants/receipt'
 import AppModal from '@/components/base/modal/index.vue'
 import AppContact from '../contact/index.vue'
+import AppReceipt from '../receipt/index.vue'
 
 const props = defineProps({
     formData: {
@@ -59,6 +62,7 @@ const emit = defineEmits(['updated'])
 const formRef = shallowRef<FormInstance>()
 const showModal = shallowRef(true)
 const showContact = shallowRef(false) // 显示联系人选择列表
+const showReceipt = shallowRef(false) // 显示发票选择列表
 const formItem = reactive({ ...props.formData })
 
 // 表单验证规则
@@ -89,6 +93,49 @@ const contactChange = (item: Model.UserReceiveInfoRsp) => {
     }
 }
 
+// 选择发票信息
+const receiptChange = (item: Model.WrUserReceiptInfoRsp) => {
+    formItem.ReceiptInfo = ''
+    Object.entries(item).forEach(([key, value]) => {
+        if (value !== '') {
+            switch (key) {
+                case 'receipttype': {
+                    formItem.ReceiptInfo += '发票类型:' + getReceiptTypeName(Number(value)) + '\n'
+                    break
+                }
+                case 'username': {
+                    formItem.ReceiptInfo += '发票抬头:' + value + '\n'
+                    break
+                }
+                case 'taxpayerid': {
+                    formItem.ReceiptInfo += '税号:' + value + '\n'
+                    break
+                }
+                case 'receiptbank': {
+                    formItem.ReceiptInfo += '开户银行:' + value + '\n'
+                    break
+                }
+                case 'receiptaccount': {
+                    formItem.ReceiptInfo += '银行账号:' + value + '\n'
+                    break
+                }
+                case 'address': {
+                    formItem.ReceiptInfo += '企业地址:' + value + '\n'
+                    break
+                }
+                case 'contactinfo': {
+                    formItem.ReceiptInfo += '企业电话:' + value + '\n'
+                    break
+                }
+                case 'email': {
+                    formItem.ReceiptInfo += '邮箱:' + value + '\n'
+                    break
+                }
+            }
+        }
+    })
+}
+
 const onSubmit = () => {
     emit('updated', formItem)
     closed()
@@ -98,6 +145,8 @@ const onSubmit = () => {
 const closed = () => {
     if (showContact.value) {
         showContact.value = false
+    } else if (showReceipt.value) {
+        showReceipt.value = false
     } else {
         showModal.value = false
     }

+ 1 - 1
src/packages/mobile/views/goods/details/components/contact/index.vue

@@ -1,5 +1,5 @@
 <template>
-    <app-modal class="goods-details-address" direction="right" height="100%" v-model:show="showModal">
+    <app-modal direction="right" height="100%" v-model:show="showModal">
         <app-address :show-radio="true" @change="onChange" />
     </app-modal>
 </template>

+ 30 - 0
src/packages/mobile/views/goods/details/components/receipt/index.vue

@@ -0,0 +1,30 @@
+<template>
+    <app-modal direction="right" height="100%" v-model:show="showModal">
+        <app-invoice :show-radio="true" @change="onChange" />
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue'
+import AppModal from '@/components/base/modal/index.vue'
+import AppInvoice from '@mobile/views/mine/invoice/index.vue'
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false
+    }
+})
+
+const emit = defineEmits(['update:show', 'change'])
+
+const showModal = computed({
+    get: () => props.show,
+    set: (val) => emit('update:show', val)
+})
+
+const onChange = (item: Model.WrUserReceiptInfoRsp) => {
+    showModal.value = false
+    emit('change', item)
+}
+</script>

+ 271 - 0
src/packages/mobile/views/goods/details/index.backup.vue

@@ -0,0 +1,271 @@
+<template>
+    <app-view class="goods-details">
+        <template #header>
+            <app-navbar title="采购详情" />
+        </template>
+        <Form ref="formRef" class="goods-details__form" @submit="onSubmit">
+            <CellGroup>
+                <Field name="THJDeliveryMode" label="交割方式" :rules="formRules.THJDeliveryMode" is-link>
+                    <template #input>
+                        <app-select v-model="formData.THJDeliveryMode" placeholder="请选择"
+                            :options="details.deliverymodes"
+                            :optionProps="{ label: 'enumdicname', value: 'enumitemname' }" />
+                    </template>
+                </Field>
+                <Field label="交割月份" name="PresaleApplyID" :rules="formRules.PresaleApplyID">
+                    <template #input>
+                        <app-select placeholder="开始月份" :options="deliveryMonths" @confirm="onMonthChange" />
+                        <app-select placeholder="结束日期" :options="deliveryDays"
+                            :optionProps="{ label: 'enddate', value: 'presaleapplyid' }" @confirm="onDayChange" />
+                    </template>
+                </Field>
+                <Field name="DepositID" label="支付方式" :rules="formRules.DepositID" is-link
+                    v-if="selectedDate?.presaleapplyid">
+                    <template #input>
+                        <app-select v-model="formData.DepositID" placeholder="请选择" :options="presaleApplyDeposits" />
+                    </template>
+                </Field>
+                <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 v-if="selectedDate">≤{{ selectedDate.remainqty }}{{
+                                        getGoodsUnitName(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>
+        <div class="goods-details__content">
+            <div class="submitbar">
+                <Button type="primary" @click="formRef?.submit" round block>采购下单</Button>
+            </div>
+            <div class="titlebar" v-if="selectedDate">
+                <span>参考价(元/{{ getGoodsUnitName(goodsinfo.unitid) }})</span>
+                <span>{{ selectedDate.unitprice ?? 0.00 }}</span>
+            </div>
+            <template v-if="chartData.price.length">
+                <Divider>历史价格走势</Divider>
+                <div class="chart">
+                    <component :is="componentMap.get('chart')" :loading="loading" :data-list="chartData" />
+                </div>
+            </template>
+            <Divider>商品详情</Divider>
+            <div class="gallery">
+                <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, 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'
+import { usePurchaseOrderDesting } from '@/business/trade'
+import AppSelect from '@mobile/components/base/select/index.vue'
+import Long from 'long'
+
+const componentMap = new Map<string, unknown>([
+    ['address', defineAsyncComponent(() => import('./components/address/index.vue'))],
+    ['chart', defineAsyncComponent(() => import('@mobile/components/modules/echarts-line/index.vue'))],
+])
+
+const { componentRef, componentId, openComponent, closeComponent, closeComponentEach } = useComponent()
+const { router, getQueryStringToNumber, beforeRouteLeave } = useNavigation()
+
+const wrstandardid = getQueryStringToNumber('wrstandardid')
+const formRef = shallowRef<FormInstance>()
+
+const { loading, details, chartData, getWrstandardDetails } = useWrstandardDetails(wrstandardid)
+const { formData, formSubmit } = usePurchaseOrderDesting()
+
+// 交割日期列表
+const deliveryDays = shallowRef<Model.THJWrstandardDetailRsp['deliverymonth']>([])
+// 当前选中的交割日期
+const selectedDate = shallowRef<Model.THJWrstandardDetailRsp['deliverymonth'][number]>()
+// 商品信息
+const goodsinfo = computed<Partial<Model.THJWrstandardDetailRsp['goodsinfo']>>(() => details.value.goodsinfo ?? {})
+
+// 商品图片列表
+const goodsImages = computed(() => {
+    const pictureurls = goodsinfo.value.pictureurls ?? ''
+    return pictureurls.split(',').map((path) => getImageUrl(path))
+})
+
+// 交割月份列表
+const deliveryMonths = computed(() => {
+    const months = details.value.deliverymonth ?? []
+    const monthMap = new Map<string, { label: string, value: string; }>()
+    months.forEach(({ endmonth }) => {
+        if (!monthMap.has(endmonth)) {
+            monthMap.set(endmonth, {
+                label: endmonth,
+                value: endmonth,
+            })
+        }
+    })
+    return [...monthMap.values()]
+})
+
+// 预售申请列表
+const presaleApplyDeposits = computed(() => {
+    const deposits = details.value.presaleapplydeposits ?? []
+    const presaleApplyId = selectedDate.value?.presaleapplyid
+
+    return deposits.filter((e) => e.presaleapplyid === presaleApplyId).map(({ depositid, depositrate }) => ({
+        label: `${depositrate * 100}%`,
+        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).toFixed(2)
+    }
+    return '0.00'
+})
+
+// 是否显示收货信息
+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) // 重新过滤交割日期列表
+    selectedDate.value = undefined // 清除选中的交割日期
+    formData.value.PresaleApplyID = undefined
+}
+
+// 切换交割日期
+const onDayChange = (value: string) => {
+    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(() => {
+        formSubmit().then(() => {
+            Toast.success('下单成功')
+            router.replace({ name: 'my-order' })
+        }).catch((err) => {
+            Toast.fail(err)
+        })
+    })
+}
+
+getWrstandardDetails()
+beforeRouteLeave(() => closeComponentEach())
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 8 - 26
src/packages/mobile/views/goods/details/index.vue

@@ -12,11 +12,10 @@
                             :optionProps="{ label: 'enumdicname', value: 'enumitemname' }" />
                     </template>
                 </Field>
-                <Field label="交割月份" name="PresaleApplyID" :rules="formRules.PresaleApplyID">
+                <Field name="PresaleApplyID" label="交割月份" :rules="formRules.PresaleApplyID" is-link>
                     <template #input>
-                        <app-select placeholder="开始月份" :options="deliveryMonths" @confirm="onMonthChange" />
-                        <app-select placeholder="结束日期" :options="deliveryDays"
-                            :optionProps="{ label: 'enddate', value: 'presaleapplyid' }" @confirm="onDayChange" />
+                        <app-select :options="details.deliverymonth"
+                            :optionProps="{ label: 'endmonth', value: 'presaleapplyid' }" @confirm="onMonthChange" />
                     </template>
                 </Field>
                 <Field name="DepositID" label="支付方式" :rules="formRules.DepositID" is-link
@@ -25,6 +24,11 @@
                         <app-select v-model="formData.DepositID" placeholder="请选择" :options="presaleApplyDeposits" />
                     </template>
                 </Field>
+                <Field label="交割日期" v-if="selectedDate?.presaleapplyid">
+                    <template #input>
+                        <span>{{ selectedDate.enddate }}</span>
+                    </template>
+                </Field>
                 <Field v-model="formData.Qty" name="Qty" type="digit" label="采购数量" :rules="formRules.Qty">
                     <template #input>
                         <div class="form-qty">
@@ -124,20 +128,6 @@ const goodsImages = computed(() => {
     return pictureurls.split(',').map((path) => getImageUrl(path))
 })
 
-// 交割月份列表
-const deliveryMonths = computed(() => {
-    const months = details.value.deliverymonth ?? []
-    const monthMap = new Map<string, { label: string, value: string; }>()
-    months.forEach(({ endmonth }) => {
-        if (!monthMap.has(endmonth)) {
-            monthMap.set(endmonth, {
-                label: endmonth,
-                value: endmonth,
-            })
-        }
-    })
-    return [...monthMap.values()]
-})
 
 // 预售申请列表
 const presaleApplyDeposits = computed(() => {
@@ -232,14 +222,6 @@ const formRules: { [key in keyof Proto.SpotPresaleDestingOrderReq | 'addressInfo
 // 切换交割月份
 const onMonthChange = (value: string) => {
     const months = details.value.deliverymonth ?? []
-    deliveryDays.value = months.filter((e) => e.endmonth === value) // 重新过滤交割日期列表
-    selectedDate.value = undefined // 清除选中的交割日期
-    formData.value.PresaleApplyID = undefined
-}
-
-// 切换交割日期
-const onDayChange = (value: string) => {
-    const months = details.value.deliverymonth ?? []
     selectedDate.value = months.find((e) => e.presaleapplyid === value)
     formData.value.PresaleApplyID = Long.fromString(value)
     formData.value.DepositID = undefined

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

@@ -16,7 +16,7 @@
           <li @click="routerTo('product')">
             <app-iconfont icon="icon-chanpinjieshao" label-direction="bottom">产品介绍</app-iconfont>
           </li>
-          <li>
+          <li @click="routerTo('contract')">
             <app-iconfont icon="icon-chanpinjiage" label-direction="bottom">合同转让</app-iconfont>
           </li>
           <li @click="routerTo('rules-ptgz')">

+ 3 - 7
src/packages/mobile/views/mine/invoice/components/edit/index.vue

@@ -9,13 +9,13 @@
                     <Field name="ReceiptType" label="发票类型" :rules="formRules.ReceiptType"
                         :is-link="!selectedRow.autoid">
                         <template #input>
-                            <app-select v-model="formData.ReceiptType" :options="receiptTypeList"
+                            <app-select v-model="formData.ReceiptType" :options="getReceiptTypeList()"
                                 :readonly="!!selectedRow.autoid" />
                         </template>
                     </Field>
                     <Field v-model="formData.UserName" :rules="formRules.UserName" name="UserName" label="发票抬头"
                         placeholder="必填" />
-                    <template v-if="formData.ReceiptType === 2">
+                    <template v-if="formData.ReceiptType === ReceiptType.Company">
                         <Field v-model="formData.TaxpayerID" :rules="formRules.TaxpayerID" name="TaxpayerID" label="税号"
                             placeholder="必填" />
                         <Field v-model="formData.ReceiptBank" :rules="formRules.ReceiptBank" name="ReceiptBank"
@@ -44,6 +44,7 @@
 import { shallowRef, PropType } from 'vue'
 import { CellGroup, Button, Field, Form, FormInstance, Toast, FieldRule } from 'vant'
 import { fullloading, dialog } from '@/utils/vant'
+import { ReceiptType, getReceiptTypeList } from '@/constants/receipt'
 import { validateRules } from '@/constants/regex'
 import { useInvoiceForm } from '@/business/user'
 import AppModal from '@/components/base/modal/index.vue'
@@ -61,11 +62,6 @@ const formRef = shallowRef<FormInstance>()
 const showModal = shallowRef(true)
 const refresh = shallowRef(false) // 是否刷新父组件数据
 
-const receiptTypeList = [
-    { label: '个人', value: 1 },
-    { label: '企业', value: 2 }
-]
-
 // 表单验证规则
 const formRules: { [key in keyof Proto.UserReceiptInfoReq]?: FieldRule[] } = {
     UserName: [{

+ 38 - 0
src/packages/mobile/views/mine/invoice/index.less

@@ -0,0 +1,38 @@
+.mine-invoice {
+    &__container {
+        padding: .32rem;
+
+        .list {
+            &-item {
+                display         : flex;
+                align-items     : center;
+                background-color: #fff;
+                border-radius   : 8px;
+                overflow        : hidden;
+                padding         : .24rem;
+
+                &:not(:first-child) {
+                    margin-top: .2rem;
+                }
+
+                &__checkbox {
+                    margin-right: .24rem;
+                }
+
+                &__info {
+                    flex: 1;
+
+                    span {
+                        margin-right: .12rem;
+                    }
+                }
+
+                &__button {
+                    .van-icon {
+                        font-size: .36rem;
+                    }
+                }
+            }
+        }
+    }
+}

+ 2 - 4
src/packages/mobile/views/mine/invoice/index.vue

@@ -11,13 +11,10 @@
                     </div>
                     <div class="list-item__info" @click="onChange(item)">
                         <div class="list-item__info-name">
-                            <span>{{ item.receipttype }}</span>
+                            <Tag size="medium">{{ getReceiptTypeName(item.receipttype) }}</Tag>
                             <span>{{ item.username }}</span>
                             <Tag type="danger" round v-if="item.isdefault">默认</Tag>
                         </div>
-                        <div class="list-item__info-address">
-                            <span>{{ item.taxpayerid }}</span>
-                        </div>
                     </div>
                     <div class="list-item__button">
                         <Button icon="edit" @click="onEdit(item)" />
@@ -39,6 +36,7 @@
 <script lang="ts" setup>
 import { shallowRef, defineAsyncComponent, PropType } from 'vue'
 import { RadioGroup, Radio, Button, Tag, Empty } from 'vant'
+import { getReceiptTypeName } from '@/constants/receipt'
 import { useNavigation } from '@/hooks/navigation'
 import { useComponent } from '@/hooks/component'
 import { useInvoice } from '@/business/user'

+ 4 - 4
src/packages/mobile/views/user/login/index.vue

@@ -18,11 +18,11 @@
     </Form>
     <div class="login-footer">
       <Checkbox shape="square" icon-size=".32rem" v-model="checked">我已阅读并同意</Checkbox>
-      <span @click="navigationTo('rules-zcxy')" style="color:#E92020">《用户注册协议》</span>
+      <span @click="routerTo('rules-zcxy')" style="color:#E92020">《用户注册协议》</span>
       <span>和</span>
-      <span @click="navigationTo('rules-yhkhfxgzs')" style="color:#E92020">《用户开户风险告知书》</span>
+      <span @click="routerTo('rules-yhkhfxgzs')" style="color:#E92020">《用户开户风险告知书》</span>
       <span>和</span>
-      <span @click="navigationTo('rules-yszc')" style="color:#E92020">《隐私政策》</span>
+      <span @click="routerTo('rules-yszc')" style="color:#E92020">《隐私政策》</span>
     </div>
   </app-statusbar>
 </template>
@@ -51,7 +51,7 @@ const navigationTo = (name: string) => {
     }).catch(() => {
       Toast.fail('初始化失败')
     })
-  })
+  }, '加载中...')
 }
 
 const formSubmit = () => {

+ 16 - 0
src/services/api/contract/index.ts

@@ -0,0 +1,16 @@
+import { httpRequest } from '@/services/http'
+import { HttpParams } from '@/services/http/interface'
+
+/**
+ * 查询合同转让列表
+ */
+export function queryTHJPurchaseTransfer(params: HttpParams<{ req: Model.THJPurchaseTransferReq, rsp: Model.THJPurchaseTransferRsp[] }>) {
+    return httpRequest('/Ferroalloy/QueryTHJPurchaseTransfer', 'get', params);
+}
+
+/**
+ * 查询合同转让详情
+ */
+export function queryTHJPurchaseTransferDetail(params: HttpParams<{ req: Model.THJPurchaseTransferDetailReq, rsp: Model.THJPurchaseTransferDetailRsp[] }>) {
+    return httpRequest('/Ferroalloy/QueryTHJPurchaseTransferDetail', 'get', params);
+}

+ 58 - 0
src/types/model/contract.d.ts

@@ -0,0 +1,58 @@
+declare namespace Model {
+    /** 查询合同转让列表 请求 */
+    interface THJPurchaseTransferReq {
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+        wrstandardname?: number; // 现货商品名称(模糊查询)
+    }
+
+    /** 查询合同转让列表 响应 */
+    interface THJPurchaseTransferRsp {
+        pictureurls: string; // 详情图片(逗号分隔)
+        refgoodsid: number; // 参考商品ID
+        thumurls: string; // 缩略图片(1:1)(逗号分隔)
+        unitid: number; // 现货商品单位ID
+        wrstandardcode: string; // 现货商品代码
+        wrstandardid: number; // 现货商品ID(自增 SEQ_GOODS 确保不重复)
+        wrstandardname: string; // 现货商品名称(模糊查询)
+    }
+
+    /** 查询合同转让详情 请求 */
+    interface THJPurchaseTransferDetailReq {
+        wrstandardid: number; // 现货商品ID
+    }
+
+    /** 查询合同转让详情 响应 */
+    interface THJPurchaseTransferDetailRsp {
+        deliverydate: {
+            depositrate: number; // 定金比例
+            enddate: string; // 预售结束日期
+            tradeprice: number; // 摘牌价格
+            tradeqty: number; // 摘牌数量
+            transferamount: number; // 转让金额
+            transferid: number; // 协议转让ID(332+Unix秒时间戳(10位)+xxxxxx)
+            transferprice: number; // 转让价格
+        }[];
+        goodsinfo: {
+            pictureurls: string; // 详情图片(逗号分隔)
+            refgoodscode: string; // 参考商品代码
+            refgoodsid: number; // 参考商品ID
+            unitid: number; // 现货商品单位ID
+            wrstandardcode: string; // 现货商品代码
+            wrstandardid: number; // 现货商品ID(自增 SEQ_GOODS 确保不重复)
+            wrstandardname: string; // 现货商品名称(模糊查询)
+        }[];
+        history: {
+            c: number; // 收盘价
+            f: boolean; // 是否补充数据
+            h: number; // 最高价
+            hv: number; // 持仓量
+            l: number; // 最低价
+            o: number; // 开盘价
+            s: number; // 结算价,日线周期(包括)以上才有
+            ts: string; // 时间
+            tt: number; // 总金额
+            tv: number; // 总量(成交量)
+        }[];
+    }
+}