chore(格式): Prettier 统一到全仓,后端和契约一次性格式化
Some checks failed
Deploy / deploy (push) Has been cancelled

原来只有 `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:
2026-09-16 08:27:34 -06:00
parent e600fd24cf
commit ed56a209ea
122 changed files with 10557 additions and 4843 deletions

View File

@@ -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)))

View File

@@ -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),
),
)

View File

@@ -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`,

View File

@@ -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(): {

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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 = {}
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -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">

View File

@@ -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() {

View File

@@ -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(() => {

View File

@@ -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)

View File

@@ -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

View File

@@ -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)