plain-design 1.0.0-beta.133 → 1.0.0-beta.135
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/dist/plain-design.commonjs.min.js +2 -18
- package/dist/plain-design.commonjs.min.js.LICENSE.txt +18 -0
- package/dist/plain-design.min.css +2 -1
- package/dist/plain-design.min.js +2 -18
- package/dist/plain-design.min.js.LICENSE.txt +18 -0
- package/dist/report.html +3 -3
- package/package.json +43 -40
- package/src/packages/components/$ai/index.tsx +214 -0
- package/src/packages/components/$configuration/index.tsx +2 -0
- package/src/packages/components/$file/index.tsx +43 -0
- package/src/packages/components/AiChatBox/ai-chat-box.scss +71 -0
- package/src/packages/components/AiChatBox/index.tsx +154 -0
- package/src/packages/components/AutoTable/auto-table.scss +4 -3
- package/src/packages/components/AutoTable/createTableOptionUser.tsx +3 -1
- package/src/packages/components/AutoTable/filter/useTableOption.filter.state.ts +4 -3
- package/src/packages/components/AutoTable/setting/useTableOption.setting.config.tsx +57 -15
- package/src/packages/components/AutoTable/setting/useTableOption.setting.export.tsx +2 -27
- package/src/packages/components/AutoTable/setting/useTableOption.setting.senior.filter.tsx +92 -92
- package/src/packages/components/AutoTable/setting/useTableOption.setting.senior.sort.tsx +14 -7
- package/src/packages/components/AutoTable/use/useTableOption.ai.tsx +485 -0
- package/src/packages/components/AutoTable/use/useTableOption.buttons.tsx +26 -16
- package/src/packages/components/AutoTable/use/useTableOption.methods.tsx +3 -0
- package/src/packages/components/AutoTable/use/useTableOption.sort.ts +4 -3
- package/src/packages/components/AutoTable/utils/AutoTable.utils.ts +75 -0
- package/src/packages/components/AutoTable/utils/TableOption.space.tsx +2 -1
- package/src/packages/components/FilterService/filter/filter.select.tsx +1 -0
- package/src/packages/components/Image/index.tsx +2 -2
- package/src/packages/components/ImageUploader/index.tsx +4 -4
- package/src/packages/components/PlcImage/index.tsx +5 -5
- package/src/packages/components/Table/editor/PlcSelect.tsx +1 -0
- package/src/packages/components/Table/table/use/useTableFormEditor.tsx +38 -34
- package/src/packages/components/Table/table/utils/table.utils.ts +1 -0
- package/src/packages/components/useDialog/DialogService.tsx +1 -1
- package/src/packages/entry.tsx +4 -0
- package/src/packages/i18n/lang/en-us.ts +23 -14
- package/src/packages/i18n/lang/zh-cn.ts +10 -1
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import {AutoModule} from "../utils/AutoModule";
|
|
2
|
+
import i18n from "../../../i18n";
|
|
3
|
+
import Tooltip from "../../Tooltip";
|
|
4
|
+
import Button from "../../Button";
|
|
5
|
+
import {iAutoTableAiConfig, iTableAiOperate, iTableAiOperateConfig, iTableAiOperatePlcMeta, iTableAiOperateSearch, iTableAiOperateSort} from "../utils/AutoTable.utils";
|
|
6
|
+
import $configuration from "../../$configuration";
|
|
7
|
+
import $dialog from "../../$dialog";
|
|
8
|
+
import AiChatBox, {iChatBoxHistory} from "../../AiChatBox";
|
|
9
|
+
import {createSimpleDate} from "../../createSimpleDate";
|
|
10
|
+
import {iTableOptionSeniorFilterMeta} from "../setting/useTableOption.setting.senior.filter";
|
|
11
|
+
import {iFilterFormSingleData} from "../../FilterFormSingle";
|
|
12
|
+
import {PlainObject} from "@peryl/utils/event";
|
|
13
|
+
import {FilterService} from "../../FilterService";
|
|
14
|
+
import {mergeQueryParam} from "../../FilterService/utils/mergeQueryParam";
|
|
15
|
+
import {eAutoTableFilterMode} from "../filter/useTableOption.filter.state";
|
|
16
|
+
import $notice from "../../$notice";
|
|
17
|
+
import {toArray} from "@peryl/utils/toArray";
|
|
18
|
+
import {iFilterConfigSelect} from "../../FilterService/filter/filter.select";
|
|
19
|
+
import {uuid} from "@peryl/utils/uuid";
|
|
20
|
+
import {eAddressTypeEnum} from "../../$address/address.utils";
|
|
21
|
+
import Alert from "../../Alert";
|
|
22
|
+
import {$ai, iAiConfiguration} from "../../$ai";
|
|
23
|
+
|
|
24
|
+
declare module '../utils/TableOption.space' {
|
|
25
|
+
namespace TableOptionSpace {
|
|
26
|
+
interface iTableOption {
|
|
27
|
+
ai: ReturnType<typeof useTableOptionAi>;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const useTableOptionAi = AutoModule.createRegistration((option) => {
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 创建filterOptions的引用,直接用高级筛选的
|
|
36
|
+
* @author 韦胜健
|
|
37
|
+
* @date 2025/4/6 23:31
|
|
38
|
+
*/
|
|
39
|
+
const filterOptions = option.filterState.createComputedFilterOptions(eAutoTableFilterMode.filterSenior);
|
|
40
|
+
|
|
41
|
+
const chatHistories: iChatBoxHistory[] = [];
|
|
42
|
+
|
|
43
|
+
const methods = {
|
|
44
|
+
/**
|
|
45
|
+
* 获取可用的字段
|
|
46
|
+
* @author 韦胜健
|
|
47
|
+
* @date 2025/4/6 23:31
|
|
48
|
+
*/
|
|
49
|
+
getAvailableFields: async (): Promise<iTableAiOperatePlcMeta[]> => {
|
|
50
|
+
const availableFields: iTableAiOperatePlcMeta[] = [];
|
|
51
|
+
const plcData = await option.methods.getPlcData();
|
|
52
|
+
plcData.flatPlcList.forEach((plc, index) => {
|
|
53
|
+
const { title, field, filterType, width, fixed, hide, align } = plc.props;
|
|
54
|
+
if (!!field && !!title) {
|
|
55
|
+
availableFields.push({
|
|
56
|
+
field: field || "",
|
|
57
|
+
title,
|
|
58
|
+
order: index,
|
|
59
|
+
align: align || "left",
|
|
60
|
+
width,
|
|
61
|
+
fixed,
|
|
62
|
+
hide,
|
|
63
|
+
|
|
64
|
+
filterType,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return availableFields;
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* 打开AI对话弹窗
|
|
72
|
+
* @author 韦胜健
|
|
73
|
+
* @date 2025/4/6 23:31
|
|
74
|
+
*/
|
|
75
|
+
openChat: async () => {
|
|
76
|
+
|
|
77
|
+
const availableFields = await methods.getAvailableFields();
|
|
78
|
+
console.log({ availableFields });
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 对话提示词
|
|
82
|
+
* @author 韦胜健
|
|
83
|
+
* @date 2025/4/6 23:31
|
|
84
|
+
*/
|
|
85
|
+
const systemContent = `
|
|
86
|
+
|
|
87
|
+
当前时间是:${createSimpleDate()["YYYY-MM-DD HH:mm:ss"]}
|
|
88
|
+
你是一名专业的自然语言分析师,你需要将用户对一个表格的需求描述转化为结构化的json数据;
|
|
89
|
+
|
|
90
|
+
这个json数据的结构如下 {type:"",data:""},type表示用户对表格的操作类型,data为分析出来的数据,type有四种类型:
|
|
91
|
+
第1个是"search",表明用户需求是搜索,也就是对数据进行筛选;
|
|
92
|
+
第2个是"sort",表明用户需求是排序;
|
|
93
|
+
第3个是"config",表明用户需求是配置表格;
|
|
94
|
+
第4个是"error",表明用户的需求不支持,此时data属性值为不支持的原因;
|
|
95
|
+
|
|
96
|
+
你只能返回给我这个json数据,用户只能对表格进行搜索,排序,配置表格,其他的需求一律拒绝;也就是返回 {type:"error",data:""},记住data要给出拒绝的原因;
|
|
97
|
+
如果用户同时存在两种及以上需求,比如同时要搜索以及排序,那么你返回的json数据应该是一个数组,比如 [{type:"search",data:""},{type:"sort",data:""}];
|
|
98
|
+
|
|
99
|
+
我会以JSON数组的方式给你表格中支持操作的字段,这个数组中每一个元素的结构为字段类型结构,结构如下所示:{title, field, filterType, width, order, fixed, hide, align},以下为结构中的每个属性说明:
|
|
100
|
+
title:字段的标题;
|
|
101
|
+
field:字段的唯一标识;
|
|
102
|
+
filterType:字段的筛选类型;
|
|
103
|
+
width:字段的宽度;
|
|
104
|
+
order:字段的顺序;
|
|
105
|
+
fixed:字段的冻结方式,如果是左冻结/左固定,那么值为left;如果是右冻结/右固定,那么值为right;
|
|
106
|
+
hide:字段是否隐藏,为布尔值;
|
|
107
|
+
align:字段的对齐方式,左对齐的话是left,居中对齐的话是center,右对齐的话是right;
|
|
108
|
+
|
|
109
|
+
表格中目前支持的字段:${JSON.stringify(availableFields)}
|
|
110
|
+
|
|
111
|
+
如果用户要配置的字段不存在,则拒绝并且给出拒绝原因;
|
|
112
|
+
|
|
113
|
+
如果用户操作为搜索或者说筛选,那么返回的结构数据中的data应该是一个对象,结构为{filters:[],filterExpression:""},
|
|
114
|
+
结构中的filters为数组,数组中的每个元素结构如下所示:{id,field,filterHandler,value},以下对元素结构中每个属性做解释:
|
|
115
|
+
id: 唯一标识,需要你生成,按照 FF_1, FF_2, FF_3 的递增规则生成;
|
|
116
|
+
field: 要搜索的字段的唯一标识;
|
|
117
|
+
filterHandler: 搜索方式;
|
|
118
|
+
如果用户要求字段等于某个值,此时filterHandler为"eq";
|
|
119
|
+
如果用户要求字段约等于某个值,此时filterHandler为"like";
|
|
120
|
+
如果用户要求字段匹配多个值,当字段的filterType既不是text也不是number,那么返回的filterHandler为"in";否则filterHandler为"in_like";
|
|
121
|
+
如果用户要求字段不包含或者排除某些值,那么此时如果字段的filterType为(text或者number)的话filterHandler为"not_in_like",否则filterHandler为"not_in";;
|
|
122
|
+
如果用户要求字段值为空,则filterHandler为"is_null";
|
|
123
|
+
如果用户要求字段值不为空,则filterHandler为"is_not_null";
|
|
124
|
+
如果用户要求字段处于某个范围,比如大于或者小于某个日期,比如大于或者小于某个数字,那么此时filterHandler为"range";
|
|
125
|
+
value: 搜索的值;
|
|
126
|
+
如果用户对这个字段的搜索只有一个值,那么value应该是一个字符串;
|
|
127
|
+
如果用户对这个字段的搜索包含多个值或者用了包含,不包含,排除之类的字眼,此时value应该是json数组;
|
|
128
|
+
如果用户对这个字段的要求是一个范围,比如大于某个值小于某个值等等,那么value应该是一个对象{start,end},start为开始时间或者最小值,end为结束时间或者最大值;
|
|
129
|
+
当字段要求是一个范围时,
|
|
130
|
+
如果这个字段的filterType为date,那么value为日期字符串,格式为YYYY-MM-DD;
|
|
131
|
+
如果这个字段的filterType为datetime,那么value为日期时间字符串,格式为YYYY-MM-DD HH:mm:ss;如果用户仅给出了日期没有给出时间,那么给一个默认的时间;如果是开始日期时间,这个时间的默认值为00:00:00;如果是结束日期时间,这个时间的默认值为23:59:59
|
|
132
|
+
|
|
133
|
+
data数据中的filterExpression为表达式,如果用户的搜索条件都是"并且"关系,那么filterHandler为空字符串;如果用户的搜索条件存在"或"关系,那么需要生成这个表达式字符串,表达式中用筛选条件的id来代替表达式;
|
|
134
|
+
比如用户要求搜索性别为男并且状态为在职的员工,你会给我返回两条数据[{id:'FF_1',field:'gender',filterHandler:'=',value:'男'},{id:'FF_2',field:'status',filterHandler:'=',value:'在职'}],此时filterExpression为空字符串;
|
|
135
|
+
如果用户要求是搜索性别为男或者状态在职的员工,你除了要给我返回这两条数据之外,返回的filterExpression应该为"FF_1 or FF_2";filterExpression表达式中的筛选条件如果是并且关系用and代替,如果是或者关系用or代替,如果用户需求带有小括号,那么需要保留小括号;
|
|
136
|
+
|
|
137
|
+
如果用户操作为排序,那么返回的结构数据中的data应该是一个数组,数组中每个元素结果如下所示:{field,desc,title},以下对元素结构中每个属性做解释:
|
|
138
|
+
field:要搜索的字段的唯一标识;
|
|
139
|
+
desc:如果降序或者从大到小,那么值为true;如果升序或者从小到大模大,那么值为false;
|
|
140
|
+
title: 字段的标题;
|
|
141
|
+
|
|
142
|
+
如果用户操作为配置列表字段,那么返回数据的data为数组,每一个元素为字段类型结构;比如有一个字段,其标题为"商品名称",其字段标识为"prodName",如果用户要隐藏这个字段,那么返回的字段类型结构为 {field:"prodName", hide:true}
|
|
143
|
+
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
// console.log(systemContent);
|
|
147
|
+
$dialog({
|
|
148
|
+
title: i18n.$it('table.aiButton').d('AI对话'),
|
|
149
|
+
position: 'right',
|
|
150
|
+
noFoot: true,
|
|
151
|
+
width: '500px',
|
|
152
|
+
render: () => (
|
|
153
|
+
<AiChatBox
|
|
154
|
+
histories={chatHistories}
|
|
155
|
+
fitHeight
|
|
156
|
+
systemContent={systemContent}
|
|
157
|
+
handleMessage={handleMessage}
|
|
158
|
+
placeholder={i18n.$it('ai.tableAiPlaceholder').d('你可以与ai对话实现对列表的查询、排序以及配置功能;比如:查询编号约等于129,创建时间大于前天的数据,先按照创建时间降序,再按照更新时间升序排序;最后是调宽编号字段的宽度')}
|
|
159
|
+
v-slots={{
|
|
160
|
+
prepend: () => <>
|
|
161
|
+
<Alert icon={null} size="mini">
|
|
162
|
+
<div>1. 在列表中与AI对话你可以实现搜索、排序、配置等功能,以下为例:</div>
|
|
163
|
+
<div>2. 搜索搜索[字段1]为[值1]并且(这里也可以用或者)[字段2]为[值2],匹配的值可以是多个值;</div>
|
|
164
|
+
<div>3. 先按照[字段1]降序再按照[字段2]升序排序</div>
|
|
165
|
+
<div>4. 将[字段1]的宽度加宽一倍,将[字段2]放在[字段3]前面,[字段4]左固定</div>
|
|
166
|
+
<div>5. 可以在一次对话中同时处理搜索、排序以及配置功能;</div>
|
|
167
|
+
</Alert>
|
|
168
|
+
</>
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
)
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const handleMessage = async ({ message }: { message: string }): Promise<iChatBoxHistory | iChatBoxHistory[] | void> => {
|
|
175
|
+
try {
|
|
176
|
+
const operateItems: iTableAiOperate[] = toArray(JSON.parse(message));
|
|
177
|
+
if (operateItems.length == 1 && operateItems[0].type === 'error') {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
await methods.apply(operateItems);
|
|
181
|
+
const workTitleMap = { search: '查询', sort: '排序', config: '配置', error: '' };
|
|
182
|
+
return [
|
|
183
|
+
{ id: uuid(), role: 'assistant', content: message },
|
|
184
|
+
{ id: uuid(), role: 'assistant', content: `已经为您执行 ${operateItems.map(i => workTitleMap[i.type]).join(',')} 工作` }
|
|
185
|
+
];
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.error(e);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
/**
|
|
193
|
+
* 应用大模型返回的结果
|
|
194
|
+
* @author 韦胜健
|
|
195
|
+
* @date 2025/4/6 23:32
|
|
196
|
+
*/
|
|
197
|
+
apply: async (operateItems: iTableAiOperate[]) => {
|
|
198
|
+
|
|
199
|
+
if (!filterOptions.value) {
|
|
200
|
+
const errorMessage = 'filterOptions.value is unavailable!';
|
|
201
|
+
$notice.error(errorMessage);
|
|
202
|
+
throw new Error(errorMessage);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const availableFields = await methods.getAvailableFields();
|
|
206
|
+
|
|
207
|
+
/*---------------------------------------处理搜索-------------------------------------------*/
|
|
208
|
+
|
|
209
|
+
let hasSearch = false;
|
|
210
|
+
const searchData: iTableAiOperateSearch['data'] | undefined = operateItems.find(i => i.type === "search")?.data;
|
|
211
|
+
if (!!searchData) {
|
|
212
|
+
|
|
213
|
+
hasSearch = true;
|
|
214
|
+
|
|
215
|
+
/*构建查询表单对象用来存储字段的值*/
|
|
216
|
+
const searchFormData = searchData.filters.reduce((prev, item) => {
|
|
217
|
+
prev[item.field] = item.value;
|
|
218
|
+
return prev;
|
|
219
|
+
}, {} as PlainObject);
|
|
220
|
+
|
|
221
|
+
console.log({ searchFormData });
|
|
222
|
+
|
|
223
|
+
/*构建新的 iTableOptionSeniorFilterMeta 存储到高级筛选的状态中*/
|
|
224
|
+
option.settingSeniorFilter.edit.state.metas = await Promise.all(searchData.filters.map(async (f): Promise<iTableOptionSeniorFilterMeta> => {
|
|
225
|
+
|
|
226
|
+
/*要筛选的字段的plc props*/
|
|
227
|
+
const plcProps = availableFields.find(i => i.field === f.field);
|
|
228
|
+
|
|
229
|
+
if (!plcProps) {
|
|
230
|
+
const errorMessage = `can't find plc: ${f.field}`;
|
|
231
|
+
$notice.error(errorMessage);
|
|
232
|
+
throw new Error(errorMessage);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/*要筛选字段的 filterOption*/
|
|
236
|
+
let usingFilterOption = filterOptions.value!.find(i => i.field === f.field);
|
|
237
|
+
if (!usingFilterOption) {
|
|
238
|
+
const errorMessage = `can't recognise field ${f.field}`;
|
|
239
|
+
$notice.error(errorMessage);
|
|
240
|
+
throw new Error(errorMessage);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
usingFilterOption = { ...usingFilterOption };
|
|
244
|
+
usingFilterOption.filterHandler = f.filterHandler;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 由于大模型返回对字段的筛选值都是显示值,某些情况下需要对这个显示值转化为实际值,才能用来进行搜索,以下做列举
|
|
248
|
+
* - 当筛选类型filterType为select时,无论filterHandler为任何值得情况下都要转化显示值为实际值;
|
|
249
|
+
* - 当筛选类型filterType为province/city/district时:
|
|
250
|
+
* - 如果filterHandler为like,此时不需要将显示值转化为实际值,因为like这个filterHandler会在生成queryParam阶段将显示值转化;
|
|
251
|
+
* - 如果filterHandler为其他情况,都要将显示值转化为实际值;
|
|
252
|
+
* @author 韦胜健
|
|
253
|
+
* @date 2025/4/6 23:34
|
|
254
|
+
*/
|
|
255
|
+
await (async () => {
|
|
256
|
+
if (plcProps.filterType === 'select') {
|
|
257
|
+
|
|
258
|
+
const selectOptions = (usingFilterOption.filterConfig as iFilterConfigSelect).getSelectOptions();
|
|
259
|
+
|
|
260
|
+
const showValueList = toArray(f.value);
|
|
261
|
+
const resultValueList: string[] = [];
|
|
262
|
+
showValueList.forEach(showValue => {
|
|
263
|
+
const matchOption = selectOptions.find(i => i.label === showValue);
|
|
264
|
+
if (!!matchOption) {resultValueList.push(matchOption.val);}
|
|
265
|
+
});
|
|
266
|
+
searchFormData[f.field] = resultValueList.length == 0 ? undefined : f.filterHandler == 'eq' ? resultValueList[0] : resultValueList;
|
|
267
|
+
} else if (
|
|
268
|
+
(plcProps.filterType === 'date' ||
|
|
269
|
+
plcProps.filterType === 'datetime' ||
|
|
270
|
+
plcProps.filterType === 'number')
|
|
271
|
+
&& f.filterHandler === 'range'
|
|
272
|
+
) {
|
|
273
|
+
searchFormData[usingFilterOption.startField] = f.value.start;
|
|
274
|
+
searchFormData[usingFilterOption.endField] = f.value.end;
|
|
275
|
+
} else if (
|
|
276
|
+
plcProps.filterType === 'province' ||
|
|
277
|
+
plcProps.filterType === 'city' ||
|
|
278
|
+
plcProps.filterType === 'district'
|
|
279
|
+
) {
|
|
280
|
+
const addressService = $configuration.get('address')!;
|
|
281
|
+
if (f.filterHandler !== 'like') {
|
|
282
|
+
const showValueList = toArray(f.value);
|
|
283
|
+
const resultValueList: string[] = [];
|
|
284
|
+
await Promise.all(showValueList.map(async (showValue) => {
|
|
285
|
+
const metas = await addressService.getAddressByLabel(showValue,
|
|
286
|
+
plcProps.filterType == 'province' ? eAddressTypeEnum.province :
|
|
287
|
+
plcProps.filterType == 'city' ? eAddressTypeEnum.city :
|
|
288
|
+
eAddressTypeEnum.district
|
|
289
|
+
);
|
|
290
|
+
metas.forEach(i => resultValueList.push(i.code));
|
|
291
|
+
}));
|
|
292
|
+
// console.log({ showValueList, resultValueList });
|
|
293
|
+
searchFormData[f.field] = resultValueList.length == 0 ? undefined : f.filterHandler == 'eq' ? resultValueList[0] : resultValueList;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
})();
|
|
297
|
+
|
|
298
|
+
const queryParam = await FilterService.getQueryParam({ option: usingFilterOption, formData: searchFormData });
|
|
299
|
+
const filterQueryParam = !queryParam ? {} : mergeQueryParam(queryParam, {});
|
|
300
|
+
|
|
301
|
+
const fsd: iFilterFormSingleData = {
|
|
302
|
+
field: f.field,
|
|
303
|
+
filterHandler: usingFilterOption.filterHandler,
|
|
304
|
+
formData: searchFormData,
|
|
305
|
+
filterQueryParam,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
id: f.id,
|
|
310
|
+
data: fsd,
|
|
311
|
+
};
|
|
312
|
+
}));
|
|
313
|
+
|
|
314
|
+
option.settingSeniorFilter.edit.state.expression = searchData.filterExpression || searchData.filters.map(i => i.id).join(' and ');
|
|
315
|
+
option.settingSeniorFilter.edit.state.isCustomExpression = true;
|
|
316
|
+
|
|
317
|
+
// console.log(deepcopy(option.settingSeniorFilter.edit.state));
|
|
318
|
+
await option.settingSeniorFilter.query.apply(false);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/*---------------------------------------处理排序-------------------------------------------*/
|
|
322
|
+
let hasSort = false;
|
|
323
|
+
const sortData: iTableAiOperateSort['data'] | undefined = operateItems.find(i => i.type === "sort")?.data;
|
|
324
|
+
if (!!sortData) {
|
|
325
|
+
hasSort = true;
|
|
326
|
+
option.settingSeniorSort.methods.update(sortData, false);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/*---------------------------------------处理配置-------------------------------------------*/
|
|
330
|
+
let hasConfig = false;
|
|
331
|
+
const configData: iTableAiOperateConfig['data'] | undefined = operateItems.find(i => i.type === "config")?.data;
|
|
332
|
+
if (!!configData) {
|
|
333
|
+
hasConfig = true;
|
|
334
|
+
await option.settingConfig.methods.updateItemPlc({
|
|
335
|
+
customData: configData,
|
|
336
|
+
customSort: configData.length === availableFields.length,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/*---------------------------------------收尾-------------------------------------------*/
|
|
340
|
+
if (hasSearch || hasSort) {await option.methods.pageMethods.reload();}
|
|
341
|
+
},
|
|
342
|
+
/**
|
|
343
|
+
* 通过ai获取已经解析并且转化过的表单数据
|
|
344
|
+
* @author 韦胜健
|
|
345
|
+
* @date 2025/4/8 11:02
|
|
346
|
+
*/
|
|
347
|
+
getFormDataByChat: async () => {
|
|
348
|
+
const availableFields = await methods.getAvailableFields();
|
|
349
|
+
const formDataList = await $ai.getFormDataByChat(availableFields);
|
|
350
|
+
const addressService = $configuration.get('address')!;
|
|
351
|
+
|
|
352
|
+
await Promise.all(formDataList.map(async (formDataItem) => {
|
|
353
|
+
await Promise.all(availableFields.map(async f => {
|
|
354
|
+
|
|
355
|
+
const showValue = formDataItem[f.field];
|
|
356
|
+
|
|
357
|
+
if (showValue == null) {return;}
|
|
358
|
+
|
|
359
|
+
if (f.filterType === 'select') {
|
|
360
|
+
let usingFilterOption = filterOptions.value!.find(i => i.field === f.field);
|
|
361
|
+
if (!usingFilterOption) {
|
|
362
|
+
console.error(`Can't convert select field: ${f.field}`);
|
|
363
|
+
formDataItem[f.field] = null;
|
|
364
|
+
} else {
|
|
365
|
+
const selectOptions = (usingFilterOption.filterConfig as iFilterConfigSelect).getSelectOptions();
|
|
366
|
+
const convertShowValue2Value = (showValue: any) => selectOptions.find(i => i.label == showValue)?.val;
|
|
367
|
+
formDataItem[f.field] = Array.isArray(showValue) ? showValue.map(i => convertShowValue2Value(i)) : convertShowValue2Value(showValue);
|
|
368
|
+
}
|
|
369
|
+
} else if (
|
|
370
|
+
f.filterType === 'province' ||
|
|
371
|
+
f.filterType === 'city' ||
|
|
372
|
+
f.filterType === 'district'
|
|
373
|
+
) {
|
|
374
|
+
const metas = await addressService.getAddressByLabel(showValue,
|
|
375
|
+
f.filterType == 'province' ? eAddressTypeEnum.province :
|
|
376
|
+
f.filterType == 'city' ? eAddressTypeEnum.city :
|
|
377
|
+
eAddressTypeEnum.district
|
|
378
|
+
);
|
|
379
|
+
formDataItem[f.field] = metas[0].code;
|
|
380
|
+
}
|
|
381
|
+
}));
|
|
382
|
+
}));
|
|
383
|
+
|
|
384
|
+
return formDataList;
|
|
385
|
+
},
|
|
386
|
+
/**
|
|
387
|
+
* ai对话批量新建
|
|
388
|
+
* @author 韦胜健
|
|
389
|
+
* @date 2025/4/8 10:51
|
|
390
|
+
*/
|
|
391
|
+
aiInsert: async () => {
|
|
392
|
+
const formDataList = await methods.getFormDataByChat();
|
|
393
|
+
console.log(formDataList);
|
|
394
|
+
await option.methods.editMethods.batchInsert({ rows: formDataList.reverse() });
|
|
395
|
+
},
|
|
396
|
+
/**
|
|
397
|
+
* 智能填写表单
|
|
398
|
+
* @author 韦胜健
|
|
399
|
+
* @date 2025/4/8 11:29
|
|
400
|
+
*/
|
|
401
|
+
smartFilling: async (formData: PlainObject) => {
|
|
402
|
+
const formDataList = await methods.getFormDataByChat();
|
|
403
|
+
Object.assign(formData, formDataList[0]);
|
|
404
|
+
},
|
|
405
|
+
testSearch: async () => {
|
|
406
|
+
const operateItems: iTableAiOperate[] = [
|
|
407
|
+
{
|
|
408
|
+
"type": "search",
|
|
409
|
+
"data": {
|
|
410
|
+
"filters": [
|
|
411
|
+
{ "id": "FF_1", "field": "provinceVal", "filterHandler": "in", "value": ["山东省", "上海市"] },
|
|
412
|
+
{ "id": "FF_2", "field": "ovVal", "filterHandler": "eq", "value": "秒杀折扣" }
|
|
413
|
+
],
|
|
414
|
+
"filterExpression": ""
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"type": "sort",
|
|
419
|
+
"data": [
|
|
420
|
+
{ "field": "count", "desc": true, title: '计数' },
|
|
421
|
+
{ "field": "numberVal", "desc": false, title: '数字' }
|
|
422
|
+
]
|
|
423
|
+
},
|
|
424
|
+
];
|
|
425
|
+
await methods.apply(operateItems);
|
|
426
|
+
},
|
|
427
|
+
testSearch2: async () => {
|
|
428
|
+
const operateItems: iTableAiOperate[] = [{ "type": "search", "data": { "filters": [{ "id": "FF_1", "field": "normalText", "filterHandler": "not_in", "value": ["hello"] }], "filterExpression": "" } }];
|
|
429
|
+
await methods.apply(operateItems);
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const autoOptionAiConfig: iAutoTableAiConfig =
|
|
434
|
+
/*如果没有设置config.ai,则默认全部开启*/
|
|
435
|
+
option.config.ai == null ? { search: true, sort: true, config: true } :
|
|
436
|
+
/*设置为false则全部关闭*/
|
|
437
|
+
option.config.ai === false ? { search: false, sort: false, config: false } : {
|
|
438
|
+
/*只要不设置为false,就是默认开启*/
|
|
439
|
+
|
|
440
|
+
search: option.config.ai.search !== false,
|
|
441
|
+
sort: option.config.ai.sort !== false,
|
|
442
|
+
config: option.config.ai.config !== false,
|
|
443
|
+
...option.config.ai,
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const aiConfig: iAiConfiguration | undefined = autoOptionAiConfig.aiConfig || $configuration.get('aiConfig');
|
|
447
|
+
|
|
448
|
+
const ret = { methods, aiConfig };
|
|
449
|
+
|
|
450
|
+
if ((autoOptionAiConfig.search ||
|
|
451
|
+
autoOptionAiConfig.sort ||
|
|
452
|
+
autoOptionAiConfig.config)) {
|
|
453
|
+
if (!aiConfig) {
|
|
454
|
+
console.error("AutoTable lacks the AutoOption.aiConfig or the general configuration $configuration.aiConfig when the intelligent search/sorting/configuration is enabled.");
|
|
455
|
+
} else {
|
|
456
|
+
/**
|
|
457
|
+
* 渲染左侧操作栏时,渲染一个按钮用来打开AI对话框
|
|
458
|
+
* @author 韦胜健
|
|
459
|
+
* @date 2023.1.8 19:13
|
|
460
|
+
*/
|
|
461
|
+
option.hooks.onRenderLeftOperation.use({
|
|
462
|
+
processor: ({ list, render }) => {list.push({ key: 'ai-chat-button', seq: 2, render });},
|
|
463
|
+
render: () => (
|
|
464
|
+
<>
|
|
465
|
+
<Tooltip message={i18n.$it('table.aiButton').d('AI对话')}><Button icon="pi-message" onClick={methods.openChat}/></Tooltip>
|
|
466
|
+
{/*<Tooltip message="测试搜索"><Button icon="pi-bug" onClick={methods.testSearch}/></Tooltip>*/}
|
|
467
|
+
{/*<Tooltip message="测试搜索2"><Button icon="pi-bug" onClick={methods.testSearch2}/></Tooltip>*/}
|
|
468
|
+
{/*<Tooltip message="测试配置"><Button icon="pi-bug" onClick={methods.testSearch}/></Tooltip>*/}
|
|
469
|
+
{/*<Tooltip message="测试智能填充表单"><Button icon="pi-bug" onClick={methods.aiInsert}/></Tooltip>*/}
|
|
470
|
+
</>
|
|
471
|
+
),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
option.hooks.onBaseTableAttrs.use((attrs) => {
|
|
477
|
+
if (!!aiConfig) {
|
|
478
|
+
attrs.formEditorFootRender = (formData) => (
|
|
479
|
+
<Tooltip message={i18n.$it('ai.smartFilling').d('智能填写')}><Button style={{ order: 20 }} icon="pi-message" onClick={() => methods.smartFilling(formData)}/></Tooltip>
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
return ret;
|
|
485
|
+
});
|
|
@@ -15,8 +15,9 @@ declare module '../utils/TableOption.space' {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
export const useTableOptionButtons = AutoModule.createRegistration((
|
|
19
|
-
|
|
18
|
+
export const useTableOptionButtons = AutoModule.createRegistration((option) => {
|
|
19
|
+
|
|
20
|
+
const {
|
|
20
21
|
state,
|
|
21
22
|
config,
|
|
22
23
|
hooks,
|
|
@@ -24,8 +25,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
24
25
|
setting,
|
|
25
26
|
keyboard,
|
|
26
27
|
permission,
|
|
27
|
-
}
|
|
28
|
-
) => {
|
|
28
|
+
} = option;
|
|
29
29
|
|
|
30
30
|
const standardButtons: iTableOperation[] = [
|
|
31
31
|
{
|
|
@@ -79,7 +79,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
79
79
|
{
|
|
80
80
|
label: i18n.$it('table.formEdit').d('表单编辑'),
|
|
81
81
|
position: 'outer',
|
|
82
|
-
dropdown:true,
|
|
82
|
+
dropdown: true,
|
|
83
83
|
type: 'update',
|
|
84
84
|
code: 'outer-update',
|
|
85
85
|
icon: 'pi-edit',
|
|
@@ -89,17 +89,27 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
89
89
|
{
|
|
90
90
|
label: i18n.$it('table.batchCreate').d('批量新建'),
|
|
91
91
|
position: 'outer',
|
|
92
|
-
dropdown:true,
|
|
92
|
+
dropdown: true,
|
|
93
93
|
type: 'insert',
|
|
94
94
|
code: 'outer-batch-insert',
|
|
95
95
|
icon: 'pi-plus',
|
|
96
96
|
keyboard: 'alt+i',
|
|
97
97
|
handler: () => methods.editMethods.batchInsert(),
|
|
98
98
|
},
|
|
99
|
+
{
|
|
100
|
+
label: i18n.$it('table.aiCreate').d('智能新建'),
|
|
101
|
+
position: 'outer',
|
|
102
|
+
dropdown: true,
|
|
103
|
+
type: 'insert',
|
|
104
|
+
code: 'outer-ai-insert',
|
|
105
|
+
icon: 'pi-robot-add',
|
|
106
|
+
keyboard: 'alt+c',
|
|
107
|
+
handler: () => option.ai.methods.aiInsert(),
|
|
108
|
+
},
|
|
99
109
|
{
|
|
100
110
|
label: i18n.$it('table.batchEdit').d('批量编辑'),
|
|
101
111
|
position: 'outer',
|
|
102
|
-
dropdown:true,
|
|
112
|
+
dropdown: true,
|
|
103
113
|
type: 'update',
|
|
104
114
|
code: 'outer-batch-update',
|
|
105
115
|
icon: 'pi-save',
|
|
@@ -109,7 +119,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
109
119
|
{
|
|
110
120
|
label: i18n.$it('table.batchDelete').d('批量删除'),
|
|
111
121
|
position: 'outer',
|
|
112
|
-
dropdown:true,
|
|
122
|
+
dropdown: true,
|
|
113
123
|
type: 'delete',
|
|
114
124
|
code: 'outer-batch-delete',
|
|
115
125
|
icon: 'pi-eraser',
|
|
@@ -119,7 +129,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
119
129
|
{
|
|
120
130
|
label: i18n.$it('table.batchModify').d('批量修改'),
|
|
121
131
|
position: 'outer',
|
|
122
|
-
dropdown:true,
|
|
132
|
+
dropdown: true,
|
|
123
133
|
type: 'update',
|
|
124
134
|
code: 'outer-batch-modify',
|
|
125
135
|
icon: 'pi-mind-mapping',
|
|
@@ -129,7 +139,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
129
139
|
{
|
|
130
140
|
label: i18n.$it('table.allFilter').d('所有筛选'),
|
|
131
141
|
position: 'outer',
|
|
132
|
-
dropdown:true,
|
|
142
|
+
dropdown: true,
|
|
133
143
|
type: 'other',
|
|
134
144
|
code: 'all-filters',
|
|
135
145
|
icon: 'pi-filter',
|
|
@@ -139,7 +149,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
139
149
|
{
|
|
140
150
|
label: i18n.$it('table.seniorFilter').d('高级筛选'),
|
|
141
151
|
position: 'outer',
|
|
142
|
-
dropdown:true,
|
|
152
|
+
dropdown: true,
|
|
143
153
|
type: 'other',
|
|
144
154
|
code: 'senior-filters',
|
|
145
155
|
icon: 'pi-find-replace',
|
|
@@ -149,7 +159,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
149
159
|
{
|
|
150
160
|
label: i18n.$it('table.seniorSort').d('高级排序'),
|
|
151
161
|
position: 'outer',
|
|
152
|
-
dropdown:true,
|
|
162
|
+
dropdown: true,
|
|
153
163
|
type: 'other',
|
|
154
164
|
code: 'senior-sort',
|
|
155
165
|
icon: 'pi-sort-ascending',
|
|
@@ -159,7 +169,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
159
169
|
{
|
|
160
170
|
label: i18n.$it('table.customSetting').d('个性设置'),
|
|
161
171
|
position: 'outer',
|
|
162
|
-
dropdown:true,
|
|
172
|
+
dropdown: true,
|
|
163
173
|
type: 'other',
|
|
164
174
|
code: 'custom-setting',
|
|
165
175
|
icon: 'pi-settings',
|
|
@@ -169,7 +179,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
169
179
|
{
|
|
170
180
|
label: i18n.$it('table.cacheSetting').d('缓存设置'),
|
|
171
181
|
position: 'outer',
|
|
172
|
-
dropdown:true,
|
|
182
|
+
dropdown: true,
|
|
173
183
|
type: 'other',
|
|
174
184
|
code: 'cache-setting',
|
|
175
185
|
icon: 'pi-layers',
|
|
@@ -179,7 +189,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
179
189
|
{
|
|
180
190
|
label: i18n.$it('table.importData').d('导入数据'),
|
|
181
191
|
position: 'outer',
|
|
182
|
-
dropdown:true,
|
|
192
|
+
dropdown: true,
|
|
183
193
|
type: 'other',
|
|
184
194
|
code: 'import',
|
|
185
195
|
icon: 'pi-import',
|
|
@@ -189,7 +199,7 @@ export const useTableOptionButtons = AutoModule.createRegistration((
|
|
|
189
199
|
{
|
|
190
200
|
label: i18n.$it('table.exportData').d('导出数据'),
|
|
191
201
|
position: 'outer',
|
|
192
|
-
dropdown:true,
|
|
202
|
+
dropdown: true,
|
|
193
203
|
type: 'other',
|
|
194
204
|
code: 'export',
|
|
195
205
|
icon: 'pi-export',
|
|
@@ -18,6 +18,7 @@ import '../utils/TableOption.space';
|
|
|
18
18
|
import {eTableOptionEditType} from "../utils/TableOption.utils";
|
|
19
19
|
import i18n from "../../i18n";
|
|
20
20
|
import {AutoModule} from "../utils/AutoModule";
|
|
21
|
+
import {delay} from "@peryl/utils/delay";
|
|
21
22
|
|
|
22
23
|
declare module '../utils/TableOption.space' {
|
|
23
24
|
namespace TableOptionSpace {
|
|
@@ -340,6 +341,8 @@ export const useTableOptionMethods = AutoModule.createRegistration((
|
|
|
340
341
|
return new Array(num).fill(null).map(() => deepcopy((!config.defaultNewRow ? {} : (typeof config.defaultNewRow === "function" ? config.defaultNewRow() : config.defaultNewRow))));
|
|
341
342
|
})();
|
|
342
343
|
if (!confirm.checkEditable(eTableOptionStatus.insert)) {await confirm.close.confirm();}
|
|
344
|
+
state.editingWhenAddRow = true;
|
|
345
|
+
await delay(23);
|
|
343
346
|
newRows.forEach(newRow => insert(newRow));
|
|
344
347
|
};
|
|
345
348
|
|
|
@@ -70,10 +70,10 @@ export const useTableOptionSortState = AutoModule.createRegistration((
|
|
|
70
70
|
methods.pageMethods.reload();
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
const useSenior = (sortMetas: iTableOptionSortMeta[]) => {
|
|
73
|
+
const useSenior = (sortMetas: iTableOptionSortMeta[], autoReload: boolean) => {
|
|
74
74
|
state.data.sortMode = eTableOptionSortMode.senior;
|
|
75
75
|
state.data.seniorParam = sortMetas.map(({ field, desc }) => ({ field, desc }));
|
|
76
|
-
methods.pageMethods.reload();
|
|
76
|
+
!!autoReload && methods.pageMethods.reload();
|
|
77
77
|
};
|
|
78
78
|
|
|
79
79
|
filterState.useState({
|
|
@@ -88,7 +88,8 @@ export const useTableOptionSortState = AutoModule.createRegistration((
|
|
|
88
88
|
state.data.sortMode = eTableOptionSortMode.single;
|
|
89
89
|
state.data.singleParam = null;
|
|
90
90
|
state.data.seniorParam = [];
|
|
91
|
-
|
|
91
|
+
/*这里不能自动刷新,会导致调用全部清空的时候重复查询*/
|
|
92
|
+
// methods.pageMethods.reload();
|
|
92
93
|
},
|
|
93
94
|
});
|
|
94
95
|
|