li.shaoyi 2 년 전
부모
커밋
4f7880cba4
61개의 변경된 파일2642개의 추가작업 그리고 250개의 파일을 삭제
  1. 91 0
      public/proto/gz.proto
  2. 245 0
      src/business/bonded/index.ts
  3. 44 1
      src/business/table/columns.ts
  4. 96 0
      src/constants/bonded.ts
  5. 7 0
      src/constants/funcode.ts
  6. 2 2
      src/hooks/datatable/index.ts
  7. 114 7
      src/mock/router.ts
  8. 28 27
      src/packages/mobile/assets/themes/base/reset.less
  9. 3 3
      src/packages/mobile/components/base/tabbar/index.vue
  10. 3 3
      src/packages/mobile/components/layouts/navbar/index.vue
  11. 4 3
      src/packages/mobile/main.ts
  12. 1 0
      src/packages/pc/assets/themes/base/reset.less
  13. 2 1
      src/packages/pc/assets/themes/default/default.less
  14. 33 27
      src/packages/pc/components/base/upload/index.vue
  15. 5 6
      src/packages/pc/components/layouts/header/index.vue
  16. 2 2
      src/packages/pc/components/layouts/page/index.vue
  17. 8 8
      src/packages/pc/components/layouts/sidebar/index.vue
  18. 7 9
      src/packages/pc/components/modules/download/index.vue
  19. 4 3
      src/packages/pc/main.ts
  20. 148 0
      src/packages/pc/views/bonded/components/detail-edit/index.vue
  21. 111 0
      src/packages/pc/views/bonded/components/upload/index.vue
  22. 76 0
      src/packages/pc/views/bonded/expense/components/details/index.vue
  23. 9 0
      src/packages/pc/views/bonded/expense/components/payment/index.less
  24. 89 0
      src/packages/pc/views/bonded/expense/components/payment/index.vue
  25. 81 0
      src/packages/pc/views/bonded/expense/index.vue
  26. 13 0
      src/packages/pc/views/bonded/inbound/components/apply/index.less
  27. 183 0
      src/packages/pc/views/bonded/inbound/components/apply/index.vue
  28. 69 0
      src/packages/pc/views/bonded/inbound/components/details/index.vue
  29. 86 0
      src/packages/pc/views/bonded/inbound/index.vue
  30. 13 0
      src/packages/pc/views/bonded/outbound/components/apply/index.less
  31. 258 0
      src/packages/pc/views/bonded/outbound/components/apply/index.vue
  32. 170 0
      src/packages/pc/views/bonded/outbound/components/apply/subdetail-edit/index.vue
  33. 27 0
      src/packages/pc/views/bonded/outbound/components/details/index.vue
  34. 95 0
      src/packages/pc/views/bonded/outbound/index.vue
  35. 3 4
      src/packages/pc/views/customs/bonded/components/payment/index.vue
  36. 1 1
      src/packages/pc/views/customs/bonded/index.vue
  37. 0 48
      src/packages/pc/views/customs/exit/components/download/index.vue
  38. 2 2
      src/packages/pc/views/customs/exit/components/edit/index.vue
  39. 1 1
      src/packages/pc/views/customs/exit/index.vue
  40. 81 0
      src/services/api/bonded/index.ts
  41. 1 1
      src/stores/modules/enum.ts
  42. 57 0
      src/stores/modules/theme.ts
  43. 0 1
      src/types/ermcp/account.d.ts
  44. 0 1
      src/types/ermcp/bank.d.ts
  45. 265 0
      src/types/ermcp/bonded.d.ts
  46. 0 1
      src/types/ermcp/common.d.ts
  47. 0 1
      src/types/ermcp/customs.d.ts
  48. 0 1
      src/types/ermcp/enum.d.ts
  49. 0 1
      src/types/ermcp/favorite.d.ts
  50. 0 1
      src/types/ermcp/goods.d.ts
  51. 0 1
      src/types/ermcp/menu.d.ts
  52. 0 1
      src/types/ermcp/performance.d.ts
  53. 0 1
      src/types/ermcp/quote.d.ts
  54. 0 1
      src/types/ermcp/table.d.ts
  55. 0 1
      src/types/ermcp/trade.d.ts
  56. 0 1
      src/types/ermcp/user.d.ts
  57. 0 1
      src/types/ermcp/warehouse.d.ts
  58. 103 0
      src/types/proto/bonded.d.ts
  59. 0 1
      src/types/proto/customs.d.ts
  60. 0 75
      src/utils/client/index.ts
  61. 1 1
      src/utils/storage/index.ts

+ 91 - 0
public/proto/gz.proto

@@ -1069,4 +1069,95 @@ optional int32 RetCode = 2; // 返回码
 optional string RetDesc = 3; // 描述信息
 	optional uint64 OrderID = 4; // 单据ID,必填
 		optional uint32 IsSuccess = 5; // 返回码
+}
+
+// 保税仓进出仓商品明细列表
+message BSCGoodsListDetail {
+	optional uint64 GoodsID = 1; // 商品ID,必填
+	optional string GoodsSpec = 2; // 规格,必填
+	optional string RawDetail = 3; // 原料明细,必填
+	optional uint32 BackageNum = 4; // 件数,必填
+	optional double NetWeightCT = 5; // 净重(克拉),必填
+	optional double NetWeightGM = 6; // 净重(克),必填
+	optional double BagWeightGM = 7; // 连袋重(克),必填
+	optional double PrePriceGM = 8; // 单价(克),必填
+	optional double TotalPrice = 9; // 总价,必填
+	optional string CurrencyDes = 10; // 币种,必填
+	optional double CustomsValue = 11; // 报关总值,必填
+	optional string OriginCountry = 12; // 原产国,必填
+	optional string Remark = 13; // 备注,选填
+}
+
+// 保税仓进出仓附表
+message BSCOutWareHouseSchedule {
+	optional uint64 GoodsID = 1; // 商品ID,必填
+		optional string CustomsNo = 2; // 报关单号,必填
+		optional string JCKDate = 3; // 进口日期,必填
+		optional double CurNetWeightCT = 4; // 本次扣减量(克拉),必填
+}
+
+// 保税仓进出仓申请接口请求
+message BSCInAndOutWareHouseApplyReq {
+	optional MessageHead Header = 1;
+	optional uint32 UserID = 2; // 用户ID,必填
+	optional string UserName = 3; // 申请方名称,必填
+	optional string UserAddress = 4; // 申请方地点,必填
+	optional string ContactName = 5; // 申请方联系人,必填
+	optional string ContactNum = 6; // 申请方联系电话,必填
+	optional string LogisticsCompany = 7; // 物流公司名称,必填
+	optional string LogisticsNo = 8; // 托运单号,选填
+	optional uint32 OrderType = 9; // 单据类型,必填1:进仓2:出仓
+	repeated BSCGoodsListDetail BSCGoodsListDetails = 10; // 明细列表(数组),必填
+	repeated BSCOutWareHouseSchedule BSCOutWareHouseSchedules = 11; // 出仓附表(数组),必填
+	optional uint32 OperateID = 12; // 操作人ID,必填
+	optional string OperateAccount = 13; // 操作人账户,必填
+		optional string ClientSerialNo = 14; // 客户端流水号
+		optional uint32 OutType = 15; // 出仓类型,出仓类型-1:转厂2:出境(枚举:GZBSCOutType)OrderType=2:出仓
+}
+
+// 保税仓进出仓申请接口响应
+message BSCInAndOutWareHouseApplyRsp {
+	optional MessageHead Header = 1; // 消息头
+	optional int32 RetCode = 2; // 返回码
+	optional string RetDesc = 3; // 描述信息
+		optional uint64 OrderID = 4; // 单据ID,必填
+			optional string ClientSerialNo = 5; // 客户端流水号
+}
+
+// 保税仓上传文件接口请求
+message BSCUploadFileReq {
+	optional MessageHead Header = 1;
+		optional uint32 UserID = 2; // 用户ID,必填
+		optional uint64 OrderID = 3; // 单据ID,必填
+		repeated FileDetail FileDetails = 4; // 文件列表,必填
+			optional string ClientSerialNo = 5; // 客户端流水号
+		optional uint32 OperateID = 6; // 操作人ID,必填
+		optional string OperateAccount = 7; // 操作人账户,必填
+}
+
+// 保税仓上传文件接口响应
+message BSCUploadFileRsp {
+	optional MessageHead Header = 1; // 消息头
+	optional int32 RetCode = 2; // 返回码
+	optional string RetDesc = 3; // 描述信息
+		optional uint32 UserID = 4; // 用户ID,必填
+		optional uint64 OrderID = 5; // 单据ID,必填
+			optional string ClientSerialNo = 6; // 客户端流水号
+}
+
+// 保税仓确认支付接口请求
+message BSCConfirmPayReq {
+	optional MessageHead Header = 1;
+		optional uint32 UserID = 2; // 用户ID,必填
+		optional string TradeMonth = 3; // 月份(yyyMM),必填
+			optional string ClientSerialNo = 4; // 客户端流水号
+}
+
+// 保税仓确认支付接口响应
+message BSCConfirmPayRsp {
+	optional MessageHead Header = 1; // 消息头
+	optional int32 RetCode = 2; // 返回码
+	optional string RetDesc = 3; // 描述信息
+		optional uint32 UserID = 4; // 用户ID,必填
+			optional string ClientSerialNo = 5; // 客户端流水号
 }

+ 245 - 0
src/business/bonded/index.ts

