chore: 前端工程化 + Git hooks + 对齐 PathEditor 规范

- 新增 .gitattributes(CRLF 统一)+ rust-toolchain.toml
- 新增 Prettier + ESLint + markdownlint 配置
- 新增 Husky Git hooks(pre-commit lint-staged + commit-msg commitlint)
- 新增 vitest 前端测试(12 tests, utils/qrText.ts)
- 新增 @ 路径别名(vite + tsconfig)
- 新增 ROADMAP / SUPPORT / CODEOWNERS / FUNDING / dependabot
- 更新 .gitignore + .editorconfig
- 更新 package.json(新增 lint/format/test 脚本)
- 全项目 prettier 格式化 + eslint 通过
- 更新 CLAUDE.md + README.md
This commit is contained in:
2026-06-19 19:42:13 +08:00
parent ce8063431e
commit c3956f0f36
40 changed files with 4034 additions and 148 deletions
+18 -9
View File
@@ -14,15 +14,24 @@ export default function EmailMode() {
return (
<div className="flex gap-2 items-center h-full px-4">
<input placeholder="收件人" value={state.formData.to || ''}
onChange={e => update('to', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input placeholder="主题" value={state.formData.subject || ''}
onChange={e => update('subject', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input placeholder="正文" value={state.formData.body || ''}
onChange={e => update('body', e.target.value)}
className="flex-[2] px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input
placeholder="收件人"
value={state.formData.to || ''}
onChange={(e) => update('to', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
/>
<input
placeholder="主题"
value={state.formData.subject || ''}
onChange={(e) => update('subject', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
/>
<input
placeholder="正文"
value={state.formData.body || ''}
onChange={(e) => update('body', e.target.value)}
className="flex-[2] px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
/>
</div>
);
}
+7 -3
View File
@@ -12,8 +12,12 @@ export default function PhoneMode() {
};
return (
<input placeholder="输入电话号码" type="tel" value={state.formData.number || ''}
onChange={e => update(e.target.value)}
className="w-full h-full px-4 text-sm bg-transparent outline-none placeholder-gray-400 dark:placeholder-gray-600 focus:ring-2 focus:ring-blue-500/30" />
<input
placeholder="输入电话号码"
type="tel"
value={state.formData.number || ''}
onChange={(e) => update(e.target.value)}
className="w-full h-full px-4 text-sm bg-transparent outline-none placeholder-gray-400 dark:placeholder-gray-600 focus:ring-2 focus:ring-blue-500/30"
/>
);
}
+13 -6
View File
@@ -14,12 +14,19 @@ export default function SmsMode() {
return (
<div className="flex gap-2 items-center h-full px-4">
<input placeholder="电话号码" type="tel" value={state.formData.number || ''}
onChange={e => update('number', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input placeholder="短信内容" value={state.formData.message || ''}
onChange={e => update('message', e.target.value)}
className="flex-[2] px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input
placeholder="电话号码"
type="tel"
value={state.formData.number || ''}
onChange={(e) => update('number', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
/>
<input
placeholder="短信内容"
value={state.formData.message || ''}
onChange={(e) => update('message', e.target.value)}
className="flex-[2] px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
/>
</div>
);
}
+1 -1
View File
@@ -14,7 +14,7 @@ export default function TextMode() {
<textarea
placeholder="输入任意文本..."
value={state.formData.text || ''}
onChange={e => handleChange(e.target.value)}
onChange={(e) => handleChange(e.target.value)}
rows={3}
className="w-full h-full resize-none px-4 py-2 text-sm bg-transparent outline-none placeholder-gray-400 dark:placeholder-gray-600"
/>
+1 -1
View File
@@ -24,7 +24,7 @@ export default function UrlMode() {
type="url"
placeholder="https://example.com"
value={state.formData.url || ''}
onChange={e => handleChange(e.target.value)}
onChange={(e) => handleChange(e.target.value)}
onBlur={handleBlur}
className="w-full h-full px-4 text-sm bg-transparent outline-none placeholder-gray-400 dark:placeholder-gray-600"
/>
+7 -4
View File
@@ -22,11 +22,14 @@ export default function VCardMode() {
return (
<div className="flex gap-2 items-center h-full px-4">
{FIELDS.map(f => (
<input key={f.key} placeholder={f.placeholder}
{FIELDS.map((f) => (
<input
key={f.key}
placeholder={f.placeholder}
value={state.formData[f.key] || ''}
onChange={e => update(f.key, e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
onChange={(e) => update(f.key, e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
/>
))}
</div>
);
+23 -11
View File
@@ -15,22 +15,34 @@ export default function WifiMode() {
return (
<div className="flex gap-2 items-center h-full px-4">
<input placeholder="SSID" value={state.formData.ssid || ''}
onChange={e => update('ssid', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<input placeholder="密码" type="password" value={state.formData.password || ''}
onChange={e => update('password', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30" />
<select value={state.formData.encryption || 'WPA'}
onChange={e => update('encryption', e.target.value)}
className="px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30">
<input
placeholder="SSID"
value={state.formData.ssid || ''}
onChange={(e) => update('ssid', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
/>
<input
placeholder="密码"
type="password"
value={state.formData.password || ''}
onChange={(e) => update('password', e.target.value)}
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
/>
<select
value={state.formData.encryption || 'WPA'}
onChange={(e) => update('encryption', e.target.value)}
className="px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-sm bg-transparent outline-none focus:ring-2 focus:ring-blue-500/30"
>
<option value="WPA">WPA/WPA2</option>
<option value="WEP">WEP</option>
<option value="nopass"></option>
</select>
<label className="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
<input type="checkbox" checked={state.formData.hidden === 'true'}
onChange={e => update('hidden', e.target.checked ? 'true' : 'false')} />
<input
type="checkbox"
checked={state.formData.hidden === 'true'}
onChange={(e) => update('hidden', e.target.checked ? 'true' : 'false')}
/>
</label>
</div>