@pnt-team/pnt-component-frontend 0.0.138 → 0.0.139
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/api/cos.d.ts +103 -0
- package/lib/cjs/api/cos.js +146 -0
- package/lib/cjs/api/index.d.ts +23 -0
- package/lib/cjs/api/index.js +38 -0
- package/lib/cjs/api/types/index.d.ts +117 -0
- package/lib/cjs/api/types/index.js +17 -0
- package/lib/cjs/assets/css/tdesign-theme.css +584 -0
- package/lib/cjs/assets/images/uploadImg.png +0 -0
- package/lib/cjs/assets/svg/inputSearch.svg +4 -0
- package/lib/cjs/assets/svg/inputTip.svg +5 -0
- package/lib/cjs/components/PntRegionSelect/index.scss +1 -1
- package/lib/esm/api/cos.d.ts +103 -0
- package/lib/esm/api/cos.js +189 -0
- package/lib/esm/api/index.d.ts +23 -0
- package/lib/esm/api/index.js +23 -0
- package/lib/esm/api/types/index.d.ts +117 -0
- package/lib/esm/api/types/index.js +1 -0
- package/lib/esm/assets/css/tdesign-theme.css +584 -0
- package/lib/esm/assets/images/uploadImg.png +0 -0
- package/lib/esm/assets/svg/inputSearch.svg +4 -0
- package/lib/esm/assets/svg/inputTip.svg +5 -0
- package/lib/esm/assets/svg/inputWarnTip.svg +5 -0
- package/lib/esm/components/PntBusinessSelect/index.d.ts +192 -0
- package/lib/esm/components/PntBusinessSelect/index.js +513 -0
- package/lib/esm/components/PntBusinessSelect/index.scss +27 -0
- package/lib/esm/components/PntCosUploader/index.d.ts +134 -0
- package/lib/esm/components/PntCosUploader/index.js +300 -0
- package/lib/esm/components/PntGeneralLayout/index.d.ts +114 -0
- package/lib/esm/components/PntGeneralLayout/index.js +103 -0
- package/lib/esm/components/PntGeneralLayout/index.scss +121 -0
- package/lib/esm/components/PntImage/index.d.ts +93 -0
- package/lib/esm/components/PntImage/index.js +200 -0
- package/lib/esm/components/PntImage/index.scss +61 -0
- package/lib/esm/components/PntRegionSelect/ImportDialog.d.ts +29 -0
- package/lib/esm/components/PntRegionSelect/ImportDialog.js +244 -0
- package/lib/esm/components/PntRegionSelect/index.d.ts +39 -0
- package/lib/esm/components/PntRegionSelect/index.js +471 -0
- package/lib/esm/components/PntRegionSelect/index.scss +174 -0
- package/lib/esm/components/PntRegionSelect/types.d.ts +181 -0
- package/lib/esm/components/PntRegionSelect/types.js +77 -0
- package/lib/esm/components/PntSecureBox/index.d.ts +122 -0
- package/lib/esm/components/PntSecureBox/index.js +166 -0
- package/lib/esm/components/PntSecureBox/index.scss +23 -0
- package/lib/esm/components/PntTable/index.d.ts +14 -0
- package/lib/esm/components/PntTable/index.js +404 -0
- package/lib/esm/components/PntTable/index.scss +290 -0
- package/lib/esm/components/PntTable/types.d.ts +250 -0
- package/lib/esm/components/PntTable/types.js +81 -0
- package/lib/esm/components/PntTable/useTableCore.d.ts +38 -0
- package/lib/esm/components/PntTable/useTableCore.js +499 -0
- package/lib/esm/components/PntUpload/index.d.ts +149 -0
- package/lib/esm/components/PntUpload/index.js +276 -0
- package/lib/esm/components/PntUpload/index.scss +101 -0
- package/lib/esm/config.d.ts +97 -0
- package/lib/esm/config.js +60 -0
- package/lib/esm/global.css +2 -0
- package/lib/esm/global.d.ts +39 -0
- package/lib/esm/hooks/useHighSecurity.d.ts +88 -0
- package/lib/esm/hooks/useHighSecurity.js +250 -0
- package/lib/esm/index.d.ts +16 -0
- package/lib/esm/index.js +33 -0
- package/lib/esm/utils/const.d.ts +13 -0
- package/lib/esm/utils/const.js +23 -0
- package/lib/esm/utils/index.d.ts +6 -0
- package/lib/esm/utils/index.js +22 -0
- package/lib/umd/pnt-component-frontend.min.css +1 -0
- package/lib/umd/pnt-component-frontend.min.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { businessRegionQuery } from "../../api";
|
|
2
|
+
import { usePntConfigOptional } from "../../config";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Button, Checkbox, Select } from 'tdesign-react';
|
|
5
|
+
import ImportDialog from "./ImportDialog";
|
|
6
|
+
import "./index.scss";
|
|
7
|
+
import { getTextsByLocale } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 归一化 region_code:纯数字字符串去掉前导零('012' -> '12'),非数字保持原样
|
|
11
|
+
* @description 后端详情接口可能返回 '12',字典接口可能返回 '012',需统一格式才能匹配
|
|
12
|
+
*/
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
15
|
+
import { Fragment as _Fragment } from "react/jsx-runtime";
|
|
16
|
+
export const normalizeRegionCode = code => {
|
|
17
|
+
if (code === null || code === undefined || code === '') return '';
|
|
18
|
+
const str = String(code);
|
|
19
|
+
return /^\d+$/.test(str) ? String(Number(str)) : str;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 组件内部选项:region_code 为归一化码(用于匹配/选中),
|
|
24
|
+
* rawRegionCode 保留数据源的原始码(如 '004',用于对外回传,避免前导零丢失)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 将后端区域项归一化为组件内部的 RegionOption
|
|
29
|
+
* @description 后端契约字段均为可选,此处补默认值;同时保留原始 region_code
|
|
30
|
+
*/
|
|
31
|
+
const toRegionOptions = list => (list || []).map(item => ({
|
|
32
|
+
...item,
|
|
33
|
+
region_code: normalizeRegionCode(item.region_code),
|
|
34
|
+
rawRegionCode: item.region_code === null || item.region_code === undefined ? '' : String(item.region_code),
|
|
35
|
+
region_ch: item.region_ch ?? '',
|
|
36
|
+
region_en: item.region_en ?? ''
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 构建标签文本
|
|
41
|
+
*/
|
|
42
|
+
const getLabelText = (item, format) => {
|
|
43
|
+
const {
|
|
44
|
+
region_ch,
|
|
45
|
+
region_en,
|
|
46
|
+
region_code,
|
|
47
|
+
rawRegionCode
|
|
48
|
+
} = item;
|
|
49
|
+
switch (format) {
|
|
50
|
+
case 'name_en':
|
|
51
|
+
return region_en || region_ch || rawRegionCode || region_code;
|
|
52
|
+
case 'name_en_name':
|
|
53
|
+
return region_en && region_ch ? `${region_en}(${region_ch})` : region_en || region_ch || rawRegionCode || region_code;
|
|
54
|
+
case 'code':
|
|
55
|
+
// 代码标签优先展示数据源原始码(保留前导零,如 '004')
|
|
56
|
+
return rawRegionCode || region_code;
|
|
57
|
+
default:
|
|
58
|
+
return region_ch || region_en || rawRegionCode || region_code;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 地区选择器组件
|
|
64
|
+
* @description 统一的国家/地区选择组件,支持单选/多选、批量选择、批量导入、自定义区域、合规区域
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* // 基础多选(内部拉取默认区域列表)
|
|
69
|
+
* <PntRegionSelect
|
|
70
|
+
* value={regions}
|
|
71
|
+
* onChange={(v) => setRegions(v)}
|
|
72
|
+
* />
|
|
73
|
+
*
|
|
74
|
+
* // 业务最终生效区域
|
|
75
|
+
* <PntRegionSelect
|
|
76
|
+
* dataSource="business"
|
|
77
|
+
* gameId={gameId}
|
|
78
|
+
* value={regions}
|
|
79
|
+
* onChange={(v) => setRegions(v)}
|
|
80
|
+
* />
|
|
81
|
+
*
|
|
82
|
+
* // 外部受控选项 + 批量导入
|
|
83
|
+
* <PntRegionSelect
|
|
84
|
+
* options={regionList}
|
|
85
|
+
* importable
|
|
86
|
+
* value={regions}
|
|
87
|
+
* onChange={(v) => setRegions(v)}
|
|
88
|
+
* />
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
const PntRegionSelect = props => {
|
|
92
|
+
const {
|
|
93
|
+
value = [],
|
|
94
|
+
onChange,
|
|
95
|
+
multiple = true,
|
|
96
|
+
dataSource = 'default',
|
|
97
|
+
scope = 'business',
|
|
98
|
+
gameId,
|
|
99
|
+
options: externalOptions,
|
|
100
|
+
disabled = false,
|
|
101
|
+
placeholder,
|
|
102
|
+
max,
|
|
103
|
+
filterable = true,
|
|
104
|
+
labelFormat = 'name',
|
|
105
|
+
texts: customTexts,
|
|
106
|
+
onLoaded,
|
|
107
|
+
optionDisabled,
|
|
108
|
+
optionFilter,
|
|
109
|
+
optionTooltip,
|
|
110
|
+
importable = false,
|
|
111
|
+
invertible = false,
|
|
112
|
+
preserveLeadingZero = true,
|
|
113
|
+
minCollapsedNum,
|
|
114
|
+
collapsedItems,
|
|
115
|
+
onBlur,
|
|
116
|
+
importDialogProps,
|
|
117
|
+
className,
|
|
118
|
+
style,
|
|
119
|
+
popupProps,
|
|
120
|
+
locale: propLocale
|
|
121
|
+
} = props;
|
|
122
|
+
|
|
123
|
+
// 外部受控模式下无需 http,因此使用可选版配置读取
|
|
124
|
+
const config = usePntConfigOptional();
|
|
125
|
+
const http = config?.http;
|
|
126
|
+
const regionQuery = config?.regionQuery;
|
|
127
|
+
// 优先级:prop locale > PntProvider locale > 默认 'zh'
|
|
128
|
+
const locale = propLocale || config?.locale || 'zh';
|
|
129
|
+
// 文案优先级:customTexts > locale 默认文案
|
|
130
|
+
const texts = useMemo(() => ({
|
|
131
|
+
...getTextsByLocale(locale),
|
|
132
|
+
...customTexts
|
|
133
|
+
}), [customTexts, locale]);
|
|
134
|
+
|
|
135
|
+
// ---- 状态 ----
|
|
136
|
+
const [innerOptions, setInnerOptions] = useState([]);
|
|
137
|
+
const [loading, setLoading] = useState(false);
|
|
138
|
+
const [searchKeyword, setSearchKeyword] = useState('');
|
|
139
|
+
const [importVisible, setImportVisible] = useState(false);
|
|
140
|
+
|
|
141
|
+
// ---- Refs ----
|
|
142
|
+
const fetchSeqRef = useRef(0);
|
|
143
|
+
const onLoadedRef = useRef(onLoaded);
|
|
144
|
+
onLoadedRef.current = onLoaded;
|
|
145
|
+
|
|
146
|
+
// ---- 外部受控模式:传入 options 时跳过内部请求 ----
|
|
147
|
+
const isExternal = externalOptions !== undefined;
|
|
148
|
+
|
|
149
|
+
// ---- 获取数据 ----
|
|
150
|
+
const fetchData = useCallback(async () => {
|
|
151
|
+
if (isExternal) return;
|
|
152
|
+
if (!http) {
|
|
153
|
+
console.warn('[PntRegionSelect] 未注入 PntProvider.http,无法内部拉取区域数据;请用 <PntProvider> 包裹,或改用外部 options 模式');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const seq = ++fetchSeqRef.current;
|
|
157
|
+
setLoading(true);
|
|
158
|
+
try {
|
|
159
|
+
let list = [];
|
|
160
|
+
const url = scope === 'platform' ? regionQuery?.queryPlatUrl || '/v5/business/region/plat-query?audience=/laas-pnt' : regionQuery?.queryUrl || '/v5/business/region/query?audience=/laas-pnt';
|
|
161
|
+
if (dataSource === 'business') {
|
|
162
|
+
if (!gameId) {
|
|
163
|
+
console.warn('[PntRegionSelect] dataSource="business" 时必须传入 gameId');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const res = await businessRegionQuery(http, {
|
|
167
|
+
game_id: Number(gameId)
|
|
168
|
+
}, url);
|
|
169
|
+
if (seq !== fetchSeqRef.current) return;
|
|
170
|
+
if (res.code === 0 && res.data?.region_list) {
|
|
171
|
+
list = toRegionOptions(res.data.region_list);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
const res = await businessRegionQuery(http, {}, url);
|
|
175
|
+
if (seq !== fetchSeqRef.current) return;
|
|
176
|
+
if (res.code === 0 && res.data?.region_list) {
|
|
177
|
+
list = toRegionOptions(res.data.region_list);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
setInnerOptions(list);
|
|
181
|
+
onLoadedRef.current?.(list);
|
|
182
|
+
} catch {
|
|
183
|
+
// 请求失败静默处理
|
|
184
|
+
} finally {
|
|
185
|
+
if (seq === fetchSeqRef.current) {
|
|
186
|
+
setLoading(false);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}, [http, dataSource, gameId, isExternal, scope, regionQuery]);
|
|
190
|
+
|
|
191
|
+
// ---- 初始加载 ----
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
fetchData();
|
|
194
|
+
}, [fetchData]);
|
|
195
|
+
|
|
196
|
+
// ---- 生效的选项列表 ----
|
|
197
|
+
// region_code 统一归一化(去前导零)用于与 valueArr 匹配;
|
|
198
|
+
// rawRegionCode 保留数据源原始码(内部拉取已有,外部 options 取其原始值)
|
|
199
|
+
const options = useMemo(() => (isExternal ? externalOptions ?? [] : innerOptions).map(item => ({
|
|
200
|
+
...item,
|
|
201
|
+
region_code: normalizeRegionCode(item.region_code),
|
|
202
|
+
rawRegionCode: item.rawRegionCode ?? (item.region_code === null || item.region_code === undefined ? '' : String(item.region_code))
|
|
203
|
+
})), [isExternal, externalOptions, innerOptions]);
|
|
204
|
+
|
|
205
|
+
// ---- 归一化码 -> 数据源原始码(如 '4' -> '004'),以选项字典为权威来源 ----
|
|
206
|
+
const rawCodeMap = useMemo(() => {
|
|
207
|
+
const map = new Map();
|
|
208
|
+
options.forEach(item => {
|
|
209
|
+
map.set(item.region_code, item.rawRegionCode || item.region_code);
|
|
210
|
+
});
|
|
211
|
+
return map;
|
|
212
|
+
}, [options]);
|
|
213
|
+
|
|
214
|
+
// ---- 受控 value 中存在、但选项里缺失的码,兜底保留调用方传入的原始写法 ----
|
|
215
|
+
const inputRawCodeMap = useMemo(() => {
|
|
216
|
+
const map = new Map();
|
|
217
|
+
if (value === undefined || value === null || value === '') return map;
|
|
218
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
219
|
+
arr.forEach(item => {
|
|
220
|
+
if (item === null || item === undefined || item === '') return;
|
|
221
|
+
map.set(normalizeRegionCode(item), String(item));
|
|
222
|
+
});
|
|
223
|
+
return map;
|
|
224
|
+
}, [value]);
|
|
225
|
+
|
|
226
|
+
// ---- 将内部归一化码还原为应对外输出的原始码 ----
|
|
227
|
+
// preserveLeadingZero=true(默认): 输出数据源原始码(保留前导零,如 '004')
|
|
228
|
+
// preserveLeadingZero=false: 输出归一化码(去前导零,如 '4')
|
|
229
|
+
const resolveRawCodes = useCallback(codes => codes.map(code => {
|
|
230
|
+
const raw = rawCodeMap.get(code) ?? inputRawCodeMap.get(code) ?? code;
|
|
231
|
+
return preserveLeadingZero ? raw : normalizeRegionCode(raw);
|
|
232
|
+
}), [rawCodeMap, inputRawCodeMap, preserveLeadingZero]);
|
|
233
|
+
|
|
234
|
+
// ---- 转为对外暴露的 RegionOption(region_code 根据 preserveLeadingZero 决定格式) ----
|
|
235
|
+
const toPublicOption = useCallback(item => {
|
|
236
|
+
const raw = rawCodeMap.get(item.region_code) ?? item.region_code;
|
|
237
|
+
return {
|
|
238
|
+
region_code: preserveLeadingZero ? raw : normalizeRegionCode(raw),
|
|
239
|
+
region_ch: item.region_ch,
|
|
240
|
+
region_en: item.region_en,
|
|
241
|
+
region_a2_code: item.region_a2_code,
|
|
242
|
+
region_a3_code: item.region_a3_code,
|
|
243
|
+
disabled: item.disabled,
|
|
244
|
+
disabledReason: item.disabledReason
|
|
245
|
+
};
|
|
246
|
+
}, [rawCodeMap, preserveLeadingZero]);
|
|
247
|
+
|
|
248
|
+
// ---- 归一化选中值为数组(兼容单选字符串),并去掉前导零与 options 对齐 ----
|
|
249
|
+
const valueArr = useMemo(() => {
|
|
250
|
+
if (value === undefined || value === null || value === '') return [];
|
|
251
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
252
|
+
return arr.map(v => normalizeRegionCode(v));
|
|
253
|
+
}, [value]);
|
|
254
|
+
|
|
255
|
+
// ---- 本地搜索过滤 + optionFilter 自定义过滤 ----
|
|
256
|
+
const filteredOptions = useMemo(() => {
|
|
257
|
+
let result = options;
|
|
258
|
+
if (optionFilter) {
|
|
259
|
+
result = result.filter(optionFilter);
|
|
260
|
+
}
|
|
261
|
+
if (!searchKeyword) return result;
|
|
262
|
+
const kw = searchKeyword.toLowerCase();
|
|
263
|
+
return result.filter(item => item.region_ch?.toLowerCase().includes(kw) || item.region_en?.toLowerCase().includes(kw) || item.region_code?.toLowerCase().includes(kw) || item.rawRegionCode?.toLowerCase().includes(kw));
|
|
264
|
+
}, [options, searchKeyword, optionFilter]);
|
|
265
|
+
|
|
266
|
+
// ---- 判断选项是否禁用 ----
|
|
267
|
+
const isOptionDisabled = useCallback(item => {
|
|
268
|
+
if (item.disabled) return true;
|
|
269
|
+
return optionDisabled ? optionDisabled(item) : false;
|
|
270
|
+
}, [optionDisabled]);
|
|
271
|
+
|
|
272
|
+
// ---- 多选 onChange 统一出口 ----
|
|
273
|
+
// 入参为内部归一化码,对外回传数据源原始码(保留前导零,如 '004')
|
|
274
|
+
const emitMultiple = useCallback(nextValue => {
|
|
275
|
+
const rawValues = resolveRawCodes(nextValue);
|
|
276
|
+
const selectedOptions = options.filter(o => nextValue.includes(o.region_code)).map(toPublicOption);
|
|
277
|
+
onChange?.(rawValues, selectedOptions);
|
|
278
|
+
return rawValues;
|
|
279
|
+
}, [onChange, options, resolveRawCodes, toPublicOption]);
|
|
280
|
+
|
|
281
|
+
// ---- 全选/清空 ----
|
|
282
|
+
const handleSelectAll = useCallback(() => {
|
|
283
|
+
const allValues = filteredOptions.filter(item => !isOptionDisabled(item)).map(item => item.region_code);
|
|
284
|
+
emitMultiple(Array.from(new Set([...valueArr, ...allValues])));
|
|
285
|
+
}, [filteredOptions, valueArr, emitMultiple, isOptionDisabled]);
|
|
286
|
+
const handleClearAll = useCallback(() => {
|
|
287
|
+
emitMultiple([]);
|
|
288
|
+
}, [emitMultiple]);
|
|
289
|
+
|
|
290
|
+
// ---- 反选 ----
|
|
291
|
+
const handleInvert = useCallback(() => {
|
|
292
|
+
const selectedSet = new Set(valueArr);
|
|
293
|
+
const nextValue = filteredOptions.filter(item => !isOptionDisabled(item)).map(item => item.region_code).filter(code => !selectedSet.has(code));
|
|
294
|
+
emitMultiple(nextValue);
|
|
295
|
+
}, [filteredOptions, valueArr, emitMultiple, isOptionDisabled]);
|
|
296
|
+
|
|
297
|
+
// ---- 是否全选 ----
|
|
298
|
+
const isAllSelected = useMemo(() => {
|
|
299
|
+
const selectable = filteredOptions.filter(item => !isOptionDisabled(item));
|
|
300
|
+
return selectable.length > 0 && selectable.every(item => valueArr.includes(item.region_code));
|
|
301
|
+
}, [filteredOptions, valueArr, isOptionDisabled]);
|
|
302
|
+
|
|
303
|
+
// ---- 是否半选 ----
|
|
304
|
+
const isIndeterminate = useMemo(() => {
|
|
305
|
+
const selectable = filteredOptions.filter(item => !isOptionDisabled(item));
|
|
306
|
+
const selected = selectable.filter(item => valueArr.includes(item.region_code));
|
|
307
|
+
return selected.length > 0 && selected.length < selectable.length;
|
|
308
|
+
}, [filteredOptions, valueArr, isOptionDisabled]);
|
|
309
|
+
|
|
310
|
+
// ---- 构建 Select options ----
|
|
311
|
+
const selectOptions = useMemo(() => {
|
|
312
|
+
return filteredOptions.map(item => {
|
|
313
|
+
const label = getLabelText(item, labelFormat);
|
|
314
|
+
const isDisabled = isOptionDisabled(item);
|
|
315
|
+
const tooltip = optionTooltip ? optionTooltip(item) : undefined;
|
|
316
|
+
return {
|
|
317
|
+
label,
|
|
318
|
+
value: item.region_code,
|
|
319
|
+
disabled: isDisabled,
|
|
320
|
+
tooltip,
|
|
321
|
+
rawItem: item
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
}, [filteredOptions, labelFormat, isOptionDisabled, optionTooltip]);
|
|
325
|
+
|
|
326
|
+
// ---- Select onChange ----
|
|
327
|
+
const handleChange = useCallback(val => {
|
|
328
|
+
const values = Array.isArray(val) ? val : val ? [val] : [];
|
|
329
|
+
// Select 回传的是 option 上的归一化码,仅用于匹配
|
|
330
|
+
const strValues = values.map(normalizeRegionCode);
|
|
331
|
+
const selectedOptions = options.filter(o => strValues.includes(o.region_code)).map(toPublicOption);
|
|
332
|
+
// 对外输出还原为数据源原始码(保留前导零)
|
|
333
|
+
const rawValues = resolveRawCodes(strValues);
|
|
334
|
+
// 多选模式下勾选后清除搜索关键字,恢复完整列表
|
|
335
|
+
if (multiple) {
|
|
336
|
+
setSearchKeyword('');
|
|
337
|
+
}
|
|
338
|
+
if (multiple) {
|
|
339
|
+
onChange?.(rawValues, selectedOptions);
|
|
340
|
+
} else {
|
|
341
|
+
onChange?.(rawValues[0] ?? '', selectedOptions[0] ?? undefined);
|
|
342
|
+
}
|
|
343
|
+
}, [onChange, options, multiple, resolveRawCodes, toPublicOption]);
|
|
344
|
+
|
|
345
|
+
// ---- 批量导入确认 ----
|
|
346
|
+
const handleImportConfirm = useCallback(nextValue => {
|
|
347
|
+
// ImportDialog 回传归一化码,经 emitMultiple 还原为原始码
|
|
348
|
+
const rawValues = emitMultiple(nextValue);
|
|
349
|
+
// 兼容外部表单 onBlur 校验触发,同样回传原始码
|
|
350
|
+
onBlur?.({
|
|
351
|
+
value: rawValues
|
|
352
|
+
});
|
|
353
|
+
}, [emitMultiple, onBlur]);
|
|
354
|
+
|
|
355
|
+
// ---- 面板顶部操作栏 ----
|
|
356
|
+
const panelTopContent = useMemo(() => {
|
|
357
|
+
if (!multiple) return null;
|
|
358
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
359
|
+
className: "pnt-region-select__toolbar",
|
|
360
|
+
children: [/*#__PURE__*/_jsx(Checkbox, {
|
|
361
|
+
checked: isAllSelected,
|
|
362
|
+
indeterminate: isIndeterminate,
|
|
363
|
+
onChange: checked => {
|
|
364
|
+
if (checked) {
|
|
365
|
+
handleSelectAll();
|
|
366
|
+
} else {
|
|
367
|
+
handleClearAll();
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
children: texts.selectAll
|
|
371
|
+
}), invertible && /*#__PURE__*/_jsx("span", {
|
|
372
|
+
role: "button",
|
|
373
|
+
tabIndex: 0,
|
|
374
|
+
className: "pnt-region-select__invert",
|
|
375
|
+
onClick: event => {
|
|
376
|
+
event.preventDefault();
|
|
377
|
+
event.stopPropagation();
|
|
378
|
+
handleInvert();
|
|
379
|
+
},
|
|
380
|
+
onKeyDown: event => {
|
|
381
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
event.stopPropagation();
|
|
384
|
+
handleInvert();
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
children: texts.invertSelection
|
|
388
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
389
|
+
className: "pnt-region-select__count",
|
|
390
|
+
children: texts.selectedCount.replace('{count}', String(valueArr.length))
|
|
391
|
+
})]
|
|
392
|
+
});
|
|
393
|
+
}, [multiple, isAllSelected, isIndeterminate, handleSelectAll, handleClearAll, invertible, handleInvert, texts, valueArr.length]);
|
|
394
|
+
|
|
395
|
+
// ---- 空状态 ----
|
|
396
|
+
const emptyContent = useMemo(() => {
|
|
397
|
+
if (loading) return null;
|
|
398
|
+
if (filteredOptions.length === 0) {
|
|
399
|
+
return /*#__PURE__*/_jsx("div", {
|
|
400
|
+
className: "pnt-region-select__empty",
|
|
401
|
+
children: texts.noResult
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}, [loading, filteredOptions.length, texts.noResult]);
|
|
406
|
+
|
|
407
|
+
// 下拉框关闭时清除搜索关键字,避免下次打开仍显示过滤结果
|
|
408
|
+
const handlePopupVisibleChange = useCallback(visible => {
|
|
409
|
+
if (!visible) {
|
|
410
|
+
setSearchKeyword('');
|
|
411
|
+
}
|
|
412
|
+
}, []);
|
|
413
|
+
const selectNode = /*#__PURE__*/_jsx(Select, {
|
|
414
|
+
value: multiple ? valueArr : valueArr[0],
|
|
415
|
+
onChange: handleChange,
|
|
416
|
+
filterable: filterable,
|
|
417
|
+
onSearch: setSearchKeyword,
|
|
418
|
+
onPopupVisibleChange: handlePopupVisibleChange,
|
|
419
|
+
loading: loading && options.length === 0,
|
|
420
|
+
multiple: multiple,
|
|
421
|
+
max: max,
|
|
422
|
+
clearable: true,
|
|
423
|
+
disabled: disabled,
|
|
424
|
+
placeholder: placeholder || texts.placeholder,
|
|
425
|
+
panelTopContent: panelTopContent,
|
|
426
|
+
empty: emptyContent,
|
|
427
|
+
minCollapsedNum: minCollapsedNum,
|
|
428
|
+
collapsedItems: collapsedItems,
|
|
429
|
+
onBlur: onBlur,
|
|
430
|
+
popupProps: {
|
|
431
|
+
...popupProps,
|
|
432
|
+
overlayClassName: ['pnt-region-select-popup', popupProps?.overlayClassName].filter(Boolean).join(' ')
|
|
433
|
+
},
|
|
434
|
+
className: className ? `pnt-region-select ${className}` : 'pnt-region-select',
|
|
435
|
+
style: style,
|
|
436
|
+
children: selectOptions.map(opt => /*#__PURE__*/_jsx(Select.Option, {
|
|
437
|
+
value: opt.value,
|
|
438
|
+
label: opt.label,
|
|
439
|
+
disabled: opt.disabled,
|
|
440
|
+
title: opt.tooltip
|
|
441
|
+
}, opt.value))
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// ---- 批量导入模式:输入框右侧叠加导入按钮 ----
|
|
445
|
+
if (importable && multiple) {
|
|
446
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
447
|
+
children: [/*#__PURE__*/_jsxs("div", {
|
|
448
|
+
className: "pnt-region-select__import-wrapper",
|
|
449
|
+
children: [selectNode, /*#__PURE__*/_jsx(Button, {
|
|
450
|
+
theme: "primary",
|
|
451
|
+
size: "small",
|
|
452
|
+
variant: "outline",
|
|
453
|
+
disabled: disabled,
|
|
454
|
+
className: "pnt-region-select__import-btn",
|
|
455
|
+
onClick: () => setImportVisible(true),
|
|
456
|
+
children: texts.importBtn
|
|
457
|
+
})]
|
|
458
|
+
}), /*#__PURE__*/_jsx(ImportDialog, {
|
|
459
|
+
visible: importVisible,
|
|
460
|
+
options: options,
|
|
461
|
+
currentValue: valueArr,
|
|
462
|
+
texts: texts,
|
|
463
|
+
dialogProps: importDialogProps,
|
|
464
|
+
onClose: () => setImportVisible(false),
|
|
465
|
+
onConfirm: handleImportConfirm
|
|
466
|
+
})]
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return selectNode;
|
|
470
|
+
};
|
|
471
|
+
export default PntRegionSelect;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
.pnt-region-select {
|
|
2
|
+
width: 100%;
|
|
3
|
+
|
|
4
|
+
&__toolbar {
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
padding: 8px 12px;
|
|
9
|
+
border-bottom: 1px solid var(--td-component-border, #e7e7e7);
|
|
10
|
+
background: var(--td-bg-color-container, #fff);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&__invert {
|
|
14
|
+
margin-left: 12px;
|
|
15
|
+
margin-right: auto;
|
|
16
|
+
font-size: 12px;
|
|
17
|
+
color: var(--td-brand-color, #0052d9);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
user-select: none;
|
|
20
|
+
|
|
21
|
+
&:hover {
|
|
22
|
+
color: var(--td-brand-color-hover, #2667d4);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&__count {
|
|
27
|
+
margin-left: auto;
|
|
28
|
+
font-size: 12px;
|
|
29
|
+
color: var(--td-text-color-secondary, #999);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&__invert + &__count {
|
|
33
|
+
margin-left: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&__empty {
|
|
37
|
+
padding: 16px;
|
|
38
|
+
text-align: center;
|
|
39
|
+
color: var(--td-text-color-secondary, #999);
|
|
40
|
+
font-size: 14px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---- 批量导入 ----
|
|
44
|
+
// 批量导入按钮绝对定位在 Select 输入框内部,
|
|
45
|
+
// 给 .t-input 预留右侧 padding,避免选中大量地区时标签与按钮重叠
|
|
46
|
+
&__import-wrapper {
|
|
47
|
+
position: relative;
|
|
48
|
+
width: 100%;
|
|
49
|
+
|
|
50
|
+
.pnt-region-select {
|
|
51
|
+
width: 100%;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Select 输入框预留按钮占位(按钮约 80px + 右侧 20px = 100px)
|
|
55
|
+
.t-input {
|
|
56
|
+
padding-right: 100px;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&__import-btn {
|
|
61
|
+
position: absolute;
|
|
62
|
+
right: 20px;
|
|
63
|
+
top: 50%;
|
|
64
|
+
transform: translateY(-50%);
|
|
65
|
+
z-index: 1;
|
|
66
|
+
flex-shrink: 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.pnt-region-select-popup {
|
|
71
|
+
.t-select__list {
|
|
72
|
+
padding-top: 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---- 批量导入弹窗 ----
|
|
77
|
+
.pnt-region-import {
|
|
78
|
+
&__tip {
|
|
79
|
+
margin-bottom: 8px;
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
color: var(--td-text-color-secondary, #999);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&__result {
|
|
85
|
+
margin-top: 12px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&__summary {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 4px;
|
|
92
|
+
margin-bottom: 8px;
|
|
93
|
+
font-size: 12px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&__summary-item {
|
|
97
|
+
&--unmatched {
|
|
98
|
+
color: var(--td-error-color, #d54941);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
&--pending {
|
|
102
|
+
color: var(--td-success-color, #2ba471);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&--exists {
|
|
106
|
+
color: var(--td-text-color-secondary, #999);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&__summary-dot {
|
|
111
|
+
color: var(--td-text-color-placeholder, #bbb);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
&__copy-btn {
|
|
115
|
+
padding: 0 4px;
|
|
116
|
+
height: 20px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&__table {
|
|
120
|
+
border: 1px solid var(--td-component-border, #e7e7e7);
|
|
121
|
+
border-radius: 4px;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
&__table-head {
|
|
126
|
+
display: flex;
|
|
127
|
+
justify-content: space-between;
|
|
128
|
+
padding: 6px 12px;
|
|
129
|
+
background: var(--td-bg-color-secondarycontainer, #f3f3f3);
|
|
130
|
+
font-size: 12px;
|
|
131
|
+
color: var(--td-text-color-secondary, #999);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&__table-body {
|
|
135
|
+
max-height: 200px;
|
|
136
|
+
overflow-y: auto;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&__table-row {
|
|
140
|
+
display: flex;
|
|
141
|
+
justify-content: space-between;
|
|
142
|
+
align-items: center;
|
|
143
|
+
padding: 6px 12px;
|
|
144
|
+
font-size: 12px;
|
|
145
|
+
|
|
146
|
+
& + & {
|
|
147
|
+
border-top: 1px solid var(--td-component-border, #e7e7e7);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&__status {
|
|
152
|
+
display: inline-flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
gap: 4px;
|
|
155
|
+
|
|
156
|
+
&--unmatched {
|
|
157
|
+
color: var(--td-error-color, #d54941);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
&--pending {
|
|
161
|
+
color: var(--td-success-color, #2ba471);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
&--exists {
|
|
165
|
+
color: var(--td-text-color-secondary, #999);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
&__empty {
|
|
170
|
+
padding: 16px;
|
|
171
|
+
text-align: center;
|
|
172
|
+
color: var(--td-text-color-secondary, #999);
|
|
173
|
+
}
|
|
174
|
+
}
|