|
|
@ -81,7 +81,10 @@ |
|
|
:wrapper-col="{ span: 18 }" |
|
|
:wrapper-col="{ span: 18 }" |
|
|
> |
|
|
> |
|
|
<a-form-item label="单词" name="word"> |
|
|
<a-form-item label="单词" name="word"> |
|
|
<a-input v-model:value="formData.word" placeholder="请输入单词" /> |
|
|
|
|
|
|
|
|
<a-input v-model:value="formData.word" placeholder="请输入单词(最多30字符,中文算2)" /> |
|
|
|
|
|
<div class="field-count"> |
|
|
|
|
|
{{ wordLen }}/30 |
|
|
|
|
|
</div> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
|
|
|
|
|
|
<a-form-item label="图片" name="image"> |
|
|
<a-form-item label="图片" name="image"> |
|
|
@ -98,9 +101,12 @@ |
|
|
<a-form-item label="释义" name="paraphrase"> |
|
|
<a-form-item label="释义" name="paraphrase"> |
|
|
<a-textarea |
|
|
<a-textarea |
|
|
v-model:value="formData.paraphrase" |
|
|
v-model:value="formData.paraphrase" |
|
|
placeholder="请输入释义" |
|
|
|
|
|
|
|
|
placeholder="请输入释义(最多240字符,中文算2)" |
|
|
:rows="3" |
|
|
:rows="3" |
|
|
/> |
|
|
/> |
|
|
|
|
|
<div class="field-count"> |
|
|
|
|
|
{{ paraphraseLen }}/240 |
|
|
|
|
|
</div> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
|
|
|
|
|
|
<a-form-item label="音标" name="soundmark"> |
|
|
<a-form-item label="音标" name="soundmark"> |
|
|
@ -126,6 +132,45 @@ import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vu |
|
|
import { JImageUpload } from '/@/components/Form' |
|
|
import { JImageUpload } from '/@/components/Form' |
|
|
import { list, saveOrUpdate, deleteOne, batchDelete } from './coursePageWord' |
|
|
import { list, saveOrUpdate, deleteOne, batchDelete } from './coursePageWord' |
|
|
|
|
|
|
|
|
|
|
|
// 更稳妥的中文判断(常用区) |
|
|
|
|
|
const isChinese = (ch: string) => /[\u4e00-\u9fa5]/.test(ch) |
|
|
|
|
|
|
|
|
|
|
|
// 计算加权长度:中文按2字符计算,其它按1 |
|
|
|
|
|
const weightedLength = (str: string = ''): number => { |
|
|
|
|
|
return Array.from(str).reduce((sum, ch) => sum + (isChinese(ch) ? 2 : 1), 0) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 截断到指定加权长度 |
|
|
|
|
|
const truncateByWeightedLen = (str: string = '', max: number): string => { |
|
|
|
|
|
// let acc = 0 |
|
|
|
|
|
// let out = '' |
|
|
|
|
|
// for (const ch of Array.from(str)) { |
|
|
|
|
|
// const w = isChinese(ch) ? 2 : 1 |
|
|
|
|
|
// if (acc + w > max) break |
|
|
|
|
|
// acc += w |
|
|
|
|
|
// out += ch |
|
|
|
|
|
// } |
|
|
|
|
|
// return out |
|
|
|
|
|
return str |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 自定义表单校验:限制加权长度(中文算2) |
|
|
|
|
|
const createWeightedMaxRule = (max: number) => ({ |
|
|
|
|
|
validator: async (_: any, value: string) => { |
|
|
|
|
|
if (!value) return Promise.resolve() |
|
|
|
|
|
const len = Array.from(value).reduce((sum, ch) => sum + (isChinese(ch) ? 2 : 1), 0) |
|
|
|
|
|
if (len > max) { |
|
|
|
|
|
return Promise.reject(`长度不能超过${max}字符(中文算2字符)`) |
|
|
|
|
|
} |
|
|
|
|
|
return Promise.resolve() |
|
|
|
|
|
}, |
|
|
|
|
|
trigger: ['change', 'blur'], |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
// 加权长度计数(中文算2) |
|
|
|
|
|
const wordLen = computed(() => weightedLength(formData.word || '')) |
|
|
|
|
|
const paraphraseLen = computed(() => weightedLength(formData.paraphrase || '')) |
|
|
|
|
|
|
|
|
interface WordRecord { |
|
|
interface WordRecord { |
|
|
id?: string |
|
|
id?: string |
|
|
word: string |
|
|
word: string |
|
|
@ -209,11 +254,11 @@ const formData = reactive<WordRecord>({ |
|
|
const formRules = { |
|
|
const formRules = { |
|
|
word: [ |
|
|
word: [ |
|
|
{ required: true, message: '请输入单词', trigger: 'blur' }, |
|
|
{ required: true, message: '请输入单词', trigger: 'blur' }, |
|
|
{ max: 50, message: '单词长度不能超过50个字符', trigger: 'blur' }, |
|
|
|
|
|
|
|
|
createWeightedMaxRule(30), |
|
|
], |
|
|
], |
|
|
paraphrase: [ |
|
|
paraphrase: [ |
|
|
{ required: true, message: '请输入释义', trigger: 'blur' }, |
|
|
{ required: true, message: '请输入释义', trigger: 'blur' }, |
|
|
{ max: 500, message: '释义长度不能超过500个字符', trigger: 'blur' }, |
|
|
|
|
|
|
|
|
createWeightedMaxRule(240), |
|
|
], |
|
|
], |
|
|
soundmark: [ |
|
|
soundmark: [ |
|
|
{ max: 100, message: '音标长度不能超过100个字符', trigger: 'blur' }, |
|
|
{ max: 100, message: '音标长度不能超过100个字符', trigger: 'blur' }, |
|
|
@ -312,6 +357,17 @@ const handleBatchDelete = async () => { |
|
|
const handleModalOk = async () => { |
|
|
const handleModalOk = async () => { |
|
|
try { |
|
|
try { |
|
|
await formRef.value.validate() |
|
|
await formRef.value.validate() |
|
|
|
|
|
// 二次校验:加权长度超限则阻止保存 |
|
|
|
|
|
const wWord = weightedLength(formData.word || '') |
|
|
|
|
|
const wPara = weightedLength(formData.paraphrase || '') |
|
|
|
|
|
if (wWord > 30) { |
|
|
|
|
|
message.error('单词长度不能超过30字符(中文算2字符)') |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if (wPara > 240) { |
|
|
|
|
|
message.error('释义长度不能超过240字符(中文算2字符)') |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
confirmLoading.value = true |
|
|
confirmLoading.value = true |
|
|
|
|
|
|
|
|
const params = { |
|
|
const params = { |
|
|
@ -377,6 +433,27 @@ watch(() => props.coursePageId, (newId) => { |
|
|
} |
|
|
} |
|
|
}, { immediate: true }) |
|
|
}, { immediate: true }) |
|
|
|
|
|
|
|
|
|
|
|
// 实时限制加权长度(中文算2):标题30,释义240 |
|
|
|
|
|
watch(() => formData.word, (val) => { |
|
|
|
|
|
const max = 30 |
|
|
|
|
|
if (!val) return |
|
|
|
|
|
const trimmed = truncateByWeightedLen(val, max) |
|
|
|
|
|
if (trimmed !== val) { |
|
|
|
|
|
formData.word = trimmed |
|
|
|
|
|
message.warning(`标题最多${max}字符(中文算2字符)`) |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
watch(() => formData.paraphrase, (val) => { |
|
|
|
|
|
const max = 240 |
|
|
|
|
|
if (!val) return |
|
|
|
|
|
const trimmed = truncateByWeightedLen(val, max) |
|
|
|
|
|
if (trimmed !== val) { |
|
|
|
|
|
formData.paraphrase = trimmed |
|
|
|
|
|
message.warning(`释义最多${max}字符(中文算2字符)`) |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
// 图片加载错误处理 |
|
|
// 图片加载错误处理 |
|
|
const handleImageError = (event: Event) => { |
|
|
const handleImageError = (event: Event) => { |
|
|
const img = event.target as HTMLImageElement |
|
|
const img = event.target as HTMLImageElement |
|
|
@ -458,4 +535,11 @@ defineExpose({ |
|
|
:deep(.ant-form-item-label > label) { |
|
|
:deep(.ant-form-item-label > label) { |
|
|
font-weight: 500; |
|
|
font-weight: 500; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.field-count { |
|
|
|
|
|
text-align: right; |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
color: #999; |
|
|
|
|
|
margin-top: 4px; |
|
|
|
|
|
} |
|
|
</style> |
|
|
</style> |