跟进记录日期展示优化;重复提交Bug修复;提交按钮添加loading限制
parent
2d502751f5
commit
0da982b584
|
|
@ -4,7 +4,13 @@
|
|||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="4c558d98-824e-4a48-ba48-bd2e6172f9f4" name="更改" comment="修改定位信息 0323" />
|
||||
<list default="true" id="4c558d98-824e-4a48-ba48-bd2e6172f9f4" name="更改" comment="修改定位信息 0323">
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/impl/ExpansionServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/impl/ExpansionServiceImpl.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/mapper/expansion/ExpansionMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/mapper/expansion/ExpansionMapper.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Expansion.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Expansion.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Work.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Work.tsx" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
|
|
@ -96,6 +102,7 @@
|
|||
<workItem from="1775721940000" duration="8576000" />
|
||||
<workItem from="1776219416113" duration="650000" />
|
||||
<workItem from="1776238420843" duration="21000" />
|
||||
<workItem from="1787113214298" duration="3043000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="修改定位信息 0323">
|
||||
<option name="closed" value="true" />
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ public class ExpansionServiceImpl implements ExpansionService {
|
|||
@Override
|
||||
public ExpansionOverviewDTO getOverview(Long userId, String keyword, boolean includeDetails, Integer limit) {
|
||||
String normalizedKeyword = normalizeKeyword(keyword);
|
||||
Integer normalizedLimit = limit != null && limit > 0 ? Math.min(limit, 1000) : null;
|
||||
Integer normalizedLimit = limit != null && limit > 0 ? limit : null;
|
||||
List<Long> extraVisibleOwnerUserIds = resolveExtraVisibleOwnerUserIds(userId);
|
||||
List<SalesExpansionItemDTO> salesItems = mergeSalesExpansionItems(
|
||||
expansionMapper.selectSalesExpansions(userId, normalizedKeyword, normalizedLimit),
|
||||
|
|
|
|||
|
|
@ -177,12 +177,24 @@
|
|||
order by cc.sort_order asc nulls last, cc.id asc
|
||||
limit 1
|
||||
) primary_contact on true
|
||||
left join cnarea province_area
|
||||
on province_area.level = 1
|
||||
and (province_area.name = c.province or province_area.area_code = c.province)
|
||||
left join cnarea city_area
|
||||
on city_area.level = 2
|
||||
and (city_area.name = c.city or city_area.area_code = c.city)
|
||||
left join lateral (
|
||||
select
|
||||
name
|
||||
from cnarea
|
||||
where level = 1
|
||||
and (name = c.province or area_code = c.province)
|
||||
order by name asc, area_code asc
|
||||
limit 1
|
||||
) province_area on true
|
||||
left join lateral (
|
||||
select
|
||||
name
|
||||
from cnarea
|
||||
where level = 2
|
||||
and (name = c.city or area_code = c.city)
|
||||
order by name asc, area_code asc
|
||||
limit 1
|
||||
) city_area on true
|
||||
left join sys_dict_item channel_attribute_dict
|
||||
on channel_attribute_dict.type_code = 'tz_qdsx'
|
||||
and channel_attribute_dict.item_value = c.channel_attribute
|
||||
|
|
@ -460,12 +472,24 @@
|
|||
order by cc.sort_order asc nulls last, cc.id asc
|
||||
limit 1
|
||||
) primary_contact on true
|
||||
left join cnarea province_area
|
||||
on province_area.level = 1
|
||||
and (province_area.name = c.province or province_area.area_code = c.province)
|
||||
left join cnarea city_area
|
||||
on city_area.level = 2
|
||||
and (city_area.name = c.city or city_area.area_code = c.city)
|
||||
left join lateral (
|
||||
select
|
||||
name
|
||||
from cnarea
|
||||
where level = 1
|
||||
and (name = c.province or area_code = c.province)
|
||||
order by name asc, area_code asc
|
||||
limit 1
|
||||
) province_area on true
|
||||
left join lateral (
|
||||
select
|
||||
name
|
||||
from cnarea
|
||||
where level = 2
|
||||
and (name = c.city or area_code = c.city)
|
||||
order by name asc, area_code asc
|
||||
limit 1
|
||||
) city_area on true
|
||||
left join sys_dict_item channel_attribute_dict
|
||||
on channel_attribute_dict.type_code = 'tz_qdsx'
|
||||
and channel_attribute_dict.item_value = c.channel_attribute
|
||||
|
|
@ -703,12 +727,24 @@
|
|||
order by cc.sort_order asc nulls last, cc.id asc
|
||||
limit 1
|
||||
) primary_contact on true
|
||||
left join cnarea province_area
|
||||
on province_area.level = 1
|
||||
and (province_area.name = c.province or province_area.area_code = c.province)
|
||||
left join cnarea city_area
|
||||
on city_area.level = 2
|
||||
and (city_area.name = c.city or city_area.area_code = c.city)
|
||||
left join lateral (
|
||||
select
|
||||
name
|
||||
from cnarea
|
||||
where level = 1
|
||||
and (name = c.province or area_code = c.province)
|
||||
order by name asc, area_code asc
|
||||
limit 1
|
||||
) province_area on true
|
||||
left join lateral (
|
||||
select
|
||||
name
|
||||
from cnarea
|
||||
where level = 2
|
||||
and (name = c.city or area_code = c.city)
|
||||
order by name asc, area_code asc
|
||||
limit 1
|
||||
) city_area on true
|
||||
left join sys_dict_item channel_attribute_dict
|
||||
on channel_attribute_dict.type_code = 'tz_qdsx'
|
||||
and channel_attribute_dict.item_value = c.channel_attribute
|
||||
|
|
@ -767,7 +803,14 @@
|
|||
f.biz_id as bizId,
|
||||
f.biz_type as bizType,
|
||||
f.followup_time as followUpTime,
|
||||
to_char(f.followup_time, 'YYYY-MM-DD HH24:MI') as date,
|
||||
case
|
||||
when (f.followup_type = '工作日报' or f.source_type = 'work_report')
|
||||
and (f.followup_time at time zone 'Asia/Shanghai')::time < time '10:00'
|
||||
then ((f.followup_time at time zone 'Asia/Shanghai')::date - 1)::text
|
||||
when (f.followup_type = '工作日报' or f.source_type = 'work_report')
|
||||
then (f.followup_time at time zone 'Asia/Shanghai')::date::text
|
||||
else to_char(f.followup_time, 'YYYY-MM-DD HH24:MI')
|
||||
end as date,
|
||||
f.followup_type as type,
|
||||
coalesce(f.content, '无') as content,
|
||||
coalesce(u.display_name, '无') as user,
|
||||
|
|
@ -831,7 +874,14 @@
|
|||
f.biz_id as bizId,
|
||||
f.biz_type as bizType,
|
||||
f.followup_time as followUpTime,
|
||||
to_char(f.followup_time, 'YYYY-MM-DD HH24:MI') as date,
|
||||
case
|
||||
when (f.followup_type = '工作日报' or f.source_type = 'work_report')
|
||||
and (f.followup_time at time zone 'Asia/Shanghai')::time < time '10:00'
|
||||
then ((f.followup_time at time zone 'Asia/Shanghai')::date - 1)::text
|
||||
when (f.followup_type = '工作日报' or f.source_type = 'work_report')
|
||||
then (f.followup_time at time zone 'Asia/Shanghai')::date::text
|
||||
else to_char(f.followup_time, 'YYYY-MM-DD HH24:MI')
|
||||
end as date,
|
||||
f.followup_type as type,
|
||||
coalesce(f.content, '无') as content,
|
||||
coalesce(u.display_name, '无') as user,
|
||||
|
|
|
|||
|
|
@ -436,7 +436,14 @@
|
|||
select
|
||||
f.id,
|
||||
f.opportunity_id as opportunityId,
|
||||
to_char(f.followup_time, 'YYYY-MM-DD HH24:MI') as date,
|
||||
case
|
||||
when (f.followup_type = '工作日报' or f.source_type = 'work_report')
|
||||
and (f.followup_time at time zone 'Asia/Shanghai')::time < time '10:00'
|
||||
then ((f.followup_time at time zone 'Asia/Shanghai')::date - 1)::text
|
||||
when (f.followup_type = '工作日报' or f.source_type = 'work_report')
|
||||
then (f.followup_time at time zone 'Asia/Shanghai')::date::text
|
||||
else to_char(f.followup_time, 'YYYY-MM-DD HH24:MI')
|
||||
end as date,
|
||||
coalesce(f.followup_type, '无') as type,
|
||||
coalesce(f.content, '无') as content,
|
||||
coalesce(f.next_action, '') as nextAction,
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,46 @@
|
|||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Ziguang Huizhi CRM Icon Logo</title>
|
||||
<desc id="desc">Brand-oriented CRM logo icon with an abstract converging symbol, data nodes, and geometric structure in purple tones.</desc>
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="72" y1="60" x2="448" y2="460" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#744CF7"/>
|
||||
<stop offset="1" stop-color="#2E1685"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="mark" x1="170" y1="152" x2="342" y2="360" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFFFFF"/>
|
||||
<stop offset="1" stop-color="#D4C4FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="accent" x1="198" y1="184" x2="322" y2="322" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A98CFF"/>
|
||||
<stop offset="1" stop-color="#6C44F2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect x="40" y="40" width="432" height="432" rx="112" fill="url(#bg)"/>
|
||||
<path d="M130 256C130 185.307 185.307 130 256 130C326.693 130 382 185.307 382 256C382 326.693 326.693 382 256 382C185.307 382 130 326.693 130 256Z" stroke="#FFFFFF" stroke-opacity="0.1" stroke-width="12"/>
|
||||
|
||||
<path d="M256 150L344 202V310L256 362L168 310V202L256 150Z" fill="#FFFFFF" fill-opacity="0.08"/>
|
||||
<path d="M256 174L323 213V299L256 338L189 299V213L256 174Z" stroke="#FFFFFF" stroke-opacity="0.16" stroke-width="2"/>
|
||||
|
||||
<path d="M206 193L256 242L306 193" stroke="url(#mark)" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M206 319L256 270L306 319" stroke="url(#mark)" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M206 193V319" stroke="url(#mark)" stroke-width="18" stroke-linecap="round"/>
|
||||
<path d="M306 193V319" stroke="url(#mark)" stroke-width="18" stroke-linecap="round"/>
|
||||
|
||||
<path d="M228 221L256 249L284 221" stroke="url(#accent)" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M228 291L256 263L284 291" stroke="url(#accent)" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<circle cx="206" cy="193" r="14" fill="#FFFFFF"/>
|
||||
<circle cx="306" cy="193" r="14" fill="#FFFFFF"/>
|
||||
<circle cx="206" cy="319" r="14" fill="#FFFFFF"/>
|
||||
<circle cx="306" cy="319" r="14" fill="#FFFFFF"/>
|
||||
<circle cx="256" cy="242" r="14" fill="#FFFFFF"/>
|
||||
<circle cx="256" cy="270" r="14" fill="#FFFFFF"/>
|
||||
|
||||
<circle cx="206" cy="193" r="5" fill="#7A57F8"/>
|
||||
<circle cx="306" cy="193" r="5" fill="#7A57F8"/>
|
||||
<circle cx="206" cy="319" r="5" fill="#7A57F8"/>
|
||||
<circle cx="306" cy="319" r="5" fill="#7A57F8"/>
|
||||
<circle cx="256" cy="242" r="5" fill="#7A57F8"/>
|
||||
<circle cx="256" cy="270" r="5" fill="#7A57F8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/crm-favicon.svg" />
|
||||
<title>紫光汇智CRM</title>
|
||||
<script type="module" crossorigin src="/assets/index-D1YmE68l.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CoNZrZYC.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1342,6 +1342,8 @@ export default function Expansion() {
|
|||
const [visibleItemCount, setVisibleItemCount] = useState(LIST_PAGE_SIZE);
|
||||
const loadMoreRef = useRef<HTMLDivElement | null>(null);
|
||||
const loadingMoreRef = useRef(false);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const [loadMoreError, setLoadMoreError] = useState("");
|
||||
const [officeOptions, setOfficeOptions] = useState<ExpansionDictOption[]>([]);
|
||||
const [industryOptions, setIndustryOptions] = useState<ExpansionDictOption[]>([]);
|
||||
const [provinceOptions, setProvinceOptions] = useState<ExpansionDictOption[]>([]);
|
||||
|
|
@ -1590,14 +1592,19 @@ export default function Expansion() {
|
|||
return;
|
||||
}
|
||||
loadingMoreRef.current = true;
|
||||
setLoadingMore(true);
|
||||
setLoadMoreError("");
|
||||
const nextVisibleCount = visibleItemCount + LIST_PAGE_SIZE;
|
||||
try {
|
||||
const data = await getExpansionOverview(keyword, false, nextVisibleCount + 1);
|
||||
setSalesData(dedupeExpansionItemsById(data.salesItems ?? []));
|
||||
setChannelData(dedupeExpansionItemsById(data.channelItems ?? []));
|
||||
setVisibleItemCount(nextVisibleCount);
|
||||
} catch {
|
||||
setLoadMoreError("加载更多失败,请重试");
|
||||
} finally {
|
||||
loadingMoreRef.current = false;
|
||||
setLoadingMore(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -2775,6 +2782,11 @@ export default function Expansion() {
|
|||
);
|
||||
})
|
||||
) : renderEmpty()}
|
||||
{loadMoreError ? (
|
||||
<div className="rounded-lg border border-rose-200 bg-rose-50 py-3 text-center text-sm font-medium text-rose-600 dark:border-rose-500/20 dark:bg-rose-500/10 dark:text-rose-300">
|
||||
{loadMoreError}
|
||||
</div>
|
||||
) : null}
|
||||
{hasMoreItems ? (
|
||||
<div
|
||||
ref={loadMoreRef}
|
||||
|
|
@ -2789,7 +2801,7 @@ export default function Expansion() {
|
|||
}}
|
||||
className="cursor-pointer rounded-lg border border-slate-200 bg-white py-3 text-center text-sm font-medium text-violet-600 shadow-sm dark:border-slate-700 dark:bg-slate-900 dark:text-violet-300"
|
||||
>
|
||||
展示更多
|
||||
{loadingMore ? "加载中..." : "展示更多"}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, ty
|
|||
import { format } from "date-fns";
|
||||
import { zhCN } from "date-fns/locale";
|
||||
import { motion } from "motion/react";
|
||||
import { ArrowUp, Camera, CheckCircle2, ChevronDown, Download, FileText, Hash, ListTodo, MapPin, Mic, NotebookPen, Paperclip, Plus, RefreshCw, RotateCcw, Search, Send, Square, Trash2, UserRoundPlus, X } from "lucide-react";
|
||||
import { ArrowUp, Camera, CheckCircle2, ChevronDown, Download, FileText, Hash, ListTodo, Loader2, MapPin, Mic, NotebookPen, Paperclip, Plus, RefreshCw, RotateCcw, Search, Send, Square, Trash2, UserRoundPlus, X } from "lucide-react";
|
||||
import { flushSync } from "react-dom";
|
||||
import { Link, Navigate, useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
|
|
@ -835,10 +835,12 @@ export default function Work() {
|
|||
const historyLoadingRequestRef = useRef<string | null>(null);
|
||||
const historyLoadedPagesRef = useRef(new Map<WorkSection, Set<number>>());
|
||||
const openedRouteReportIdRef = useRef<number | null>(null);
|
||||
const reportSubmitInFlightRef = useRef(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshingLocation, setRefreshingLocation] = useState(false);
|
||||
const [submittingCheckIn, setSubmittingCheckIn] = useState(false);
|
||||
const [submittingReport, setSubmittingReport] = useState(false);
|
||||
const [reportSubmitLocked, setReportSubmitLocked] = useState(false);
|
||||
const [uploadingPhoto, setUploadingPhoto] = useState(false);
|
||||
const [uploadingReportAttachmentIndex, setUploadingReportAttachmentIndex] = useState<number | null>(null);
|
||||
const [uploadingReportAttachmentProgress, setUploadingReportAttachmentProgress] = useState<Record<number, number>>({});
|
||||
|
|
@ -2153,7 +2155,7 @@ export default function Work() {
|
|||
};
|
||||
|
||||
const handleReportSubmit = async () => {
|
||||
if (submittingReport) {
|
||||
if (submittingReport || reportSubmitLocked || reportSubmitInFlightRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2182,6 +2184,8 @@ export default function Work() {
|
|||
}
|
||||
validateReportLineItems(normalizedLineItems, reportOpportunityStageOptions, dailyReportObjectSelectionRequired);
|
||||
|
||||
reportSubmitInFlightRef.current = true;
|
||||
setReportSubmitLocked(true);
|
||||
await saveWorkDailyReport({
|
||||
workContent: buildReportSummary(normalizedLineItems),
|
||||
lineItems: normalizedLineItems,
|
||||
|
|
@ -2189,6 +2193,8 @@ export default function Work() {
|
|||
tomorrowPlan: buildPlanSummary(normalizedPlanItems),
|
||||
sourceType: reportForm.sourceType || "manual",
|
||||
});
|
||||
reportSubmitInFlightRef.current = false;
|
||||
setReportSubmitLocked(false);
|
||||
await Promise.all([
|
||||
loadOverview(),
|
||||
loadHistory(historySection, 1, true),
|
||||
|
|
@ -2319,6 +2325,7 @@ export default function Work() {
|
|||
reportSuccess={reportSuccess}
|
||||
pageError={pageError}
|
||||
submittingReport={submittingReport}
|
||||
reportSubmitLocked={reportSubmitLocked}
|
||||
onSubmit={() => void handleReportSubmit()}
|
||||
disableMobileMotion={disableMobileMotion}
|
||||
/>
|
||||
|
|
@ -4202,6 +4209,7 @@ function ReportPanel({
|
|||
reportSuccess,
|
||||
pageError,
|
||||
submittingReport,
|
||||
reportSubmitLocked,
|
||||
onSubmit,
|
||||
disableMobileMotion,
|
||||
}: {
|
||||
|
|
@ -4235,6 +4243,7 @@ function ReportPanel({
|
|||
reportSuccess: string;
|
||||
pageError: string;
|
||||
submittingReport: boolean;
|
||||
reportSubmitLocked: boolean;
|
||||
onSubmit: () => void;
|
||||
disableMobileMotion: boolean;
|
||||
}) {
|
||||
|
|
@ -5034,14 +5043,18 @@ function ReportPanel({
|
|||
<button
|
||||
type="button"
|
||||
onClick={onSubmit}
|
||||
disabled={submittingReport || loading || !dailyReportFeatureLoaded}
|
||||
disabled={submittingReport || reportSubmitLocked || loading || !dailyReportFeatureLoaded}
|
||||
className={cn(
|
||||
"crm-btn crm-btn-primary mt-6 flex w-full items-center justify-center gap-2 disabled:cursor-not-allowed disabled:opacity-60",
|
||||
disableMobileMotion ? "active:scale-100" : "active:scale-[0.98]",
|
||||
)}
|
||||
>
|
||||
<Send className="crm-icon-md" />
|
||||
{submittingReport ? "提交中..." : "提交日报"}
|
||||
{submittingReport || reportSubmitLocked ? (
|
||||
<Loader2 className="crm-icon-md animate-spin" />
|
||||
) : (
|
||||
<Send className="crm-icon-md" />
|
||||
)}
|
||||
{submittingReport ? "提交中..." : reportSubmitLocked ? "提交未确认,请刷新后重试" : "提交日报"}
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue