Compare commits
3 Commits
b79dd6c721
...
0d7336bce8
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d7336bce8 | |||
| 4db0dc7e83 | |||
| 56d06eb456 |
@@ -7,6 +7,12 @@ import {
|
|||||||
type MetricOption,
|
type MetricOption,
|
||||||
} from "admin/api"
|
} from "admin/api"
|
||||||
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
||||||
|
import { RARITY_LABEL } from "utils/constants"
|
||||||
|
|
||||||
|
const rarityOptions = Object.entries(RARITY_LABEL).map(([value, label]) => ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
}))
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
@@ -118,15 +124,7 @@ async function save() {
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="稀有度">
|
<n-form-item label="稀有度">
|
||||||
<n-select
|
<n-select v-model:value="form.rarity" :options="rarityOptions" />
|
||||||
v-model:value="form.rarity"
|
|
||||||
:options="[
|
|
||||||
{ label: '青铜', value: 'bronze' },
|
|
||||||
{ label: '白银', value: 'silver' },
|
|
||||||
{ label: '黄金', value: 'gold' },
|
|
||||||
{ label: '白金', value: 'platinum' },
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="指标" required>
|
<n-form-item label="指标" required>
|
||||||
<n-select
|
<n-select
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from "admin/api"
|
} from "admin/api"
|
||||||
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
||||||
import AchievementModal from "./components/AchievementModal.vue"
|
import AchievementModal from "./components/AchievementModal.vue"
|
||||||
|
import { RARITY_LABEL } from "utils/constants"
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@@ -16,13 +17,6 @@ const loading = ref(false)
|
|||||||
const showModal = ref(false)
|
const showModal = ref(false)
|
||||||
const editing = ref<AdminAchievement | null>(null)
|
const editing = ref<AdminAchievement | null>(null)
|
||||||
|
|
||||||
const RARITY_LABEL: Record<string, string> = {
|
|
||||||
bronze: "青铜",
|
|
||||||
silver: "白银",
|
|
||||||
gold: "黄金",
|
|
||||||
platinum: "白金",
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下架的只在有的时候才提,全部上架时标题不啰嗦
|
// 下架的只在有的时候才提,全部上架时标题不啰嗦
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
if (!list.value.length) return "成就管理"
|
if (!list.value.length) return "成就管理"
|
||||||
|
|||||||
@@ -1,22 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
||||||
|
import { useRarityColor } from "shared/composables/rarity"
|
||||||
|
import { RARITY_COLOR, RARITY_LABEL } from "utils/constants"
|
||||||
import type { Achievement } from "utils/types"
|
import type { Achievement } from "utils/types"
|
||||||
|
|
||||||
const props = defineProps<{ achievement: Achievement }>()
|
const props = defineProps<{ achievement: Achievement }>()
|
||||||
|
|
||||||
const RARITY_COLOR: Record<string, string> = {
|
// 边框用原色,tag 里的文字用跟主题走的那套
|
||||||
bronze: "#b87333",
|
const rarityTextColor = useRarityColor()
|
||||||
silver: "#9fa6b2",
|
|
||||||
gold: "#e0a300",
|
|
||||||
platinum: "#7dd3fc",
|
|
||||||
}
|
|
||||||
|
|
||||||
const RARITY_LABEL: Record<string, string> = {
|
|
||||||
bronze: "青铜",
|
|
||||||
silver: "白银",
|
|
||||||
gold: "黄金",
|
|
||||||
platinum: "白金",
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏且未解锁:后端已把名称/描述/图标和条件三件套都遮成 ??? 和 null,
|
// 隐藏且未解锁:后端已把名称/描述/图标和条件三件套都遮成 ??? 和 null,
|
||||||
// 这里只负责不要把 null 渲染出来,也不要画出会泄露门槛的进度条
|
// 这里只负责不要把 null 渲染出来,也不要画出会泄露门槛的进度条
|
||||||
@@ -78,7 +69,7 @@ const unlockDate = computed(() => {
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
:color="{
|
:color="{
|
||||||
borderColor: RARITY_COLOR[achievement.rarity],
|
borderColor: RARITY_COLOR[achievement.rarity],
|
||||||
textColor: RARITY_COLOR[achievement.rarity],
|
textColor: rarityTextColor[achievement.rarity],
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ RARITY_LABEL[achievement.rarity] }}
|
{{ RARITY_LABEL[achievement.rarity] }}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getAchievements, getAchievementSummary } from "oj/achievement/api"
|
import { getAchievements, getAchievementSummary } from "oj/achievement/api"
|
||||||
import { getUserBadges } from "oj/api"
|
import { getUserBadges } from "oj/api"
|
||||||
|
import { useRarityColor } from "shared/composables/rarity"
|
||||||
import type { Achievement, AchievementSummary } from "utils/types"
|
import type { Achievement, AchievementSummary } from "utils/types"
|
||||||
import AchievementCard from "./components/AchievementCard.vue"
|
import AchievementCard from "./components/AchievementCard.vue"
|
||||||
|
|
||||||
@@ -18,6 +19,9 @@ interface UserBadge {
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const name = computed(() => (route.query.name as string) || undefined)
|
const name = computed(() => (route.query.name as string) || undefined)
|
||||||
|
|
||||||
|
// 标签和进度条同色,整行读作一个单位
|
||||||
|
const rarityColor = useRarityColor()
|
||||||
|
|
||||||
const achievements = ref<Achievement[]>([])
|
const achievements = ref<Achievement[]>([])
|
||||||
const summary = ref<AchievementSummary | null>(null)
|
const summary = ref<AchievementSummary | null>(null)
|
||||||
const badges = ref<UserBadge[]>([])
|
const badges = ref<UserBadge[]>([])
|
||||||
@@ -59,19 +63,44 @@ watch(name, load)
|
|||||||
<n-card v-if="summary" class="overview">
|
<n-card v-if="summary" class="overview">
|
||||||
<div class="overview-row">
|
<div class="overview-row">
|
||||||
<div class="percent">
|
<div class="percent">
|
||||||
<div class="big">{{ summary.percent }}%</div>
|
<n-progress
|
||||||
|
type="circle"
|
||||||
|
:percentage="summary.percent"
|
||||||
|
:stroke-width="8"
|
||||||
|
>
|
||||||
|
<div class="ring-text">
|
||||||
|
<div class="ring-percent">{{ summary.percent }}%</div>
|
||||||
|
</div>
|
||||||
|
</n-progress>
|
||||||
<div class="sub">
|
<div class="sub">
|
||||||
{{ summary.unlocked }} / {{ summary.total }} 已获得
|
已获得 {{ summary.unlocked }} / {{ summary.total }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rarity">
|
<div class="rarity">
|
||||||
<div
|
<div
|
||||||
v-for="r in summary.rarity"
|
v-for="r in summary.rarity"
|
||||||
:key="r.rarity"
|
:key="r.rarity"
|
||||||
class="rarity-item"
|
class="rarity-item"
|
||||||
>
|
>
|
||||||
<div class="rarity-label">{{ r.label }}</div>
|
<span
|
||||||
<div class="rarity-count">{{ r.unlocked }} / {{ r.total }}</div>
|
class="rarity-label"
|
||||||
|
:style="{ color: rarityColor[r.rarity] }"
|
||||||
|
>
|
||||||
|
{{ r.label }}
|
||||||
|
</span>
|
||||||
|
<span class="rarity-bar">
|
||||||
|
<span
|
||||||
|
class="rarity-fill"
|
||||||
|
:style="{
|
||||||
|
width: r.total ? (r.unlocked / r.total) * 100 + '%' : '0%',
|
||||||
|
background: rarityColor[r.rarity],
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span class="rarity-count">
|
||||||
|
<b>{{ r.unlocked }}</b> / {{ r.total }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,34 +143,70 @@ watch(name, load)
|
|||||||
.overview-row {
|
.overview-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 32px;
|
||||||
gap: 24px;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.big {
|
.percent {
|
||||||
font-size: 40px;
|
flex: none;
|
||||||
|
width: 110px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.percent :deep(.n-progress) {
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
.ring-percent {
|
||||||
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.sub {
|
.sub {
|
||||||
margin-top: 6px;
|
margin-top: 8px;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
opacity: 0.7;
|
opacity: 0.65;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.rarity {
|
.rarity {
|
||||||
display: flex;
|
flex: 1;
|
||||||
gap: 20px;
|
min-width: 240px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 10px 28px;
|
||||||
}
|
}
|
||||||
.rarity-item {
|
.rarity-item {
|
||||||
text-align: center;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.rarity-label {
|
.rarity-label {
|
||||||
font-size: 12px;
|
flex: none;
|
||||||
opacity: 0.7;
|
width: 30px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.rarity-bar {
|
||||||
|
flex: 1;
|
||||||
|
height: 6px;
|
||||||
|
min-width: 40px;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(128, 128, 128, 0.2); /* 灰底在明暗两套主题下都成立 */
|
||||||
|
}
|
||||||
|
.rarity-fill {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.4s ease;
|
||||||
}
|
}
|
||||||
.rarity-count {
|
.rarity-count {
|
||||||
font-weight: 600;
|
flex: none;
|
||||||
margin-top: 2px;
|
font-size: 12px;
|
||||||
|
opacity: 0.7;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.rarity-count b {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
.tabs {
|
.tabs {
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
import AchievementIcon from "shared/components/AchievementIcon.vue"
|
||||||
import { useAchievementStore } from "shared/store/achievement"
|
import { useAchievementStore } from "shared/store/achievement"
|
||||||
|
import { RARITY_COLOR } from "utils/constants"
|
||||||
|
|
||||||
const store = useAchievementStore()
|
const store = useAchievementStore()
|
||||||
const { current, queue } = storeToRefs(store)
|
const { current, queue } = storeToRefs(store)
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
|
||||||
const RARITY_COLOR: Record<string, string> = {
|
|
||||||
bronze: "#b87333",
|
|
||||||
silver: "#9fa6b2",
|
|
||||||
gold: "#e0a300",
|
|
||||||
platinum: "#7dd3fc",
|
|
||||||
}
|
|
||||||
|
|
||||||
let timer: ReturnType<typeof setTimeout> | null = null
|
let timer: ReturnType<typeof setTimeout> | null = null
|
||||||
let gapTimer: ReturnType<typeof setTimeout> | null = null
|
let gapTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
|||||||
15
src/shared/composables/rarity.ts
Normal file
15
src/shared/composables/rarity.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { useDark } from "@vueuse/core"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { RARITY_TEXT_COLOR } from "utils/constants"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成就稀有度的文字配色,跟随明暗主题切换。
|
||||||
|
* 两套色值都压到 4.5:1 以上,不然浅色模式下白金和黄金的小字看不清。
|
||||||
|
* 边框和色块不用这个,直接用 RARITY_COLOR 的原色。
|
||||||
|
*/
|
||||||
|
export function useRarityColor() {
|
||||||
|
const isDark = useDark()
|
||||||
|
return computed(() =>
|
||||||
|
isDark.value ? RARITY_TEXT_COLOR.dark : RARITY_TEXT_COLOR.light,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { SUBMISSION_RESULT } from "./types"
|
import type { AchievementRarity, SUBMISSION_RESULT } from "./types"
|
||||||
|
|
||||||
export enum SubmissionStatus {
|
export enum SubmissionStatus {
|
||||||
compile_error = -2,
|
compile_error = -2,
|
||||||
@@ -276,6 +276,42 @@ export enum ChartType {
|
|||||||
Activity,
|
Activity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 成就稀有度
|
||||||
|
export const RARITY_LABEL: Record<AchievementRarity, string> = {
|
||||||
|
bronze: "青铜",
|
||||||
|
silver: "白银",
|
||||||
|
gold: "黄金",
|
||||||
|
platinum: "白金",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 边框、色块用的原色:饱和度够高,明暗底上都醒目
|
||||||
|
export const RARITY_COLOR: Record<AchievementRarity, string> = {
|
||||||
|
bronze: "#b87333",
|
||||||
|
silver: "#9fa6b2",
|
||||||
|
gold: "#e0a300",
|
||||||
|
platinum: "#7dd3fc",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文字要另配一套。上面那组是照深色底调的,搬到白底上白金只有 1.7:1、
|
||||||
|
// 黄金 2.2:1,12px 的稀有度标签根本看不清。取色见 useRarityColor
|
||||||
|
export const RARITY_TEXT_COLOR: Record<
|
||||||
|
"dark" | "light",
|
||||||
|
Record<AchievementRarity, string>
|
||||||
|
> = {
|
||||||
|
dark: {
|
||||||
|
bronze: "#d18d4f",
|
||||||
|
silver: "#b6bcc7",
|
||||||
|
gold: "#e0a300",
|
||||||
|
platinum: "#7dd3fc",
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
bronze: "#9a5b25",
|
||||||
|
silver: "#6b7280",
|
||||||
|
gold: "#a37500",
|
||||||
|
platinum: "#0284c7",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// 时间范围配置
|
// 时间范围配置
|
||||||
export const DURATION_OPTIONS = [
|
export const DURATION_OPTIONS = [
|
||||||
{ label: "本节课内", value: "hours:1" },
|
{ label: "本节课内", value: "hours:1" },
|
||||||
|
|||||||
Reference in New Issue
Block a user