finish app develop

This commit is contained in:
kuangji
2026-05-18 15:50:43 +08:00
parent 8f23a841f0
commit 17decab2fc
20 changed files with 2447 additions and 0 deletions

72
app/static/app.js Normal file
View File

@@ -0,0 +1,72 @@
const form = document.querySelector("#upload-form");
const result = document.querySelector("#result");
const summary = document.querySelector("#summary");
const skills = document.querySelector("#skills");
const mdLink = document.querySelector("#download-md");
const progressBar = document.querySelector("#analysis-progress");
const statusText = document.querySelector("#analysis-status");
const button = form.querySelector("button");
async function pollTask(statusUrl) {
while (true) {
const response = await fetch(statusUrl);
const payload = await response.json();
progressBar.style.width = `${payload.progress || 0}%`;
statusText.textContent = payload.message || "分析中";
if (payload.status === "completed") {
return payload;
}
if (payload.status === "error") {
throw new Error(payload.error || "分析失败");
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
form.addEventListener("submit", async (event) => {
event.preventDefault();
button.disabled = true;
button.textContent = "分析中...";
result.hidden = true;
progressBar.style.width = "0%";
statusText.textContent = "任务提交中...";
const data = new FormData(form);
if (!data.has("use_model")) {
data.set("use_model", "false");
}
try {
const response = await fetch("/analyze", {
method: "POST",
body: data,
});
const payload = await response.json();
if (!response.ok) {
throw new Error(payload.detail || "分析失败");
}
result.hidden = false;
const task = await pollTask(payload.status_url);
summary.textContent = task.summary;
skills.innerHTML = "";
task.matched_skills.forEach((name) => {
const item = document.createElement("span");
item.textContent = name;
skills.appendChild(item);
});
mdLink.href = task.downloads.markdown;
} catch (error) {
summary.textContent = error.message;
skills.innerHTML = "";
result.hidden = false;
statusText.textContent = "分析失败";
} finally {
button.disabled = false;
button.textContent = "开始分析";
}
});

218
app/static/styles.css Normal file
View File

@@ -0,0 +1,218 @@
:root {
color-scheme: light;
font-family: "Inter", "Segoe UI", Arial, sans-serif;
background: #f5f7fb;
color: #172033;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.shell {
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px;
}
.panel {
width: min(880px, 100%);
background: #ffffff;
border: 1px solid #dbe2ee;
border-radius: 8px;
box-shadow: 0 20px 60px rgba(23, 32, 51, 0.08);
padding: 32px;
}
.header {
display: flex;
justify-content: space-between;
gap: 24px;
align-items: flex-start;
margin-bottom: 28px;
}
.eyebrow {
margin: 0 0 8px;
color: #5d6d83;
font-size: 13px;
letter-spacing: 0;
}
h1,
h2 {
margin: 0;
}
h1 {
font-size: 32px;
font-weight: 700;
}
h2 {
font-size: 20px;
}
.meta {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.meta span,
.skills span {
border: 1px solid #cfd8e6;
border-radius: 6px;
padding: 6px 9px;
color: #44546a;
background: #f8fafc;
font-size: 13px;
}
.form {
display: grid;
gap: 20px;
}
.drop-zone {
border: 1px dashed #8aa1bd;
border-radius: 8px;
padding: 34px;
background: #fbfcfe;
display: grid;
gap: 8px;
cursor: pointer;
}
.drop-zone input {
width: 100%;
}
.drop-title {
font-size: 18px;
font-weight: 700;
}
.drop-subtitle {
color: #5d6d83;
}
.controls {
display: grid;
grid-template-columns: 1fr auto;
gap: 16px;
align-items: end;
}
label {
display: grid;
gap: 8px;
color: #344054;
font-size: 14px;
}
select {
height: 42px;
border: 1px solid #cfd8e6;
border-radius: 6px;
padding: 0 12px;
background: #ffffff;
}
.checkbox {
display: flex;
align-items: center;
gap: 8px;
height: 42px;
}
button,
.downloads a {
border: 0;
border-radius: 6px;
background: #1f6feb;
color: #ffffff;
min-height: 44px;
padding: 0 18px;
font-weight: 700;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
}
button:disabled {
background: #8aa1bd;
cursor: wait;
}
.result {
margin-top: 28px;
border-top: 1px solid #dbe2ee;
padding-top: 24px;
}
.progress-wrap {
display: grid;
gap: 8px;
margin: 14px 0 18px;
}
.progress-track {
height: 10px;
border-radius: 999px;
background: #e8edf4;
overflow: hidden;
}
.progress-bar {
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #1f6feb, #4f8df5);
transition: width 0.2s ease;
}
.status-text {
margin: 0;
color: #5d6d83;
font-size: 14px;
}
.skills,
.downloads {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 16px;
}
.downloads a:last-child {
background: #334155;
}
@media (max-width: 680px) {
.shell {
padding: 16px;
}
.panel {
padding: 22px;
}
.header,
.controls {
grid-template-columns: 1fr;
display: grid;
}
.meta {
justify-content: flex-start;
}
}