vome-core 0.0.99 → 0.0.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/config/plugin-dev.js +1 -1
- package/dist/admin/crud/components/vm-color-picker.vue +338 -53
- package/dist/admin/crud/config.js +1 -1
- package/dist/admin/crud/confirm.js +1 -1
- package/dist/admin/crud/dict.js +1 -1
- package/dist/admin/crud/index.js +1 -1
- package/dist/admin/crud/key.js +1 -1
- package/dist/admin/crud/mitt.js +1 -1
- package/dist/admin/crud/plugins.js +1 -1
- package/dist/admin/crud/span.js +1 -1
- package/dist/admin/crud/style.js +1 -1
- package/dist/admin/crud/validate.js +1 -1
- package/dist/admin/directives/perm.js +1 -1
- package/dist/admin/hooks/useUpload.js +1 -1
- package/dist/admin/lib/browser.js +1 -1
- package/dist/admin/lib/cn.js +1 -1
- package/dist/admin/lib/dialog-float.js +1 -1
- package/dist/admin/lib/export-excel.js +1 -1
- package/dist/admin/lib/import-excel.js +1 -1
- package/dist/admin/lib/json.js +1 -1
- package/dist/admin/lib/menu.js +1 -1
- package/dist/admin/lib/tree.js +1 -1
- package/dist/admin/lib/upload.js +1 -1
- package/dist/admin/lib/video-frame.js +1 -1
- package/dist/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/shared/excel.js +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/locale-json.js +1 -1
- package/dist/shared/tree.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const _0x3ff90c=_0x5f53;(function(_0x23faa9,_0x3572ce){const _0xa7d377=_0x5f53,_0x8dc756=_0x23faa9();while(!![]){try{const _0x142915=-parseInt(_0xa7d377(0x163))/0x1*(-parseInt(_0xa7d377(0x16b))/0x2)+-parseInt(_0xa7d377(0x171))/0x3+-parseInt(_0xa7d377(0x168))/0x4+-parseInt(_0xa7d377(0x165))/0x5+-parseInt(_0xa7d377(0x16d))/0x6*(-parseInt(_0xa7d377(0x16c))/0x7)+parseInt(_0xa7d377(0x173))/0x8*(-parseInt(_0xa7d377(0x161))/0x9)+-parseInt(_0xa7d377(0x16f))/0xa*(-parseInt(_0xa7d377(0x166))/0xb);if(_0x142915===_0x3572ce)break;else _0x8dc756['push'](_0x8dc756['shift']());}catch(_0x45a0a5){_0x8dc756['push'](_0x8dc756['shift']());}}}(_0x3f1f,0x930a9));function _0x5f53(_0x43315f,_0xc905dc){_0x43315f=_0x43315f-0x161;const _0x3f1f34=_0x3f1f();let _0x5f5342=_0x3f1f34[_0x43315f];if(_0x5f53['clGSmL']===undefined){var _0x101aee=function(_0x464ddb){const _0x4b4663='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x35dbb7='',_0x275b70='';for(let _0x12ab83=0x0,_0xc22f40,_0x4894b0,_0x3d5255=0x0;_0x4894b0=_0x464ddb['charAt'](_0x3d5255++);~_0x4894b0&&(_0xc22f40=_0x12ab83%0x4?_0xc22f40*0x40+_0x4894b0:_0x4894b0,_0x12ab83++%0x4)?_0x35dbb7+=String['fromCharCode'](0xff&_0xc22f40>>(-0x2*_0x12ab83&0x6)):0x0){_0x4894b0=_0x4b4663['indexOf'](_0x4894b0);}for(let _0x392816=0x0,_0xea8127=_0x35dbb7['length'];_0x392816<_0xea8127;_0x392816++){_0x275b70+='%'+('00'+_0x35dbb7['charCodeAt'](_0x392816)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x275b70);};_0x5f53['RkKRtU']=_0x101aee,_0x5f53['UELVwS']={},_0x5f53['clGSmL']=!![];}const _0x226af0=_0x3f1f34[0x0],_0x3f6264=_0x43315f+_0x226af0,_0x35b9fb=_0x5f53['UELVwS'][_0x3f6264];return!_0x35b9fb?(_0x5f5342=_0x5f53['RkKRtU'](_0x5f5342),_0x5f53['UELVwS'][_0x3f6264]=_0x5f5342):_0x5f5342=_0x35b9fb,_0x5f5342;}const ORG=_0x3ff90c(0x167);export function serviceUrlFromDocs(_0x35dbb7){const _0x3faaab=_0x3ff90c,_0x275b70=new URL(_0x35dbb7),_0x12ab83=_0x275b70['hostname'][_0x3faaab(0x17c)](/^www\./,'')[_0x3faaab(0x17c)](/^service\./,'');return _0x275b70[_0x3faaab(0x16e)]+_0x3faaab(0x17b)+_0x12ab83;}export function seatApiBaseFromDocs(_0xc22f40){return serviceUrlFromDocs(_0xc22f40)+'/app/pluginStore/seat';}export function licenseIssueUrlFromDocs(_0x4894b0){const _0x36d9fa=_0x3ff90c;return serviceUrlFromDocs(_0x4894b0)+_0x36d9fa(0x164);}function giteeArchive(_0x3d5255){const _0x228479=_0x3ff90c;return'https://gitee.com/'+ORG+'/'+_0x3d5255+_0x228479(0x17a);}function githubArchive(_0x392816){const _0x5bcdee=_0x3ff90c;return _0x5bcdee(0x169)+ORG+'/'+_0x392816+_0x5bcdee(0x179);}function giteeRepo(_0xea8127){return'https://gitee.com/'+ORG+'/'+_0xea8127;}function githubRepo(_0x32dc63){return'https://github.com/'+ORG+'/'+_0x32dc63;}function _0x3f1f(){const _0x2631af=['57QV5yMn56UV','DM9Tzs1WBhvNAw4TzNjVBNq','mJa4mZqXy3POALbz','DM9Tzs1WBhvNAw4TC2vYDMLJzq','mNzuyuzwBq','l2fWCc9WBhvNAw5tDg9Yzs93ywXSzxqVBgLJzw5Zzs9PC3n1zq','ntiZmdeZmgvdsgLVAq','mtfRtuPTsKW','DM9TzwTQ','mJy5mdKYoeLwEerzvq','Ahr0Chm6lY9NAxrODwiUy29TlW','Ahr0Chm6lY9NAxrLzs5JB20V','nZeWodzZyKnMzw0','mJeYnty5y2zrBLzw','nKDXt09vtG','ChjVDg9JB2W','mZu5mde1ndbOu0rjBfa','zNvSBa','mJm3ntuYowL5BNvXCG','5yMn56UVicSG5zco56UV','mJaWD1HzqwDz','DM9Tzs1WBhvNAw4TzNvSBa','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','l3bSDwDPBNmVDxbSB2fK','zNjVBNrLBMq','yMfJA2vUza','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','l3jLCg9ZAxrVCNKVyxjJAgL2zs9Tyxn0zxiUEMLW','lY9Zzxj2AwnLlG','CMvWBgfJzq','Ahr0Chm6lY92B21LA2OUy29T','57QV5zco56UV'];_0x3f1f=function(){return _0x2631af;};return _0x3f1f();}const docsUrl=_0x3ff90c(0x17d);export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+_0x3ff90c(0x176),'mirrors':{'prefer':'gitee','giteeOrg':_0x3ff90c(0x16a)+ORG,'githubOrg':_0x3ff90c(0x169)+ORG},'scaffolds':{'full':{'key':_0x3ff90c(0x170),'title':_0x3ff90c(0x172),'desc':_0x3ff90c(0x175),'repo':_0x3ff90c(0x174),'repoUrl':giteeRepo(_0x3ff90c(0x174)),'githubRepoUrl':githubRepo(_0x3ff90c(0x174)),'downloadUrl':giteeArchive(_0x3ff90c(0x174)),'githubDownloadUrl':githubArchive(_0x3ff90c(0x174))},'backend':{'key':_0x3ff90c(0x178),'title':_0x3ff90c(0x17e),'desc':'钩子\x20/\x20服务端插件脚手架(.vome:仅\x20server),适合上传、短信、支付等能力扩展。','repo':_0x3ff90c(0x162),'repoUrl':giteeRepo(_0x3ff90c(0x162)),'githubRepoUrl':githubRepo(_0x3ff90c(0x162)),'downloadUrl':giteeArchive('vome-plugin-service'),'githubDownloadUrl':githubArchive(_0x3ff90c(0x162))},'frontend':{'key':_0x3ff90c(0x177),'title':_0x3ff90c(0x17f),'desc':'后台微应用脚手架(.vome:含\x20web/),以\x20wujie\x20挂载独立前端页面。','repo':'vome-plugin-front','repoUrl':giteeRepo(_0x3ff90c(0x180)),'githubRepoUrl':githubRepo(_0x3ff90c(0x180)),'downloadUrl':giteeArchive(_0x3ff90c(0x180)),'githubDownloadUrl':githubArchive('vome-plugin-front')}}};
|
|
@@ -67,7 +67,19 @@
|
|
|
67
67
|
title="吸取颜色"
|
|
68
68
|
@click="pickEyeDropper"
|
|
69
69
|
>
|
|
70
|
-
|
|
70
|
+
<!-- 内联 SVG:不依赖 remixicon 字体是否已加载 -->
|
|
71
|
+
<svg
|
|
72
|
+
class="vm-color-picker__eyedropper-ico"
|
|
73
|
+
viewBox="0 0 24 24"
|
|
74
|
+
width="16"
|
|
75
|
+
height="16"
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
>
|
|
78
|
+
<path
|
|
79
|
+
fill="currentColor"
|
|
80
|
+
d="M19.07 3.66a2.5 2.5 0 0 1 0 3.54l-1.42 1.41 2.12 2.12-1.41 1.42-2.12-2.13-7.07 7.07c-.3.3-.68.48-1.08.54l-3.36.48a.75.75 0 0 1-.84-.84l.48-3.36c.06-.4.24-.78.54-1.08l7.07-7.07-1.41-1.41 1.41-1.42 2.12 2.13 1.42-1.42a2.5 2.5 0 0 1 3.54 0Zm-2.12 2.12.71-.7a1 1 0 0 0-1.42-1.42l-.7.71 1.41 1.41ZM8.48 15.9l-.17.17-.28 1.9 1.9-.28.17-.17 5.66-5.66-1.62-1.62-5.66 5.66Z"
|
|
81
|
+
/>
|
|
82
|
+
</svg>
|
|
71
83
|
</button>
|
|
72
84
|
<div class="vm-color-picker__slider-col">
|
|
73
85
|
<div
|
|
@@ -77,7 +89,7 @@
|
|
|
77
89
|
>
|
|
78
90
|
<span
|
|
79
91
|
class="vm-color-picker__slider-thumb"
|
|
80
|
-
:style="{ left:
|
|
92
|
+
:style="{ left: thumbLeft(hue / 360) }"
|
|
81
93
|
/>
|
|
82
94
|
</div>
|
|
83
95
|
<div
|
|
@@ -91,25 +103,57 @@
|
|
|
91
103
|
/>
|
|
92
104
|
<span
|
|
93
105
|
class="vm-color-picker__slider-thumb"
|
|
94
|
-
:style="{ left:
|
|
106
|
+
:style="{ left: thumbLeft(alpha) }"
|
|
95
107
|
/>
|
|
96
108
|
</div>
|
|
97
109
|
</div>
|
|
98
110
|
</div>
|
|
99
111
|
|
|
100
112
|
<div class="vm-color-picker__footer">
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
<div class="vm-color-picker__format-wrap">
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
class="vm-color-picker__format"
|
|
117
|
+
:class="{ 'is-open': formatOpen }"
|
|
118
|
+
title="颜色格式"
|
|
119
|
+
@click.stop="toggleFormatMenu"
|
|
120
|
+
>
|
|
121
|
+
{{ formatLabel }}
|
|
122
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
|
|
123
|
+
<path fill="currentColor" d="M12 15.4 6.6 10l1.4-1.4 4 4 4-4L17.4 10 12 15.4Z" />
|
|
124
|
+
</svg>
|
|
125
|
+
</button>
|
|
126
|
+
<div v-if="formatOpen" class="vm-color-picker__format-menu" role="listbox">
|
|
127
|
+
<button
|
|
128
|
+
v-for="opt in FORMAT_OPTS"
|
|
129
|
+
:key="opt.id"
|
|
130
|
+
type="button"
|
|
131
|
+
class="vm-color-picker__format-item"
|
|
132
|
+
:class="{ 'is-active': colorFormat === opt.id }"
|
|
133
|
+
role="option"
|
|
134
|
+
:aria-selected="colorFormat === opt.id"
|
|
135
|
+
@click.stop="setFormat(opt.id)"
|
|
136
|
+
>
|
|
137
|
+
<span class="vm-color-picker__format-check" aria-hidden="true">
|
|
138
|
+
<svg v-if="colorFormat === opt.id" viewBox="0 0 24 24" width="14" height="14">
|
|
139
|
+
<path
|
|
140
|
+
fill="currentColor"
|
|
141
|
+
d="M9.55 17.6 4.9 12.95l1.4-1.4 3.25 3.25 7.15-7.15 1.4 1.4-8.55 8.55Z"
|
|
142
|
+
/>
|
|
143
|
+
</svg>
|
|
144
|
+
</span>
|
|
145
|
+
{{ opt.label }}
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
105
149
|
<input
|
|
106
150
|
type="text"
|
|
107
|
-
class="vm-color-
|
|
108
|
-
:value="
|
|
151
|
+
class="vm-color-picker__value"
|
|
152
|
+
:value="panelValue"
|
|
109
153
|
spellcheck="false"
|
|
110
154
|
autocomplete="off"
|
|
111
|
-
@input="
|
|
112
|
-
@blur="
|
|
155
|
+
@input="onPanelValueInput"
|
|
156
|
+
@blur="onPanelValueBlur"
|
|
113
157
|
@keydown.esc.stop="closePanel"
|
|
114
158
|
/>
|
|
115
159
|
<input
|
|
@@ -155,6 +199,17 @@ const emit = defineEmits<{
|
|
|
155
199
|
|
|
156
200
|
type Rgba = { r: number; g: number; b: number; a: number }
|
|
157
201
|
type Hsv = { h: number; s: number; v: number }
|
|
202
|
+
type ColorFormat = 'hex' | 'rgb' | 'css' | 'hsl' | 'hsb'
|
|
203
|
+
|
|
204
|
+
const FORMAT_OPTS: { id: ColorFormat; label: string }[] = [
|
|
205
|
+
{ id: 'hex', label: 'Hex' },
|
|
206
|
+
{ id: 'rgb', label: 'RGB' },
|
|
207
|
+
{ id: 'css', label: 'CSS' },
|
|
208
|
+
{ id: 'hsl', label: 'HSL' },
|
|
209
|
+
{ id: 'hsb', label: 'HSB' },
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
const THUMB_PX = 14
|
|
158
213
|
|
|
159
214
|
const rootRef = ref<HTMLElement | null>(null)
|
|
160
215
|
const triggerRef = ref<HTMLElement | null>(null)
|
|
@@ -166,15 +221,17 @@ const alphaRef = ref<HTMLElement | null>(null)
|
|
|
166
221
|
const open = ref(false)
|
|
167
222
|
const suppressOpen = ref(false)
|
|
168
223
|
const panelStyle = ref<Record<string, string>>({})
|
|
224
|
+
const formatOpen = ref(false)
|
|
225
|
+
const colorFormat = ref<ColorFormat>('hex')
|
|
169
226
|
|
|
170
227
|
const hue = ref(0)
|
|
171
228
|
const sat = ref(0)
|
|
172
229
|
const val = ref(0)
|
|
173
230
|
const alpha = ref(1)
|
|
174
231
|
|
|
175
|
-
/** 行内
|
|
232
|
+
/** 行内 Hex(不含 #);面板按当前格式展示 */
|
|
176
233
|
const inlineHex = ref('')
|
|
177
|
-
const
|
|
234
|
+
const panelValue = ref('')
|
|
178
235
|
const alphaPctText = ref('100%')
|
|
179
236
|
|
|
180
237
|
const hasEyeDropper = ref(false)
|
|
@@ -184,10 +241,20 @@ const placeholderText = computed(() => {
|
|
|
184
241
|
return p.startsWith('#') ? p.slice(1) : p
|
|
185
242
|
})
|
|
186
243
|
|
|
244
|
+
const formatLabel = computed(
|
|
245
|
+
() => FORMAT_OPTS.find((o) => o.id === colorFormat.value)?.label ?? 'Hex',
|
|
246
|
+
)
|
|
247
|
+
|
|
187
248
|
function clamp(n: number, min: number, max: number) {
|
|
188
249
|
return Math.min(max, Math.max(min, n))
|
|
189
250
|
}
|
|
190
251
|
|
|
252
|
+
/** 拇指中心落在轨道内,两端不溢出 */
|
|
253
|
+
function thumbLeft(ratio: number) {
|
|
254
|
+
const t = clamp(ratio, 0, 1)
|
|
255
|
+
return `calc(${t} * (100% - ${THUMB_PX}px) + ${THUMB_PX / 2}px)`
|
|
256
|
+
}
|
|
257
|
+
|
|
191
258
|
function toHex2(n: number) {
|
|
192
259
|
return clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0')
|
|
193
260
|
}
|
|
@@ -251,6 +318,61 @@ function rgbToHsv(r: number, g: number, b: number): Hsv {
|
|
|
251
318
|
return { h, s, v: max }
|
|
252
319
|
}
|
|
253
320
|
|
|
321
|
+
function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
|
|
322
|
+
const rr = r / 255
|
|
323
|
+
const gg = g / 255
|
|
324
|
+
const bb = b / 255
|
|
325
|
+
const max = Math.max(rr, gg, bb)
|
|
326
|
+
const min = Math.min(rr, gg, bb)
|
|
327
|
+
const l = (max + min) / 2
|
|
328
|
+
const d = max - min
|
|
329
|
+
let h = 0
|
|
330
|
+
let s = 0
|
|
331
|
+
if (d !== 0) {
|
|
332
|
+
s = d / (1 - Math.abs(2 * l - 1))
|
|
333
|
+
if (max === rr) h = ((gg - bb) / d) % 6
|
|
334
|
+
else if (max === gg) h = (bb - rr) / d + 2
|
|
335
|
+
else h = (rr - gg) / d + 4
|
|
336
|
+
h *= 60
|
|
337
|
+
if (h < 0) h += 360
|
|
338
|
+
}
|
|
339
|
+
return { h, s, l }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
|
|
343
|
+
const hh = ((h % 360) + 360) % 360
|
|
344
|
+
const c = (1 - Math.abs(2 * l - 1)) * s
|
|
345
|
+
const x = c * (1 - Math.abs(((hh / 60) % 2) - 1))
|
|
346
|
+
const m = l - c / 2
|
|
347
|
+
let rp = 0
|
|
348
|
+
let gp = 0
|
|
349
|
+
let bp = 0
|
|
350
|
+
if (hh < 60) {
|
|
351
|
+
rp = c
|
|
352
|
+
gp = x
|
|
353
|
+
} else if (hh < 120) {
|
|
354
|
+
rp = x
|
|
355
|
+
gp = c
|
|
356
|
+
} else if (hh < 180) {
|
|
357
|
+
gp = c
|
|
358
|
+
bp = x
|
|
359
|
+
} else if (hh < 240) {
|
|
360
|
+
gp = x
|
|
361
|
+
bp = c
|
|
362
|
+
} else if (hh < 300) {
|
|
363
|
+
rp = x
|
|
364
|
+
bp = c
|
|
365
|
+
} else {
|
|
366
|
+
rp = c
|
|
367
|
+
bp = x
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
r: (rp + m) * 255,
|
|
371
|
+
g: (gp + m) * 255,
|
|
372
|
+
b: (bp + m) * 255,
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
254
376
|
/** 解析为 rgba;无效返回 null */
|
|
255
377
|
function parseColor(raw: string): Rgba | null {
|
|
256
378
|
const s = raw.trim()
|
|
@@ -283,9 +405,8 @@ function parseColor(raw: string): Rgba | null {
|
|
|
283
405
|
return null
|
|
284
406
|
}
|
|
285
407
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return parseColor(`#${bare}`)
|
|
408
|
+
if (/^[0-9a-fA-F]{3}$/.test(s) || /^[0-9a-fA-F]{6}$/.test(s) || /^[0-9a-fA-F]{8}$/.test(s)) {
|
|
409
|
+
return parseColor(`#${s}`)
|
|
289
410
|
}
|
|
290
411
|
|
|
291
412
|
const m = s.match(
|
|
@@ -299,6 +420,68 @@ function parseColor(raw: string): Rgba | null {
|
|
|
299
420
|
a: m[4] != null ? clamp(Number(m[4]), 0, 1) : 1,
|
|
300
421
|
}
|
|
301
422
|
}
|
|
423
|
+
|
|
424
|
+
const hsl = s.match(
|
|
425
|
+
/^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%(?:\s*,\s*([\d.]+))?\s*\)$/i,
|
|
426
|
+
)
|
|
427
|
+
if (hsl) {
|
|
428
|
+
const rgb = hslToRgb(Number(hsl[1]), Number(hsl[2]) / 100, Number(hsl[3]) / 100)
|
|
429
|
+
return {
|
|
430
|
+
r: rgb.r,
|
|
431
|
+
g: rgb.g,
|
|
432
|
+
b: rgb.b,
|
|
433
|
+
a: hsl[4] != null ? clamp(Number(hsl[4]), 0, 1) : 1,
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return null
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** 按当前格式解析面板中间输入(不改变 alpha,除非 CSS/rgba 自带) */
|
|
441
|
+
function parsePanelValue(raw: string, keepAlpha: number): Rgba | null {
|
|
442
|
+
const s = raw.trim()
|
|
443
|
+
if (!s) return null
|
|
444
|
+
const fmt = colorFormat.value
|
|
445
|
+
|
|
446
|
+
if (fmt === 'hex' || fmt === 'css') {
|
|
447
|
+
const parsed = parseColor(s)
|
|
448
|
+
if (!parsed) return null
|
|
449
|
+
if (fmt === 'hex') parsed.a = keepAlpha
|
|
450
|
+
return parsed
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (fmt === 'rgb') {
|
|
454
|
+
const parts = s.split(/[,\s]+/).filter(Boolean)
|
|
455
|
+
if (parts.length < 3) return null
|
|
456
|
+
const r = Number(parts[0])
|
|
457
|
+
const g = Number(parts[1])
|
|
458
|
+
const b = Number(parts[2])
|
|
459
|
+
if (![r, g, b].every(Number.isFinite)) return null
|
|
460
|
+
return { r: clamp(r, 0, 255), g: clamp(g, 0, 255), b: clamp(b, 0, 255), a: keepAlpha }
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (fmt === 'hsl') {
|
|
464
|
+
const parts = s.replace(/%/g, '').split(/[,\s]+/).filter(Boolean)
|
|
465
|
+
if (parts.length < 3) return null
|
|
466
|
+
const h = Number(parts[0])
|
|
467
|
+
const satPct = Number(parts[1])
|
|
468
|
+
const lPct = Number(parts[2])
|
|
469
|
+
if (![h, satPct, lPct].every(Number.isFinite)) return null
|
|
470
|
+
const rgb = hslToRgb(h, clamp(satPct, 0, 100) / 100, clamp(lPct, 0, 100) / 100)
|
|
471
|
+
return { ...rgb, a: keepAlpha }
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (fmt === 'hsb') {
|
|
475
|
+
const parts = s.replace(/%/g, '').split(/[,\s]+/).filter(Boolean)
|
|
476
|
+
if (parts.length < 3) return null
|
|
477
|
+
const h = Number(parts[0])
|
|
478
|
+
const sPct = Number(parts[1])
|
|
479
|
+
const bPct = Number(parts[2])
|
|
480
|
+
if (![h, sPct, bPct].every(Number.isFinite)) return null
|
|
481
|
+
const rgb = hsvToRgb(h, clamp(sPct, 0, 100) / 100, clamp(bPct, 0, 100) / 100)
|
|
482
|
+
return { ...rgb, a: keepAlpha }
|
|
483
|
+
}
|
|
484
|
+
|
|
302
485
|
return null
|
|
303
486
|
}
|
|
304
487
|
|
|
@@ -311,6 +494,31 @@ function currentHex(): string {
|
|
|
311
494
|
return rgbToHex(r, g, b, alpha.value)
|
|
312
495
|
}
|
|
313
496
|
|
|
497
|
+
function formatPanelValue(): string {
|
|
498
|
+
const { r, g, b } = currentRgb()
|
|
499
|
+
const rr = Math.round(r)
|
|
500
|
+
const gg = Math.round(g)
|
|
501
|
+
const bb = Math.round(b)
|
|
502
|
+
const a = alpha.value
|
|
503
|
+
switch (colorFormat.value) {
|
|
504
|
+
case 'rgb':
|
|
505
|
+
return `${rr}, ${gg}, ${bb}`
|
|
506
|
+
case 'css':
|
|
507
|
+
return a < 1 - 1e-6
|
|
508
|
+
? `rgba(${rr}, ${gg}, ${bb}, ${Number(a.toFixed(2))})`
|
|
509
|
+
: `#${toHex2(rr)}${toHex2(gg)}${toHex2(bb)}`
|
|
510
|
+
case 'hsl': {
|
|
511
|
+
const hsl = rgbToHsl(rr, gg, bb)
|
|
512
|
+
return `${Math.round(hsl.h)}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%`
|
|
513
|
+
}
|
|
514
|
+
case 'hsb':
|
|
515
|
+
return `${Math.round(hue.value)}, ${Math.round(sat.value * 100)}%, ${Math.round(val.value * 100)}%`
|
|
516
|
+
case 'hex':
|
|
517
|
+
default:
|
|
518
|
+
return `${toHex2(rr)}${toHex2(gg)}${toHex2(bb)}`
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
314
522
|
const cssColor = computed(() => {
|
|
315
523
|
const parsed = parseColor(props.modelValue ?? '')
|
|
316
524
|
if (!parsed && !(props.modelValue ?? '').trim()) return 'transparent'
|
|
@@ -338,9 +546,8 @@ const svCursorStyle = computed(() => ({
|
|
|
338
546
|
function syncTextsFromState() {
|
|
339
547
|
const hex = currentHex()
|
|
340
548
|
const bare = hex.startsWith('#') ? hex.slice(1) : hex
|
|
341
|
-
// 行内只展示 RGB 六位;透明度在面板 % 里改
|
|
342
549
|
inlineHex.value = bare.slice(0, 6)
|
|
343
|
-
|
|
550
|
+
panelValue.value = formatPanelValue()
|
|
344
551
|
alphaPctText.value = `${Math.round(alpha.value * 100)}%`
|
|
345
552
|
}
|
|
346
553
|
|
|
@@ -362,7 +569,7 @@ function loadFromModel(raw: string | null | undefined) {
|
|
|
362
569
|
val.value = 0
|
|
363
570
|
alpha.value = 1
|
|
364
571
|
inlineHex.value = ''
|
|
365
|
-
|
|
572
|
+
panelValue.value = ''
|
|
366
573
|
alphaPctText.value = '100%'
|
|
367
574
|
return
|
|
368
575
|
}
|
|
@@ -383,7 +590,6 @@ watch(
|
|
|
383
590
|
() => props.modelValue,
|
|
384
591
|
(v) => {
|
|
385
592
|
if (open.value) {
|
|
386
|
-
// 面板打开时若外部写入且与当前一致则跳过抖动
|
|
387
593
|
const cur = currentHex()
|
|
388
594
|
const parsed = parseColor(v ?? '')
|
|
389
595
|
if (parsed && rgbToHex(parsed.r, parsed.g, parsed.b, parsed.a) === cur) return
|
|
@@ -413,7 +619,7 @@ function onInlineInput(e: Event) {
|
|
|
413
619
|
hue.value = hsv.h
|
|
414
620
|
sat.value = hsv.s
|
|
415
621
|
val.value = hsv.v
|
|
416
|
-
|
|
622
|
+
panelValue.value = formatPanelValue()
|
|
417
623
|
commit(currentHex())
|
|
418
624
|
} else {
|
|
419
625
|
const raw = inlineHex.value.trim()
|
|
@@ -437,25 +643,25 @@ function onInlineBlur() {
|
|
|
437
643
|
}
|
|
438
644
|
}
|
|
439
645
|
|
|
440
|
-
function
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
646
|
+
function onPanelValueInput(e: Event) {
|
|
647
|
+
panelValue.value = (e.target as HTMLInputElement).value
|
|
648
|
+
const parsed = parsePanelValue(panelValue.value, alpha.value)
|
|
649
|
+
if (!parsed) return
|
|
650
|
+
const hsv = rgbToHsv(parsed.r, parsed.g, parsed.b)
|
|
651
|
+
hue.value = hsv.h
|
|
652
|
+
sat.value = hsv.s
|
|
653
|
+
val.value = hsv.v
|
|
654
|
+
if (colorFormat.value === 'css') alpha.value = parsed.a
|
|
655
|
+
const hex = currentHex()
|
|
656
|
+
inlineHex.value = hex.replace(/^#/, '').slice(0, 6)
|
|
657
|
+
alphaPctText.value = `${Math.round(alpha.value * 100)}%`
|
|
658
|
+
commit(hex)
|
|
453
659
|
}
|
|
454
660
|
|
|
455
|
-
function
|
|
456
|
-
const parsed =
|
|
661
|
+
function onPanelValueBlur() {
|
|
662
|
+
const parsed = parsePanelValue(panelValue.value, alpha.value)
|
|
457
663
|
if (parsed) {
|
|
458
|
-
parsed.a = alpha.value
|
|
664
|
+
if (colorFormat.value !== 'css') parsed.a = alpha.value
|
|
459
665
|
applyRgba(parsed, true)
|
|
460
666
|
} else {
|
|
461
667
|
syncTextsFromState()
|
|
@@ -479,6 +685,16 @@ function onAlphaPctBlur() {
|
|
|
479
685
|
commit(currentHex())
|
|
480
686
|
}
|
|
481
687
|
|
|
688
|
+
function toggleFormatMenu() {
|
|
689
|
+
formatOpen.value = !formatOpen.value
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function setFormat(id: ColorFormat) {
|
|
693
|
+
colorFormat.value = id
|
|
694
|
+
formatOpen.value = false
|
|
695
|
+
syncTextsFromState()
|
|
696
|
+
}
|
|
697
|
+
|
|
482
698
|
function ratioFromEvent(el: HTMLElement, e: PointerEvent, axis: 'x' | 'y' | 'both') {
|
|
483
699
|
const r = el.getBoundingClientRect()
|
|
484
700
|
const x = clamp((e.clientX - r.left) / r.width, 0, 1)
|
|
@@ -540,7 +756,11 @@ function onAlphaPointer(e: PointerEvent) {
|
|
|
540
756
|
}
|
|
541
757
|
|
|
542
758
|
async function pickEyeDropper() {
|
|
543
|
-
const ED = (
|
|
759
|
+
const ED = (
|
|
760
|
+
window as unknown as {
|
|
761
|
+
EyeDropper?: new () => { open: () => Promise<{ sRGBHex: string }> }
|
|
762
|
+
}
|
|
763
|
+
).EyeDropper
|
|
544
764
|
if (!ED) return
|
|
545
765
|
try {
|
|
546
766
|
const result = await new ED().open()
|
|
@@ -564,7 +784,7 @@ function syncPanelPos() {
|
|
|
564
784
|
left = Math.max(8, window.innerWidth - width - 8)
|
|
565
785
|
}
|
|
566
786
|
let top = r.bottom + 6
|
|
567
|
-
const estH =
|
|
787
|
+
const estH = 320
|
|
568
788
|
if (top + estH > window.innerHeight - 8) {
|
|
569
789
|
top = Math.max(8, r.top - estH - 6)
|
|
570
790
|
}
|
|
@@ -580,6 +800,7 @@ function syncPanelPos() {
|
|
|
580
800
|
async function openPanel() {
|
|
581
801
|
if (props.disabled || suppressOpen.value) return
|
|
582
802
|
loadFromModel(props.modelValue)
|
|
803
|
+
formatOpen.value = false
|
|
583
804
|
open.value = true
|
|
584
805
|
await nextTick()
|
|
585
806
|
syncPanelPos()
|
|
@@ -592,6 +813,7 @@ function onPanelUnmountAutoFocus(event: Event) {
|
|
|
592
813
|
function closePanel() {
|
|
593
814
|
if (!open.value) return
|
|
594
815
|
suppressOpen.value = true
|
|
816
|
+
formatOpen.value = false
|
|
595
817
|
open.value = false
|
|
596
818
|
nextTick(() => {
|
|
597
819
|
requestAnimationFrame(() => {
|
|
@@ -623,9 +845,7 @@ function onWinChange() {
|
|
|
623
845
|
}
|
|
624
846
|
|
|
625
847
|
onMounted(() => {
|
|
626
|
-
hasEyeDropper.value =
|
|
627
|
-
typeof window !== 'undefined' &&
|
|
628
|
-
'EyeDropper' in window
|
|
848
|
+
hasEyeDropper.value = typeof window !== 'undefined' && 'EyeDropper' in window
|
|
629
849
|
document.addEventListener('pointerdown', onDocPointer, true)
|
|
630
850
|
window.addEventListener('resize', onWinChange)
|
|
631
851
|
window.addEventListener('scroll', onWinChange, true)
|
|
@@ -741,6 +961,7 @@ onUnmounted(() => {
|
|
|
741
961
|
0 4px 6px -1px rgb(0 0 0 / 10%),
|
|
742
962
|
0 10px 24px -4px rgb(0 0 0 / 12%);
|
|
743
963
|
user-select: none;
|
|
964
|
+
overflow: visible;
|
|
744
965
|
}
|
|
745
966
|
|
|
746
967
|
.vm-color-picker__sv {
|
|
@@ -783,7 +1004,6 @@ onUnmounted(() => {
|
|
|
783
1004
|
border-radius: 6px;
|
|
784
1005
|
background: var(--muted);
|
|
785
1006
|
color: var(--foreground);
|
|
786
|
-
font-size: 15px;
|
|
787
1007
|
cursor: pointer;
|
|
788
1008
|
|
|
789
1009
|
&:hover {
|
|
@@ -792,6 +1012,10 @@ onUnmounted(() => {
|
|
|
792
1012
|
}
|
|
793
1013
|
}
|
|
794
1014
|
|
|
1015
|
+
.vm-color-picker__eyedropper-ico {
|
|
1016
|
+
display: block;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
795
1019
|
.vm-color-picker__slider-col {
|
|
796
1020
|
flex: 1;
|
|
797
1021
|
min-width: 0;
|
|
@@ -807,6 +1031,8 @@ onUnmounted(() => {
|
|
|
807
1031
|
border-radius: 999px;
|
|
808
1032
|
cursor: pointer;
|
|
809
1033
|
touch-action: none;
|
|
1034
|
+
/* 允许拇指画出轨道高度,但水平用 thumbLeft 钳在内 */
|
|
1035
|
+
overflow: visible;
|
|
810
1036
|
}
|
|
811
1037
|
|
|
812
1038
|
.vm-color-picker__hue {
|
|
@@ -835,7 +1061,6 @@ onUnmounted(() => {
|
|
|
835
1061
|
4px -4px,
|
|
836
1062
|
-4px 0;
|
|
837
1063
|
background-color: #fff;
|
|
838
|
-
overflow: hidden;
|
|
839
1064
|
}
|
|
840
1065
|
|
|
841
1066
|
.vm-color-picker__alpha-fill {
|
|
@@ -843,6 +1068,7 @@ onUnmounted(() => {
|
|
|
843
1068
|
inset: 0;
|
|
844
1069
|
border-radius: inherit;
|
|
845
1070
|
pointer-events: none;
|
|
1071
|
+
overflow: hidden;
|
|
846
1072
|
}
|
|
847
1073
|
|
|
848
1074
|
.vm-color-picker__slider-thumb {
|
|
@@ -850,43 +1076,102 @@ onUnmounted(() => {
|
|
|
850
1076
|
top: 50%;
|
|
851
1077
|
width: 14px;
|
|
852
1078
|
height: 14px;
|
|
853
|
-
|
|
1079
|
+
transform: translate(-50%, -50%);
|
|
854
1080
|
border: 2px solid #fff;
|
|
855
1081
|
border-radius: 50%;
|
|
856
1082
|
background: transparent;
|
|
857
1083
|
box-shadow: 0 0 0 1px rgb(0 0 0 / 35%);
|
|
858
1084
|
pointer-events: none;
|
|
1085
|
+
box-sizing: border-box;
|
|
859
1086
|
}
|
|
860
1087
|
|
|
861
1088
|
.vm-color-picker__footer {
|
|
1089
|
+
position: relative;
|
|
862
1090
|
display: flex;
|
|
863
1091
|
align-items: center;
|
|
864
1092
|
gap: 6px;
|
|
865
1093
|
margin-top: 12px;
|
|
866
1094
|
}
|
|
867
1095
|
|
|
868
|
-
.vm-color-picker__format {
|
|
1096
|
+
.vm-color-picker__format-wrap {
|
|
1097
|
+
position: relative;
|
|
869
1098
|
flex-shrink: 0;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.vm-color-picker__format {
|
|
870
1102
|
display: inline-flex;
|
|
871
1103
|
align-items: center;
|
|
872
1104
|
gap: 2px;
|
|
873
1105
|
height: 28px;
|
|
874
|
-
padding: 0
|
|
1106
|
+
padding: 0 8px;
|
|
875
1107
|
border: 1px solid var(--border);
|
|
876
1108
|
border-radius: 6px;
|
|
877
1109
|
background: var(--muted);
|
|
878
|
-
color: var(--
|
|
1110
|
+
color: var(--foreground);
|
|
879
1111
|
font-size: 12px;
|
|
880
1112
|
line-height: 1;
|
|
881
|
-
cursor:
|
|
1113
|
+
cursor: pointer;
|
|
1114
|
+
|
|
1115
|
+
&:hover,
|
|
1116
|
+
&.is-open {
|
|
1117
|
+
border-color: var(--brand, #4e5dff);
|
|
1118
|
+
}
|
|
882
1119
|
|
|
883
|
-
|
|
884
|
-
font-size: 14px;
|
|
1120
|
+
svg {
|
|
885
1121
|
opacity: 0.7;
|
|
886
1122
|
}
|
|
887
1123
|
}
|
|
888
1124
|
|
|
889
|
-
.vm-color-
|
|
1125
|
+
.vm-color-picker__format-menu {
|
|
1126
|
+
position: absolute;
|
|
1127
|
+
left: 0;
|
|
1128
|
+
bottom: calc(100% + 4px);
|
|
1129
|
+
z-index: 2;
|
|
1130
|
+
min-width: 96px;
|
|
1131
|
+
padding: 4px;
|
|
1132
|
+
border: 1px solid var(--border);
|
|
1133
|
+
border-radius: 8px;
|
|
1134
|
+
background: var(--popover, var(--background, #fff));
|
|
1135
|
+
box-shadow:
|
|
1136
|
+
0 4px 6px -1px rgb(0 0 0 / 10%),
|
|
1137
|
+
0 10px 20px -4px rgb(0 0 0 / 12%);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.vm-color-picker__format-item {
|
|
1141
|
+
display: flex;
|
|
1142
|
+
align-items: center;
|
|
1143
|
+
gap: 6px;
|
|
1144
|
+
width: 100%;
|
|
1145
|
+
height: 28px;
|
|
1146
|
+
padding: 0 8px;
|
|
1147
|
+
border: none;
|
|
1148
|
+
border-radius: 6px;
|
|
1149
|
+
background: transparent;
|
|
1150
|
+
color: var(--foreground);
|
|
1151
|
+
font-size: 12px;
|
|
1152
|
+
text-align: left;
|
|
1153
|
+
cursor: pointer;
|
|
1154
|
+
|
|
1155
|
+
&:hover {
|
|
1156
|
+
background: color-mix(in srgb, var(--brand, #4e5dff) 12%, transparent);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
&.is-active {
|
|
1160
|
+
background: var(--brand, #4e5dff);
|
|
1161
|
+
color: #fff;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.vm-color-picker__format-check {
|
|
1166
|
+
display: inline-flex;
|
|
1167
|
+
width: 14px;
|
|
1168
|
+
height: 14px;
|
|
1169
|
+
flex-shrink: 0;
|
|
1170
|
+
align-items: center;
|
|
1171
|
+
justify-content: center;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
.vm-color-picker__value,
|
|
890
1175
|
.vm-color-picker__alpha-pct {
|
|
891
1176
|
height: 28px;
|
|
892
1177
|
padding: 0 8px;
|
|
@@ -903,7 +1188,7 @@ onUnmounted(() => {
|
|
|
903
1188
|
}
|
|
904
1189
|
}
|
|
905
1190
|
|
|
906
|
-
.vm-color-
|
|
1191
|
+
.vm-color-picker__value {
|
|
907
1192
|
flex: 1;
|
|
908
1193
|
min-width: 0;
|
|
909
1194
|
}
|