|
|
@@ -76,7 +76,12 @@
|
|
|
style="width: 100%"
|
|
|
@change="handleOwnerChange"
|
|
|
>
|
|
|
- <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
|
|
|
+ <el-option
|
|
|
+ v-for="user in ownerUserList"
|
|
|
+ :key="user.userId"
|
|
|
+ :label="user.nickName"
|
|
|
+ :value="user.userId"
|
|
|
+ />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<!-- 第三行:运费、询价/报价日期、有效期(天) -->
|
|
|
@@ -139,47 +144,69 @@
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="别名" min-width="100">
|
|
|
+ <el-table-column label="别名" min-width="140">
|
|
|
<template #default="scope">
|
|
|
- <el-select
|
|
|
- v-if="(scope.row.aliasOptions || []).length"
|
|
|
- v-model="scope.row.aliasName"
|
|
|
- placeholder="不选则用产品名称"
|
|
|
- clearable
|
|
|
- filterable
|
|
|
- size="small"
|
|
|
- style="width: 100%"
|
|
|
- >
|
|
|
- <el-option
|
|
|
- v-for="name in scope.row.aliasOptions"
|
|
|
- :key="name"
|
|
|
- :label="name"
|
|
|
- :value="name"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
- <span v-else class="text-muted">—</span>
|
|
|
+ <div class="line-meta-cell">
|
|
|
+ <el-select
|
|
|
+ v-if="(scope.row.aliasOptions || []).length"
|
|
|
+ v-model="scope.row.aliasName"
|
|
|
+ placeholder="不选则用产品名称"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ size="small"
|
|
|
+ class="line-meta-cell__control"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="name in scope.row.aliasOptions"
|
|
|
+ :key="name"
|
|
|
+ :label="name"
|
|
|
+ :value="name"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <span v-else class="text-muted line-meta-cell__empty">—</span>
|
|
|
+ <el-button
|
|
|
+ v-if="scope.row.productId"
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ class="line-meta-cell__btn"
|
|
|
+ @click="openAliasBrandManage(scope.row, 'alias')"
|
|
|
+ >
|
|
|
+ <el-icon><Setting /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="品牌" min-width="100">
|
|
|
+ <el-table-column label="品牌" min-width="140">
|
|
|
<template #default="scope">
|
|
|
- <el-select
|
|
|
- v-if="(scope.row.brandOptions || []).length"
|
|
|
- v-model="scope.row.brandId"
|
|
|
- placeholder="不限品牌"
|
|
|
- clearable
|
|
|
- filterable
|
|
|
- size="small"
|
|
|
- style="width: 100%"
|
|
|
- @change="(val) => handleBrandIdChange(scope.row, val)"
|
|
|
- >
|
|
|
- <el-option
|
|
|
- v-for="b in scope.row.brandOptions"
|
|
|
- :key="b.id"
|
|
|
- :label="b.brandName"
|
|
|
- :value="b.id"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
- <span v-else class="text-muted">—</span>
|
|
|
+ <div class="line-meta-cell">
|
|
|
+ <el-select
|
|
|
+ v-if="(scope.row.brandOptions || []).length"
|
|
|
+ v-model="scope.row.brandId"
|
|
|
+ placeholder="不限品牌"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ size="small"
|
|
|
+ class="line-meta-cell__control"
|
|
|
+ @change="(val) => handleBrandIdChange(scope.row, val)"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="b in scope.row.brandOptions"
|
|
|
+ :key="b.id"
|
|
|
+ :label="b.brandName"
|
|
|
+ :value="b.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <span v-else class="text-muted line-meta-cell__empty">—</span>
|
|
|
+ <el-button
|
|
|
+ v-if="scope.row.productId"
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ class="line-meta-cell__btn"
|
|
|
+ @click="openAliasBrandManage(scope.row, 'brand')"
|
|
|
+ >
|
|
|
+ <el-icon><Setting /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="型号/规格" prop="model" min-width="100" :show-overflow-tooltip="true">
|
|
|
@@ -421,6 +448,14 @@
|
|
|
@confirm="handleProductConfirm"
|
|
|
/>
|
|
|
|
|
|
+ <ProductAliasBrandDialog
|
|
|
+ v-model="aliasBrandManageVisible"
|
|
|
+ :product-id="aliasBrandManageProductId"
|
|
|
+ :product-name="aliasBrandManageProductName"
|
|
|
+ :mode="aliasBrandManageMode"
|
|
|
+ @saved="handleAliasBrandManaged"
|
|
|
+ />
|
|
|
+
|
|
|
<!-- 客户选择器 -->
|
|
|
<CustomerSelector :visible="customerSelectorVisible" @update:visible="customerSelectorVisible = $event" @confirm="handleCustomerConfirm" />
|
|
|
|
|
|
@@ -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<FormInstance>();
|
|
|
|
|
|
-// 用户列表(负责人 + 审批人候选)
|
|
|
+// 用户列表(审批人候选用全量;负责人仅方金强 / 吴九华)
|
|
|
const userList = ref<any[]>([]);
|
|
|
+const ownerUserList = computed(() => filterOwnerCandidates(userList.value));
|
|
|
|
|
|
// 供方信息列表
|
|
|
const supplierList = ref<SupplierInfoVO[]>([]);
|
|
|
@@ -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<string, unknown>)
|
|
|
- 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<string, string | undefined>
|
|
|
- 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<string, any>) {
|
|
|
+ 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;
|
|
|
+}
|
|
|
</style>
|