Skip to content

Commit fb40acb

Browse files
committed
feat: 抽出 RippleButton 组件并接入按钮波纹效果
弹窗、设置页、帮助页的矩形按钮替换为 RippleButton,pointerdown 时显示克制的扩散波纹(默认 18%、主按钮 28%、420ms);Select 触发器和文本链接形态按钮保持原样。 将版本号提升到 1.2.1。
1 parent 8cdd322 commit fb40acb

5 files changed

Lines changed: 166 additions & 52 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.0",
4+
"version": "1.2.1",
55
"type": "module",
66
"description": "StackPrism 用于检测网页前端、后端、CDN、SaaS、广告营销、统计、登录、支付、网站程序和主题模板线索。",
77
"scripts": {

src/ui/components/RippleButton.vue

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<template>
2+
<button ref="btnRef" :type="type" :disabled="disabled" class="sp-rb" :class="[`sp-rb--${variant}`]" @pointerdown="onPointerDown">
3+
<span class="sp-rb-content"><slot /></span>
4+
<span class="sp-rb-ripples" aria-hidden="true">
5+
<span
6+
v-for="r in ripples"
7+
:key="r.id"
8+
class="sp-rb-ripple"
9+
:style="{ left: `${r.x}px`, top: `${r.y}px`, width: `${r.size}px`, height: `${r.size}px` }"
10+
@animationend="onRippleEnd(r.id)"
11+
/>
12+
</span>
13+
</button>
14+
</template>
15+
16+
<script setup lang="ts">
17+
import { ref } from 'vue'
18+
19+
type Variant = 'default' | 'primary' | 'danger'
20+
21+
const props = withDefaults(
22+
defineProps<{
23+
type?: 'button' | 'submit' | 'reset'
24+
disabled?: boolean
25+
variant?: Variant
26+
}>(),
27+
{
28+
type: 'button',
29+
disabled: false,
30+
variant: 'default'
31+
}
32+
)
33+
34+
interface Ripple {
35+
id: number
36+
x: number
37+
y: number
38+
size: number
39+
}
40+
41+
const ripples = ref<Ripple[]>([])
42+
const btnRef = ref<HTMLButtonElement | null>(null)
43+
let nextId = 0
44+
45+
const onPointerDown = (e: PointerEvent) => {
46+
if (props.disabled) return
47+
const el = btnRef.value
48+
if (!el) return
49+
const rect = el.getBoundingClientRect()
50+
const dx = Math.max(e.clientX - rect.left, rect.right - e.clientX)
51+
const dy = Math.max(e.clientY - rect.top, rect.bottom - e.clientY)
52+
const radius = Math.sqrt(dx * dx + dy * dy)
53+
const size = radius * 2
54+
ripples.value.push({
55+
id: nextId++,
56+
x: e.clientX - rect.left - radius,
57+
y: e.clientY - rect.top - radius,
58+
size
59+
})
60+
}
61+
62+
const onRippleEnd = (id: number) => {
63+
ripples.value = ripples.value.filter(r => r.id !== id)
64+
}
65+
</script>
66+
67+
<style scoped>
68+
.sp-rb {
69+
position: relative;
70+
overflow: hidden;
71+
isolation: isolate;
72+
}
73+
74+
.sp-rb-content {
75+
position: relative;
76+
z-index: 1;
77+
display: inline-flex;
78+
align-items: center;
79+
gap: 6px;
80+
}
81+
82+
.sp-rb-ripples {
83+
position: absolute;
84+
inset: 0;
85+
pointer-events: none;
86+
z-index: 0;
87+
}
88+
89+
.sp-rb-ripple {
90+
position: absolute;
91+
border-radius: 50%;
92+
background: currentColor;
93+
opacity: 0.18;
94+
transform: scale(0);
95+
animation: sp-rb-spread 420ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
96+
}
97+
98+
.sp-rb--primary .sp-rb-ripple {
99+
background: #ffffff;
100+
opacity: 0.28;
101+
}
102+
103+
@keyframes sp-rb-spread {
104+
to {
105+
transform: scale(1);
106+
opacity: 0;
107+
}
108+
}
109+
110+
@media (prefers-reduced-motion: reduce) {
111+
.sp-rb-ripple {
112+
animation-duration: 0ms;
113+
}
114+
}
115+
</style>

src/ui/help/Help.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
<p>这页专门讲怎么添加自己的识别规则。你可以把规则理解成一句话:看到什么字,就认为网页用了什么东西。</p>
1010
</div>
1111
<div class="header-actions">
12-
<button type="button" class="icon-btn primary" title="打开设置页" @click="openSettings">
12+
<RippleButton class="icon-btn primary" variant="primary" title="打开设置页" @click="openSettings">
1313
<Settings2 :size="16" :stroke-width="2" />
14-
</button>
15-
<button type="button" class="icon-btn" title="GitHub 仓库" @click="openRepo">
14+
</RippleButton>
15+
<RippleButton class="icon-btn" title="GitHub 仓库" @click="openRepo">
1616
<ExternalLink :size="16" :stroke-width="2" />
17-
</button>
17+
</RippleButton>
1818
</div>
1919
</div>
2020
</header>
@@ -341,6 +341,7 @@ examplepay-sdk</pre
341341
<script setup lang="ts">
342342
import { onMounted, ref } from 'vue'
343343
import { ExternalLink, Settings2 } from 'lucide-vue-next'
344+
import RippleButton from '@/ui/components/RippleButton.vue'
344345
import { REPOSITORY_URL } from '@/utils/constants'
345346
346347
const version = ref('')

src/ui/popup/Popup.vue

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@
99
<p class="url">{{ pageUrl }}</p>
1010
</div>
1111
<div class="actions">
12-
<button type="button" class="icon-btn" :title="`主题:${themeLabel(theme)}(点击切换)`" @click="toggleTheme">
12+
<RippleButton class="icon-btn" :title="`主题:${themeLabel(theme)}(点击切换)`" @click="toggleTheme">
1313
<Sun v-if="theme === 'light'" :size="16" :stroke-width="2" />
1414
<Moon v-else-if="theme === 'dark'" :size="16" :stroke-width="2" />
1515
<Monitor v-else :size="16" :stroke-width="2" />
16-
</button>
17-
<button type="button" class="icon-btn" title="打开设置页" @click="openSettings">
16+
</RippleButton>
17+
<RippleButton class="icon-btn" title="打开设置页" @click="openSettings">
1818
<Settings2 :size="16" :stroke-width="2" />
19-
</button>
20-
<button type="button" class="icon-btn" title="复制检测 JSON" @click="copyResult">
19+
</RippleButton>
20+
<RippleButton class="icon-btn" title="复制检测 JSON" @click="copyResult">
2121
<Copy :size="16" :stroke-width="2" />
22-
</button>
23-
<button type="button" class="icon-btn primary" title="重新检测" @click="runDetection({ force: true })">
22+
</RippleButton>
23+
<RippleButton class="icon-btn primary" variant="primary" title="重新检测" @click="runDetection({ force: true })">
2424
<RefreshCw :size="16" :stroke-width="2" />
25-
</button>
25+
</RippleButton>
2626
</div>
2727
</header>
2828

@@ -59,26 +59,24 @@
5959

6060
<nav class="filter-bar" aria-label="技术分类过滤">
6161
<div class="segment" role="tablist">
62-
<button
63-
type="button"
62+
<RippleButton
6463
role="tab"
6564
:class="['segment-btn', { active: state.activeCategory === FOCUS_CATEGORY }]"
6665
:aria-selected="state.activeCategory === FOCUS_CATEGORY"
6766
@click="selectCategory(FOCUS_CATEGORY)"
6867
>
6968
<span>重点</span>
7069
<span class="segment-count">{{ focusCount }}</span>
71-
</button>
72-
<button
73-
type="button"
70+
</RippleButton>
71+
<RippleButton
7472
role="tab"
7573
:class="['segment-btn', { active: state.activeCategory === '全部' }]"
7674
:aria-selected="state.activeCategory === '全部'"
7775
@click="selectCategory('全部')"
7876
>
7977
<span>全部</span>
8078
<span class="segment-count">{{ totalCount }}</span>
81-
</button>
79+
</RippleButton>
8280
</div>
8381
<div class="filter-select">
8482
<Select v-model="categoryFilterValue" :options="categoryFilterOptions" placeholder="选择分类" />
@@ -149,9 +147,9 @@
149147
</div>
150148

151149
<Transition name="scroll-top-fade">
152-
<button v-show="showScrollTop" type="button" class="scroll-top" title="返回顶部" @click="scrollSectionsTop">
150+
<RippleButton v-show="showScrollTop" class="scroll-top" title="返回顶部" @click="scrollSectionsTop">
153151
<ArrowUp :size="16" :stroke-width="2" />
154-
</button>
152+
</RippleButton>
155153
</Transition>
156154
</template>
157155
</template>
@@ -162,14 +160,14 @@
162160
<span class="footer-panel-title">
163161
{{ footerPanel === 'search' ? '网页源代码搜索' : rawPanelTitle }}
164162
</span>
165-
<button type="button" class="footer-panel-close" title="关闭面板" @click="closeFooterPanel">
163+
<RippleButton class="footer-panel-close" title="关闭面板" @click="closeFooterPanel">
166164
<X :size="14" :stroke-width="2" />
167-
</button>
165+
</RippleButton>
168166
</header>
169167
<div v-if="footerPanel === 'search'" class="footer-panel-body">
170168
<div class="search-row">
171169
<input v-model="search.query" type="search" placeholder="输入关键词或正则表达式" @keydown.enter="searchPageSourceFromPopup" />
172-
<button type="button" @click="searchPageSourceFromPopup">搜索</button>
170+
<RippleButton @click="searchPageSourceFromPopup">搜索</RippleButton>
173171
</div>
174172
<div class="search-options">
175173
<label>
@@ -196,24 +194,22 @@
196194

197195
<footer class="app-footer">
198196
<div class="footer-tools">
199-
<button
200-
type="button"
197+
<RippleButton
201198
:class="['footer-tool-btn', { active: footerPanel === 'search' }]"
202199
title="网页源代码搜索"
203200
@click="toggleFooterPanel('search')"
204201
>
205202
<Search :size="13" :stroke-width="2" />
206203
<span>搜索</span>
207-
</button>
208-
<button
209-
type="button"
204+
</RippleButton>
205+
<RippleButton
210206
:class="['footer-tool-btn', { active: footerPanel === 'raw' }]"
211207
title="查看原始线索"
212208
@click="toggleFooterPanel('raw')"
213209
>
214210
<FileCode :size="13" :stroke-width="2" />
215211
<span>原始线索</span>
216-
</button>
212+
</RippleButton>
217213
</div>
218214
<a class="footer-repo" :href="REPOSITORY_URL" target="_blank" rel="noreferrer" @click="openRepository">GitHub</a>
219215
</footer>
@@ -241,6 +237,7 @@
241237
X
242238
} from 'lucide-vue-next'
243239
import Select from '@/ui/components/Select.vue'
240+
import RippleButton from '@/ui/components/RippleButton.vue'
244241
import { categoryIndex, confidenceClass, confidenceRank } from '@/utils/category-order'
245242
import { applyCustomCss } from '@/utils/apply-custom-css'
246243
import { normalizeSettings } from '@/utils/normalize-settings'

