导出功能字段与前端显示字段文案调整

main
kangwenjing 2026-04-16 13:37:11 +08:00
parent 04e7422438
commit 07da7c4d5b
6 changed files with 348 additions and 30 deletions

View File

@ -475,7 +475,7 @@ public class ExpansionServiceImpl implements ExpansionService {
request.setCertificationLevel(normalizeRequiredText(request.getCertificationLevel(), "请选择认证级别"));
request.setOfficeAddress(normalizeRequiredText(request.getOfficeAddress(), "请填写办公地址"));
request.setChannelIndustry(normalizeRequiredText(request.getChannelIndustry(), "请选择聚焦行业"));
request.setAnnualRevenue(requirePositiveAmount(request.getAnnualRevenue(), "请填写年营收"));
request.setAnnualRevenue(requirePositiveAmount(request.getAnnualRevenue(), "请填写年度营业额(万元)"));
request.setStaffSize(requirePositiveInteger(request.getStaffSize(), "请填写人员规模"));
if (request.getContactEstablishedDate() == null) {
throw new BusinessException("请选择建立联系时间");
@ -504,7 +504,7 @@ public class ExpansionServiceImpl implements ExpansionService {
request.setCertificationLevel(normalizeRequiredText(request.getCertificationLevel(), "请选择认证级别"));
request.setOfficeAddress(normalizeRequiredText(request.getOfficeAddress(), "请填写办公地址"));
request.setChannelIndustry(normalizeRequiredText(request.getChannelIndustry(), "请选择聚焦行业"));
request.setAnnualRevenue(requirePositiveAmount(request.getAnnualRevenue(), "请填写年营收"));
request.setAnnualRevenue(requirePositiveAmount(request.getAnnualRevenue(), "请填写年度营业额(万元)"));
request.setStaffSize(requirePositiveInteger(request.getStaffSize(), "请填写人员规模"));
if (request.getContactEstablishedDate() == null) {
throw new BusinessException("请选择建立联系时间");

View File

@ -151,11 +151,10 @@
coalesce(c.channel_industry, c.industry, '') as channelIndustryCode,
coalesce(c.channel_industry, c.industry, '无') as channelIndustry,
coalesce(c.certification_level, '无') as certificationLevel,
coalesce(cast(c.annual_revenue as varchar), '') as annualRevenue,
coalesce(trim(to_char(c.annual_revenue, 'FM999999990.##')), '') as annualRevenue,
case
when c.annual_revenue is null then '无'
when c.annual_revenue >= 10000 then trim(to_char(c.annual_revenue / 10000.0, 'FM999999990.##')) || '万'
else trim(to_char(c.annual_revenue, 'FM999999990.##'))
else trim(to_char(c.annual_revenue, 'FM999999990.##')) || '万元'
end as revenue,
coalesce(c.staff_size, 0) as size,
coalesce(primary_contact.contact_name, c.contact_name, '无') as primaryContactName,

View File

@ -77,6 +77,8 @@ function createEmptyChannelContact(): ChannelExpansionContact {
};
}
const CHANNEL_REVENUE_LABEL = "年度营业额(万元)";
const defaultSalesForm: CreateSalesExpansionPayload = {
employeeNo: "",
candidateName: "",
@ -303,6 +305,18 @@ function formatExportFollowUps(followUps?: ExpansionFollowUp[]) {
.join("\n\n");
}
function formatExportProjectCell(project?: { opportunityCode?: string; opportunityName?: string; amount?: number | null }) {
if (!project) {
return "";
}
const lines = [
normalizeExportText(project.opportunityCode) ? `编码:${normalizeExportText(project.opportunityCode)}` : "",
normalizeExportText(project.opportunityName) ? `项目名称:${normalizeExportText(project.opportunityName)}` : "",
project.amount === null || project.amount === undefined ? "" : `金额:${formatAmount(Number(project.amount))}`,
].filter(Boolean);
return lines.join("\n");
}
function formatExportFilenameTime(date = new Date()) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
@ -342,7 +356,7 @@ function buildSalesExportHeaders(items: SalesExpansionItem[]) {
];
for (let index = 0; index < maxProjects; index += 1) {
headers.push(`项目${index + 1}编码`, `项目${index + 1}名称`, `项目${index + 1}金额`);
headers.push(`项目${index + 1}`);
}
headers.push("创建人", "更新修改时间");
@ -370,11 +384,7 @@ function buildSalesExportData(items: SalesExpansionItem[]) {
for (let index = 0; index < maxProjects; index += 1) {
const project = item.relatedProjects?.[index];
row.push(
normalizeExportText(project?.opportunityCode),
normalizeExportText(project?.opportunityName),
project?.amount === null || project?.amount === undefined ? "" : formatAmount(Number(project.amount)),
);
row.push(formatExportProjectCell(project));
}
row.push(
@ -401,14 +411,14 @@ function buildChannelExportHeaders(items: ChannelExpansionItem[]) {
"新华三内部属性",
"合作意向",
"建立联系时间",
"营收规模",
CHANNEL_REVENUE_LABEL,
"人员规模",
"以前是否做过云桌面项目",
"跟进项目金额",
];
for (let index = 0; index < maxProjects; index += 1) {
headers.push(`项目${index + 1}编码`, `项目${index + 1}名称`, `项目${index + 1}金额`);
headers.push(`项目${index + 1}`);
}
for (let index = 0; index < maxContacts; index += 1) {
@ -446,11 +456,7 @@ function buildChannelExportData(items: ChannelExpansionItem[]) {
for (let index = 0; index < maxProjects; index += 1) {
const project = item.relatedProjects?.[index];
row.push(
normalizeExportText(project?.opportunityCode),
normalizeExportText(project?.opportunityName),
project?.amount === null || project?.amount === undefined ? "" : formatAmount(Number(project.amount)),
);
row.push(formatExportProjectCell(project));
}
for (let index = 0; index < maxContacts; index += 1) {
@ -529,7 +535,7 @@ function validateChannelForm(form: CreateChannelExpansionPayload, channelOtherOp
errors.channelIndustry = "请选择聚焦行业";
}
if (!form.annualRevenue || form.annualRevenue <= 0) {
errors.annualRevenue = "请填写年营收";
errors.annualRevenue = `请填写${CHANNEL_REVENUE_LABEL}`;
}
if (!form.staffSize || form.staffSize <= 0) {
errors.staffSize = "请填写人员规模";
@ -1622,6 +1628,9 @@ export default function Expansion() {
worksheet.views = [{ state: "frozen", ySplit: 1 }];
const followUpColumnIndex = headers.indexOf("跟进记录") + 1;
const projectColumnIndexes = headers
.map((header, index) => (header.startsWith("项目") ? index + 1 : -1))
.filter((index) => index > 0);
worksheet.getRow(1).height = 24;
worksheet.getRow(1).font = { bold: true };
worksheet.getRow(1).alignment = { vertical: "middle", horizontal: "center" };
@ -1632,8 +1641,8 @@ export default function Expansion() {
column.width = 42;
} else if (header.includes("办公地址") || header.includes("备注")) {
column.width = 24;
} else if (header.includes("项目") && header.includes("名称")) {
column.width = 24;
} else if (header.startsWith("项目")) {
column.width = 26;
} else if (header.includes("渠道属性") || header.includes("内部属性") || header.includes("聚焦行业")) {
column.width = 18;
} else {
@ -1652,13 +1661,18 @@ export default function Expansion() {
cell.alignment = {
vertical: "top",
horizontal: rowNumber === 1 ? "center" : "left",
wrapText: headers[columnNumber - 1] === "跟进记录",
wrapText: headers[columnNumber - 1] === "跟进记录" || headers[columnNumber - 1].startsWith("项目"),
};
});
if (rowNumber > 1 && followUpColumnIndex > 0) {
const followUpText = normalizeExportText(row.getCell(followUpColumnIndex).value as string | null | undefined);
const lineCount = followUpText ? followUpText.split("\n").length : 1;
row.height = Math.max(22, lineCount * 16);
const followUpText = row.getCell(followUpColumnIndex).value as string | null | undefined;
const followUpLineCount = typeof followUpText === "string" && followUpText ? followUpText.split("\n").length : 1;
const projectLineCount = projectColumnIndexes.reduce((max, columnIndex) => {
const projectText = row.getCell(columnIndex).value as string | null | undefined;
const lineCount = typeof projectText === "string" && projectText ? projectText.split("\n").length : 1;
return Math.max(max, lineCount);
}, 1);
row.height = Math.max(22, Math.max(followUpLineCount, projectLineCount) * 16);
}
});
@ -1919,8 +1933,16 @@ export default function Expansion() {
{fieldErrors?.certificationLevel ? <p className="text-xs text-rose-500">{fieldErrors.certificationLevel}</p> : null}
</label>
<label className="space-y-2">
<span className="text-sm font-medium text-slate-700 dark:text-slate-300"><RequiredMark /></span>
<input type="number" value={form.annualRevenue ?? ""} onChange={(e) => onChange("annualRevenue", e.target.value ? Number(e.target.value) : undefined)} className={getFieldInputClass(Boolean(fieldErrors?.annualRevenue))} />
<span className="text-sm font-medium text-slate-700 dark:text-slate-300">{CHANNEL_REVENUE_LABEL}<RequiredMark /></span>
<input
type="number"
min="0.01"
step="0.01"
placeholder="请输入万元"
value={form.annualRevenue ?? ""}
onChange={(e) => onChange("annualRevenue", e.target.value ? Number(e.target.value) : undefined)}
className={getFieldInputClass(Boolean(fieldErrors?.annualRevenue))}
/>
{fieldErrors?.annualRevenue ? <p className="text-xs text-rose-500">{fieldErrors.annualRevenue}</p> : null}
</label>
<label className="space-y-2">
@ -2363,7 +2385,7 @@ export default function Expansion() {
<DetailItem label="认证级别" value={selectedItem.certificationLevel || "无"} />
<DetailItem label="办公地址" value={selectedItem.officeAddress || "无"} className="sm:col-span-2" />
<DetailItem label="聚焦行业" value={selectedItem.channelIndustry || "无"} icon={<Building2 className="h-3 w-3" />} />
<DetailItem label="营收规模" value={selectedItem.revenue || "无"} />
<DetailItem label={CHANNEL_REVENUE_LABEL} value={selectedItem.revenue || "无"} />
<DetailItem label="人员规模" value={`${selectedItem.size ?? 0}`} />
<DetailItem label="建立联系时间" value={selectedItem.establishedDate || "无"} icon={<Calendar className="h-3 w-3" />} />
<DetailItem label="合作意向" value={selectedItem.intent || "无"} />

View File

@ -131,7 +131,7 @@ with column_comments(table_name, column_name, comment_text) as (
('crm_channel_expansion', 'channel_industry', '聚焦行业'),
('crm_channel_expansion', 'certification_level', '认证级别'),
('crm_channel_expansion', 'industry', '行业(兼容旧字段)'),
('crm_channel_expansion', 'annual_revenue', '营收'),
('crm_channel_expansion', 'annual_revenue', '度营业额(万元)'),
('crm_channel_expansion', 'staff_size', '人员规模'),
('crm_channel_expansion', 'contact_established_date', '建立联系日期'),
('crm_channel_expansion', 'intent_level', '合作意向'),

View File

@ -837,7 +837,7 @@ WITH column_comments(table_name, column_name, comment_text) AS (
('crm_channel_expansion', 'channel_industry', '聚焦行业'),
('crm_channel_expansion', 'certification_level', '认证级别'),
('crm_channel_expansion', 'industry', '行业(兼容旧字段)'),
('crm_channel_expansion', 'annual_revenue', '营收'),
('crm_channel_expansion', 'annual_revenue', '度营业额(万元)'),
('crm_channel_expansion', 'staff_size', '人员规模'),
('crm_channel_expansion', 'contact_established_date', '建立联系日期'),
('crm_channel_expansion', 'intent_level', '合作意向'),

View File

@ -0,0 +1,297 @@
set search_path to public;
do $$
declare
v_system_id bigint;
v_frontend_home_id bigint;
v_todo_perm_id bigint;
v_activity_perm_id bigint;
v_has_role_permission_tenant boolean;
begin
-- 1. 检查 sys_role_permission 是否带 tenant_id
select exists (
select 1
from information_schema.columns
where table_schema = current_schema()
and table_name = 'sys_role_permission'
and column_name = 'tenant_id'
) into v_has_role_permission_tenant;
-- 2. 定位系统管理
select perm_id
into v_system_id
from sys_permission
where code = 'system'
and coalesce(is_deleted, 0) = 0
order by perm_id
limit 1;
if v_system_id is null then
raise exception '未找到 system 权限,请先确认系统管理目录存在';
end if;
-- 3. 创建或修复“前台首页”分组
select perm_id
into v_frontend_home_id
from sys_permission
where code = 'menu:frontend-home'
order by coalesce(is_deleted, 0) asc, perm_id asc
limit 1;
if v_frontend_home_id is null then
insert into sys_permission (
parent_id, name, code, perm_type, level, path, component, icon,
sort_order, is_visible, status, description, meta, is_deleted, created_at, updated_at
) values (
v_system_id,
'前台首页',
'menu:frontend-home',
'menu',
2,
null,
null,
'HomeOutlined',
7,
0,
1,
'前台首页相关权限分组',
'{}'::jsonb,
0,
now(),
now()
)
returning perm_id into v_frontend_home_id;
else
update sys_permission
set parent_id = v_system_id,
name = '前台首页',
code = 'menu:frontend-home',
perm_type = 'menu',
level = 2,
path = null,
component = null,
icon = 'HomeOutlined',
sort_order = 7,
is_visible = 0,
status = 1,
description = '前台首页相关权限分组',
meta = '{}'::jsonb,
is_deleted = 0,
updated_at = now()
where perm_id = v_frontend_home_id;
end if;
-- 4. 创建或修复“查看首页待办卡片”
select perm_id
into v_todo_perm_id
from sys_permission
where code = 'dashboard_todo_card:view'
order by coalesce(is_deleted, 0) asc, perm_id asc
limit 1;
if v_todo_perm_id is null then
insert into sys_permission (
parent_id, name, code, perm_type, level, path, component, icon,
sort_order, is_visible, status, description, meta, is_deleted, created_at, updated_at
) values (
v_frontend_home_id,
'查看首页待办卡片',
'dashboard_todo_card:view',
'button',
3,
null,
null,
null,
20,
1,
1,
'控制首页待办事项卡片是否可见',
'{}'::jsonb,
0,
now(),
now()
)
returning perm_id into v_todo_perm_id;
else
update sys_permission
set parent_id = v_frontend_home_id,
name = '查看首页待办卡片',
code = 'dashboard_todo_card:view',
perm_type = 'button',
level = 3,
path = null,
component = null,
icon = null,
sort_order = 20,
is_visible = 1,
status = 1,
description = '控制首页待办事项卡片是否可见',
meta = '{}'::jsonb,
is_deleted = 0,
updated_at = now()
where perm_id = v_todo_perm_id;
end if;
-- 5. 创建或修复“查看首页最新动态卡片”
select perm_id
into v_activity_perm_id
from sys_permission
where code = 'dashboard_activity_card:view'
order by coalesce(is_deleted, 0) asc, perm_id asc
limit 1;
if v_activity_perm_id is null then
insert into sys_permission (
parent_id, name, code, perm_type, level, path, component, icon,
sort_order, is_visible, status, description, meta, is_deleted, created_at, updated_at
) values (
v_frontend_home_id,
'查看首页最新动态卡片',
'dashboard_activity_card:view',
'button',
3,
null,
null,
null,
21,
1,
1,
'控制首页最新动态卡片是否可见',
'{}'::jsonb,
0,
now(),
now()
)
returning perm_id into v_activity_perm_id;
else
update sys_permission
set parent_id = v_frontend_home_id,
name = '查看首页最新动态卡片',
code = 'dashboard_activity_card:view',
perm_type = 'button',
level = 3,
path = null,
component = null,
icon = null,
sort_order = 21,
is_visible = 1,
status = 1,
description = '控制首页最新动态卡片是否可见',
meta = '{}'::jsonb,
is_deleted = 0,
updated_at = now()
where perm_id = v_activity_perm_id;
end if;
-- 6. 软删重复的前台首页分组
update sys_permission
set is_deleted = 1,
updated_at = now()
where code = 'menu:frontend-home'
and perm_id <> v_frontend_home_id;
-- 7. 软删重复的待办权限
update sys_permission
set is_deleted = 1,
updated_at = now()
where code = 'dashboard_todo_card:view'
and perm_id <> v_todo_perm_id;
-- 8. 软删重复的动态权限
update sys_permission
set is_deleted = 1,
updated_at = now()
where code = 'dashboard_activity_card:view'
and perm_id <> v_activity_perm_id;
-- 9. 给管理员类角色补齐 system / 前台首页 / 待办卡片 / 最新动态卡片 权限
if v_has_role_permission_tenant then
insert into sys_role_permission (role_id, perm_id, tenant_id, is_deleted, created_at, updated_at)
select
r.role_id,
p.perm_id,
r.tenant_id,
0,
now(),
now()
from sys_role r
join sys_permission p
on p.code in ('system', 'menu:frontend-home', 'dashboard_todo_card:view', 'dashboard_activity_card:view')
and coalesce(p.is_deleted, 0) = 0
where coalesce(r.is_deleted, 0) = 0
and (
r.role_code in ('TENANT_ADMIN', 'ADMIN', 'SYS_ADMIN', 'PLATFORM_ADMIN', 'SUPER_ADMIN')
or r.role_name ilike '%管理员%'
or r.role_name ilike '%admin%'
)
and not exists (
select 1
from sys_role_permission rp
where rp.role_id = r.role_id
and rp.perm_id = p.perm_id
);
update sys_role_permission rp
set tenant_id = coalesce(rp.tenant_id, r.tenant_id),
is_deleted = 0,
updated_at = now()
from sys_role r,
sys_permission p
where rp.role_id = r.role_id
and p.perm_id = rp.perm_id
and coalesce(r.is_deleted, 0) = 0
and p.code in ('system', 'menu:frontend-home', 'dashboard_todo_card:view', 'dashboard_activity_card:view')
and (
r.role_code in ('TENANT_ADMIN', 'ADMIN', 'SYS_ADMIN', 'PLATFORM_ADMIN', 'SUPER_ADMIN')
or r.role_name ilike '%管理员%'
or r.role_name ilike '%admin%'
);
else
insert into sys_role_permission (role_id, perm_id, is_deleted, created_at, updated_at)
select
r.role_id,
p.perm_id,
0,
now(),
now()
from sys_role r
join sys_permission p
on p.code in ('system', 'menu:frontend-home', 'dashboard_todo_card:view', 'dashboard_activity_card:view')
and coalesce(p.is_deleted, 0) = 0
where coalesce(r.is_deleted, 0) = 0
and (
r.role_code in ('TENANT_ADMIN', 'ADMIN', 'SYS_ADMIN', 'PLATFORM_ADMIN', 'SUPER_ADMIN')
or r.role_name ilike '%管理员%'
or r.role_name ilike '%admin%'
)
and not exists (
select 1
from sys_role_permission rp
where rp.role_id = r.role_id
and rp.perm_id = p.perm_id
);
update sys_role_permission rp
set is_deleted = 0,
updated_at = now()
where exists (
select 1
from sys_role r
where r.role_id = rp.role_id
and coalesce(r.is_deleted, 0) = 0
and (
r.role_code in ('TENANT_ADMIN', 'ADMIN', 'SYS_ADMIN', 'PLATFORM_ADMIN', 'SUPER_ADMIN')
or r.role_name ilike '%管理员%'
or r.role_name ilike '%admin%'
)
)
and exists (
select 1
from sys_permission p
where p.perm_id = rp.perm_id
and p.code in ('system', 'menu:frontend-home', 'dashboard_todo_card:view', 'dashboard_activity_card:view')
and coalesce(p.is_deleted, 0) = 0
);
end if;
end
$$;