From 64e53b6ee9855f2221d1f9eacd5a56b94354df3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Mon, 8 Sep 2025 19:09:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=87=E8=B4=A7=E5=8D=95=20=E5=8A=A0=20?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/adminApi.ts | 37 + src/api/commonApi.ts | 12 + src/api/crmApi.ts | 16 +- src/assets/img/crm/contract.png | Bin 0 -> 1508 bytes .../custom/CustomerSelectorDialog.vue | 198 --- src/components/custom/MoneyInput.vue | 131 -- src/components/custom/OrderItemRow.vue | 338 ---- .../custom/VehicleSelectorDialog.vue | 205 --- src/components/custom/index.ts | 4 - src/composables/usePdfPreview.ts | 81 + src/composables/useStockingForm.ts | 862 +++++++++ src/router/routes/asyncRoutes.ts | 66 + src/router/routes/staticRoutes.ts | 6 + src/router/routesAlias.ts | 8 +- src/views/admin/approval/approval_add.vue | 1278 ++++++++++++++ src/views/admin/approval/approval_edit.vue | 1373 +++++++++++++++ src/views/admin/approval/approval_list.vue | 283 +++ src/views/admin/system/dictionary/index.vue | 4 +- src/views/admin/user/user_info.vue | 37 +- src/views/auth/login/index.vue | 4 +- src/views/auth/signature/mobile.vue | 237 +++ src/views/crmpc/stocking copy/Add.vue | 1485 ++++++++++++++++ src/views/crmpc/stocking copy/final_add.vue | 1567 +++++++++++++++++ src/views/crmpc/stocking copy/final_list.vue | 149 ++ src/views/crmpc/stocking copy/final_show.vue | 625 +++++++ src/views/crmpc/stocking copy/show.vue | 619 +++++++ .../crmpc/stocking copy/stocking_auth.vue | 157 ++ .../crmpc/stocking copy/stocking_list.vue | 179 ++ src/views/crmpc/stocking/Add.vue | 804 +++------ src/views/crmpc/stocking/final_add.vue | 978 +++++----- src/views/crmpc/stocking/final_list.vue | 72 +- src/views/crmpc/stocking/final_show.vue | 1062 ++++++----- src/views/crmpc/stocking/show.vue | 597 +++++-- src/views/crmpc/stocking/stocking_auth.vue | 34 +- .../crmpc/stocking/stocking_auth_detil.vue | 625 +++++++ src/views/crmpc/stocking/stocking_list.vue | 136 +- 36 files changed, 11717 insertions(+), 2552 deletions(-) create mode 100644 src/assets/img/crm/contract.png delete mode 100644 src/components/custom/CustomerSelectorDialog.vue delete mode 100644 src/components/custom/MoneyInput.vue delete mode 100644 src/components/custom/OrderItemRow.vue delete mode 100644 src/components/custom/VehicleSelectorDialog.vue delete mode 100644 src/components/custom/index.ts create mode 100644 src/composables/usePdfPreview.ts create mode 100644 src/composables/useStockingForm.ts create mode 100644 src/views/admin/approval/approval_add.vue create mode 100644 src/views/admin/approval/approval_edit.vue create mode 100644 src/views/admin/approval/approval_list.vue create mode 100644 src/views/auth/signature/mobile.vue create mode 100644 src/views/crmpc/stocking copy/Add.vue create mode 100644 src/views/crmpc/stocking copy/final_add.vue create mode 100644 src/views/crmpc/stocking copy/final_list.vue create mode 100644 src/views/crmpc/stocking copy/final_show.vue create mode 100644 src/views/crmpc/stocking copy/show.vue create mode 100644 src/views/crmpc/stocking copy/stocking_auth.vue create mode 100644 src/views/crmpc/stocking copy/stocking_list.vue create mode 100644 src/views/crmpc/stocking/stocking_auth_detil.vue diff --git a/src/api/adminApi.ts b/src/api/adminApi.ts index f118573..0eea85d 100644 --- a/src/api/adminApi.ts +++ b/src/api/adminApi.ts @@ -205,4 +205,41 @@ export class manageService { params }) } + //备货单审核列表 + static getstocklist(params: Api.Common.PaginatingParams) { + return request.get({ + url: '/admin/approval/list', + params + }) + } + //备货单审核 + static stockapproval(params: any) { + return request.post({ + url: '/admin/approval/store', + params + // showErrorMessage: false // 不显示错误消息 + }) + } + // 审核更新 + static stockapprovalupdate(params: any) { + return request.post({ + url: '/admin/approval/update', + params + // showErrorMessage: false // 不显示错误消息 + }) + } + //审核详情 + static stockapprovaldetail(params: any) { + return request.get({ + url: '/admin/approval/show', + params + }) + } + //审核删除 + static stockapprovaldelete(params: any) { + return request.get({ + url: '/admin/approval/destroy', + params + }) + } } diff --git a/src/api/commonApi.ts b/src/api/commonApi.ts index 45348ce..a359043 100644 --- a/src/api/commonApi.ts +++ b/src/api/commonApi.ts @@ -106,4 +106,16 @@ export class commonService { url: '/crm/customer/preset' }) } + //角色下拉框 + static getRoleSelect() { + return request.get({ + url: '/common/preset/roles' + }) + } + //用户下拉框 + static getUserSelect() { + return request.get({ + url: '/common/preset/users' + }) + } } diff --git a/src/api/crmApi.ts b/src/api/crmApi.ts index d3eb064..cf871d8 100644 --- a/src/api/crmApi.ts +++ b/src/api/crmApi.ts @@ -210,7 +210,7 @@ export class CrmService { //备货单 static getStockList(params: Api.Common.PaginatingParams) { return request.get({ - url: '/crm/stock/list', + url: '/crm/stock/order/list', params }) } @@ -222,4 +222,18 @@ export class CrmService { // showErrorMessage: false // 不显示错误消息 }) } + //详情备货单 + static getStockDetail(params: any) { + return request.get({ + url: '/crm/stock/order/show', + params + }) + } + //打印 + static printStock(params: any) { + return request.get({ + url: '/crm/stock/order/print', + params + }) + } } diff --git a/src/assets/img/crm/contract.png b/src/assets/img/crm/contract.png new file mode 100644 index 0000000000000000000000000000000000000000..eb64eef95707e15fc057381a9eb0237d331af6ce GIT binary patch literal 1508 zcmVSWv)<^pXW1Hz0k2p-(XGH3gL_ z7Nmf{{GUf++-~C#x0J}m`&Btslqj}ekRVPjl<*>z#D| z;o)I~-E>~r)z#HX#xX-2^B>CRE6Ub0$}Xeq5#^unHqN(|QMT~4X5o=w1XQFg{$rDQ zzD^t+9R( zzsD~ub?lZ^m5)dm3dpaGT~eJiAmH;#O20*ulas$^)nFl?lz_XC5d4t z7^tbsP%D?}ZE$yY#|^7DufG7{ej%YS{eJ(GPD8GB57M618E-W)gluZHny$IrIR3__ zSfJiVB>WO-mFkmV8g0sb7vUNP4O^(w(nGU|kB-9(34$QJP$9u6a3|DtTC4@bpl5cW zl95;o7V+WDi9KTQD@1E+YjY$RgVjRF3G{_YFpH=Ki->S7GFEHh?CdP0(b8=+8Y3GF z7>jTrRQ4gW$r8|r!Lg&q$H(T<(o%;kGZx{(pRoXYaO8qj@R(s8`wm$E*A|cF@JOf7StVl+&dlarH<2=@)3 zp_wjVp3Re^xYnR8tF5%?U_6dl6KlaRiSX6PR}p$KCWG+!ccBYECxcEjH#avyo};K* ztp*dYjf-#<&?em%FML$H&KCAZRKA{^YS)6J==7!rR;1 ztsn?uU0fzg(fwr|-0gOGD2mENX-qK|@YU578Da}#hx2Gy8#bpYyk|Ri7HWoVQlo>i z_`{mKfA^gTsF^w$26sYRr~RaJmjt8uN$0L$ma;g@Bc3CNeW@nIIInYg`q9M1#GIOM z&_OHD*-rXz(>N{Mlwf0czL(1aJ~%jt1M#>Gb5<>630A`b#%`M&gncT?uQKbgbC);N zh6Qp6Em)rLtz%wQI^;A2+)B(?bP_-5+@=2p2%X^m{{Bx0bNAulfp4>2&g=Dh0XuRa z;5l9w0e0#vIgHCXw*>*WGVxj{h?QpkE&hS}TOo7JnJh_zrC-}gBrEPiB; zSPK@B~{r*?| zu3D{bkx&>84bW-yeA`~FR@)(^8hA#bdVH5KBuXV{B5WN`P3TBY-e^HRv`82VmZO?0 z7pccEMlU7;YHSO;xYn{WCp|pF`gqW@9id`&H#RnQ()k!+-Wjy0#A{2gpzJw6L@2K; z+B`w-`p=@CTb;)yNcfU*Ovs~@_*5bzEO#`qVo>nP~Tl - -
- - - - - - - - - - - - - -
- -
-
- - -
- - - - - diff --git a/src/components/custom/MoneyInput.vue b/src/components/custom/MoneyInput.vue deleted file mode 100644 index 343a802..0000000 --- a/src/components/custom/MoneyInput.vue +++ /dev/null @@ -1,131 +0,0 @@ - - - - - diff --git a/src/components/custom/OrderItemRow.vue b/src/components/custom/OrderItemRow.vue deleted file mode 100644 index 13d1c90..0000000 --- a/src/components/custom/OrderItemRow.vue +++ /dev/null @@ -1,338 +0,0 @@ - - - - - diff --git a/src/components/custom/VehicleSelectorDialog.vue b/src/components/custom/VehicleSelectorDialog.vue deleted file mode 100644 index 9d40777..0000000 --- a/src/components/custom/VehicleSelectorDialog.vue +++ /dev/null @@ -1,205 +0,0 @@ - - - - - diff --git a/src/components/custom/index.ts b/src/components/custom/index.ts deleted file mode 100644 index b8a1c01..0000000 --- a/src/components/custom/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as VehicleSelectorDialog } from './VehicleSelectorDialog.vue' -export { default as CustomerSelectorDialog } from './CustomerSelectorDialog.vue' -export { default as MoneyInput } from './MoneyInput.vue' -export { default as OrderItemRow } from './OrderItemRow.vue' diff --git a/src/composables/usePdfPreview.ts b/src/composables/usePdfPreview.ts new file mode 100644 index 0000000..1da68d4 --- /dev/null +++ b/src/composables/usePdfPreview.ts @@ -0,0 +1,81 @@ +import { ref, reactive } from 'vue' + +export const usePdfPreview = () => { + const state = reactive({ visible: false, url: '', name: '', blobUrl: '' }) + const frameRef = ref(null) + + const open = (url: string, name = '') => { + if (!url) return + state.url = url + state.name = name + state.visible = true + } + + const openWithResponse = (res: any) => { + const url = res?.data?.file_url || res?.file_url + const name = res?.data?.file_name || res?.file_name || '' + open(url, name) + } + + const inferFilenameFromUrl = (url: string): string => { + try { + const u = new URL(url) + const parts = u.pathname.split('/') + return parts[parts.length - 1] || 'document.pdf' + } catch (e) { + const parts = (url || '').split('?')[0].split('/') + return parts[parts.length - 1] || 'document.pdf' + } + } + + const download = async () => { + if (!state.url) return + const filename = state.name || inferFilenameFromUrl(state.url) + try { + const resp = await fetch(state.url, { credentials: 'include' }) + const blob = await resp.blob() + const url = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = filename + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(url) + } catch (err) { + window.open(state.url, '_blank') + } + } + + // 预览时也用 blob,提升兼容性 + const preparePreview = async () => { + if (!state.url) return + try { + const resp = await fetch(state.url, { credentials: 'include' }) + const blob = await resp.blob() + state.blobUrl && window.URL.revokeObjectURL(state.blobUrl) + state.blobUrl = window.URL.createObjectURL(blob) + } catch (e) { + state.blobUrl = '' + } + } + + const print = () => { + const frame = frameRef.value + if (frame && frame.contentWindow) { + frame.contentWindow.focus() + frame.contentWindow.print() + return + } + if (state.url) { + const win = window.open(state.url, '_blank') + if (win) { + win.addEventListener('load', () => win.print()) + } + } + } + + return { pdf: state, frameRef, open, openWithResponse, download, print, preparePreview } +} + + diff --git a/src/composables/useStockingForm.ts b/src/composables/useStockingForm.ts new file mode 100644 index 0000000..e28ac64 --- /dev/null +++ b/src/composables/useStockingForm.ts @@ -0,0 +1,862 @@ +import { ref, reactive, watch, onMounted } from 'vue' +import { ElMessage } from 'element-plus' +import { CrmService } from '@/api/crmApi' +import { commonService } from '@/api/commonApi' +import { vehicleService } from '@/api/vehicleApi' +import { useUploadHeaders } from '@/composables/useUploadHeaders' + +// 类型定义 +export interface BaseForm { + pi_no: string + biz_date: string + clearance_country_id: string + clearance_country_snapshot: string + customer_source_id: string + customer_source_snapshot: string + customer_id: string + customer_snapshot: string + customer_country_id: string + customer_country_snapshot: string + declarant_name: string + eta_ship_date: string + shipping_method_id: string + shipping_method_snapshot: string + domestic_port_id: string + domestic_port_snapshot: string + trade_term_id: string + trade_term_snapshot: string + clearance_port_id: string + clearance_port_snapshot: string + latest_arrival_date: string + contract_exchange_rate: string + contract_exchange_rate_currency: string + remark: string + total_cif: number | undefined + total_cif_currency: string + total_cif_currency_snapshot: string + is_final: string + object_id: string | number + contract: string + contract_url?: string +} + +export interface ItemRow { + sku_id: string + vin?: string + mileage?: number + listing_price?: number | undefined + listing_price_currency: string + listing_price_currency_snapshot: string + reduction_price?: number | undefined + reduction_price_currency: string + reduction_price_currency_snapshot: string + model_snapshot: string + color_interior_code: string + color_interior_snapshot: string + color_exterior_code: string + color_exterior_snapshot: string + unit_price: number | undefined + handling_fee: string + fob_transport_fee: number | undefined + port_misc_fee: string + retrofit_fee: string + total_price: number | undefined + official_price: number | undefined + discount_price: number | undefined + fob_fee?: number | undefined + fob_fee_currency: string + fob_fee_currency_snapshot: string + helper_fee?: number | undefined + helper_fee_currency: string + helper_fee_currency_snapshot: string + domestic_freight?: number | undefined + domestic_freight_currency: string + domestic_freight_currency_snapshot: string + port_fee?: number | undefined + port_fee_currency: string + port_fee_currency_snapshot: string + unit_price_currency: string + unit_price_currency_snapshot: string + total_price_currency: string + total_price_currency_snapshot: string + guide_price_currency: string + guide_price_currency_snapshot: string + discount?: number | undefined + discount_currency: string + discount_currency_snapshot: string + contract_rate?: number | undefined + profit: number | undefined + profit_type_snapshot?: string + modify_ids: string[] + modify_list: Array<{ option_id: string; option_name_snapshot: string }> + modify_fee_currency: string + modify_fee_currency_snapshot: string +} + +export interface PaymentItem { + remit_country_id: string + remit_country_name_snapshot: string + remit_date: string + currency_code: string + currency_name_snapshot: string + account_id: string + account_name_snapshot: string + remitter_name: string + amount: number | undefined +} + +export interface DialogState { + visible: boolean + search: { keyword: string } + loading: boolean + tableData: any[] + pagination: { currentPage: number; pageSize: number; total: number } + targetIndex?: number +} + +export function useStockingForm(isFinal = false, stockingId?: string | number) { + const { uploadHeaders } = useUploadHeaders() + const actionImageUplode = `${import.meta.env.VITE_API_URL}/ticket/upload?scene=stock_orders` + + // 选项数据 + const channelOptions = ref() + const countryOptions = ref() + const modificationOptions = ref() + const currencyOptions = ref() + const accountOptions = ref() + const customerOptions = ref() + const portOptions = ref() + const portOptionsall = ref() + const wcolorOptions = ref>([]) + const ncolorOptions = ref>([]) + const transportOptions = ref>([]) + const trade_termsOptions = ref>([]) + + // 表单数据 + const baseForm = reactive({ + pi_no: '', + biz_date: '', + clearance_country_id: '', + clearance_country_snapshot: '', + customer_source_id: '', + customer_source_snapshot: '', + customer_id: '', + customer_snapshot: '', + customer_country_id: '', + customer_country_snapshot: '', + declarant_name: '', + eta_ship_date: '', + shipping_method_id: '', + shipping_method_snapshot: '', + domestic_port_id: '', + domestic_port_snapshot: '', + trade_term_id: '', + trade_term_snapshot: '', + clearance_port_id: '', + clearance_port_snapshot: '', + latest_arrival_date: '', + contract_exchange_rate: '', + contract_exchange_rate_currency: 'USD', + remark: '', + total_cif: undefined, + total_cif_currency: 'USD', + total_cif_currency_snapshot: '', + is_final: isFinal ? '1' : '0', + object_id: stockingId || '0', + contract: '', + contract_url: '' + }) + + // 创建新的订单项 + const newItem = (): ItemRow => ({ + sku_id: '', + model_snapshot: '', + mileage: 0, + listing_price: undefined, + listing_price_currency: 'USD', + listing_price_currency_snapshot: '', + reduction_price: undefined, + reduction_price_currency: 'USD', + reduction_price_currency_snapshot: '', + color_interior_code: '', + color_interior_snapshot: '', + color_exterior_code: '', + color_exterior_snapshot: '', + unit_price: undefined, + handling_fee: '', + fob_transport_fee: undefined, + port_misc_fee: '', + retrofit_fee: '', + total_price: undefined, + official_price: undefined, + discount_price: undefined, + fob_fee: undefined, + fob_fee_currency: 'USD', + fob_fee_currency_snapshot: '', + helper_fee: undefined, + helper_fee_currency: 'USD', + helper_fee_currency_snapshot: '', + domestic_freight: undefined, + domestic_freight_currency: 'USD', + domestic_freight_currency_snapshot: '', + port_fee: undefined, + port_fee_currency: 'USD', + port_fee_currency_snapshot: '', + unit_price_currency: 'USD', + unit_price_currency_snapshot: '', + total_price_currency: 'USD', + total_price_currency_snapshot: '', + guide_price_currency: 'USD', + guide_price_currency_snapshot: '', + discount: undefined, + discount_currency: 'USD', + discount_currency_snapshot: '', + contract_rate: 7.16, + profit: undefined, + modify_ids: [], + modify_list: [], + modify_fee_currency: 'USD', + modify_fee_currency_snapshot: '' + }) + + // 创建新的收款项 + const newPaymentItem = (): PaymentItem => ({ + remit_country_id: '', + remit_country_name_snapshot: '', + remit_date: '', + currency_code: '', + currency_name_snapshot: '', + account_id: '', + account_name_snapshot: '', + remitter_name: '', + amount: undefined + }) + + const items = ref([newItem()]) + const paymentItems = ref([newPaymentItem()]) + const contractFile = ref<{ name: string; url: string; file_name: string } | null>(null) + + // 弹框状态 + const customerDialog = reactive({ + visible: false, + search: { keyword: '' }, + loading: false, + tableData: [], + pagination: { currentPage: 1, pageSize: 10, total: 0 } + }) + + const vehicleDialog = reactive({ + visible: false, + search: { keyword: '' }, + loading: false, + tableData: [], + pagination: { currentPage: 1, pageSize: 10, total: 0 }, + targetIndex: -1 + }) + + // 数据加载方法 + const getgetChannel = async () => { + const res = await CrmService.getChannel() + channelOptions.value = res || [] + } + + const getcustomer = async () => { + const res = await commonService.getCustomerSelect() + customerOptions.value = res || [] + } + + const getarea = async () => { + const res = await CrmService.getCountry() + countryOptions.value = res || [] + } + + const getmodification = async () => { + const res = await commonService.getModificationSelect() + modificationOptions.value = res || [] + } + + const loadColorOptions = async () => { + try { + const res = await commonService.getColorSelect({ type: 1 }) + wcolorOptions.value = res + const response = await commonService.getColorSelect({ type: 2 }) + ncolorOptions.value = response + } catch (error) { + console.error('获取颜色列表失败:', error) + ElMessage.error('获取颜色列表失败') + } + } + + const loadAccountOptions = async () => { + try { + const res = await commonService.getAccountSelect() + accountOptions.value = res + } catch (error) { + console.error('获取账户列表失败:', error) + ElMessage.error('获取账户列表失败') + } + } + + const loadCurrencyOptions = async () => { + try { + const res = await commonService.getCurrencySelect() + currencyOptions.value = res + } catch (error) { + console.error('获取币种列表失败:', error) + ElMessage.error('获取币种列表失败') + } + } + + const loadPortOptions = async () => { + try { + const res = await commonService.getPortSelect({ scene: 'domestic' }) + const resall = await commonService.getPortSelect({}) + portOptions.value = res + portOptionsall.value = resall + } catch (error) { + console.error('获取港口列表失败:', error) + ElMessage.error('获取港口列表失败') + } + } + + const loadDictOptions = async () => { + const response = await commonService.getDictSelect({ scope: '2,15' }) + transportOptions.value = response.transport + trade_termsOptions.value = response.trade_terms + } + + // 币种相关方法 + const getCurrencyRate = (code: string): string => { + try { + const found = (currencyOptions?.value || []).find((c: any) => c.currency_code === code) + return found?.rate || found?.exchange_rate || '' + } catch (e) { + return '' + } + } + + const getCurrencyBounds = (code: string): { min: number; max: number; base: number } => { + const found: any = (currencyOptions?.value || []).find((c: any) => c.currency_code === code) || {} + const base = Number(found?.rate || found?.exchange_rate || 0) || 0 + const up = Number(found?.float_percent_up) + const down = Number(found?.float_percent_down) + if (!Number.isNaN(up) || !Number.isNaN(down)) { + const upPct = (Number.isNaN(up) ? 0 : up) / 100 + const downPct = (Number.isNaN(down) ? 0 : down) / 100 + return { min: base * (1 - downPct), max: base * (1 + upPct), base } + } + return { min: base * 0.9, max: base * 1.1, base } + } + + const validateContractRate = () => { + const code = baseForm.contract_exchange_rate_currency || items.value[0]?.unit_price_currency || 'USD' + const { min, max } = getCurrencyBounds(code) + const val = Number(baseForm.contract_exchange_rate) + if (Number.isNaN(val)) return + if (val < min) { + baseForm.contract_exchange_rate = String(Number(min.toFixed(6))) + ElMessage?.warning?.('合同汇率已按币种浮动下限自动纠正') + } else if (val > max) { + baseForm.contract_exchange_rate = String(Number(max.toFixed(6))) + ElMessage?.warning?.('合同汇率已按币种浮动上限自动纠正') + } + } + + // 金额包装方法 + const wrapMoney = (amount: any, currency: string, remark: string) => ({ + currency: currency || '', + amount: amount === undefined || amount === null ? '' : String(amount), + rate: getCurrencyRate(currency), + remark + }) + + // 利润计算方法 + const getRateNumber = (code: string): number => { + const r = Number(getCurrencyRate(code)) + return Number.isNaN(r) ? 0 : r + } + + const toUSD = (amount: any, code?: string): number => { + const val = amount === undefined || amount === null || amount === '' ? 0 : Number(amount) + if (Number.isNaN(val)) return 0 + const cur = code || 'USD' + const rateCur = getRateNumber(cur) + const rateUSD = getRateNumber('USD') || 1 + if (!rateCur) return 0 + return (val * rateCur) / rateUSD + } + + const calcProfit = (it: any): number => { + const unit = toUSD(it.unit_price, it.unit_price_currency) + const retrofit = toUSD(it.retrofit_fee, (it as any).modify_fee_currency) + const fob = toUSD(it.fob_transport_fee ?? (it as any).fob_fee, (it as any).fob_fee_currency) + const handling = toUSD(it.handling_fee ?? (it as any).helper_fee, (it as any).helper_fee_currency) + const port = toUSD(it.port_misc_fee ?? (it as any).port_fee, (it as any).port_fee_currency) + + const mileageVal = Number(it.mileage ?? 0) + if (mileageVal > 0) { + // 二手车:用挂牌价与二手减价替代官方指导价与优惠 + const listing = toUSD(it.listing_price ?? (it as any).listing_price, (it as any).listing_price_currency) + const reduction = toUSD(it.reduction_price ?? (it as any).reduction_price, (it as any).reduction_price_currency) + return unit - (retrofit + fob + listing + reduction + handling + port) + } + + // 新车/里程<=0:保持原有计算(官方指导价与优惠) + const guide = toUSD(it.official_price ?? (it as any).guide_price, (it as any).guide_price_currency) + const discount = toUSD(it.discount_price ?? (it as any).discount, (it as any).discount_currency) + return unit - (retrofit + fob + guide + discount + handling + port) + } + + // 合同文件相关方法 + const isImageFile = (file: { name: string; url: string; file_name: string }) => { + return /\.(jpg|jpeg|png|gif)$/i.test(file.name) || file.file_name.startsWith('image/') + } + + const beforeContractUpload = (file: File) => { + const isValidType = /\.(jpg|jpeg|png|gif|pdf|doc|docx|xls|xlsx|txt)$/i.test(file.name) + if (!isValidType) { + ElMessage.error('只能上传图片或文档文件!') + return false + } + const isLt10M = file.size / 1024 / 1024 < 10 + if (!isLt10M) { + ElMessage.error('文件大小不能超过 10MB!') + return false + } + return true + } + + const handleContractUpload = (response: any) => { + if (response.code === 200) { + const fileInfo = { + name: response.data.original_name || '合同文件', + url: response.data.file_url, + file_name: response.data.file_name + } + contractFile.value = fileInfo + baseForm.contract = response.data.file_name + ElMessage.success('合同上传成功!') + } else { + ElMessage.error(response.message || '合同上传失败!') + } + } + + const removeContractFile = () => { + contractFile.value = null + baseForm.contract = '' + ElMessage.success('合同文件已删除') + } + + // 订单项操作方法 + const copyRow = (idx: number) => { + const copied = JSON.parse(JSON.stringify(items.value[idx])) as ItemRow + items.value.splice(idx + 1, 0, copied) + } + + const addRowAfter = (idx: number) => { + items.value.splice(idx + 1, 0, newItem()) + } + + const removeRow = (idx: number) => { + items.value.splice(idx, 1) + } + + // 收款项操作方法 + const addPaymentItem = () => { + paymentItems.value.push(newPaymentItem()) + } + + const removePaymentItem = (idx: number) => { + if (paymentItems.value.length > 1) { + paymentItems.value.splice(idx, 1) + } + } + + // 客户选择相关方法 + const openCustomerDialog = () => { + customerDialog.visible = true + customerDialog.pagination.currentPage = 1 + getCustomerList() + } + + const getCustomerList = async () => { + customerDialog.loading = true + try { + const { currentPage, pageSize } = customerDialog.pagination + const params = { current: currentPage, size: pageSize, keyword: customerDialog.search.keyword } + const { data, current, size, total } = await CrmService.getcustomerList(params as any) + customerDialog.tableData = data || [] + Object.assign(customerDialog.pagination, { currentPage: current, pageSize: size, total }) + } catch (e) { + } finally { + customerDialog.loading = false + } + } + + const handleCustomerSizeChange = (size: number) => { + customerDialog.pagination.pageSize = size + getCustomerList() + } + + const handleCustomerCurrentChange = (page: number) => { + customerDialog.pagination.currentPage = page + getCustomerList() + } + + const chooseCustomer = (row: any) => { + baseForm.customer_id = row.id || '' + baseForm.customer_snapshot = row.contacts || row.name || '' + customerDialog.visible = false + } + + // 车辆选择相关方法 + const openVehicleDialog = (index: number) => { + vehicleDialog.targetIndex = index + vehicleDialog.visible = true + vehicleDialog.pagination.currentPage = 1 + getVehicleList() + } + + const getVehicleList = async () => { + vehicleDialog.loading = true + try { + const { currentPage, pageSize } = vehicleDialog.pagination + const params = { current: currentPage, size: pageSize, keyword: vehicleDialog.search.keyword } + const { records, current, size, total } = await vehicleService.getVehicleList(params) + vehicleDialog.tableData = records || [] + Object.assign(vehicleDialog.pagination, { currentPage: current, pageSize: size, total }) + } catch { + ElMessage.error('获取车辆列表失败') + } finally { + vehicleDialog.loading = false + } + } + + const handleVehicleSizeChange = (size: number) => { + vehicleDialog.pagination.pageSize = size + getVehicleList() + } + + const handleVehicleCurrentChange = (page: number) => { + vehicleDialog.pagination.currentPage = page + getVehicleList() + } + + const chooseVehicle = (row: any) => { + const idx = vehicleDialog.targetIndex + if (idx !== undefined && idx >= 0 && idx < items.value.length) { + const target = items.value[idx] + target.sku_id = row.id || '' + target.model_snapshot = row.model_name || row.name || '' + target.color_exterior_code = row.color_exterior_code || '' + target.color_exterior_snapshot = row.color_exterior_text || '' + target.color_interior_code = row.color_interior_code || '' + target.color_interior_snapshot = row.color_interior_text || '' + target.official_price = row.msrp ?? target.official_price + if (row.currency) { + target.guide_price_currency = row.currency + } + } + vehicleDialog.visible = false + } + + // 数据回显方法 + const populateFromDetail = (res: any) => { + if (!res) return + + // 基础信息 + baseForm.pi_no = res.pi_no || '' + baseForm.contract = res.contract || '' + baseForm.biz_date = res.biz_date || '' + baseForm.clearance_country_id = res.clearance_country_id || '' + baseForm.clearance_country_snapshot = res.clearance_country_snapshot || '' + baseForm.customer_source_id = res.customer_source_id || '' + baseForm.customer_source_snapshot = res.customer_source_snapshot || '' + baseForm.customer_id = res.customer_id || '' + baseForm.customer_snapshot = res.customer_snapshot || '' + baseForm.customer_country_id = res.customer_country_id || '' + baseForm.customer_country_snapshot = res.customer_country_snapshot || '' + baseForm.declarant_name = res.declarant_name || '' + baseForm.eta_ship_date = res.eta_ship_date || '' + baseForm.shipping_method_id = res.shipping_method_id || '' + baseForm.shipping_method_snapshot = res.shipping_method_snapshot || '' + baseForm.domestic_port_id = res.domestic_port_id || '' + baseForm.domestic_port_snapshot = res.domestic_port_snapshot || '' + baseForm.trade_term_id = res.trade_term_id || '' + baseForm.trade_term_snapshot = res.trade_term_snapshot || '' + baseForm.clearance_port_id = res.clearance_port_id || '' + baseForm.clearance_port_snapshot = res.clearance_port_snapshot || '' + baseForm.latest_arrival_date = res.latest_arrival_date || '' + baseForm.remark = res.remark || '' + + // 合计与汇率 + baseForm.total_cif = res.cif_total_amount ? Number(res.cif_total_amount) : undefined + baseForm.total_cif_currency = res.settlement_currency || baseForm.total_cif_currency + baseForm.contract_exchange_rate = res.contract_exchange_rate || getCurrencyRate(baseForm.total_cif_currency) + + // 明细 + const list = Array.isArray(res.veh_list) ? res.veh_list : [] + items.value = list.map((it: any) => ({ + sku_id: it.sku_id ? String(it.sku_id) : '', + vin: it.vin || '', + mileage: it.mileage ?? 0, + model_snapshot: it.model_snapshot || '', + listing_price: it.listing_price?.amount ? Number(it.listing_price.amount) : undefined, + listing_price_currency: it.listing_price?.currency || 'USD', + listing_price_currency_snapshot: '', + reduction_price: it.reduction_price?.amount ? Number(it.reduction_price.amount) : undefined, + reduction_price_currency: it.reduction_price?.currency || 'USD', + reduction_price_currency_snapshot: '', + color_interior_code: it.color_interior_code || '', + color_interior_snapshot: it.color_interior_snapshot || '', + color_exterior_code: it.color_exterior_code || '', + color_exterior_snapshot: it.color_exterior_snapshot || '', + unit_price: it.unit_price?.amount ? Number(it.unit_price.amount) : undefined, + handling_fee: it.handling_fee?.amount ?? '', + fob_transport_fee: it.fob_transport_fee?.amount ? Number(it.fob_transport_fee.amount) : undefined, + port_misc_fee: it.port_misc_fee?.amount ?? '', + retrofit_fee: it.retrofit_fee?.amount ?? '', + total_price: it.total_price?.amount ? Number(it.total_price.amount) : undefined, + official_price: it.official_price?.amount ? Number(it.official_price.amount) : undefined, + discount_price: it.discount_price?.amount ? Number(it.discount_price.amount) : undefined, + fob_fee: undefined, + fob_fee_currency: it.fob_transport_fee?.currency || 'USD', + fob_fee_currency_snapshot: '', + helper_fee: undefined, + helper_fee_currency: it.handling_fee?.currency || 'USD', + helper_fee_currency_snapshot: '', + domestic_freight: undefined, + domestic_freight_currency: it.domestic_freight?.currency || 'USD', + domestic_freight_currency_snapshot: '', + port_fee: undefined, + port_fee_currency: it.port_misc_fee?.currency || 'USD', + port_fee_currency_snapshot: '', + unit_price_currency: it.unit_price?.currency || 'USD', + unit_price_currency_snapshot: '', + total_price_currency: it.total_price?.currency || 'USD', + total_price_currency_snapshot: '', + guide_price_currency: it.official_price?.currency || 'USD', + guide_price_currency_snapshot: '', + discount: undefined, + discount_currency: it.discount_price?.currency || 'USD', + discount_currency_snapshot: '', + contract_rate: baseForm.contract_exchange_rate ? Number(baseForm.contract_exchange_rate) : undefined, + profit: undefined, + profit_type_snapshot: '', + modify_ids: Array.isArray(it.modify_list) ? it.modify_list.map((m: any) => Number(m.option_id)) : [], + modify_list: Array.isArray(it.modify_list) + ? it.modify_list.map((m: any) => ({ option_id: String(m.option_id), option_name_snapshot: m.option_name_snapshot })) + : [], + modify_fee_currency: it.retrofit_fee?.currency || 'USD', + modify_fee_currency_snapshot: '' + })) + // 计划收款 + const pays = Array.isArray(res.account_list) ? res.account_list : [] + paymentItems.value = pays.map((p: any) => ({ + remit_country_id: p.remit_country_id ? Number(p.remit_country_id) : '', + remit_country_name_snapshot: p.remit_country_name_snapshot || '', + remit_date: p.remit_date || '', + currency_code: p.currency_code || '', + currency_name_snapshot: p.currency_name_snapshot || '', + account_id: p.account_id ? String(p.account_id) : '', + account_name_snapshot: p.account_name_snapshot || '', + remitter_name: p.remitter_name || '', + amount: p.amount ? Number(p.amount) : undefined + })) + // 合同文件回显 + if (res.contract) { + contractFile.value = { + name: '合同文件', + url: res.contract_url || res.contract, + file_name: res.contract + } + } + } + + // 提交方法 + const handleSubmit = async () => { + const mappedItems = items.value.map((it) => ({ + sku_id: it.sku_id, + vin: it.vin || '', + mileage: it.mileage, + model_snapshot: it.model_snapshot, + color_interior_code: it.color_interior_code, + color_interior_snapshot: it.color_interior_snapshot, + color_exterior_code: it.color_exterior_code, + color_exterior_snapshot: it.color_exterior_snapshot, + unit_price: wrapMoney(it.unit_price, (it as any).unit_price_currency, '单价'), + handling_fee: wrapMoney(it.handling_fee, (it as any).helper_fee_currency, '手续费'), + fob_transport_fee: wrapMoney( + it.fob_transport_fee ?? (it as any).fob_fee, + (it as any).fob_fee_currency, + 'FOB运输费' + ), + port_misc_fee: wrapMoney( + it.port_misc_fee ?? (it as any).port_fee, + (it as any).port_fee_currency, + '港杂费' + ), + retrofit_fee: wrapMoney((it as any).retrofit_fee, (it as any).modify_fee_currency, '改装费'), + modify_list: it.modify_list, + total_price: wrapMoney(it.total_price, (it as any).total_price_currency, '总价'), + official_price: wrapMoney( + (it as any).official_price ?? (it as any).guide_price, + (it as any).guide_price_currency, + '官方指导价' + ), + discount_price: wrapMoney( + (it as any).discount_price ?? (it as any).discount, + (it as any).discount_currency, + '优惠额' + ), + listing_price: wrapMoney(it.listing_price, (it as any).listing_price_currency, '挂牌价'), + reduction_price: wrapMoney(it.reduction_price, (it as any).reduction_price_currency, '二手减价') + })) + + const payload = { + ...baseForm, + veh_list: mappedItems, + account_list: paymentItems.value + } + + try { + const { message } = await CrmService.addStock(payload as any) + ElMessage.success(message || '保存成功') + setTimeout(() => { + window.location.href = '/crmpc/stocking/stocking_list' + }, 1000) + } catch (e) { + ElMessage.error('保存失败') + } + } + + // 监听器 + watch( + () => baseForm.contract_exchange_rate_currency, + (newCurrency) => { + if (newCurrency) { + baseForm.contract_exchange_rate = getCurrencyRate(newCurrency) + validateContractRate() + } + } + ) + + watch( + () => baseForm.contract_exchange_rate, + (newCurrency) => { + if (newCurrency) { + validateContractRate() + } + } + ) + + watch( + items, + (val) => { + val.forEach((it: any, index: number) => { + const profit = calcProfit(it) + if (items.value[index]) items.value[index].profit = isNaN(profit) ? 0 : Number(profit.toFixed(2)) + }) + + // 合计到岸价 = 所有单价相加 + const sum = (val || []).reduce((acc: number, cur: any) => { + const n = cur?.unit_price === undefined || cur?.unit_price === null || cur?.unit_price === '' ? 0 : Number(cur.unit_price) + return acc + (isNaN(n) ? 0 : n) + }, 0) + baseForm.total_cif = Number(sum.toFixed(2)) + + // 同步合计币种为第一行单价币种(若有) + if (items.value.length > 0) { + const c = items.value[0]?.unit_price_currency + if (c) baseForm.total_cif_currency = c + } + }, + { deep: true } + ) + + // 初始化方法 + const initializeForm = async () => { + await Promise.all([ + getcustomer(), + loadCurrencyOptions(), + getgetChannel(), + getarea(), + getmodification(), + loadColorOptions(), + loadAccountOptions(), + loadPortOptions(), + loadDictOptions() + ]) + + // 设置默认汇率 + baseForm.contract_exchange_rate = getCurrencyRate('USD') + } + + return { + // 数据 + baseForm, + items, + paymentItems, + contractFile, + customerDialog, + vehicleDialog, + + // 选项数据 + channelOptions, + countryOptions, + modificationOptions, + currencyOptions, + accountOptions, + customerOptions, + portOptions, + portOptionsall, + wcolorOptions, + ncolorOptions, + transportOptions, + trade_termsOptions, + + // 上传相关 + actionImageUplode, + uploadHeaders, + + // 方法 + initializeForm, + populateFromDetail, + handleSubmit, + getCurrencyRate, + validateContractRate, + wrapMoney, + calcProfit, + + // 合同文件方法 + isImageFile, + beforeContractUpload, + handleContractUpload, + removeContractFile, + + // 订单项方法 + copyRow, + addRowAfter, + removeRow, + newItem, + + // 收款项方法 + addPaymentItem, + removePaymentItem, + newPaymentItem, + + // 客户选择方法 + openCustomerDialog, + getCustomerList, + handleCustomerSizeChange, + handleCustomerCurrentChange, + chooseCustomer, + + // 车辆选择方法 + openVehicleDialog, + getVehicleList, + handleVehicleSizeChange, + handleVehicleCurrentChange, + chooseVehicle + } +} diff --git a/src/router/routes/asyncRoutes.ts b/src/router/routes/asyncRoutes.ts index 100633b..086015c 100644 --- a/src/router/routes/asyncRoutes.ts +++ b/src/router/routes/asyncRoutes.ts @@ -67,5 +67,71 @@ export const asyncRoutes: AppRouteRecord[] = [ title: '回收站', isHide: true } + }, + // 备货单相关路由 + { + path: '/crmpc/stocking/stocking_list', + name: 'StockingList', + component: 'crmpc/stocking/stocking_list', + meta: { + title: '备货单列表', + isHide: true + } + }, + { + path: '/crmpc/stocking/Add', + name: 'StockingAdd', + component: 'crmpc/stocking/Add', + meta: { + title: '创建备货单', + isHide: true + } + }, + { + path: '/crmpc/stocking/show', + name: 'StockingShow', + component: 'crmpc/stocking/show', + meta: { + title: '备货单详情', + isHide: true + } + }, + { + path: '/crmpc/stocking/final_add', + name: 'StockingFinalAdd', + component: 'crmpc/stocking/final_add', + meta: { + title: '最终备货单', + isHide: true + } + }, + { + path: '/crmpc/stocking/final_show', + name: 'StockingFinalShow', + component: 'crmpc/stocking/final_show', + meta: { + title: '最终备货单详情', + isHide: true + } + }, + //审批详情 + { + path: '/crmpc/stocking/stocking_auth_detil', + name: 'StockingAuth', + component: 'crmpc/stocking/stocking_auth_detil', + meta: { + title: '最终备货单详情', + isHide: true + } + }, + // 审批流程添加 + { + path: '/admin/approval/approval_add', + name: 'approvalAdd', + component: 'admin/approval/approval_add', + meta: { + title: '审批流程添加', + isHide: true + } } ] diff --git a/src/router/routes/staticRoutes.ts b/src/router/routes/staticRoutes.ts index 7a011c4..5525e3f 100644 --- a/src/router/routes/staticRoutes.ts +++ b/src/router/routes/staticRoutes.ts @@ -41,6 +41,12 @@ export const staticRoutes: AppRouteRecordRaw[] = [ component: () => import('@/views/auth/feishu-callback.vue'), meta: { title: '飞书回调', noLogin: true, isHideTab: true } }, + { + path: '/auth/signature/mobile', + name: 'SignatureMobile', + component: () => import('@views/auth/signature/mobile.vue'), + meta: { title: '电子签名', isHideTab: true, noLogin: true, setTheme: true } + }, { path: '/exception', component: Home, diff --git a/src/router/routesAlias.ts b/src/router/routesAlias.ts index 3a15dce..7e8ceb7 100644 --- a/src/router/routesAlias.ts +++ b/src/router/routesAlias.ts @@ -62,7 +62,13 @@ export enum RoutesAlias { UserCenter = '/system/user-center', // 用户中心 inquirySystem = '/admin/inquiry_system', // 询单系统 customer = '/admin/customer', // 管理端客户列表 - customeraudit = '/admin/customer/customeraudit' // 管理端待审客户列表 + customeraudit = '/admin/customer/customeraudit', // 管理端待审客户列表 + // 备货单相关路由 + StockingList = '/crmpc/stocking/stocking_list', // 备货单列表 + StockingAdd = '/crmpc/stocking/Add', // 创建备货单 + StockingShow = '/crmpc/stocking/show', // 备货单详情 + StockingFinalAdd = '/crmpc/stocking/final_add', // 最终备货单添加 + StockingFinalShow = '/crmpc/stocking/final_show' // 最终备货单详情 } // 主页路由 diff --git a/src/views/admin/approval/approval_add.vue b/src/views/admin/approval/approval_add.vue new file mode 100644 index 0000000..f8bb745 --- /dev/null +++ b/src/views/admin/approval/approval_add.vue @@ -0,0 +1,1278 @@ + + + + + diff --git a/src/views/admin/approval/approval_edit.vue b/src/views/admin/approval/approval_edit.vue new file mode 100644 index 0000000..d898f3d --- /dev/null +++ b/src/views/admin/approval/approval_edit.vue @@ -0,0 +1,1373 @@ + + + + + diff --git a/src/views/admin/approval/approval_list.vue b/src/views/admin/approval/approval_list.vue new file mode 100644 index 0000000..479993a --- /dev/null +++ b/src/views/admin/approval/approval_list.vue @@ -0,0 +1,283 @@ + + + + + diff --git a/src/views/admin/system/dictionary/index.vue b/src/views/admin/system/dictionary/index.vue index 7d9dbb8..f5e1f0a 100644 --- a/src/views/admin/system/dictionary/index.vue +++ b/src/views/admin/system/dictionary/index.vue @@ -349,7 +349,8 @@ dict_code: typeForm.dict_code, dict_name: typeForm.dict_name, description: typeForm.description, - status: typeForm.status + status: typeForm.status, + category_id: typeForm.category_id }) ElMessage.success(message) } else { @@ -358,6 +359,7 @@ dict_code: typeForm.dict_code, dict_name: typeForm.dict_name, description: typeForm.description, + category_id: typeForm.category_id, status: typeForm.status }) ElMessage.success(message) diff --git a/src/views/admin/user/user_info.vue b/src/views/admin/user/user_info.vue index a66e992..4b2e28a 100644 --- a/src/views/admin/user/user_info.vue +++ b/src/views/admin/user/user_info.vue @@ -116,6 +116,19 @@ + + + + 签名 + +