feat: fictional demo persona (profile seed), word export mirrors preview layout, print scrollbar fix
This commit is contained in:
+17
-2
@@ -370,9 +370,9 @@ export const DEMO_ROWS = [
|
|||||||
['学生工作', '院学生会宣传部副部长', '主导', '院级', null, '院学生会', '2025-09-01', null, 'ongoing',
|
['学生工作', '院学生会宣传部副部长', '主导', '院级', null, '院学生会', '2025-09-01', null, 'ongoing',
|
||||||
'统筹部门工作,管理 8 名干事,主导院级活动宣传方案', '', ['管理'], ['综合素养', '职业发展'],
|
'统筹部门工作,管理 8 名干事,主导院级活动宣传方案', '', ['管理'], ['综合素养', '职业发展'],
|
||||||
{ duties: '部门统筹、活动宣传方案设计', term: '2025.09 - 至今' }, {}],
|
{ duties: '部门统筹、活动宣传方案设计', term: '2025.09 - 至今' }, {}],
|
||||||
['荣誉惩处', '校级二等奖学金', '参加', '校级', null, '重庆城市科技学院', '2025-10-15', '2025-10-15', 'finished',
|
['荣誉惩处', '校级二等奖学金', '参加', '校级', null, '云川理工学院', '2025-10-15', '2025-10-15', 'finished',
|
||||||
'2024-2025 学年综合测评前列', '二等奖学金', ['奖学金'], ['学业深造'],
|
'2024-2025 学年综合测评前列', '二等奖学金', ['奖学金'], ['学业深造'],
|
||||||
{ issued_by: '重庆城市科技学院' }, {}],
|
{ issued_by: '云川理工学院' }, {}],
|
||||||
['荣誉惩处', '优秀共青团员', '参加', '校级', null, '校团委', '2025-05-04', '2025-05-04', 'finished',
|
['荣誉惩处', '优秀共青团员', '参加', '校级', null, '校团委', '2025-05-04', '2025-05-04', 'finished',
|
||||||
'五四表彰', '优秀共青团员', ['表彰'], ['综合素养'],
|
'五四表彰', '优秀共青团员', ['表彰'], ['综合素养'],
|
||||||
{ issued_by: '共青团重庆城市科技学院委员会' }, {}],
|
{ issued_by: '共青团重庆城市科技学院委员会' }, {}],
|
||||||
@@ -398,6 +398,19 @@ export const DEMO_ROWS = [
|
|||||||
'参与企业授权渗透测试项目 3 个,编写测试报告', '获实习优秀评价', ['实习', '安全'], ['职业发展'],
|
'参与企业授权渗透测试项目 3 个,编写测试报告', '获实习优秀评价', ['实习', '安全'], ['职业发展'],
|
||||||
{ position: '渗透测试实习生', mentor: '陈工' }, {}]
|
{ position: '渗透测试实习生', mentor: '陈工' }, {}]
|
||||||
];
|
];
|
||||||
|
// 演示个人档案(虚构人设:云川理工学院 · 信息安全 · 林晓阳)
|
||||||
|
export const DEMO_PROFILE = {
|
||||||
|
info: {
|
||||||
|
name: '林晓阳', gender: '男', birth: '2004-06', nation: '汉族', native: '四川成都',
|
||||||
|
political: '共青团员', school: '云川理工学院', college: '信息工程学院', major: '信息安全',
|
||||||
|
className: '信安2201', studentId: '2023110245', degree: '本科', eduLength: '四年',
|
||||||
|
eduStart: '2023-09', eduEnd: '2027-06',
|
||||||
|
majorCourses: '计算机网络、数据库技术、操作系统、信息安全导论、密码学基础、网络安全、云计算与虚拟化技术',
|
||||||
|
phone: '138****8000', email: 'linxy.demo@example.com', homepage: 'https://blog.example.com/linxy',
|
||||||
|
photoUrl: '', selfIntro: '性格沉稳踏实,做事有计划性,习惯提前预判风险并准备预案;对网络安全方向有持续热情,动手能力强,能独立完成从方案设计到落地验证的完整流程;责任心强,注重细节与条理,是可靠的技术执行者。'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 学期: { term, gpa, rank, total, courses: [课程名, 学分, 分数, 绩点][] }
|
// 学期: { term, gpa, rank, total, courses: [课程名, 学分, 分数, 绩点][] }
|
||||||
export const DEMO_SEMESTERS = [
|
export const DEMO_SEMESTERS = [
|
||||||
{ term: '2023-2024-1', gpa: 3.42, rank: 38, total: 156, courses: [
|
{ term: '2023-2024-1', gpa: 3.42, rank: 38, total: 156, courses: [
|
||||||
@@ -1173,6 +1186,8 @@ function listFootprints(d, query) {
|
|||||||
const fpCnt = d.prepare('SELECT COUNT(*) AS c FROM footprint WHERE deleted_at IS NULL').get().c;
|
const fpCnt = d.prepare('SELECT COUNT(*) AS c FROM footprint WHERE deleted_at IS NULL').get().c;
|
||||||
const semCnt = d.prepare('SELECT COUNT(*) AS c FROM semester WHERE deleted_at IS NULL').get().c;
|
const semCnt = d.prepare('SELECT COUNT(*) AS c FROM semester WHERE deleted_at IS NULL').get().c;
|
||||||
if (fpCnt > 0 || semCnt > 0) return { seeded: false, footprints: fpCnt, semesters: semCnt };
|
if (fpCnt > 0 || semCnt > 0) return { seeded: false, footprints: fpCnt, semesters: semCnt };
|
||||||
|
// 个人档案(简历与个人信息页展示用)
|
||||||
|
handleCommand('profile_upsert', { content: DEMO_PROFILE });
|
||||||
const typeIds = Object.fromEntries(d.prepare('SELECT id, name FROM footprint_type').all().map((r) => [r.name, r.id]));
|
const typeIds = Object.fromEntries(d.prepare('SELECT id, name FROM footprint_type').all().map((r) => [r.name, r.id]));
|
||||||
const dimIds = Object.fromEntries(d.prepare('SELECT id, name FROM dimension').all().map((r) => [r.name, r.id]));
|
const dimIds = Object.fromEntries(d.prepare('SELECT id, name FROM dimension').all().map((r) => [r.name, r.id]));
|
||||||
let n = 0;
|
let n = 0;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function AppShell() {
|
|||||||
<Sidebar open={navOpen} onClose={() => setNavOpen(false)} />
|
<Sidebar open={navOpen} onClose={() => setNavOpen(false)} />
|
||||||
<div className="flex min-w-0 flex-1 flex-col">
|
<div className="flex min-w-0 flex-1 flex-col">
|
||||||
<Header onMenu={() => setNavOpen(true)} />
|
<Header onMenu={() => setNavOpen(true)} />
|
||||||
<main className="min-h-0 flex-1 overflow-y-auto">
|
<main className="app-scroll min-h-0 flex-1 overflow-y-auto">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+62
-60
@@ -23,14 +23,14 @@ export const RESUME_SECTION_DEFS: ResumeSectionDef[] = [
|
|||||||
{ key: 'self', defaultTitle: '自我评价', kind: 'text', fixed: true },
|
{ key: 'self', defaultTitle: '自我评价', kind: 'text', fixed: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 首次使用种子内容(IT/网安方向示例,可自由修改)
|
// 首次使用种子内容(虚构示例,可自由修改)
|
||||||
const SEED: Record<string, { text?: string; items?: { title: string; subtitle?: string; date?: string; desc?: string }[] }> = {
|
const SEED: Record<string, { text?: string; items?: { title: string; subtitle?: string; date?: string; desc?: string }[] }> = {
|
||||||
skills: { text: '路由交换:熟悉TCP/IP协议栈与基础路由交换,掌握VLAN、链路聚合、MSTP、VRRP/VRRP6等园区网冗余技术,熟悉OSPF、RIP、BGP等动态路由协议,具备IPv4/IPv6双栈网络的规划与部署能力。\n网络安全:熟悉防火墙安全区域、ACL访问控制与安全策略配置,掌握NAPT、NAT Server、NAT64地址转换,了解GRE over IPsec隧道与防火墙双机热备。\n网络设备:熟悉华为交换机、路由器、防火墙CLI操作与配置,掌握GVRP、MSTP、VRRP、链路聚合及BFD联动快速收敛,熟悉Wireshark抓包分析。\n运维基础:熟悉RedHat、Ubuntu等Linux系统部署与日常维护,了解Docker、K8s、Prometheus等云原生运维工具。' },
|
skills: { text: '程序设计:熟悉 Python 与 C/C++,掌握常用数据结构与算法,能独立完成中小型工具与脚本开发。\n网络安全:熟悉常见 Web 漏洞原理(SQL 注入、XSS、CSRF 等)与防护方案,掌握 Burp Suite、Nmap、Wireshark 等常用工具。\n网络基础:熟悉 TCP/IP 协议栈与交换路由基础配置,理解 VLAN、NAT、ACL 等核心技术。\n运维基础:熟悉 Linux 常用操作与 Shell 脚本,了解 Docker 容器化部署与 Nginx 服务配置。' },
|
||||||
campus: { items: [
|
campus: { items: [
|
||||||
{ title: '学校图书馆 勤工俭学固定岗', subtitle: '图书管理员', date: '2024.12 - 2026.7', desc: '负责图书上架、整理和维护,定时进行馆内安全巡检,协助读者检索和借阅服务,累计服务读者超2000人次,零服务投诉。这段经历培养了高度的责任意识与服务意识,锻炼了高效执行和多任务时间管理能力。' },
|
{ title: '校图书馆学生助理', subtitle: '读者服务岗', date: '2024.9 - 2025.6', desc: '负责书库整理与借阅服务,协助开展入馆培训与阅读推广活动,累计服务读者千余人次。培养了责任意识与服务意识,锻炼了多任务时间管理能力。' },
|
||||||
{ title: '学院团总支学生会宣传部 干事', subtitle: '视觉设计', date: '2023.9 - 2024.9', desc: '独立完成学院活动海报、邀请函、KT板、易拉宝等40余份宣传物料设计,参与换届大会、迎新、毕业季等重大活动视觉设计与宣传品制作。负责报告厅后台多媒体设备控制,保障12场大型活动视觉呈现零失误,锻炼了应急处置能力与谨慎细致的工作态度。' },
|
{ title: '院学生会宣传部干事', subtitle: '平面设计与文案', date: '2023.9 - 2024.6', desc: '参与学院活动的海报设计与推文撰写,负责活动现场的后勤与设备保障。锻炼了审美表达、协作沟通与临场应变能力。' },
|
||||||
]},
|
]},
|
||||||
self: { text: '心态沉稳、抗压能力较好,遇到突发状况能冷静应对、稳住局面。做事有计划性,习惯提前预判风险并做好预案,追求稳妥与确定性,执行质量有保障,能在压力下保持稳定输出。责任心强,凡事有始有终,注重细节与条理,能在复杂环境中稳步推进工作,是一名可靠、值得信赖的执行者。' },
|
self: { text: '性格沉稳踏实,做事有计划性,习惯提前预判风险并准备预案;学习能力强,能快速上手新技术与工具;责任心强,注重细节与条理,凡事有始有终,能独立推进任务并在压力下保持稳定输出。' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ export const TEMPLATES: { key: ResumeTemplate; name: string; desc: string }[] =
|
|||||||
|
|
||||||
export const ACCENT_CHOICES = ['#3056d3', '#0f766e', '#7c3aed', '#c2410c', '#0e7490', '#1f2937'];
|
export const ACCENT_CHOICES = ['#3056d3', '#0f766e', '#7c3aed', '#c2410c', '#0e7490', '#1f2937'];
|
||||||
|
|
||||||
// ---------- Word 导出(内联样式,按模板着色) ----------
|
// ---------- Word 导出(按预览布局生成,Word/WPS 直接打开) ----------
|
||||||
|
|
||||||
const esc = (t: unknown) => String(t ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
const esc = (t: unknown) => String(t ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
|
||||||
@@ -171,73 +171,75 @@ export function resumeToWordHtml(
|
|||||||
const scale = cfg.fontScale;
|
const scale = cfg.fontScale;
|
||||||
const pt = (base: number) => (base * scale).toFixed(1) + 'pt';
|
const pt = (base: number) => (base * scale).toFixed(1) + 'pt';
|
||||||
const accent = cfg.accent;
|
const accent = cfg.accent;
|
||||||
const titleStyle: Record<ResumeTemplate, string> = {
|
const P = profile;
|
||||||
classic: 'font-size:' + pt(14) + ';border-bottom:1.5pt solid #333;padding-bottom:3pt;margin:12pt 0 6pt;color:#111',
|
|
||||||
tech: 'font-size:' + pt(14) + ';color:' + accent + ';border-left:3pt solid ' + accent + ';padding-left:6pt;margin:12pt 0 6pt',
|
// 模板标题样式
|
||||||
minimal: 'font-size:' + pt(11.5) + ';color:#555;letter-spacing:1.5pt;text-transform:uppercase;margin:14pt 0 6pt',
|
const titleCss: Record<ResumeTemplate, string> = {
|
||||||
|
classic: 'font-size:' + pt(14) + ';color:#111;border-bottom:1.5pt solid #333;padding-bottom:3pt;margin:14pt 0 7pt',
|
||||||
|
tech: 'font-size:' + pt(14) + ';color:' + accent + ';border-bottom:1pt solid ' + accent + ';padding-bottom:3pt;margin:14pt 0 7pt',
|
||||||
|
minimal: 'font-size:' + pt(11.5) + ';color:#555;letter-spacing:1.5pt;text-transform:uppercase;border-bottom:0.75pt solid #e0e0e0;padding-bottom:3pt;margin:14pt 0 7pt',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 头部:姓名 + 校园信息 + 联系方式(左) + 证件照(右)
|
||||||
|
const nameColor = cfg.template === 'tech' ? accent : '#111';
|
||||||
|
const schoolLine = [P.school, P.college, P.major, P.degree].filter(v => v !== '').map(v => esc(v)).join(' · ');
|
||||||
|
const contactLine = [P.phone, P.email, P.homepage].filter(v => v !== '').map(v => esc(v)).join(' · ');
|
||||||
|
const photo = photoUrl && P.photoUrl
|
||||||
|
? '<td style="width:' + (90 * scale).toFixed(0) + 'pt;text-align:right;vertical-align:top">' +
|
||||||
|
'<img src="' + esc(P.photoUrl) + '" alt="证件照" style="width:' + (90 * scale).toFixed(0) + 'pt;height:' + (120 * scale).toFixed(0) + 'pt;object-fit:cover" /></td>'
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const head = '<table style="width:100%;border-collapse:collapse"><tr>' +
|
||||||
|
'<td style="vertical-align:top">' +
|
||||||
|
'<div style="font-size:' + pt(20) + ';font-weight:bold;color:' + nameColor + '">' + esc(P.name || '个人简历') + '</div>' +
|
||||||
|
(schoolLine ? '<div style="color:#555;font-size:' + pt(11) + ';margin-top:3pt">' + schoolLine + '</div>' : '') +
|
||||||
|
(contactLine ? '<div style="color:#555;font-size:' + pt(11) + ';margin-top:2pt">' + contactLine + '</div>' : '') +
|
||||||
|
'</td>' + photo + '</tr></table>';
|
||||||
|
|
||||||
|
// 条目:标题(左) + 日期(右) 表格行 + 副标题 + 描述
|
||||||
|
const itemHtml = (it: { title: string; subtitle?: string; date?: string; desc?: string }) =>
|
||||||
|
'<table style="width:100%;border-collapse:collapse;margin-top:' + (cfg.density === 'compact' ? 4 : 7) + 'pt"><tr>' +
|
||||||
|
'<td style="font-weight:bold;font-size:' + pt(12) + '">' + esc(it.title) + '</td>' +
|
||||||
|
'<td style="text-align:right;color:#666;font-size:' + pt(10.5) + ';white-space:nowrap">' + esc(it.date ?? '') + '</td></tr></table>' +
|
||||||
|
(it.subtitle ? '<div style="color:#666;font-size:' + pt(10.5) + '">' + esc(it.subtitle) + '</div>' : '') +
|
||||||
|
(it.desc ? '<div style="color:#444;font-size:' + pt(11) + ';line-height:1.55;margin-top:2pt">' + esc(it.desc) + '</div>' : '');
|
||||||
|
|
||||||
const secHtml = sections.map(sec => {
|
const secHtml = sections.map(sec => {
|
||||||
let inner = '';
|
let inner = '';
|
||||||
if (sec.kind === 'info') {
|
if (sec.kind === 'edu') {
|
||||||
const rows: [string, string][] = ([
|
const rows: [string, string][] = ([
|
||||||
['姓名', P0(profile).name], ['性别', P0(profile).gender], ['出生年月', P0(profile).birth],
|
['学校', P.school ?? ''], ['专业', P.major ?? ''], ['学制', P.eduLength ?? ''],
|
||||||
['民族', P0(profile).nation], ['籍贯', P0(profile).native], ['政治面貌', P0(profile).political],
|
['学历', P.degree ?? ''], ['入学', P.eduStart ?? ''], ['毕业', P.eduEnd ?? ''],
|
||||||
['学校', P0(profile).school], ['学院', P0(profile).college], ['专业', P0(profile).major],
|
|
||||||
['班级', P0(profile).className], ['学号', P0(profile).studentId], ['学历', P0(profile).degree],
|
|
||||||
['手机', P0(profile).phone], ['邮箱', P0(profile).email],
|
|
||||||
] as [string, string][]).filter(r => r[1] !== '');
|
] as [string, string][]).filter(r => r[1] !== '');
|
||||||
inner = '<table style="width:100%;border-collapse:collapse;font-size:' + pt(12) + '">' +
|
inner = '<table style="width:100%;border-collapse:collapse;font-size:' + pt(12) + '">' +
|
||||||
rows.map(([k, v], i) =>
|
rows.map(kv =>
|
||||||
|
'<tr><td style="width:80px;padding:3px 6px;color:#555">' + esc(kv[0]) + '</td>' +
|
||||||
|
'<td style="padding:3px 6px">' + esc(kv[1]) + '</td></tr>').join('') + '</table>' +
|
||||||
|
(P.majorCourses ? '<div style="margin-top:6pt;font-size:' + pt(11.5) + ';line-height:1.6"><b>主修课程:</b>' + esc(P.majorCourses) + '</div>' : '');
|
||||||
|
} else if (sec.kind === 'info') {
|
||||||
|
const rows: [string, string][] = ([
|
||||||
|
['姓名', P.name ?? ''], ['性别', P.gender ?? ''], ['出生年月', P.birth ?? ''],
|
||||||
|
['民族', P.nation ?? ''], ['籍贯', P.native ?? ''], ['政治面貌', P.political ?? ''],
|
||||||
|
['班级', P.className ?? ''], ['学号', P.studentId ?? ''], ['学历', P.degree ?? ''],
|
||||||
|
['手机', P.phone ?? ''], ['邮箱', P.email ?? ''],
|
||||||
|
] as [string, string][]).filter(r => r[1] !== '');
|
||||||
|
inner = '<table style="width:100%;border-collapse:collapse;font-size:' + pt(12) + '">' +
|
||||||
|
rows.map((kv, i) =>
|
||||||
(i % 2 === 0 ? '<tr>' : '') +
|
(i % 2 === 0 ? '<tr>' : '') +
|
||||||
'<td style="width:80px;padding:3px 6px;color:#555">' + esc(k) + '</td>' +
|
'<td style="width:80px;padding:3px 6px;color:#555">' + esc(kv[0]) + '</td>' +
|
||||||
'<td style="padding:3px 6px">' + esc(v) + '</td>' +
|
'<td style="padding:3px 6px">' + esc(kv[1]) + '</td>' +
|
||||||
(i % 2 === 1 ? '</tr>' : '')
|
(i % 2 === 1 ? '</tr>' : '')).join('') + '</table>';
|
||||||
).join('') + '</table>';
|
|
||||||
} else if (sec.kind === 'edu') {
|
|
||||||
const rows: [string, string][] = ([
|
|
||||||
['学校', profile.school ?? ''], ['专业', profile.major ?? ''], ['学制', profile.eduLength ?? ''],
|
|
||||||
['学历', profile.degree ?? ''], ['入学', profile.eduStart ?? ''], ['毕业', profile.eduEnd ?? ''],
|
|
||||||
] as [string, string][]).filter(r => r[1] !== '');
|
|
||||||
inner = '<table style="width:100%;border-collapse:collapse;font-size:' + pt(12) + '">' +
|
|
||||||
rows.map(function (kv) {
|
|
||||||
return '<tr><td style="width:80px;padding:3px 6px;color:#555">' + esc(kv[0]) + '</td>' +
|
|
||||||
'<td style="padding:3px 6px">' + esc(kv[1]) + '</td></tr>';
|
|
||||||
}).join('') + '</table>' +
|
|
||||||
(profile.majorCourses ? '<div style="margin-top:6pt"><b style="font-size:' + pt(11) + '">主修课程:</b>' +
|
|
||||||
'<span style="font-size:' + pt(11.5) + '">' + esc(profile.majorCourses) + '</span></div>' : '');
|
|
||||||
} else if (sec.kind === 'list') {
|
} else if (sec.kind === 'list') {
|
||||||
inner = '<div style="margin-bottom:6pt">' + (sec.items ?? []).map(it => {
|
inner = '<div>' + (sec.items ?? []).map(itemHtml).join('') + '</div>';
|
||||||
const sub = it.subtitle ? '<div style="color:#666;font-size:' + pt(10.5) + '">' + esc(it.subtitle) + '</div>' : '';
|
|
||||||
const desc = it.desc ? '<div style="color:#444;font-size:' + pt(11) + ';line-height:1.5">' + esc(it.desc) + '</div>' : '';
|
|
||||||
return '<div style="margin-bottom:7pt">' +
|
|
||||||
'<div style="display:flex;justify-content:space-between"><b style="font-size:' + pt(12) + '">' + esc(it.title) + '</b>' +
|
|
||||||
'<span style="color:#666;font-size:' + pt(10.5) + '">' + esc(it.date ?? '') + '</span></div>' +
|
|
||||||
sub + desc + '</div>';
|
|
||||||
}).join('') + '</div>';
|
|
||||||
} else {
|
} else {
|
||||||
inner = '<p style="font-size:' + pt(12) + ';line-height:1.7;margin:0">' + esc(sec.text ?? '') + '</p>';
|
inner = '<p style="font-size:' + pt(12) + ';line-height:1.7;margin:0">' + esc(sec.text ?? '').replace(/\n/g, '<br/>') + '</p>';
|
||||||
}
|
}
|
||||||
return '<h2 style="' + titleStyle[cfg.template] + '">' + esc(sec.title) + '</h2>' + inner;
|
return '<div style="' + titleCss[cfg.template] + '">' + esc(sec.title) + '</div>' + inner;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
const photo = photoUrl && profile.photoUrl
|
|
||||||
? '<div style="text-align:right"><img src="' + esc(profile.photoUrl) + '" alt="证件照" style="width:' + (90 * scale).toFixed(0) + 'pt;height:' + (120 * scale).toFixed(0) + 'pt;object-fit:cover" /></div>'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const headSub = [P0(profile).school, P0(profile).college, P0(profile).major, P0(profile).degree].filter(v => v !== '').map(v => esc(v)).join(' · ');
|
|
||||||
|
|
||||||
return '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word">' +
|
return '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word">' +
|
||||||
'<head><meta charset="utf-8"><title>' + esc(P0(profile).name || '个人简历') + '</title></head>' +
|
'<head><meta charset="utf-8"><title>' + esc(P.name || '个人简历') + '</title>' +
|
||||||
'<body style="font-family:SimSun,Calibri,sans-serif;margin:36pt;color:#222">' +
|
'<!--[if gte mso 9]><xml><w:WordDocument><w:View>Print</w:View></w:WordDocument></xml><![endif]--></head>' +
|
||||||
'<div style="display:flex;justify-content:space-between;align-items:flex-start">' +
|
'<body style="font-family:Calibri,SimSun,sans-serif;margin:36pt;color:#222">' + head + secHtml + '</body></html>';
|
||||||
'<h1 style="font-size:' + pt(20) + ';margin:0 0 4pt;color:' + (cfg.template === 'tech' ? accent : '#111') + '">' + esc(P0(profile).name || '个人简历') + '</h1>' + photo + '</div>' +
|
|
||||||
(headSub ? '<div style="color:#555;font-size:' + pt(11) + ';margin-bottom:8pt">' + headSub + '</div>' : '') +
|
|
||||||
secHtml +
|
|
||||||
'</body></html>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 避免循环引用的小工具
|
|
||||||
function P0(p: Partial<ProfileInfo> | undefined): Partial<ProfileInfo> {
|
|
||||||
return p ?? {};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -157,12 +157,15 @@ input[type='number'] { -moz-appearance: textfield; appearance: textfield; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 简历打印 / 导出 ---------- */
|
/* ---------- 简历打印 / 导出 ---------- */
|
||||||
|
@page { size: A4; margin: 10mm; }
|
||||||
@media print {
|
@media print {
|
||||||
.no-print { display: none !important; }
|
.no-print { display: none !important; }
|
||||||
html, body, #root { height: auto !important; background: #fff !important; }
|
html, body, #root { height: auto !important; background: #fff !important; }
|
||||||
body { padding: 0 !important; }
|
body { padding: 0 !important; }
|
||||||
.print-scroll { overflow: visible !important; height: auto !important; }
|
.app-scroll, .print-scroll { overflow: visible !important; height: auto !important; max-height: none !important; }
|
||||||
.print-sheet { border: none !important; box-shadow: none !important; margin: 0 !important; max-width: none !important; width: 100% !important; padding: 0 !important; }
|
.print-sheet { border: none !important; box-shadow: none !important; margin: 0 !important; max-width: none !important; width: 100% !important; padding: 0 !important; border-radius: 0 !important; }
|
||||||
|
* { scrollbar-width: none !important; }
|
||||||
|
*::-webkit-scrollbar { width: 0 !important; height: 0 !important; display: none !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 简历模板 ---------- */
|
/* ---------- 简历模板 ---------- */
|
||||||
|
|||||||
+3
-3
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
|||||||
import{r as g,f as v,j as t,S as j,t as S}from"./index-DLLvDi5q.js";import{C as c,a as d,b as x}from"./card-DFL6pamx.js";import{u as N,i as u,a as m}from"./useChart-BzkWtSPl.js";import{u as k,i as w,a as C,b as L,c as R,d as $,e as z,f as T}from"./echarts-BIWefrIc.js";k([w,C,L,R,$,z,T]);function q(){const[l,n]=g.useState(""),i=N(),e=v({queryKey:["stats",l||void 0],queryFn:()=>S(l?Number(l):void 0)}).data,p=u("2"),r=u("3"),h=m(()=>({grid:{left:34,right:12,top:14,bottom:24},tooltip:{trigger:"axis"},xAxis:{type:"category",data:(e==null?void 0:e.monthly.map(a=>a.ym.slice(2)))??[],axisLine:{lineStyle:{color:r}},axisTick:{show:!1},axisLabel:{color:r,fontSize:10}},yAxis:{type:"value",splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:r,fontSize:10}},series:[{type:"bar",data:(e==null?void 0:e.monthly.map(a=>a.count))??[],barMaxWidth:18,itemStyle:{color:"#3056d3",borderRadius:[3,3,0,0]}}]}),[e,i]),b=m(()=>{const a=((e==null?void 0:e.by_type)??[]).filter(s=>s.count>0);return{tooltip:{trigger:"item"},legend:{orient:"vertical",right:4,top:"middle",textStyle:{color:p,fontSize:11},itemWidth:10,itemHeight:10},series:[{type:"pie",radius:["48%","72%"],center:["34%","50%"],label:{show:!1},data:a.map(s=>({name:s.label,value:s.count,itemStyle:{color:s.color||"#64748b"}}))}]}},[e,i]),y=m(()=>{const a=((e==null?void 0:e.by_level)??[]).filter(s=>s.count>0).slice().reverse();return{grid:{left:64,right:24,top:8,bottom:22},tooltip:{trigger:"axis"},xAxis:{type:"value",splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:r,fontSize:10}},yAxis:{type:"category",data:a.map(s=>s.label),axisLine:{show:!1},axisTick:{show:!1},axisLabel:{color:p,fontSize:11}},series:[{type:"bar",data:a.map(s=>({value:s.count,itemStyle:{color:s.color||"#3056d3",borderRadius:[0,3,3,0]}})),barMaxWidth:14}]}},[e,i]),f=((e==null?void 0:e.metrics)??[]).slice(0,3);return t.jsxs("div",{className:"mx-auto max-w-[1080px] px-3 py-4 sm:px-6 sm:py-5",children:[t.jsxs("div",{className:"mb-4 flex flex-wrap items-center justify-between gap-3",children:[t.jsxs("div",{className:"flex flex-wrap gap-3",children:[t.jsx(o,{label:"总记录",value:(e==null?void 0:e.total)??0}),t.jsx(o,{label:l?l+" 年新增":"本年新增",value:(e==null?void 0:e.total_this_year)??0}),t.jsx(o,{label:"附件总数",value:(e==null?void 0:e.attachment_total)??0}),f.map(a=>t.jsx(o,{label:a.label,value:a.value,unit:a.unit??""},a.key))]}),t.jsx(j,{className:"w-[120px]",value:l,onChange:a=>n(a.target.value),options:[{value:"",label:"全部年份"},...((e==null?void 0:e.years)??[]).map(a=>({value:String(a),label:a+" 年"}))]})]}),!(e!=null&&e.metrics.length)&&t.jsx("div",{className:"mb-4 rounded-md border border-dashed border-line px-4 py-2 text-[12px] text-ink-3",children:"提示:在「统计」配置中注册指标(如志愿时长、读书本数),带单位的数值字段即可在此量化展示。"}),t.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-4",children:[t.jsxs(c,{children:[t.jsx(d,{title:"近 12 个月活跃度"}),t.jsx(x,{children:t.jsx("div",{ref:h,className:"h-[220px]"})})]}),t.jsxs(c,{children:[t.jsx(d,{title:"类型分布"}),t.jsx(x,{children:t.jsx("div",{ref:b,className:"h-[220px]"})})]}),t.jsxs(c,{className:"lg:col-span-2",children:[t.jsx(d,{title:"级别分布"}),t.jsx(x,{children:t.jsx("div",{ref:y,className:"h-[200px]"})})]})]})]})}function o({label:l,value:n,unit:i}){return t.jsxs("div",{className:"min-w-[120px] rounded-lg border border-line bg-surface px-4 py-3 shadow-card",children:[t.jsx("div",{className:"text-[12px] text-ink-3",children:l}),t.jsxs("div",{className:"num mt-0.5 text-[22px] font-semibold leading-tight text-ink-1",children:[n,i&&t.jsx("span",{className:"ml-1 text-[12px] font-normal text-ink-3",children:i})]})]})}export{q as DashboardPage};
|
import{r as g,f as v,j as t,S as j,t as S}from"./index-CshikkKk.js";import{C as c,a as d,b as x}from"./card-D52MYnXt.js";import{u as N,i as u,a as m}from"./useChart-Dwieg_pe.js";import{u as k,i as w,a as C,b as L,c as R,d as $,e as z,f as T}from"./echarts-BIWefrIc.js";k([w,C,L,R,$,z,T]);function q(){const[l,n]=g.useState(""),i=N(),e=v({queryKey:["stats",l||void 0],queryFn:()=>S(l?Number(l):void 0)}).data,p=u("2"),r=u("3"),h=m(()=>({grid:{left:34,right:12,top:14,bottom:24},tooltip:{trigger:"axis"},xAxis:{type:"category",data:(e==null?void 0:e.monthly.map(a=>a.ym.slice(2)))??[],axisLine:{lineStyle:{color:r}},axisTick:{show:!1},axisLabel:{color:r,fontSize:10}},yAxis:{type:"value",splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:r,fontSize:10}},series:[{type:"bar",data:(e==null?void 0:e.monthly.map(a=>a.count))??[],barMaxWidth:18,itemStyle:{color:"#3056d3",borderRadius:[3,3,0,0]}}]}),[e,i]),b=m(()=>{const a=((e==null?void 0:e.by_type)??[]).filter(s=>s.count>0);return{tooltip:{trigger:"item"},legend:{orient:"vertical",right:4,top:"middle",textStyle:{color:p,fontSize:11},itemWidth:10,itemHeight:10},series:[{type:"pie",radius:["48%","72%"],center:["34%","50%"],label:{show:!1},data:a.map(s=>({name:s.label,value:s.count,itemStyle:{color:s.color||"#64748b"}}))}]}},[e,i]),y=m(()=>{const a=((e==null?void 0:e.by_level)??[]).filter(s=>s.count>0).slice().reverse();return{grid:{left:64,right:24,top:8,bottom:22},tooltip:{trigger:"axis"},xAxis:{type:"value",splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:r,fontSize:10}},yAxis:{type:"category",data:a.map(s=>s.label),axisLine:{show:!1},axisTick:{show:!1},axisLabel:{color:p,fontSize:11}},series:[{type:"bar",data:a.map(s=>({value:s.count,itemStyle:{color:s.color||"#3056d3",borderRadius:[0,3,3,0]}})),barMaxWidth:14}]}},[e,i]),f=((e==null?void 0:e.metrics)??[]).slice(0,3);return t.jsxs("div",{className:"mx-auto max-w-[1080px] px-3 py-4 sm:px-6 sm:py-5",children:[t.jsxs("div",{className:"mb-4 flex flex-wrap items-center justify-between gap-3",children:[t.jsxs("div",{className:"flex flex-wrap gap-3",children:[t.jsx(o,{label:"总记录",value:(e==null?void 0:e.total)??0}),t.jsx(o,{label:l?l+" 年新增":"本年新增",value:(e==null?void 0:e.total_this_year)??0}),t.jsx(o,{label:"附件总数",value:(e==null?void 0:e.attachment_total)??0}),f.map(a=>t.jsx(o,{label:a.label,value:a.value,unit:a.unit??""},a.key))]}),t.jsx(j,{className:"w-[120px]",value:l,onChange:a=>n(a.target.value),options:[{value:"",label:"全部年份"},...((e==null?void 0:e.years)??[]).map(a=>({value:String(a),label:a+" 年"}))]})]}),!(e!=null&&e.metrics.length)&&t.jsx("div",{className:"mb-4 rounded-md border border-dashed border-line px-4 py-2 text-[12px] text-ink-3",children:"提示:在「统计」配置中注册指标(如志愿时长、读书本数),带单位的数值字段即可在此量化展示。"}),t.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-4",children:[t.jsxs(c,{children:[t.jsx(d,{title:"近 12 个月活跃度"}),t.jsx(x,{children:t.jsx("div",{ref:h,className:"h-[220px]"})})]}),t.jsxs(c,{children:[t.jsx(d,{title:"类型分布"}),t.jsx(x,{children:t.jsx("div",{ref:b,className:"h-[220px]"})})]}),t.jsxs(c,{className:"lg:col-span-2",children:[t.jsx(d,{title:"级别分布"}),t.jsx(x,{children:t.jsx("div",{ref:y,className:"h-[200px]"})})]})]})]})}function o({label:l,value:n,unit:i}){return t.jsxs("div",{className:"min-w-[120px] rounded-lg border border-line bg-surface px-4 py-3 shadow-card",children:[t.jsx("div",{className:"text-[12px] text-ink-3",children:l}),t.jsxs("div",{className:"num mt-0.5 text-[22px] font-semibold leading-tight text-ink-1",children:[n,i&&t.jsx("span",{className:"ml-1 text-[12px] font-normal text-ink-3",children:i})]})]})}export{q as DashboardPage};
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import{c as R,f as I,j as e,E as O,w as $,u as N,d as f,r as u,g as v,x as H,y as W,B as b,P as q,C as L,z as J,A as T,D as U,m as k,l as F,T as z,I as g,F as Y,H as S,J as E,K as V}from"./index-DLLvDi5q.js";import{C,a as M,b as _}from"./card-DFL6pamx.js";import{u as X,i as Q,a as K}from"./useChart-BzkWtSPl.js";import{u as Z,i as ee,b as se,a as te,c as ae,d as re,e as ne,f as le}from"./echarts-BIWefrIc.js";/**
|
import{c as R,f as I,j as e,E as O,w as $,u as N,d as f,r as u,g as v,x as H,y as W,B as b,P as q,C as L,z as J,A as T,D as U,m as k,l as F,T as z,I as g,F as Y,H as S,J as E,K as V}from"./index-CshikkKk.js";import{C,a as M,b as _}from"./card-D52MYnXt.js";import{u as X,i as Q,a as K}from"./useChart-Dwieg_pe.js";import{u as Z,i as ee,b as se,a as te,c as ae,d as re,e as ne,f as le}from"./echarts-BIWefrIc.js";/**
|
||||||
* @license lucide-react v0.475.0 - ISC
|
* @license lucide-react v0.475.0 - ISC
|
||||||
*
|
*
|
||||||
* This source code is licensed under the ISC license.
|
* This source code is licensed under the ISC license.
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import{c as E,u as P,d as S,r as p,f as U,g as F,j as e,B as f,l as q,a9 as B,F as L,H as k,S as z,I as M,aa as Q,ab as _,ac as v,ad as K}from"./index-DLLvDi5q.js";/**
|
import{c as E,u as P,d as S,r as p,f as U,g as F,j as e,B as f,l as q,a9 as B,F as L,H as k,S as z,I as M,aa as Q,ab as _,ac as v,ad as K}from"./index-CshikkKk.js";/**
|
||||||
* @license lucide-react v0.475.0 - ISC
|
* @license lucide-react v0.475.0 - ISC
|
||||||
*
|
*
|
||||||
* This source code is licensed under the ISC license.
|
* This source code is licensed under the ISC license.
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import{c as fe,j as e,a as N,r as s,b as he,u as ve,d as ge,e as je,f as L,g as ye,I as be,S as k,B as w,P as Q,h as te,L as se,G as ae,R as Ne,i as ne,k as ke,l as we,T as Ce,E as le,m as Ee,n as Se,o as _e,C as ze,p as qe,q as Le,s as Re,t as De,v as Ie}from"./index-DLLvDi5q.js";/**
|
import{c as fe,j as e,a as N,r as s,b as he,u as ve,d as ge,e as je,f as L,g as ye,I as be,S as k,B as w,P as Q,h as te,L as se,G as ae,R as Ne,i as ne,k as ke,l as we,T as Ce,E as le,m as Ee,n as Se,o as _e,C as ze,p as qe,q as Le,s as Re,t as De,v as Ie}from"./index-CshikkKk.js";/**
|
||||||
* @license lucide-react v0.475.0 - ISC
|
* @license lucide-react v0.475.0 - ISC
|
||||||
*
|
*
|
||||||
* This source code is licensed under the ISC license.
|
* This source code is licensed under the ISC license.
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import{c as D,u as K,d as F,f as L,r,g as I,j as e,B as p,P as q,M as O,m as _,l as H,T as P,C as R,N as G,F as U,H as i,I as j,O as W,S as z,Q as J,U as V,q as X}from"./index-DLLvDi5q.js";import{C as Y}from"./card-DFL6pamx.js";/**
|
import{c as D,u as K,d as F,f as L,r,g as I,j as e,B as p,P as q,M as O,m as _,l as H,T as P,C as R,N as G,F as U,H as i,I as j,O as W,S as z,Q as J,U as V,q as X}from"./index-CshikkKk.js";import{C as Y}from"./card-D52MYnXt.js";/**
|
||||||
* @license lucide-react v0.475.0 - ISC
|
* @license lucide-react v0.475.0 - ISC
|
||||||
*
|
*
|
||||||
* This source code is licensed under the ISC license.
|
* This source code is licensed under the ISC license.
|
||||||
@@ -1 +1 @@
|
|||||||
import{j as s,a}from"./index-DLLvDi5q.js";function i({className:r,...e}){return s.jsx("div",{className:a("rounded-lg border border-line bg-surface shadow-card",r),...e})}function t({title:r,extra:e,className:n}){return s.jsxs("div",{className:a("flex min-h-11 flex-wrap items-center justify-between gap-2 border-b border-line px-4 py-1.5",n),children:[s.jsx("h3",{className:"text-[13px] font-semibold text-ink-1",children:r}),e&&s.jsx("div",{className:"flex flex-wrap items-center gap-2",children:e})]})}function c({className:r,...e}){return s.jsx("div",{className:a("p-4",r),...e})}export{i as C,t as a,c as b};
|
import{j as s,a}from"./index-CshikkKk.js";function i({className:r,...e}){return s.jsx("div",{className:a("rounded-lg border border-line bg-surface shadow-card",r),...e})}function t({title:r,extra:e,className:n}){return s.jsxs("div",{className:a("flex min-h-11 flex-wrap items-center justify-between gap-2 border-b border-line px-4 py-1.5",n),children:[s.jsx("h3",{className:"text-[13px] font-semibold text-ink-1",children:r}),e&&s.jsx("div",{className:"flex flex-wrap items-center gap-2",children:e})]})}function c({className:r,...e}){return s.jsx("div",{className:a("p-4",r),...e})}export{i as C,t as a,c as b};
|
||||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
|||||||
import{r}from"./index-DLLvDi5q.js";import{g as f}from"./echarts-BIWefrIc.js";function p(){const[n,o]=r.useState(0);return r.useEffect(()=>{const s=()=>o(t=>t+1);return window.addEventListener("fp-theme-change",s),()=>window.removeEventListener("fp-theme-change",s)},[]),n}function d(n){return getComputedStyle(document.documentElement).getPropertyValue("--text-"+n).trim()||"#454e5c"}function h(n,o){const s=r.useRef(null),[t,i]=r.useState(null),u=r.useRef(n);return u.current=n,r.useEffect(()=>{if(!t)return;const e=f(t);s.current=e,e.setOption(u.current(),{notMerge:!0});const c=new ResizeObserver(()=>e.resize());return c.observe(t),()=>{c.disconnect(),e.dispose(),s.current=null}},[t]),r.useEffect(()=>{var e;(e=s.current)==null||e.setOption(u.current(),{notMerge:!0})},[t,...o]),e=>i(e)}export{h as a,d as i,p as u};
|
import{r}from"./index-CshikkKk.js";import{g as f}from"./echarts-BIWefrIc.js";function p(){const[n,o]=r.useState(0);return r.useEffect(()=>{const s=()=>o(t=>t+1);return window.addEventListener("fp-theme-change",s),()=>window.removeEventListener("fp-theme-change",s)},[]),n}function d(n){return getComputedStyle(document.documentElement).getPropertyValue("--text-"+n).trim()||"#454e5c"}function h(n,o){const s=r.useRef(null),[t,i]=r.useState(null),u=r.useRef(n);return u.current=n,r.useEffect(()=>{if(!t)return;const e=f(t);s.current=e,e.setOption(u.current(),{notMerge:!0});const c=new ResizeObserver(()=>e.resize());return c.observe(t),()=>{c.disconnect(),e.dispose(),s.current=null}},[t]),r.useEffect(()=>{var e;(e=s.current)==null||e.setOption(u.current(),{notMerge:!0})},[t,...o]),e=>i(e)}export{h as a,d as i,p as u};
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
<link rel="icon" href="/icons/pwa-192.png" />
|
<link rel="icon" href="/icons/pwa-192.png" />
|
||||||
<link rel="apple-touch-icon" href="/icons/pwa-192.png" />
|
<link rel="apple-touch-icon" href="/icons/pwa-192.png" />
|
||||||
<title>足迹 Footprints</title>
|
<title>足迹 Footprints</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DLLvDi5q.js"></script>
|
<script type="module" crossorigin src="/assets/index-CshikkKk.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-B7ZSJA0M.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-B0aJKtiv.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user