@@ -0,0 +1,245 @@
+import { shallowRef, reactive, computed } from 'vue'
+import { v4 } from 'uuid'
+import { useDataTable } from '@/hooks/datatable'
+import { queryGzbscinOutOrder, queryBScinOutOrderDetail, queryGzbscusermonthpay, queryGzbscuserpowerfee, queryBscinoutorder, bscInAndOutWareHouseApply, bscUploadFile, bscConfirmPay } from '@/services/api/bonded'
+import { loginStore } from '@/stores'
+import Long from 'long'
+
+const { userId } = loginStore.$mapGetters()
+
+export type RequiredType<T, K extends keyof T> = Partial<T> & Pick<T, K> // 指定某个属性为必选
+
+// 保税仓出入库申请列表
+export function useGzbscinOutOrder() {
+    const { dataList, total, pageIndex, pageSize } = useDataTable<Ermcp.GzbscinOutOrderRsp>()
+    const loading = shallowRef(false)
+
+    const getGzbscinOutOrder = (params: RequiredType<Ermcp.GzbscinOutOrderReq, 'ordertype'>) => {
+        loading.value = true
+        return queryGzbscinOutOrder({
+            data: {
+                page: pageIndex.value,
+                pagesize: pageSize.value,
+                userid: userId.value,
+                ...params,
+            },
+            success: (res) => {
+                total.value = res.total
+                dataList.value = res.data
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        loading,
+        dataList,
+        total,
+        pageIndex,
+        pageSize,
+        getGzbscinOutOrder,
+    }
+}
+
+// 保税仓出库申请明细附表
+export function useBScinOutOrderDetail() {
+    const { dataList } = useDataTable<Ermcp.BScinOutOrderDetailRsp>()
+    const loading = shallowRef(false)
+
+    const getBScinOutOrderDetail = (params: RequiredType<Ermcp.BScinOutOrderDetailReq, 'orderid'>) => {
+        loading.value = true
+        return queryBScinOutOrderDetail({
+            data: {
+                userid: userId.value,
+                ...params,
+            },
+            success: (res) => {
+                dataList.value = res.data
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        loading,
+        dataList,
+        getBScinOutOrderDetail,
+    }
+}
+
+// 保税仓进出仓申请
+export function useBscInAndOutWareHouseApply(OrderType: number) {
+    const { UserID, LoginID, LoginCode } = loginStore.state.loginInfo
+    const loading = shallowRef(false)
+
+    const formData = reactive<Proto.BSCInAndOutWareHouseApplyReq>({
+        UserID, // 用户ID,必填
+        UserName: '', // 申请方名称,必填
+        UserAddress: '', // 申请方地点,必填
+        ContactName: '', // 申请方联系人,必填
+        ContactNum: '', // 申请方联系电话,必填
+        LogisticsCompany: '', // 物流公司名称,必填
+        LogisticsNo: '', // 托运单号,选填
+        OrderType, // 单据类型,必填1:进仓2:出仓
+        BSCGoodsListDetails: [], // 明细列表(数组),必填
+        BSCOutWareHouseSchedules: [], // 出仓附表(数组),必填
+        OperateID: LoginID, // 操作人ID,必填
+        OperateAccount: LoginCode, // 操作人账户,必填
+        ClientSerialNo: '', // 客户端流水号
+    })
+
+    const formSubmit = () => {
+        loading.value = true
+        return bscInAndOutWareHouseApply({
+            data: {
+                ...formData,
+                ClientSerialNo: v4(),
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        loading,
+        formData,
+        formSubmit,
+    }
+}
+
+// 保税仓上传文件
+export function useBscUploadFile(orderId: string) {
+    const { UserID, LoginID, LoginCode } = loginStore.state.loginInfo
+    const loading = shallowRef(false)
+
+    const formData = reactive<Proto.BSCUploadFileReq>({
+        UserID, // 用户ID,必填
+        OrderID: Long.fromString(orderId), // 单据ID,必填
+        FileDetails: [], // 文件列表,必填
+        ClientSerialNo: '', // 客户端流水号
+        OperateID: LoginID, // 操作人ID,必填
+        OperateAccount: LoginCode, // 操作人账户,必填
+    })
+
+    const formSubmit = () => {
+        loading.value = true
+        return bscUploadFile({
+            data: {
+                ...formData,
+                ClientSerialNo: v4(),
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        loading,
+        formData,
+        formSubmit,
+    }
+}
+
+// 保税仓计费管理列表
+export function useGzbscusermonthpay() {
+    const { dataList, total, pageIndex, pageSize } = useDataTable<Ermcp.GzbscusermonthpayRsp>()
+    const loading = shallowRef(false)
+
+    const getGzbscusermonthpay = (params: Partial<Ermcp.GzbscusermonthpayReq> = {}) => {
+        loading.value = true
+        return queryGzbscusermonthpay({
+            data: {
+                page: pageIndex.value,
+                pagesize: pageSize.value,
+                userid: userId.value,
+                ...params,
+            },
+            success: (res) => {
+                total.value = res.total
+                dataList.value = res.data
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        loading,
+        dataList,
+        total,
+        pageIndex,
+        pageSize,
+        getGzbscusermonthpay,
+    }
+}
+
+// 保税仓计费管理详情
+export function useGzbscusermonthpayDetails(trademonth: string) {
+    const powerfeeList = shallowRef<Ermcp.GzbscuserpowerfeeRsp[]>([]) // 电费明细列表
+    const bondedList = shallowRef<Ermcp.BscinoutorderRsp[]>([]) // 保税仓明细列表
+
+    // 进口明细列表
+    const importList = computed(() => bondedList.value.filter((e) => e.ordertype === 1))
+    // 出境明细列表
+    const exportList = computed(() => bondedList.value.filter((e) => e.ordertype === 2 && e.outtype === 1))
+    // 转厂明细列表
+    const transferList = computed(() => bondedList.value.filter((e) => e.ordertype === 2 && e.outtype === 2))
+
+    queryGzbscuserpowerfee({
+        data: {
+            userid: userId.value,
+            trademonth,
+        },
+        success: (res) => {
+            powerfeeList.value = res.data
+        }
+    })
+
+    queryBscinoutorder({
+        data: {
+            userid: userId.value,
+            jckdate: trademonth,
+        },
+        success: (res) => {
+            bondedList.value = res.data
+        }
+    })
+
+    return {
+        powerfeeList,
+        importList,
+        exportList,
+        transferList,
+    }
+}
+
+// 保税仓确认支付
+export function useBscConfirmPay(TradeMonth: string) {
+    const loading = shallowRef(false)
+
+    const formSubmit = () => {
+        loading.value = true
+        return bscConfirmPay({
+            data: {
+                UserID: userId.value, // 用户ID,必填
+                TradeMonth, // 月份(yyyMM),必填
+                ClientSerialNo: v4() // 客户端流水号
+            },
+            complete: () => {
+                loading.value = false
+            }
+        })
+    }
+
+    return {
+        loading,
+        formSubmit,
+    }
+}

+ 44 - 1
src/business/table/columns.ts

@@ -1,4 +1,4 @@
-const tableColumnKeys = ['system_menu', 'system_role', 'warehousing_diamond', 'warehousing_warehouse', 'mine_capital', 'listing_sellorder', 'listing_buyorder', 'listing_saleorder', 'listing_purchaseorder', 'listing_delisting', 'listing_bargain_buy', 'listing_bargain_sell', 'mine_address', 'mine_invoice', 'favorite', 'order', 'order_step', 'bargain_buy', 'bargain_sell', 'customs_cjjc', 'customs_bsfw', 'customs_bsfw_file', 'report_member', 'report_warehousing', 'report_trade'] as const
+const tableColumnKeys = ['system_menu', 'system_role', 'warehousing_diamond', 'warehousing_warehouse', 'mine_capital', 'listing_sellorder', 'listing_buyorder', 'listing_saleorder', 'listing_purchaseorder', 'listing_delisting', 'listing_bargain_buy', 'listing_bargain_sell', 'mine_address', 'mine_invoice', 'favorite', 'order', 'order_step', 'bargain_buy', 'bargain_sell', 'customs_cjjc', 'customs_bsfw', 'customs_bsfw_file', 'report_member', 'report_warehousing', 'report_trade', 'bonded_inbound', 'bonded_outbound', 'bonded_expense'] as const
 
 export type TableColumnKey = typeof tableColumnKeys[number]
 
@@ -354,4 +354,47 @@ export const pcTableColumnMap = new Map<TableColumnKey, Ermcp.TableColumn[]>([
         { prop: 'selltradeqty', label: '销售重量(ct)' },
         { prop: 'selltradeamount', label: '销售金额(¥)' },
     ]],
+    ['bonded_inbound', [
+        { prop: 'ordernum', label: '单据编号' },
+        { prop: 'goodsname', label: '商品名称' },
+        { prop: 'netweightct', label: '净重(克拉)' },
+        { prop: 'netweightgm', label: '净重(克)' },
+        { prop: 'bagweightgm', label: '连袋重(克)' },
+        { prop: 'totalprice', label: '总价' },
+        { prop: 'customstotalvalue', label: '报关总值' },
+        { prop: 'orderstatus', label: '单据状态' },
+        { prop: 'customsno', label: '报关单号' },
+        { prop: 'checklistno', label: '核注清单号' },
+        { prop: 'applicanttime', label: '申请时间' },
+        { prop: 'operate', label: '操作' }
+    ]],
+    ['bonded_outbound', [
+        { prop: 'ordernum', label: '单据编号' },
+        { prop: 'outtype', label: '出仓类型' },
+        { prop: 'goodsname', label: '商品名称' },
+        { prop: 'netweightct', label: '净重(克拉)' },
+        { prop: 'netweightgm', label: '净重(克)' },
+        { prop: 'bagweightgm', label: '连袋重(克)' },
+        { prop: 'totalprice', label: '总价' },
+        { prop: 'customstotalvalue', label: '报关总值' },
+        { prop: 'orderstatus', label: '单据状态' },
+        { prop: 'customsno', label: '报关单号' },
+        { prop: 'checklistno', label: '核注清单号' },
+        { prop: 'applicanttime', label: '申请时间' },
+        { prop: 'operate', label: '操作' }
+    ]],
+    ['bonded_expense', [
+        { prop: 'trademonth', label: '月份' },
+        { prop: 'servicefee', label: '分拣室服务费' },
+        { prop: 'storagefee', label: '仓储费' },
+        { prop: 'premium', label: '保险费' },
+        { prop: 'powerfee', label: '分拣室电费' },
+        { prop: 'infee', label: '进仓报关费' },
+        { prop: 'outfee', label: '出仓报关费' },
+        { prop: 'totalfee', label: '合计费用' },
+        { prop: 'paystatus', label: '支付状态' },
+        { prop: 'paymode', label: '支付方式' },
+        { prop: 'paytime', label: '支付时间' },
+        { prop: 'operate', label: '操作' }
+    ]],
 ])

+ 96 - 0
src/constants/bonded.ts

@@ -0,0 +1,96 @@
+import { enumStore } from '@/stores'
+
+const { getEnumTypeList, getEnumTypeName } = enumStore.actions
+
+/**
+ * 单据类型
+ */
+export enum GZBSCOrderType {
+    In = 1,  // 进仓
+    Out = 2,  // 出仓
+}
+
+/**
+ * 获取单据类型列表
+ * @returns 
+ */
+export function getGZBSCOrderTypeList() {
+    return getEnumTypeList('GZBSCOrderType')
+}
+
+/**
+ * 获取单据类型名称
+ * @returns 
+ */
+export function getGZBSCOrderTypeName(value?: number) {
+    const enums = getGZBSCOrderTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取单据状态列表
+ * @returns 
+ */
+export function getGZBSCOrderStatusList() {
+    return getEnumTypeList('GZBSCOrderStatus')
+}
+
+/**
+ * 获取单据状态名称
+ * @returns 
+ */
+export function getGZBSCOrderStatusName(value?: number) {
+    const enums = getGZBSCOrderStatusList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取出仓类型列表
+ * @returns 
+ */
+export function getGZBSCOutTypeList() {
+    return getEnumTypeList('GZBSCOutType')
+}
+
+/**
+ * 获取出仓类型名称
+ * @returns 
+ */
+export function getGZBSCOutTypeName(value?: number) {
+    const enums = getGZBSCOutTypeList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取支付状态列表
+ * @returns 
+ */
+export function getGZBSCPayStatusList() {
+    return getEnumTypeList('GZBSCPayStatus')
+}
+
+/**
+ * 获取支付状态名称
+ * @returns 
+ */
+export function getGZBSCPayStatusName(value?: number) {
+    const enums = getGZBSCPayStatusList()
+    return getEnumTypeName(enums, value)
+}
+
+/**
+ * 获取支付方式列表
+ * @returns 
+ */
+export function getGZBSCPayModeList() {
+    return getEnumTypeList('GZBSCPayMode')
+}
+
+/**
+ * 获取支付方式名称
+ * @returns 
+ */
+export function getGZBSCPayModeName(value?: number) {
+    const enums = getGZBSCPayModeList()
+    return getEnumTypeName(enums, value)
+}

+ 7 - 0
src/constants/funcode.ts

@@ -88,4 +88,11 @@ export enum FunCode {
     BSFWMemberOperateRsp = 1114120, // 保税服务会员操作响应
     CJJCConfirmMarginBuildFileReq = 1114121, // 出境检测确认预付款生成文件接口请求
     CJJCConfirmMarginBuildFileRsp = 1114122, // 出境检测确认预付款生成文件接口响应
+
+    BSCInAndOutWareHouseApplyReq = 1114125, // 保税仓进出仓申请接口请求
+    BSCInAndOutWareHouseApplyRsp = 1114126, // 保税仓进出仓申请接口响应
+    BSCUploadFileReq = 1114127, // 保税仓上传文件接口请求
+    BSCUploadFileRsp = 1114128, // 保税仓上传文件接口响应
+    BSCConfirmPayReq = 1114133, // 保税仓确认支付接口请求
+    BSCConfirmPayRsp = 1114134, // 保税仓确认支付接口响应
 }

+ 2 - 2
src/hooks/datatable/index.ts

@@ -105,9 +105,9 @@ export function useDataFilter<T>() {
     }
 
     // 获取查询参数,支持多条件查询
-    const getQueryParams = (callback: (params: Partial<T>) => void, clear = false) => {
+    const getQueryParams = (callback: (params: T) => void, clear = false) => {
         clear && clearAll()
-        const params: Partial<T> = {}
+        const params: T = Object.create(null)
 
         filterOptons.selectList.forEach((e) => {
             if (e.selectedValue !== undefined) {

+ 114 - 7
src/mock/router.ts

@@ -411,7 +411,7 @@ const appmenu = {
                                 authType: 3,
                                 title: '下载',
                                 code: 'customs_exit_download',
-                                component: 'views/customs/exit/components/download/index.vue',
+                                component: 'components/modules/download/index.vue',
                             },
                             {
                                 authType: 3,
@@ -464,7 +464,7 @@ const appmenu = {
                                 authType: 3,
                                 title: '下载',
                                 code: 'customs_bonded_download',
-                                component: 'views/customs/bonded/components/download/index.vue',
+                                component: 'components/modules/download/index.vue',
                             },
                             {
                                 authType: 3,
@@ -491,6 +491,113 @@ const appmenu = {
             {
                 authType: 1,
                 sort: 8,
+                title: '保税仓业务',
+                code: 'bonded',
+                url: '/bonded',
+                urlType: 1,
+                component: 'Main',
+                icon: 'g-icon--trade-filled',
+                children: [
+                    {
+                        authType: 1,
+                        sort: 1,
+                        title: '进仓管理',
+                        code: 'bonded_inbound',
+                        url: '',
+                        urlType: 1,
+                        component: 'views/bonded/inbound/index.vue',
+                        children: [
+                            {
+                                authType: 3,
+                                title: '提交申请',
+                                code: 'bonded_inbound_apply',
+                                component: 'views/bonded/inbound/components/apply/index.vue',
+                                className: 'el-button--primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '详情',
+                                code: 'bonded_inbound_details',
+                                component: 'views/bonded/inbound/components/details/index.vue',
+                            },
+                            {
+                                authType: 3,
+                                title: '下载',
+                                code: 'bonded_inbound_download',
+                                component: 'components/modules/download/index.vue',
+                            },
+                            {
+                                authType: 3,
+                                title: '上传',
+                                code: 'bonded_inbound_upload',
+                                component: 'views/bonded/components/upload/index.vue',
+                            },
+                        ]
+                    },
+                    {
+                        authType: 1,
+                        sort: 2,
+                        title: '出仓管理',
+                        code: 'bonded_outbound',
+                        url: 'outbound',
+                        urlType: 1,
+                        component: 'views/bonded/outbound/index.vue',
+                        children: [
+                            {
+                                authType: 3,
+                                title: '提交申请',
+                                code: 'bonded_outbound_apply',
+                                component: 'views/bonded/outbound/components/apply/index.vue',
+                                className: 'el-button--primary',
+                            },
+                            {
+                                authType: 3,
+                                title: '详情',
+                                code: 'bonded_outbound_details',
+                                component: 'views/bonded/outbound/components/details/index.vue',
+                            },
+                            {
+                                authType: 3,
+                                title: '下载',
+                                code: 'bonded_outbound_download',
+                                component: 'components/modules/download/index.vue',
+                            },
+                            {
+                                authType: 3,
+                                title: '上传',
+                                code: 'bonded_outbound_upload',
+                                component: 'views/bonded/components/upload/index.vue',
+                            },
+                        ]
+                    },
+                    {
+                        authType: 1,
+                        sort: 3,
+                        title: '计费管理',
+                        code: 'bonded_expense',
+                        url: 'expense',
+                        urlType: 1,
+                        component: 'views/bonded/expense/index.vue',
+                        children: [
+                            {
+                                authType: 3,
+                                title: '详情',
+                                code: 'bonded_expense_details',
+                                component: 'views/bonded/expense/components/details/index.vue',
+                            },
+                            {
+                                authType: 3,
+                                title: '支付',
+                                code: 'bonded_expense_payment',
+                                component: 'views/bonded/expense/components/payment/index.vue',
+                            },
+                        ]
+                    }
+                ]
+            },
+            {
+                authType: 1,
+                sort: 9,
                 title: '我的仓储',
                 code: 'warehousing',
                 url: '/warehousing',
@@ -575,7 +682,7 @@ const appmenu = {
             },
             {
                 authType: 1,
-                sort: 9,
+                sort: 10,
                 title: '会员中心',
                 code: 'mine',
                 url: '/mine',
@@ -726,7 +833,7 @@ const appmenu = {
             },
             {
                 authType: 1,
-                sort: 10,
+                sort: 11,
                 title: '报表',
                 code: 'report',
                 url: '/report',
@@ -765,7 +872,7 @@ const appmenu = {
             },
             {
                 authType: 1,
-                sort: 11,
+                sort: 12,
                 title: '询价消息',
                 code: 'bargain',
                 url: '/bargain',
@@ -824,7 +931,7 @@ const appmenu = {
             },
             {
                 authType: 1,
-                sort: 12,
+                sort: 13,
                 title: '帮助支持',
                 code: 'help',
                 url: '/help',
@@ -834,7 +941,7 @@ const appmenu = {
             },
             {
                 authType: 1,
-                sort: 13,
+                sort: 14,
                 title: '系统管理',
                 code: 'system',
                 url: '/system',

+ 28 - 27
src/packages/mobile/assets/themes/base/reset.less

@@ -2,14 +2,14 @@
 @import './animation.less';
 
 * {
-    font-family                : -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", STHeiti, "Microsoft Yahei", Tahoma, Simsun, sans-serif;
-    margin                     : 0;
-    padding                    : 0;
-    box-sizing                 : border-box;
-    background-repeat          : no-repeat;
-    background-position        : center center;
-    background-size            : contain;
-    outline                    : none;
+    font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", STHeiti, "Microsoft Yahei", Tahoma, Simsun, sans-serif;
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+    background-repeat: no-repeat;
+    background-position: center center;
+    background-size: contain;
+    outline: none;
     -webkit-tap-highlight-color: transparent;
 
     //-webkit-appearance: none;
@@ -23,19 +23,20 @@ html {
 }
 
 body {
-    height                    : inherit;
-    font-size                 : .28rem;
-    background-color          : #666;
-    margin                    : auto !important;
-    overflow                  : hidden;
+    position: relative;
+    height: inherit;
+    font-size: .28rem;
+    background-color: #666;
+    margin: auto !important;
+    overflow: hidden;
     -webkit-overflow-scrolling: touch;
 
     a {
         text-decoration: none;
-        color          : #333;
+        color: #333;
 
         &:hover {
-            color          : inherit;
+            color: inherit;
             text-decoration: underline;
         }
 
@@ -52,12 +53,12 @@ body {
     h4,
     h5,
     h6 {
-        margin     : 0;
+        margin: 0;
         font-weight: normal;
     }
 
     img {
-        border   : 0;
+        border: 0;
         max-width: 100%;
     }
 
@@ -65,10 +66,10 @@ body {
     input,
     textarea,
     select {
-        line-height     : initial;
-        border          : 0;
+        line-height: initial;
+        border: 0;
         background-color: transparent;
-        outline         : none;
+        outline: none;
 
         &:focus {
             outline: none;
@@ -76,10 +77,10 @@ body {
     }
 
     button {
-        display        : inline-flex;
+        display: inline-flex;
         justify-content: center;
-        align-items    : center;
-        cursor         : pointer;
+        align-items: center;
+        cursor: pointer;
 
         span {
             display: inline-block;
@@ -103,9 +104,9 @@ body {
 }
 
 .app {
-    width           : 100%;
-    height          : 100%;
+    width: 100%;
+    height: 100%;
     background-color: #fff;
-    position        : relative;
-    overflow-x      : hidden;
+    position: relative;
+    overflow-x: hidden;
 }

+ 3 - 3
src/packages/mobile/components/base/tabbar/index.vue

@@ -27,7 +27,7 @@
 <script lang="ts" setup>
 import { PropType, ref, watch, computed } from 'vue'
 import { Tabbar } from './interface'
-import client from '@/utils/client'
+import { themeStore } from '@/stores'
 
 const emit = defineEmits(['change', 'update:dataIndex'])
 
@@ -50,11 +50,11 @@ const props = defineProps({
 })
 
 const selectedIndex = ref(props.dataIndex);
-const { state } = client;
+const { clientWidth } = themeStore.$mapState();
 
 const styles = computed(() => ({
   position: props.fixed ? 'fixed' : 'static',
-  width: state.clientWidth + 'px',
+  width: clientWidth.value + 'px',
 }))
 
 const onChange = (index: number) => {

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

@@ -36,7 +36,7 @@
 import { useAttrs, computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { Icon } from 'vant'
-import client from '@/utils/client'
+import { themeStore } from '@/stores'
 
 const emit = defineEmits<{ (event: string, ...args: unknown[]): void }>()
 
@@ -54,11 +54,11 @@ const props = defineProps({
 
 const router = useRouter();
 const attrs = useAttrs();
-const { state } = client;
+const { clientWidth } = themeStore.$mapState();
 
 const styles = computed(() => ({
   position: props.fixed ? 'fixed' : 'static',
-  width: state.clientWidth + 'px',
+  width: clientWidth.value + 'px',
 }))
 
 // 返回按钮事件

+ 4 - 3
src/packages/mobile/main.ts

@@ -6,10 +6,10 @@ import directives from '@/directives' // 自定义指令集
 import '@/services/subscribe' // 全局订阅通知
 import '@/mock' // 模拟数据
 import '@/utils/h5plus' // 加载html5+
-import client from '@/utils/client' // 适配客户端
 import layouts from './components/layouts' // 全局布局组件
 import './assets/themes/style.less' // 主题样式
 import { timerInterceptor } from '@/utils/timer'
+import { themeStore } from '@/stores'
 // import Vconsole from 'vconsole'
 // new Vconsole();
 
@@ -21,7 +21,8 @@ app.mount('#app')
 
 // 等待 html 加载完成
 document.addEventListener('DOMContentLoaded', () => {
-    client.screenAdapter(true);
+    // 适配客户端
+    themeStore.actions.screenAdapter(true)
     // 监听窗口大小变化
-    window.addEventListener('resize', timerInterceptor.setDebounce(() => client.screenAdapter(true)));
+    window.addEventListener('resize', timerInterceptor.setDebounce(() => themeStore.actions.screenAdapter(true)))
 }, false)

+ 1 - 0
src/packages/pc/assets/themes/base/reset.less

@@ -23,6 +23,7 @@ html {
 }
 
 body {
+    position: relative;
     height: inherit;
     font-size: 14px;
     margin: auto;

+ 2 - 1
src/packages/pc/assets/themes/default/default.less

@@ -62,7 +62,8 @@
                 }
             }
 
-            .el-select {
+            .el-select,
+            .el-input-number {
                 width: 100%;
             }
         }

+ 33 - 27
src/packages/pc/components/base/upload/index.vue

@@ -1,8 +1,8 @@
 <template>
     <div class="app-upload">
-        <el-upload ref="uploadRef" :file-list="fileList" :limit="limit" :multiple="limit > 1" :action="uploadUrl"
-            :before-upload="onBeforeUpload" @success="onSuccess" @remove="onRemove" @preview="onPreview"
-            @exceed="onExceed">
+        <el-upload ref="uploadRef" :accept="acceptTypes.join(',')" :file-list="fileList" :limit="limit"
+            :multiple="limit > 1" :action="uploadUrl" :before-upload="onBeforeUpload" @success="onSuccess"
+            @remove="onRemove" @preview="onPreview" @exceed="onExceed">
             <template #tip>
                 <div class="el-upload__tip">
                     <slot name="tip"></slot>
@@ -18,7 +18,7 @@
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, computed, PropType, toRaw } from 'vue'
+import { shallowRef, shallowReactive, computed, PropType, toRaw, onMounted } from 'vue'
 import { ElMessage, UploadProps, UploadUserFile, UploadRawFile, UploadFile, UploadFiles, UploadInstance, genFileId } from 'element-plus'
 import { getImageUrl } from '@/filters'
 import service from '@/services'
@@ -48,32 +48,11 @@ const { uploadUrl } = service.config
 const uploadRef = shallowRef<UploadInstance>()
 const showViewer = shallowRef(false)
 const fileList = shallowRef<UploadUserFile[]>([])
+const acceptTypes = shallowReactive<string[]>([]) // 接受上传的文件类型
+const uploadTypes = shallowReactive<string[]>([]) // 允许上传的文件类型
 const imageIndex = shallowRef(0)
 const imageTypes = ['image/png', 'image/jpeg']
 
-// 允许上传的文件类型
-const uploadTypes = props.fileTypes.reduce((res, value) => {
-    switch (value) {
-        case 'image': {
-            res.push(...imageTypes)
-            break
-        }
-        case 'pdf': {
-            res.push('application/pdf')
-            break
-        }
-        case 'word': {
-            res.push('application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')
-            break
-        }
-        case 'excel': {
-            res.push('application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
-            break
-        }
-    }
-    return res
-}, [] as string[])
-
 // 预览图列表
 const imageList = computed(() => {
     const result: { uid: number, filePath: string }[] = []
@@ -161,6 +140,33 @@ const onPreview: UploadProps['onPreview'] = ({ uid, raw }: UploadFile) => {
 const onViewerClose = () => {
     showViewer.value = false
 }
+
+onMounted(() => {
+    props.fileTypes.forEach((value) => {
+        switch (value) {
+            case 'image': {
+                acceptTypes.push('.jpg', '.jpeg', '.png')
+                uploadTypes.push(...imageTypes)
+                break
+            }
+            case 'pdf': {
+                acceptTypes.push('.pdf')
+                uploadTypes.push('application/pdf')
+                break
+            }
+            case 'word': {
+                acceptTypes.push('.doc', '.docx')
+                uploadTypes.push('application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')
+                break
+            }
+            case 'excel': {
+                acceptTypes.push('.xls', '.xlsx')
+                uploadTypes.push('application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
+                break
+            }
+        }
+    })
+})
 </script>
 
 <style lang="less">

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

@@ -2,7 +2,7 @@
     <div class="app-header">
         <div class="app-header__left">
             <slot name="left"></slot>
-            <el-breadcrumb separator-icon="ArrowRight" v-show="!state.isMobile" v-if="false">
+            <el-breadcrumb separator-icon="ArrowRight" v-show="!isMobile" v-if="false">
                 <template v-for="(item, index) in $route.matched" :key="index">
                     <el-breadcrumb-item :to="{ path: item.path }">
                         <!--<i :class="item.meta.icon" v-if="item.meta.icon"></i>-->
@@ -27,7 +27,7 @@
             <el-dropdown class="user-dropdown" trigger="click">
                 <span class="user-dropdown__link">
                     <img class="g-image--avatar" :title="accountName" />
-                    <span v-if="!state.isMobile">{{ accountName }}</span>
+                    <span v-if="!isMobile">{{ accountName }}</span>
                     <app-icon class="el-icon--right" icon="ArrowDown" />
                 </span>
                 <template #dropdown>
@@ -46,8 +46,7 @@
 <script lang="ts" setup>
 import { ref, onMounted, defineAsyncComponent } from 'vue'
 import { useRouter } from 'vue-router'
-import client from '@/utils/client'
-import { userStore } from '@/stores'
+import { userStore, themeStore } from '@/stores'
 import { useAuth } from '@/business/auth'
 import { useComponent } from '@/hooks/component'
 import { useNotice } from '@/business/notice'
@@ -61,9 +60,9 @@ const componentMap = new Map<string, unknown>([
 
 const { componentId, openComponent, closeComponent } = useComponent()
 const { dataList, unreadList, getNoticeList } = useNotice()
-const { state } = client
-const { logout } = useAuth()
+const { isMobile } = themeStore.$mapState()
 const { accountName } = userStore.$mapGetters()
+const { logout } = useAuth()
 const router = useRouter()
 const fullScreen = ref(false)
 

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

@@ -30,14 +30,14 @@
 <script lang="ts" setup>
 import { shallowRef, watch } from 'vue'
 import { useMenu } from '@/hooks/menu'
-import client from '@/utils/client'
+import { themeStore } from '@/stores'
 import AppTabs from '@/components/base/tabs/index.vue'
 import AppHeader from '../header/index.vue'
 import AppFooter from '../footer/index.vue'
 import AppSidebar from '../sidebar/index.vue'
 
 const { route, router, getChildrenMenus } = useMenu()
-const isCollapse = shallowRef(client.state.isMobile)
+const isCollapse = shallowRef(themeStore.state.isMobile)
 const secondMenus = shallowRef<Ermcp.UserRoutes[]>([]) // 二级菜单
 const dataIndex = shallowRef(-1) // 选中的标签
 

+ 8 - 8
src/packages/pc/components/layouts/sidebar/index.vue

@@ -1,22 +1,22 @@
 <template>
   <el-scrollbar :class="['app-sidebar', collapse && 'is-hide']" view-class="app-sidebar__view">
-    <div :class="['app-sidebar__header', state.isMobile ? 'is-show' : collapse && 'is-hide']" v-if="false">
+    <div :class="['app-sidebar__header', isMobile ? 'is-show' : collapse && 'is-hide']" v-if="false">
       <span class="logo">{{ $t('app.name') }}</span>
     </div>
     <div class="app-sidebar__menu">
-      <app-sidemenu :collapse="state.isMobile ? false : collapse" @click="routerTo" />
+      <app-sidemenu :collapse="isMobile ? false : collapse" @click="routerTo" />
     </div>
-    <div :class="['app-sidebar__copyright', state.isMobile ? 'is-show' : collapse && 'is-hide']" v-if="false">
+    <div :class="['app-sidebar__copyright', isMobile ? 'is-show' : collapse && 'is-hide']" v-if="false">
       <span>&copy;{{ year }} Muchinfo</span>
     </div>
-    <div :class="['app-sidebar__mask', collapse && 'is-hide']" @click="hideSidebar()" v-if="state.isMobile"></div>
+    <div :class="['app-sidebar__mask', collapse && 'is-hide']" @click="hideSidebar()" v-if="isMobile"></div>
   </el-scrollbar>
 </template>
 
 <script lang="ts" setup>
 import { watch } from 'vue'
 import { useMenu } from '@/hooks/menu'
-import client from '@/utils/client'
+import { themeStore } from '@/stores'
 import AppSidemenu from '../sidemenu/index.vue'
 
 const emit = defineEmits(['update:collapse'])
@@ -27,11 +27,11 @@ defineProps({
 })
 
 const { router, getChildrenMenus } = useMenu()
-const { state } = client
+const { isMobile } = themeStore.$mapState()
 const year = new Date().getFullYear()
 
 const hideSidebar = () => {
-  if (state.isMobile) {
+  if (isMobile.value) {
     emit('update:collapse', true)
   }
 }
@@ -49,7 +49,7 @@ const routerTo = (active: string) => {
 }
 
 // 监听设备变化
-watch(() => state.isMobile, () => hideSidebar())
+watch(isMobile, () => hideSidebar())
 </script>
 
 <style lang="less">

+ 7 - 9
src/packages/pc/views/customs/bonded/components/download/index.vue → src/packages/pc/components/modules/download/index.vue

@@ -1,4 +1,4 @@
-<!-- 交易服务-出境检测-文件下载 -->
+<!-- 文件下载 -->
 <template>
     <app-drawer title="下载" :width="800" v-model:show="show">
         <app-table :data="dataList" :loading="loading" :columns="columns" :show-toolbar="false" border>
@@ -23,19 +23,17 @@ import AppTable from '@pc/components/base/table/index.vue'
 
 const props = defineProps({
     selectedRow: {
-        type: Object as PropType<Ermcp.GZCJJCOrderRsp | Ermcp.GZBSFWOrderRsp>,
+        type: Object as PropType<{ orderidstr: string; orderid: string }>,
         required: true
     },
 })
 
-const orderId = (() => {
-    if ('orderidstr' in props.selectedRow) {
-        return props.selectedRow.orderidstr
-    }
-    return props.selectedRow.orderid
-})()
+const getOrderId = () => {
+    const { orderidstr, orderid } = props.selectedRow
+    return orderidstr ?? orderid
+}
 
-const { loading, dataList, columns, getGZCJBSOrderFileList } = useGZCJBSOrderFileList(orderId)
+const { loading, dataList, columns, getGZCJBSOrderFileList } = useGZCJBSOrderFileList(getOrderId())
 const show = shallowRef(true)
 
 const downloadFile = (file?: string) => {

+ 4 - 3
src/packages/pc/main.ts

@@ -4,7 +4,6 @@ import router from './router'
 import directives from '@/directives' // 自定义指令集
 import '@/services/subscribe' // 全局订阅通知
 import '@/mock' // 模拟数据
-import client from '@/utils/client' // 适配客户端
 import { i18n } from '@/stores' // 国际化语言
 import layouts from "./components/layouts" // 布局组件
 import ElementPlus from 'element-plus'
@@ -12,6 +11,7 @@ import * as ElementIcons from '@element-plus/icons-vue'
 import 'element-plus/dist/index.css'
 import './assets/themes/style.less' // 主题样式
 import { timerInterceptor } from '@/utils/timer'
+import { themeStore } from '@/stores'
 
 const app = createApp(App)
 app.use(router)
@@ -23,9 +23,10 @@ app.mount('#app')
 
 // 等待 html 加载完成
 document.addEventListener('DOMContentLoaded', () => {
-    client.screenAdapter(false);
+    // 适配客户端
+    themeStore.actions.screenAdapter()
     // 监听窗口大小变化
-    window.addEventListener('resize', timerInterceptor.setDebounce(() => client.screenAdapter(false)));
+    window.addEventListener('resize', timerInterceptor.setDebounce(() => themeStore.actions.screenAdapter()));
 }, false)
 
 // 注册全局图标

+ 148 - 0
src/packages/pc/views/bonded/components/detail-edit/index.vue

@@ -0,0 +1,148 @@
+<!-- 保税仓业务-商品编辑 -->
+<template>
+    <app-drawer :title="titel" :width="860" v-model:show="show">
+        <el-form ref="formRef" class="el-form--horizontal" label-width="150px" :model="formItem" :rules="formRules">
+            <el-form-item label="商品名称" prop="goodsid">
+                <el-select v-model="formItem.goodsid" @change="onChange">
+                    <el-option :label="item.goodsname" :value="item.goodsid" v-for="(item, index) in goodsList"
+                        :key="index" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="规格" prop="goodsspec">
+                <el-input placeholder="请输入" v-model="formItem.goodsspec" />
+            </el-form-item>
+            <el-form-item label="成品对应原料明细" prop="rawdetail">
+                <el-input placeholder="请输入" v-model="formItem.rawdetail" />
+            </el-form-item>
+            <el-form-item label="件数" prop="backagenum">
+                <el-input-number placeholder="请输入" v-model="formItem.backagenum" />
+            </el-form-item>
+            <el-form-item label="净重(克拉)" prop="netweightct">
+                <el-input-number placeholder="请输入" v-model="formItem.netweightct" />
+            </el-form-item>
+            <el-form-item label="净重(克)" prop="netweightgm">
+                <el-input-number placeholder="请输入" v-model="formItem.netweightgm" />
+            </el-form-item>
+            <el-form-item label="连袋重(克)" prop="bagweightgm">
+                <el-input-number placeholder="请输入" v-model="formItem.bagweightgm" />
+            </el-form-item>
+            <el-form-item label="单价(克)" prop="prepricegm">
+                <el-input-number placeholder="请输入" v-model="formItem.prepricegm" />
+            </el-form-item>
+            <el-form-item label="总价" prop="totalprice">
+                <el-input-number placeholder="请输入" v-model="formItem.totalprice" />
+            </el-form-item>
+            <el-form-item label="币种" prop="currencydes">
+                <el-input placeholder="请输入" v-model="formItem.currencydes" />
+            </el-form-item>
+            <el-form-item label="报关总值" prop="customsvalue">
+                <el-input-number placeholder="请输入" v-model="formItem.customsvalue" />
+            </el-form-item>
+            <el-form-item label="原产国" prop="origincountry">
+                <el-input placeholder="请输入" v-model="formItem.origincountry" />
+            </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 } from 'vue'
+import type { FormInstance, FormRules } from 'element-plus'
+import { queryGZBSCGoods } from '@/services/api/bonded'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+
+const props = defineProps({
+    titel: {
+        type: String,
+        default: '商品编辑'
+    },
+    selectedRow: {
+        type: Object as PropType<Ermcp.BScinOutOrderDetailRsp>
+    }
+})
+
+const emit = defineEmits(['update'])
+const show = ref(true)
+const formRef = ref<FormInstance>()
+const goodsList = ref<Ermcp.GZBSCGoodsRsp[]>([])
+const formItem = ref<Partial<Ermcp.BScinOutOrderDetailRsp>>({ detailid: '0', ...props.selectedRow })
+
+const formRules: FormRules = {
+    goodsid: [{
+        required: true,
+        message: '请选择商品'
+    }],
+    goodsspec: [{
+        required: true,
+        message: '请输入规格'
+    }],
+    rawdetail: [{
+        required: true,
+        message: '请输入成品对应原料明细'
+    }],
+    netweightct: [{
+        required: true,
+        message: '请输入净重'
+    }],
+    netweightgm: [{
+        required: true,
+        message: '请输入净重'
+    }],
+    bagweightgm: [{
+        required: true,
+        message: '请输入连袋重'
+    }],
+    prepricegm: [{
+        required: true,
+        message: '请输入单价'
+    }],
+    totalprice: [{
+        required: true,
+        message: '请输入总价'
+    }],
+    currencydes: [{
+        required: true,
+        message: '请输入币种'
+    }],
+    customsvalue: [{
+        required: true,
+        message: '请输入报关总值'
+    }],
+    origincountry: [{
+        required: true,
+        message: '请输入原产国'
+    }],
+}
+
+const onChange = (goodsid: number) => {
+    const item = goodsList.value.find((e) => e.goodsid === goodsid)
+    formItem.value.goodsid = item?.goodsid
+    formItem.value.goodsname = item?.goodsname
+}
+
+const onCancel = () => {
+    show.value = false
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            emit('update', formItem.value)
+            onCancel()
+        }
+    })
+}
+
+queryGZBSCGoods({
+    success: (res) => {
+        goodsList.value = res.data
+    }
+})
+</script>

+ 111 - 0
src/packages/pc/views/bonded/components/upload/index.vue

@@ -0,0 +1,111 @@
+<!-- 保税仓业务-上传 -->
+<template>
+    <app-drawer title="上传发票/装箱单" v-model:show="show" :loading="loading" :refresh="refresh">
+        <el-form ref="formRef" label-width="100px" :model="formData" :rules="formRules">
+            <el-form-item label="单据编号">
+                {{ selectedRow.orderid }}
+            </el-form-item>
+            <el-form-item label="盖章发票" prop="invoiceFile">
+                <app-upload :file-types="['pdf', 'word', 'excel']" @change="onUploadInvoice">
+                    <template #tip>
+                        <span>支持Word、excel、pdf格式</span>
+                    </template>
+                </app-upload>
+            </el-form-item>
+            <el-form-item label="盖章装箱单" prop="orderFile">
+                <app-upload :file-types="['pdf', 'word', 'excel']" @change="onUploadOrder">
+                    <template #tip>
+                        <span>支持Word、excel、pdf格式</span>
+                    </template>
+                </app-upload>
+            </el-form-item>
+        </el-form>
+        <template #footer>
+            <el-button @click="onCancel(false)" plain>取消</el-button>
+            <el-button type="primary" @click="onSubmit">确认</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType } from 'vue'
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { useBscUploadFile } from '@/business/bonded'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+import AppUpload from '@pc/components/base/upload/index.vue'
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Ermcp.GzbscinOutOrderRsp>,
+        required: true
+    },
+})
+
+const { loading, formData, formSubmit } = useBscUploadFile(props.selectedRow.orderid)
+const show = shallowRef(true)
+const refresh = shallowRef(false)
+const formRef = shallowRef<FormInstance>()
+const invoiceFile = shallowRef<Proto.FileDetail>() // 盖章发票
+const orderFile = shallowRef<Proto.FileDetail>() // 盖章装箱单
+
+const formRules: FormRules = {
+    invoiceFile: [{
+        required: true,
+        validator: (rule, value, callback) => {
+            if (invoiceFile.value) {
+                callback()
+            } else {
+                callback(new Error('请上传盖章发票'))
+            }
+        }
+    }],
+    orderFile: [{
+        required: true,
+        validator: (rule, value, callback) => {
+            if (orderFile.value) {
+                callback()
+            } else {
+                callback(new Error('请上传盖章装箱单'))
+            }
+        }
+    }],
+}
+
+// 上传盖章发票
+const onUploadInvoice = (file: { originalName: string, filePath: string }) => {
+    invoiceFile.value = {
+        FileName: file.originalName,
+        FilePath: file.filePath,
+    }
+}
+
+// 上传盖章装箱单
+const onUploadOrder = (file: { originalName: string, filePath: string }) => {
+    orderFile.value = {
+        FileName: file.originalName,
+        FilePath: file.filePath,
+    }
+}
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            if (invoiceFile.value && orderFile.value) {
+                formData.FileDetails = [invoiceFile.value, orderFile.value]
+            }
+            formSubmit().then(() => {
+                ElMessage.success('提交成功')
+                onCancel(true)
+            }).catch((err) => {
+                ElMessage.error('提交失败:' + err)
+            })
+        }
+    })
+}
+</script>

+ 76 - 0
src/packages/pc/views/bonded/expense/components/details/index.vue

@@ -0,0 +1,76 @@
+<!-- 保税仓业务-计费管理-详情 -->
+<template>
+    <app-drawer title="详情" :width="960" v-model:show="show">
+        <table cellspacing="10" cellpadding="0">
+            <tr>
+                <th>分拣室服务费:</th>
+                <td>{{ selectedRow.servicefee.toFixed(2) }}</td>
+                <th>分拣室电费:</th>
+                <td>{{ selectedRow.powerfee.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>仓储费:</th>
+                <td>{{ selectedRow.storagefee.toFixed(2) }}</td>
+                <th>保险费:</th>
+                <td>{{ selectedRow.premium.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>进仓报关费:</th>
+                <td>{{ selectedRow.infee.toFixed(2) }}</td>
+                <th>出仓报关费:</th>
+                <td>{{ selectedRow.outfee.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>合计费用:</th>
+                <td>{{ selectedRow.totalfee.toFixed(2) }}</td>
+                <th>支付状态:</th>
+                <td>{{ selectedRow.paystatus }}</td>
+            </tr>
+        </table>
+        <app-table :data="powerfeeList" :columns="powerfeeColumns" :show-toolbar="false" border>
+        </app-table>
+        <app-table :data="importList" :columns="bondedColumns" :show-toolbar="false" border>
+        </app-table>
+        <app-table :data="exportList" :columns="bondedColumns" :show-toolbar="false" border>
+        </app-table>
+        <app-table :data="transferList" :columns="transferColumns" :show-toolbar="false" border>
+        </app-table>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType } from 'vue'
+import { useGzbscusermonthpayDetails } from '@/business/bonded'
+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.GzbscusermonthpayRsp>,
+        required: true
+    },
+})
+
+const { powerfeeList, importList, exportList, transferList } = useGzbscusermonthpayDetails(props.selectedRow.trademonth)
+const show = shallowRef(true)
+
+const powerfeeColumns: Ermcp.TableColumn[] = [
+    { prop: 'instrumentno', label: '仪表编号' },
+    { prop: 'username', label: '使用单位' },
+    { prop: 'startpower', label: '月初读数' },
+    { prop: 'endpower', label: '月末读数' },
+    { prop: 'powernum', label: '本月用量' },
+    { prop: 'powerunit', label: '单位' },
+    { prop: 'powerfee', label: '电费(元)' },
+]
+
+const bondedColumns: Ermcp.TableColumn[] = [
+    { prop: 'jckdate', label: '进口日期' },
+    { prop: 'customsno', label: '报关单号' },
+]
+
+const transferColumns: Ermcp.TableColumn[] = [
+    { prop: 'jckdate', label: '转厂日期' },
+    { prop: 'checklistno', label: '核注清单号' },
+]
+</script>

+ 9 - 0
src/packages/pc/views/bonded/expense/components/payment/index.less

@@ -0,0 +1,9 @@
+.bonded-expense-payment {
+    &__table {
+        th {
+            color: #666;
+            font-weight: normal;
+            text-align: right;
+        }
+    }
+}

+ 89 - 0
src/packages/pc/views/bonded/expense/components/payment/index.vue

@@ -0,0 +1,89 @@
+<!-- 保税仓业务-计费管理-线上支付 -->
+<template>
+    <app-drawer class="bonded-expense-payment" :title="`款项明细【${selectedRow.trademonth}】`" v-model:show="show"
+        :loading="loading" :refresh="refresh">
+        <table class="bonded-expense-payment__table" cellspacing="10" cellpadding="0">
+            <tr>
+                <th>分拣室服务费:</th>
+                <td>{{ selectedRow.servicefee.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>分拣室电费:</th>
+                <td>{{ selectedRow.powerfee.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>仓储费:</th>
+                <td>{{ selectedRow.storagefee.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>保险费:</th>
+                <td>{{ selectedRow.premium.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>进仓报关费:</th>
+                <td>{{ selectedRow.infee.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>出仓报关费:</th>
+                <td>{{ selectedRow.outfee.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>合计费用:</th>
+                <td>{{ selectedRow.totalfee.toFixed(2) }}</td>
+            </tr>
+            <tr>
+                <th>账户可用余额:</th>
+                <td :style="{ color: balanceIsInsufficient ? 'red' : '#409340' }">{{ avaiableMoney.toFixed(2) }}</td>
+            </tr>
+            <tr v-if="balanceIsInsufficient">
+                <th></th>
+                <td style="color:red">*余额不足,请与平台联系。</td>
+            </tr>
+        </table>
+        <template #footer>
+            <el-button @click="onCancel(false)" plain>取消</el-button>
+            <el-button type="primary" :disabled="balanceIsInsufficient" @click="onSubmit">确认</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType, computed } from 'vue'
+import { ElMessage } from 'element-plus'
+import { accountStore } from '@/stores'
+import { useBscConfirmPay } from '@/business/bonded'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Ermcp.GzbscusermonthpayRsp>,
+        required: true
+    },
+})
+
+const { loading, formSubmit } = useBscConfirmPay(props.selectedRow.trademonth)
+const { avaiableMoney } = accountStore.$mapGetters()
+const show = shallowRef(true)
+const refresh = shallowRef(false)
+
+// 是否余额不足
+const balanceIsInsufficient = computed(() => avaiableMoney.value < props.selectedRow.totalfee)
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onSubmit = () => {
+    formSubmit().then(() => {
+        ElMessage.success('提交成功')
+        onCancel(true)
+    }).catch((err) => {
+        ElMessage.error('提交失败:' + err)
+    })
+}
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 81 - 0
src/packages/pc/views/bonded/expense/index.vue

@@ -0,0 +1,81 @@
+<!-- 保税仓业务-计费管理 -->
+<template>
+    <app-view>
+        <template #header>
+            <app-filter :options="filterOptons" />
+        </template>
+        <!-- 表格数据 -->
+        <app-table :data="dataList" v-model:columns="tableColumns" :loading="loading">
+            <!-- 支付状态 -->
+            <template #paystatus="{ value }">
+                {{ getGZBSCPayStatusName(value) }}
+            </template>
+            <!-- 支付方式 -->
+            <template #paymode="{ value }">
+                {{ getGZBSCPayModeName(value) }}
+            </template>
+            <!-- 操作 -->
+            <template #operate="{ row }">
+                <app-auth-operation type="dropdown" :menus="handleOperateButtons(row)" :options="{ selectedRow: row }"
+                    @closed="onRefresh" />
+            </template>
+            <template #footer>
+                <app-pagination :total="total" v-model:page-size="pageSize" v-model:page-index="pageIndex"
+                    @change="onRefresh" />
+            </template>
+        </app-table>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import { useDataFilter } from '@/hooks/datatable'
+import { useComposeTable } from '@pc/components/base/table'
+import { getGZBSCPayStatusList, getGZBSCPayStatusName, getGZBSCPayModeName } from '@/constants/bonded'
+import { useGzbscusermonthpay } from '@/business/bonded'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppFilter from '@pc/components/base/table-filter/index.vue'
+import AppPagination from '@pc/components/base/pagination/index.vue'
+import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
+
+const { loading, dataList, total, pageIndex, pageSize, getGzbscusermonthpay } = useGzbscusermonthpay()
+const { tableColumns } = useComposeTable<Ermcp.GzbscusermonthpayRsp>({ rowKey: 'paytime', columnKey: 'bonded_expense' })
+const { filterOptons, getQueryParams } = useDataFilter<Ermcp.GzbscusermonthpayReq>()
+
+// 状态”2:待支付 “多操作:“支付”
+const handleOperateButtons = (row: Ermcp.GzbscusermonthpayRsp) => {
+    const buttons = ['bonded_expense_details']
+    if (row.paystatus === 2) {
+        buttons.push('bonded_expense_payment')
+    }
+    return buttons
+}
+
+const onSearch = (clear = false) => {
+    getQueryParams((qs) => {
+        pageIndex.value = 1
+        getGzbscusermonthpay(qs)
+    }, clear)
+}
+
+const onRefresh = () => {
+    getQueryParams((qs) => {
+        getGzbscusermonthpay(qs).catch((err) => ElMessage.error(err))
+    })
+}
+
+filterOptons.selectList = [
+    {
+        label: '支付状态',
+        key: 'paystatus',
+        options: getGZBSCPayStatusList(),
+    },
+]
+
+filterOptons.buttonList = [
+    { lable: '重置', onClick: () => onSearch(true) },
+    { lable: '查询', className: 'el-button--primary', onClick: () => onSearch() }
+]
+
+onRefresh()
+</script>

+ 13 - 0
src/packages/pc/views/bonded/inbound/components/apply/index.less

@@ -0,0 +1,13 @@
+.bonded-inbound-apply {
+    &__title {
+        color: #666;
+    }
+
+    &__table+&__table {
+        margin-top: 20px;
+    }
+
+    .el-form {
+        margin-top: 10px;
+    }
+}

+ 183 - 0
src/packages/pc/views/bonded/inbound/components/apply/index.vue

@@ -0,0 +1,183 @@
+<!-- 保税仓业务-进仓管理-提交申请 -->
+<template>
+    <app-drawer class="bonded-inbound-apply" title="提交申请" v-model:show="show" :width="960" :loading="loading"
+        :refresh="refresh">
+        <h3 class="bonded-inbound-apply__title">发货信息</h3>
+        <el-form ref="formRef" class="el-form--horizontal" label-width="100px" :model="formData" :rules="formRules">
+            <el-form-item label="发货方" prop="UserName">
+                <el-input placeholder="请输入" v-model="formData.UserName" />
+            </el-form-item>
+            <el-form-item label="发货地址" prop="UserAddress">
+                <el-input placeholder="请输入" v-model="formData.UserAddress" />
+            </el-form-item>
+            <el-form-item label="联系人" prop="ContactName">
+                <el-input placeholder="请输入" v-model="formData.ContactName" />
+            </el-form-item>
+            <el-form-item label="联系电话" prop="ContactNum">
+                <el-input placeholder="请输入" v-model="formData.ContactNum" />
+            </el-form-item>
+            <el-form-item label="物流公司" prop="LogisticsCompany">
+                <el-input placeholder="请输入" v-model="formData.LogisticsCompany" />
+            </el-form-item>
+            <el-form-item label="托运单号" prop="LogisticsNo">
+                <el-input placeholder="请输入" v-model="formData.LogisticsNo" />
+            </el-form-item>
+        </el-form>
+        <div class="bonded-inbound-apply__table">
+            <app-table :data="orderDetailList" :columns="columns" :max-height="400" border>
+                <template #header>
+                    <h3 class="bonded-inbound-apply__title">商品信息</h3>
+                </template>
+                <template #toolbar>
+                    <el-button-group>
+                        <el-button size="small" @click="openEdit()">新增</el-button>
+                        <el-button size="small" @click="orderDetailList = []">清空</el-button>
+                    </el-button-group>
+                </template>
+                <!-- 操作 -->
+                <template #operate="{ row, index }">
+                    <el-button-group size="small">
+                        <el-button @click="openEdit(row)">修改</el-button>
+                        <el-button @click="deleteRecord(index)">删除</el-button>
+                    </el-button-group>
+                </template>
+            </app-table>
+        </div>
+        <template #footer>
+            <el-button @click="onCancel(false)" plain>取消</el-button>
+            <el-button type="primary" @click="onSubmit">提交</el-button>
+        </template>
+        <component :is="DetailEdit" v-bind="{ selectedRow }" @update="onUpdate" @closed="showEdit = false"
+            v-if="showEdit" />
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, ref, defineAsyncComponent } from 'vue'
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { validateRules } from '@/constants/regex'
+import { GZBSCOrderType } from '@/constants/bonded'
+import { useBscInAndOutWareHouseApply } from '@/business/bonded'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+import AppTable from '@pc/components/base/table/index.vue'
+
+const DetailEdit = defineAsyncComponent(() => import('../../../components/detail-edit/index.vue'))
+
+const { loading, formData, formSubmit } = useBscInAndOutWareHouseApply(GZBSCOrderType.In)
+const show = shallowRef(true)
+const refresh = shallowRef(false)
+const formRef = shallowRef<FormInstance>()
+const showEdit = shallowRef(false)
+const orderDetailList = ref<Ermcp.BScinOutOrderDetailRsp[]>([]) // 明细列表
+const selectedRow = shallowRef<Ermcp.BScinOutOrderDetailRsp>() // 当前选择的商品明细
+
+const columns: Ermcp.TableColumn[] = [
+    { prop: 'goodsname', label: '商品名称' },
+    { prop: 'goodsspec', label: '规格' },
+    { prop: 'rawdetail', label: '成品对应原料明细', width: 160 },
+    { prop: 'backagenum', label: '件数' },
+    { prop: 'netweightct', label: '净重(克拉)' },
+    { prop: 'netweightgm', label: '净重(克)' },
+    { prop: 'bagweightgm', label: '连袋重(克)' },
+    { prop: 'prepricegm', label: '单价(克)' },
+    { prop: 'totalprice', label: '总价' },
+    { prop: 'currencydes', label: '币种' },
+    { prop: 'customsvalue', label: '报关总值' },
+    { prop: 'origincountry', label: '原产国' },
+    { prop: 'remark', label: '备注' },
+    { prop: 'operate', label: '操作', width: 160, fixed: 'right' }
+]
+
+const formRules: FormRules = {
+    UserName: [{
+        required: true,
+        message: '请输入发货方'
+    }],
+    UserAddress: [{
+        required: true,
+        message: '请输入发货地址'
+    }],
+    ContactName: [{
+        required: true,
+        message: '请输入联系人'
+    }],
+    ContactNum: [{
+        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('请输入联系电话'))
+            }
+        }
+    }],
+}
+
+// 打开商品编辑
+const openEdit = (row?: Ermcp.BScinOutOrderDetailRsp) => {
+    selectedRow.value = row
+    showEdit.value = true
+}
+
+// 删除商品记录
+const deleteRecord = (index: number) => {
+    orderDetailList.value.splice(index, 1)
+    orderDetailList.value.forEach((e, i) => e.detailid = (i + 1).toString()) // 重置序列
+}
+
+// 更新商品数据
+const onUpdate = (item: Ermcp.BScinOutOrderDetailRsp) => {
+    const list = orderDetailList.value
+    const index = list.findIndex((e) => e.detailid === item.detailid)
+    if (index > -1) {
+        list[index] = item
+    } else {
+        const n = list.length
+        item.detailid = (n + 1).toString()
+        list[n] = item
+    }
+}
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            formData.BSCGoodsListDetails = orderDetailList.value.map((e) => ({
+                GoodsID: e.goodsid, // 商品ID,必填
+                GoodsSpec: e.goodsspec, // 规格,必填
+                RawDetail: e.rawdetail, // 原料明细,必填
+                BackageNum: e.backagenum, // 件数,必填
+                NetWeightCT: e.netweightct, // 净重(克拉),必填
+                NetWeightGM: e.netweightgm, // 净重(克),必填
+                BagWeightGM: e.bagweightgm, // 连袋重(克),必填
+                PrePriceGM: e.prepricegm, // 单价(克),必填
+                TotalPrice: e.totalprice, // 总价,必填
+                CurrencyDes: e.currencydes, // 币种,必填
+                CustomsValue: e.customsvalue, // 报关总值,必填
+                OriginCountry: e.origincountry, // 原产国,必填
+                Remark: e.remark, // 备注,选填
+            }))
+            formSubmit().then(() => {
+                ElMessage.success('提交成功')
+                onCancel(true)
+            }).catch((err) => {
+                ElMessage.error('提交失败:' + err)
+            })
+        }
+    })
+}
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 69 - 0
src/packages/pc/views/bonded/inbound/components/details/index.vue

@@ -0,0 +1,69 @@
+<!-- 保税仓业务-进仓管理-详情 -->
+<template>
+    <app-drawer title="提交申请" :width="960" v-model:show="show">
+        <el-descriptions title="发货信息" :column="2" border>
+            <el-descriptions-item label="单据编号">{{ selectedRow.ordernum }}</el-descriptions-item>
+            <el-descriptions-item label="申请日期">{{ selectedRow.applicanttime }}</el-descriptions-item>
+            <el-descriptions-item label="发货方">{{ selectedRow.username }}</el-descriptions-item>
+            <el-descriptions-item label="发货地址">{{ selectedRow.useraddress }}</el-descriptions-item>
+            <el-descriptions-item label="联系人">{{ selectedRow.contactname }}</el-descriptions-item>
+            <el-descriptions-item label="联系电话">{{ selectedRow.contactnum }}</el-descriptions-item>
+            <el-descriptions-item label="物流公司">{{ selectedRow.logisticscompany }}</el-descriptions-item>
+            <el-descriptions-item label="托运单号">{{ selectedRow.logisticsno }}</el-descriptions-item>
+        </el-descriptions>
+        <el-descriptions title="签收信息" :column="2" border>
+            <el-descriptions-item label="收货人">{{ selectedRow.signeename }}</el-descriptions-item>
+            <el-descriptions-item label="收货日期">{{ selectedRow.signeedate }}</el-descriptions-item>
+            <el-descriptions-item label="进仓确认人">{{ selectedRow.confirmername }}</el-descriptions-item>
+            <el-descriptions-item label="确认日期">{{ selectedRow.confirmdate }}</el-descriptions-item>
+            <el-descriptions-item label="进仓封条号">{{ selectedRow.sealno }}</el-descriptions-item>
+            <el-descriptions-item label="报关单号">{{ selectedRow.customsno }}</el-descriptions-item>
+            <el-descriptions-item label="进口日期">{{ selectedRow.jckdate }}</el-descriptions-item>
+            <el-descriptions-item label="核注清单号">{{ selectedRow.checklistno }}</el-descriptions-item>
+        </el-descriptions>
+        <h3>商品信息</h3><el-button @click="showDownload = true">文件下载</el-button>
+        <app-table :data="dataList" :columns="columns" :loading="loading" :show-toolbar="false" border>
+        </app-table>
+        <component :is="Download" v-bind="{ selectedRow }" @closed="showDownload = false" v-if="showDownload" />
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, PropType, defineAsyncComponent } from 'vue'
+import { useBScinOutOrderDetail } from '@/business/bonded'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+import AppTable from '@pc/components/base/table/index.vue'
+
+const Download = defineAsyncComponent(() => import('@pc/components/modules/download/index.vue'))
+
+const props = defineProps({
+    selectedRow: {
+        type: Object as PropType<Ermcp.GzbscinOutOrderRsp>,
+        required: true
+    },
+})
+
+const { loading, dataList, getBScinOutOrderDetail } = useBScinOutOrderDetail()
+const show = shallowRef(true)
+const showDownload = shallowRef(false)
+
+const columns: Ermcp.TableColumn[] = [
+    { prop: 'goodsname', label: '商品名称' },
+    { prop: 'goodsspec', label: '规格' },
+    { prop: 'rawdetail', label: '成品对应原料明细', width: 160 },
+    { prop: 'backagenum', label: '件数' },
+    { prop: 'netweightct', label: '净重(克拉)' },
+    { prop: 'netweightgm', label: '净重(克)' },
+    { prop: 'bagweightgm', label: '连袋重(克)' },
+    { prop: 'prepricegm', label: '单价(克)' },
+    { prop: 'totalprice', label: '总价' },
+    { prop: 'currencydes', label: '币种' },
+    { prop: 'customsvalue', label: '报关总值' },
+    { prop: 'origincountry', label: '原产国' },
+    { prop: 'remark', label: '备注' },
+]
+
+getBScinOutOrderDetail({
+    orderid: props.selectedRow.orderid
+})
+</script>

+ 86 - 0
src/packages/pc/views/bonded/inbound/index.vue

@@ -0,0 +1,86 @@
+<!-- 保税仓业务-进仓管理 -->
+<template>
+    <app-view>
+        <template #header>
+            <app-filter :options="filterOptons" />
+        </template>
+        <!-- 表格数据 -->
+        <app-table :data="dataList" v-model:columns="tableColumns" :loading="loading">
+            <template #header>
+                <app-auth-operation :menus="['bonded_inbound_apply']" @closed="onRefresh" />
+            </template>
+            <!-- 单据状态 -->
+            <template #orderstatus="{ value }">
+                {{ getGZBSCOrderStatusName(value) }}
+            </template>
+            <!-- 操作 -->
+            <template #operate="{ row }">
+                <app-auth-operation type="dropdown" :menus="handleOperateButtons(row)" :options="{ selectedRow: row }"
+                    @closed="onRefresh" />
+            </template>
+            <template #footer>
+                <app-pagination :total="total" v-model:page-size="pageSize" v-model:page-index="pageIndex"
+                    @change="onRefresh" />
+            </template>
+        </app-table>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import { useDataFilter } from '@/hooks/datatable'
+import { useComposeTable } from '@pc/components/base/table'
+import { GZBSCOrderType, getGZBSCOrderStatusName } from '@/constants/bonded'
+import { useGzbscinOutOrder } from '@/business/bonded'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppFilter from '@pc/components/base/table-filter/index.vue'
+import AppPagination from '@pc/components/base/pagination/index.vue'
+import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
+
+const { loading, dataList, total, pageIndex, pageSize, getGzbscinOutOrder } = useGzbscinOutOrder()
+const { tableColumns } = useComposeTable<Ermcp.GzbscinOutOrderRsp>({ rowKey: 'orderid', columnKey: 'bonded_inbound' })
+const { filterOptons, getQueryParams } = useDataFilter<Ermcp.GzbscinOutOrderReq>()
+
+// 状态”2.待上传 “多操作:“上传”
+const handleOperateButtons = (row: Ermcp.GzbscinOutOrderRsp) => {
+    const buttons = ['bonded_inbound_details', 'bonded_inbound_download']
+    if (row.orderstatus === 2) {
+        buttons.push('bonded_inbound_upload')
+    }
+    return buttons
+}
+
+const onSearch = (clear = false) => {
+    getQueryParams((qs) => {
+        qs.ordertype = GZBSCOrderType.In
+        pageIndex.value = 1
+        getGzbscinOutOrder(qs)
+    }, clear)
+}
+
+const onRefresh = () => {
+    getQueryParams((qs) => {
+        qs.ordertype = GZBSCOrderType.In
+        getGzbscinOutOrder(qs).catch((err) => ElMessage.error(err))
+    })
+}
+
+filterOptons.selectList = [
+    {
+        label: '单据状态',
+        key: 'listtype',
+        options: [
+            { label: '待上传', value: 1 },
+            { label: '进行中', value: 2 },
+            { label: '已结束', value: 3 }
+        ],
+    },
+]
+
+filterOptons.buttonList = [
+    { lable: '重置', onClick: () => onSearch(true) },
+    { lable: '查询', className: 'el-button--primary', onClick: () => onSearch() }
+]
+
+onRefresh()
+</script>

+ 13 - 0
src/packages/pc/views/bonded/outbound/components/apply/index.less

@@ -0,0 +1,13 @@
+.bonded-outbound-apply {
+    &__title {
+        color: #666;
+    }
+
+    &__table+&__table {
+        margin-top: 20px;
+    }
+
+    .el-form {
+        margin-top: 10px;
+    }
+}

+ 258 - 0
src/packages/pc/views/bonded/outbound/components/apply/index.vue

@@ -0,0 +1,258 @@
+<!-- 保税仓业务-出仓管理-提交申请 -->
+<template>
+    <app-drawer class="bonded-outbound-apply" title="提交申请" v-model:show="show" :width="960" :loading="loading"
+        :refresh="refresh">
+        <h3 class="bonded-outbound-apply__title">收货信息</h3>
+        <el-form ref="formRef" class="el-form--horizontal" label-width="100px" :model="formData" :rules="formRules">
+            <el-form-item label="收货方" prop="UserName">
+                <el-input placeholder="请输入" v-model="formData.UserName" />
+            </el-form-item>
+            <el-form-item label="收货地址" prop="UserAddress">
+                <el-input placeholder="请输入" v-model="formData.UserAddress" />
+            </el-form-item>
+            <el-form-item label="联系人" prop="ContactName">
+                <el-input placeholder="请输入" v-model="formData.ContactName" />
+            </el-form-item>
+            <el-form-item label="联系电话" prop="ContactNum">
+                <el-input placeholder="请输入" v-model="formData.ContactNum" />
+            </el-form-item>
+            <el-form-item label="物流公司" prop="LogisticsCompany">
+                <el-input placeholder="请输入" v-model="formData.LogisticsCompany" />
+            </el-form-item>
+            <el-form-item label="托运单号" prop="LogisticsNo">
+                <el-input placeholder="请输入" v-model="formData.LogisticsNo" />
+            </el-form-item>
+            <el-form-item label="出仓类型" prop="OutType">
+                <el-select v-model="formData.OutType">
+                    <el-option :label="item.label" :value="item.value" v-for="(item, index) in getGZBSCOutTypeList()"
+                        :key="index" />
+                </el-select>
+            </el-form-item>
+        </el-form>
+        <div class="bonded-outbound-apply__table">
+            <app-table :data="orderDetailList" :columns="columns1" :max-height="400" border>
+                <template #header>
+                    <h3 class="bonded-outbound-apply__title">商品信息</h3>
+                </template>
+                <template #toolbar>
+                    <el-button-group>
+                        <el-button size="small" @click="openEdit('detailEdit')">新增</el-button>
+                        <el-button size="small" @click="orderDetailList = []">清空</el-button>
+                    </el-button-group>
+                </template>
+                <!-- 操作 -->
+                <template #operate="{ row, index }">
+                    <el-button-group size="small">
+                        <el-button @click="openEdit('detailEdit', row)">修改</el-button>
+                        <el-button @click="deleteRecord(index)">删除</el-button>
+                    </el-button-group>
+                </template>
+            </app-table>
+        </div>
+        <div class="bonded-outbound-apply__table">
+            <app-table :data="orderDetailAttList" :columns="columns2" :max-height="400" border>
+                <template #header>
+                    <h3 class="bonded-outbound-apply__title">附表信息</h3>
+                </template>
+                <template #toolbar>
+                    <el-button-group>
+                        <el-button size="small" @click="openEdit('subDetailEdit')">新增</el-button>
+                        <el-button size="small" @click="orderDetailAttList = []">清空</el-button>
+                    </el-button-group>
+                </template>
+                <!-- 操作 -->
+                <template #operate="{ row, index }">
+                    <el-button-group size="small">
+                        <el-button @click="openEdit('subDetailEdit', row)">修改</el-button>
+                        <el-button @click="deleteSubRecord(index)">删除</el-button>
+                    </el-button-group>
+                </template>
+            </app-table>
+        </div>
+        <template #footer>
+            <el-button @click="onCancel(false)" plain>取消</el-button>
+            <el-button type="primary" @click="onSubmit">提交</el-button>
+        </template>
+        <component :is="componentMap.get(componentId)"
+            v-bind="{ selectedRow, filteredValue: orderDetailAttList.map((e) => e.goodsid) }" @update="onUpdate"
+            @closed="closeComponent" v-if="componentId" />
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, ref, defineAsyncComponent } 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 { getGZBSCOutTypeList } from '@/constants/bonded'
+import { GZBSCOrderType } from '@/constants/bonded'
+import { useBscInAndOutWareHouseApply } from '@/business/bonded'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+import AppTable from '@pc/components/base/table/index.vue'
+
+const componentMap = new Map<string, unknown>([
+    ['detailEdit', defineAsyncComponent(() => import('../../../components/detail-edit/index.vue'))],
+    ['subDetailEdit', defineAsyncComponent(() => import('./subdetail-edit/index.vue'))],
+])
+
+const { componentId, openComponent, closeComponent } = useComponent()
+const { loading, formData, formSubmit } = useBscInAndOutWareHouseApply(GZBSCOrderType.Out)
+const show = shallowRef(true)
+const refresh = shallowRef(false)
+const formRef = shallowRef<FormInstance>()
+const orderDetailList = ref<Ermcp.BScinOutOrderDetailRsp[]>([]) // 明细列表
+const orderDetailAttList = ref<Ermcp.BScOutOrderDetailattRsp[]>([]) // 附表列表
+const selectedRow = shallowRef<Ermcp.BScinOutOrderDetailRsp | Ermcp.BScOutOrderDetailattRsp>() // 当前选择的商品信息
+
+const columns1: Ermcp.TableColumn[] = [
+    { prop: 'goodsname', label: '商品名称' },
+    { prop: 'goodsspec', label: '规格' },
+    { prop: 'rawdetail', label: '成品对应原料明细', width: 160 },
+    { prop: 'backagenum', label: '件数' },
+    { prop: 'netweightct', label: '净重(克拉)' },
+    { prop: 'netweightgm', label: '净重(克)' },
+    { prop: 'bagweightgm', label: '连袋重(克)' },
+    { prop: 'prepricegm', label: '单价(克)' },
+    { prop: 'totalprice', label: '总价' },
+    { prop: 'currencydes', label: '币种' },
+    { prop: 'customsvalue', label: '报关总值' },
+    { prop: 'origincountry', label: '原产国' },
+    { prop: 'remark', label: '备注' },
+    { prop: 'operate', label: '操作', width: 160, fixed: 'right' }
+]
+
+const columns2: Ermcp.TableColumn[] = [
+    { prop: 'goodsname', label: '商品名称' },
+    { prop: 'customsno', label: '进口单号' },
+    { prop: 'jckdate', label: '进口日期' },
+    { prop: 'netweightct', label: '净重(克拉)' },
+    { prop: 'curnetweightct', label: '本次扣减量(克拉)', width: 160 },
+    { prop: 'remainnetweightct', label: '结余量(克拉)' },
+    { prop: 'customsvalue', label: '报关总值' },
+    { prop: 'curcustomsvalue', label: '本次扣减货值' },
+    { prop: 'remaincustomsvalue', label: '结余货值' },
+    { prop: 'operate', label: '操作', width: 160, fixed: 'right' }
+]
+
+const formRules: FormRules = {
+    UserName: [{
+        required: true,
+        message: '请输入发货方'
+    }],
+    UserAddress: [{
+        required: true,
+        message: '请输入发货地址'
+    }],
+    ContactName: [{
+        required: true,
+        message: '请输入联系人'
+    }],
+    ContactNum: [{
+        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('请输入联系电话'))
+            }
+        }
+    }],
+    OutType: [{
+        required: true,
+        message: '请选择出仓类型'
+    }],
+}
+
+// 打开商品编辑
+const openEdit = (name: string, row?: Ermcp.BScinOutOrderDetailRsp | Ermcp.BScOutOrderDetailattRsp) => {
+    selectedRow.value = row
+    openComponent(name)
+}
+
+// 删除商品记录
+const deleteRecord = (index: number) => {
+    orderDetailList.value.splice(index, 1)
+    orderDetailList.value.forEach((e, i) => e.detailid = (i + 1).toString()) // 重置序列
+}
+
+// 删除附表记录
+const deleteSubRecord = (index: number) => {
+    orderDetailAttList.value.splice(index, 1)
+    orderDetailAttList.value.forEach((e, i) => e.detailattid = (i + 1).toString()) // 重置序列
+}
+
+// 更新商品数据
+const onUpdate = (item: Ermcp.BScinOutOrderDetailRsp | Ermcp.BScOutOrderDetailattRsp) => {
+    if ('detailid' in item) {
+        const list = orderDetailList.value
+        const index = list.findIndex((e) => e.detailid === item.detailid)
+        if (index > -1) {
+            list[index] = item
+        } else {
+            const n = list.length
+            item.detailid = (n + 1).toString()
+            list[n] = item
+        }
+    }
+    if ('detailattid' in item) {
+        const list = orderDetailAttList.value
+        const index = list.findIndex((e) => e.detailattid === item.detailattid)
+        if (index > -1) {
+            list[index] = item
+        } else {
+            const n = list.length
+            item.detailattid = (n + 1).toString()
+            list[n] = item
+        }
+    }
+}
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            formData.BSCGoodsListDetails = orderDetailList.value.map((e) => ({
+                GoodsID: e.goodsid, // 商品ID,必填
+                GoodsSpec: e.goodsspec, // 规格,必填
+                RawDetail: e.rawdetail, // 原料明细,必填
+                BackageNum: e.backagenum, // 件数,必填
+                NetWeightCT: e.netweightct, // 净重(克拉),必填
+                NetWeightGM: e.netweightgm, // 净重(克),必填
+                BagWeightGM: e.bagweightgm, // 连袋重(克),必填
+                PrePriceGM: e.prepricegm, // 单价(克),必填
+                TotalPrice: e.totalprice, // 总价,必填
+                CurrencyDes: e.currencydes, // 币种,必填
+                CustomsValue: e.customsvalue, // 报关总值,必填
+                OriginCountry: e.origincountry, // 原产国,必填
+                Remark: e.remark, // 备注,选填
+            }))
+            formData.BSCOutWareHouseSchedules = orderDetailAttList.value.map((e) => ({
+                GoodsID: e.goodsid, // 商品ID,必填
+                CustomsNo: e.customsno, // 报关单号,必填
+                JCKDate: e.jckdate, // 进口日期,必填
+                CurNetWeightCT: e.curnetweightct, // 本次扣减量(克拉),必填
+            }))
+            formSubmit().then(() => {
+                ElMessage.success('提交成功')
+                onCancel(true)
+            }).catch((err) => {
+                ElMessage.error('提交失败:' + err)
+            })
+        }
+    })
+}
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 170 - 0
src/packages/pc/views/bonded/outbound/components/apply/subdetail-edit/index.vue

@@ -0,0 +1,170 @@
+<!-- 保税仓业务-出仓管理-进仓信息 -->
+<template>
+    <app-drawer title="进仓信息" :width="860" v-model:show="show">
+        <el-form ref="formRef" class="el-form--horizontal" label-width="150px" :model="formItem" :rules="formRules">
+            <el-form-item label="本次扣减量(克拉)" prop="curnetweightct">
+                <el-input-number placeholder="请输入" v-model="formItem.curnetweightct" :max="maxNetWeightCT"
+                    :disabled="!selectedPosition" />
+            </el-form-item>
+            <el-form-item label="本次扣减净重(克)">
+                <el-input :value="calcField.netWeight" readonly />
+            </el-form-item>
+            <el-form-item label="本次扣减总价">
+                <el-input :value="calcField.totalPrice" readonly />
+            </el-form-item>
+            <el-form-item label="本次扣减货值">
+                <el-input :value="calcField.value" readonly />
+            </el-form-item>
+        </el-form>
+        <app-table ref="tableRef" :data="positionList" :columns="columns" :max-height="400" :show-toolbar="false"
+            selection-type="single" border @select="onTableSelect">
+            <!-- 可用净重(克拉) -->
+            <template #netweight="{ row }">
+                {{ row.netweightct - row.freezenetweightct }}
+            </template>
+        </app-table>
+        <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, nextTick } from 'vue'
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { queryGzbscPosition } from '@/services/api/bonded'
+import { loginStore } from '@/stores'
+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.BScOutOrderDetailattRsp>
+    },
+    // 头寸列表过滤项
+    filteredValue: {
+        type: Array as PropType<number[]>,
+        default: () => ([])
+    }
+})
+
+const emit = defineEmits(['update'])
+const { userId } = loginStore.$mapGetters()
+const show = ref(true)
+const formRef = ref<FormInstance>()
+const tableRef = ref()
+const positionList = ref<Ermcp.GzbscPositionRsp[]>([])
+const formItem = ref<Partial<Ermcp.BScOutOrderDetailattRsp>>({ detailattid: '0', ...props.selectedRow })
+const selectedPosition = ref<Ermcp.GzbscPositionRsp>() // 当前选中的头寸
+
+const columns: Ermcp.TableColumn[] = [
+    { prop: 'goodsname', label: '商品名称' },
+    { prop: 'customsno', label: '进口单号' },
+    { prop: 'jckdate', label: '进口日期' },
+    { prop: 'netweightct', label: '净重(克拉)' },
+    { prop: 'freezenetweightct', label: '冻结净重(克拉)' },
+    { prop: 'netweight', label: '可用净重(克拉)' },
+    { prop: 'netweightgm', label: '净重(克)' },
+    { prop: 'totalprice', label: '总价' },
+    { prop: 'customsvalue', label: '报关总值' },
+]
+
+const formRules: FormRules = {
+    curnetweightct: [{
+        required: true,
+        message: '请输入本次扣减量'
+    }],
+}
+
+// 可用净重(克拉)
+const maxNetWeightCT = computed(() => {
+    if (selectedPosition.value) {
+        const { netweightct = 0, freezenetweightct = 0 } = selectedPosition.value
+        return (netweightct - freezenetweightct) || 0
+    }
+    return undefined
+})
+
+// 相关字段计算值
+const calcField = computed(() => {
+    const curnetweightct = formItem.value.curnetweightct ?? 0
+    let netWeight = 0
+    let totalPrice = 0
+    let unitPrice = 0
+    let value = 0
+
+    if (curnetweightct && selectedPosition.value) {
+        const { netweightct, netweightgm, totalprice, customsvalue } = selectedPosition.value
+        const n = curnetweightct / netweightct
+        netWeight = n * netweightgm
+        totalPrice = n * totalprice
+        unitPrice = totalPrice / netWeight
+        value = n * customsvalue
+    }
+
+    return {
+        netWeight: netWeight.toFixed(2), // 净重(克)
+        totalPrice: totalPrice.toFixed(2), // 总价
+        unitPrice: unitPrice.toFixed(2), // 单价(克)
+        value: value.toFixed(2), // 货值
+    }
+})
+
+// 勾选表格行
+const onTableSelect = ([row]: Ermcp.GzbscPositionRsp[]) => {
+    formItem.value.curnetweightct = undefined
+    selectedPosition.value = row
+}
+
+const onCancel = () => {
+    show.value = false
+}
+
+const onSubmit = () => {
+    formRef.value?.validate((valid) => {
+        if (valid) {
+            const row = selectedPosition.value
+            if (row) {
+                const { detailattid, curnetweightct = 0 } = formItem.value ?? {}
+                formItem.value = {
+                    goodsid: Number(row.goodsid),
+                    goodsname: row.goodsname,
+                    customsno: row.customsno,
+                    jckdate: row.jckdate,
+                    netweightct: row.netweightct,
+                    remainnetweightct: row.netweightct - curnetweightct,
+                    customsvalue: row.customsvalue,
+                    curcustomsvalue: Number(calcField.value.value),
+                    remaincustomsvalue: row.customsvalue - curnetweightct,
+                    detailattid,
+                    curnetweightct
+                }
+                emit('update', formItem.value)
+                onCancel()
+            } else {
+                ElMessage.warning('请选择商品')
+            }
+        }
+    })
+}
+
+queryGzbscPosition({
+    data: {
+        userid: userId.value
+    },
+    success: (res) => {
+        const goodsId = formItem.value.goodsid
+        if (goodsId) {
+            positionList.value = res.data.filter((e) => e.goodsid === goodsId?.toString())
+        } else {
+            positionList.value = res.data.filter((e) => !props.filteredValue.includes(Number(e.goodsid)))
+        }
+        nextTick(() => {
+            selectedPosition.value = res.data.find((e) => e.goodsid === goodsId?.toString())
+            tableRef.value?.elTable.toggleRowSelection(selectedPosition.value)
+        })
+    }
+})
+</script>

+ 27 - 0
src/packages/pc/views/bonded/outbound/components/details/index.vue

@@ -0,0 +1,27 @@
+<!-- 保税仓业务-出仓管理-详情 -->
+<template>
+    <app-drawer title="提交申请" v-model:show="show" :refresh="refresh">
+        详情
+        <template #footer>
+            <el-button @click="onCancel(false)" plain>取消</el-button>
+            <el-button type="primary" @click="onSubmit">确认</el-button>
+        </template>
+    </app-drawer>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef } from 'vue'
+import AppDrawer from '@pc/components/base/drawer/index.vue'
+
+const show = shallowRef(true)
+const refresh = shallowRef(false)
+
+const onCancel = (isRefresh = false) => {
+    show.value = false
+    refresh.value = isRefresh
+}
+
+const onSubmit = () => {
+    onCancel(true)
+}
+</script>

+ 95 - 0
src/packages/pc/views/bonded/outbound/index.vue

@@ -0,0 +1,95 @@
+<!-- 保税仓业务-出仓管理 -->
+<template>
+    <app-view>
+        <template #header>
+            <app-filter :options="filterOptons" />
+        </template>
+        <!-- 表格数据 -->
+        <app-table :data="dataList" v-model:columns="tableColumns" :loading="loading">
+            <template #header>
+                <app-auth-operation :menus="['bonded_outbound_apply']" @closed="onRefresh" />
+            </template>
+            <!-- 出仓类型 -->
+            <template #outtype="{ value }">
+                {{ getGZBSCOutTypeName(value) }}
+            </template>
+            <!-- 单据状态 -->
+            <template #orderstatus="{ value }">
+                {{ getGZBSCOrderStatusName(value) }}
+            </template>
+            <!-- 操作 -->
+            <template #operate="{ row }">
+                <app-auth-operation type="dropdown" :menus="handleOperateButtons(row)" :options="{ selectedRow: row }"
+                    @closed="onRefresh" />
+            </template>
+            <template #footer>
+                <app-pagination :total="total" v-model:page-size="pageSize" v-model:page-index="pageIndex"
+                    @change="onRefresh" />
+            </template>
+        </app-table>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import { useDataFilter } from '@/hooks/datatable'
+import { useComposeTable } from '@pc/components/base/table'
+import { GZBSCOrderType, getGZBSCOutTypeList, getGZBSCOutTypeName, getGZBSCOrderStatusName } from '@/constants/bonded'
+import { useGzbscinOutOrder } from '@/business/bonded'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppFilter from '@pc/components/base/table-filter/index.vue'
+import AppPagination from '@pc/components/base/pagination/index.vue'
+import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
+
+const { loading, dataList, total, pageIndex, pageSize, getGzbscinOutOrder } = useGzbscinOutOrder()
+const { tableColumns } = useComposeTable<Ermcp.GzbscinOutOrderRsp>({ rowKey: 'orderid', columnKey: 'bonded_outbound' })
+const { filterOptons, getQueryParams } = useDataFilter<Ermcp.GzbscinOutOrderReq>()
+
+// 状态”2.待上传 “多操作:“上传”
+const handleOperateButtons = (row: Ermcp.GzbscinOutOrderRsp) => {
+    const buttons = ['bonded_outbound_details', 'bonded_outbound_download']
+    if (row.orderstatus === 2) {
+        buttons.push('bonded_outbound_upload')
+    }
+    return buttons
+}
+
+const onSearch = (clear = false) => {
+    getQueryParams((qs) => {
+        qs.ordertype = GZBSCOrderType.Out
+        pageIndex.value = 1
+        getGzbscinOutOrder(qs)
+    }, clear)
+}
+
+const onRefresh = () => {
+    getQueryParams((qs) => {
+        qs.ordertype = GZBSCOrderType.Out
+        getGzbscinOutOrder(qs).catch((err) => ElMessage.error(err))
+    })
+}
+
+filterOptons.selectList = [
+    {
+        label: '单据状态',
+        key: 'listtype',
+        options: [
+            { label: '待上传', value: 1 },
+            { label: '进行中', value: 2 },
+            { label: '已结束', value: 3 }
+        ],
+    },
+    {
+        label: '出仓类型',
+        key: 'outtype',
+        options: getGZBSCOutTypeList(),
+    },
+]
+
+filterOptons.buttonList = [
+    { lable: '重置', onClick: () => onSearch(true) },
+    { lable: '查询', className: 'el-button--primary', onClick: () => onSearch() }
+]
+
+onRefresh()
+</script>

+ 3 - 4
src/packages/pc/views/customs/bonded/components/payment/index.vue

@@ -18,7 +18,7 @@
         </el-descriptions>
         <template #footer>
             <el-button @click="onCancel(false)" plain>取消</el-button>
-            <el-button type="primary" :disabled="disabled" @click="onSubmit">确认</el-button>
+            <el-button type="primary" :disabled="balanceIsInsufficient" @click="onSubmit">确认</el-button>
         </template>
     </app-drawer>
 </template>
@@ -49,9 +49,8 @@ const arrearage = computed(() => {
     return settleamount - advanceamount
 })
 
-const disabled = computed(() => {
-    return arrearage.value > avaiableMoney.value
-})
+// 是否余额不足
+const balanceIsInsufficient = computed(() => arrearage.value > avaiableMoney.value)
 
 const onCancel = (isRefresh = false) => {
     show.value = false

+ 1 - 1
src/packages/pc/views/customs/bonded/index.vue

@@ -32,7 +32,7 @@ import AppPagination from '@pc/components/base/pagination/index.vue'
 
 const { loading, dataList, total, pageIndex, pageSize, getBSFWOrderList } = useBSFWOrderList()
 const { tableColumns } = useComposeTable<Ermcp.GZBSFWOrderRsp>({ rowKey: 'orderid', columnKey: 'customs_bsfw' })
-const { filterOptons, getQueryParams } = useDataFilter<Ermcp.GZBSFWOrderRsp>()
+const { filterOptons, getQueryParams } = useDataFilter<Ermcp.GZBSFWOrderReq>()
 
 const handleOperateButtons = (row: Ermcp.GZBSFWOrderRsp) => {
     const buttons = ['customs_bonded_details', 'customs_bonded_download']

+ 0 - 48
src/packages/pc/views/customs/exit/components/download/index.vue

@@ -1,48 +0,0 @@
-<!-- 交易服务-出境检测-文件下载 -->
-<template>
-    <app-drawer title="下载" :width="800" v-model:show="show">
-        <app-table :data="dataList" :loading="loading" :columns="columns" :show-toolbar="false" border>
-            <!-- 生成时间 -->
-            <template #operatetime="{ value }">
-                {{ formatDate(value) }}
-            </template>
-            <!-- 操作 -->
-            <template #operate="{ row }">
-                <el-button size="small" @click="downloadFile(row.fileaddress)">下载</el-button>
-            </template>
-        </app-table>
-    </app-drawer>
-</template>
-
-<script lang="ts" setup>
-import { shallowRef, PropType } from 'vue'
-import { getUrl, formatDate } from '@/filters'
-import { useGZCJBSOrderFileList } from '@/business/customs/bonded'
-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 | Ermcp.GZBSFWOrderRsp>,
-        required: true
-    },
-})
-
-const orderId = (() => {
-    if ('orderidstr' in props.selectedRow) {
-        return props.selectedRow.orderidstr
-    }
-    return props.selectedRow.orderid
-})()
-
-const { loading, dataList, columns, getGZCJBSOrderFileList } = useGZCJBSOrderFileList(orderId)
-const show = shallowRef(true)
-
-const downloadFile = (file?: string) => {
-    if (file) {
-        window.location.href = getUrl(file).href
-    }
-}
-
-getGZCJBSOrderFileList()
-</script>

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

@@ -91,7 +91,7 @@
                 <template #operate="{ row, index }">
                     <el-button-group size="small">
                         <el-button @click="openEdit(row)">修改</el-button>
-                        <el-button @click="deleteDetail(index)">删除</el-button>
+                        <el-button @click="deleteRecord(index)">删除</el-button>
                     </el-button-group>
                 </template>
             </app-table>
@@ -262,7 +262,7 @@ const clearList = () => {
 }
 
 // 删除列表数据
-const deleteDetail = (index: number) => {
+const deleteRecord = (index: number) => {
     formData.GZCJCategoryDetails?.splice(index, 1)
     formData.GZCJCategoryDetails?.forEach((e, i) => e.OrderIndex = i + 1) // 重置序列
 }

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

@@ -35,7 +35,7 @@ import AppPagination from '@pc/components/base/pagination/index.vue'
 
 const { loading, dataList, total, pageIndex, pageSize, getGZCJJCOrderList } = useCJJCOrderList()
 const { tableColumns } = useComposeTable<Ermcp.GZCJJCOrderRsp>({ rowKey: 'orderidstr', columnKey: 'customs_cjjc' })
-const { filterOptons, getQueryParams } = useDataFilter<Ermcp.GZCJJCOrderRsp>()
+const { filterOptons, getQueryParams } = useDataFilter<Ermcp.GZCJJCOrderReq>()
 
 const handleOperateButtons = (row: Ermcp.GZCJJCOrderRsp) => {
     switch (row.gzcjstatus) {

+ 81 - 0
src/services/api/bonded/index.ts

@@ -0,0 +1,81 @@
+import { httpRequest } from '@/services/http'
+import { HttpParams } from '@/services/http/interface'
+import { tradeServerRequest } from '@/services/socket/trade'
+import { TradeParams } from '@/services/socket/trade/interface'
+
+/**
+ * 获取保税商品表
+ */
+export function queryGZBSCGoods(params: HttpParams<{ rsp: Ermcp.GZBSCGoodsRsp[] }>) {
+    return httpRequest('/Guangzuan/GetGZBSCGoods', 'get', params);
+}
+
+/**
+ * 保税仓出入库申请表查询
+ */
+export function queryGzbscinOutOrder(params: HttpParams<{ req: Ermcp.GzbscinOutOrderReq, rsp: Ermcp.GzbscinOutOrderRsp[] }>) {
+    return httpRequest('/Guangzuan/QueryGzbscinOutOrder', 'get', params);
+}
+
+/**
+ * 保税仓出库申请明细附表查询
+ */
+export function queryBScinOutOrderDetail(params: HttpParams<{ req: Ermcp.BScinOutOrderDetailReq, rsp: Ermcp.BScinOutOrderDetailRsp[] }>) {
+    return httpRequest('/Guangzuan/QueryBScinOutOrderDetail', 'get', params);
+}
+
+/**
+ * 保税商品报关头寸表查询
+ */
+export function queryGzbscPosition(params: HttpParams<{ req: Ermcp.GzbscPositionReq, rsp: Ermcp.GzbscPositionRsp[] }>) {
+    return httpRequest('/Guangzuan/QueryGzbscPosition', 'get', params);
+}
+
+/**
+ * 保税仓出库申请明细附表查询
+ */
+export function queryBScOutOrderDetailatt(params: HttpParams<{ req: Ermcp.BScOutOrderDetailattReq, rsp: Ermcp.BScOutOrderDetailattRsp[] }>) {
+    return httpRequest('/Guangzuan/QueryBScOutOrderDetailatt', 'get', params);
+}
+
+/**
+ * 保税仓用户月付款通知书表查询(计费管理)
+ */
+export function queryGzbscusermonthpay(params: HttpParams<{ req: Ermcp.GzbscusermonthpayReq, rsp: Ermcp.GzbscusermonthpayRsp[] }>) {
+    return httpRequest('/Guangzuan/QueryGzbscusermonthpay', 'get', params);
+}
+
+/**
+ * 保税仓月电费登记表查询
+ */
+export function queryGzbscuserpowerfee(params: HttpParams<{ req: Ermcp.GzbscuserpowerfeeReq, rsp: Ermcp.GzbscuserpowerfeeRsp[] }>) {
+    return httpRequest('/Guangzuan/QueryGzbscuserpowerfee', 'get', params);
+}
+
+/**
+ * 保税仓本月进口明细/本月出境明细/本月转厂明细查询
+ */
+export function queryBscinoutorder(params: HttpParams<{ req: Ermcp.BscinoutorderReq, rsp: Ermcp.BscinoutorderRsp[] }>) {
+    return httpRequest('/Guangzuan/QueryBscinoutorder', 'get', params);
+}
+
+/**
+ * 保税仓进出仓申请
+ */
+export function bscInAndOutWareHouseApply(params: TradeParams<Proto.BSCInAndOutWareHouseApplyReq, Proto.BSCInAndOutWareHouseApplyRsp>) {
+    return tradeServerRequest('BSCInAndOutWareHouseApplyReq', 'BSCInAndOutWareHouseApplyRsp', params, 66201);
+}
+
+/**
+ * 保税仓上传文件
+ */
+export function bscUploadFile(params: TradeParams<Proto.BSCUploadFileReq, Proto.BSCUploadFileRsp>) {
+    return tradeServerRequest('BSCUploadFileReq', 'BSCUploadFileRsp', params, 66201);
+}
+
+/**
+ * 保税仓确认支付
+ */
+export function bscConfirmPay(params: TradeParams<Proto.BSCConfirmPayReq, Proto.BSCConfirmPayRsp>) {
+    return tradeServerRequest('BSCConfirmPayReq', 'BSCConfirmPayRsp', params, 66201);
+}

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

@@ -12,7 +12,7 @@ export interface EnumType {
     disabled?: boolean;
 }
 
-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', 'GZBSStatus', 'GZBSDeliveryType'] 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', 'GZBSStatus', 'GZBSDeliveryType', 'GZBSCOrderType', 'GZBSCOrderStatus', 'GZBSCOutType', 'GZBSCPayStatus', 'GZBSCPayMode'] as const
 
 export const enumMap = new Map<typeof enumKeys[number], ShallowRef<Ermcp.EnumRsp[]>>()
 

+ 57 - 0
src/stores/modules/theme.ts

@@ -7,6 +7,8 @@ export const themeStore = createStore({
     state() {
         return {
             appTheme: localData.getRef('appTheme'),
+            clientWidth: 0, // 客户端宽度
+            isMobile: false, // 是否移动设备
         }
     },
     actions: {
@@ -29,6 +31,61 @@ export const themeStore = createStore({
             this.actions.setStatusBarTheme(theme)
             document.documentElement.setAttribute('theme', theme)
             this.state.appTheme = theme
+        },
+        // 适配客户端屏幕
+        screenAdapter(remsize = false) {
+            const { clientWidth, isMobile } = this.$mapState()
+            const { isPc } = this.actions.getClientAgent()
+            const el = document.documentElement
+            const body = document.body
+            let screenWidth = el.clientWidth
+
+            if (remsize) {
+                const designSize = 750
+                isMobile.value = true
+
+                if (isPc) {
+                    screenWidth = designSize / 1.8
+                    body.style.setProperty('width', '540px')
+                } else {
+                    body.style.removeProperty('width')
+                }
+
+                if (screenWidth > 0) {
+                    const fontSize = (screenWidth / designSize) * 100 + 'px'
+                    el.style.setProperty('font-size', fontSize)
+                }
+            } else {
+                if (screenWidth > 768) {
+                    isMobile.value = false
+                } else {
+                    isMobile.value = true
+                }
+
+                el.setAttribute('screen', isMobile.value ? 'small' : 'normal')
+            }
+
+            clientWidth.value = body.clientWidth
+        },
+        // 获取客户端平台
+        getClientAgent() {
+            const ua = navigator.userAgent,
+                isWindowsPhone = /(?:Windows Phone)/.test(ua),
+                isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
+                isAndroid = /(?:Android)/.test(ua),
+                isFireFox = /(?:Firefox)/.test(ua),
+                isChrome = /(?:Chrome|CriOS)/.test(ua),
+                isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
+                isiPhone = /(?:iPhone)/.test(ua) && !isTablet,
+                isPc = !isiPhone && !isAndroid && !isSymbian && !isTablet
+
+            return {
+                isTablet,
+                isiPhone,
+                isAndroid,
+                isChrome,
+                isPc,
+            }
         }
     }
 })

+ 0 - 1
src/types/ermcp/account.d.ts

@@ -1,7 +1,6 @@
 import { AuthType, UrlType } from '@/constants/menu'
 
 declare global {
-    /** 企业风管 */
     namespace Ermcp {
         /** 账户登录后信息查询请求 */
         interface LoginQueryReq {

+ 0 - 1
src/types/ermcp/bank.d.ts

@@ -1,7 +1,6 @@
 import { AuthType, UrlType } from '@/constants/menu'
 
 declare global {
-    /** 企业风管 */
     namespace Ermcp {
         /** 查询托管银行 响应 */
         interface CusBankSignBankRsp {

+ 265 - 0
src/types/ermcp/bonded.d.ts

@@ -0,0 +1,265 @@
+declare namespace Ermcp {
+    /** 获取保税商品表 响应 */
+    interface GZBSCGoodsRsp {
+        countryname: string; // 产销国
+        currencyname: string; // 币制
+        executionflag: string; // 海关执行标志
+        goodsid: number; // 商品ID(料号)
+        goodsname: string; // 商品名称
+        goodsnum: string; // 商品编号
+        goodsspec: string; // 规格型号
+        legalunit: string; // 法定计量单位
+        reportunit: string; // 申报计量单位
+        storageperiod: string; // 存储(监管)期限
+        unitprice: number; // 单价
+    }
+
+    /** 保税仓出入库申请表查询 请求 */
+    interface GzbscinOutOrderReq {
+        userid: number; // 用户ID
+        ordertype: number; // 单据类型 - 1:进仓 2:出仓(枚举:GZBSCOrderType)
+        listtype?: number; // 列表类型 - 0.全部 1.待上传 2.进行中 3.已结束
+        outtype?: number; // 出仓类型 - 1:转厂 2:出境(枚举:GZBSCOutType)
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+    }
+
+    /** 保税仓出入库申请表查询 响应 */
+    interface GzbscinOutOrderRsp {
+        applicanttime: string; // 申请时间
+        bagweightgm: number; // 连袋重(克)
+        checklistno: string; // 核注清单号
+        confirmdate: string; // 进出仓确认时间
+        confirmername: string; // 进出仓确认人
+        confirmtradedate: string; // 进出仓确认交易日(yyyyMMdd)
+        contactname: string; // 申请方联系人
+        contactnum: string; // 申请方联系电话
+        customsno: string; // 报关单号
+        customstotalvalue: number; // 报关总值
+        goodsname: string; // 商品名称 - 明细的商品名称拼接(“/”)
+        jckdate: string; // 进出口日期
+        logisticscompany: string; // 物流公司名称
+        logisticsno: string; // 托运单号
+        netweightct: number; // 净重(克拉)
+        netweightgm: number; // 净重(克)
+        orderid: string; // 申请ID(806+Unix秒时间戳(10位)+xxxxxx)
+        ordernum: string; // 单据编号
+        orderstatus: number; // 进出仓状态 - 1.待确认 2.待上传 3.报关中 4.进仓中 5.出仓中 20.已关闭 21.确认拒绝(枚举:GZBSCOrderStatus)
+        ordertype: number; // 单据类型 - 1:进仓 2:出仓(枚举:GZBSCOrderType)
+        outtype: number; // 出仓类型 - 1:转厂 2:出境(枚举:GZBSCOutType)
+        sealno: string; // 进出仓封条号
+        signeedate: string; // 收发货人日期
+        signeename: string; // 收发货人
+        totalprice: number; // 总价
+        useraddress: string; // 申请方地点
+        userid: number; // 申请用户ID
+        username: string; // 申请方名称
+    }
+
+    /** 保税仓出库申请明细附表查询 请求 */
+    interface BScinOutOrderDetailReq {
+        userid: number; // 用户ID
+        orderid: string; // 申请ID
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+    }
+
+    /** 保税仓出库申请明细附表查询 响应 */
+    interface BScinOutOrderDetailRsp {
+        backagenum: number; // 件数
+        bagweightgm: number; // 连袋重(克)
+        countryname: string; // 产销国
+        currencydes: string; // 币种
+        currencyname: string; // 币制
+        customsno: string; // 报关单号(进仓:录入时更新,出仓申请时有)
+        customsvalue: number; // 报关总值
+        detailid: string; // 明细ID(807+Unix秒时间戳(10位)+xxxxxx)
+        executionflag: string; // 海关执行标志
+        goodsid: number; // 商品ID
+        goodsname: string; // 商品名称
+        goodsnum: string; // 商品编号
+        goodsspec: string; // 规格
+        legalunit: string; // 法定计量单位
+        netweightct: number; // 净重(克拉)
+        netweightgm: number; // 净重(克)
+        orderid: string; // 申请ID
+        origincountry: string; // 原产国
+        prepricegm: number; // 单价(克)
+        rawdetail: string; // 原料明细
+        remark: string; // 备注
+        reportunit: string; // 申报计量单位
+        storageperiod: string; // 存储(监管)期限
+        totalprice: number; // 总价
+        unitprice: number; // 单价
+        userid: number; // 用户ID
+    }
+
+    /** 保税商品报关头寸表查询 请求 */
+    interface GzbscPositionReq {
+        userid: number; // 用户ID
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+    }
+
+    /** 保税商品报关头寸表查询 响应 */
+    interface GzbscPositionRsp {
+        countryname: string; // 产销国
+        currencyname: string; // 币制
+        customsno: string; // 报关单号
+        customsvalue: number; // 报关总值
+        executionflag: string; // 海关执行标志
+        freezenetweightct: number; // 冻结净重(克拉)
+        goodsid: string; // 商品ID
+        goodsname: string; // 商品名称
+        goodsnum: string; // 商品编号
+        goodsspec: string; // 规格型号
+        jckdate: string; // 进口日期
+        legalunit: string; // 法定计量单位
+        netweightct: number; // 净重(克拉)
+        netweightgm: number; // 净重(克)
+        oricustomsvalue: number; // 期初报关总值
+        orifreezeweight: number; // 期初冻结净重(克拉)
+        orinetweightct: number; // 期初净重(克拉)
+        orinetweightgm: number; // 期初净重(克)
+        oritotalprice: number; // 期初总价
+        reportunit: string; // 申报计量单位
+        storageperiod: string; // 存储(监管)期限
+        totalprice: number; // 总价
+        unitprice: number; // 单价
+        userid: number; // 用户ID
+    }
+
+    /** 保税仓出库申请明细附表查询 请求 */
+    interface BScOutOrderDetailattReq {
+        userid: number; // 用户ID
+        orderid: string; // 申请ID
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+    }
+
+    /** 保税仓出库申请明细附表查询 响应 */
+    interface BScOutOrderDetailattRsp {
+        countryname: string; // 产销国
+        curcustomsvalue: number; // 本次扣减货值(报关总值)
+        curnetweightct: number; // 本次扣减量(克拉)
+        curnetweightgm: number; // 本次扣减净重(克)
+        currencyname: string; // 币制
+        curtotalprice: number; // 本次扣减货值
+        customsno: string; // 报关单号
+        customsvalue: number; // 报关总值
+        detailattid: string; // 明细ID(810+Unix秒时间戳(10位)+xxxxxx)
+        executionflag: string; // 海关执行标志
+        goodsid: number; // 商品ID
+        goodsname: string; // 商品名称
+        goodsnum: string; // 商品编号
+        goodsspec: string; // 规格型号
+        jckdate: string; // 进口日期
+        legalunit: string; // 法定计量单位
+        netweightct: number; // 净重(克拉)
+        netweightgm: number; // 净重(克)
+        orderid: string; // 申请ID
+        remaincurnetweightgm: number; // 结余净重(克)
+        remaincustomsvalue: number; // 结余报关总值
+        remainnetweightct: number; // 结余净重(克拉)
+        remaintotalprice: number; // 结余总价
+        reportunit: string; // 申报计量单位
+        storageperiod: string; // 存储(监管)期限
+        totalprice: number; // 总价
+        unitprice: number; // 单价
+        userid: number; // 用户ID
+    }
+
+    /** 保税仓用户月付款通知书表查询(计费管理) 请求 */
+    interface GzbscusermonthpayReq {
+        userid: number; // 用户ID
+        paystatus?: number; // 支付状态 - 1:待确认 2:待支付 3:已支付(枚举:GZBSCPayStatus)
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+    }
+
+    /** 保税仓用户月付款通知书表查询(计费管理) 响应 */
+    interface GzbscusermonthpayRsp {
+        accountid: number; // 资金账户ID
+        confirmtime: string; // 确认时间
+        handlestatus: number; // 处理状态
+        infee: number; // 进仓报关费
+        innum: number; // 进仓报关单数
+        outfee: number; // 出仓报关费
+        outnum: number; // 出仓报关单数
+        paymode: number; // 支付方式 - 1:线上 2:线下(枚举:GZBSCPayMode)
+        paystatus: number; // 支付状态 - 1:待确认 2:待支付 3:已支付(枚举:GZBSCPayStatus)
+        paytime: string; // 支付时间
+        powerfee: number; // 分拣室电费
+        premium: number; // 保险费
+        rentdays: number; // 月租天数
+        servicefee: number; // 分拣室服务费
+        startdate: string; // 协议起始日
+        storagefee: number; // 仓储费
+        totalfee: number; // 合计费用
+        trademonth: string; // 月份(yyyMM)
+        userid: number; // 用户ID
+    }
+
+    /** 保税仓月电费登记表查询 请求 */
+    interface GzbscuserpowerfeeReq {
+        userid: number; // 用户ID
+        trademonth: string; // 月份(yyyMM)
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+    }
+
+    /** 保税仓月电费登记表查询 响应 */
+    interface GzbscuserpowerfeeRsp {
+        createtime: string; // 创建时间
+        endpower: number; // 月末初数
+        instrumentno: number; // 仪表编号
+        powerfee: number; // 电费(元)
+        powerfeeid: string; // 电费单ID(809+Unix秒时间戳(10位)+xxxxxx)
+        powernum: number; // 本月用量
+        powerunit: number; // 单位
+        startpower: number; // 月初读数
+        trademonth: string; // 月份(yyyMM)
+        userid: number; // 用户ID
+        username: number; // 使用单位
+    }
+
+    /** 保税仓本月进口明细/本月出境明细/本月转厂明细查询 请求 */
+    interface BscinoutorderReq {
+        userid: number; // 用户ID
+        jckdate: string; // 进出口月份(yyyMM)
+        page?: number; // 页码
+        pagesize?: number; // 每页条数
+    }
+
+    /** 保税仓本月进口明细/本月出境明细/本月转厂明细查询 响应 */
+    interface BscinoutorderRsp {
+        applicanttime: string; // 申请时间
+        bagweightgm: number; // 连袋重(克)
+        checklistno: string; // 核注清单号
+        confirmdate: string; // 进出仓确认时间
+        confirmername: string; // 进出仓确认人
+        confirmtradedate: string; // 进出仓确认交易日(yyyyMMdd)
+        contactname: string; // 申请方联系人
+        contactnum: string; // 申请方联系电话
+        customsno: string; // 报关单号
+        customstotalvalue: number; // 报关总值
+        goodsname: string; // 商品名称 - 明细的商品名称拼接(“/”)
+        jckdate: string; // 进出口日期
+        logisticscompany: string; // 物流公司名称
+        logisticsno: string; // 托运单号
+        netweightct: number; // 净重(克拉)
+        netweightgm: number; // 净重(克)
+        orderid: string; // 申请ID(806+Unix秒时间戳(10位)+xxxxxx)
+        ordernum: string; // 单据编号
+        orderstatus: number; // 进出仓状态 - 1.待确认 2.待上传 3.报关中 4.进仓中 5.出仓中 20.已关闭 21.确认拒绝(枚举:GZBSCOrderStatus)
+        ordertype: number; // 单据类型 - 1:进仓 2:出仓(枚举:GZBSCOrderType)
+        outtype: number; // 出仓类型 - 1:转厂 2:出境(枚举:GZBSCOutType)
+        sealno: string; // 进出仓封条号
+        signeedate: string; // 收发货人日期
+        signeename: string; // 收发货人
+        totalprice: number; // 总价
+        useraddress: string; // 申请方地点
+        userid: number; // 申请用户ID
+        username: string; // 申请方名称
+    }
+}

+ 0 - 1
src/types/ermcp/common.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 首页统计数据 请求 */
     interface HomeDataReq {

+ 0 - 1
src/types/ermcp/customs.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 查询出境检测单据 请求 */
     interface GZCJJCOrderReq {

+ 0 - 1
src/types/ermcp/enum.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 枚举信息 请求 */
     interface EnumReq {

+ 0 - 1
src/types/ermcp/favorite.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 查询我的收藏 请求 */
     interface MyFavoriteReq {

+ 0 - 1
src/types/ermcp/goods.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 商品合约 */
     interface GoodsRsp {

+ 0 - 1
src/types/ermcp/menu.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 获取菜单表数据 请求 */
     interface NewFuncmenuReq {

+ 0 - 1
src/types/ermcp/performance.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 查询履约模板 请求 */
     interface PermancePlanTmpReq {

+ 0 - 1
src/types/ermcp/quote.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 商品盘面 */
     interface QuoteDayRsp {

+ 0 - 1
src/types/ermcp/table.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     interface TableColumn {
         prop: string;

+ 0 - 1
src/types/ermcp/trade.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 钻石搜索请求 */
     interface SellOrderSearchReq {

+ 0 - 1
src/types/ermcp/user.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 查询收货地址信息 请求 */
     interface UserReceiveInfoReq {

+ 0 - 1
src/types/ermcp/warehouse.d.ts

@@ -1,4 +1,3 @@
-/** 企业风管 */
 declare namespace Ermcp {
     /** 查询仓库信息 请求 */
     interface WarehouseInfoReq {

+ 103 - 0
src/types/proto/bonded.d.ts

@@ -0,0 +1,103 @@
+import { IMessageHead } from '@/services/socket/trade/protobuf/proto'
+import Long from 'long'
+
+declare global {
+    namespace Proto {
+        /** 保税仓进出仓商品明细列表 */
+        interface BSCGoodsListDetail {
+            GoodsID: number; // 商品ID,必填
+            GoodsSpec: string; // 规格,必填
+            RawDetail: string; // 原料明细,必填
+            BackageNum: number; // 件数,必填
+            NetWeightCT: number; // 净重(克拉),必填
+            NetWeightGM: number; // 净重(克),必填
+            BagWeightGM: number; // 连袋重(克),必填
+            PrePriceGM: number; // 单价(克),必填
+            TotalPrice: number; // 总价,必填
+            CurrencyDes: string; // 币种,必填
+            CustomsValue: number; // 报关总值,必填
+            OriginCountry: string; // 原产国,必填
+            Remark: string; // 备注,选填
+        }
+
+        /** 保税仓进出仓附表 */
+        interface BSCOutWareHouseSchedule {
+            GoodsID: number; // 商品ID,必填
+            CustomsNo: string; // 报关单号,必填
+            JCKDate: string; // 进口日期,必填
+            CurNetWeightCT: number; // 本次扣减量(克拉),必填
+        }
+
+        /** 保税仓进出仓申请接口请求 */
+        interface BSCInAndOutWareHouseApplyReq {
+            Header?: IMessageHead;
+            UserID: number; // 用户ID,必填
+            UserName: string; // 申请方名称,必填
+            UserAddress: string; // 申请方地点,必填
+            ContactName: string; // 申请方联系人,必填
+            ContactNum: string; // 申请方联系电话,必填
+            LogisticsCompany: string; // 物流公司名称,必填
+            LogisticsNo: string; // 托运单号,选填
+            OrderType: number; // 单据类型,必填1:进仓2:出仓
+            BSCGoodsListDetails: BSCGoodsListDetail[]; // 明细列表(数组),必填
+            BSCOutWareHouseSchedules: BSCOutWareHouseSchedule[]; // 出仓附表(数组),必填
+            OperateID: number; // 操作人ID,必填
+            OperateAccount: string; // 操作人账户,必填
+            ClientSerialNo: string; // 客户端流水号
+            OutType?: number; // 出仓类型,出仓类型-1:转厂2:出境(枚举:GZBSCOutType)OrderType=2:出仓
+        }
+
+        // 保税仓进出仓申请接口响应
+        interface BSCInAndOutWareHouseApplyRsp {
+            Header?: IMessageHead;
+            RetCode: number; // 返回码
+            RetDesc: string; // 描述信息
+            OrderID: number; // 单据ID,必填
+            ClientSerialNo: string; // 客户端流水号
+        }
+
+        /** 文件列表 */
+        interface FileDetail {
+            FileName: string; // 文件名
+            FilePath: string; // 文件地址
+        }
+
+        // 保税仓上传文件接口请求
+        interface BSCUploadFileReq {
+            Header?: IMessageHead;
+            UserID: number; // 用户ID,必填
+            OrderID: Long; // 单据ID,必填
+            FileDetails: FileDetail[]; // 文件列表,必填
+            ClientSerialNo: string; // 客户端流水号
+            OperateID: number; // 操作人ID,必填
+            OperateAccount: string; // 操作人账户,必填
+        }
+
+        // 保税仓上传文件接口响应
+        interface BSCUploadFileRsp {
+            Header?: IMessageHead;
+            RetCode: number; // 返回码
+            RetDesc: string; // 描述信息
+            UserID: number; // 用户ID,必填
+            OrderID: number; // 单据ID,必填
+            ClientSerialNo: string; // 客户端流水号
+        }
+
+        // 保税仓确认支付接口请求
+        interface BSCConfirmPayReq {
+            Header?: IMessageHead;
+            UserID: number; // 用户ID,必填
+            TradeMonth: string; // 月份(yyyMM),必填
+            ClientSerialNo: string; // 客户端流水号
+        }
+
+        // 保税仓确认支付接口响应
+        interface BSCConfirmPayRsp {
+            Header?: IMessageHead;
+            RetCode: number; // 返回码
+            RetDesc: string; // 描述信息
+            UserID: number; // 用户ID,必填
+            ClientSerialNo: string; // 客户端流水号
+        }
+    }
+}

+ 0 - 1
src/types/proto/customs.d.ts

@@ -1,5 +1,4 @@
 import { IMessageHead } from '@/services/socket/trade/protobuf/proto'
-import { number } from 'echarts/core';
 import Long from 'long'
 
 declare global {

+ 0 - 75
src/utils/client/index.ts

@@ -1,75 +0,0 @@
-import { reactive, toRefs, readonly } from 'vue'
-
-export default new (class {
-    private _state = reactive({
-        layout: 'default', // 页面布局
-        clientWidth: 0, // 客户端宽度
-        isMobile: false, // 是否移动设备
-    })
-
-    /** 只读状态 */
-    state = readonly(this._state);
-
-    /**
-     * 适配客户端屏幕
-     * @param pxtorem rem 布局
-     */
-    screenAdapter(pxtorem: boolean) {
-        const { clientWidth, isMobile } = toRefs(this._state);
-        const { isPc } = this.getClientAgent();
-        const el = document.documentElement;
-        const body = document.body;
-        let screenWidth = el.clientWidth;
-
-        if (pxtorem) {
-            const designSize = 750;
-            isMobile.value = true;
-
-            if (isPc) {
-                screenWidth = designSize / 1.8;
-                body.style.setProperty('width', '540px');
-            } else {
-                body.style.removeProperty('width')
-            }
-
-            if (screenWidth > 0) {
-                const fontSize = (screenWidth / designSize) * 100 + 'px';
-                el.style.setProperty('font-size', fontSize);
-            }
-        } else {
-            if (screenWidth > 768) {
-                isMobile.value = false;
-            } else {
-                isMobile.value = true;
-            }
-
-            el.setAttribute('screen', isMobile.value ? 'small' : 'normal');
-        }
-
-        clientWidth.value = body.clientWidth;
-    }
-
-    /**
-     * 获取客户端平台
-     * @returns 
-     */
-    getClientAgent() {
-        const ua = navigator.userAgent,
-            isWindowsPhone = /(?:Windows Phone)/.test(ua),
-            isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
-            isAndroid = /(?:Android)/.test(ua),
-            isFireFox = /(?:Firefox)/.test(ua),
-            isChrome = /(?:Chrome|CriOS)/.test(ua),
-            isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
-            isiPhone = /(?:iPhone)/.test(ua) && !isTablet,
-            isPc = !isiPhone && !isAndroid && !isSymbian;
-
-        return {
-            isTablet,
-            isiPhone,
-            isAndroid,
-            isChrome,
-            isPc,
-        }
-    }
-})

+ 1 - 1
src/utils/storage/index.ts

@@ -4,7 +4,7 @@ import { shallowRef, ShallowRef, watch } from 'vue'
  * 本地存储类
  */
 export default class <T extends object> {
-    constructor(storage: Storage, source: T, prefix = window.location.host + '@') {
+    constructor(storage: Storage, source: T, prefix = '@') {
         this.prefix = prefix
         this.storage = storage
         this.source = source