style(格式化): 改成单引号、一行最长 160,全仓重新格式化
Deploy / deploy (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-26 13:52:24 +08:00
1 parent 530e1a3d5d
commit 195069450f
480 files changed
+15507 -24650

No files matched your search

@@ -1,16 +1,9 @@
<script setup lang="ts">
import type { AchievementRarity } from "utils/types"
import {
createAchievement,
getMetricOptions,
updateAchievement,
type AdminAchievement,
type MetricOption,
deleteAchievement,
} from "admin/api"
import AchievementIcon from "shared/components/AchievementIcon.vue"
import { RARITY_LABEL } from "utils/constants"
import { useBreakpoints } from "shared/composables/breakpoints"
import type { AchievementRarity } from 'utils/types'
import { createAchievement, getMetricOptions, updateAchievement, type AdminAchievement, type MetricOption, deleteAchievement } from 'admin/api'
import AchievementIcon from 'shared/components/AchievementIcon.vue'
import { RARITY_LABEL } from 'utils/constants'
import { useBreakpoints } from 'shared/composables/breakpoints'
const rarityOptions = Object.entries(RARITY_LABEL).map(([value, label]) => ({
label,
@@ -21,7 +14,7 @@ const props = defineProps<{
show: boolean
editing: AdminAchievement | null
}>()
const emit = defineEmits<{ "update:show": [boolean]; saved: [] }>()
const emit = defineEmits<{ 'update:show': [boolean]; saved: [] }>()
const dialog = useDialog()
/** 删除放在编辑框左下角(列表改成阶梯之后,每一档上只留「点了就编辑」) */
@@ -30,16 +23,14 @@ function confirmDelete() {
if (!target) return
dialog.warning({
title: `删除「${target.name}」?`,
content: target.unlockCount
? `${target.unlockCount} 人已经拿到了,删掉会连他们的记录一起删。只是不想再发,关掉「上架」就行。`
: "删掉就找不回来了。",
positiveText: "删除",
negativeText: "不删",
content: target.unlockCount ? `${target.unlockCount} 人已经拿到了,删掉会连他们的记录一起删。只是不想再发,关掉「上架」就行。` : '删掉就找不回来了。',
positiveText: '删除',
negativeText: '不删',
onPositiveClick: async () => {
await deleteAchievement(target.id)
message.success("删掉了")
emit("update:show", false)
emit("saved")
message.success('删掉了')
emit('update:show', false)
emit('saved')
},
})
}
@@ -51,13 +42,13 @@ const saving = ref(false)
function emptyForm() {
return {
name: "",
description: "",
icon: "noto:trophy",
rarity: "bronze" as AchievementRarity,
name: '',
description: '',
icon: 'noto:trophy',
rarity: 'bronze' as AchievementRarity,
hidden: false,
metric: "",
operator: "gte" as "gte" | "lte",
metric: '',
operator: 'gte' as 'gte' | 'lte',
threshold: 1,
visible: true,
order: 0,
@@ -68,9 +59,7 @@ const form = ref(emptyForm())
const metricOptions = computed(() => metrics.value.map((m) => ({ label: m.name, value: m.key })))
const metricHelp = computed(
() => metrics.value.find((m) => m.key === form.value.metric)?.helpText ?? "",
)
const metricHelp = computed(() => metrics.value.find((m) => m.key === form.value.metric)?.helpText ?? '')
watch(
() => props.show,
@@ -83,14 +72,14 @@ watch(
if (props.editing) {
form.value = { ...emptyForm(), ...props.editing }
} else {
form.value = { ...emptyForm(), metric: metrics.value[0]?.key ?? "" }
form.value = { ...emptyForm(), metric: metrics.value[0]?.key ?? '' }
}
},
)
async function save() {
if (!form.value.name || !form.value.metric) {
message.error("名称和指标不能为空")
message.error('名称和指标不能为空')
return
}
saving.value = true
@@ -100,9 +89,9 @@ async function save() {
} else {
await createAchievement(form.value)
}
message.success("保存成功")
emit("update:show", false)
emit("saved")
message.success('保存成功')
emit('update:show', false)
emit('saved')
} finally {
saving.value = false
}
@@ -110,24 +99,14 @@ async function save() {
</script>
<template>
<n-modal
:show="show"
preset="card"
style="width: 560px"
:title="editing ? '编辑成就' : '新建成就'"
@update:show="emit('update:show', $event)"
>
<n-modal :show="show" preset="card" style="width: 560px" :title="editing ? '编辑成就' : '新建成就'" @update:show="emit('update:show', $event)">
<!-- 手机上弹框只有 350 宽,标签放到上面,输入框才有地方 -->
<n-form :label-placement="isMobile ? 'top' : 'left'" :label-width="80">
<n-form-item label="名称" required>
<n-input v-model:value="form.name" placeholder="成就名称" />
</n-form-item>
<n-form-item label="描述" required>
<n-input
v-model:value="form.description"
type="textarea"
placeholder="达成条件的描述,展示给学生看"
/>
<n-input v-model:value="form.description" type="textarea" placeholder="达成条件的描述,展示给学生看" />
</n-form-item>
<n-form-item label="图标">
<n-flex vertical :size="4" style="flex: 1">
@@ -138,8 +117,7 @@ async function save() {
<n-input v-model:value="form.icon" placeholder="iconify 图标名,例如 noto:owl" />
</n-flex>
<n-text depth="3" style="font-size: 12px">
填 iconify 图标名(推荐 noto: 开头的彩色 emoji 图标),左侧是实时
预览;预览不出来说明名字写错了。图标名可在 icon-sets.iconify.design 搜索。
填 iconify 图标名(推荐 noto: 开头的彩色 emoji 图标),左侧是实时 预览;预览不出来说明名字写错了。图标名可在 icon-sets.iconify.design 搜索。
</n-text>
</n-flex>
</n-form-item>
@@ -178,9 +156,7 @@ async function save() {
</n-form>
<template #footer>
<n-flex justify="end">
<n-button v-if="editing" quaternary type="error" class="delete" @click="confirmDelete">
删除
</n-button>
<n-button v-if="editing" quaternary type="error" class="delete" @click="confirmDelete"> 删除 </n-button>
<n-button @click="emit('update:show', false)">取消</n-button>
<n-button type="primary" :loading="saving" @click="save"> 保存 </n-button>
</n-flex>
+19 -37
View File
@@ -1,16 +1,10 @@
<script setup lang="ts">
import PageHeader from "admin/components/PageHeader.vue"
import { useThemeVars } from "naive-ui"
import {
getAdminAchievements,
getBaseInfo,
getMetricOptions,
type AdminAchievement,
type MetricOption,
} from "admin/api"
import AchievementIcon from "shared/components/AchievementIcon.vue"
import AchievementModal from "./components/AchievementModal.vue"
import { RARITY_LABEL } from "utils/constants"
import PageHeader from 'admin/components/PageHeader.vue'
import { useThemeVars } from 'naive-ui'
import { getAdminAchievements, getBaseInfo, getMetricOptions, type AdminAchievement, type MetricOption } from 'admin/api'
import AchievementIcon from 'shared/components/AchievementIcon.vue'
import AchievementModal from './components/AchievementModal.vue'
import { RARITY_LABEL } from 'utils/constants'
/**
* 后台成就(设计稿「我的成就重设计」AchAdmin):按指标分组,一组一行排成阶梯 ——
@@ -37,11 +31,7 @@ const groups = computed(() => {
for (const a of list.value) byMetric.set(a.metric, [...(byMetric.get(a.metric) ?? []), a])
const order = metrics.value.map((m) => m.key)
return [...byMetric.entries()]
.sort(
([a], [b]) =>
(order.indexOf(a) === -1 ? 99 : order.indexOf(a)) -
(order.indexOf(b) === -1 ? 99 : order.indexOf(b)),
)
.sort(([a], [b]) => (order.indexOf(a) === -1 ? 99 : order.indexOf(a)) - (order.indexOf(b) === -1 ? 99 : order.indexOf(b)))
.map(([metric, items]) => ({
metric,
name: items[0]?.metricName ?? metric,
@@ -50,9 +40,9 @@ const groups = computed(() => {
})
function percent(a: AdminAchievement) {
if (!students.value) return ""
if (!students.value) return ''
const value = (a.unlockCount / students.value) * 100
return value > 0 && value < 1 ? "<1%" : `${Math.round(value)}%`
return value > 0 && value < 1 ? '<1%' : `${Math.round(value)}%`
}
async function load() {
@@ -103,20 +93,12 @@ onMounted(load)
<div class="steps">
<template v-for="(a, i) in group.items" :key="a.id">
<span v-if="i" class="arrow" aria-hidden="true">→</span>
<button
type="button"
class="step"
:class="{ off: !a.visible }"
:title="`${RARITY_LABEL[a.rarity] ?? a.rarity} · 点了编辑`"
@click="edit(a)"
>
<button type="button" class="step" :class="{ off: !a.visible }" :title="`${RARITY_LABEL[a.rarity] ?? a.rarity} · 点了编辑`" @click="edit(a)">
<AchievementIcon :icon="a.icon" :size="26" />
<span class="step-text">
<b>
{{ a.name }}
<span class="muted small"
>{{ a.operator === "gte" ? "≥" : "≤" }} {{ a.threshold }}</span
>
<span class="muted small">{{ a.operator === 'gte' ? '≥' : '≤' }} {{ a.threshold }}</span>
</b>
<span class="small">
{{ a.unlockCount }} 人
@@ -143,7 +125,7 @@ onMounted(load)
/* 斑马底纹,和后台其它列表一样 */
.group:nth-child(even) {
background: v-bind("theme.tableColorStriped");
background: v-bind('theme.tableColorStriped');
}
.group {
@@ -151,7 +133,7 @@ onMounted(load)
align-items: center;
gap: 12px;
padding: 8px 10px;
border-bottom: 1px solid v-bind("theme.dividerColor");
border-bottom: 1px solid v-bind('theme.dividerColor');
}
.metric {
@@ -168,7 +150,7 @@ onMounted(load)
}
.arrow {
color: v-bind("theme.textColor3");
color: v-bind('theme.textColor3');
}
.step {
@@ -178,8 +160,8 @@ onMounted(load)
min-width: 160px;
padding: 6px 10px;
border-radius: 8px;
border: 1px solid v-bind("theme.dividerColor");
background: v-bind("theme.cardColor");
border: 1px solid v-bind('theme.dividerColor');
background: v-bind('theme.cardColor');
font: inherit;
color: inherit;
text-align: left;
@@ -187,7 +169,7 @@ onMounted(load)
}
.step:hover {
border-color: v-bind("theme.primaryColor");
border-color: v-bind('theme.primaryColor');
}
.step.off {
@@ -210,11 +192,11 @@ onMounted(load)
}
.muted {
color: v-bind("theme.textColor3");
color: v-bind('theme.textColor3');
}
.off-note {
color: v-bind("theme.errorColor");
color: v-bind('theme.errorColor');
}
/* 手机上指标名放在一组上头,下面两档一行;箭头拿掉(折行之后箭头会单独落在行首) */