src/ui/settings/Settings.vue

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,28 @@
99
<p>控制识别分类,添加自定义规则,并覆盖弹窗样式。</p>
1010
</div>
1111
<div class="header-actions">
12-
<button type="button" :title="`主题:${themeLabel(theme)}(点击切换)`" @click="toggleTheme">
12+
<RippleButton :title="`主题:${themeLabel(theme)}(点击切换)`" @click="toggleTheme">
1313
<Sun v-if="theme === 'light'" :size="14" :stroke-width="2" />
1414
<Moon v-else-if="theme === 'dark'" :size="14" :stroke-width="2" />
1515
<Monitor v-else :size="14" :stroke-width="2" />
1616
<span>主题:{{ themeLabel(theme) }}</span>
17-
</button>
18-
<button type="button" @click="openHelp">
17+
</RippleButton>
18+
<RippleButton @click="openHelp">
1919
<BookOpen :size="14" :stroke-width="2" />
2020
<span>使用说明</span>
21-
</button>
22-
<button type="button" title="GitHub 仓库" @click="openRepository">
21+
</RippleButton>
22+
<RippleButton title="GitHub 仓库" @click="openRepository">
2323
<ExternalLink :size="14" :stroke-width="2" />
2424
<span>GitHub 仓库</span>
25-
</button>
26-
<button type="button" class="primary" @click="saveSettings">
25+
</RippleButton>
26+
<RippleButton class="primary" variant="primary" @click="saveSettings">
2727
<Save :size="14" :stroke-width="2" />
2828
<span>保存设置</span>
29-
</button>
30-
<button type="button" @click="resetSettings">
29+
</RippleButton>
30+
<RippleButton @click="resetSettings">
3131
<RotateCcw :size="14" :stroke-width="2" />
3232
<span>恢复默认</span>
33-
</button>
33+
</RippleButton>
3434
</div>
3535
</div>
3636
</header>
@@ -41,8 +41,8 @@
4141
<div class="panel-head">
4242
<h2>识别开关</h2>
4343
<div class="inline-actions">
44-
<button type="button" @click="setAllCategories(true)">全开</button>
45-
<button type="button" @click="setAllCategories(false)">全关</button>
44+
<RippleButton @click="setAllCategories(true)">全开</RippleButton>
45+
<RippleButton @click="setAllCategories(false)">全关</RippleButton>
4646
</div>
4747
</div>
4848
<div class="category-grid">
@@ -75,8 +75,8 @@
7575
<div class="panel-head">
7676
<h2>自定义规则</h2>
7777
<div class="inline-actions">
78-
<button type="button" @click="openContribute">提交规则贡献</button>
79-
<button type="button" @click="clearRuleForm">清空表单</button>
78+
<RippleButton @click="openContribute">提交规则贡献</RippleButton>
79+
<RippleButton @click="clearRuleForm">清空表单</RippleButton>
8080
</div>
8181
</div>
8282

