li.shaoyi il y a 3 ans
Parent
commit
2b5643b66e

+ 85 - 1
src/business/customs/index.ts

@@ -1,13 +1,36 @@
-import { shallowRef } from 'vue'
+import { shallowRef, reactive } from 'vue'
+import { v4 } from 'uuid'
 import { useDataTable } from '@/hooks/datatable'
 import { getTableColumns } from '@/business/table'
+import { cjjcApply, queryGZCJJCOrder } from '@/services/api/customs'
+import { ClientType } from '@/constants/client'
+import { useLoginStore } from '@/stores'
 
 // 出境检测
 export function useCJJCOrder() {
+    const { getUserId } = useLoginStore()
     const { dataList, total, pageIndex, pageSize } = useDataTable<Ermcp.GZCJJCOrderRsp>()
     const loading = shallowRef(false)
     const columns = shallowRef(getTableColumns('customs_cjjc'))
 
+    const getGZCJJCOrderList = () => {
+        loading.value = true
+        return queryGZCJJCOrder({
+            data: {
+                page: pageIndex.value,
+                pagesize: pageSize.value,
+                userid: getUserId(),
+            },
+            success: (res) => {
+                total.value = res.total
+                dataList.value = res.data
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
     return {
         loading,
         dataList,
@@ -15,6 +38,67 @@ export function useCJJCOrder() {
         pageIndex,
         pageSize,
         columns,
+        getGZCJJCOrderList,
+    }
+}
+
+// 出境检测
+export function useCJJCOrderEdit(selectedRow?: Ermcp.GZCJJCOrderRsp) {
+    const { loginInfo, getFirstAccountId } = useLoginStore()
+    const { UserID, LoginID, LoginCode } = loginInfo.value
+    const loading = shallowRef(false)
+
+    const formData = reactive<Partial<Proto.CJJCApplyReq>>({
+        Header: {
+            AccountID: getFirstAccountId(),
+        },
+        UserID,
+        AccountID: getFirstAccountId(),
+        OperateID: LoginID, // 操作人ID,LoginAccount的LoginID,必填
+        OperateAccount: LoginCode || LoginID.toString(), // 操作人账户,LoginAccount的LoginCode,LoginCode为空则填LoginID,必填
+        GZCJCategoryDetails: [],
+        ClientType: ClientType.Web,
+    })
+
+    if (selectedRow?.orderid) {
+        ({
+            userid: formData.UserID,
+            accountid: formData.AccountID,
+            orderid: formData.OrderID,
+            gzcjaccounttype: formData.GZCJAccountType,
+            gzcjaccount: formData.GZCJAccount,
+            companynamecn: formData.CompanyNameCN,
+            companynameen: formData.CompanyNameEn,
+            addresscn: formData.AddressCN,
+            addressen: formData.AddressEN,
+            contactname: formData.ContactName,
+            contactposition: formData.ContactPosition,
+            contactphoneno: formData.ContactPhoneNo,
+            email: formData.Email,
+            gzcjcategorytype: formData.GZCJCategoryType,
+            processingcountry: formData.ProcessingCountry,
+            origincountry: formData.ZSOrigin,
+            gzcjdeliverytype: formData.GZCJDeliveryType,
+        } = selectedRow)
+    }
+
+    const formSubmit = () => {
+        loading.value = true
+        return cjjcApply({
+            data: {
+                ...formData,
+                ClientSerialNo: v4(),
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        loading,
+        formData,
+        formSubmit,
     }
 }
 

+ 1 - 1
src/business/table/index.ts

@@ -294,7 +294,7 @@ const pcTableColumnMap = new Map<TableColumnKey, Ermcp.TableColumn[]>([
         { prop: 'contactname', label: '联系人姓名' },
         { prop: 'contactphoneno', label: '联系人电话' },
         { prop: 'gzcjstatusdisplay', label: '单据状态' },
-        { prop: 'operate', label: '操作', width: 180 }
+        { prop: 'operate', label: '操作', width: 850 }
     ]],
     // 交易服务-保税服务
     ['customs_bsfw', [

+ 151 - 0
src/constants/customs.ts

@@ -0,0 +1,151 @@
+import { useEnumStore } from '@/stores'
+
+const { getEnumTypeList, getEnumTypeName } = useEnumStore()
+
+/**
+ * 获取检测账户类型列表
+ * @returns 
+ */
+export function getGZCJAccountTypeList() {
+    return getEnumTypeList('GZCJAccountType')
+}
+
+/**
+ * 获取检测账户类型名称
+ * @returns 
+ */
+export function getGZCJAccountTypeName(value?: number) {
+    const enums = getGZCJAccountTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取货物品类列表
+ * @returns 
+ */
+export function getGZCJCategoryTypeList() {
+    return getEnumTypeList('GZCJCategoryType')
+}
+
+/**
+ * 获取货物品类名称
+ * @returns 
+ */
+export function getGZCJCategoryTypeName(value?: number) {
+    const enums = getGZCJCategoryTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取收货方式列表
+ * @returns 
+ */
+export function getGZCJDeliveryTypeList() {
+    return getEnumTypeList('GZCJDeliveryType')
+}
+
+/**
+ * 获取收货方式名称
+ * @returns 
+ */
+export function getGZCJDeliveryTypeName(value?: number) {
+    const enums = getGZCJDeliveryTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取形状列表
+ * @returns 
+ */
+export function getGZCJShapeTypeList() {
+    return getEnumTypeList('GZCJShapeType', ['enumdicname', 'param1', 'param2'])
+}
+
+/**
+ * 获取形状名称
+ * @returns 
+ */
+export function getGZCJShapeTypeName(value?: number) {
+    const enums = getGZCJShapeTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取刻印服务列表
+ * @returns 
+ */
+export function getGZCJMarkTypeList() {
+    return getEnumTypeList('GZCJMarkType', ['enumdicname', 'param1'])
+}
+
+/**
+ * 获取刻印服务名称
+ * @returns 
+ */
+export function getGZCJMarkTypeName(value?: number) {
+    const enums = getGZCJMarkTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取披露处理列表
+ * @returns 
+ */
+export function getGZCJPublishTypeList() {
+    return getEnumTypeList('GZCJPublishType')
+}
+
+/**
+ * 获取披露处理名称
+ * @returns 
+ */
+export function getGZCJPublishTypeName(value?: number) {
+    const enums = getGZCJPublishTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取服务类别列表
+ * @returns 
+ */
+export function getGZCJServiceTypeList() {
+    return getEnumTypeList('GZCJServiceType')
+}
+
+/**
+ * 获取服务类别名称
+ * @returns 
+ */
+export function getGZCJServiceTypeName(value?: number) {
+    const enums = getGZCJServiceTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 单据状态
+ */
+export enum GZCJStatus {
+    Audit = 1, // 待审核
+    Shipping = 2, // 发货中
+    Checking = 8, // 检测结果确认中
+    Minutes60 = 10, // 预付款确认中
+    Hours2 = 13, // 付款确认中
+    Uncommitted = 22, // 未提交
+}
+
+/**
+ * 获取单据状态列表
+ * @returns 
+ */
+export function getGZCJStatusList() {
+    return getEnumTypeList('GZCJStatus')
+}
+
+/**
+ * 获取单据状态名称
+ * @returns 
+ */
+export function getGZCJStatusName(value?: number) {
+    const enums = getGZCJStatusList()
+    return getEnumTypeName(enums, value)
+}

+ 33 - 0
src/constants/regex.ts

@@ -0,0 +1,33 @@
+/**
+ * 表单验证规则
+ */
+export const validateRules = {
+    password: {
+        validate: (val: string) => /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)])+$)^.{6,64}$/.test(val),
+        message: '密码必须包含字母、数字、特殊符号中的任意两种组合,长度最少6位',
+    },
+    phone: {
+        validate: (val: string) => /^$|^1[3456789]\d{9}$/.test(val),
+        message: '手机号码无效',
+    },
+    email: {
+        validate: (val: string) => /^$|^([a-zA-Z]|[0-9])(\w)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/.test(val),
+        message: '邮箱地址无效',
+    },
+    en: {
+        validate: (val: string) => /^[A-Za-z]+$/.test(val),
+        message: '只能输入英文字母(不允许空格)',
+    },
+    enname: {
+        validate: (val: string) => /^[a-zA-Z0-9_]{1,}$/.test(val),
+        message: '只能输入英文字母、数字、下划线',
+    },
+    cardno: {
+        validate: (val: string) => /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(val),
+        message: '身份证号码不合规',
+    },
+    bankcardno: {
+        validate: (val: string) => /^([1-9]{1})(\d{15}|\d{16}|\d{18})$/.test(val),
+        message: '银行卡号码不合规',
+    }
+}

+ 3 - 1
src/hooks/menu/index.ts

@@ -1,4 +1,4 @@
-import { defineAsyncComponent, Component } from 'vue'
+import { shallowRef, defineAsyncComponent, Component } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { useMenuStore } from '@/stores'
 import { AuthType } from '@/constants/menu'
@@ -8,6 +8,7 @@ export function useMenu(authCode?: string) {
     const route = useRoute()
     const router = useRouter()
     const componentMap = new Map<string, Component>()
+    const componentId = shallowRef<string>() // 当前选中的组件
 
     // 过滤菜单
     const filterMenu = (data: Ermcp.UserMenu[], parentPath = '') => {
@@ -131,6 +132,7 @@ export function useMenu(authCode?: string) {
         router,
         userMenus,
         componentMap,
+        componentId,
         getMenus,
         getChildrenMenus,
         getAuthButtons,

+ 74 - 0
src/mock/router.ts

@@ -436,6 +436,80 @@ const appmenu = {
                         url: '',
                         urlType: 1,
                         component: 'views/customs/exit/index.vue',
+                        children: [
+                            {
+                                authType: 3,
+                                title: '详情',
+                                code: 'customs_exit_details',
+                                component: 'views/customs/exit/components/details/index.vue',
+                                buttonName: 'details',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '提交申请',
+                                code: 'customs_exit_add',
+                                component: 'views/customs/exit/components/edit/index.vue',
+                                buttonName: 'add',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '修改',
+                                code: 'customs_exit_edit',
+                                component: 'views/customs/exit/components/edit/index.vue',
+                                buttonName: 'edit',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '撤回',
+                                code: 'customs_exit_cancel',
+                                component: 'views/customs/exit/components/cancel/index.vue',
+                                buttonName: 'cancel',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '下载',
+                                code: 'customs_exit_download',
+                                component: 'views/customs/exit/components/download/index.vue',
+                                buttonName: 'download',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '确认发货',
+                                code: 'customs_exit_delivery',
+                                component: 'views/customs/exit/components/delivery/index.vue',
+                                buttonName: 'delivery',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '确认检测结果',
+                                code: 'customs_exit_check',
+                                component: 'views/customs/exit/components/check/index.vue',
+                                buttonName: 'check',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '确认预付款',
+                                code: 'customs_exit_advance_payment',
+                                component: 'views/customs/exit/components/advance-payment/index.vue',
+                                buttonName: 'advance_payment',
+                                buttonType: 'primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '确认付款',
+                                code: 'customs_exit_payment',
+                                component: 'views/customs/exit/components/payment/index.vue',
+                                buttonName: 'payment',
+                                buttonType: 'primary',
+                            },
+                        ]
                     },
                     {
                         authType: 1,

+ 30 - 28
src/packages/pc/components/base/menu-group/index.vue → src/packages/pc/components/modules/auth-group/index.vue

@@ -1,38 +1,38 @@
 <template>
-    <div class="app-menu-group">
+    <div class="app-auth-group">
         <teleport to="body" v-if="type === 'contextmenu'">
-            <div class="app-menu-group__contextmenu" @contextmenu.prevent.capture @click="closeContextmenu"
+            <div class="app-auth-group__contextmenu" @contextmenu.prevent.capture @click="closeContextmenu"
                 v-show="contextmenuOptions.show">
                 <ul :style="styles">
-                    <li :class="item.code" v-for="(item, index) in menus" :key="index"
+                    <li :class="item.className" v-for="(item, index) in actionMenus" :key="index"
                         @click.stop="onClick(item, index)">
                         <el-icon v-if="item.icon">
                             <component :is="item.icon" />
                         </el-icon>
-                        <span>{{ item.title }}</span>
+                        <span>{{ item.label }}</span>
                     </li>
                 </ul>
             </div>
         </teleport>
-        <el-dropdown class="app-menu-group__dropdown" v-else-if="type === 'dropdown'">
+        <el-dropdown class="app-auth-group__dropdown" v-else-if="type === 'dropdown'">
             <el-button type="info" icon="Setting" />
             <template #dropdown>
                 <el-dropdown-menu>
-                    <el-dropdown-item :class="item.code" v-for="(item, index) in menus" :key="index" :icon="item.icon"
-                        @click="onClick(item, index)">
-                        {{ item.title }}
+                    <el-dropdown-item :class="item.className" v-for="(item, index) in actionMenus" :key="index"
+                        :icon="item.icon" @click="onClick(item, index)">
+                        {{ item.label }}
                     </el-dropdown-item>
                 </el-dropdown-menu>
             </template>
         </el-dropdown>
-        <div class="app-menu-group__btnbar" v-else>
-            <template v-for="(item, index) in menus" :key="index">
-                <el-button :class="item.code" :type="item.buttonType" :disabled="activeName === item.code"
+        <div class="app-auth-group__btnbar" v-else>
+            <template v-for="(item, index) in actionMenus" :key="index">
+                <el-button :class="item.className" :type="item.type" :disabled="item.disabled"
                     @click="onClick(item, index)" :link="linkButton">
                     <el-icon v-if="item.icon">
                         <component :is="item.icon" />
                     </el-icon>
-                    <span v-if="item.title">{{ item.title }}</span>
+                    <span v-if="item.label">{{ item.label }}</span>
                 </el-button>
             </template>
         </div>
@@ -40,17 +40,8 @@
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, computed, watch, PropType } from 'vue'
-
-interface ActionMenu {
-    name: string;
-    label: string;
-    type?: string;
-    className?: string;
-    icon?: string;
-    disabled?: boolean;
-    hide?: boolean;
-}
+import { shallowRef, reactive, computed, watch, PropType, onMounted } from 'vue'
+import { ActionMenu } from './interface'
 
 const props = defineProps({
     // 操作菜单
@@ -77,12 +68,12 @@ const props = defineProps({
         type: Boolean,
         default: false,
     },
-    options: Object
+    record: Object,
 })
 
 const emit = defineEmits(['click'])
 const contextmenuOptions = shallowRef(props.contextmenu)
-const activeName = shallowRef('')
+const actionMenus = reactive<ActionMenu[]>([])
 
 // 右键菜单坐标
 const styles = computed(() => {
@@ -98,11 +89,22 @@ const closeContextmenu = () => {
     contextmenuOptions.value.show = false
 }
 
-const onClick = (item: Ermcp.UserMenu, index: number) => {
-    activeName.value = item.code
+const onClick = (item: ActionMenu, index: number) => {
     closeContextmenu()
-    emit('click', activeName.value, props.options, index)
+    emit('click', item, props.record, index)
 }
 
+onMounted(() => {
+    props.menus.forEach((e) => {
+        actionMenus.push({
+            name: e.code,
+            label: e.title,
+            icon: e.icon,
+            className: e.className,
+            hide: e.hidden,
+        })
+    })
+})
+
 watch(() => props.contextmenu, (options) => contextmenuOptions.value = options)
 </script>

+ 9 - 0
src/packages/pc/components/modules/auth-group/interface.ts

@@ -0,0 +1,9 @@
+export interface ActionMenu {
+    name: string;
+    label: string;
+    type?: string;
+    className?: string;
+    icon?: string;
+    disabled?: boolean;
+    hide?: boolean;
+}

+ 142 - 0
src/packages/pc/views/customs/exit/components/edit/detail-edit/index.vue

@@ -0,0 +1,142 @@
+<template>
+    <app-drawer :title="detail?.OrderIndex ? '修改批次' : '新增批次'" :width="860" v-model:show="show">
+        <el-form ref="formRef" class="el-form--horizontal" label-width="130px" :model="formItem" :rules="formRules">
+            <el-form-item label="货物编号" prop="GZNo">
+                <el-input placeholder="请输入" v-model="formItem.GZNo" />
+            </el-form-item>
+            <el-form-item label="形状" prop="GZCJShapeType">
+                <el-select v-model="formItem.GZCJShapeType">
+                    <el-option :label="item.label" :value="item.value" v-for="(item, index) in getGZCJShapeTypeList()"
+                        :key="index" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="重量(ct)" prop="Weight">
+                <el-input type="number" placeholder="请输入" v-model.number="formItem.Weight" />
+            </el-form-item>
+            <el-form-item label="参考货值(USD)" prop="Amount">
+                <el-input type="number" placeholder="请输入" v-model.number="formItem.Amount" />
+            </el-form-item>
+            <template v-if="formData.GZCJAccountType === 2">
+                <el-form-item label="刻印服务" prop="GZCJMarkType">
+                    <el-select v-model="formItem.GZCJMarkType">
+                        <el-option :label="item.label" :value="item.value"
+                            v-for="(item, index) in getGZCJMarkTypeList()" :key="index" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="是否披露处理" prop="GZCJPublishType">
+                    <el-select v-model="formItem.GZCJPublishType">
+                        <el-option :label="item.label" :value="item.value"
+                            v-for="(item, index) in getGZCJPublishTypeList()" :key="index" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="服务类别" prop="GZCJServiceType">
+                    <el-select v-model="formItem.GZCJServiceType">
+                        <el-option :label="item.enumdicname" :value="item.enumitemname"
+                            v-for="(item, index) in gzcjServiceTypeList" :key="index" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="原证书号" prop="OriginCertNo">
+                    <el-input placeholder="请输入" v-model="formItem.OriginCertNo" />
+                </el-form-item>
+            </template>
+            <el-form-item label="彩钻信息" prop="ColorInfo">
+                <el-input placeholder="请输入" v-model="formItem.ColorInfo" />
+            </el-form-item>
+            <el-form-item class="el-form-item--row" label="其他" prop="Remark">
+                <el-input type="textarea" v-model="formItem.Remark" />
+            </el-form-item>
+        </el-form>
+        <template #footer>
+            <el-button @click="onCancel" plain>取消</el-button>
+            <el-button type="primary" @click="onSubmit">保存</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { ref, PropType, computed } from 'vue'
+import type { FormInstance, FormRules, } from 'element-plus'
+import { useEnumStore } from '@/stores'
+import { getGZCJShapeTypeList, getGZCJMarkTypeList, getGZCJPublishTypeList } from '@/constants/customs'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+
+const props = defineProps({
+    formData: {
+        type: Object as PropType<Proto.CJJCApplyReq>,
+        required: true,
+    },
+    detail: {
+        type: Object as PropType<Proto.GZCJCategoryDetail>
+    }
+})
+
+const emit = defineEmits(['update'])
+const { enumMap } = useEnumStore()
+const show = ref(true)
+const formRef = ref<FormInstance>()
+const formItem = ref<Partial<Proto.GZCJCategoryDetail>>({ ...props.detail })
+
+// 服务类别列表
+const gzcjServiceTypeList = computed(() => {
+    const enums = enumMap.get('GZCJServiceType')
+    if (enums) {
+        return enums.value.filter(({ enumitemvalue }) => {
+            switch (props.formData.GZCJCategoryType) {
+                case 1:
+                case 3: {
+                    return enumitemvalue === '0' || enumitemvalue === '1'
+                }
+                case 2: {
+                    return enumitemvalue === '0' || enumitemvalue === '2'
+                }
+            }
+            return false
+        })
+    }
+    return []
+})
+
+const formRules: FormRules = {
+    GZNo: [{
+        required: true,
+        message: '请输入货物编号'
+    }],
+    GZCJShapeType: [{
+        required: true,
+        message: '请选择形状'
+    }],
+    Weight: [{
+        required: true,
+        message: '请输入重量'
+    }],
+    Amount: [{
+        required: true,
+        message: '请输入参考货值'
+    }],
+    GZCJMarkType: [{
+        required: true,
+        message: '请选择刻印服务'
+    }],
+    GZCJPublishType: [{
+        required: true,
+        message: '请选择是否披露处理'
+    }],
+    GZCJServiceType: [{
+        required: true,
+        message: '请选择服务类别'
+    }],
+}
+
+const onCancel = () => {
+    show.value = false
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            emit('update', formItem.value)
+            onCancel()
+        }
+    })
+}
+</script>

+ 17 - 0
src/packages/pc/views/customs/exit/components/edit/index.less

@@ -0,0 +1,17 @@
+.customs-exit-edit {
+    &__info {
+        font-size: 12px;
+        line-height: 18px;
+        color: #999;
+        border: 1px solid #eee;
+        padding: 15px;
+        margin-bottom: 20px;
+    }
+
+    &__table {
+        .btnbar {
+            text-align: right;
+            margin-bottom: 12px;
+        }
+    }
+}

+ 314 - 0
src/packages/pc/views/customs/exit/components/edit/index.vue

@@ -0,0 +1,314 @@
+<!-- 交易服务-出境检测-提交申请/修改 -->
+<template>
+    <app-drawer class="customs-exit-edit" title="出境申请" :width="960" v-model:show="show" :loading="loading"
+        :refresh="refresh">
+        <div class="customs-exit-edit__info">
+            <p>*若货物由广州钻石交易中心经快递发送,快递员揽件时即视为委托方已收货,广州钻石交易中心不承担后续货物安全责任。</p>
+            <p>本表将作为委托方委托广州钻石交易中心办理出境检测业务申请使用,敬请务必如实填写公司信息、货物资料,并盖章确认。广州钻石交易中心根据此表确认信息,作为接受委托办理该批货物出境检测业务的凭证,仅对数量负责。</p>
+        </div>
+        <el-form ref="formRef" class="el-form--horizontal" label-width="170px" :model="formData" :rules="formRules">
+            <el-form-item :class="formData.GZCJAccountType === 2 ? 'el-form-item--row' : ''" label="用户类型"
+                prop="GZCJAccountType">
+                <el-radio-group v-model="formData.GZCJAccountType" @change="clearList">
+                    <el-radio :label="item.value" v-for="(item, index) in gzcjAccountTypeList" :key="index">
+                        {{ item.label }}
+                    </el-radio>
+                </el-radio-group>
+            </el-form-item>
+            <el-form-item label="送检账户" prop="GZCJAccount" v-if="formData.GZCJAccountType === 1">
+                <el-input placeholder="请输入" v-model="formData.GZCJAccount" />
+            </el-form-item>
+            <el-form-item label="公司名称(中文)" prop="CompanyNameCN">
+                <el-input placeholder="请输入" v-model="formData.CompanyNameCN" />
+            </el-form-item>
+            <el-form-item label="公司名称(英文)" prop="CompanyNameEn">
+                <el-input placeholder="请输入" v-model="formData.CompanyNameEn" />
+            </el-form-item>
+            <el-form-item class="el-form-item--row" label="地址(中文)" prop="AddressCN">
+                <el-input placeholder="请输入" v-model="formData.AddressCN" />
+            </el-form-item>
+            <el-form-item class="el-form-item--row" label="地址(英文)" prop="AddressEN">
+                <el-input placeholder="请输入" v-model="formData.AddressEN" />
+            </el-form-item>
+            <el-form-item label="联系人姓名" prop="ContactName">
+                <el-input placeholder="请输入" v-model="formData.ContactName" />
+            </el-form-item>
+            <el-form-item label="联系人职位" prop="ContactPosition">
+                <el-input placeholder="请输入" v-model="formData.ContactPosition" />
+            </el-form-item>
+            <el-form-item label="联系人电话" prop="ContactPhoneNo">
+                <el-input type="number" placeholder="请输入" v-model.number="formData.ContactPhoneNo" />
+            </el-form-item>
+            <el-form-item label="邮箱" prop="Email">
+                <el-input placeholder="请输入" v-model="formData.Email" />
+            </el-form-item>
+            <el-form-item label="货物品类" prop="GZCJCategoryType">
+                <el-select v-model="formData.GZCJCategoryType">
+                    <el-option :label="item.label" :value="item.value"
+                        v-for="(item, index) in getGZCJCategoryTypeList()" :key="index" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="成品钻石加工国" prop="ProcessingCountry">
+                <el-input placeholder="请输入" v-model="formData.ProcessingCountry" />
+            </el-form-item>
+            <el-form-item label="天然钻石毛坯原产地" prop="ZSOrigin">
+                <el-input placeholder="请输入" v-model="formData.ZSOrigin" />
+            </el-form-item>
+            <el-form-item label="完成检测后收货方式" prop="GZCJDeliveryType">
+                <el-select v-model="formData.GZCJDeliveryType">
+                    <el-option :label="item.label" :value="item.value"
+                        v-for="(item, index) in getGZCJDeliveryTypeList()" :key="index" />
+                </el-select>
+            </el-form-item>
+        </el-form>
+        <div class="customs-exit-edit__table"
+            v-if="formData.GZCJAccountType === 2 ? !!formData.GZCJCategoryType : !!formData.GZCJAccountType">
+            <div class="btnbar">
+                <!-- <el-button size="small">导入</el-button> -->
+                <el-button size="small" @click="openEdit()">新增</el-button>
+                <el-button size="small" @click="clearList()">清空</el-button>
+            </div>
+            <app-table :data="formData.GZCJCategoryDetails" :columns="columns" :max-height="400" :show-header="false"
+                border>
+                <!-- 形状 -->
+                <template #GZCJShapeType="{ value }">
+                    {{ getGZCJShapeTypeName(value) }}
+                </template>
+                <!-- 刻印服务(证书号/ 其他/ 无) -->
+                <template #GZCJMarkType="{ value }">
+                    {{ getGZCJMarkTypeName(value) }}
+                </template>
+                <!-- 是否披露处理 -->
+                <template #GZCJPublishType="{ value }">
+                    {{ getGZCJPublishTypeName(value) }}
+                </template>
+                <!-- 服务类别 -->
+                <template #GZCJServiceType="{ value }">
+                    {{ getGZCJServiceTypeName(value) }}
+                </template>
+                <!-- 操作 -->
+                <template #operate="{ row, index }">
+                    <el-button size="small" @click="openEdit(row)">修改</el-button>
+                    <el-button size="small" @click="deleteDetail(index)">删除</el-button>
+                </template>
+            </app-table>
+        </div>
+        <template #footer>
+            <el-button @click="onCancel(false)" plain>取消</el-button>
+            <el-button type="primary" @click="onSubmit(true)">保存草稿</el-button>
+            <el-button type="primary" @click="onSubmit()">提交申请</el-button>
+        </template>
+        <component :is="componentMap.get(componentId)" v-bind="{ formData, detail }" @update="onUpdate"
+            @closed="closeComponent" v-if="componentId" />
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed, onMounted, defineAsyncComponent, PropType } from 'vue'
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules, } from 'element-plus'
+import { useComponent } from '@/hooks/component'
+import { validateRules } from '@/constants/regex'
+import { getGZCJAccountTypeList, getGZCJCategoryTypeList, getGZCJDeliveryTypeList, getGZCJShapeTypeName, getGZCJPublishTypeName, getGZCJMarkTypeName, getGZCJServiceTypeName } from '@/constants/customs'
+import { useCJJCOrderEdit } from '@/business/customs'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+import AppTable from '@pc/components/base/table/index.vue'
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Ermcp.GZCJJCOrderRsp>
+    }
+})
+
+const componentMap = new Map<string, unknown>([
+    ['detailEdit', defineAsyncComponent(() => import('./detail-edit/index.vue'))],
+])
+
+const { componentId, openComponent, closeComponent } = useComponent()
+const { loading, formData, formSubmit } = useCJJCOrderEdit(props.selectedRow)
+const show = ref(true)
+const refresh = ref(false)
+const formRef = ref<FormInstance>()
+const gzcjAccountTypeList = getGZCJAccountTypeList()
+const detail = ref<Proto.GZCJCategoryDetail>() // 当前选择的货物明细
+
+const columns = computed<Ermcp.TableColumn[]>(() => {
+    switch (formData.GZCJAccountType) {
+        case 1: {
+            return [
+                { prop: 'GZNo', label: '货物编号', show: true },
+                { prop: 'GZCJShapeType', label: '形状', width: 200, show: true },
+                { prop: 'Weight', label: '重量ct', show: true },
+                { prop: 'Amount', label: '参考货值USD', show: true },
+                { prop: 'ColorInfo', label: '彩钻信息', show: true },
+                { prop: 'Remark', label: '其他', show: true },
+                { prop: 'operate', label: '操作', width: 160, fixed: 'right', show: true }
+            ]
+        }
+        case 2: {
+            return [
+                { prop: 'GZNo', label: '货物编号', show: true },
+                { prop: 'GZCJShapeType', label: '形状', width: 200, show: true },
+                { prop: 'Weight', label: '重量ct', show: true },
+                { prop: 'Amount', label: '参考货值USD', show: true },
+                { prop: 'GZCJMarkType', label: '刻印服务(证书号/ 其他/ 无)', width: 220, show: true },
+                { prop: 'GZCJPublishType', label: '是否披露处理', show: true },
+                { prop: 'GZCJServiceType', label: '服务类别', show: true },
+                { prop: 'OriginCertNo', label: '原证书号', show: true },
+                { prop: 'ColorInfo', label: '彩钻信息', show: true },
+                { prop: 'Remark', label: '其他', show: true },
+                { prop: 'operate', label: '操作', width: 160, fixed: 'right', show: true }
+            ]
+        }
+    }
+    return []
+})
+
+const formRules: FormRules = {
+    GZCJAccountType: [{
+        required: true,
+        message: '请选择用户类型'
+    }],
+    GZCJAccount: [{
+        required: true,
+        message: '请输入送检账户'
+    }],
+    CompanyNameCN: [{
+        required: true,
+        message: '请输入公司名称(中文)'
+    }],
+    CompanyNameEn: [{
+        required: true,
+        message: '请输入公司名称(英文)'
+    }],
+    AddressCN: [{
+        required: true,
+        message: '请输入地址(中文)'
+    }],
+    AddressEN: [{
+        required: true,
+        message: '请输入地址(英文)'
+    }],
+    ContactName: [{
+        required: true,
+        message: '请输入联系人姓名'
+    }],
+    ContactPosition: [{
+        required: true,
+        message: '请输入联系人职位'
+    }],
+    ContactPhoneNo: [{
+        required: true,
+        trigger: 'blur',
+        validator: (rule, value, callback) => {
+            if (value) {
+                if (validateRules.phone.validate(value)) {
+                    callback()
+                } else {
+                    callback(new Error(validateRules.phone.message))
+                }
+            } else {
+                callback(new Error('请输入联系人电话'))
+            }
+        }
+    }],
+    Email: [{
+        required: true,
+        trigger: 'blur',
+        validator: (rule, value, callback) => {
+            if (value) {
+                if (validateRules.email.validate(value)) {
+                    callback()
+                } else {
+                    callback(new Error(validateRules.email.message))
+                }
+            } else {
+                callback(new Error('请输入邮箱'))
+            }
+        }
+    }],
+    GZCJCategoryType: [{
+        required: true,
+        message: '请选择货物品类'
+    }],
+    ProcessingCountry: [{
+        required: true,
+        message: '请输入成品钻石加工国'
+    }],
+    ZSOrigin: [{
+        required: true,
+        message: '请输入天然钻石毛坯原产地'
+    }],
+    GZCJDeliveryType: [{
+        required: true,
+        message: '请选择完成检测后收货方式'
+    }],
+}
+
+// 打开编辑
+const openEdit = (row?: Proto.GZCJCategoryDetail) => {
+    detail.value = row
+    openComponent('detailEdit')
+}
+
+// 清空列表数据
+const clearList = () => {
+    formData.GZCJCategoryDetails = []
+}
+
+// 删除列表数据
+const deleteDetail = (index: number) => {
+    formData.GZCJCategoryDetails?.splice(index, 1)
+    formData.GZCJCategoryDetails?.forEach((e, i) => e.OrderIndex = i + 1) // 重置序列
+}
+
+// 更新列表数据
+const onUpdate = (item: Proto.GZCJCategoryDetail) => {
+    const { GZCJCategoryDetails = [] } = formData
+    const index = GZCJCategoryDetails.findIndex((e) => e.OrderIndex === item.OrderIndex)
+
+    if (index > -1) {
+        GZCJCategoryDetails[index] = item
+    } else {
+        item.OrderIndex = GZCJCategoryDetails.length + 1
+        GZCJCategoryDetails.push(item)
+    }
+}
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onSubmit = (draft = false) => {
+    if (draft) {
+        formData.ApplyType = props.selectedRow?.orderid ? 2 : 1
+    } else {
+        formData.ApplyType = 3
+    }
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            if (formData.GZCJCategoryDetails?.length) {
+                formSubmit().then(() => {
+                    ElMessage.success('提交成功')
+                    onCancel(true)
+                }).catch((err) => {
+                    ElMessage.error(err)
+                })
+            } else {
+                ElMessage.warning('请添加批次')
+            }
+        }
+    })
+}
+
+onMounted(() => {
+    if (gzcjAccountTypeList.length) {
+        formData.GZCJAccountType = gzcjAccountTypeList[0].value
+    }
+})
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 21 - 3
src/packages/pc/views/customs/exit/index.vue

@@ -3,22 +3,40 @@
     <app-view>
         <!-- 表格数据 -->
         <app-table :data="dataList" v-model:columns="columns" :loading="loading">
+            <template #header>
+                <app-auth-operation :menus="['add']" />
+            </template>
             <!-- 操作 -->
             <template #operate="{ row }">
-                <app-auth-operation :options="{ selectedRow: row }" />
+                <app-auth-operation :menus="handleOperateButtons(row)" :options="{ selectedRow: row }"
+                    @closed="getGZCJJCOrderList" />
             </template>
             <template #footer>
-                <app-pagination :total="total" v-model:page-size="pageSize" v-model:page-index="pageIndex" />
+                <app-pagination :total="total" v-model:page-size="pageSize" v-model:page-index="pageIndex"
+                    @change="getGZCJJCOrderList" />
             </template>
         </app-table>
+        {{ getGZCJStatusList() }}
     </app-view>
 </template>
 
 <script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import { getGZCJStatusList } from '@/constants/customs'
 import { useCJJCOrder } from '@/business/customs'
 import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
 import AppTable from '@pc/components/base/table/index.vue'
 import AppPagination from '@pc/components/base/pagination/index.vue'
 
-const { loading, dataList, columns, total, pageIndex, pageSize } = useCJJCOrder()
+const { loading, dataList, columns, total, pageIndex, pageSize, getGZCJJCOrderList } = useCJJCOrder()
+
+const handleOperateButtons = (row: Ermcp.GZCJJCOrderRsp) => {
+    switch (row.gzcjstatus) {
+        default: {
+            return null
+        }
+    }
+}
+
+getGZCJJCOrderList().catch((err) => ElMessage.error(err))
 </script>

+ 5 - 1
src/packages/pc/views/trade/buy/components/details/index.vue

@@ -1,6 +1,6 @@
 <!-- 挂牌大厅-求购大厅-详情 -->
 <template>
-    <teleport to="#appMainTeleport">
+    <teleport :to="teleportTo">
         <app-view class="app-details" v-bind="$attrs">
             <template #header>
                 <div>
@@ -39,6 +39,10 @@ import AppPerformanceRule from '@pc/components/modules/performance-rule/index.vu
 
 const props = defineProps({
     code: String,
+    teleportTo: {
+        type: String as PropType<'#appPageTeleport' | '#appMainTeleport'>,
+        default: '#appPageTeleport'
+    },
     selectedRow: {
         type: Object as PropType<Ermcp.BuyOrderRsp>,
         default: () => ({})

+ 61 - 0
src/packages/pc/views/trade/buy/index.next.vue

@@ -0,0 +1,61 @@
+<!-- 挂牌大厅-求购大厅 -->
+<template>
+    <app-view>
+        <template #header>
+            <app-filter v-bind="{ selectList, inputList, buttonList }" :loading="loading" />
+        </template>
+        <!-- 表格数据 -->
+        <app-table :data="dataList" v-model:columns="columns" :loading="loading">
+            <!-- 操作 -->
+            <template #operate="{ row }">
+                <app-auth-group :menus="handleOperateButtons(row)" :record="row" @click="openComponent" />
+            </template>
+            <template #footer>
+                <app-pagination :total="total" v-model:page-size="pageSize" v-model:page-index="pageIndex"
+                    @change="getBuyOrderList" />
+            </template>
+        </app-table>
+        <component ref="componentRef" :is="componentMap.get(componentId)" v-bind="{ selectedRow }"
+            @closed="closeComponent" v-if="componentId" />
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import { useBuyOrder } from '@/business/trade/list'
+import { useLoginStore } from '@/stores'
+import { useMenu } from '@/hooks/menu'
+import { useTable } from '@pc/components/base/table'
+import { ActionMenu } from '@pc/components/modules/auth-group/interface'
+import AppAuthGroup from '@pc/components/modules/auth-group/index.vue'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppPagination from '@pc/components/base/pagination/index.vue'
+import AppFilter from '@pc/components/base/table-filter/index.vue'
+
+const { getUserId } = useLoginStore()
+const { componentMap, componentId, getAuthButtons } = useMenu()
+const { selectedRow } = useTable<Ermcp.SellOrderRsp>('uuid')
+const { loading, dataList, columns, total, pageIndex, pageSize, selectList, inputList, buttonList, getBuyOrderList } = useBuyOrder()
+const actionMenus = getAuthButtons()
+
+const handleOperateButtons = (row: Ermcp.SellOrderRsp) => {
+    if (row.userid === getUserId()) {
+        // 自己上架的商品不能摘牌
+        return actionMenus.filter((e) => !['trade_buy_delisting'].includes(e.code))
+    }
+    return actionMenus
+}
+
+const openComponent = (e: ActionMenu, row: Ermcp.SellOrderRsp) => {
+    selectedRow.value = row
+    componentId.value = e.name
+}
+
+const closeComponent = (refresh: boolean) => {
+    componentId.value = undefined
+    selectedRow.value = undefined
+    refresh && getBuyOrderList()
+}
+
+getBuyOrderList().catch((err) => ElMessage.error(err))
+</script>

+ 3 - 2
src/services/api/customs/index.ts

@@ -2,6 +2,7 @@ import { httpRequest } from '@/services/http'
 import { HttpParams } from '@/services/http/interface'
 import { tradeServerRequest } from '@/services/socket/trade'
 import { TradeParams } from '@/services/socket/trade/interface'
+import { Market } from '@/constants/market'
 
 /**
  * 查询出境检测单据
@@ -48,8 +49,8 @@ export function queryGZCJBSOrderFile(params: HttpParams<{ req: Ermcp.GZCJBSOrder
 /**
  * 出境检测申请
  */
-export function cjjcApply(params: TradeParams<Proto.CJJCApplyReq, Proto.CJJCApplyRsp>) {
-    return tradeServerRequest('CJJCApplyReq', 'CJJCApplyRsp', params);
+export function cjjcApply(params: TradeParams<Partial<Proto.CJJCApplyReq>, Proto.CJJCApplyRsp>) {
+    return tradeServerRequest('CJJCApplyReq', 'CJJCApplyRsp', params, Market.GZ);
 }
 
 /**

+ 15 - 7
src/stores/modules/enum.ts

@@ -17,7 +17,7 @@ interface StoreState {
     allEnums: ShallowRef<Ermcp.EnumRsp[]>;
 }
 
-const enumKeys = ['ZSCategory', 'ZSCurrencyType', 'ZSCurrencyType', 'ZSColorType', 'ZSClarityType', 'ZSCutType', 'ZSShapeType', 'ZSSymmetryType', 'ZSPolishType', 'ZSFluorescenceType', 'ZSCertType', 'ZSCrystalType', 'ZSCZColor1Type', 'ZSCZColor2Type', 'ZSCZColor3Type', 'ZSStyleType', 'signstatus', 'applystatus', 'executetype', 'certificatetype', 'clientType', 'wrApplyStatus', 'performanceStatus', 'stepStatus'] as const
+const enumKeys = ['ZSCategory', 'ZSCurrencyType', 'ZSCurrencyType', 'ZSColorType', 'ZSClarityType', 'ZSCutType', 'ZSShapeType', 'ZSSymmetryType', 'ZSPolishType', 'ZSFluorescenceType', 'ZSCertType', 'ZSCrystalType', 'ZSCZColor1Type', 'ZSCZColor2Type', 'ZSCZColor3Type', 'ZSStyleType', 'signstatus', 'applystatus', 'executetype', 'certificatetype', 'clientType', 'wrApplyStatus', 'performanceStatus', 'stepStatus', 'GZCJAccountType', 'GZCJCategoryType', 'GZCJDeliveryType', 'GZCJShapeType', 'GZCJMarkType', 'GZCJPublishType', 'GZCJServiceType', 'GZCJStatus'] as const
 
 /**
  * 枚举存储类
@@ -37,7 +37,7 @@ const store = new (class extends VueStore<StoreState>{
         }
     }
 
-    private enumMap = new Map<typeof enumKeys[number], ShallowRef<Ermcp.EnumRsp[]>>()
+    enumMap = new Map<typeof enumKeys[number], ShallowRef<Ermcp.EnumRsp[]>>()
 
     private setEnumMap = () => {
         // 清空列表数据
@@ -77,13 +77,20 @@ const store = new (class extends VueStore<StoreState>{
             return enums?.value.find((e) => e.enumitemname === value)
         },
         /** 获取枚举列表 */
-        getEnumTypeList: (enumKey: typeof enumKeys[number]) => {
+        getEnumTypeList: (enumKey: typeof enumKeys[number], propertys?: (keyof Ermcp.EnumRsp)[]) => {
             const enums = this.enumMap.get(enumKey)
             if (enums) {
-                return enums.value.map((e) => ({
-                    label: e.enumdicname,
-                    value: e.enumitemname,
-                }))
+                return enums.value.map((e) => {
+                    const props = propertys?.reduce((res, prop) => {
+                        const value = e[prop]
+                        if (value) res.push(value)
+                        return res
+                    }, [] as unknown[])
+                    return {
+                        label: props?.length ? props.join('-') : e.enumdicname,
+                        value: e.enumitemname,
+                    }
+                })
             }
             return []
         },
@@ -100,5 +107,6 @@ export function useEnumStore() {
         ...toRefs(store.state),
         ...store.actions,
         ...store.methods,
+        enumMap: store.enumMap,
     })
 }