feat(web): 申请页改用中文姓名,前端转拼音提交

- ApplyView 的'姓名'字段(2~10 个汉字)替代自定义用户名
- 前端经 pinyin-pro(toneType:none + surname:head 处理姓氏多音字)转拼音作为用户名
- 提交前兜底校验拼音用户名长度(后端规则 [a-z][a-z0-9]{1,31})
- alert 文案改为实时预览 '通过后自动创建账号 ext_<username>'
- 新增依赖 pinyin-pro@3.29.3
This commit is contained in:
2026-08-30 17:28:05 +08:00
parent 7340a3dd48
commit ab3a874556
3 changed files with 44 additions and 15 deletions
+7
View File
@@ -12,6 +12,7 @@
"axios": "^1.7.9", "axios": "^1.7.9",
"element-plus": "^2.9.3", "element-plus": "^2.9.3",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"pinyin-pro": "^3.29.3",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
}, },
@@ -1830,6 +1831,12 @@
} }
} }
}, },
"node_modules/pinyin-pro": {
"version": "3.29.3",
"resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.29.3.tgz",
"integrity": "sha512-+UU9bx6vfDw8amOJGHm0TE0rdQl8VPylsDWviQ5OOQ3e+on1xRP4OqDbiDuMT5OISgvfl/Y6ez1BBRaIP80GLQ==",
"license": "MIT"
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.26", "version": "8.5.26",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+1
View File
@@ -14,6 +14,7 @@
"axios": "^1.7.9", "axios": "^1.7.9",
"element-plus": "^2.9.3", "element-plus": "^2.9.3",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"pinyin-pro": "^3.29.3",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
}, },
+36 -15
View File
@@ -1,26 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from 'vue' import { computed, reactive, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { pinyin } from 'pinyin-pro'
import * as api from '@/api' import * as api from '@/api'
const router = useRouter() const router = useRouter()
const submitting = ref(false) const submitting = ref(false)
const form = reactive({ const form = reactive({
username: '', realName: '', // 中文姓名,提交时转换为拼音作为用户名
email: '', email: '',
supervisor: '', supervisor: '',
purpose: '', purpose: '',
}) })
// 前端校验:中文姓名、邮箱、挂靠老师、用途均在前端完成
const rules = { const rules = {
username: [ realName: [
{ required: true, message: '请输入用户名', trigger: 'blur' }, { required: true, message: '请输入中文姓名', trigger: 'blur' },
{ { pattern: /^[\u4e00-\u9fa5]{2,10}$/, message: '请输入 2~10 个汉字', trigger: 'blur' },
pattern: /^[a-z][a-z0-9_]{1,30}$/,
message: '小写字母开头,仅小写字母/数字/下划线,2~31 位',
trigger: 'blur',
},
], ],
email: [ email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' }, { required: true, message: '请输入邮箱', trigger: 'blur' },
@@ -32,21 +30,44 @@ const rules = {
const formRef = ref() const formRef = ref()
// 姓名 → 拼音(小写、无空格;首字按姓氏读音处理多音字)
function nameToPinyin(name: string): string {
const parts = pinyin(name, { toneType: 'none', type: 'array', surname: 'head' })
return parts.join('')
}
// 实时预览将创建的账号名;姓名非法或未输入时返回空
const accountPreview = computed(() => {
const n = form.realName.trim()
if (!/^[\u4e00-\u9fa5]{2,10}$/.test(n)) return ''
return `ext_${nameToPinyin(n)}`
})
// 提交前的前端校验(姓名→拼音合法性兜底,防止姓名过长)
function validUsername(uname: string): boolean {
return /^[a-z][a-z0-9]{1,31}$/.test(uname)
}
async function onSubmit() { async function onSubmit() {
const valid = await formRef.value?.validate().catch(() => false) const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return if (!valid) return
const uname = nameToPinyin(form.realName.trim())
if (!validUsername(uname)) {
ElMessage.warning('姓名拼音过长,无法生成可用账号,请联系管理员')
return
}
submitting.value = true submitting.value = true
try { try {
await api.submitApproval({ await api.submitApproval({
username: form.username.trim(), username: uname,
email: form.email.trim(), email: form.email.trim(),
supervisor: form.supervisor.trim(), supervisor: form.supervisor.trim(),
purpose: form.purpose.trim(), purpose: form.purpose.trim(),
}) })
ElMessage.success('申请已提交,等待管理员审批(结果将邮件通知)') ElMessage.success('申请已提交,等待管理员审批(结果将邮件通知)')
Object.assign(form, { username: '', email: '', supervisor: '', purpose: '' }) Object.assign(form, { realName: '', email: '', supervisor: '', purpose: '' })
} catch { } catch {
// 拦截器已提示(如用户名已被占用 // 拦截器已提示(如拼音与已有申请重名
} finally { } finally {
submitting.value = false submitting.value = false
} }
@@ -65,14 +86,14 @@ async function onSubmit() {
</div> </div>
<el-alert type="info" :closable="false" show-icon class="apply-tip"> <el-alert type="info" :closable="false" show-icon class="apply-tip">
通过后自动创建账号用户名将带 <code>ext_</code> 前缀 通过后自动创建账号 {{ accountPreview || 'ext_<username>' }}
</el-alert> </el-alert>
<el-divider /> <el-divider />
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" @submit.prevent="onSubmit"> <el-form ref="formRef" :model="form" :rules="rules" label-width="90px" @submit.prevent="onSubmit">
<el-form-item label="用户名" prop="username"> <el-form-item label="名" prop="realName">
<el-input v-model="form.username" placeholder="如 zhangsan(自动成为 ext_zhangsan" maxlength="31" /> <el-input v-model="form.realName" placeholder="输入中文姓名,如 张三" maxlength="10" />
</el-form-item> </el-form-item>
<el-form-item label="邮箱" prop="email"> <el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="接收通知与 OTP 验证码" /> <el-input v-model="form.email" placeholder="接收通知与 OTP 验证码" />