Add linked Chinese postal code input and lookup data

This commit is contained in:
biss committed 2026-08-17 19:24:58 +08:00
1 parent 3f685ca068
commit 1e43d910c0
8 files changed
+141 -4

No files matched your search

@@ -0,0 +1,17 @@
import { readFile, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
const [inputFile, outputFile] = process.argv.slice(2)
if (!inputFile || !outputFile) {
throw new Error('用法:node scripts/generate-china-postcode-data.mjs <china_zipcode_adcode.json> <输出文件>')
}
const sourceData = JSON.parse(await readFile(resolve(inputFile), 'utf8'))
const rows = sourceData
.filter((row) => /^\d{6}$/.test(row.code) && /^\d{6}$/.test(row.zipCode))
.map((row) => [row.code, row.zipCode])
const source = `// 由 tombcato/china-zipcode-data 的 china_zipcode_adcode.json 生成。\n// 上游项目采用 MIT License:https://github.com/tombcato/china-zipcode-data\nexport type ChinaPostcodeRow = readonly [districtCode: string, postcode: string]\n\nexport const chinaPostcodeRows: readonly ChinaPostcodeRow[] = ${JSON.stringify(rows)} as const\n`
await writeFile(resolve(outputFile), source, 'utf8')
+1
View File
@@ -12,6 +12,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
AppUpdateManagementPanel: typeof import('./components/AppUpdateManagementPanel.vue')['default']
ChinesePostalCodeInput: typeof import('./components/ChinesePostalCodeInput.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton']
@@ -0,0 +1,66 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { nativePlaceCandidatesForPostcode, postcodeForNativePlace } from '../data/nativePlaceLookup'
const postcode = defineModel<string>({ default: '' })
const nativePlace = defineModel<string>('nativePlace', { default: '' })
const props = defineProps<{ linked?: boolean }>()
const candidates = ref<string[]>([])
const suggestedPostcode = computed(() => props.linked === false ? undefined : postcodeForNativePlace(nativePlace.value))
const lockedPrefix = computed(() => suggestedPostcode.value?.slice(0, 4) ?? '')
const suffix = computed({
get: () => lockedPrefix.value && postcode.value.startsWith(lockedPrefix.value) ? postcode.value.slice(4, 6) : '',
set: (value: string) => { postcode.value = `${lockedPrefix.value}${value.replace(/\D/g, '').slice(0, 2)}` },
})
function normalize(value: string) {
return value.replace(/\D/g, '').slice(0, 6)
}
function updatePostcode(value: string) {
postcode.value = normalize(value)
}
function selectCandidate(value: string) {
nativePlace.value = value
candidates.value = []
}
watch(suggestedPostcode, (suggested) => {
if (!suggested || props.linked === false) return
if (!postcode.value.startsWith(suggested.slice(0, 4))) postcode.value = suggested
}, { immediate: true })
watch(postcode, (value) => {
if (props.linked === false || nativePlace.value || lockedPrefix.value) return
const matches = nativePlaceCandidatesForPostcode(value)
candidates.value = matches
if (matches.length === 1) selectCandidate(matches[0])
})
watch(() => props.linked, (linked) => {
if (linked === false) candidates.value = []
})
</script>
<template>
<div class="postcode-control">
<template v-if="linked !== false && lockedPrefix">
<el-input :model-value="lockedPrefix" disabled class="postcode-prefix" aria-label="邮政编码前四位" />
<el-input v-model="suffix" inputmode="numeric" maxlength="2" placeholder="后两位" aria-label="邮政编码后两位" />
</template>
<el-input v-else-if="linked !== false" :model-value="postcode" inputmode="numeric" maxlength="6" placeholder="6位邮政编码" @update:model-value="updatePostcode" />
<el-input v-else v-model="postcode" maxlength="20" placeholder="邮政编码" />
<el-select v-if="linked !== false && candidates.length > 1" placeholder="请选择匹配的区县" @update:model-value="selectCandidate">
<el-option v-for="candidate in candidates" :key="candidate" :label="candidate" :value="candidate" />
</el-select>
</div>
</template>
<style scoped>
.postcode-control { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; width: 100%; }
.postcode-prefix { width: 100%; }
.postcode-control:has(.postcode-prefix) { grid-template-columns: 1fr 1fr; }
@media (max-width: 480px) { .postcode-control:has(.postcode-prefix) { grid-template-columns: 1fr; } }
</style>
Loaded 3 of 8 files, more files were not shown because too many files have changed in this diff. Show more