调整,音乐模块
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
|
||||
const input = ref('')
|
||||
const showFrequency = ref(true)
|
||||
|
||||
function utf8Bytes(s: string): number {
|
||||
return new TextEncoder().encode(s).length
|
||||
}
|
||||
|
||||
/** 统计词:中文按字、英文按单词 */
|
||||
function countWords(s: string): number {
|
||||
const cjk = (s.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) ?? []).length
|
||||
const latin = (s.match(/[a-zA-Z0-9]+(?:[-'’][a-zA-Z0-9]+)*/g) ?? []).length
|
||||
return cjk + latin
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
chars: number
|
||||
charsNoSpace: number
|
||||
bytes: number
|
||||
words: number
|
||||
lines: number
|
||||
nonEmptyLines: number
|
||||
sentences: number
|
||||
paragraphs: number
|
||||
}
|
||||
|
||||
const stats = computed<Stats>(() => {
|
||||
const v = input.value
|
||||
const lines = v === '' ? 0 : v.split('\n').length
|
||||
const nonEmpty = v.split('\n').filter(l => l.trim()).length
|
||||
// 句子:以 。!?.!?;; 结尾的段落片段
|
||||
const sentences = (v.match(/[^。!?!?\n]+[。!?!?]?/g) ?? []).filter(s => s.trim()).length
|
||||
const paragraphs = v.split(/\n\s*\n/).filter(p => p.trim()).length
|
||||
return {
|
||||
chars: v.length,
|
||||
charsNoSpace: v.replace(/\s/g, '').length,
|
||||
bytes: utf8Bytes(v),
|
||||
words: countWords(v),
|
||||
lines,
|
||||
nonEmptyLines: nonEmpty,
|
||||
sentences,
|
||||
paragraphs
|
||||
}
|
||||
})
|
||||
|
||||
const FIELDS: Array<{ key: keyof Stats; label: string }> = [
|
||||
{ key: 'chars', label: '字符数' },
|
||||
{ key: 'charsNoSpace', label: '字符数(不含空白)' },
|
||||
{ key: 'bytes', label: '字节数(UTF-8)' },
|
||||
{ key: 'words', label: '词数(中文按字/英文按词)' },
|
||||
{ key: 'lines', label: '行数' },
|
||||
{ key: 'nonEmptyLines', label: '非空行数' },
|
||||
{ key: 'sentences', label: '句子数' },
|
||||
{ key: 'paragraphs', label: '段落数(空行分隔)' }
|
||||
]
|
||||
|
||||
/** 高频字符 / 高频词 top 10 */
|
||||
const topChars = computed(() => {
|
||||
if (!showFrequency.value || !input.value) return []
|
||||
const map = new Map<string, number>()
|
||||
for (const ch of input.value) {
|
||||
if (/\s/.test(ch)) continue
|
||||
map.set(ch, (map.get(ch) ?? 0) + 1)
|
||||
}
|
||||
return [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10)
|
||||
})
|
||||
|
||||
const topWords = computed(() => {
|
||||
if (!showFrequency.value || !input.value) return []
|
||||
const tokens = input.value.match(/[\u4e00-\u9fff]|[a-zA-Z0-9]+(?:[-'’][a-zA-Z0-9]+)*/g) ?? []
|
||||
const map = new Map<string, number>()
|
||||
for (const t of tokens) {
|
||||
map.set(t, (map.get(t) ?? 0) + 1)
|
||||
}
|
||||
return [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label class="text-xs">文本</Label>
|
||||
<label class="flex items-center gap-2 text-xs text-muted-foreground cursor-pointer">
|
||||
显示高频统计
|
||||
<Switch v-model="showFrequency" />
|
||||
</label>
|
||||
</div>
|
||||
<Textarea v-model="input" placeholder="粘贴或输入文本,实时统计..." class="min-h-[160px] font-mono text-xs resize-y" />
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border border-border divide-y divide-border text-xs">
|
||||
<div v-for="f in FIELDS" :key="f.key" class="flex justify-between px-3 py-1.5">
|
||||
<span class="text-muted-foreground">{{ f.label }}</span>
|
||||
<span class="font-mono">{{ stats[f.key].toLocaleString() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="showFrequency && input">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<Label class="text-xs">高频字符 Top 10</Label>
|
||||
<div class="rounded-md border border-border divide-y divide-border text-xs">
|
||||
<div v-for="([ch, n], i) in topChars" :key="i" class="flex justify-between px-3 py-1">
|
||||
<span class="font-mono w-8 text-center rounded bg-muted">{{ ch }}</span>
|
||||
<span class="font-mono text-muted-foreground">{{ n }} 次</span>
|
||||
</div>
|
||||
<div v-if="topChars.length === 0" class="px-3 py-2 text-muted-foreground">无内容</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<Label class="text-xs">高频词 Top 10</Label>
|
||||
<div class="rounded-md border border-border divide-y divide-border text-xs">
|
||||
<div v-for="([w, n], i) in topWords" :key="i" class="flex justify-between px-3 py-1">
|
||||
<span class="font-mono">{{ w }}</span>
|
||||
<span class="font-mono text-muted-foreground">{{ n }} 次</span>
|
||||
</div>
|
||||
<div v-if="topWords.length === 0" class="px-3 py-2 text-muted-foreground">无内容</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user