fix: complete editable input hints
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readdirSync, readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const sourceRoot = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
function collectVueFiles(directory) {
|
||||
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
const target = path.join(directory, entry.name)
|
||||
return entry.isDirectory() ? collectVueFiles(target) : entry.name.endsWith('.vue') ? [target] : []
|
||||
})
|
||||
}
|
||||
|
||||
function lineNumber(source, offset) {
|
||||
return source.slice(0, offset).split('\n').length
|
||||
}
|
||||
|
||||
function hasLiteralAttribute(tag, name) {
|
||||
return new RegExp(`\\s${name}(?:\\s|=|/?>)`).test(tag)
|
||||
}
|
||||
|
||||
function isEditableNativeTextInput(tag) {
|
||||
if (hasLiteralAttribute(tag, 'disabled') || hasLiteralAttribute(tag, 'readonly')) return false
|
||||
const type = tag.match(/\stype\s*=\s*["']([^"']+)["']/)?.[1]?.toLowerCase() ?? 'text'
|
||||
return ['text', 'search', 'email', 'tel', 'url', 'password'].includes(type)
|
||||
}
|
||||
|
||||
test('all editable text inputs provide a user-facing placeholder', () => {
|
||||
const violations = []
|
||||
|
||||
for (const file of collectVueFiles(sourceRoot)) {
|
||||
const source = readFileSync(file, 'utf8')
|
||||
const template = source.match(/<template(?:\s[^>]*)?>([\s\S]*)<\/template>/)?.[1] ?? ''
|
||||
const withoutComments = template.replace(/<!--[\s\S]*?-->/g, '')
|
||||
const attributeContent = String.raw`(?:[^>"']|"[^"]*"|'[^']*')*`
|
||||
const tagPattern = new RegExp(`<el-input(?!-number)\\b${attributeContent}/?>|<(?:input|textarea)\\b${attributeContent}/?>`, 'g')
|
||||
|
||||
for (const match of withoutComments.matchAll(tagPattern)) {
|
||||
const tag = match[0]
|
||||
const isElementInput = tag.startsWith('<el-input')
|
||||
const editable = isElementInput
|
||||
? !hasLiteralAttribute(tag, 'disabled') && !hasLiteralAttribute(tag, 'readonly')
|
||||
: isEditableNativeTextInput(tag)
|
||||
|
||||
if (editable && !/\s:?(?:placeholder)\s*=/.test(tag)) {
|
||||
violations.push(`${path.relative(sourceRoot, file)}:${lineNumber(withoutComments, match.index ?? 0)} ${tag.replace(/\s+/g, ' ')}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.deepEqual(violations, [], `Editable text inputs without placeholders:\n${violations.join('\n')}`)
|
||||
})
|
||||
@@ -516,12 +516,12 @@ onMounted(loadChannels)
|
||||
<el-form-item label="私人地址名称" required><el-input v-model="destinationForm.privateName" maxlength="128" placeholder="例如:洛杉矶客户自有仓" /></el-form-item>
|
||||
<div class="destination-private-grid">
|
||||
<el-form-item label="目的国家" required><el-input v-model="destinationForm.countryCode" maxlength="2" placeholder="US" /></el-form-item>
|
||||
<el-form-item label="联系人" required><el-input v-model="destinationForm.contactName" maxlength="64" /></el-form-item>
|
||||
<el-form-item label="联系电话" required><el-input v-model="destinationForm.phone" maxlength="32" /></el-form-item>
|
||||
<el-form-item label="城市" required><el-input v-model="destinationForm.city" maxlength="64" /></el-form-item>
|
||||
<el-form-item label="联系人" required><el-input v-model="destinationForm.contactName" maxlength="64" placeholder="请输入收件人姓名" /></el-form-item>
|
||||
<el-form-item label="联系电话" required><el-input v-model="destinationForm.phone" maxlength="32" placeholder="请输入收件人联系电话" /></el-form-item>
|
||||
<el-form-item label="城市" required><el-input v-model="destinationForm.city" maxlength="64" placeholder="请输入收件城市" /></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="详细地址" required><el-input v-model="destinationForm.address" maxlength="256" /></el-form-item>
|
||||
<el-form-item label="邮编"><el-input v-model="destinationForm.postalCode" maxlength="20" /></el-form-item>
|
||||
<el-form-item label="详细地址" required><el-input v-model="destinationForm.address" maxlength="256" placeholder="请输入收件详细地址" /></el-form-item>
|
||||
<el-form-item label="邮编"><el-input v-model="destinationForm.postalCode" maxlength="20" placeholder="请输入邮编(选填)" /></el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
|
||||
@@ -188,13 +188,13 @@ async function openDetail(value: Record<string, string>) {
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>总体积(CBM)</span><el-input-number v-model="form.volumeCbm" :min="0.0001" :precision="4" controls-position="right" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>预计到达时间</span><el-date-picker v-model="form.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计到达时间" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="请输入 FBA 货件编号" /></label>
|
||||
<label v-if="props.service === 'labeling'"><span>贴标要求</span><el-input v-model="form.requirement" /></label>
|
||||
<label v-else-if="props.service !== 'disposal'"><span>物流跟踪号</span><el-input v-model="form.trackingNo" /></label>
|
||||
<label v-if="props.service === 'labeling'"><span>贴标要求</span><el-input v-model="form.requirement" placeholder="请输入贴标要求(选填)" /></label>
|
||||
<label v-else-if="props.service !== 'disposal'"><span>物流跟踪号</span><el-input v-model="form.trackingNo" placeholder="请输入物流跟踪号(选填)" /></label>
|
||||
<label class="warehouse-span-2"><span>订单备注</span><el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" /></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="warehouse-card warehouse-form-section"><div class="warehouse-section-heading"><h3>货物明细</h3><div v-if="props.service !== 'disposal'"><el-button type="primary" size="small" @click="addCargoRow"><Plus :size="15" />添加行</el-button><el-button size="small" @click="cargoRows = []">清空</el-button></div></div>
|
||||
<div class="warehouse-table-scroll"><table class="warehouse-edit-table"><thead><tr><th v-if="props.service !== 'disposal'">操作</th><th>商品 SKU *</th><th>中文品名 *</th><th v-if="props.service === 'disposal'">可用箱数</th><th v-else>{{ props.service === 'labeling' ? '货件编号 *' : 'FNSKU / 箱唛' }}</th><th v-if="props.service === 'labeling'">换标新 SKU *</th><th>{{ props.service === 'disposal' ? '本次废弃箱数' : props.service === 'relabel' ? '退件数量' : '箱数' }} *</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" /></button></td><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" /></td><td><el-input v-model="row.name" :readonly="props.service === 'disposal'" /></td><td v-if="props.service === 'disposal'"><strong>{{ row.availableQuantity }} 箱</strong></td><td v-else><el-input v-if="props.service === 'labeling'" v-model="row.shipmentNo" /><el-input v-else v-model="row.shippingMark" /></td><td v-if="props.service === 'labeling'"><el-input v-model="row.newSkuCode" /></td><td><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="props.service === 'disposal' ? row.availableQuantity : undefined" controls-position="right" /></td></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'labeling' ? 6 : props.service === 'disposal' ? 4 : 5"><div class="warehouse-empty">{{ props.service === 'disposal' ? '请先选择入库单,系统将自动带出 SKU 和可用箱数' : '暂无货物明细,请点击“添加行”' }}</div></td></tr></tbody></table></div>
|
||||
<div class="warehouse-table-scroll"><table class="warehouse-edit-table"><thead><tr><th v-if="props.service !== 'disposal'">操作</th><th>商品 SKU *</th><th>中文品名 *</th><th v-if="props.service === 'disposal'">可用箱数</th><th v-else>{{ props.service === 'labeling' ? '货件编号 *' : 'FNSKU / 箱唛' }}</th><th v-if="props.service === 'labeling'">换标新 SKU *</th><th>{{ props.service === 'disposal' ? '本次废弃箱数' : props.service === 'relabel' ? '退件数量' : '箱数' }} *</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" /></button></td><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" placeholder="请输入商品 SKU" /></td><td><el-input v-model="row.name" :readonly="props.service === 'disposal'" placeholder="请输入中文品名" /></td><td v-if="props.service === 'disposal'"><strong>{{ row.availableQuantity }} 箱</strong></td><td v-else><el-input v-if="props.service === 'labeling'" v-model="row.shipmentNo" placeholder="请输入货件编号" /><el-input v-else v-model="row.shippingMark" placeholder="请输入 FNSKU 或箱唛" /></td><td v-if="props.service === 'labeling'"><el-input v-model="row.newSkuCode" placeholder="请输入换标后的 SKU" /></td><td><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="props.service === 'disposal' ? row.availableQuantity : undefined" controls-position="right" /></td></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'labeling' ? 6 : props.service === 'disposal' ? 4 : 5"><div class="warehouse-empty">{{ props.service === 'disposal' ? '请先选择入库单,系统将自动带出 SKU 和可用箱数' : '暂无货物明细,请点击“添加行”' }}</div></td></tr></tbody></table></div>
|
||||
</div>
|
||||
<div v-if="props.service === 'transfer' || props.service === 'relabel'" class="warehouse-card warehouse-form-section"><h3>换标设置</h3><el-checkbox v-model="form.relabel">是否换标</el-checkbox><p v-if="form.relabel" class="warehouse-hint">请确认 SKU 与 FNSKU 信息准确,提交后海外仓将按明细执行换标。</p></div>
|
||||
<div class="warehouse-submit-bar"><el-button @click="resetForm">重置</el-button><el-button type="primary" native-type="submit" :loading="saving">提交{{ config.title }}</el-button></div>
|
||||
|
||||
Reference in New Issue
Block a user