自制 Strapi 多级联动选择
在使用 Strapi 无头 CMS 过程中,因其本身未提供多级列举选择(下拉菜单联动)无法在 Content Manager 中的实时交互功能,由 @Chris Ebert 国家选择插件启发,便试着在其基础上实现省、市、区、县…多级列举选择功能~
一、搭建 Strapi 环境(可跳过)
确保服务器内已部署 node.js 环境(建议v20.xx.x版本),再执行 `npx create-strapi-app@latest strapi-name --quickstart` 安装 Strapi 项目
下载报错则需修改为 npm 阿里云镜像源
npm config set registry https://registry.npmmirror.com
如遇个别组件(如 sharp)下载失败,则需在
strapi-name
文件夹内新建.npmrc
文件,内容为:
registry=https://registry.npmmirror.com
sharp_binary_host=https://npmmirror.com/mirrors/sharp
sharp_libvips_binary_host=https://npmmirror.com/mirrors/sharp-libvips
再接着进入
strapi-name
项目目录内运行终端
npm install
npm run develop
弹出以下内容即为成功
Project information
┌────────────────────┬──────────────────────────────────────────────────┐
│ Time │ Wed Jan 15 2025 15:10:13 GMT+0800 (China Standa… │
│ Launched in │ 14431 ms │
│ Environment │ development │
│ Process PID │ 31396 │
│ Version │ 5.7.0 (node v20.18.1) │
│ Edition │ Enterprise │
│ Database │ mysql │
│ Database name │ strapi │
└────────────────────┴──────────────────────────────────────────────────┘
Actions available
<p>One more thing...
Create your first administrator 💻 by going to the administration panel at:</p>
<p>┌─────────────────────────────┐
│ http://localhost:1337/admin │
└─────────────────────────────┘</p>
<p>[2024-12-10 08:49:37.549] info: Strapi started successfully
(可选)解锁高级版指令:
npx @yek-plus/strapi-crack
二、复制并创建 country-select 插件
(1) 安装原版插件
在项目根目录启动终端并执行
npm install strapi-plugin-country-select
(报错可加上
--legacy-peer-deps
再次尝试)
(2) 将插件复制到插件目录
创建插件目录
mkdir -p src/plugins/region-select
复制插件代码
cp -R node_modules/strapi-plugin-country-select/* src/plugins/region-select/
删除
node_modules
中的原始插件版本(防止冲突):
npm uninstall strapi-plugin-country-select
打开根目录的
package.json
注册本地插件:
"dependencies": {
"strapi-plugin-country-select": "file:./src/plugins/region-select"
}
重新安装依赖
npm install
三、修改插件代码
将原有的国家数据替换为省市数据。创建一个新的
regions.json
文件,保存到src/plugins/region-select/dist/admin/src/components/data
中:
{
"江苏省": {
"苏州市": {
"姑苏区": ["平江街道", "沧浪街道", "金阊街道"],
"虎丘区": ["狮山街道", "枫桥街道", "横塘街道"],
"吴中区": ["长桥街道", "越溪街道", "木渎镇"],
"相城区": ["元和街道", "黄桥街道", "望亭镇"],
"吴江区": ["松陵街道", "同里镇", "震泽镇"],
"苏州工业园区": ["湖西社区", "湖东社区", "斜塘街道"],
"常熟市": ["虞山镇", "海虞镇", "古里镇"],
"张家港市": ["杨舍镇", "塘桥镇", "金港镇"],
"昆山市": ["玉山镇", "花桥镇", "周市镇"],
"太仓市": ["城厢镇", "沙溪镇", "浮桥镇"]
}
},
"上海市": {
"黄浦区": ["南京东路街道", "外滩街道", "半淞园路街道"],
"徐汇区": ["天平路街道", "湖南路街道", "斜土路街道"],
"长宁区": ["华阳路街道", "江苏路街道", "新华路街道"],
"静安区": ["静安寺街道", "南京西路街道", "曹家渡街道"],
"普陀区": ["长寿路街道", "甘泉路街道", "石泉路街道"],
"虹口区": ["欧阳路街道", "曲阳路街道", "广中路街道"],
"杨浦区": ["四平路街道", "控江路街道", "长白新村街道"],
"闵行区": ["江川路街道", "古美路街道", "莘庄镇"],
"宝山区": ["友谊路街道", "吴淞街道", "张庙街道"],
"嘉定区": ["新成路街道", "真新街道", "嘉定镇街道"],
"浦东新区": ["陆家嘴街道", "周家渡街道", "花木街道"],
"金山区": ["石化街道", "朱泾镇", "枫泾镇"],
"松江区": ["岳阳街道", "永丰街道", "泗泾镇"],
"青浦区": ["夏阳街道", "盈浦街道", "朱家角镇"],
"奉贤区": ["南桥镇", "奉城镇", "四团镇"],
"崇明区": ["城桥镇", "堡镇", "新河镇"]
},
"浙江省": {
"杭州市": {
"上城区": ["清波街道", "湖滨街道", "小营街道"],
"拱墅区": ["米市巷街道", "湖墅街道", "大关街道"],
"西湖区": ["西溪街道", "文新街道", "翠苑街道"],
"滨江区": ["西兴街道", "长河街道", "浦沿街道"],
"萧山区": ["城厢街道", "北干街道", "蜀山街道"],
"余杭区": ["南苑街道", "临平街道", "星桥街道"],
"富阳区": ["富春街道", "东洲街道", "春江街道"],
"临安区": ["锦城街道", "锦北街道", "玲珑街道"],
"桐庐县": ["桐君街道", "城南街道", "分水镇"],
"淳安县": ["千岛湖镇", "汾口镇", "威坪镇"],
"建德市": ["新安江街道", "洋溪街道", "梅城镇"]
},
"宁波市": {
"海曙区": ["南门街道", "西门街道", "鼓楼街道"],
"江北区": ["白沙街道", "甬江街道", "文教街道"],
"北仑区": ["新碶街道", "小港街道", "大碶街道"],
"镇海区": ["招宝山街道", "蛟川街道", "骆驼街道"],
"鄞州区": ["钟公庙街道", "首南街道", "中河街道"],
"奉化区": ["锦屏街道", "岳林街道", "西坞街道"],
"象山县": ["丹东街道", "丹西街道", "石浦镇"],
"宁海县": ["桃源街道", "跃龙街道", "梅林街道"],
"余姚市": ["阳明街道", "梨洲街道", "兰江街道"],
"慈溪市": ["浒山街道", "宗汉街道", "坎墩街道"]
},
"温州市": {
"鹿城区": ["五马街道", "南门街道", "松台街道"],
"龙湾区": ["永中街道", "永兴街道", "海滨街道"],
"瓯海区": ["景山街道", "梧田街道", "新桥街道"],
"洞头区": ["北岙街道", "东屏街道", "元觉街道"],
"永嘉县": ["瓯北街道", "桥头镇", "桥下镇"],
"平阳县": ["昆阳镇", "鳌江镇", "水头镇"],
"苍南县": ["灵溪镇", "龙港市", "宜山镇"],
"文成县": ["大峃镇", "玉壶镇", "珊溪镇"],
"泰顺县": ["罗阳镇", "司前畲族镇", "雅阳镇"],
"瑞安市": ["安阳街道", "玉海街道", "锦湖街道"],
"乐清市": ["乐成街道", "柳市镇", "虹桥镇"]
}
}
}
找到文件
src/plugins/country-select/dist/admin/src/components/CountrySelect/index.d.js
并修改代码为:
import React, { useState, useEffect } from 'react';
import { useIntl } from 'react-intl';
import { Select, Option } from '@strapi/design-system/Select';
import { Field, FieldLabel, FieldInput, FieldHint, FieldError } from '@strapi/design-system/Field';
import { Stack } from '@strapi/design-system/Stack';
import data from './data/regions.json'; // 请确保此路径正确</p>
<p>const ProvinceCityAreaCountySelect = ({ name, value = {}, onChange, error, description, required }) => {
const { formatMessage } = useIntl();
const [province, setProvince] = useState(value.province || '');
const [city, setCity] = useState(value.city || '');
const [area, setArea] = useState(value.area || '');
const [county, setCounty] = useState(value.county || '');</p>
<p>useEffect(() => {
onChange({ target: { name, value: { province, city, area, county } } });
}, [province, city, area, county]);</p>
<p>return (
<Field name={name} error={error} hint={description} required={required}>
<Stack spacing={1}>
<FieldLabel>{formatMessage({ id: 'components.ProvinceCityAreaCountySelect.label', defaultMessage: 'Location' })}</FieldLabel>
<FieldInput>
<Select
placeholder={formatMessage({ id: 'components.ProvinceCityAreaCountySelect.selectProvince', defaultMessage: 'Select Province' })}
value={province}
onChange={setProvince}
>
{Object.keys(data).map((prov) => (
<Option key={prov} value={prov}>
{prov}
</Option>
))}
</Select>
</FieldInput>
{province && (
<FieldInput>
<Select
placeholder={formatMessage({ id: 'components.ProvinceCityAreaCountySelect.selectCity', defaultMessage: 'Select City' })}
value={city}
onChange={setCity}
>
{Object.keys(data[province] || {}).map((ct) => (
<Option key={ct} value={ct}>
{ct}
</Option>
))}
</Select>
</FieldInput>
)}
{city && (
<FieldInput>
<Select
placeholder={formatMessage({ id: 'components.ProvinceCityAreaCountySelect.selectArea', defaultMessage: 'Select Area' })}
value={area}
onChange={setArea}
>
{Object.keys(data[province][city] || {}).map((ar) => (
<Option key={ar} value={ar}>
{ar}
</Option>
))}
</Select>
</FieldInput>
)}
{area && (
<FieldInput>
<Select
placeholder={formatMessage({ id: 'components.ProvinceCityAreaCountySelect.selectCounty', defaultMessage: 'Select County' })}
value={county}
onChange={setCounty}
>
{(data[province][city][area] || []).map((cnty) => (
<Option key={cnty} value={cnty}>
{cnty}
</Option>
))}
</Select>
</FieldInput>
)}
<FieldHint />
<FieldError />
</Stack>
</Field>
);
};</p>
<p>export default ProvinceCityAreaCountySelect;
最后在项目的
config/plugins.ts
文件中启用插件:
export default ({ env }) => ({
'region-select': {
enabled: true,
},
});
保存并测试
npm run build && npm run develop
四、使用后如何移除插件
删除
src/plugins/region-select
文件夹去除
config/plugins.ts
文件中代码
export default () => ({});
- 感谢你赐予我前进的力量