diff --git a/web-admin/src/views/crm/quote/components/QuoteFormPanel.vue b/web-admin/src/views/crm/quote/components/QuoteFormPanel.vue index 24726d010..593960370 100644 --- a/web-admin/src/views/crm/quote/components/QuoteFormPanel.vue +++ b/web-admin/src/views/crm/quote/components/QuoteFormPanel.vue @@ -76,7 +76,12 @@ style="width: 100%" @change="handleOwnerChange" > - + @@ -139,47 +144,69 @@ /> - + - - - - — + + + + + — + + + + - + - handleBrandIdChange(scope.row, val)" - > - - - — + + handleBrandIdChange(scope.row, val)" + > + + + — + + + + @@ -421,6 +448,14 @@ @confirm="handleProductConfirm" /> + + @@ -513,19 +548,23 @@ import { Loading, Plus, Search, + Setting, UploadFilled } from '@element-plus/icons-vue' import ImagePreview from '@/components/ImagePreview/index.vue'; -import ProductSelector from './ProductSelector.vue'; +import ProductSelector from '@/components/business/product-selector/index.vue'; +import ProductAliasBrandDialog from '@/components/business/product-alias-brand-dialog/index.vue' import CustomerSelector from '@/components/CustomerSelector.vue'; import ContactSelector from '@/components/ContactSelector.vue'; import OpportunitySelector from '@/components/OpportunitySelector.vue'; import { fetchUserOptions } from '@/api/admin'; import { fetchContactsByCustomerId } from '@/api/crm/contact'; import { listAllSupplierInfo, type SupplierInfoVO } from '@/api/crm/supplierInfo'; +import { filterOwnerCandidates, findDefaultOwnerUser } from '@/utils/crm-owner-candidates'; import { removeProductsNotAllowedByHazard } from '@/utils/crm-supplier-hazard' import { applyBrandSelectChange, + applyManagedProductMetaToLines, initLineBrandMetaFromProduct, prepareLineAliasAndBrandForSubmit, resolveAliasFromSavedName @@ -690,8 +729,9 @@ const submitRules = ref({ }); const submitFormRef = ref(); -// 用户列表(负责人 + 审批人候选) +// 用户列表(审批人候选用全量;负责人仅方金强 / 吴九华) const userList = ref([]); +const ownerUserList = computed(() => filterOwnerCandidates(userList.value)); // 供方信息列表 const supplierList = ref([]); @@ -1035,43 +1075,18 @@ const handleOpportunityConfirm = (opportunity: OpportunityVO) => { } }; -/** 打开用户选择器 */ +/** 负责人变化 */ const handleOwnerChange = (userId: string | number | undefined) => { - const user = userList.value.find((item) => String(item.userId) === String(userId)) + const user = ownerUserList.value.find((item) => String(item.userId) === String(userId)) form.value.ownerUserName = user?.nickName || '' } -/** 当前登录用户 ID(后端 current-user 返回 id,兼容历史 userId 字段) */ -const resolveCurrentUserId = (): string => { - const info = userStore.info || ({} as Api.Auth.UserInfo & Record) - const fromInfo = String(info.id || info.userId || '') - if (fromInfo) return fromInfo - const token = userStore.accessToken - if (!token || token.split('.').length < 2) return '' - try { - const payload = token.split('.')[1] - const b64 = payload.replace(/-/g, '+').replace(/_/g, '/') - const pad = '='.repeat((4 - (b64.length % 4)) % 4) - const obj = JSON.parse(atob(b64 + pad)) - return String(obj?.sub || '') - } catch { - return '' - } -} - -/** 新增时默认负责人为当前登录人 */ +/** 新增时默认负责人为方金强 */ const applyDefaultOwnerUser = () => { - const currentUserId = resolveCurrentUserId() - if (!currentUserId) return - const matched = userList.value.find((item) => String(item.userId) === currentUserId) - if (matched) { - form.value.ownerUserId = matched.userId as QuoteForm['ownerUserId'] - form.value.ownerUserName = matched.nickName || '' - return - } - form.value.ownerUserId = currentUserId as QuoteForm['ownerUserId'] - const info = userStore.info as Record - form.value.ownerUserName = info?.realName || info?.userName || info?.username || '' + const matched = findDefaultOwnerUser(ownerUserList.value) + if (!matched) return + form.value.ownerUserId = matched.userId as QuoteForm['ownerUserId'] + form.value.ownerUserName = matched.nickName || '' } const formatApproverOptionLabel = (user: { nickName?: string; userName?: string }) => { @@ -1266,6 +1281,29 @@ const handleBrandIdChange = (row: QuoteProductForm, val: string | undefined | nu applyBrandSelectChange(row, val, row.brandOptions || []) } +const aliasBrandManageVisible = ref(false) +const aliasBrandManageProductId = ref('') +const aliasBrandManageProductName = ref('') +const aliasBrandManageMode = ref<'alias' | 'brand'>('alias') + +function openAliasBrandManage(row: QuoteProductForm, mode: 'alias' | 'brand') { + if (!row.productId) return + aliasBrandManageProductId.value = String(row.productId) + aliasBrandManageProductName.value = String( + row.standardProductName || row.productName || '' + ) + aliasBrandManageMode.value = mode + aliasBrandManageVisible.value = true +} + +function handleAliasBrandManaged(product: Record) { + applyManagedProductMetaToLines( + form.value.products || [], + aliasBrandManageProductId.value, + product + ) +} + async function enrichQuoteProductBrandMeta(lines: QuoteProductForm[]) { await Promise.all( (lines || []).map(async (line) => { @@ -2266,4 +2304,23 @@ onMounted(async () => { background: transparent !important; border: none !important; } + +.line-meta-cell { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; +} +.line-meta-cell__control { + flex: 1; + min-width: 0; +} +.line-meta-cell__empty { + flex: 1; + min-width: 0; +} +.line-meta-cell__btn { + flex-shrink: 0; + padding: 4px; +}