feat: profile page + resume builder (section reorder/enable, word/pdf export)

This commit is contained in:
root
2026-10-17 12:14:01 +08:00
parent 500d59ba58
commit 013c9fb929
25 changed files with 891 additions and 339 deletions
+23 -1
View File
@@ -1,7 +1,8 @@
import type {
AppInfo, Attachment, Dimension, FootprintDetail, FootprintInput, FootprintQuery,
FootprintRow, FootprintType, FormField, GradeBand, GradesOverview, MetricValue,
StatsOverview, SyncStatus, TaxonomyItem, TimelineGroup
StatsOverview, SyncStatus, TaxonomyItem, TimelineGroup,
ProfileInfo, ResumeConfig,
} from './types';
@@ -212,3 +213,24 @@ export const adminUpdateInvite = (id: number, patch: { remark?: string; valid?:
adminCall<{ id: number }>('/admin/invites/update', { id, ...patch }, baseUrl);
export const adminDeleteInvite = (id: number, baseUrl?: string) =>
adminCall<{ deleted: number }>('/admin/invites/delete', { id }, baseUrl);
// ---- 个人信息 / 简历 ----
export const profileGet = () => call<{ content: { info?: Partial<ProfileInfo>; resume?: ResumeConfig } }>('profile_get', {});
export const profileUpsert = (content: { info?: Partial<ProfileInfo>; resume?: ResumeConfig }) =>
call<{ ok: true }>('profile_upsert', { content });
export async function profilePhotoUpload(file: File): Promise<string> {
const dataB64 = await new Promise<string>((resolve, reject) => {
const fr = new FileReader();
fr.onload = () => resolve(String(fr.result || '').split(',')[1] || '');
fr.onerror = () => reject(new Error('读取文件失败'));
fr.readAsDataURL(file);
});
const r = await fetch(getServerUrl() + '/api/profile_photo', {
method: 'POST',
headers: { 'content-type': 'application/json', authorization: 'Bearer ' + getToken() },
body: JSON.stringify({ data_b64: dataB64, file_name: file.name, mime: file.type || 'image/png' }),
});
const j = await r.json().catch(() => null);
if (!j?.ok) throw new Error(j?.error || '证件照上传失败');
return j.data.url as string;
}