@@ -135,8 +135,8 @@
135135
</div>
136136

137137
<div class="form-actions">
138-
<button type="button" class="primary" @click="addRuleFromForm">添加规则</button>
139-
<button type="button" @click="updateRuleFromForm">更新当前规则</button>
138+
<RippleButton class="primary" variant="primary" @click="addRuleFromForm">添加规则</RippleButton>
139+
<RippleButton @click="updateRuleFromForm">更新当前规则</RippleButton>
140140
</div>
141141

142142
<div class="rules-list">
@@ -150,12 +150,12 @@
150150
<div class="rule-meta">{{ ruleListLines[index] }}</div>
151151
</div>
152152
<div class="rule-actions">
153-
<button type="button" class="icon-btn" title="编辑此规则" @click="fillRuleForm(rule, index)">
153+
<RippleButton class="icon-btn" title="编辑此规则" @click="fillRuleForm(rule, index)">
154154
<Pencil :size="14" :stroke-width="2" />
155-
</button>
156-
<button type="button" class="icon-btn danger" title="删除此规则" @click="deleteRule(index)">
155+
</RippleButton>
156+
<RippleButton class="icon-btn danger" variant="danger" title="删除此规则" @click="deleteRule(index)">
157157
<Trash2 :size="14" :stroke-width="2" />
158-
</button>
158+
</RippleButton>
159159
</div>
160160
</div>
161161
</div>
@@ -165,8 +165,8 @@
165165
<div class="panel-head">
166166
<h2>规则 JSON</h2>
167167
<div class="inline-actions">
168-
<button type="button" @click="importRulesJson">从 JSON 导入</button>
169-
<button type="button" @click="formatRulesJson">格式化</button>
168+
<RippleButton @click="importRulesJson">从 JSON 导入</RippleButton>
169+
<RippleButton @click="formatRulesJson">格式化</RippleButton>
170170
</div>
171171
</div>
172172
<textarea v-model="rulesJsonText" rows="13" spellcheck="false"></textarea>
@@ -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 RippleButton from '@/ui/components/RippleButton.vue'
181182
import { CATEGORY_ORDER } from '@/utils/category-order'
182183
import { applyCustomCss } from '@/utils/apply-custom-css'
183184
import { cleanCustomRules, cleanStringArray, defaultSettings, normalizeSettings } from '@/utils/normalize-settings'

0 commit comments

Comments
 (0)