bug处理优化页面
parent
3250a9da24
commit
b56d509cc9
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -22,6 +22,7 @@ public class CreateOpportunityRequest {
|
||||||
@Size(max = 100, message = "项目地不能超过100字符")
|
@Size(max = 100, message = "项目地不能超过100字符")
|
||||||
private String projectLocation;
|
private String projectLocation;
|
||||||
|
|
||||||
|
@NotBlank(message = "业绩归属地不能为空")
|
||||||
@Size(max = 100, message = "业绩归属地不能超过100字符")
|
@Size(max = 100, message = "业绩归属地不能超过100字符")
|
||||||
private String projectOwnershipLocation;
|
private String projectOwnershipLocation;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ public class OpportunityItemDTO {
|
||||||
private String owner;
|
private String owner;
|
||||||
private String createdAt;
|
private String createdAt;
|
||||||
private String updatedAt;
|
private String updatedAt;
|
||||||
|
private String archivedAt;
|
||||||
private String projectLocation;
|
private String projectLocation;
|
||||||
private String projectOwnershipLocation;
|
private String projectOwnershipLocation;
|
||||||
private String projectOwnershipLocationName;
|
private String projectOwnershipLocationName;
|
||||||
|
|
@ -111,6 +112,14 @@ public class OpportunityItemDTO {
|
||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getArchivedAt() {
|
||||||
|
return archivedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArchivedAt(String archivedAt) {
|
||||||
|
this.archivedAt = archivedAt;
|
||||||
|
}
|
||||||
|
|
||||||
public String getProjectLocation() {
|
public String getProjectLocation() {
|
||||||
return projectLocation;
|
return projectLocation;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -533,7 +533,8 @@ public class OpportunityServiceImpl implements OpportunityService {
|
||||||
request.setCustomerName(normalizeRequiredText(request.getCustomerName(), "最终客户不能为空"));
|
request.setCustomerName(normalizeRequiredText(request.getCustomerName(), "最终客户不能为空"));
|
||||||
request.setOpportunityName(normalizeRequiredText(request.getOpportunityName(), "项目名称不能为空"));
|
request.setOpportunityName(normalizeRequiredText(request.getOpportunityName(), "项目名称不能为空"));
|
||||||
request.setProjectLocation(normalizeRequiredText(request.getProjectLocation(), "请选择项目地"));
|
request.setProjectLocation(normalizeRequiredText(request.getProjectLocation(), "请选择项目地"));
|
||||||
request.setProjectOwnershipLocation(normalizeProjectOwnershipLocationCode(request.getProjectOwnershipLocation()));
|
request.setProjectOwnershipLocation(normalizeProjectOwnershipLocationCode(
|
||||||
|
normalizeRequiredText(request.getProjectOwnershipLocation(), "请选择业绩归属地")));
|
||||||
request.setOperatorName(normalizeRequiredText(request.getOperatorName(), "请选择运作方"));
|
request.setOperatorName(normalizeRequiredText(request.getOperatorName(), "请选择运作方"));
|
||||||
request.setAmount(requirePositiveAmount(request.getAmount(), "请填写预计金额"));
|
request.setAmount(requirePositiveAmount(request.getAmount(), "请填写预计金额"));
|
||||||
request.setDescription(normalizeOptionalText(request.getDescription()));
|
request.setDescription(normalizeOptionalText(request.getDescription()));
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@
|
||||||
),
|
),
|
||||||
'无'
|
'无'
|
||||||
) as updatedAt,
|
) as updatedAt,
|
||||||
|
coalesce(to_char(o.archived_at, 'YYYY-MM-DD HH24:MI'), '') as archivedAt,
|
||||||
coalesce(o.project_location, '') as projectLocation,
|
coalesce(o.project_location, '') as projectLocation,
|
||||||
coalesce(o.project_ownership_location, '') as projectOwnershipLocation,
|
coalesce(o.project_ownership_location, '') as projectOwnershipLocation,
|
||||||
coalesce(project_ownership_area.short_name, nullif(o.project_ownership_location, ''), '') as projectOwnershipLocationName,
|
coalesce(project_ownership_area.short_name, nullif(o.project_ownership_location, ''), '') as projectOwnershipLocationName,
|
||||||
|
|
@ -296,6 +297,7 @@
|
||||||
),
|
),
|
||||||
'无'
|
'无'
|
||||||
) as updatedAt,
|
) as updatedAt,
|
||||||
|
coalesce(to_char(o.archived_at, 'YYYY-MM-DD HH24:MI'), '') as archivedAt,
|
||||||
coalesce(o.project_location, '') as projectLocation,
|
coalesce(o.project_location, '') as projectLocation,
|
||||||
coalesce(o.project_ownership_location, '') as projectOwnershipLocation,
|
coalesce(o.project_ownership_location, '') as projectOwnershipLocation,
|
||||||
coalesce(project_ownership_area.short_name, nullif(o.project_ownership_location, ''), '') as projectOwnershipLocationName,
|
coalesce(project_ownership_area.short_name, nullif(o.project_ownership_location, ''), '') as projectOwnershipLocationName,
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,7 @@ class OpportunityServiceImplTest {
|
||||||
request.setCustomerName(customerName);
|
request.setCustomerName(customerName);
|
||||||
request.setOpportunityName("商机A");
|
request.setOpportunityName("商机A");
|
||||||
request.setProjectLocation("上海");
|
request.setProjectLocation("上海");
|
||||||
|
request.setProjectOwnershipLocation("上海");
|
||||||
request.setOperatorName("直营");
|
request.setOperatorName("直营");
|
||||||
request.setAmount(new BigDecimal("100"));
|
request.setAmount(new BigDecimal("100"));
|
||||||
request.setExpectedCloseDate(LocalDate.of(2026, 4, 30));
|
request.setExpectedCloseDate(LocalDate.of(2026, 4, 30));
|
||||||
|
|
|
||||||
|
|
@ -472,6 +472,7 @@ export interface OpportunityItem {
|
||||||
owner?: string;
|
owner?: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
|
archivedAt?: string;
|
||||||
projectLocation?: string;
|
projectLocation?: string;
|
||||||
projectOwnershipLocation?: string;
|
projectOwnershipLocation?: string;
|
||||||
projectOwnershipLocationName?: string;
|
projectOwnershipLocationName?: string;
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@ type OpportunityExportFieldKey =
|
||||||
| "owner"
|
| "owner"
|
||||||
| "createdAt"
|
| "createdAt"
|
||||||
| "updatedAt"
|
| "updatedAt"
|
||||||
|
| "archivedAt"
|
||||||
| "archived"
|
| "archived"
|
||||||
| "pushedToOms";
|
| "pushedToOms";
|
||||||
type OpportunityExportColumn = {
|
type OpportunityExportColumn = {
|
||||||
|
|
@ -328,6 +329,7 @@ const opportunityExportColumns: OpportunityExportColumn[] = [
|
||||||
{ key: "owner", label: "创建人", value: (item) => normalizeOpportunityExportText(item.owner) },
|
{ key: "owner", label: "创建人", value: (item) => normalizeOpportunityExportText(item.owner) },
|
||||||
{ key: "createdAt", label: "创建时间", value: (item) => normalizeOpportunityExportText(item.createdAt) },
|
{ key: "createdAt", label: "创建时间", value: (item) => normalizeOpportunityExportText(item.createdAt) },
|
||||||
{ key: "updatedAt", label: "更新修改时间", value: (item) => normalizeOpportunityExportText(item.updatedAt) },
|
{ key: "updatedAt", label: "更新修改时间", value: (item) => normalizeOpportunityExportText(item.updatedAt) },
|
||||||
|
{ key: "archivedAt", label: "签单时间", value: (item) => normalizeOpportunityExportText(item.archivedAt) },
|
||||||
{ key: "archived", label: "是否签单", value: (item) => formatOpportunityBoolean(item.archived, "已签单", "未签单") },
|
{ key: "archived", label: "是否签单", value: (item) => formatOpportunityBoolean(item.archived, "已签单", "未签单") },
|
||||||
{ key: "pushedToOms", label: "是否推送OMS", value: (item) => formatOpportunityBoolean(item.pushedToOms, "已推送", "未推送") },
|
{ key: "pushedToOms", label: "是否推送OMS", value: (item) => formatOpportunityBoolean(item.pushedToOms, "已推送", "未推送") },
|
||||||
{ key: "actualSignedAmount", label: "实际签约金额(元)", numFmt: "#,##0.00", value: (item) => normalizeOpportunityExportNumber(item.actualSignedAmount) ?? "" },
|
{ key: "actualSignedAmount", label: "实际签约金额(元)", numFmt: "#,##0.00", value: (item) => normalizeOpportunityExportNumber(item.actualSignedAmount) ?? "" },
|
||||||
|
|
@ -811,6 +813,9 @@ function validateOpportunityForm(
|
||||||
if (!form.projectLocation?.trim()) {
|
if (!form.projectLocation?.trim()) {
|
||||||
errors.projectLocation = "请选择项目地";
|
errors.projectLocation = "请选择项目地";
|
||||||
}
|
}
|
||||||
|
if (!form.projectOwnershipLocation?.trim()) {
|
||||||
|
errors.projectOwnershipLocation = "请选择业绩归属地";
|
||||||
|
}
|
||||||
if (!form.opportunityName?.trim()) {
|
if (!form.opportunityName?.trim()) {
|
||||||
errors.opportunityName = "请填写项目名称";
|
errors.opportunityName = "请填写项目名称";
|
||||||
}
|
}
|
||||||
|
|
@ -1900,6 +1905,8 @@ export default function Opportunities() {
|
||||||
const [items, setItems] = useState<OpportunityItem[]>([]);
|
const [items, setItems] = useState<OpportunityItem[]>([]);
|
||||||
const [salesExpansionOptions, setSalesExpansionOptions] = useState<SalesExpansionItem[]>([]);
|
const [salesExpansionOptions, setSalesExpansionOptions] = useState<SalesExpansionItem[]>([]);
|
||||||
const [channelExpansionOptions, setChannelExpansionOptions] = useState<ChannelExpansionItem[]>([]);
|
const [channelExpansionOptions, setChannelExpansionOptions] = useState<ChannelExpansionItem[]>([]);
|
||||||
|
const [selectedSalesExpansionOption, setSelectedSalesExpansionOption] = useState<SearchableOption | null>(null);
|
||||||
|
const [selectedChannelExpansionOption, setSelectedChannelExpansionOption] = useState<SearchableOption | null>(null);
|
||||||
const [omsPreSalesOptions, setOmsPreSalesOptions] = useState<OmsPreSalesOption[]>([]);
|
const [omsPreSalesOptions, setOmsPreSalesOptions] = useState<OmsPreSalesOption[]>([]);
|
||||||
const [stageOptions, setStageOptions] = useState<OpportunityDictOption[]>([]);
|
const [stageOptions, setStageOptions] = useState<OpportunityDictOption[]>([]);
|
||||||
const [operatorOptions, setOperatorOptions] = useState<OpportunityDictOption[]>([]);
|
const [operatorOptions, setOperatorOptions] = useState<OpportunityDictOption[]>([]);
|
||||||
|
|
@ -2390,11 +2397,29 @@ export default function Opportunities() {
|
||||||
const showSalesExpansionField = operatorMode === "h3c" || operatorMode === "both";
|
const showSalesExpansionField = operatorMode === "h3c" || operatorMode === "both";
|
||||||
const showChannelExpansionField = operatorMode === "channel" || operatorMode === "both";
|
const showChannelExpansionField = operatorMode === "channel" || operatorMode === "both";
|
||||||
const showCustomCompetitorInput = selectedCompetitors.includes("其他");
|
const showCustomCompetitorInput = selectedCompetitors.includes("其他");
|
||||||
const selectedSalesExpansionFallbackOption = form.salesExpansionId && selectedSalesExpansionName
|
const formSalesExpansionOption = form.salesExpansionId
|
||||||
? { value: form.salesExpansionId, label: selectedSalesExpansionName, keywords: [] }
|
? salesExpansionOptions.find((item) => item.id === form.salesExpansionId) ?? null
|
||||||
: null;
|
: null;
|
||||||
const selectedChannelExpansionFallbackOption = form.channelExpansionId && selectedChannelExpansionName
|
const formChannelExpansionOption = form.channelExpansionId
|
||||||
? { value: form.channelExpansionId, label: selectedChannelExpansionName, keywords: [] }
|
? channelExpansionOptions.find((item) => item.id === form.channelExpansionId) ?? null
|
||||||
|
: null;
|
||||||
|
const selectedSalesExpansionFallbackOption = formSalesExpansionOption
|
||||||
|
? {
|
||||||
|
value: formSalesExpansionOption.id,
|
||||||
|
label: formatSalesExpansionOptionLabel(formSalesExpansionOption),
|
||||||
|
keywords: [formSalesExpansionOption.employeeNo || "", formSalesExpansionOption.officeName || "", formSalesExpansionOption.phone || "", formSalesExpansionOption.title || ""],
|
||||||
|
}
|
||||||
|
: form.salesExpansionId && selectedSalesExpansionOption?.value === form.salesExpansionId
|
||||||
|
? selectedSalesExpansionOption
|
||||||
|
: null;
|
||||||
|
const selectedChannelExpansionFallbackOption = formChannelExpansionOption
|
||||||
|
? {
|
||||||
|
value: formChannelExpansionOption.id,
|
||||||
|
label: formChannelExpansionOption.name || `渠道#${formChannelExpansionOption.id}`,
|
||||||
|
keywords: [formChannelExpansionOption.channelCode || "", formChannelExpansionOption.province || "", formChannelExpansionOption.primaryContactName || "", formChannelExpansionOption.primaryContactMobile || ""],
|
||||||
|
}
|
||||||
|
: form.channelExpansionId && selectedChannelExpansionOption?.value === form.channelExpansionId
|
||||||
|
? selectedChannelExpansionOption
|
||||||
: null;
|
: null;
|
||||||
const salesExpansionSearchOptions: SearchableOption[] = appendSearchableOptionIfMissing(salesExpansionOptions.map((item) => ({
|
const salesExpansionSearchOptions: SearchableOption[] = appendSearchableOptionIfMissing(salesExpansionOptions.map((item) => ({
|
||||||
value: item.id,
|
value: item.id,
|
||||||
|
|
@ -2569,6 +2594,20 @@ export default function Opportunities() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSalesExpansionChange = (value?: number) => {
|
||||||
|
setSelectedSalesExpansionOption(
|
||||||
|
value ? salesExpansionSearchOptions.find((option) => option.value === value) ?? null : null,
|
||||||
|
);
|
||||||
|
handleChange("salesExpansionId", value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChannelExpansionChange = (value?: number) => {
|
||||||
|
setSelectedChannelExpansionOption(
|
||||||
|
value ? channelExpansionSearchOptions.find((option) => option.value === value) ?? null : null,
|
||||||
|
);
|
||||||
|
handleChange("channelExpansionId", value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleQuickSalesChange = <K extends keyof CreateSalesExpansionPayload>(key: K, value: CreateSalesExpansionPayload[K]) => {
|
const handleQuickSalesChange = <K extends keyof CreateSalesExpansionPayload>(key: K, value: CreateSalesExpansionPayload[K]) => {
|
||||||
setQuickSalesForm((current) => ({ ...current, [key]: value }));
|
setQuickSalesForm((current) => ({ ...current, [key]: value }));
|
||||||
if (key in quickSalesFieldErrors) {
|
if (key in quickSalesFieldErrors) {
|
||||||
|
|
@ -2705,9 +2744,25 @@ export default function Opportunities() {
|
||||||
const latestOverview = await refreshExpansionOptions();
|
const latestOverview = await refreshExpansionOptions();
|
||||||
if (quickCreateType === "sales") {
|
if (quickCreateType === "sales") {
|
||||||
const createdItem = (latestOverview.salesItems ?? []).find((item) => item.id === createdId);
|
const createdItem = (latestOverview.salesItems ?? []).find((item) => item.id === createdId);
|
||||||
|
const createdOption = createdItem
|
||||||
|
? {
|
||||||
|
value: createdItem.id,
|
||||||
|
label: formatSalesExpansionOptionLabel(createdItem),
|
||||||
|
keywords: [createdItem.employeeNo || "", createdItem.officeName || "", createdItem.phone || "", createdItem.title || ""],
|
||||||
|
}
|
||||||
|
: { value: createdId, label: quickSalesForm.candidateName?.trim() || `拓展人员#${createdId}`, keywords: [] };
|
||||||
|
setSelectedSalesExpansionOption(createdOption);
|
||||||
handleChange("salesExpansionId", createdItem?.id ?? createdId);
|
handleChange("salesExpansionId", createdItem?.id ?? createdId);
|
||||||
} else {
|
} else {
|
||||||
const createdItem = (latestOverview.channelItems ?? []).find((item) => item.id === createdId);
|
const createdItem = (latestOverview.channelItems ?? []).find((item) => item.id === createdId);
|
||||||
|
const createdOption = createdItem
|
||||||
|
? {
|
||||||
|
value: createdItem.id,
|
||||||
|
label: createdItem.name || `渠道#${createdItem.id}`,
|
||||||
|
keywords: [createdItem.channelCode || "", createdItem.province || "", createdItem.primaryContactName || "", createdItem.primaryContactMobile || ""],
|
||||||
|
}
|
||||||
|
: { value: createdId, label: quickChannelForm.channelName?.trim() || `渠道#${createdId}`, keywords: [] };
|
||||||
|
setSelectedChannelExpansionOption(createdOption);
|
||||||
handleChange("channelExpansionId", createdItem?.id ?? createdId);
|
handleChange("channelExpansionId", createdItem?.id ?? createdId);
|
||||||
}
|
}
|
||||||
resetQuickCreateState();
|
resetQuickCreateState();
|
||||||
|
|
@ -2721,6 +2776,8 @@ export default function Opportunities() {
|
||||||
setError("");
|
setError("");
|
||||||
setFieldErrors({});
|
setFieldErrors({});
|
||||||
setForm(buildEmptyForm());
|
setForm(buildEmptyForm());
|
||||||
|
setSelectedSalesExpansionOption(null);
|
||||||
|
setSelectedChannelExpansionOption(null);
|
||||||
setSelectedCompetitors([]);
|
setSelectedCompetitors([]);
|
||||||
setCustomCompetitorName("");
|
setCustomCompetitorName("");
|
||||||
setCreateOpen(true);
|
setCreateOpen(true);
|
||||||
|
|
@ -2733,6 +2790,8 @@ export default function Opportunities() {
|
||||||
setError("");
|
setError("");
|
||||||
setFieldErrors({});
|
setFieldErrors({});
|
||||||
setForm(buildEmptyForm());
|
setForm(buildEmptyForm());
|
||||||
|
setSelectedSalesExpansionOption(null);
|
||||||
|
setSelectedChannelExpansionOption(null);
|
||||||
setSelectedCompetitors([]);
|
setSelectedCompetitors([]);
|
||||||
setCustomCompetitorName("");
|
setCustomCompetitorName("");
|
||||||
};
|
};
|
||||||
|
|
@ -2787,6 +2846,16 @@ export default function Opportunities() {
|
||||||
setError("");
|
setError("");
|
||||||
setFieldErrors({});
|
setFieldErrors({});
|
||||||
setForm(toFormFromItem(sourceItem, effectiveConfidenceOptions));
|
setForm(toFormFromItem(sourceItem, effectiveConfidenceOptions));
|
||||||
|
setSelectedSalesExpansionOption(sourceItem.salesExpansionId && sourceItem.salesExpansionName ? {
|
||||||
|
value: sourceItem.salesExpansionId,
|
||||||
|
label: sourceItem.salesExpansionName,
|
||||||
|
keywords: [],
|
||||||
|
} : null);
|
||||||
|
setSelectedChannelExpansionOption(sourceItem.channelExpansionId && sourceItem.channelExpansionName ? {
|
||||||
|
value: sourceItem.channelExpansionId,
|
||||||
|
label: sourceItem.channelExpansionName,
|
||||||
|
keywords: [],
|
||||||
|
} : null);
|
||||||
const competitorState = parseCompetitorState(sourceItem.competitorName);
|
const competitorState = parseCompetitorState(sourceItem.competitorName);
|
||||||
setSelectedCompetitors(competitorState.selections);
|
setSelectedCompetitors(competitorState.selections);
|
||||||
setCustomCompetitorName(competitorState.customName);
|
setCustomCompetitorName(competitorState.customName);
|
||||||
|
|
@ -3034,6 +3103,8 @@ export default function Opportunities() {
|
||||||
{visibleItems.length > 0 ? (
|
{visibleItems.length > 0 ? (
|
||||||
visibleItems.map((opp, i) => {
|
visibleItems.map((opp, i) => {
|
||||||
const isOwnedByCurrentUser = currentUserId !== undefined && opp.ownerUserId === currentUserId;
|
const isOwnedByCurrentUser = currentUserId !== undefined && opp.ownerUserId === currentUserId;
|
||||||
|
const amountLabel = archiveTab === "archived" ? "实际签约金额" : "预计金额";
|
||||||
|
const displayAmount = archiveTab === "archived" ? opp.actualSignedAmount : opp.amount;
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={disableMobileMotion ? false : { opacity: 0, y: 10 }}
|
initial={disableMobileMotion ? false : { opacity: 0, y: 10 }}
|
||||||
|
|
@ -3082,8 +3153,8 @@ export default function Opportunities() {
|
||||||
<span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">{opp.date || "待定"}</span>
|
<span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">{opp.date || "待定"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
|
<div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
|
||||||
<span className="shrink-0 text-slate-400 dark:text-slate-500">预计金额:</span>
|
<span className="shrink-0 text-slate-400 dark:text-slate-500">{amountLabel}:</span>
|
||||||
<span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">¥{formatAmount(opp.amount)}</span>
|
<span className="min-w-0 flex-1 truncate font-medium text-slate-900 dark:text-white">¥{formatAmount(displayAmount)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
|
<div className="flex min-w-0 items-center gap-2 text-slate-600 dark:text-slate-300">
|
||||||
<span className="shrink-0 text-slate-400 dark:text-slate-500">项目最新进展:</span>
|
<span className="shrink-0 text-slate-400 dark:text-slate-500">项目最新进展:</span>
|
||||||
|
|
@ -3212,7 +3283,7 @@ export default function Opportunities() {
|
||||||
{fieldErrors.projectLocation ? <p className="text-xs text-rose-500">{fieldErrors.projectLocation}</p> : null}
|
{fieldErrors.projectLocation ? <p className="text-xs text-rose-500">{fieldErrors.projectLocation}</p> : null}
|
||||||
</label>
|
</label>
|
||||||
<label className="space-y-2">
|
<label className="space-y-2">
|
||||||
<span className="text-sm font-medium text-slate-700 dark:text-slate-300">业绩归属地</span>
|
<span className="text-sm font-medium text-slate-700 dark:text-slate-300">业绩归属地<RequiredMark /></span>
|
||||||
<AdaptiveSelect
|
<AdaptiveSelect
|
||||||
value={form.projectOwnershipLocation || ""}
|
value={form.projectOwnershipLocation || ""}
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
|
|
@ -3269,7 +3340,7 @@ export default function Opportunities() {
|
||||||
className={cn(
|
className={cn(
|
||||||
fieldErrors.salesExpansionId ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
|
fieldErrors.salesExpansionId ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
|
||||||
)}
|
)}
|
||||||
onChange={(value) => handleChange("salesExpansionId", value)}
|
onChange={handleSalesExpansionChange}
|
||||||
onQueryChange={setSalesExpansionQuery}
|
onQueryChange={setSalesExpansionQuery}
|
||||||
onCreate={(query) => void openQuickCreateModal("sales", query)}
|
onCreate={(query) => void openQuickCreateModal("sales", query)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -3291,7 +3362,7 @@ export default function Opportunities() {
|
||||||
className={cn(
|
className={cn(
|
||||||
fieldErrors.channelExpansionId ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
|
fieldErrors.channelExpansionId ? "border-rose-400 bg-rose-50/60 focus:border-rose-500 focus:ring-rose-500 dark:border-rose-500/70 dark:bg-rose-500/10" : "",
|
||||||
)}
|
)}
|
||||||
onChange={(value) => handleChange("channelExpansionId", value)}
|
onChange={handleChannelExpansionChange}
|
||||||
onQueryChange={setChannelExpansionQuery}
|
onQueryChange={setChannelExpansionQuery}
|
||||||
onCreate={(query) => void openQuickCreateModal("channel", query)}
|
onCreate={(query) => void openQuickCreateModal("channel", query)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,9 @@ function validateOpportunityQuickForm(
|
||||||
if (!form.projectLocation?.trim()) {
|
if (!form.projectLocation?.trim()) {
|
||||||
errors.projectLocation = "请选择项目地";
|
errors.projectLocation = "请选择项目地";
|
||||||
}
|
}
|
||||||
|
if (!form.projectOwnershipLocation?.trim()) {
|
||||||
|
errors.projectOwnershipLocation = "请选择业绩归属地";
|
||||||
|
}
|
||||||
if (!form.opportunityName?.trim()) {
|
if (!form.opportunityName?.trim()) {
|
||||||
errors.opportunityName = "请填写项目名称";
|
errors.opportunityName = "请填写项目名称";
|
||||||
}
|
}
|
||||||
|
|
@ -2688,7 +2691,7 @@ export default function Work() {
|
||||||
{quickOpportunityFieldErrors.projectLocation ? <p className="text-xs text-rose-500">{quickOpportunityFieldErrors.projectLocation}</p> : null}
|
{quickOpportunityFieldErrors.projectLocation ? <p className="text-xs text-rose-500">{quickOpportunityFieldErrors.projectLocation}</p> : null}
|
||||||
</label>
|
</label>
|
||||||
<label className="space-y-2">
|
<label className="space-y-2">
|
||||||
<span className="text-sm font-medium text-slate-700 dark:text-slate-300">业绩归属地</span>
|
<span className="text-sm font-medium text-slate-700 dark:text-slate-300">业绩归属地<RequiredMark /></span>
|
||||||
<AdaptiveSelect
|
<AdaptiveSelect
|
||||||
value={quickOpportunityForm.projectOwnershipLocation || ""}
|
value={quickOpportunityForm.projectOwnershipLocation || ""}
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue