新增农户功能升级:身份证OCR/OSS上传、头像采集、同名村检测、Region热度排序及表单体验优化
- Region 表新增热度列,热门省份优先显示 - 区县下拉按地级市分组、已录区县优先、支持全省搜索 - 组选择内置一组至二十组 - 银行卡号独立行+粗体预览,开户行独立行 - 新增农户类型:农户/个体户/合作社/经营集体/公司 - 身份证正反面支持高拍仪/摄像头OCR与手机扫码上传,附件存本地/OSS - 新增农户头像采集组件(摄像头/本地上传),预留人脸识别收购扩展 - 同名村弹窗提醒(防选错乡镇) - 编辑窗体禁止遮罩/Esc误关,整体加宽适配高频操作 - 修复二维码局域网手机无法访问(监听0.0.0.0、局域网IP识别、Vite代理兜底)
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* AreaCity 全国行政区划数据导入脚本
|
||||
* ==================================
|
||||
* 数据源: https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov
|
||||
* 下载: https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov/releases/download/2025.251231.260403/ok_data_level3-4.csv.7z
|
||||
* (Gitee 备用: https://gitee.com/xiangyuecn/AreaCity-JsSpider-StatsGov/releases)
|
||||
* 说明: 解压后得到 ok_data_level4.csv(省市区镇四级,UTF-8 带 BOM)
|
||||
* 列: id, pid, deep, name, pinyin_prefix, pinyin, ext_id, ext_name
|
||||
* deep: 0=省 1=市(地级/直辖市虚拟) 2=县(区/县级市) 3=乡镇(镇/乡/街道)
|
||||
*
|
||||
* 用法:
|
||||
* 1) 下载并解压 ok_data_level4.csv 到本目录 tmp/ok_data_level4.csv
|
||||
* 2) npm i mysql2
|
||||
* 3) node import-regions.mjs
|
||||
* 脚本会 DROP 并重建 Regions 表后导入(幂等,可重复执行)。
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import mysql from 'mysql2/promise'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const root = path.resolve(__dirname, '../..')
|
||||
const csvPath = path.join(__dirname, 'tmp/ok_data_level4.csv')
|
||||
|
||||
if (!fs.existsSync(csvPath)) {
|
||||
console.error('未找到 ' + csvPath + ',请先下载并解压数据文件')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// ---------- 读取数据库连接串 ----------
|
||||
const appCfg = JSON.parse(fs.readFileSync(path.join(root, 'backend/AgriculturalPlatform.Api/appsettings.json'), 'utf8'))
|
||||
const parseCs = (s) => Object.fromEntries(
|
||||
s.split(';').filter(Boolean).map(kv => {
|
||||
const i = kv.indexOf('=')
|
||||
return [kv.slice(0, i).trim().toLowerCase(), kv.slice(i + 1).trim()]
|
||||
})
|
||||
)
|
||||
const dbc = parseCs(appCfg.ConnectionStrings.Default)
|
||||
|
||||
// ---------- 解析 CSV ----------
|
||||
const raw = fs.readFileSync(csvPath, 'utf8').replace(/^\uFEFF/, '')
|
||||
const lines = raw.split(/\r?\n/).filter(l => l.trim().length > 0)
|
||||
lines.shift() // 去掉表头
|
||||
|
||||
const parseRow = (line) => {
|
||||
const fields = []
|
||||
let cur = '', inQ = false
|
||||
for (const ch of line) {
|
||||
if (ch === '"') inQ = !inQ
|
||||
else if (ch === ',' && !inQ) { fields.push(cur); cur = '' }
|
||||
else cur += ch
|
||||
}
|
||||
fields.push(cur)
|
||||
return fields
|
||||
}
|
||||
|
||||
// 常用省份(热度高的排前面,值越大越靠前,供前端优先展示)
|
||||
const HOT_PROVINCES = [
|
||||
'四川省', '广西壮族自治区', '陕西省', '重庆市', '安徽省',
|
||||
'云南省', '贵州省', '河南省', '山东省', '湖北省', '湖南省',
|
||||
'甘肃省', '新疆维吾尔自治区', '江西省', '福建省', '广东省',
|
||||
'河北省', '山西省', '江苏省', '浙江省'
|
||||
]
|
||||
const hotOf = (deep, extName) => {
|
||||
if (deep !== 0) return 0
|
||||
const i = HOT_PROVINCES.indexOf(extName)
|
||||
return i < 0 ? 0 : HOT_PROVINCES.length - i
|
||||
}
|
||||
|
||||
const rows = []
|
||||
for (const line of lines) {
|
||||
const [id, pid, deep, name, pfx, pinyin, , extName] = parseRow(line)
|
||||
if (!id) continue
|
||||
rows.push([Number(id), Number(pid), Number(deep), name, extName, pinyin, pfx, hotOf(Number(deep), extName)])
|
||||
}
|
||||
console.log('解析完成,共 ' + rows.length + ' 条')
|
||||
const stat = {}
|
||||
for (const r of rows) stat[r[2]] = (stat[r[2]] || 0) + 1
|
||||
console.log('层级分布(deep):', JSON.stringify(stat))
|
||||
|
||||
// ---------- 建表并导入 ----------
|
||||
const conn = await mysql.createConnection({
|
||||
host: dbc.server,
|
||||
port: Number(dbc.port || 3306),
|
||||
user: dbc.user,
|
||||
password: dbc.password,
|
||||
database: dbc.database,
|
||||
charset: 'utf8mb4'
|
||||
})
|
||||
|
||||
await conn.query('DROP TABLE IF EXISTS Regions')
|
||||
await conn.query(`CREATE TABLE Regions (
|
||||
Id INT NOT NULL PRIMARY KEY,
|
||||
Pid INT NOT NULL,
|
||||
Deep INT NOT NULL,
|
||||
Name VARCHAR(100) NOT NULL,
|
||||
ExtName VARCHAR(100) NOT NULL,
|
||||
Pinyin VARCHAR(200) NOT NULL DEFAULT '',
|
||||
PinyinPrefix VARCHAR(10) NOT NULL DEFAULT '',
|
||||
Hot INT NOT NULL DEFAULT 0,
|
||||
KEY idx_regions_pid (Pid),
|
||||
KEY idx_regions_deep (Deep)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci`)
|
||||
|
||||
const BATCH = 1000
|
||||
for (let i = 0; i < rows.length; i += BATCH) {
|
||||
const chunk = rows.slice(i, i + BATCH)
|
||||
await conn.query('INSERT INTO Regions (Id, Pid, Deep, Name, ExtName, Pinyin, PinyinPrefix, Hot) VALUES ?', [chunk])
|
||||
}
|
||||
|
||||
const [cnt] = await conn.query('SELECT COUNT(*) AS c FROM Regions')
|
||||
console.log('导入完成,Regions 表共 ' + cnt[0].c + ' 条记录')
|
||||
await conn.end()
|
||||
Reference in New Issue
Block a user