原来只有 `apps/web` 在 Prettier 下(配置在 `apps/web/.prettierrc.toml`、脚本在 web 的 package.json),后端和契约从来没格式化过 —— 手写在 100 列上下,`db/schema.ts` 还是 drizzle-kit pull 留下的 tab 缩进。两套口径分叉久了,跨端改一处就得记着「这边 什么风格」。 - 配置搬到根目录 `.prettierrc.toml`,内容不变(`semi=false`,其余全默认, printWidth 80 —— 和前端已有的格式一致,不另立一套宽度); - 脚本统一成根目录 `bun run fmt`,覆盖 `apps/*/src`、`apps/web/tests` 和两个构建 配置;web 自己那份 `fmt` 和重复的 prettier 依赖删掉; - `.prettierignore` 挡掉两类不该碰的:drizzle-kit 生成的 `src/db/meta/` 结构快照 (它是 db:generate 的比对输入,只该由 drizzle-kit 写)、unplugin 每次 dev 都会 重写的 `auto-imports.d.ts` / `components.d.ts`; - 全量跑了一遍。纯格式,无行为改动:api typecheck / check:routes / check:ast、 前端 type-check 全过,起 api 打了接口确认正常。前端这 39 个文件的小改动是 prettier 版本漂移(类型断言的换行口径变了),不是新配置带来的。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,9 @@ aiStore.targetUsername = urlUsername.value
|
||||
aiStore.duration = urlDuration.value
|
||||
|
||||
const subOptions = computed<Duration>(
|
||||
() => durationFromValue(aiStore.duration) ?? durationFromValue(DURATION_OPTIONS[0].value)!,
|
||||
() =>
|
||||
durationFromValue(aiStore.duration) ??
|
||||
durationFromValue(DURATION_OPTIONS[0].value)!,
|
||||
)
|
||||
|
||||
const start = computed(() => formatISO(sub(new Date(), subOptions.value)))
|
||||
|
||||
@@ -31,7 +31,8 @@ const { chartKey } = useChartTheme()
|
||||
// 第二个 tab 里列成表格。这里直接画出来,不用改后端和契约
|
||||
const items = computed(() =>
|
||||
[...aiStore.detailsData.flowcharts].sort(
|
||||
(a, b) => b.bestScore - a.bestScore || a.problemId.localeCompare(b.problemId),
|
||||
(a, b) =>
|
||||
b.bestScore - a.bestScore || a.problemId.localeCompare(b.problemId),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -255,12 +255,9 @@ export function getContestAccess(id: string) {
|
||||
|
||||
// 注意和 GET /access 不一样:这个返回裸 true,密码错是 403 走 catch
|
||||
export function checkContestPassword(contestID: string, password: string) {
|
||||
return api.post<boolean>(
|
||||
`contests/${encodeURIComponent(contestID)}/access`,
|
||||
{
|
||||
password,
|
||||
},
|
||||
)
|
||||
return api.post<boolean>(`contests/${encodeURIComponent(contestID)}/access`, {
|
||||
password,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getContestProblems(contestID: string) {
|
||||
@@ -295,9 +292,12 @@ export function updateProfile(data: { realName: string; mood: string }) {
|
||||
}
|
||||
|
||||
export function getAnnouncementList(offset = 0, limit = 10) {
|
||||
return api.get<{ results: AnnouncementListItem[]; total: number }>("announcements", {
|
||||
params: { limit, offset },
|
||||
})
|
||||
return api.get<{ results: AnnouncementListItem[]; total: number }>(
|
||||
"announcements",
|
||||
{
|
||||
params: { limit, offset },
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export function getAnnouncement(id: number) {
|
||||
@@ -465,7 +465,6 @@ export function joinProblemSet(problemSetId: number) {
|
||||
return api.post("problem-set-progress", { problemSetId })
|
||||
}
|
||||
|
||||
|
||||
export function getUserBadges(username?: string) {
|
||||
return api.get<UserBadge[]>(
|
||||
`users/${encodeURIComponent(username ?? "me")}/badges`,
|
||||
|
||||
@@ -66,7 +66,9 @@ const timeRangeOptions: SelectOption[] = [
|
||||
]
|
||||
|
||||
// 「全部时间」的 value 是空串,解不出来就是 null —— 正是不带时间条件的意思
|
||||
const subOptions = computed<Duration | null>(() => durationFromValue(duration.value))
|
||||
const subOptions = computed<Duration | null>(() =>
|
||||
durationFromValue(duration.value),
|
||||
)
|
||||
|
||||
// 根据时间段选项计算开始和结束时间
|
||||
function getTimeRange(): {
|
||||
|
||||
@@ -55,7 +55,10 @@ function submit() {
|
||||
|
||||
/** 给老师看的一句人话:选项按 A/B/C 报,报下标没人看得懂 */
|
||||
function describe(sel: Set<number>) {
|
||||
return `选了 ${[...sel].sort((a, b) => a - b).map((i) => String.fromCharCode(65 + i)).join("、")}`
|
||||
return `选了 ${[...sel]
|
||||
.sort((a, b) => a - b)
|
||||
.map((i) => String.fromCharCode(65 + i))
|
||||
.join("、")}`
|
||||
}
|
||||
|
||||
function reset() {
|
||||
|
||||
@@ -23,10 +23,7 @@ const IDLE_MS = 10 * 60 * 1000
|
||||
* @param tutorialId 当前这一课,0 表示还没加载好
|
||||
* @param enabled 是否留痕。未登录时为 false:教程本身保持免登录可读,只是不记
|
||||
*/
|
||||
export function useLearnTrace(
|
||||
tutorialId: Ref<number>,
|
||||
enabled: Ref<boolean>,
|
||||
) {
|
||||
export function useLearnTrace(tutorialId: Ref<number>, enabled: Ref<boolean>) {
|
||||
const visibility = useDocumentVisibility()
|
||||
const { idle } = useIdle(IDLE_MS)
|
||||
|
||||
|
||||
@@ -126,7 +126,12 @@
|
||||
<script setup lang="ts">
|
||||
import { MdPreview } from "md-editor-v3"
|
||||
import "md-editor-v3/lib/preview.css"
|
||||
import type { Tutorial, Exercise, LANGUAGE, TutorialProgress } from "utils/types"
|
||||
import type {
|
||||
Tutorial,
|
||||
Exercise,
|
||||
LANGUAGE,
|
||||
TutorialProgress,
|
||||
} from "utils/types"
|
||||
import {
|
||||
getTutorial,
|
||||
getTutorials,
|
||||
@@ -220,7 +225,9 @@ async function loadProgress() {
|
||||
}
|
||||
try {
|
||||
const rows = await getLearnProgress(type.value)
|
||||
progress.value = Object.fromEntries(rows.map((row) => [row.tutorialId, row]))
|
||||
progress.value = Object.fromEntries(
|
||||
rows.map((row) => [row.tutorialId, row]),
|
||||
)
|
||||
} catch {
|
||||
progress.value = {}
|
||||
}
|
||||
|
||||
@@ -311,7 +311,9 @@ function type(status: ProblemStatus) {
|
||||
</p>
|
||||
<n-list bordered style="margin-bottom: 8px">
|
||||
<n-list-item v-for="(rule, i) in rules" :key="i">
|
||||
<n-tag :type="KIND_TAG_TYPE[rule.kind]">{{ rule.description }}</n-tag>
|
||||
<n-tag :type="KIND_TAG_TYPE[rule.kind]">{{
|
||||
rule.description
|
||||
}}</n-tag>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</div>
|
||||
|
||||
@@ -111,8 +111,7 @@ const POLL_INTERVAL = 3000
|
||||
const POLL_TIMEOUT = 3 * 60 * 1000
|
||||
|
||||
type Outcome =
|
||||
| { ok: true; score: number; grade: string }
|
||||
| { ok: false; error?: string }
|
||||
{ ok: true; score: number; grade: string } | { ok: false; error?: string }
|
||||
|
||||
const { pause: pausePolling, resume: resumePolling } = useIntervalFn(
|
||||
async () => {
|
||||
@@ -509,11 +508,7 @@ onUnmounted(() => {
|
||||
</n-card>
|
||||
|
||||
<!-- 详细评分 -->
|
||||
<n-card
|
||||
v-if="sortedCriteria.length"
|
||||
size="small"
|
||||
title="详细评分"
|
||||
>
|
||||
<n-card v-if="sortedCriteria.length" size="small" title="详细评分">
|
||||
<div
|
||||
v-for="[key, detail] in sortedCriteria"
|
||||
:key="key"
|
||||
|
||||
@@ -182,11 +182,7 @@ watch(
|
||||
<n-tab-pane name="content" tab="题目描述">
|
||||
<ProblemContent />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane
|
||||
v-if="canShowFlowchart"
|
||||
name="flowchart"
|
||||
tab="流程图表"
|
||||
>
|
||||
<n-tab-pane v-if="canShowFlowchart" name="flowchart" tab="流程图表">
|
||||
<ProblemFlowchart />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="info" tab="题目统计" :disabled="!!problemSetId">
|
||||
@@ -234,11 +230,7 @@ watch(
|
||||
<n-tab-pane name="content" tab="题目描述">
|
||||
<ProblemContent />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane
|
||||
v-if="canShowFlowchart"
|
||||
name="flowchart"
|
||||
tab="流程图表"
|
||||
>
|
||||
<n-tab-pane v-if="canShowFlowchart" name="flowchart" tab="流程图表">
|
||||
<ProblemFlowchart />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="info" tab="题目统计" :disabled="!!problemSetId">
|
||||
|
||||
@@ -42,11 +42,15 @@ function getProgressPercentage() {
|
||||
|
||||
// 有选做题时把「必做 N 题」标出来,否则「共 10 道题目」和「9 / 9」对不上
|
||||
const optionalCount = computed(
|
||||
() => props.problemSet.problemsCount - (props.problemSet.userProgress?.totalCount ?? 0),
|
||||
() =>
|
||||
props.problemSet.problemsCount -
|
||||
(props.problemSet.userProgress?.totalCount ?? 0),
|
||||
)
|
||||
|
||||
const endTimeText = computed(() =>
|
||||
props.problemSet.endTime ? parseTime(props.problemSet.endTime, "YYYY-MM-DD HH:mm") : "",
|
||||
props.problemSet.endTime
|
||||
? parseTime(props.problemSet.endTime, "YYYY-MM-DD HH:mm")
|
||||
: "",
|
||||
)
|
||||
|
||||
function handleJoin() {
|
||||
|
||||
@@ -286,7 +286,9 @@ const options: SelectOption[] = [...LONG_DURATION_OPTIONS]
|
||||
|
||||
// 认不出来退回 options[1](一个月内),和 duration 的初值一致
|
||||
const subOptions = computed<Duration>(
|
||||
() => durationFromValue(duration.value) ?? durationFromValue(LONG_DURATION_OPTIONS[1]!.value)!,
|
||||
() =>
|
||||
durationFromValue(duration.value) ??
|
||||
durationFromValue(LONG_DURATION_OPTIONS[1]!.value)!,
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -69,11 +69,7 @@
|
||||
</n-card>
|
||||
|
||||
<!-- 详细评分 -->
|
||||
<n-card
|
||||
v-if="sortedCriteria.length > 0"
|
||||
size="small"
|
||||
title="详细评分"
|
||||
>
|
||||
<n-card v-if="sortedCriteria.length > 0" size="small" title="详细评分">
|
||||
<div
|
||||
v-for="[key, detail] in sortedCriteria"
|
||||
:key="key"
|
||||
@@ -148,7 +144,9 @@ const criteriaDetails = computed<
|
||||
)
|
||||
})
|
||||
// jsonb 不保留键序,直接遍历会把 40 分的「逻辑正确性」排到最后
|
||||
const sortedCriteria = computed(() => sortFlowchartCriteria(criteriaDetails.value))
|
||||
const sortedCriteria = computed(() =>
|
||||
sortFlowchartCriteria(criteriaDetails.value),
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
const rendering = ref(false)
|
||||
|
||||
@@ -43,7 +43,9 @@ const loading = ref(false)
|
||||
* 测试点明细。`info` 在契约里是「完整形状或空对象」的联合(非管理员拿到的是空对象),
|
||||
* `data` 本身也可能为 null —— 两种情况都由这个访问器归成空数组,模板里不再直接取。
|
||||
*/
|
||||
const caseResults = computed(() => submissionCaseResults(submission.value?.info))
|
||||
const caseResults = computed(() =>
|
||||
submissionCaseResults(submission.value?.info),
|
||||
)
|
||||
|
||||
async function init() {
|
||||
submission.value = props.submission
|
||||
|
||||
@@ -80,14 +80,8 @@ async function init() {
|
||||
const metricsRes = await getMetrics(res.user.id)
|
||||
firstSubmissionAt.value = parseTime(metricsRes.first)
|
||||
latestSubmissionAt.value = parseTime(metricsRes.latest)
|
||||
toLatestAt.value = durationToDays(
|
||||
metricsRes.latest,
|
||||
metricsRes.now,
|
||||
)
|
||||
learnDuration.value = durationToDays(
|
||||
metricsRes.first,
|
||||
metricsRes.latest,
|
||||
)
|
||||
toLatestAt.value = durationToDays(metricsRes.latest, metricsRes.now)
|
||||
learnDuration.value = durationToDays(metricsRes.first, metricsRes.latest)
|
||||
}
|
||||
} finally {
|
||||
toggle(false)
|
||||
|
||||
Reference in New Issue
Block a user