@pnt-team/pnt-component-frontend 0.0.111 → 0.0.116
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/README.md +29 -30
- package/lib/cjs/api/index.d.ts +9 -1
- package/lib/cjs/api/index.js +6 -0
- package/lib/cjs/api/types/index.d.ts +43 -6
- package/lib/cjs/components/PntBusinessSelect/index.d.ts +0 -5
- package/lib/cjs/components/PntBusinessSelect/index.js +1 -11
- package/lib/cjs/components/PntRegionSelect/ImportDialog.d.ts +25 -0
- package/lib/cjs/components/PntRegionSelect/ImportDialog.js +244 -0
- package/lib/cjs/components/PntRegionSelect/index.d.ts +34 -0
- package/lib/cjs/components/PntRegionSelect/index.js +387 -0
- package/lib/cjs/components/PntRegionSelect/index.scss +166 -0
- package/lib/cjs/components/PntRegionSelect/types.d.ts +160 -0
- package/lib/cjs/components/PntRegionSelect/types.js +51 -0
- package/lib/cjs/components/PntSecureBox/index.scss +3 -0
- package/lib/cjs/config.d.ts +17 -0
- package/lib/cjs/index.d.ts +5 -1
- package/lib/cjs/index.js +10 -0
- package/lib/esm/api/index.d.ts +9 -1
- package/lib/esm/api/index.js +10 -1
- package/lib/esm/api/types/index.d.ts +43 -6
- package/lib/esm/components/PntBusinessSelect/index.d.ts +0 -5
- package/lib/esm/components/PntBusinessSelect/index.js +1 -3
- package/lib/esm/components/PntRegionSelect/ImportDialog.d.ts +25 -0
- package/lib/esm/components/PntRegionSelect/ImportDialog.js +241 -0
- package/lib/esm/components/PntRegionSelect/index.d.ts +34 -0
- package/lib/esm/components/PntRegionSelect/index.js +368 -0
- package/lib/esm/components/PntRegionSelect/index.scss +166 -0
- package/lib/esm/components/PntRegionSelect/types.d.ts +160 -0
- package/lib/esm/components/PntRegionSelect/types.js +49 -0
- package/lib/esm/components/PntSecureBox/index.scss +3 -0
- package/lib/esm/config.d.ts +17 -0
- package/lib/esm/config.js +4 -0
- package/lib/esm/index.d.ts +5 -1
- package/lib/esm/index.js +3 -0
- package/lib/umd/pnt-component-frontend.min.css +1 -1
- package/lib/umd/pnt-component-frontend.min.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { businessRegionQuery } from "../../api";
|
|
2
|
+
import { usePntConfig } 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 { DEFAULT_TEXTS } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 将后端区域项归一化为组件内部的 RegionOption
|
|
11
|
+
* @description 后端契约字段均为可选,此处补默认值
|
|
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
|
+
const toRegionOptions = list => (list || []).map(item => ({
|
|
17
|
+
...item,
|
|
18
|
+
region_code: item.region_code ?? '',
|
|
19
|
+
region_ch: item.region_ch ?? '',
|
|
20
|
+
region_en: item.region_en ?? ''
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 构建标签文本
|
|
25
|
+
*/
|
|
26
|
+
const getLabelText = (item, format) => {
|
|
27
|
+
const {
|
|
28
|
+
region_ch,
|
|
29
|
+
region_en,
|
|
30
|
+
region_code
|
|
31
|
+
} = item;
|
|
32
|
+
switch (format) {
|
|
33
|
+
case 'name_en':
|
|
34
|
+
return region_en || region_ch || region_code;
|
|
35
|
+
case 'name_en_name':
|
|
36
|
+
return region_en && region_ch ? `${region_en}(${region_ch})` : region_en || region_ch || region_code;
|
|
37
|
+
case 'code':
|
|
38
|
+
return region_code;
|
|
39
|
+
default:
|
|
40
|
+
return region_ch || region_en || region_code;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 地区选择器组件
|
|
46
|
+
* @description 统一的国家/地区选择组件,支持单选/多选、批量选择、批量导入、自定义区域、合规区域
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* // 基础多选(内部拉取默认区域列表)
|
|
51
|
+
* <PntRegionSelect
|
|
52
|
+
* value={regions}
|
|
53
|
+
* onChange={(v) => setRegions(v)}
|
|
54
|
+
* />
|
|
55
|
+
*
|
|
56
|
+
* // 业务最终生效区域
|
|
57
|
+
* <PntRegionSelect
|
|
58
|
+
* dataSource="business"
|
|
59
|
+
* gameId={gameId}
|
|
60
|
+
* value={regions}
|
|
61
|
+
* onChange={(v) => setRegions(v)}
|
|
62
|
+
* />
|
|
63
|
+
*
|
|
64
|
+
* // 外部受控选项 + 批量导入
|
|
65
|
+
* <PntRegionSelect
|
|
66
|
+
* options={regionList}
|
|
67
|
+
* importable
|
|
68
|
+
* value={regions}
|
|
69
|
+
* onChange={(v) => setRegions(v)}
|
|
70
|
+
* />
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
const PntRegionSelect = props => {
|
|
74
|
+
const {
|
|
75
|
+
value = [],
|
|
76
|
+
onChange,
|
|
77
|
+
multiple = true,
|
|
78
|
+
dataSource = 'default',
|
|
79
|
+
scope = 'business',
|
|
80
|
+
gameId,
|
|
81
|
+
options: externalOptions,
|
|
82
|
+
disabled = false,
|
|
83
|
+
placeholder,
|
|
84
|
+
max,
|
|
85
|
+
filterable = true,
|
|
86
|
+
labelFormat = 'name',
|
|
87
|
+
texts: customTexts,
|
|
88
|
+
onLoaded,
|
|
89
|
+
optionDisabled,
|
|
90
|
+
optionTooltip,
|
|
91
|
+
importable = false,
|
|
92
|
+
invertible = false,
|
|
93
|
+
minCollapsedNum,
|
|
94
|
+
collapsedItems,
|
|
95
|
+
onBlur,
|
|
96
|
+
importDialogProps,
|
|
97
|
+
className,
|
|
98
|
+
style,
|
|
99
|
+
popupProps
|
|
100
|
+
} = props;
|
|
101
|
+
const {
|
|
102
|
+
http,
|
|
103
|
+
regionQuery
|
|
104
|
+
} = usePntConfig();
|
|
105
|
+
const texts = useMemo(() => ({
|
|
106
|
+
...DEFAULT_TEXTS,
|
|
107
|
+
...customTexts
|
|
108
|
+
}), [customTexts]);
|
|
109
|
+
|
|
110
|
+
// ---- 状态 ----
|
|
111
|
+
const [innerOptions, setInnerOptions] = useState([]);
|
|
112
|
+
const [loading, setLoading] = useState(false);
|
|
113
|
+
const [searchKeyword, setSearchKeyword] = useState('');
|
|
114
|
+
const [importVisible, setImportVisible] = useState(false);
|
|
115
|
+
|
|
116
|
+
// ---- Refs ----
|
|
117
|
+
const fetchSeqRef = useRef(0);
|
|
118
|
+
const onLoadedRef = useRef(onLoaded);
|
|
119
|
+
onLoadedRef.current = onLoaded;
|
|
120
|
+
|
|
121
|
+
// ---- 外部受控模式:传入 options 时跳过内部请求 ----
|
|
122
|
+
const isExternal = externalOptions !== undefined;
|
|
123
|
+
|
|
124
|
+
// ---- 获取数据 ----
|
|
125
|
+
const fetchData = useCallback(async () => {
|
|
126
|
+
if (isExternal) return;
|
|
127
|
+
const seq = ++fetchSeqRef.current;
|
|
128
|
+
setLoading(true);
|
|
129
|
+
try {
|
|
130
|
+
let list = [];
|
|
131
|
+
const url = scope === 'platform' ? regionQuery?.queryPlatUrl || '/api/v5/business/region/plat-query?audience=/laas-pnt' : regionQuery?.queryUrl || '/api/v5/business/region/query?audience=/laas-pnt';
|
|
132
|
+
if (dataSource === 'business') {
|
|
133
|
+
if (!gameId) {
|
|
134
|
+
console.warn('[PntRegionSelect] dataSource="business" 时必须传入 gameId');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const res = await businessRegionQuery(http, {
|
|
138
|
+
game_id: Number(gameId)
|
|
139
|
+
}, url);
|
|
140
|
+
if (seq !== fetchSeqRef.current) return;
|
|
141
|
+
if (res.code === 0 && res.data?.region_list) {
|
|
142
|
+
list = toRegionOptions(res.data.region_list);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
const res = await businessRegionQuery(http, {}, url);
|
|
146
|
+
if (seq !== fetchSeqRef.current) return;
|
|
147
|
+
if (res.code === 0 && res.data?.region_list) {
|
|
148
|
+
list = toRegionOptions(res.data.region_list);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
setInnerOptions(list);
|
|
152
|
+
onLoadedRef.current?.(list);
|
|
153
|
+
} catch {
|
|
154
|
+
// 请求失败静默处理
|
|
155
|
+
} finally {
|
|
156
|
+
if (seq === fetchSeqRef.current) {
|
|
157
|
+
setLoading(false);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}, [http, dataSource, gameId, isExternal, scope, regionQuery]);
|
|
161
|
+
|
|
162
|
+
// ---- 初始加载 ----
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
fetchData();
|
|
165
|
+
}, [fetchData]);
|
|
166
|
+
|
|
167
|
+
// ---- 生效的选项列表 ----
|
|
168
|
+
const options = useMemo(() => isExternal ? externalOptions : innerOptions, [isExternal, externalOptions, innerOptions]);
|
|
169
|
+
|
|
170
|
+
// ---- 归一化选中值为数组(兼容单选字符串) ----
|
|
171
|
+
const valueArr = useMemo(() => {
|
|
172
|
+
if (value === undefined || value === null || value === '') return [];
|
|
173
|
+
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
174
|
+
}, [value]);
|
|
175
|
+
|
|
176
|
+
// ---- 本地搜索过滤 ----
|
|
177
|
+
const filteredOptions = useMemo(() => {
|
|
178
|
+
if (!searchKeyword) return options;
|
|
179
|
+
const kw = searchKeyword.toLowerCase();
|
|
180
|
+
return options.filter(item => item.region_ch?.toLowerCase().includes(kw) || item.region_en?.toLowerCase().includes(kw) || item.region_code?.toLowerCase().includes(kw));
|
|
181
|
+
}, [options, searchKeyword]);
|
|
182
|
+
|
|
183
|
+
// ---- 判断选项是否禁用 ----
|
|
184
|
+
const isOptionDisabled = useCallback(item => {
|
|
185
|
+
if (item.disabled) return true;
|
|
186
|
+
return optionDisabled ? optionDisabled(item) : false;
|
|
187
|
+
}, [optionDisabled]);
|
|
188
|
+
|
|
189
|
+
// ---- 多选 onChange 统一出口 ----
|
|
190
|
+
const emitMultiple = useCallback(nextValue => {
|
|
191
|
+
onChange?.(nextValue, options.filter(o => nextValue.includes(o.region_code)));
|
|
192
|
+
}, [onChange, options]);
|
|
193
|
+
|
|
194
|
+
// ---- 全选/清空 ----
|
|
195
|
+
const handleSelectAll = useCallback(() => {
|
|
196
|
+
const allValues = filteredOptions.filter(item => !isOptionDisabled(item)).map(item => item.region_code);
|
|
197
|
+
emitMultiple(Array.from(new Set([...valueArr, ...allValues])));
|
|
198
|
+
}, [filteredOptions, valueArr, emitMultiple, isOptionDisabled]);
|
|
199
|
+
const handleClearAll = useCallback(() => {
|
|
200
|
+
emitMultiple([]);
|
|
201
|
+
}, [emitMultiple]);
|
|
202
|
+
|
|
203
|
+
// ---- 反选 ----
|
|
204
|
+
const handleInvert = useCallback(() => {
|
|
205
|
+
const selectedSet = new Set(valueArr);
|
|
206
|
+
const nextValue = filteredOptions.filter(item => !isOptionDisabled(item)).map(item => item.region_code).filter(code => !selectedSet.has(code));
|
|
207
|
+
emitMultiple(nextValue);
|
|
208
|
+
}, [filteredOptions, valueArr, emitMultiple, isOptionDisabled]);
|
|
209
|
+
|
|
210
|
+
// ---- 是否全选 ----
|
|
211
|
+
const isAllSelected = useMemo(() => {
|
|
212
|
+
const selectable = filteredOptions.filter(item => !isOptionDisabled(item));
|
|
213
|
+
return selectable.length > 0 && selectable.every(item => valueArr.includes(item.region_code));
|
|
214
|
+
}, [filteredOptions, valueArr, isOptionDisabled]);
|
|
215
|
+
|
|
216
|
+
// ---- 是否半选 ----
|
|
217
|
+
const isIndeterminate = useMemo(() => {
|
|
218
|
+
const selectable = filteredOptions.filter(item => !isOptionDisabled(item));
|
|
219
|
+
const selected = selectable.filter(item => valueArr.includes(item.region_code));
|
|
220
|
+
return selected.length > 0 && selected.length < selectable.length;
|
|
221
|
+
}, [filteredOptions, valueArr, isOptionDisabled]);
|
|
222
|
+
|
|
223
|
+
// ---- 构建 Select options ----
|
|
224
|
+
const selectOptions = useMemo(() => {
|
|
225
|
+
return filteredOptions.map(item => {
|
|
226
|
+
const label = getLabelText(item, labelFormat);
|
|
227
|
+
const isDisabled = isOptionDisabled(item);
|
|
228
|
+
const tooltip = optionTooltip ? optionTooltip(item) : undefined;
|
|
229
|
+
return {
|
|
230
|
+
label,
|
|
231
|
+
value: item.region_code,
|
|
232
|
+
disabled: isDisabled,
|
|
233
|
+
tooltip,
|
|
234
|
+
rawItem: item
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
}, [filteredOptions, labelFormat, isOptionDisabled, optionTooltip]);
|
|
238
|
+
|
|
239
|
+
// ---- Select onChange ----
|
|
240
|
+
const handleChange = useCallback(val => {
|
|
241
|
+
const values = Array.isArray(val) ? val : val ? [val] : [];
|
|
242
|
+
const strValues = values.map(String);
|
|
243
|
+
const selectedOptions = options.filter(o => strValues.includes(o.region_code));
|
|
244
|
+
if (multiple) {
|
|
245
|
+
onChange?.(strValues, selectedOptions);
|
|
246
|
+
} else {
|
|
247
|
+
onChange?.(strValues[0] ?? '', selectedOptions[0] ?? undefined);
|
|
248
|
+
}
|
|
249
|
+
}, [onChange, options, multiple]);
|
|
250
|
+
|
|
251
|
+
// ---- 批量导入确认 ----
|
|
252
|
+
const handleImportConfirm = useCallback(nextValue => {
|
|
253
|
+
emitMultiple(nextValue);
|
|
254
|
+
// 兼容外部表单 onBlur 校验触发
|
|
255
|
+
onBlur?.({
|
|
256
|
+
value: nextValue
|
|
257
|
+
});
|
|
258
|
+
}, [emitMultiple, onBlur]);
|
|
259
|
+
|
|
260
|
+
// ---- 面板顶部操作栏 ----
|
|
261
|
+
const panelTopContent = useMemo(() => {
|
|
262
|
+
if (!multiple) return null;
|
|
263
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
264
|
+
className: "pnt-region-select__toolbar",
|
|
265
|
+
children: [/*#__PURE__*/_jsx(Checkbox, {
|
|
266
|
+
checked: isAllSelected,
|
|
267
|
+
indeterminate: isIndeterminate,
|
|
268
|
+
onChange: checked => {
|
|
269
|
+
if (checked) {
|
|
270
|
+
handleSelectAll();
|
|
271
|
+
} else {
|
|
272
|
+
handleClearAll();
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
children: texts.selectAll
|
|
276
|
+
}), invertible && /*#__PURE__*/_jsx("span", {
|
|
277
|
+
role: "button",
|
|
278
|
+
tabIndex: 0,
|
|
279
|
+
className: "pnt-region-select__invert",
|
|
280
|
+
onClick: event => {
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
event.stopPropagation();
|
|
283
|
+
handleInvert();
|
|
284
|
+
},
|
|
285
|
+
onKeyDown: event => {
|
|
286
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
event.stopPropagation();
|
|
289
|
+
handleInvert();
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
children: texts.invertSelection
|
|
293
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
294
|
+
className: "pnt-region-select__count",
|
|
295
|
+
children: texts.selectedCount.replace('{count}', String(valueArr.length))
|
|
296
|
+
})]
|
|
297
|
+
});
|
|
298
|
+
}, [multiple, isAllSelected, isIndeterminate, handleSelectAll, handleClearAll, invertible, handleInvert, texts, valueArr.length]);
|
|
299
|
+
|
|
300
|
+
// ---- 空状态 ----
|
|
301
|
+
const emptyContent = useMemo(() => {
|
|
302
|
+
if (loading) return null;
|
|
303
|
+
if (filteredOptions.length === 0) {
|
|
304
|
+
return /*#__PURE__*/_jsx("div", {
|
|
305
|
+
className: "pnt-region-select__empty",
|
|
306
|
+
children: texts.noResult
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}, [loading, filteredOptions.length, texts.noResult]);
|
|
311
|
+
const selectNode = /*#__PURE__*/_jsx(Select, {
|
|
312
|
+
value: multiple ? valueArr : valueArr[0],
|
|
313
|
+
onChange: handleChange,
|
|
314
|
+
filterable: filterable,
|
|
315
|
+
onSearch: setSearchKeyword,
|
|
316
|
+
loading: loading && options.length === 0,
|
|
317
|
+
multiple: multiple,
|
|
318
|
+
max: max,
|
|
319
|
+
clearable: true,
|
|
320
|
+
disabled: disabled,
|
|
321
|
+
placeholder: placeholder || texts.placeholder,
|
|
322
|
+
panelTopContent: panelTopContent,
|
|
323
|
+
empty: emptyContent,
|
|
324
|
+
minCollapsedNum: minCollapsedNum,
|
|
325
|
+
collapsedItems: collapsedItems,
|
|
326
|
+
onBlur: onBlur,
|
|
327
|
+
popupProps: {
|
|
328
|
+
...popupProps,
|
|
329
|
+
overlayClassName: ['pnt-region-select-popup', popupProps?.overlayClassName].filter(Boolean).join(' ')
|
|
330
|
+
},
|
|
331
|
+
className: className ? `pnt-region-select ${className}` : 'pnt-region-select',
|
|
332
|
+
style: style,
|
|
333
|
+
children: selectOptions.map(opt => /*#__PURE__*/_jsx(Select.Option, {
|
|
334
|
+
value: opt.value,
|
|
335
|
+
label: opt.label,
|
|
336
|
+
disabled: opt.disabled,
|
|
337
|
+
title: opt.tooltip
|
|
338
|
+
}, opt.value))
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// ---- 批量导入模式:输入框右侧叠加导入按钮 ----
|
|
342
|
+
if (importable && multiple) {
|
|
343
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
344
|
+
children: [/*#__PURE__*/_jsxs("div", {
|
|
345
|
+
className: "pnt-region-select__import-wrapper",
|
|
346
|
+
children: [selectNode, /*#__PURE__*/_jsx(Button, {
|
|
347
|
+
theme: "primary",
|
|
348
|
+
size: "small",
|
|
349
|
+
variant: "outline",
|
|
350
|
+
disabled: disabled,
|
|
351
|
+
className: "pnt-region-select__import-btn",
|
|
352
|
+
onClick: () => setImportVisible(true),
|
|
353
|
+
children: texts.importBtn
|
|
354
|
+
})]
|
|
355
|
+
}), /*#__PURE__*/_jsx(ImportDialog, {
|
|
356
|
+
visible: importVisible,
|
|
357
|
+
options: options,
|
|
358
|
+
currentValue: valueArr,
|
|
359
|
+
texts: texts,
|
|
360
|
+
dialogProps: importDialogProps,
|
|
361
|
+
onClose: () => setImportVisible(false),
|
|
362
|
+
onConfirm: handleImportConfirm
|
|
363
|
+
})]
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return selectNode;
|
|
367
|
+
};
|
|
368
|
+
export default PntRegionSelect;
|
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
&__import-wrapper {
|
|
45
|
+
position: relative;
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
|
|
49
|
+
.pnt-region-select {
|
|
50
|
+
flex: 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&__import-btn {
|
|
55
|
+
position: absolute;
|
|
56
|
+
right: 32px;
|
|
57
|
+
z-index: 1;
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.pnt-region-select-popup {
|
|
63
|
+
.t-select__list {
|
|
64
|
+
padding-top: 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---- 批量导入弹窗 ----
|
|
69
|
+
.pnt-region-import {
|
|
70
|
+
&__tip {
|
|
71
|
+
margin-bottom: 8px;
|
|
72
|
+
font-size: 12px;
|
|
73
|
+
color: var(--td-text-color-secondary, #999);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&__result {
|
|
77
|
+
margin-top: 12px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&__summary {
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 4px;
|
|
84
|
+
margin-bottom: 8px;
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&__summary-item {
|
|
89
|
+
&--unmatched {
|
|
90
|
+
color: var(--td-error-color, #d54941);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&--pending {
|
|
94
|
+
color: var(--td-success-color, #2ba471);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
&--exists {
|
|
98
|
+
color: var(--td-text-color-secondary, #999);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&__summary-dot {
|
|
103
|
+
color: var(--td-text-color-placeholder, #bbb);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
&__copy-btn {
|
|
107
|
+
padding: 0 4px;
|
|
108
|
+
height: 20px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&__table {
|
|
112
|
+
border: 1px solid var(--td-component-border, #e7e7e7);
|
|
113
|
+
border-radius: 4px;
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&__table-head {
|
|
118
|
+
display: flex;
|
|
119
|
+
justify-content: space-between;
|
|
120
|
+
padding: 6px 12px;
|
|
121
|
+
background: var(--td-bg-color-secondarycontainer, #f3f3f3);
|
|
122
|
+
font-size: 12px;
|
|
123
|
+
color: var(--td-text-color-secondary, #999);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
&__table-body {
|
|
127
|
+
max-height: 200px;
|
|
128
|
+
overflow-y: auto;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&__table-row {
|
|
132
|
+
display: flex;
|
|
133
|
+
justify-content: space-between;
|
|
134
|
+
align-items: center;
|
|
135
|
+
padding: 6px 12px;
|
|
136
|
+
font-size: 12px;
|
|
137
|
+
|
|
138
|
+
& + & {
|
|
139
|
+
border-top: 1px solid var(--td-component-border, #e7e7e7);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
&__status {
|
|
144
|
+
display: inline-flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 4px;
|
|
147
|
+
|
|
148
|
+
&--unmatched {
|
|
149
|
+
color: var(--td-error-color, #d54941);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&--pending {
|
|
153
|
+
color: var(--td-success-color, #2ba471);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
&--exists {
|
|
157
|
+
color: var(--td-text-color-secondary, #999);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
&__empty {
|
|
162
|
+
padding: 16px;
|
|
163
|
+
text-align: center;
|
|
164
|
+
color: var(--td-text-color-secondary, #999);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
/**
|
|
3
|
+
* 地区选项数据结构(统一格式)
|
|
4
|
+
*/
|
|
5
|
+
export interface RegionOption {
|
|
6
|
+
/** 地区代码(唯一标识,如 'CN'、'HK') */
|
|
7
|
+
region_code: string;
|
|
8
|
+
/** 中文名称 */
|
|
9
|
+
region_ch: string;
|
|
10
|
+
/** 英文名称 */
|
|
11
|
+
region_en: string;
|
|
12
|
+
/** ISO 3166-1 alpha-2 代码 */
|
|
13
|
+
region_a2_code?: string;
|
|
14
|
+
/** ISO 3166-1 alpha-3 代码 */
|
|
15
|
+
region_a3_code?: string;
|
|
16
|
+
/** 是否禁用(合规区域 creatable=false 时使用) */
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/** 禁用原因提示 */
|
|
19
|
+
disabledReason?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 数据源类型
|
|
23
|
+
* - default: 全部国家区域(不带任何参数,返回全部 251 个)/api/v5/business/region/query
|
|
24
|
+
* - business: 业务最终生效的区域(必须传 gameId)/api/v5/business/region/query
|
|
25
|
+
*/
|
|
26
|
+
export type RegionDataSource = 'default' | 'business';
|
|
27
|
+
/**
|
|
28
|
+
* 标签格式
|
|
29
|
+
* - name: 中文名
|
|
30
|
+
* - name_en: 英文名
|
|
31
|
+
* - name_en_name: 英文名(中文名)
|
|
32
|
+
* - code: 仅代码
|
|
33
|
+
*/
|
|
34
|
+
export type RegionLabelFormat = 'name' | 'name_en' | 'name_en_name' | 'code';
|
|
35
|
+
/**
|
|
36
|
+
* 组件文案集合
|
|
37
|
+
*/
|
|
38
|
+
export interface PntRegionSelectTexts {
|
|
39
|
+
/** 占位提示 */
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
/** 全选按钮 */
|
|
42
|
+
selectAll?: string;
|
|
43
|
+
/** 反选按钮 */
|
|
44
|
+
invertSelection?: string;
|
|
45
|
+
/** 清空按钮 */
|
|
46
|
+
clearAll?: string;
|
|
47
|
+
/** 已选数量提示({count} 为占位符) */
|
|
48
|
+
selectedCount?: string;
|
|
49
|
+
/** 搜索无结果提示 */
|
|
50
|
+
noResult?: string;
|
|
51
|
+
/** 加载中提示 */
|
|
52
|
+
loading?: string;
|
|
53
|
+
/** 批量导入按钮 */
|
|
54
|
+
importBtn?: string;
|
|
55
|
+
/** 批量导入弹窗标题 */
|
|
56
|
+
importTitle?: string;
|
|
57
|
+
/** 批量导入弹窗说明 */
|
|
58
|
+
importTip?: string;
|
|
59
|
+
/** 批量导入输入框占位 */
|
|
60
|
+
importPlaceholder?: string;
|
|
61
|
+
/** 解析按钮 */
|
|
62
|
+
parseBtn?: string;
|
|
63
|
+
/** 导入确认按钮 */
|
|
64
|
+
importConfirmBtn?: string;
|
|
65
|
+
/** 取消按钮 */
|
|
66
|
+
cancelBtn?: string;
|
|
67
|
+
/** 解析结果:未匹配 */
|
|
68
|
+
importUnmatched?: string;
|
|
69
|
+
/** 解析结果:待导入 */
|
|
70
|
+
importPending?: string;
|
|
71
|
+
/** 解析结果:已存在 */
|
|
72
|
+
importExists?: string;
|
|
73
|
+
/** 复制未匹配项提示 */
|
|
74
|
+
copyUnmatchedTip?: string;
|
|
75
|
+
/** 复制成功提示 */
|
|
76
|
+
copySuccess?: string;
|
|
77
|
+
/** 解析结果表格:地区列 */
|
|
78
|
+
importRegionColumn?: string;
|
|
79
|
+
/** 解析结果表格:状态列 */
|
|
80
|
+
importStatusColumn?: string;
|
|
81
|
+
}
|
|
82
|
+
export declare const DEFAULT_TEXTS: Required<PntRegionSelectTexts>;
|
|
83
|
+
/**
|
|
84
|
+
* 地区选择器组件 Props
|
|
85
|
+
*/
|
|
86
|
+
export interface PntRegionSelectProps {
|
|
87
|
+
/**
|
|
88
|
+
* 当前选中值:多选为地区代码数组,单选为单个地区代码
|
|
89
|
+
* (兼容 TDesign FormItem 注入的单值/多值)
|
|
90
|
+
*/
|
|
91
|
+
value?: string | string[];
|
|
92
|
+
/**
|
|
93
|
+
* 值变化回调:多选时回传 (string[], RegionOption[]),
|
|
94
|
+
* 单选时回传 (string, RegionOption)
|
|
95
|
+
*/
|
|
96
|
+
onChange?: (value: string[] | string, options: RegionOption[] | RegionOption) => void;
|
|
97
|
+
/** 是否多选 @default true */
|
|
98
|
+
multiple?: boolean;
|
|
99
|
+
/** 数据源类型 @default 'default' */
|
|
100
|
+
dataSource?: RegionDataSource;
|
|
101
|
+
/**
|
|
102
|
+
* 接口作用域 @default 'business'
|
|
103
|
+
* - business: 业务级区域查询 /api/v5/business/region/query
|
|
104
|
+
* - platform: 平台级区域查询 /api/v5/business/region/plat-query
|
|
105
|
+
*/
|
|
106
|
+
scope?: 'business' | 'platform';
|
|
107
|
+
/** 业务ID(dataSource='business' 时必传) */
|
|
108
|
+
gameId?: string | number;
|
|
109
|
+
/**
|
|
110
|
+
* 外部受控选项列表(传入后跳过内部请求,直接使用该数据;
|
|
111
|
+
* 适用于选项由父组件获取/过滤的场景)
|
|
112
|
+
*/
|
|
113
|
+
options?: RegionOption[];
|
|
114
|
+
/** 是否禁用 @default false */
|
|
115
|
+
disabled?: boolean;
|
|
116
|
+
/** 占位文案 */
|
|
117
|
+
placeholder?: string;
|
|
118
|
+
/** 最大可选数量(多选时生效) */
|
|
119
|
+
max?: number;
|
|
120
|
+
/** 是否可搜索 @default true */
|
|
121
|
+
filterable?: boolean;
|
|
122
|
+
/** 标签格式 @default 'name' */
|
|
123
|
+
labelFormat?: RegionLabelFormat;
|
|
124
|
+
/** 自定义文案 */
|
|
125
|
+
texts?: PntRegionSelectTexts;
|
|
126
|
+
/** 数据加载完成回调 */
|
|
127
|
+
onLoaded?: (options: RegionOption[]) => void;
|
|
128
|
+
/** 自定义选项禁用逻辑 */
|
|
129
|
+
optionDisabled?: (item: RegionOption) => boolean;
|
|
130
|
+
/** 自定义选项 tooltip */
|
|
131
|
+
optionTooltip?: (item: RegionOption) => string;
|
|
132
|
+
/**
|
|
133
|
+
* 是否启用批量导入(仅多选时生效):
|
|
134
|
+
* 输入框右侧显示导入按钮,支持粘贴文本按 代码/中文名/英文名 解析匹配
|
|
135
|
+
* @default false
|
|
136
|
+
*/
|
|
137
|
+
importable?: boolean;
|
|
138
|
+
/** 是否在工具栏显示反选按钮(仅多选时生效) @default false */
|
|
139
|
+
invertible?: boolean;
|
|
140
|
+
/** 最小折叠数量(多选时透传 TDesign Select) */
|
|
141
|
+
minCollapsedNum?: number;
|
|
142
|
+
/** 折叠项自定义渲染(透传 TDesign Select) */
|
|
143
|
+
collapsedItems?: (props: {
|
|
144
|
+
value: unknown[];
|
|
145
|
+
collapsedSelectedItems: unknown[];
|
|
146
|
+
count: number;
|
|
147
|
+
}) => React.ReactNode;
|
|
148
|
+
/** 失焦回调(透传 TDesign Select,常用于表单校验触发) */
|
|
149
|
+
onBlur?: (context: {
|
|
150
|
+
value: unknown;
|
|
151
|
+
}) => void;
|
|
152
|
+
/** 批量导入弹窗属性透传(如 attach 挂载容器) */
|
|
153
|
+
importDialogProps?: Record<string, unknown>;
|
|
154
|
+
/** 自定义类名 */
|
|
155
|
+
className?: string;
|
|
156
|
+
/** 自定义样式 */
|
|
157
|
+
style?: React.CSSProperties;
|
|
158
|
+
/** 弹层属性透传 */
|
|
159
|
+
popupProps?: Record<string, unknown>;
|
|
160
|
+
}
|