tool.xpcool.com/src/views/case-convert/CaseConvertView.vue

158 lines
4.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 命名转换把输入串切分为词元实时生成 camelCase / PascalCase / snake_case / kebab-case 6 种风格 -->
<template>
<div class="page-wrap">
<h2 class="page-title">命名转换</h2>
<p class="page-desc">输入变量名或短语实时转换为 camelCasePascalCasesnake_casekebab-case 6 种常见命名风格</p>
<t-card class="tool-section" title="输入">
<t-input
v-model="input"
placeholder="输入变量名,例如 my_user_name 或 myUserName"
clearable
@enter="saveHistory"
/>
</t-card>
<ErrorAlert :message="error" @close="error = ''" />
<t-card v-if="words.length" class="tool-section" title="转换结果">
<div class="convert-list">
<div v-for="row in resultRows" :key="row.name" class="convert-row">
<span class="convert-label">{{ row.name }}</span>
<code class="convert-value">{{ row.value }}</code>
<CopyButton :text="row.value" label="复制" />
</div>
</div>
</t-card>
<HistoryPanel
:items="history.items"
@select="onHistorySelect"
@remove="history.remove"
@clear="history.clear"
/>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import ErrorAlert from '@/components/common/ErrorAlert.vue'
import CopyButton from '@/components/common/CopyButton.vue'
import HistoryPanel from '@/components/common/HistoryPanel.vue'
import { useHistory } from '@/composables/useHistory'
const history = useHistory('case-convert')
const input = ref('')
// 本页为实时转换无校验失败场景error 仅用于保持全站页面结构一致
const error = ref('')
/**
* 将输入切分为全小写词元数组
* 先按非字母数字切分下划线/连字符/空格等作为分隔符中文每个汉字视为独立词元
* 再把连续字母数字串按 camelCase 边界小写/数字大写字母数字数字字母细分
*/
function tokenize(s: string): string[] {
const words: string[] = []
const segments = s.match(/[a-zA-Z0-9]+|[\u4e00-\u9fa5]/g) ?? []
for (const seg of segments) {
if (/^[\u4e00-\u9fa5]$/.test(seg)) {
words.push(seg)
continue
}
// 在驼峰边界插入空格再切分最后统一转小写
const parts = seg
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
.replace(/([A-Za-z])(\d)/g, '$1 $2')
.replace(/(\d)([A-Za-z])/g, '$1 $2')
.split(/\s+/)
.map((w) => w.toLowerCase())
words.push(...parts)
}
return words
}
const words = computed(() => tokenize(input.value))
/** 单词首字母大写(对中文词元无副作用) */
function capitalize(w: string): string {
return w.charAt(0).toUpperCase() + w.slice(1)
}
/** 6 种命名风格的实时输出 */
const outputs = computed(() => {
const ws = words.value
return {
camel: ws.map((w, i) => (i === 0 ? w : capitalize(w))).join(''),
pascal: ws.map(capitalize).join(''),
snake: ws.join('_'),
upperSnake: ws.join('_').toUpperCase(),
kebab: ws.join('-'),
space: ws.join(' '),
}
})
/** 供模板遍历的展示行(风格名固定宽度放左侧) */
const resultRows = computed(() => [
{ name: 'camelCase', value: outputs.value.camel },
{ name: 'PascalCase', value: outputs.value.pascal },
{ name: 'snake_case', value: outputs.value.snake },
{ name: 'UPPER_SNAKE_CASE', value: outputs.value.upperSnake },
{ name: 'kebab-case', value: outputs.value.kebab },
{ name: '空格分隔', value: outputs.value.space },
])
/** 回车把当前输入存入历史 */
function saveHistory() {
if (input.value.trim()) history.add(input.value)
}
function onHistorySelect(text: string) {
input.value = text
}
</script>
<style scoped>
.convert-list {
display: flex;
flex-direction: column;
}
.convert-row {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
border-bottom: 1px solid var(--td-border-level-1-color);
}
.convert-row:last-child {
border-bottom: none;
}
.convert-label {
width: 150px;
flex-shrink: 0;
font-size: 13px;
font-weight: 600;
color: var(--td-text-color-secondary);
}
.convert-value {
flex: 1;
min-width: 0;
padding: 6px 10px;
border-radius: 6px;
background: var(--td-bg-color-secondarycontainer);
font-family: monospace;
font-size: 13px;
color: var(--td-text-color-primary);
overflow-wrap: anywhere;
}
@media (max-width: 767px) {
/* 窄屏下风格名与内容上下排列,避免横向滚动 */
.convert-row {
flex-wrap: wrap;
}
.convert-label {
width: 100%;
}
}
</style>