Skip to content

Commit 45ce655

Browse files
committed
feat: Checkbox 组件带 SVG 勾画动画,summary 改用 RippleButton
新增 src/ui/components/Checkbox.vue,svg path stroke-dashoffset 24→0 over 0.24s 形成手绘勾选效果,支持 boolean 与 array v-model + :value 两种模式,settings 与 popup 共 5 处原生 checkbox 替换。popup summary 三个数字块从 native button 改用 RippleButton 通用按钮组件,与其它操作按钮风格一致;"技术"块加点击事件 focusTechnologyList,将列表分类重置到 FOCUS_CATEGORY、关闭底部面板、滚动到顶部。将版本号提升到 1.2.97。
1 parent a2dd904 commit 45ce655

4 files changed

Lines changed: 154 additions & 14 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "stackprism",
33
"private": true,
4-
"version": "1.2.96",
4+
"version": "1.2.97",
55
"type": "module",
66
"description": "StackPrism 用于检测网页前端、后端、CDN、SaaS、广告营销、统计、登录、支付、网站程序和主题模板线索。",
77
"scripts": {

src/ui/components/Checkbox.vue

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<template>
2+
<span class="sp-checkbox" :class="{ checked: isChecked, disabled }">
3+
<input type="checkbox" class="sp-checkbox-input" :checked="isChecked" :disabled="disabled" @change="onChange" />
4+
<span class="sp-checkbox-box" aria-hidden="true">
5+
<svg
6+
class="sp-checkbox-mark"
7+
viewBox="0 0 24 24"
8+
fill="none"
9+
stroke="currentColor"
10+
stroke-width="3"
11+
stroke-linecap="round"
12+
stroke-linejoin="round"
13+
>
14+
<path d="M20 6 9 17l-5-5" />
15+
</svg>
16+
</span>
17+
</span>
18+
</template>
19+
20+
<script setup lang="ts">
21+
import { computed } from 'vue'
22+
23+
const props = withDefaults(
24+
defineProps<{
25+
modelValue: boolean | unknown[]
26+
value?: unknown
27+
disabled?: boolean
28+
}>(),
29+
{
30+
disabled: false
31+
}
32+
)
33+
34+
const emit = defineEmits<{
35+
'update:modelValue': [value: boolean | unknown[]]
36+
change: [event: Event]
37+
}>()
38+
39+
const isChecked = computed(() => {
40+
if (Array.isArray(props.modelValue)) {
41+
return props.value !== undefined && props.modelValue.includes(props.value)
42+
}
43+
return Boolean(props.modelValue)
44+
})
45+
46+
const onChange = (event: Event) => {
47+
const target = event.target as HTMLInputElement
48+
if (Array.isArray(props.modelValue)) {
49+
const next = [...props.modelValue]
50+
const idx = next.indexOf(props.value)
51+
if (target.checked && idx === -1) next.push(props.value)
52+
else if (!target.checked && idx !== -1) next.splice(idx, 1)
53+
emit('update:modelValue', next)
54+
} else {
55+
emit('update:modelValue', target.checked)
56+
}
57+
emit('change', event)
58+
}
59+
</script>
60+
61+
<style scoped>
62+
.sp-checkbox {
63+
align-items: center;
64+
display: inline-flex;
65+
position: relative;
66+
}
67+
68+
.sp-checkbox-input {
69+
appearance: none;
70+
-webkit-appearance: none;
71+
cursor: pointer;
72+
height: 14px;
73+
inset: 0;
74+
margin: 0;
75+
opacity: 0;
76+
position: absolute;
77+
width: 14px;
78+
}
79+
80+
.sp-checkbox-input:disabled {
81+
cursor: not-allowed;
82+
}
83+
84+
.sp-checkbox-box {
85+
align-items: center;
86+
background: var(--panel);
87+
border: 1.5px solid var(--line);
88+
border-radius: 3px;
89+
color: #ffffff;
90+
display: inline-flex;
91+
flex-shrink: 0;
92+
height: 14px;
93+
justify-content: center;
94+
pointer-events: none;
95+
transition:
96+
background 0.15s ease,
97+
border-color 0.15s ease;
98+
width: 14px;
99+
}
100+
101+
.sp-checkbox:hover .sp-checkbox-box {
102+
border-color: var(--accent);
103+
}
104+
105+
.sp-checkbox.checked .sp-checkbox-box {
106+
background: var(--accent);
107+
border-color: var(--accent);
108+
}
109+
110+
.sp-checkbox.disabled {
111+
opacity: 0.5;
112+
}
113+
114+
.sp-checkbox-mark {
115+
height: 10px;
116+
stroke-dasharray: 24;
117+
stroke-dashoffset: 24;
118+
transition: stroke-dashoffset 0.24s cubic-bezier(0.4, 0, 0.2, 1);
119+
width: 10px;
120+
}
121+
122+
.sp-checkbox.checked .sp-checkbox-mark {
123+
stroke-dashoffset: 0;
124+
}
125+
126+
.sp-checkbox-input:focus-visible + .sp-checkbox-box {
127+
outline: 2px solid var(--accent);
128+
outline-offset: 1px;
129+
}
130+
</style>

src/ui/popup/Popup.vue

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,23 @@
4747

4848
<template v-else>
4949
<section class="summary" aria-label="检测概览">
50-
<div>
50+
<RippleButton
51+
:class="['summary-tile', { active: state.activeCategory === FOCUS_CATEGORY && !footerPanel }]"
52+
title="返回重点列表"
53+
@click="focusTechnologyList"
54+
>
5155
<span>{{ animatedTotal }}</span>
5256
<label>技术</label>
53-
</div>
54-
<button
55-
type="button"
57+
</RippleButton>
58+
<RippleButton
5659
:class="['summary-tile', { active: footerPanel === 'resources' }]"
5760
title="查看抓取到的资源列表"
5861
@click="openSummaryDetail('resources')"
5962
>
6063
<span>{{ animatedResource }}</span>
6164
<label>资源</label>
62-
</button>
63-
<button
64-
type="button"
65+
</RippleButton>
66+
<RippleButton
6567
:class="['summary-tile', { active: footerPanel === 'headers' }]"
6668
title="查看主文档响应头"
6769
@click="openSummaryDetail('headers')"
@@ -71,7 +73,7 @@
7173
响应头
7274
<Loader2 v-if="isDetecting" class="detection-spinner" :size="12" :stroke-width="2" />
7375
</label>
74-
</button>
76+
</RippleButton>
7577
</section>
7678

7779
<nav class="filter-bar" aria-label="技术分类过滤">
@@ -195,15 +197,15 @@
195197
</div>
196198
<div class="search-options">
197199
<label>
198-
<input v-model="search.caseSensitive" type="checkbox" />
200+
<Checkbox v-model="search.caseSensitive" />
199201
区分大小写
200202
</label>
201203
<label>
202-
<input v-model="search.wholeWord" type="checkbox" />
204+
<Checkbox v-model="search.wholeWord" />
203205
全字匹配
204206
</label>
205207
<label>
206-
<input v-model="search.useRegex" type="checkbox" />
208+
<Checkbox v-model="search.useRegex" />
207209
正则表达式
208210
</label>
209211
</div>
@@ -283,6 +285,7 @@
283285
X
284286
} from 'lucide-vue-next'
285287
import Select from '@/ui/components/Select.vue'
288+
import Checkbox from '@/ui/components/Checkbox.vue'
286289
import RippleButton from '@/ui/components/RippleButton.vue'
287290
import { categoryIndex, confidenceClass, confidenceRank } from '@/utils/category-order'
288291
import { applyCustomCss } from '@/utils/apply-custom-css'
@@ -723,6 +726,12 @@
723726
state.activeCategory = category
724727
}
725728
729+
const focusTechnologyList = () => {
730+
closeFooterPanel()
731+
state.activeCategory = FOCUS_CATEGORY
732+
sectionsScroller.value?.scrollTo({ top: 0, behavior: 'smooth' })
733+
}
734+
726735
const openTechnologyLink = (tech: any) => {
727736
if (!tech.url) return
728737
chrome.tabs.create({ url: tech.url })

src/ui/settings/Settings.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
</div>
4848
<div class="category-grid">
4949
<label v-for="cat in CATEGORY_ORDER" :key="cat" class="toggle-item">
50-
<input v-model="enabledCategories[cat]" type="checkbox" :value="cat" @change="collectCategorySettings" />
50+
<Checkbox v-model="enabledCategories[cat]" :value="cat" @change="collectCategorySettings" />
5151
{{ cat }}
5252
</label>
5353
</div>
@@ -109,7 +109,7 @@
109109

110110
<div class="match-targets" aria-label="匹配范围">
111111
<label v-for="target in MATCH_TARGETS" :key="target.value">
112-
<input v-model="form.matchIn" type="checkbox" :value="target.value" />
112+
<Checkbox v-model="form.matchIn" :value="target.value" />
113113
{{ target.label }}
114114
</label>
115115
</div>
@@ -178,6 +178,7 @@
178178
import { onMounted, reactive, ref, watch, computed } from 'vue'
179179
import { BookOpen, ExternalLink, Inbox, Monitor, Moon, Pencil, RotateCcw, Save, Sun, Trash2 } from 'lucide-vue-next'
180180
import Select from '@/ui/components/Select.vue'
181+
import Checkbox from '@/ui/components/Checkbox.vue'
181182
import RippleButton from '@/ui/components/RippleButton.vue'
182183
import { CATEGORY_ORDER } from '@/utils/category-order'
183184
import { applyCustomCss } from '@/utils/apply-custom-css'

0 commit comments

Comments
 (0)